author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Mon, 01 Jun 2015 15:24:09 +0200 | |
changeset 246496 | a29d6aa8a5a74c19d60fa679a47e2455241fb9b8 |
parent 246495 | a44a427b81e3fc7158521c753c6ad0e77742a047 (current diff) |
parent 246484 | 39c85ec2d64487a2a4226d53d9bde41716e78351 (diff) |
child 246497 | ee70ffd05d29ed02655a3c8bbb515eb3f37bb3c6 |
push id | 28833 |
push user | kwierso@gmail.com |
push date | Mon, 01 Jun 2015 19:19:37 +0000 |
treeherder | mozilla-central@a2d68ee6628c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 41.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/accessible/atk/AccessibleWrap.cpp +++ b/accessible/atk/AccessibleWrap.cpp @@ -795,17 +795,21 @@ getParentCB(AtkObject *aAtkObj) return aAtkObj->accessible_parent; AtkObject* atkParent = nullptr; if (AccessibleWrap* wrapper = GetAccessibleWrap(aAtkObj)) { Accessible* parent = wrapper->Parent(); atkParent = parent ? AccessibleWrap::GetAtkObject(parent) : nullptr; } else if (ProxyAccessible* proxy = GetProxy(aAtkObj)) { ProxyAccessible* parent = proxy->Parent(); - atkParent = parent ? GetWrapperFor(parent) : nullptr; + if (parent) + atkParent = GetWrapperFor(parent); + + // Otherwise this should be the proxy for the tab's top level document. + atkParent = AccessibleWrap::GetAtkObject(proxy->OuterDocOfRemoteBrowser()); } if (atkParent) atk_object_set_parent(aAtkObj, atkParent); return aAtkObj->accessible_parent; } @@ -854,16 +858,19 @@ gint getIndexInParentCB(AtkObject* aAtkObj) { // We don't use Accessible::IndexInParent() because we don't include text // leaf nodes as children in ATK. if (ProxyAccessible* proxy = GetProxy(aAtkObj)) { if (ProxyAccessible* parent = proxy->Parent()) return parent->IndexOfEmbeddedChild(proxy); + if (proxy->OuterDocOfRemoteBrowser()) + return 0; + return -1; } AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj); if (!accWrap) { return -1; }
--- a/accessible/ipc/ProxyAccessible.cpp +++ b/accessible/ipc/ProxyAccessible.cpp @@ -1,16 +1,20 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ProxyAccessible.h" #include "DocAccessibleParent.h" +#include "DocAccessible.h" +#include "mozilla/a11y/DocManager.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/TabParent.h" #include "mozilla/unused.h" #include "mozilla/a11y/Platform.h" #include "RelationType.h" #include "mozilla/a11y/Role.h" namespace mozilla { namespace a11y { @@ -958,10 +962,24 @@ ProxyAccessible::MimeType(nsString aMime void ProxyAccessible::URLDocTypeMimeType(nsString& aURL, nsString& aDocType, nsString& aMimeType) { unused << mDoc->SendURLDocTypeMimeType(mID, &aURL, &aDocType, &aMimeType); } +Accessible* +ProxyAccessible::OuterDocOfRemoteBrowser() const +{ + auto tab = static_cast<dom::TabParent*>(mDoc->Manager()); + dom::Element* frame = tab->GetOwnerElement(); + NS_ASSERTION(frame, "why isn't the tab in a frame!"); + if (!frame) + return nullptr; + + DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc()); + NS_ASSERTION(chromeDoc, "accessible tab in not accessible chromeDocument"); + + return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr; } } +}
--- a/accessible/ipc/ProxyAccessible.h +++ b/accessible/ipc/ProxyAccessible.h @@ -13,16 +13,17 @@ #include "nsString.h" #include "nsTArray.h" #include "nsRect.h" #include "Accessible.h" namespace mozilla { namespace a11y { +class Accessible; class Attribute; class DocAccessibleParent; enum class RelationType; class ProxyAccessible { public: @@ -60,16 +61,18 @@ public: void RemoveChild(ProxyAccessible* aChild) { mChildren.RemoveElement(aChild); } /** * Return the proxy for the parent of the wrapped accessible. */ ProxyAccessible* Parent() const { return mParent; } + Accessible* OuterDocOfRemoteBrowser() const; + /** * Get the role of the accessible we're proxying. */ role Role() const { return mRole; } /* * Return the states for the proxied accessible. */
--- a/accessible/mac/AccessibleWrap.h +++ b/accessible/mac/AccessibleWrap.h @@ -54,20 +54,17 @@ public: // construction, destruction virtual nsresult HandleAccEvent(AccEvent* aEvent) override; /** * Ignored means that the accessible might still have children, but is not * displayed to the user. it also has no native accessible object represented * for it. */ bool IsIgnored(); - - inline bool HasPopup () - { return (NativeState() & mozilla::a11y::states::HASPOPUP); } - + /** * Returns this accessible's all children, adhering to "flat" accessibles by * not returning their children. */ void GetUnignoredChildren(nsTArray<Accessible*>* aChildrenArray); Accessible* GetUnignoredParent() const; protected: @@ -103,12 +100,14 @@ private: /** * We have created our native. This does not mean there is one. * This can never go back to false. * We need it because checking whether we need a native object cost time. */ bool mNativeInited; }; +Class GetTypeFromRole(roles::Role aRole); + } // namespace a11y } // namespace mozilla #endif
--- a/accessible/mac/AccessibleWrap.mm +++ b/accessible/mac/AccessibleWrap.mm @@ -10,16 +10,17 @@ #include "nsAccUtils.h" #include "Role.h" #import "mozAccessible.h" #import "mozActionElements.h" #import "mozHTMLAccessible.h" #import "mozTextAccessible.h" +using namespace mozilla; using namespace mozilla::a11y; AccessibleWrap:: AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc) : Accessible(aContent, aDoc), mNativeObject(nil), mNativeInited(false) { } @@ -28,18 +29,20 @@ AccessibleWrap::~AccessibleWrap() { } mozAccessible* AccessibleWrap::GetNativeObject() { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - if (!mNativeInited && !mNativeObject && !IsDefunct() && !AncestorIsFlat()) - mNativeObject = [[GetNativeType() alloc] initWithAccessible:this]; + if (!mNativeInited && !mNativeObject && !IsDefunct() && !AncestorIsFlat()) { + uintptr_t accWrap = reinterpret_cast<uintptr_t>(this); + mNativeObject = [[GetNativeType() alloc] initWithAccessible:accWrap]; + } mNativeInited = true; return mNativeObject; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } @@ -54,61 +57,17 @@ AccessibleWrap::GetNativeInterface(void* Class AccessibleWrap::GetNativeType () { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; if (IsXULTabpanels()) return [mozPaneAccessible class]; - roles::Role role = Role(); - switch (role) { - case roles::PUSHBUTTON: - case roles::SPLITBUTTON: - case roles::TOGGLE_BUTTON: - { - // if this button may show a popup, let's make it of the popupbutton type. - return HasPopup() ? [mozPopupButtonAccessible class] : - [mozButtonAccessible class]; - } - - case roles::PAGETAB: - return [mozButtonAccessible class]; - - case roles::CHECKBUTTON: - return [mozCheckboxAccessible class]; - - case roles::HEADING: - return [mozHeadingAccessible class]; - - case roles::PAGETABLIST: - return [mozTabsAccessible class]; - - case roles::ENTRY: - case roles::STATICTEXT: - case roles::CAPTION: - case roles::ACCEL_LABEL: - case roles::PASSWORD_TEXT: - // normal textfield (static or editable) - return [mozTextAccessible class]; - - case roles::TEXT_LEAF: - return [mozTextLeafAccessible class]; - - case roles::LINK: - return [mozLinkAccessible class]; - - case roles::COMBOBOX: - return [mozPopupButtonAccessible class]; - - default: - return [mozAccessible class]; - } - - return nil; + return GetTypeFromRole(Role()); NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } // this method is very important. it is fired when an accessible object "dies". after this point // the object might still be around (because some 3rd party still has a ref to it), but it is // in fact 'dead'. void @@ -269,8 +228,57 @@ AccessibleWrap::AncestorIsFlat() if (nsAccUtils::MustPrune(parent)) return true; parent = parent->Parent(); } // no parent was flat return false; } + +Class +a11y::GetTypeFromRole(roles::Role aRole) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + switch (aRole) { + case roles::COMBOBOX: + case roles::PUSHBUTTON: + case roles::SPLITBUTTON: + case roles::TOGGLE_BUTTON: + { + return [mozButtonAccessible class]; + } + + case roles::PAGETAB: + return [mozButtonAccessible class]; + + case roles::CHECKBUTTON: + return [mozCheckboxAccessible class]; + + case roles::HEADING: + return [mozHeadingAccessible class]; + + case roles::PAGETABLIST: + return [mozTabsAccessible class]; + + case roles::ENTRY: + case roles::STATICTEXT: + case roles::CAPTION: + case roles::ACCEL_LABEL: + case roles::PASSWORD_TEXT: + // normal textfield (static or editable) + return [mozTextAccessible class]; + + case roles::TEXT_LEAF: + return [mozTextLeafAccessible class]; + + case roles::LINK: + return [mozLinkAccessible class]; + + default: + return [mozAccessible class]; + } + + return nil; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +}
--- a/accessible/mac/Platform.mm +++ b/accessible/mac/Platform.mm @@ -2,16 +2,17 @@ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #import <Cocoa/Cocoa.h> #include "Platform.h" +#include "ProxyAccessible.h" #include "nsAppShell.h" namespace mozilla { namespace a11y { // Mac a11y whitelisting static bool sA11yShouldBeEnabled = false; @@ -29,23 +30,33 @@ PlatformInit() } void PlatformShutdown() { } void -ProxyCreated(ProxyAccessible*, uint32_t) +ProxyCreated(ProxyAccessible* aProxy, uint32_t) { + // Pass in dummy state for now as retrieving proxy state requires IPC. + Class type = GetTypeFromRole(aProxy->Role()); + uintptr_t accWrap = reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY; + mozAccessible* mozWrapper = [[type alloc] initWithAccessible:accWrap]; + aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper)); } void -ProxyDestroyed(ProxyAccessible*) +ProxyDestroyed(ProxyAccessible* aProxy) { + mozAccessible* wrapper = + reinterpret_cast<mozAccessible*>(aProxy->GetWrapper()); + [wrapper expire]; + [wrapper release]; + aProxy->SetWrapper(0); } void ProxyEvent(ProxyAccessible*, uint32_t) { } void
--- a/accessible/mac/moz.build +++ b/accessible/mac/moz.build @@ -25,16 +25,17 @@ UNIFIED_SOURCES += [ 'Platform.mm', 'RootAccessibleWrap.mm', ] LOCAL_INCLUDES += [ '/accessible/base', '/accessible/generic', '/accessible/html', + '/accessible/ipc', '/accessible/xul', '/layout/generic', '/layout/xul', '/widget', '/widget/cocoa', ] FINAL_LIBRARY = 'xul'
--- a/accessible/mac/mozAccessible.h +++ b/accessible/mac/mozAccessible.h @@ -52,21 +52,24 @@ static const uintptr_t IS_PROXY = 1; mozAccessible* mParent; /** * The role of our gecko accessible. */ mozilla::a11y::role mRole; } -// return the Accessible for this mozAccessible. -- (mozilla::a11y::AccessibleWrap*) getGeckoAccessible; +// return the Accessible for this mozAccessible if it exists. +- (mozilla::a11y::AccessibleWrap*)getGeckoAccessible; + +// return the ProxyAccessible for this mozAccessible if it exists. +- (mozilla::a11y::ProxyAccessible*)getProxyAccessible; // inits with the gecko owner. -- (id)initWithAccessible:(mozilla::a11y::AccessibleWrap*)geckoParent; +- (id)initWithAccessible:(uintptr_t)aGeckoObj; // our accessible parent (AXParent) - (id <mozAccessible>)parent; // a lazy cache of our accessible children (AXChildren). updated - (NSArray*)children; // returns the size of this accessible.
--- a/accessible/mac/mozAccessible.mm +++ b/accessible/mac/mozAccessible.mm @@ -55,25 +55,28 @@ GetClosestInterestingAccessible(id anObj NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } #pragma mark - @implementation mozAccessible -- (id)initWithAccessible:(AccessibleWrap*)geckoAccessible +- (id)initWithAccessible:(uintptr_t)aGeckoAccessible { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; if ((self = [super init])) { - mGeckoAccessible = reinterpret_cast<uintptr_t>(geckoAccessible); - mRole = geckoAccessible->Role(); + mGeckoAccessible = aGeckoAccessible; + if (aGeckoAccessible & IS_PROXY) + mRole = [self getProxyAccessible]->Role(); + else + mRole = [self getGeckoAccessible]->Role(); } - + return self; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (void)dealloc { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; @@ -87,16 +90,25 @@ GetClosestInterestingAccessible(id anObj - (mozilla::a11y::AccessibleWrap*)getGeckoAccessible { // Check if mGeckoAccessible points at a proxy if (mGeckoAccessible & IS_PROXY) return nil; return reinterpret_cast<AccessibleWrap*>(mGeckoAccessible); } + +- (mozilla::a11y::ProxyAccessible*)getProxyAccessible +{ + // Check if mGeckoAccessible points at a proxy + if (!(mGeckoAccessible & IS_PROXY)) + return nil; + + return reinterpret_cast<ProxyAccessible*>(mGeckoAccessible & ~IS_PROXY); +} #pragma mark - - (BOOL)accessibilityIsIgnored { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; // unknown (either unimplemented, or irrelevant) elements are marked as ignored
--- a/accessible/mac/mozActionElements.h +++ b/accessible/mac/mozActionElements.h @@ -4,29 +4,28 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #import <Cocoa/Cocoa.h> #import "mozAccessible.h" /* Simple subclasses for things like checkboxes, buttons, etc. */ @interface mozButtonAccessible : mozAccessible + { + } +- (BOOL)hasPopup; - (void)click; - (BOOL)isTab; @end @interface mozCheckboxAccessible : mozButtonAccessible // returns one of the constants defined in CheckboxValue - (int)isChecked; @end -/* Used for buttons that may pop up a menu. */ -@interface mozPopupButtonAccessible : mozButtonAccessible -@end - /* Class for tabs - not individual tabs */ @interface mozTabsAccessible : mozAccessible { NSMutableArray* mTabs; } -(id)tabs; @end
--- a/accessible/mac/mozActionElements.mm +++ b/accessible/mac/mozActionElements.mm @@ -37,84 +37,102 @@ enum CheckboxValue { NSAccessibilitySizeAttribute, // required NSAccessibilityWindowAttribute, // required NSAccessibilityPositionAttribute, // required NSAccessibilityTopLevelUIElementAttribute, // required NSAccessibilityHelpAttribute, NSAccessibilityEnabledAttribute, // required NSAccessibilityFocusedAttribute, // required NSAccessibilityTitleAttribute, // required + NSAccessibilityChildrenAttribute, NSAccessibilityDescriptionAttribute, #if DEBUG @"AXMozDescription", #endif nil]; } return attributes; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (id)accessibilityAttributeValue:(NSString *)attribute { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) + if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { + if ([self hasPopup]) + return [self children]; return nil; + } + if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { if ([self isTab]) return utils::LocalizedString(NS_LITERAL_STRING("tab")); - + return NSAccessibilityRoleDescription([self role], nil); } - + return [super accessibilityAttributeValue:attribute]; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } - (BOOL)accessibilityIsIgnored { return ![self getGeckoAccessible]; } - (NSArray*)accessibilityActionNames { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - if ([self isEnabled]) + if ([self isEnabled]) { + if ([self hasPopup]) + return [NSArray arrayWithObjects:NSAccessibilityPressAction, + NSAccessibilityShowMenuAction, + nil]; return [NSArray arrayWithObject:NSAccessibilityPressAction]; - + } return nil; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } -- (NSString*)accessibilityActionDescription:(NSString*)action +- (NSString*)accessibilityActionDescription:(NSString*)action { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; if ([action isEqualToString:NSAccessibilityPressAction]) { if ([self isTab]) return utils::LocalizedString(NS_LITERAL_STRING("switch")); - + return @"press button"; // XXX: localize this later? } - + + if ([self hasPopup]) { + if ([action isEqualToString:NSAccessibilityShowMenuAction]) + return @"show menu"; + } + return nil; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } -- (void)accessibilityPerformAction:(NSString*)action +- (void)accessibilityPerformAction:(NSString*)action { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - if ([action isEqualToString:NSAccessibilityPressAction]) + if ([self isEnabled] && [action isEqualToString:NSAccessibilityPressAction]) { + // TODO: this should bring up the menu, but currently doesn't. + // once msaa and atk have merged better, they will implement + // the action needed to show the menu. [self click]; + } NS_OBJC_END_TRY_ABORT_BLOCK; } - (void)click { // both buttons and checkboxes have only one action. we should really stop using arbitrary // arrays with actions, and define constants for these actions. @@ -122,16 +140,22 @@ enum CheckboxValue { } - (BOOL)isTab { AccessibleWrap* accWrap = [self getGeckoAccessible]; return (accWrap && (accWrap->Role() == roles::PAGETAB)); } +- (BOOL)hasPopup +{ + AccessibleWrap* accWrap = [self getGeckoAccessible]; + return accWrap && (accWrap->NativeState() & mozilla::a11y::states::HASPOPUP); +} + @end @implementation mozCheckboxAccessible - (NSString*)accessibilityActionDescription:(NSString*)action { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; @@ -165,101 +189,16 @@ enum CheckboxValue { return [NSNumber numberWithInt:[self isChecked]]; NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } @end -@implementation mozPopupButtonAccessible - -- (NSArray *)accessibilityAttributeNames -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - static NSArray *attributes = nil; - - if (!attributes) { - attributes = [[NSArray alloc] initWithObjects:NSAccessibilityParentAttribute, // required - NSAccessibilityPositionAttribute, // required - NSAccessibilityRoleAttribute, // required - NSAccessibilitySizeAttribute, // required - NSAccessibilityWindowAttribute, // required - NSAccessibilityTopLevelUIElementAttribute, // required - NSAccessibilityHelpAttribute, - NSAccessibilityEnabledAttribute, // required - NSAccessibilityFocusedAttribute, // required - NSAccessibilityTitleAttribute, // required for popupmenus, and for menubuttons with a title - NSAccessibilityChildrenAttribute, // required - NSAccessibilityDescriptionAttribute, // required if it has no title attr -#if DEBUG - @"AXMozDescription", -#endif - nil]; - } - return attributes; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -- (id)accessibilityAttributeValue:(NSString *)attribute -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { - return [super children]; - } - return [super accessibilityAttributeValue:attribute]; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -- (NSArray *)accessibilityActionNames -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - if ([self isEnabled]) { - return [NSArray arrayWithObjects:NSAccessibilityPressAction, - NSAccessibilityShowMenuAction, - nil]; - } - return nil; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -- (NSString *)accessibilityActionDescription:(NSString *)action -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; - - if ([action isEqualToString:NSAccessibilityShowMenuAction]) - return @"show menu"; - return [super accessibilityActionDescription:action]; - - NS_OBJC_END_TRY_ABORT_BLOCK_NIL; -} - -- (void)accessibilityPerformAction:(NSString *)action -{ - NS_OBJC_BEGIN_TRY_ABORT_BLOCK; - - // both the ShowMenu and Click action do the same thing. - if ([self isEnabled]) { - // TODO: this should bring up the menu, but currently doesn't. - // once msaa and atk have merged better, they will implement - // the action needed to show the menu. - [super click]; - } - - NS_OBJC_END_TRY_ABORT_BLOCK; -} - -@end - @implementation mozTabsAccessible - (void)dealloc { [mTabs release]; [super dealloc]; }
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -267,16 +267,17 @@ pref("general.smoothScroll", true); pref("general.autoScroll", false); #else pref("general.autoScroll", true); #endif // At startup, check if we're the default browser and prompt user if not. pref("browser.shell.checkDefaultBrowser", true); pref("browser.shell.shortcutFavicons",true); +pref("browser.shell.isSetAsDefaultBrowser", false); // 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session // The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore pref("browser.startup.page", 1); pref("browser.startup.homepage", "chrome://branding/locale/browserconfig.properties"); pref("browser.slowStartup.notificationDisabled", false); pref("browser.slowStartup.timeThreshold", 40000);
--- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -43,17 +43,17 @@ } searchbar { -moz-binding: url("chrome://browser/content/search/search.xml#searchbar"); } /* Prevent shrinking the page content to 0 height and width */ .browserStack > browser { - min-height: 25px; + min-height: 100px; min-width: 25px; } .browserStack > browser { -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-browser"); } .browserStack > browser[remote="true"] {
--- a/browser/components/customizableui/test/browser.ini +++ b/browser/components/customizableui/test/browser.ini @@ -40,17 +40,19 @@ skip-if = os == "linux" [browser_914138_widget_API_overflowable_toolbar.js] skip-if = os == "linux" [browser_914863_disabled_help_quit_buttons.js] [browser_918049_skipintoolbarset_dnd.js] [browser_923857_customize_mode_event_wrapping_during_reset.js] [browser_927717_customize_drag_empty_toolbar.js] -[browser_932928_show_notice_when_palette_empty.js] + +# Bug 1163231 - Causes failures on Developer Edition on Windows 7. +# [browser_932928_show_notice_when_palette_empty.js] [browser_934113_menubar_removable.js] # Because this test is about the menubar, it can't be run on mac skip-if = os == "mac" [browser_946320_tabs_from_other_computers.js] skip-if = os == "linux"
--- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -1108,16 +1108,18 @@ BrowserGlue.prototype = { try { // Report default browser status on startup to telemetry // so we can track whether we are the default. Services.telemetry.getHistogramById("BROWSER_IS_USER_DEFAULT") .add(isDefault); } catch (ex) { /* Don't break the default prompt if telemetry is broken. */ } + Services.setBoolPref("browser.shell.isSetAsDefaultBrowser", isDefault); + if (shouldCheck && !isDefault && !willRecoverSession) { Services.tm.mainThread.dispatch(function() { DefaultBrowserCheck.prompt(RecentWindow.getMostRecentBrowserWindow()); }.bind(this), Ci.nsIThread.DISPATCH_NORMAL); } } #ifdef E10S_TESTING_ONLY
--- a/browser/devtools/debugger/test/browser.ini +++ b/browser/devtools/debugger/test/browser.ini @@ -199,16 +199,18 @@ skip-if = e10s && debug [browser_dbg_conditional-breakpoints-03.js] skip-if = e10s && debug [browser_dbg_conditional-breakpoints-04.js] skip-if = e10s && debug [browser_dbg_conditional-breakpoints-05.js] skip-if = e10s && debug [browser_dbg_console-eval.js] skip-if = e10s && debug +[browser_dbg_console-named-eval.js] +skip-if = e10s && debug [browser_dbg_server-conditional-bp-01.js] skip-if = e10s && debug [browser_dbg_server-conditional-bp-02.js] skip-if = e10s && debug [browser_dbg_server-conditional-bp-03.js] skip-if = e10s && debug [browser_dbg_server-conditional-bp-04.js] skip-if = e10s && debug
new file mode 100644 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_console-named-eval.js @@ -0,0 +1,40 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Breaking in the middle of a named eval script created by the + * console should work + */ + +function test() { + Task.spawn(runTests); +} + +function* runTests() { + let TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html"; + let [,, panel] = yield initDebugger(TAB_URL); + let dbgWin = panel.panelWin; + let sources = dbgWin.DebuggerView.Sources; + let frames = dbgWin.DebuggerView.StackFrames; + let editor = dbgWin.DebuggerView.editor; + let toolbox = gDevTools.getToolbox(panel.target); + + let paused = waitForSourceAndCaretAndScopes(panel, "foo.js", 1); + + toolbox.once("webconsole-ready", () => { + ok(toolbox.splitConsole, "Split console is shown."); + let jsterm = toolbox.getPanel("webconsole").hud.jsterm; + jsterm.execute("eval('debugger; //# sourceURL=foo.js')"); + }); + EventUtils.synthesizeKey("VK_ESCAPE", {}, dbgWin); + + yield paused; + is(sources.selectedItem.attachment.label, 'foo.js', + 'New source is selected in sources'); + is(sources.selectedItem.attachment.group, 'http://example.com', + 'New source is in the right group'); + ok(editor.getText() === 'debugger; //# sourceURL=foo.js', 'Editor has correct text'); + + yield toolbox.closeSplitConsole(); + yield resumeDebuggerThenCloseAndFinish(panel); +}
--- a/browser/devtools/framework/test/browser_toolbox_hosts_size.js +++ b/browser/devtools/framework/test/browser_toolbox_hosts_size.js @@ -15,27 +15,28 @@ add_task(function*() { // to be clamped to fit into the browser window. Services.prefs.setIntPref("devtools.toolbox.footer.height", 10000); Services.prefs.setIntPref("devtools.toolbox.sidebar.width", 10000); let tab = yield addTab(URL); let nbox = gBrowser.getNotificationBox(); let {clientHeight: nboxHeight, clientWidth: nboxWidth} = nbox; let toolbox = yield gDevTools.showToolbox(TargetFactory.forTab(tab)); + let {MIN_PAGE_SIZE} = devtools.require("devtools/framework/toolbox-hosts"); is (nbox.clientHeight, nboxHeight, "Opening the toolbox hasn't changed the height of the nbox"); is (nbox.clientWidth, nboxWidth, "Opening the toolbox hasn't changed the width of the nbox"); let iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-bottom-iframe"); - is (iframe.clientHeight, nboxHeight - 25, "The iframe fits within the available space"); + is (iframe.clientHeight, nboxHeight - MIN_PAGE_SIZE, "The iframe fits within the available space"); yield toolbox.switchHost(devtools.Toolbox.HostType.SIDE); iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-side-iframe"); iframe.style.minWidth = "1px"; // Disable the min width set in css - is (iframe.clientWidth, nboxWidth - 25, "The iframe fits within the available space"); + is (iframe.clientWidth, nboxWidth - MIN_PAGE_SIZE, "The iframe fits within the available space"); yield cleanup(toolbox); }); add_task(function*() { // Set size prefs to something reasonable, so we can check to make sure // they are being set properly. Services.prefs.setIntPref("devtools.toolbox.footer.height", 100);
--- a/browser/devtools/framework/toolbox-hosts.js +++ b/browser/devtools/framework/toolbox-hosts.js @@ -9,17 +9,17 @@ const EventEmitter = require("devtools/t const {Promise: promise} = require("resource://gre/modules/Promise.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource:///modules/devtools/DOMHelpers.jsm"); /* A host should always allow this much space for the page to be displayed. * There is also a min-height on the browser, but we still don't want to set * frame.height to be larger than that, since it can cause problems with * resizing the toolbox and panel layout. */ -const MIN_PAGE_SIZE = 25; +const MIN_PAGE_SIZE = exports.MIN_PAGE_SIZE = 100; /** * A toolbox host represents an object that contains a toolbox (e.g. the * sidebar or a separate window). Any host object should implement the * following functions: * * create() - create the UI and emit a 'ready' event when the UI is ready to use * destroy() - destroy the host's UI
--- a/browser/devtools/performance/modules/global.js +++ b/browser/devtools/performance/modules/global.js @@ -1,19 +1,15 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -const { Cc, Ci, Cu, Cr } = require("chrome"); const { ViewHelpers } = require("resource:///modules/devtools/ViewHelpers.jsm"); -// String used to fill in platform data when it should be hidden. -const GECKO_SYMBOL = "(Gecko)"; - /** * Localization convenience methods. + TODO: merge these into a single file: Bug 1082695. */ const L10N = new ViewHelpers.MultiL10N([ "chrome://browser/locale/devtools/timeline.properties", "chrome://browser/locale/devtools/profiler.properties" ]); @@ -83,317 +79,16 @@ const CATEGORY_MAPPINGS = { "256": CATEGORIES[3], // js::ProfileEntry::Category::CC "512": CATEGORIES[4], // js::ProfileEntry::Category::NETWORK "1024": CATEGORIES[5], // js::ProfileEntry::Category::GRAPHICS "2048": CATEGORIES[6], // js::ProfileEntry::Category::STORAGE "4096": CATEGORIES[7], // js::ProfileEntry::Category::EVENTS }; /** - * A simple schema for mapping markers to the timeline UI. The keys correspond - * to marker names, while the values are objects with the following format: - * - * - group: The row index in the timeline overview graph; multiple markers - * can be added on the same row. @see <overview.js/buildGraphImage> - * - label: The label used in the waterfall to identify the marker. Can be a - * string or just a function that accepts the marker and returns a - * string, if you want to use a dynamic property for the main label. - * If you use a function for a label, it *must* handle the case where - * no marker is provided for a main label to describe all markers of - * this type. - * - colorName: The label of the DevTools color used for this marker. If - * adding a new color, be sure to check that there's an entry - * for `.marker-details-bullet.{COLORNAME}` for the equivilent - * entry in ./browser/themes/shared/devtools/performance.inc.css - * https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors - * - collapseFunc: A function determining how markers are collapsed together. - * Invoked with 3 arguments: the current parent marker, the - * current marker and a method for peeking i markers ahead. If - * nothing is returned, the marker is added as a standalone entry - * in the waterfall. Otherwise, an object needs to be returned - * with the following properties: - * - toParent: The parent marker name (needs to be an entry in - * the `TIMELINE_BLUEPRINT` itself). - * - withData: An object containing some properties to staple - * on the parent marker. - * - forceNew: True if a new parent marker needs to be created - * even though there is one currently available - * with the same name. - * - forceEnd: True if the current parent marker is full after - * this collapse operation and should be finalized. - * - fields: An optional array of marker properties you wish to display in the - * marker details view. For example, a field in the array such as - * { property: "aCauseName", label: "Cause" } would render a string - * like `Cause: ${marker.aCauseName}` in the marker details view. - * Each `field` item may take the following properties: - * - property: The property that must exist on the marker to render, - * and the value of the property will be displayed. - * - label: The name of the property that should be displayed. - * - formatter: If a formatter is provided, instead of directly using - * the `property` property on the marker, the marker is - * passed into the formatter function to determine the - * displayed value. - * Can also be a function that returns an object. Each key in the object - * will be rendered as a field, with its value rendering as the value. - * - * Whenever this is changed, browser_timeline_waterfall-styles.js *must* be - * updated as well. - */ -const TIMELINE_BLUEPRINT = { - /* Group 0 - Reflow and Rendering pipeline */ - "Styles": { - group: 0, - colorName: "graphs-purple", - collapseFunc: collapseConsecutiveIdentical, - label: L10N.getStr("timeline.label.styles2"), - fields: getStylesFields, - }, - "Reflow": { - group: 0, - colorName: "graphs-purple", - collapseFunc: collapseConsecutiveIdentical, - label: L10N.getStr("timeline.label.reflow2"), - }, - "Paint": { - group: 0, - colorName: "graphs-green", - collapseFunc: collapseConsecutiveIdentical, - label: L10N.getStr("timeline.label.paint"), - }, - - /* Group 1 - JS */ - "DOMEvent": { - group: 1, - colorName: "graphs-yellow", - collapseFunc: collapseDOMIntoDOMJS, - label: L10N.getStr("timeline.label.domevent"), - fields: getDOMEventFields, - }, - "Javascript": { - group: 1, - colorName: "graphs-yellow", - collapseFunc: either(collapseJSIntoDOMJS, collapseConsecutiveIdentical), - label: getJSLabel, - fields: getJSFields, - }, - "meta::DOMEvent+JS": { - colorName: "graphs-yellow", - label: getDOMJSLabel, - fields: getDOMEventFields, - }, - "Parse HTML": { - group: 1, - colorName: "graphs-yellow", - collapseFunc: collapseConsecutiveIdentical, - label: L10N.getStr("timeline.label.parseHTML"), - }, - "Parse XML": { - group: 1, - colorName: "graphs-yellow", - collapseFunc: collapseConsecutiveIdentical, - label: L10N.getStr("timeline.label.parseXML"), - }, - "GarbageCollection": { - group: 1, - colorName: "graphs-red", - collapseFunc: collapseAdjacentGC, - label: getGCLabel, - fields: [ - { property: "causeName", label: "Reason:" }, - { property: "nonincrementalReason", label: "Non-incremental Reason:" } - ], - }, - - /* Group 2 - User Controlled */ - "ConsoleTime": { - group: 2, - colorName: "graphs-grey", - label: sublabelForProperty(L10N.getStr("timeline.label.consoleTime"), "causeName"), - fields: [{ - property: "causeName", - label: L10N.getStr("timeline.markerDetail.consoleTimerName") - }], - }, - "TimeStamp": { - group: 2, - colorName: "graphs-blue", - label: sublabelForProperty(L10N.getStr("timeline.label.timestamp"), "causeName"), - fields: [{ - property: "causeName", - label: "Label:" - }], - }, -}; - -/** - * Helper for creating a function that returns the first defined result from - * a list of functions passed in as params, in order. - * @param ...function fun - * @return any - */ -function either(...fun) { - return function() { - for (let f of fun) { - let result = f.apply(null, arguments); - if (result !== undefined) return result; - } - } -} - -/** - * A series of collapsers used by the blueprint. These functions are - * consecutively invoked on a moving window of two markers. - */ - -function collapseConsecutiveIdentical(parent, curr, peek) { - // If there is a parent marker currently being filled and the current marker - // should go into the parent marker, make it so. - if (parent && parent.name == curr.name) { - return { toParent: parent.name }; - } - // Otherwise if the current marker is the same type as the next marker type, - // create a new parent marker containing the current marker. - let next = peek(1); - if (next && curr.name == next.name) { - return { toParent: curr.name }; - } -} - -function collapseAdjacentGC(parent, curr, peek) { - let next = peek(1); - if (next && (next.start < curr.end || next.start - curr.end <= 10 /* ms */)) { - return collapseConsecutiveIdentical(parent, curr, peek); - } -} - -function collapseDOMIntoDOMJS(parent, curr, peek) { - // If the next marker is a JavaScript marker, create a new meta parent marker - // containing the current marker. - let next = peek(1); - if (next && next.name == "Javascript") { - return { - forceNew: true, - toParent: "meta::DOMEvent+JS", - withData: { - type: curr.type, - eventPhase: curr.eventPhase - }, - }; - } -} - -function collapseJSIntoDOMJS(parent, curr, peek) { - // If there is a parent marker currently being filled, and it's the one - // created from a `DOMEvent` via `collapseDOMIntoDOMJS`, then the current - // marker has to go into that one. - if (parent && parent.name == "meta::DOMEvent+JS") { - return { - forceEnd: true, - toParent: "meta::DOMEvent+JS", - withData: { - stack: curr.stack, - endStack: curr.endStack - }, - }; - } -} - -/** - * A series of formatters used by the blueprint. - */ - -function getGCLabel (marker={}) { - let label = L10N.getStr("timeline.label.garbageCollection"); - // Only if a `nonincrementalReason` exists, do we want to label - // this as a non incremental GC event. - if ("nonincrementalReason" in marker) { - label = `${label} (Non-incremental)`; - } - return label; -} - -/** - * Mapping of JS marker causes to a friendlier form. Only - * markers that are considered "from content" should be labeled here. - */ -const JS_MARKER_MAP = { - "<script> element": "Script Tag", - "setInterval handler": "setInterval", - "setTimeout handler": "setTimeout", - "FrameRequestCallback": "requestAnimationFrame", - "promise callback": "Promise Callback", - "promise initializer": "Promise Init", - "Worker runnable": "Worker", - "javascript: URI": "JavaScript URI", - // The difference between these two event handler markers are differences - // in their WebIDL implementation, so distinguishing them is not necessary. - "EventHandlerNonNull": "Event Handler", - "EventListener.handleEvent": "Event Handler", -}; - -function getJSLabel (marker={}) { - let generic = L10N.getStr("timeline.label.javascript2"); - if ("causeName" in marker) { - return JS_MARKER_MAP[marker.causeName] || generic; - } - return generic; -} - -function getDOMJSLabel (marker={}) { - return `Event (${marker.type})`; -} - -/** - * Returns a hash for computing a fields object for a JS marker. If the cause - * is considered content (so an entry exists in the JS_MARKER_MAP), do not display it - * since it's redundant with the label. Otherwise for Gecko code, either display - * the cause, or "(Gecko)", depending on if "show-platform-data" is set. - */ -function getJSFields (marker) { - if ("causeName" in marker && !JS_MARKER_MAP[marker.causeName]) { - return { Reason: PREFS["show-platform-data"] ? marker.causeName : GECKO_SYMBOL }; - } -} - -function getDOMEventFields (marker) { - let fields = Object.create(null); - if ("type" in marker) { - fields[L10N.getStr("timeline.markerDetail.DOMEventType")] = marker.type; - } - if ("eventPhase" in marker) { - let phase; - if (marker.eventPhase === Ci.nsIDOMEvent.AT_TARGET) { - phase = L10N.getStr("timeline.markerDetail.DOMEventTargetPhase"); - } else if (marker.eventPhase === Ci.nsIDOMEvent.CAPTURING_PHASE) { - phase = L10N.getStr("timeline.markerDetail.DOMEventCapturingPhase"); - } else if (marker.eventPhase === Ci.nsIDOMEvent.BUBBLING_PHASE) { - phase = L10N.getStr("timeline.markerDetail.DOMEventBubblingPhase"); - } - fields[L10N.getStr("timeline.markerDetail.DOMEventPhase")] = phase; - } - return fields; -} - -function getStylesFields (marker) { - if ("restyleHint" in marker) { - return { "Restyle Hint": marker.restyleHint.replace(/eRestyle_/g, "") }; - } -} - -/** - * Takes a main label (like "Timestamp") and a property, - * and returns a marker that will print out the property - * value for a marker if it exists ("Timestamp (rendering)"), - * or just the main label if it does not. - */ -function sublabelForProperty (mainLabel, prop) { - return (marker={}) => marker[prop] ? `${mainLabel} (${marker[prop]})` : mainLabel; -} - -/** * Get the numeric bitmask (or set of masks) for the given category * abbreviation. See CATEGORIES and CATEGORY_MAPPINGS above. * * CATEGORY_MASK can be called with just a name if it is expected that the * category is mapped to by exactly one bitmask. If the category is mapped * to by multiple masks, CATEGORY_MASK for that name must be called with * an additional argument specifying the desired id (in ascending order). */ @@ -444,15 +139,14 @@ const CATEGORY_OTHER = CATEGORY_MASK('ot // Human-readable JIT category bitmask. Certain pseudo-frames in a sample, // like "EnterJIT", don't have any associated `cateogry` information. const CATEGORY_JIT = CATEGORY_MASK('js'); // Exported symbols. exports.L10N = L10N; exports.PREFS = PREFS; -exports.TIMELINE_BLUEPRINT = TIMELINE_BLUEPRINT; exports.CATEGORIES = CATEGORIES; exports.CATEGORY_MAPPINGS = CATEGORY_MAPPINGS; exports.CATEGORY_MASK = CATEGORY_MASK; exports.CATEGORY_MASK_LIST = CATEGORY_MASK_LIST; exports.CATEGORY_OTHER = CATEGORY_OTHER; exports.CATEGORY_JIT = CATEGORY_JIT;
--- a/browser/devtools/performance/modules/logic/marker-utils.js +++ b/browser/devtools/performance/modules/logic/marker-utils.js @@ -3,23 +3,30 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; /** * This file contains utilities for creating elements for markers to be displayed, * and parsing out the blueprint to generate correct values for markers. */ +const { Ci } = require("chrome"); + loader.lazyRequireGetter(this, "L10N", "devtools/performance/global", true); +loader.lazyRequireGetter(this, "PREFS", + "devtools/performance/global", true); loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT", - "devtools/performance/global", true); + "devtools/performance/markers", true); loader.lazyRequireGetter(this, "WebConsoleUtils", "devtools/toolkit/webconsole/utils"); +// String used to fill in platform data when it should be hidden. +const GECKO_SYMBOL = "(Gecko)"; + /** * Returns the correct label to display for passed in marker, based * off of the blueprints. * * @param {ProfileTimelineMarker} marker * @return {string} */ function getMarkerLabel (marker) { @@ -265,12 +272,157 @@ const DOM = { frameIndex = frame.parent; } } return container; } }; +/** + * A series of collapsers used by the blueprint. These functions are + * invoked on a moving window of two markers. + */ + +const CollapseFunctions = { + identical: function (parent, curr, peek) { + // If there is a parent marker currently being filled and the current marker + // should go into the parent marker, make it so. + if (parent && parent.name == curr.name) { + return { toParent: parent.name }; + } + // Otherwise if the current marker is the same type as the next marker type, + // create a new parent marker containing the current marker. + let next = peek(1); + if (next && curr.name == next.name) { + return { toParent: curr.name }; + } + }, + + adjacent: function (parent, curr, peek) { + let next = peek(1); + if (next && (next.start < curr.end || next.start - curr.end <= 10 /* ms */)) { + return CollapseFunctions.identical(parent, curr, peek); + } + }, + + DOMtoDOMJS: function (parent, curr, peek) { + // If the next marker is a JavaScript marker, create a new meta parent marker + // containing the current marker. + let next = peek(1); + if (next && next.name == "Javascript") { + return { + forceNew: true, + toParent: "meta::DOMEvent+JS", + withData: { + type: curr.type, + eventPhase: curr.eventPhase + }, + }; + } + }, + + JStoDOMJS: function (parent, curr, peek) { + // If there is a parent marker currently being filled, and it's the one + // created from a `DOMEvent` via `collapseDOMIntoDOMJS`, then the current + // marker has to go into that one. + if (parent && parent.name == "meta::DOMEvent+JS") { + return { + forceEnd: true, + toParent: "meta::DOMEvent+JS", + withData: { + stack: curr.stack, + endStack: curr.endStack + }, + }; + } + }, +}; + +/** + * Mapping of JS marker causes to a friendlier form. Only + * markers that are considered "from content" should be labeled here. + */ +const JS_MARKER_MAP = { + "<script> element": "Script Tag", + "setInterval handler": "setInterval", + "setTimeout handler": "setTimeout", + "FrameRequestCallback": "requestAnimationFrame", + "promise callback": "Promise Callback", + "promise initializer": "Promise Init", + "Worker runnable": "Worker", + "javascript: URI": "JavaScript URI", + // The difference between these two event handler markers are differences + // in their WebIDL implementation, so distinguishing them is not necessary. + "EventHandlerNonNull": "Event Handler", + "EventListener.handleEvent": "Event Handler", +}; + +/** + * A series of formatters used by the blueprint. + */ +const Formatters = { + GCLabel: function (marker={}) { + let label = L10N.getStr("timeline.label.garbageCollection"); + // Only if a `nonincrementalReason` exists, do we want to label + // this as a non incremental GC event. + if ("nonincrementalReason" in marker) { + label = `${label} (Non-incremental)`; + } + return label; + }, + + JSLabel: function (marker={}) { + let generic = L10N.getStr("timeline.label.javascript2"); + if ("causeName" in marker) { + return JS_MARKER_MAP[marker.causeName] || generic; + } + return generic; + }, + + DOMJSLabel: function (marker={}) { + return `Event (${marker.type})`; + }, + + /** + * Returns a hash for computing a fields object for a JS marker. If the cause + * is considered content (so an entry exists in the JS_MARKER_MAP), do not display it + * since it's redundant with the label. Otherwise for Gecko code, either display + * the cause, or "(Gecko)", depending on if "show-platform-data" is set. + */ + JSFields: function (marker) { + if ("causeName" in marker && !JS_MARKER_MAP[marker.causeName]) { + return { Reason: PREFS["show-platform-data"] ? marker.causeName : GECKO_SYMBOL }; + } + }, + + DOMEventFields: function (marker) { + let fields = Object.create(null); + if ("type" in marker) { + fields[L10N.getStr("timeline.markerDetail.DOMEventType")] = marker.type; + } + if ("eventPhase" in marker) { + let phase; + if (marker.eventPhase === Ci.nsIDOMEvent.AT_TARGET) { + phase = L10N.getStr("timeline.markerDetail.DOMEventTargetPhase"); + } else if (marker.eventPhase === Ci.nsIDOMEvent.CAPTURING_PHASE) { + phase = L10N.getStr("timeline.markerDetail.DOMEventCapturingPhase"); + } else if (marker.eventPhase === Ci.nsIDOMEvent.BUBBLING_PHASE) { + phase = L10N.getStr("timeline.markerDetail.DOMEventBubblingPhase"); + } + fields[L10N.getStr("timeline.markerDetail.DOMEventPhase")] = phase; + } + return fields; + }, + + StylesFields: function (marker) { + if ("restyleHint" in marker) { + return { "Restyle Hint": marker.restyleHint.replace(/eRestyle_/g, "") }; + } + }, +}; + exports.getMarkerLabel = getMarkerLabel; exports.getMarkerClassName = getMarkerClassName; exports.getMarkerFields = getMarkerFields; exports.DOM = DOM; +exports.CollapseFunctions = CollapseFunctions; +exports.Formatters = Formatters;
--- a/browser/devtools/performance/modules/logic/recording-model.js +++ b/browser/devtools/performance/modules/logic/recording-model.js @@ -128,18 +128,21 @@ RecordingModel.prototype = { this._duration = endTime - this._localStartTime; this._recording = false; }, /** * Sets results available from stopping a recording from SharedPerformanceConnection. * Should only be called by SharedPerformanceConnection. */ - _onStopRecording: Task.async(function *(info) { - this._profile = info.profile; + _onStopRecording: Task.async(function *({ profilerEndTime, profile }) { + // Update the duration with the accurate profilerEndTime, so we don't have + // samples outside of the approximate duration set in `_onStoppingRecording`. + this._duration = profilerEndTime - this._profilerStartTime; + this._profile = profile; this._completed = true; // We filter out all samples that fall out of current profile's range // since the profiler is continuously running. Because of this, sample // times are not guaranteed to have a zero epoch, so offset the // timestamps. // TODO move this into FakeProfilerFront in ./actors.js after bug 1154115 RecordingUtils.offsetSampleTimes(this._profile, this._profilerStartTime);
--- a/browser/devtools/performance/modules/logic/waterfall-utils.js +++ b/browser/devtools/performance/modules/logic/waterfall-utils.js @@ -3,17 +3,17 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; /** * Utility functions for collapsing markers into a waterfall. */ loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT", - "devtools/performance/global", true); + "devtools/performance/markers", true); /** * Collapses markers into a tree-like structure. Currently, this only goes * one level deep. * @param object markerNode * @param array markersList */ function collapseMarkersIntoNode({ markerNode, markersList }) {
new file mode 100644 --- /dev/null +++ b/browser/devtools/performance/modules/markers.js @@ -0,0 +1,171 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const { L10N } = require("devtools/performance/global"); +const { Formatters, CollapseFunctions } = require("devtools/performance/marker-utils"); + +/** + * A simple schema for mapping markers to the timeline UI. The keys correspond + * to marker names, while the values are objects with the following format: + * + * - group: The row index in the timeline overview graph; multiple markers + * can be added on the same row. @see <overview.js/buildGraphImage> + * - label: The label used in the waterfall to identify the marker. Can be a + * string or just a function that accepts the marker and returns a + * string, if you want to use a dynamic property for the main label. + * If you use a function for a label, it *must* handle the case where + * no marker is provided for a main label to describe all markers of + * this type. + * - colorName: The label of the DevTools color used for this marker. If + * adding a new color, be sure to check that there's an entry + * for `.marker-details-bullet.{COLORNAME}` for the equivilent + * entry in ./browser/themes/shared/devtools/performance.inc.css + * https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors + * - collapseFunc: A function determining how markers are collapsed together. + * Invoked with 3 arguments: the current parent marker, the + * current marker and a method for peeking i markers ahead. If + * nothing is returned, the marker is added as a standalone entry + * in the waterfall. Otherwise, an object needs to be returned + * with the following properties: + * - toParent: The parent marker name (needs to be an entry in + * the `TIMELINE_BLUEPRINT` itself). + * - withData: An object containing some properties to staple + * on the parent marker. + * - forceNew: True if a new parent marker needs to be created + * even though there is one currently available + * with the same name. + * - forceEnd: True if the current parent marker is full after + * this collapse operation and should be finalized. + * - fields: An optional array of marker properties you wish to display in the + * marker details view. For example, a field in the array such as + * { property: "aCauseName", label: "Cause" } would render a string + * like `Cause: ${marker.aCauseName}` in the marker details view. + * Each `field` item may take the following properties: + * - property: The property that must exist on the marker to render, + * and the value of the property will be displayed. + * - label: The name of the property that should be displayed. + * - formatter: If a formatter is provided, instead of directly using + * the `property` property on the marker, the marker is + * passed into the formatter function to determine the + * displayed value. + * Can also be a function that returns an object. Each key in the object + * will be rendered as a field, with its value rendering as the value. + * + * Whenever this is changed, browser_timeline_waterfall-styles.js *must* be + * updated as well. + */ +const TIMELINE_BLUEPRINT = { + /* Group 0 - Reflow and Rendering pipeline */ + "Styles": { + group: 0, + colorName: "graphs-purple", + collapseFunc: CollapseFunctions.identical, + label: L10N.getStr("timeline.label.styles2"), + fields: Formatters.StylesFields, + }, + "Reflow": { + group: 0, + colorName: "graphs-purple", + collapseFunc: CollapseFunctions.identical, + label: L10N.getStr("timeline.label.reflow2"), + }, + "Paint": { + group: 0, + colorName: "graphs-green", + collapseFunc: CollapseFunctions.identical, + label: L10N.getStr("timeline.label.paint"), + }, + + /* Group 1 - JS */ + "DOMEvent": { + group: 1, + colorName: "graphs-yellow", + collapseFunc: CollapseFunctions.DOMtoDOMJS, + label: L10N.getStr("timeline.label.domevent"), + fields: Formatters.DOMEventFields, + }, + "Javascript": { + group: 1, + colorName: "graphs-yellow", + collapseFunc: either(CollapseFunctions.JStoDOMJS, CollapseFunctions.identical), + label: Formatters.JSLabel, + fields: Formatters.JSFields + }, + "meta::DOMEvent+JS": { + colorName: "graphs-yellow", + label: Formatters.DOMJSLabel, + fields: Formatters.DOMJSFields, + }, + "Parse HTML": { + group: 1, + colorName: "graphs-yellow", + collapseFunc: CollapseFunctions.identical, + label: L10N.getStr("timeline.label.parseHTML"), + }, + "Parse XML": { + group: 1, + colorName: "graphs-yellow", + collapseFunc: CollapseFunctions.identical, + label: L10N.getStr("timeline.label.parseXML"), + }, + "GarbageCollection": { + group: 1, + colorName: "graphs-red", + collapseFunc: CollapseFunctions.adjacent, + label: Formatters.GCLabel, + fields: [ + { property: "causeName", label: "Reason:" }, + { property: "nonincrementalReason", label: "Non-incremental Reason:" } + ], + }, + + /* Group 2 - User Controlled */ + "ConsoleTime": { + group: 2, + colorName: "graphs-grey", + label: sublabelForProperty(L10N.getStr("timeline.label.consoleTime"), "causeName"), + fields: [{ + property: "causeName", + label: L10N.getStr("timeline.markerDetail.consoleTimerName") + }], + }, + "TimeStamp": { + group: 2, + colorName: "graphs-blue", + label: sublabelForProperty(L10N.getStr("timeline.label.timestamp"), "causeName"), + fields: [{ + property: "causeName", + label: "Label:" + }], + }, +}; + +/** + * Helper for creating a function that returns the first defined result from + * a list of functions passed in as params, in order. + * @param ...function fun + * @return any + */ +function either(...fun) { + return function() { + for (let f of fun) { + let result = f.apply(null, arguments); + if (result !== undefined) return result; + } + } +} + +/** + * Takes a main label (like "Timestamp") and a property, + * and returns a marker that will print out the property + * value for a marker if it exists ("Timestamp (rendering)"), + * or just the main label if it does not. + */ +function sublabelForProperty (mainLabel, prop) { + return (marker={}) => marker[prop] ? `${mainLabel} (${marker[prop]})` : mainLabel; +} + +// Exported symbols. +exports.TIMELINE_BLUEPRINT = TIMELINE_BLUEPRINT;
--- a/browser/devtools/performance/modules/widgets/marker-details.js +++ b/browser/devtools/performance/modules/widgets/marker-details.js @@ -9,17 +9,17 @@ const { Cc, Ci, Cu, Cr } = require("chrome"); loader.lazyRequireGetter(this, "EventEmitter", "devtools/toolkit/event-emitter"); loader.lazyRequireGetter(this, "L10N", "devtools/performance/global", true); loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT", - "devtools/performance/global", true); + "devtools/performance/markers", true); loader.lazyRequireGetter(this, "MarkerUtils", "devtools/performance/marker-utils"); /** * A detailed view for one single marker. * * @param nsIDOMNode parent * The parent node holding the view.
--- a/browser/devtools/performance/modules/widgets/marker-view.js +++ b/browser/devtools/performance/modules/widgets/marker-view.js @@ -6,17 +6,17 @@ /** * This file contains the "marker" view, essentially a detailed list * of all the markers in the timeline data. */ const { Cc, Ci, Cu, Cr } = require("chrome"); const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm"); const { AbstractTreeItem } = require("resource:///modules/devtools/AbstractTreeItem.jsm"); -const { TIMELINE_BLUEPRINT: ORIGINAL_BP } = require("devtools/performance/global"); +const { TIMELINE_BLUEPRINT: ORIGINAL_BP } = require("devtools/performance/markers"); loader.lazyRequireGetter(this, "MarkerUtils", "devtools/performance/marker-utils"); const HTML_NS = "http://www.w3.org/1999/xhtml"; const LEVEL_INDENT = 10; // px const ARROW_NODE_OFFSET = -15; // px
--- a/browser/devtools/performance/moz.build +++ b/browser/devtools/performance/moz.build @@ -11,18 +11,20 @@ EXTRA_JS_MODULES.devtools.performance += 'modules/logic/front.js', 'modules/logic/io.js', 'modules/logic/jit.js', 'modules/logic/marker-utils.js', 'modules/logic/recording-model.js', 'modules/logic/recording-utils.js', 'modules/logic/tree-model.js', 'modules/logic/waterfall-utils.js', + 'modules/markers.js', 'modules/widgets/graphs.js', 'modules/widgets/marker-details.js', 'modules/widgets/marker-view.js', 'modules/widgets/markers-overview.js', 'modules/widgets/tree-view.js', 'modules/widgets/waterfall-ticks.js', 'panel.js' ] BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] +XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
--- a/browser/devtools/performance/performance-controller.js +++ b/browser/devtools/performance/performance-controller.js @@ -13,17 +13,17 @@ loader.lazyRequireGetter(this, "EventEmi loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/toolkit/DevToolsUtils"); // Logic modules loader.lazyRequireGetter(this, "L10N", "devtools/performance/global", true); loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT", - "devtools/performance/global", true); + "devtools/performance/markers", true); loader.lazyRequireGetter(this, "RecordingUtils", "devtools/performance/recording-utils"); loader.lazyRequireGetter(this, "RecordingModel", "devtools/performance/recording-model", true); loader.lazyRequireGetter(this, "GraphsController", "devtools/performance/graphs", true); loader.lazyRequireGetter(this, "WaterfallHeader", "devtools/performance/waterfall-ticks", true);
--- a/browser/devtools/performance/test/browser.ini +++ b/browser/devtools/performance/test/browser.ini @@ -118,35 +118,26 @@ skip-if = e10s # GC events seem unreliab [browser_perf-recording-selected-04.js] [browser_perf-theme-toggle-01.js] [browser_profiler_categories.js] [browser_profiler_content-check.js] [browser_profiler_tree-abstract-01.js] [browser_profiler_tree-abstract-02.js] [browser_profiler_tree-abstract-03.js] [browser_profiler_tree-abstract-04.js] -[browser_profiler_tree-frame-node.js] -[browser_profiler_tree-model-01.js] -[browser_profiler_tree-model-02.js] -[browser_profiler_tree-model-03.js] -[browser_profiler_tree-model-04.js] -[browser_profiler_tree-model-05.js] -[browser_profiler_tree-model-06.js] -[browser_profiler_tree-model-07.js] [browser_profiler_tree-view-01.js] [browser_profiler_tree-view-02.js] [browser_profiler_tree-view-03.js] [browser_profiler_tree-view-04.js] [browser_profiler_tree-view-05.js] [browser_profiler_tree-view-06.js] [browser_profiler_tree-view-07.js] [browser_profiler_tree-view-08.js] [browser_profiler_tree-view-09.js] [browser_profiler_tree-view-10.js] -[browser_profiler-frame-utils-01.js] [browser_timeline-blueprint.js] [browser_timeline-filters.js] [browser_timeline-waterfall-background.js] [browser_timeline-waterfall-generic.js] [browser_timeline-waterfall-rerender.js] [browser_timeline-waterfall-sidebar.js] skip-if = os == 'linux' # Bug 1161817 [browser_waterfall-collapse.js]
--- a/browser/devtools/performance/test/browser_marker-utils.js +++ b/browser/devtools/performance/test/browser_marker-utils.js @@ -1,17 +1,17 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests the marker utils methods. */ function* spawnTest() { - let { TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/global"); + let { TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/markers"); let Utils = devtools.require("devtools/performance/marker-utils"); Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false); is(Utils.getMarkerLabel({ name: "DOMEvent" }), "DOM Event", "getMarkerLabel() returns a simple label"); is(Utils.getMarkerLabel({ name: "Javascript", causeName: "setTimeout handler" }), "setTimeout", "getMarkerLabel() returns a label defined via function");
deleted file mode 100644 --- a/browser/devtools/performance/test/browser_profiler_tree-frame-node.js +++ /dev/null @@ -1,248 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -/** - * Verifies if FrameNodes retain and parse their data appropriately. - */ - -function test() { - let FrameUtils = devtools.require("devtools/performance/frame-utils"); - let { FrameNode } = devtools.require("devtools/performance/tree-model"); - let { CATEGORY_OTHER } = devtools.require("devtools/performance/global"); - - let frame1 = new FrameNode("hello/<.world (http://foo/bar.js:123:987)", { - location: "hello/<.world (http://foo/bar.js:123:987)", - line: 456, - isContent: FrameUtils.isContent({ - location: "hello/<.world (http://foo/bar.js:123:987)" - }) - }, false); - - is(frame1.getInfo().nodeType, "Frame", - "The first frame node has the correct type."); - is(frame1.getInfo().functionName, "hello/<.world", - "The first frame node has the correct function name."); - is(frame1.getInfo().fileName, "bar.js", - "The first frame node has the correct file name."); - is(frame1.getInfo().hostName, "foo", - "The first frame node has the correct host name."); - is(frame1.getInfo().url, "http://foo/bar.js", - "The first frame node has the correct url."); - is(frame1.getInfo().line, 123, - "The first frame node has the correct line."); - is(frame1.getInfo().column, 987, - "The first frame node has the correct column."); - is(frame1.getInfo().categoryData.toSource(), "({})", - "The first frame node has the correct category data."); - is(frame1.getInfo().isContent, true, - "The first frame node has the correct content flag."); - - let frame2 = new FrameNode("hello/<.world (http://foo/bar.js#baz:123:987)", { - location: "hello/<.world (http://foo/bar.js#baz:123:987)", - line: 456, - isContent: FrameUtils.isContent({ - location: "hello/<.world (http://foo/bar.js#baz:123:987)" - }) - }, false); - - is(frame2.getInfo().nodeType, "Frame", - "The second frame node has the correct type."); - is(frame2.getInfo().functionName, "hello/<.world", - "The second frame node has the correct function name."); - is(frame2.getInfo().fileName, "bar.js", - "The second frame node has the correct file name."); - is(frame2.getInfo().hostName, "foo", - "The second frame node has the correct host name."); - is(frame2.getInfo().url, "http://foo/bar.js#baz", - "The second frame node has the correct url."); - is(frame2.getInfo().line, 123, - "The second frame node has the correct line."); - is(frame2.getInfo().column, 987, - "The second frame node has the correct column."); - is(frame2.getInfo().categoryData.toSource(), "({})", - "The second frame node has the correct category data."); - is(frame2.getInfo().isContent, true, - "The second frame node has the correct content flag."); - - let frame3 = new FrameNode("hello/<.world (http://foo/#bar:123:987)", { - location: "hello/<.world (http://foo/#bar:123:987)", - line: 456, - isContent: FrameUtils.isContent({ - location: "hello/<.world (http://foo/#bar:123:987)" - }) - }, false); - - is(frame3.getInfo().nodeType, "Frame", - "The third frame node has the correct type."); - is(frame3.getInfo().functionName, "hello/<.world", - "The third frame node has the correct function name."); - is(frame3.getInfo().fileName, "/", - "The third frame node has the correct file name."); - is(frame3.getInfo().hostName, "foo", - "The third frame node has the correct host name."); - is(frame3.getInfo().url, "http://foo/#bar", - "The third frame node has the correct url."); - is(frame3.getInfo().line, 123, - "The third frame node has the correct line."); - is(frame3.getInfo().column, 987, - "The third frame node has the correct column."); - is(frame3.getInfo().categoryData.toSource(), "({})", - "The third frame node has the correct category data."); - is(frame3.getInfo().isContent, true, - "The third frame node has the correct content flag."); - - let frame4 = new FrameNode("hello/<.world (http://foo/:123:987)", { - location: "hello/<.world (http://foo/:123:987)", - line: 456, - isContent: FrameUtils.isContent({ - location: "hello/<.world (http://foo/:123:987)" - }) - }, false); - - is(frame4.getInfo().nodeType, "Frame", - "The fourth frame node has the correct type."); - is(frame4.getInfo().functionName, "hello/<.world", - "The fourth frame node has the correct function name."); - is(frame4.getInfo().fileName, "/", - "The fourth frame node has the correct file name."); - is(frame4.getInfo().hostName, "foo", - "The fourth frame node has the correct host name."); - is(frame4.getInfo().url, "http://foo/", - "The fourth frame node has the correct url."); - is(frame4.getInfo().line, 123, - "The fourth frame node has the correct line."); - is(frame4.getInfo().column, 987, - "The fourth frame node has the correct column."); - is(frame4.getInfo().categoryData.toSource(), "({})", - "The fourth frame node has the correct category data."); - is(frame4.getInfo().isContent, true, - "The fourth frame node has the correct content flag."); - - let frame5 = new FrameNode("hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", { - location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", - line: 456, - isContent: FrameUtils.isContent({ - location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)" - }) - }, false); - - is(frame5.getInfo().nodeType, "Frame", - "The fifth frame node has the correct type."); - is(frame5.getInfo().functionName, "hello/<.world", - "The fifth frame node has the correct function name."); - is(frame5.getInfo().fileName, "baz.js", - "The fifth frame node has the correct file name."); - is(frame5.getInfo().hostName, "bar", - "The fifth frame node has the correct host name."); - is(frame5.getInfo().url, "http://bar/baz.js", - "The fifth frame node has the correct url."); - is(frame5.getInfo().line, 123, - "The fifth frame node has the correct line."); - is(frame5.getInfo().column, 987, - "The fifth frame node has the correct column."); - is(frame5.getInfo().categoryData.toSource(), "({})", - "The fifth frame node has the correct category data."); - is(frame5.getInfo().isContent, false, - "The fifth frame node has the correct content flag."); - - let frame6 = new FrameNode("Foo::Bar::Baz", { - location: "Foo::Bar::Baz", - line: 456, - category: CATEGORY_OTHER, - isContent: FrameUtils.isContent({ - location: "Foo::Bar::Baz", - category: CATEGORY_OTHER - }) - }, false); - - is(frame6.getInfo().nodeType, "Frame", - "The sixth frame node has the correct type."); - is(frame6.getInfo().functionName, "Foo::Bar::Baz", - "The sixth frame node has the correct function name."); - is(frame6.getInfo().fileName, null, - "The sixth frame node has the correct file name."); - is(frame6.getInfo().hostName, null, - "The sixth frame node has the correct host name."); - is(frame6.getInfo().url, null, - "The sixth frame node has the correct url."); - is(frame6.getInfo().line, 456, - "The sixth frame node has the correct line."); - is(frame6.getInfo().categoryData.abbrev, "other", - "The sixth frame node has the correct category data."); - is(frame6.getInfo().isContent, false, - "The sixth frame node has the correct content flag."); - - let frame7 = new FrameNode("EnterJIT", { - location: "EnterJIT", - isContent: FrameUtils.isContent({ - location: "EnterJIT" - }) - }, false); - - is(frame7.getInfo().nodeType, "Frame", - "The seventh frame node has the correct type."); - is(frame7.getInfo().functionName, "EnterJIT", - "The seventh frame node has the correct function name."); - is(frame7.getInfo().fileName, null, - "The seventh frame node has the correct file name."); - is(frame7.getInfo().hostName, null, - "The seventh frame node has the correct host name."); - is(frame7.getInfo().url, null, - "The seventh frame node has the correct url."); - is(frame7.getInfo().line, null, - "The seventh frame node has the correct line."); - is(frame7.getInfo().column, null, - "The seventh frame node has the correct column."); - is(frame7.getInfo().categoryData.abbrev, "js", - "The seventh frame node has the correct category data."); - is(frame7.getInfo().isContent, false, - "The seventh frame node has the correct content flag."); - - let frame8 = new FrameNode("chrome://browser/content/content.js", { - location: "chrome://browser/content/content.js", - line: 456, - column: 123 - }, false); - - is(frame8.getInfo().hostName, null, - "The eighth frame node has the correct host name."); - - let frame9 = new FrameNode("hello/<.world (resource://gre/foo.js:123:434)", { - location: "hello/<.world (resource://gre/foo.js:123:434)", - line: 456 - }, false); - - is(frame9.getInfo().hostName, null, - "The ninth frame node has the correct host name."); - - let frame10 = new FrameNode("main (http://localhost:8888/file.js:123:987)", { - location: "main (http://localhost:8888/file.js:123:987)", - line: 123, - isContent: FrameUtils.isContent({ - location: "main (http://localhost:8888/file.js:123:987)" - }) - }, false); - - is(frame10.getInfo().nodeType, "Frame", - "The tenth frame node has the correct type."); - is(frame10.getInfo().functionName, "main", - "The tenth frame node has the correct function name."); - is(frame10.getInfo().fileName, "file.js", - "The tenth frame node has the correct file name."); - is(frame10.getInfo().hostName, "localhost", - "The tenth frame node has the correct host name."); - is(frame10.getInfo().url, "http://localhost:8888/file.js", - "The tenth frame node has the correct url."); - is(frame10.getInfo().line, 123, - "The tenth frame node has the correct line."); - is(frame10.getInfo().column, 987, - "The tenth frame node has the correct column."); - is(frame10.getInfo().isContent, true, - "The tenth frame node has the correct content flag."); - is(frame10.getInfo().host, "localhost:8888", - "The tenth frame node has the correct host."); - is(frame10.getInfo().port, 8888, - "The tenth frame node has the correct port."); - - finish(); -}
--- a/browser/devtools/performance/test/browser_timeline-blueprint.js +++ b/browser/devtools/performance/test/browser_timeline-blueprint.js @@ -1,17 +1,17 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests if the timeline blueprint has a correct structure. */ function* spawnTest() { - let { TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/global"); + let { TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/markers"); ok(TIMELINE_BLUEPRINT, "A timeline blueprint should be available."); ok(Object.keys(TIMELINE_BLUEPRINT).length, "The timeline blueprint has at least one entry."); for (let [key, value] of Iterator(TIMELINE_BLUEPRINT)) {
--- a/browser/devtools/performance/test/browser_timeline-waterfall-sidebar.js +++ b/browser/devtools/performance/test/browser_timeline-waterfall-sidebar.js @@ -3,17 +3,18 @@ /** * Tests if the sidebar is properly updated when a marker is selected. */ function* spawnTest() { let { target, panel } = yield initPerformance(SIMPLE_URL); let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView } = panel.panelWin; - let { L10N, TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/global"); + let { L10N } = devtools.require("devtools/performance/global"); + let { TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/markers"); let { getMarkerLabel } = devtools.require("devtools/performance/marker-utils"); // Hijack the markers massaging part of creating the waterfall view, // to prevent collapsing markers and allowing this test to verify // everything individually. A better solution would be to just expand // all markers first and then skip the meta nodes, but I'm lazy. WaterfallView._prepareWaterfallTree = markers => { return { submarkers: markers };
--- a/browser/devtools/performance/test/head.js +++ b/browser/devtools/performance/test/head.js @@ -533,32 +533,16 @@ function getInflatedStackLocations(threa stackIndex = stackEntry[STACK_PREFIX_SLOT]; } // The profiler tree is inverted, so reverse the array. return locations.reverse(); } /** - * Get a path in a FrameNode call tree. - */ -function getFrameNodePath(root, path) { - let calls = root.calls; - let node; - for (let key of path.split(" > ")) { - node = calls.find((node) => node.key == key); - if (!node) { - break; - } - calls = node.calls; - } - return node; -} - -/** * Synthesize a profile for testing. */ function synthesizeProfileForTest(samples) { const RecordingUtils = devtools.require("devtools/performance/recording-utils"); samples.unshift({ time: 0, frames: [
new file mode 100644 --- /dev/null +++ b/browser/devtools/performance/test/unit/head.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); +const RecordingUtils = devtools.require("devtools/performance/recording-utils"); + +/** + * Get a path in a FrameNode call tree. + */ +function getFrameNodePath(root, path) { + let calls = root.calls; + let node; + for (let key of path.split(" > ")) { + node = calls.find((node) => node.key == key); + if (!node) { + break; + } + calls = node.calls; + } + return node; +} + +/** + * Synthesize a profile for testing. + */ +function synthesizeProfileForTest(samples) { + samples.unshift({ + time: 0, + frames: [ + { location: "(root)" } + ] + }); + + let uniqueStacks = new RecordingUtils.UniqueStacks(); + return RecordingUtils.deflateThread({ + samples: samples, + markers: [] + }, uniqueStacks); +}
rename from browser/devtools/performance/test/browser_profiler-frame-utils-01.js rename to browser/devtools/performance/test/unit/test_frame-utils-01.js --- a/browser/devtools/performance/test/browser_profiler-frame-utils-01.js +++ b/browser/devtools/performance/test/unit/test_frame-utils-01.js @@ -30,17 +30,21 @@ const CHROME_LOCATIONS = [ { location: "Startup::XRE_InitChildProcess", line: 456, column: 123 }, { location: "chrome://browser/content/content.js", line: 456, column: 123 }, "setTimeout_timer (resource://gre/foo.js:123:434)", "hello/<.world (jar:file://Users/mcurie/Dev/jetpacks.js)", "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", "EnterJIT", ].map(argify); -function test() { +function run_test() { + run_next_test(); +} + +add_task(function () { const { isContent, parseLocation } = devtools.require("devtools/performance/frame-utils"); for (let frame of CONTENT_LOCATIONS) { ok(isContent.apply(null, frameify(frame)), `${frame[0]} should be considered a content frame.`); } for (let frame of CHROME_LOCATIONS) { ok(!isContent.apply(null, frameify(frame)), `${frame[0]} should not be considered a content frame.`); @@ -61,38 +65,36 @@ function test() { ["hello/<.world", "file.js", "localhost", "http://localhost:8888/file.js", 100, null, "localhost:8888", 8888], ["hello/<.world", "/", "localhost", "http://localhost:8888/", 1, null, "localhost:8888", 8888], ["hello/<.world", "/", "localhost", "http://localhost:8888/", 100, 50, "localhost:8888", 8888], ]; for (let i = 0; i < PARSED_CONTENT.length; i++) { let parsed = parseLocation.apply(null, CONTENT_LOCATIONS[i]); for (let j = 0; j < FIELDS.length; j++) { - is(parsed[FIELDS[j]], PARSED_CONTENT[i][j], `${CONTENT_LOCATIONS[i]} was parsed to correct ${FIELDS[j]}`); + equal(parsed[FIELDS[j]], PARSED_CONTENT[i][j], `${CONTENT_LOCATIONS[i]} was parsed to correct ${FIELDS[j]}`); } } const PARSED_CHROME = [ ["Startup::XRE_InitChildProcess", null, null, null, 456, 123, null, null], ["chrome://browser/content/content.js", null, null, null, 456, 123, null, null], ["setTimeout_timer", "foo.js", null, "resource://gre/foo.js", 123, 434, null, null], ["hello/<.world (jar:file://Users/mcurie/Dev/jetpacks.js)", null, null, null, null, null, null, null], ["hello/<.world", "baz.js", "bar", "http://bar/baz.js", 123, 987, "bar", null], ["EnterJIT", null, null, null, null, null, null, null], ]; for (let i = 0; i < PARSED_CHROME.length; i++) { let parsed = parseLocation.apply(null, CHROME_LOCATIONS[i]); for (let j = 0; j < FIELDS.length; j++) { - is(parsed[FIELDS[j]], PARSED_CHROME[i][j], `${CHROME_LOCATIONS[i]} was parsed to correct ${FIELDS[j]}`); + equal(parsed[FIELDS[j]], PARSED_CHROME[i][j], `${CHROME_LOCATIONS[i]} was parsed to correct ${FIELDS[j]}`); } } - - finish(); -} +}); /** * Takes either a string or an object and turns it into an array that * parseLocation.apply expects. */ function argify (val) { if (typeof val === "string") { return [val];
rename from browser/devtools/performance/test/browser_profiler_tree-model-01.js rename to browser/devtools/performance/test/unit/test_tree-model-01.js --- a/browser/devtools/performance/test/browser_profiler_tree-model-01.js +++ b/browser/devtools/performance/test/unit/test_tree-model-01.js @@ -1,117 +1,119 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests if a call tree model can be correctly computed from a samples array. */ -function test() { +function run_test() { + run_next_test(); +} + +add_task(function test() { const { ThreadNode } = devtools.require("devtools/performance/tree-model"); // Create a root node from a given samples array. let threadNode = new ThreadNode(gThread); let root = getFrameNodePath(threadNode, "(root)"); // Test the root node. - is(threadNode.getInfo().nodeType, "Thread", + equal(threadNode.getInfo().nodeType, "Thread", "The correct node type was retrieved for the root node."); - is(root.duration, 20, + equal(root.duration, 20, "The correct duration was calculated for the root node."); - is(root.getInfo().functionName, "(root)", + equal(root.getInfo().functionName, "(root)", "The correct function name was retrieved for the root node."); - is(root.getInfo().categoryData.toSource(), "({})", + equal(root.getInfo().categoryData.toSource(), "({})", "The correct empty category data was retrieved for the root node."); - is(root.calls.length, 1, + equal(root.calls.length, 1, "The correct number of child calls were calculated for the root node."); ok(getFrameNodePath(root, "A"), "The root node's only child call is correct."); // Test all the descendant nodes. - is(getFrameNodePath(root, "A").calls.length, 2, + equal(getFrameNodePath(root, "A").calls.length, 2, "The correct number of child calls were calculated for the 'A' node."); ok(getFrameNodePath(root, "A > B"), "The 'A' node has a 'B' child call."); ok(getFrameNodePath(root, "A > E"), "The 'A' node has a 'E' child call."); - is(getFrameNodePath(root, "A > B").calls.length, 2, + equal(getFrameNodePath(root, "A > B").calls.length, 2, "The correct number of child calls were calculated for the 'A > B' node."); ok(getFrameNodePath(root, "A > B > C"), "The 'A > B' node has a 'C' child call."); ok(getFrameNodePath(root, "A > B > D"), "The 'A > B' node has a 'D' child call."); - is(getFrameNodePath(root, "A > E").calls.length, 1, + equal(getFrameNodePath(root, "A > E").calls.length, 1, "The correct number of child calls were calculated for the 'A > E' node."); ok(getFrameNodePath(root, "A > E > F"), "The 'A > E' node has a 'F' child call."); - is(getFrameNodePath(root, "A > B > C").calls.length, 1, + equal(getFrameNodePath(root, "A > B > C").calls.length, 1, "The correct number of child calls were calculated for the 'A > B > C' node."); ok(getFrameNodePath(root, "A > B > C > D"), "The 'A > B > C' node has a 'D' child call."); - is(getFrameNodePath(root, "A > B > C > D").calls.length, 1, + equal(getFrameNodePath(root, "A > B > C > D").calls.length, 1, "The correct number of child calls were calculated for the 'A > B > C > D' node."); ok(getFrameNodePath(root, "A > B > C > D > E"), "The 'A > B > C > D' node has a 'E' child call."); - is(getFrameNodePath(root, "A > B > C > D > E").calls.length, 1, + equal(getFrameNodePath(root, "A > B > C > D > E").calls.length, 1, "The correct number of child calls were calculated for the 'A > B > C > D > E' node."); ok(getFrameNodePath(root, "A > B > C > D > E > F"), "The 'A > B > C > D > E' node has a 'F' child call."); - is(getFrameNodePath(root, "A > B > C > D > E > F").calls.length, 1, + equal(getFrameNodePath(root, "A > B > C > D > E > F").calls.length, 1, "The correct number of child calls were calculated for the 'A > B > C > D > E > F' node."); ok(getFrameNodePath(root, "A > B > C > D > E > F > G"), "The 'A > B > C > D > E > F' node has a 'G' child call."); - is(getFrameNodePath(root, "A > B > C > D > E > F > G").calls.length, 0, + equal(getFrameNodePath(root, "A > B > C > D > E > F > G").calls.length, 0, "The correct number of child calls were calculated for the 'A > B > C > D > E > F > G' node."); - is(getFrameNodePath(root, "A > B > D").calls.length, 0, + equal(getFrameNodePath(root, "A > B > D").calls.length, 0, "The correct number of child calls were calculated for the 'A > B > D' node."); - is(getFrameNodePath(root, "A > E > F").calls.length, 0, + equal(getFrameNodePath(root, "A > E > F").calls.length, 0, "The correct number of child calls were calculated for the 'A > E > F' node."); // Check the location, sample times, duration and samples of the root. - is(getFrameNodePath(root, "A").location, "A", + equal(getFrameNodePath(root, "A").location, "A", "The 'A' node has the correct location."); - is(getFrameNodePath(root, "A").duration, 20, + equal(getFrameNodePath(root, "A").duration, 20, "The 'A' node has the correct duration in milliseconds."); - is(getFrameNodePath(root, "A").samples, 4, + equal(getFrameNodePath(root, "A").samples, 4, "The 'A' node has the correct number of samples."); // ...and the rightmost leaf. - is(getFrameNodePath(root, "A > E > F").location, "F", + equal(getFrameNodePath(root, "A > E > F").location, "F", "The 'A > E > F' node has the correct location."); - is(getFrameNodePath(root, "A > E > F").duration, 7, + equal(getFrameNodePath(root, "A > E > F").duration, 7, "The 'A > E > F' node has the correct duration in milliseconds."); - is(getFrameNodePath(root, "A > E > F").samples, 1, + equal(getFrameNodePath(root, "A > E > F").samples, 1, "The 'A > E > F' node has the correct number of samples."); // ...and the leftmost leaf. - is(getFrameNodePath(root, "A > B > C > D > E > F > G").location, "G", + equal(getFrameNodePath(root, "A > B > C > D > E > F > G").location, "G", "The 'A > B > C > D > E > F > G' node has the correct location."); - is(getFrameNodePath(root, "A > B > C > D > E > F > G").duration, 2, + equal(getFrameNodePath(root, "A > B > C > D > E > F > G").duration, 2, "The 'A > B > C > D > E > F > G' node has the correct duration in milliseconds."); - is(getFrameNodePath(root, "A > B > C > D > E > F > G").samples, 1, + equal(getFrameNodePath(root, "A > B > C > D > E > F > G").samples, 1, "The 'A > B > C > D > E > F > G' node has the correct number of samples."); - - finish(); -} +}); let gThread = synthesizeProfileForTest([{ time: 5, frames: [ { location: "(root)" }, { location: "A" }, { location: "B" }, { location: "C" }
rename from browser/devtools/performance/test/browser_profiler_tree-model-02.js rename to browser/devtools/performance/test/unit/test_tree-model-02.js --- a/browser/devtools/performance/test/browser_profiler_tree-model-02.js +++ b/browser/devtools/performance/test/unit/test_tree-model-02.js @@ -1,49 +1,51 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests if a call tree model ignores samples with no timing information. */ -function test() { +function run_test() { + run_next_test(); +} + +add_task(function test() { let { ThreadNode } = devtools.require("devtools/performance/tree-model"); // Create a root node from a given samples array. let root = getFrameNodePath(new ThreadNode(gThread), "(root)"); // Test the root node. - is(root.duration, 5, + equal(root.duration, 5, "The correct duration was calculated for the root node."); - is(root.calls.length, 1, + equal(root.calls.length, 1, "The correct number of child calls were calculated for the root node."); ok(getFrameNodePath(root, "A"), "The root node's only child call is correct."); // Test all the descendant nodes. - is(getFrameNodePath(root, "A").calls.length, 1, + equal(getFrameNodePath(root, "A").calls.length, 1, "The correct number of child calls were calculated for the 'A' node."); ok(getFrameNodePath(root, "A > B"), "The 'A' node's only child call is correct."); - is(getFrameNodePath(root, "A > B").calls.length, 1, + equal(getFrameNodePath(root, "A > B").calls.length, 1, "The correct number of child calls were calculated for the 'A > B' node."); ok(getFrameNodePath(root, "A > B > C"), "The 'A > B' node's only child call is correct."); - is(getFrameNodePath(root, "A > B > C").calls.length, 0, + equal(getFrameNodePath(root, "A > B > C").calls.length, 0, "The correct number of child calls were calculated for the 'A > B > C' node."); - - finish(); -} +}); let gThread = synthesizeProfileForTest([{ time: 5, frames: [ { location: "(root)" }, { location: "A" }, { location: "B" }, { location: "C" }
rename from browser/devtools/performance/test/browser_profiler_tree-model-03.js rename to browser/devtools/performance/test/unit/test_tree-model-03.js --- a/browser/devtools/performance/test/browser_profiler_tree-model-03.js +++ b/browser/devtools/performance/test/unit/test_tree-model-03.js @@ -1,64 +1,66 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests if a call tree model can be correctly computed from a samples array, * while at the same time filtering by duration. */ -function test() { +function run_test() { + run_next_test(); +} + +add_task(function test() { let { ThreadNode } = devtools.require("devtools/performance/tree-model"); // Create a root node from a given samples array, filtering by time. // // Filtering from 5 to 18 includes the 2nd and 3rd samples. The 2nd sample // starts exactly on 5 and ends at 11. The 3rd sample starts at 11 and ends // exactly at 18. let startTime = 5; let endTime = 18; let root = getFrameNodePath(new ThreadNode(gThread, { startTime, endTime }), "(root)"); // Test the root node. - is(root.duration, endTime - startTime, + equal(root.duration, endTime - startTime, "The correct duration was calculated for the root node."); - is(root.calls.length, 1, + equal(root.calls.length, 1, "The correct number of child calls were calculated for the root node."); ok(getFrameNodePath(root, "A"), "The root node's only child call is correct."); // Test all the descendant nodes. - is(getFrameNodePath(root, "A").calls.length, 2, + equal(getFrameNodePath(root, "A").calls.length, 2, "The correct number of child calls were calculated for the 'A' node."); ok(getFrameNodePath(root, "A > B"), "The 'A' node has a 'B' child call."); ok(getFrameNodePath(root, "A > E"), "The 'A' node has a 'E' child call."); - is(getFrameNodePath(root, "A > B").calls.length, 1, + equal(getFrameNodePath(root, "A > B").calls.length, 1, "The correct number of child calls were calculated for the 'A > B' node."); ok(getFrameNodePath(root, "A > B > D"), "The 'A > B' node's only child call is correct."); - is(getFrameNodePath(root, "A > E").calls.length, 1, + equal(getFrameNodePath(root, "A > E").calls.length, 1, "The correct number of child calls were calculated for the 'A > E' node."); ok(getFrameNodePath(root, "A > E > F"), "The 'A > E' node's only child call is correct."); - is(getFrameNodePath(root, "A > B > D").calls.length, 0, + equal(getFrameNodePath(root, "A > B > D").calls.length, 0, "The correct number of child calls were calculated for the 'A > B > D' node."); - is(getFrameNodePath(root, "A > E > F").calls.length, 0, + equal(getFrameNodePath(root, "A > E > F").calls.length, 0, "The correct number of child calls were calculated for the 'A > E > F' node."); - - finish(); -} +}); let gThread = synthesizeProfileForTest([{ time: 5, frames: [ { location: "(root)" }, { location: "A" }, { location: "B" }, { location: "C" }
rename from browser/devtools/performance/test/browser_profiler_tree-model-04.js rename to browser/devtools/performance/test/unit/test_tree-model-04.js --- a/browser/devtools/performance/test/browser_profiler_tree-model-04.js +++ b/browser/devtools/performance/test/unit/test_tree-model-04.js @@ -1,59 +1,61 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests if a call tree model can be correctly computed from a samples array, * while at the same time filtering by duration and content-only frames. */ -function test() { +function run_test() { + run_next_test(); +} + +add_task(function test() { let { ThreadNode } = devtools.require("devtools/performance/tree-model"); // Create a root node from a given samples array, filtering by time. let startTime = 5; let endTime = 18; let root = getFrameNodePath(new ThreadNode(gThread, { startTime: startTime, endTime: endTime, contentOnly: true }), "(root)"); // Test the root node. - is(root.duration, endTime - startTime, + equal(root.duration, endTime - startTime, "The correct duration was calculated for the root node."); - is(root.calls.length, 2, + equal(root.calls.length, 2, "The correct number of child calls were calculated for the root node."); ok(getFrameNodePath(root, "http://D"), "The root has a 'http://D' child call."); ok(getFrameNodePath(root, "http://A"), "The root has a 'http://A' child call."); // Test all the descendant nodes. - is(getFrameNodePath(root, "http://A").calls.length, 1, + equal(getFrameNodePath(root, "http://A").calls.length, 1, "The correct number of child calls were calculated for the 'http://A' node."); ok(getFrameNodePath(root, "http://A > https://E"), "The 'http://A' node's only child call is correct."); - is(getFrameNodePath(root, "http://A > https://E").calls.length, 1, + equal(getFrameNodePath(root, "http://A > https://E").calls.length, 1, "The correct number of child calls were calculated for the 'http://A > http://E' node."); ok(getFrameNodePath(root, "http://A > https://E > file://F"), "The 'http://A > https://E' node's only child call is correct."); - is(getFrameNodePath(root, "http://A > https://E > file://F").calls.length, 1, + equal(getFrameNodePath(root, "http://A > https://E > file://F").calls.length, 1, "The correct number of child calls were calculated for the 'http://A > https://E >> file://F' node."); ok(getFrameNodePath(root, "http://A > https://E > file://F > app://H"), "The 'http://A > https://E >> file://F' node's only child call is correct."); - is(getFrameNodePath(root, "http://D").calls.length, 0, + equal(getFrameNodePath(root, "http://D").calls.length, 0, "The correct number of child calls were calculated for the 'http://D' node."); - - finish(); -} +}); let gThread = synthesizeProfileForTest([{ time: 5, frames: [ { location: "(root)" }, { location: "http://A" }, { location: "http://B" }, { location: "http://C" }
rename from browser/devtools/performance/test/browser_profiler_tree-model-05.js rename to browser/devtools/performance/test/unit/test_tree-model-05.js --- a/browser/devtools/performance/test/browser_profiler_tree-model-05.js +++ b/browser/devtools/performance/test/unit/test_tree-model-05.js @@ -37,43 +37,45 @@ let gThread = synthesizeProfileForTest([ frames: [ { location: "(root)" }, { location: "A" }, { location: "B" }, { location: "F" } ] }]); -function test() { +function run_test() { + run_next_test(); +} + +add_task(function test() { let { ThreadNode } = devtools.require("devtools/performance/tree-model"); let root = new ThreadNode(gThread, { invertTree: true }); - is(root.calls.length, 2, + equal(root.calls.length, 2, "Should get the 2 youngest frames, not the 1 oldest frame"); let C = getFrameNodePath(root, "C"); ok(C, "Should have C as a child of the root."); - is(C.calls.length, 3, + equal(C.calls.length, 3, "Should have 3 frames that called C."); ok(getFrameNodePath(C, "B"), "B called C."); ok(getFrameNodePath(C, "D"), "D called C."); ok(getFrameNodePath(C, "E"), "E called C."); - is(getFrameNodePath(C, "B").calls.length, 1); + equal(getFrameNodePath(C, "B").calls.length, 1); ok(getFrameNodePath(C, "B > A"), "A called B called C"); - is(getFrameNodePath(C, "D").calls.length, 1); + equal(getFrameNodePath(C, "D").calls.length, 1); ok(getFrameNodePath(C, "D > A"), "A called D called C"); - is(getFrameNodePath(C, "E").calls.length, 1); + equal(getFrameNodePath(C, "E").calls.length, 1); ok(getFrameNodePath(C, "E > A"), "A called E called C"); let F = getFrameNodePath(root, "F"); ok(F, "Should have F as a child of the root."); - is(F.calls.length, 1); + equal(F.calls.length, 1); ok(getFrameNodePath(F, "B"), "B called F"); - is(getFrameNodePath(F, "B").calls.length, 1); + equal(getFrameNodePath(F, "B").calls.length, 1); ok(getFrameNodePath(F, "B > A"), "A called B called F"); - - finish(); -} +});
rename from browser/devtools/performance/test/browser_profiler_tree-model-06.js rename to browser/devtools/performance/test/unit/test_tree-model-06.js --- a/browser/devtools/performance/test/browser_profiler_tree-model-06.js +++ b/browser/devtools/performance/test/unit/test_tree-model-06.js @@ -1,18 +1,16 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests that when constructing FrameNodes, if optimization data is available, * the FrameNodes have the correct optimization data after iterating over samples. */ -const RecordingUtils = devtools.require("devtools/performance/recording-utils"); - let gUniqueStacks = new RecordingUtils.UniqueStacks(); function uniqStr(s) { return gUniqueStacks.getOrAddStringIndex(s); } let time = 1; @@ -149,33 +147,35 @@ gThread.frameTable.data.forEach((frame) break; case "E_O3": frame[LOCATION_SLOT] = uniqStr("E"); frame[OPTIMIZATIONS_SLOT] = gRawSite3; break; } }); -function test() { +function run_test() { + run_next_test(); +} + +add_task(function test() { let { ThreadNode } = devtools.require("devtools/performance/tree-model"); let root = new ThreadNode(gThread); let A = getFrameNodePath(root, "(root) > A"); let opts = A.getOptimizations(); let sites = opts.optimizationSites; - is(sites.length, 2, "Frame A has two optimization sites."); - is(sites[0].samples, 2, "first opt site has 2 samples."); - is(sites[1].samples, 1, "second opt site has 1 sample."); + equal(sites.length, 2, "Frame A has two optimization sites."); + equal(sites[0].samples, 2, "first opt site has 2 samples."); + equal(sites[1].samples, 1, "second opt site has 1 sample."); let E = getFrameNodePath(A, "E"); opts = E.getOptimizations(); sites = opts.optimizationSites; - is(sites.length, 1, "Frame E has one optimization site."); - is(sites[0].samples, 1, "first opt site has 1 samples."); + equal(sites.length, 1, "Frame E has one optimization site."); + equal(sites[0].samples, 1, "first opt site has 1 samples."); let D = getFrameNodePath(A, "D"); ok(!D.getOptimizations(), "frames that do not have any opts data do not have JITOptimizations instances."); - - finish(); -} +});
rename from browser/devtools/performance/test/browser_profiler_tree-model-07.js rename to browser/devtools/performance/test/unit/test_tree-model-07.js --- a/browser/devtools/performance/test/browser_profiler_tree-model-07.js +++ b/browser/devtools/performance/test/unit/test_tree-model-07.js @@ -2,17 +2,21 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ /** * Tests that when displaying only content nodes, platform nodes are generalized. */ let { CATEGORY_MASK } = devtools.require("devtools/performance/global"); -function test() { +function run_test() { + run_next_test(); +} + +add_task(function test() { let { ThreadNode } = devtools.require("devtools/performance/tree-model"); let url = (n) => `http://content/${n}`; // Create a root node from a given samples array. let root = getFrameNodePath(new ThreadNode(gThread, { contentOnly: true }), "(root)"); /* @@ -26,34 +30,32 @@ function test() { * - D * - E * - F * - (JS) */ // Test the root node. - is(root.calls.length, 2, "root has 2 children"); + equal(root.calls.length, 2, "root has 2 children"); ok(getFrameNodePath(root, url("A")), "root has content child"); ok(getFrameNodePath(root, "64"), "root has platform generalized child"); - is(getFrameNodePath(root, "64").calls.length, 0, "platform generalized child is a leaf."); + equal(getFrameNodePath(root, "64").calls.length, 0, "platform generalized child is a leaf."); ok(getFrameNodePath(root, `${url("A")} > 128`), "A has platform generalized child of another type"); - is(getFrameNodePath(root, `${url("A")} > 128`).calls.length, 0, "second generalized type is a leaf."); + equal(getFrameNodePath(root, `${url("A")} > 128`).calls.length, 0, "second generalized type is a leaf."); ok(getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 64`), "a second leaf of the first generalized type exists deep in the tree."); ok(getFrameNodePath(root, `${url("A")} > 128`), "A has platform generalized child of another type"); - is(getFrameNodePath(root, "64").category, + equal(getFrameNodePath(root, "64").category, getFrameNodePath(root, `${url("A")} > ${url("E")} > ${url("F")} > 64`).category, "generalized frames of same type are duplicated in top-down view"); - - finish(); -} +}); let gThread = synthesizeProfileForTest([{ time: 5, frames: [ { location: "(root)" }, { location: "http://content/A" }, { location: "http://content/B" }, { location: "http://content/C" }
new file mode 100644 --- /dev/null +++ b/browser/devtools/performance/test/unit/test_tree-model-08.js @@ -0,0 +1,250 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Verifies if FrameNodes retain and parse their data appropriately. + */ + +function run_test() { + run_next_test(); +} + +add_task(function test() { + let FrameUtils = devtools.require("devtools/performance/frame-utils"); + let { FrameNode } = devtools.require("devtools/performance/tree-model"); + let { CATEGORY_OTHER } = devtools.require("devtools/performance/global"); + + let frame1 = new FrameNode("hello/<.world (http://foo/bar.js:123:987)", { + location: "hello/<.world (http://foo/bar.js:123:987)", + line: 456, + isContent: FrameUtils.isContent({ + location: "hello/<.world (http://foo/bar.js:123:987)" + }) + }, false); + + equal(frame1.getInfo().nodeType, "Frame", + "The first frame node has the correct type."); + equal(frame1.getInfo().functionName, "hello/<.world", + "The first frame node has the correct function name."); + equal(frame1.getInfo().fileName, "bar.js", + "The first frame node has the correct file name."); + equal(frame1.getInfo().hostName, "foo", + "The first frame node has the correct host name."); + equal(frame1.getInfo().url, "http://foo/bar.js", + "The first frame node has the correct url."); + equal(frame1.getInfo().line, 123, + "The first frame node has the correct line."); + equal(frame1.getInfo().column, 987, + "The first frame node has the correct column."); + equal(frame1.getInfo().categoryData.toSource(), "({})", + "The first frame node has the correct category data."); + equal(frame1.getInfo().isContent, true, + "The first frame node has the correct content flag."); + + let frame2 = new FrameNode("hello/<.world (http://foo/bar.js#baz:123:987)", { + location: "hello/<.world (http://foo/bar.js#baz:123:987)", + line: 456, + isContent: FrameUtils.isContent({ + location: "hello/<.world (http://foo/bar.js#baz:123:987)" + }) + }, false); + + equal(frame2.getInfo().nodeType, "Frame", + "The second frame node has the correct type."); + equal(frame2.getInfo().functionName, "hello/<.world", + "The second frame node has the correct function name."); + equal(frame2.getInfo().fileName, "bar.js", + "The second frame node has the correct file name."); + equal(frame2.getInfo().hostName, "foo", + "The second frame node has the correct host name."); + equal(frame2.getInfo().url, "http://foo/bar.js#baz", + "The second frame node has the correct url."); + equal(frame2.getInfo().line, 123, + "The second frame node has the correct line."); + equal(frame2.getInfo().column, 987, + "The second frame node has the correct column."); + equal(frame2.getInfo().categoryData.toSource(), "({})", + "The second frame node has the correct category data."); + equal(frame2.getInfo().isContent, true, + "The second frame node has the correct content flag."); + + let frame3 = new FrameNode("hello/<.world (http://foo/#bar:123:987)", { + location: "hello/<.world (http://foo/#bar:123:987)", + line: 456, + isContent: FrameUtils.isContent({ + location: "hello/<.world (http://foo/#bar:123:987)" + }) + }, false); + + equal(frame3.getInfo().nodeType, "Frame", + "The third frame node has the correct type."); + equal(frame3.getInfo().functionName, "hello/<.world", + "The third frame node has the correct function name."); + equal(frame3.getInfo().fileName, "/", + "The third frame node has the correct file name."); + equal(frame3.getInfo().hostName, "foo", + "The third frame node has the correct host name."); + equal(frame3.getInfo().url, "http://foo/#bar", + "The third frame node has the correct url."); + equal(frame3.getInfo().line, 123, + "The third frame node has the correct line."); + equal(frame3.getInfo().column, 987, + "The third frame node has the correct column."); + equal(frame3.getInfo().categoryData.toSource(), "({})", + "The third frame node has the correct category data."); + equal(frame3.getInfo().isContent, true, + "The third frame node has the correct content flag."); + + let frame4 = new FrameNode("hello/<.world (http://foo/:123:987)", { + location: "hello/<.world (http://foo/:123:987)", + line: 456, + isContent: FrameUtils.isContent({ + location: "hello/<.world (http://foo/:123:987)" + }) + }, false); + + equal(frame4.getInfo().nodeType, "Frame", + "The fourth frame node has the correct type."); + equal(frame4.getInfo().functionName, "hello/<.world", + "The fourth frame node has the correct function name."); + equal(frame4.getInfo().fileName, "/", + "The fourth frame node has the correct file name."); + equal(frame4.getInfo().hostName, "foo", + "The fourth frame node has the correct host name."); + equal(frame4.getInfo().url, "http://foo/", + "The fourth frame node has the correct url."); + equal(frame4.getInfo().line, 123, + "The fourth frame node has the correct line."); + equal(frame4.getInfo().column, 987, + "The fourth frame node has the correct column."); + equal(frame4.getInfo().categoryData.toSource(), "({})", + "The fourth frame node has the correct category data."); + equal(frame4.getInfo().isContent, true, + "The fourth frame node has the correct content flag."); + + let frame5 = new FrameNode("hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", { + location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)", + line: 456, + isContent: FrameUtils.isContent({ + location: "hello/<.world (resource://foo.js -> http://bar/baz.js:123:987)" + }) + }, false); + + equal(frame5.getInfo().nodeType, "Frame", + "The fifth frame node has the correct type."); + equal(frame5.getInfo().functionName, "hello/<.world", + "The fifth frame node has the correct function name."); + equal(frame5.getInfo().fileName, "baz.js", + "The fifth frame node has the correct file name."); + equal(frame5.getInfo().hostName, "bar", + "The fifth frame node has the correct host name."); + equal(frame5.getInfo().url, "http://bar/baz.js", + "The fifth frame node has the correct url."); + equal(frame5.getInfo().line, 123, + "The fifth frame node has the correct line."); + equal(frame5.getInfo().column, 987, + "The fifth frame node has the correct column."); + equal(frame5.getInfo().categoryData.toSource(), "({})", + "The fifth frame node has the correct category data."); + equal(frame5.getInfo().isContent, false, + "The fifth frame node has the correct content flag."); + + let frame6 = new FrameNode("Foo::Bar::Baz", { + location: "Foo::Bar::Baz", + line: 456, + category: CATEGORY_OTHER, + isContent: FrameUtils.isContent({ + location: "Foo::Bar::Baz", + category: CATEGORY_OTHER + }) + }, false); + + equal(frame6.getInfo().nodeType, "Frame", + "The sixth frame node has the correct type."); + equal(frame6.getInfo().functionName, "Foo::Bar::Baz", + "The sixth frame node has the correct function name."); + equal(frame6.getInfo().fileName, null, + "The sixth frame node has the correct file name."); + equal(frame6.getInfo().hostName, null, + "The sixth frame node has the correct host name."); + equal(frame6.getInfo().url, null, + "The sixth frame node has the correct url."); + equal(frame6.getInfo().line, 456, + "The sixth frame node has the correct line."); + equal(frame6.getInfo().categoryData.abbrev, "other", + "The sixth frame node has the correct category data."); + equal(frame6.getInfo().isContent, false, + "The sixth frame node has the correct content flag."); + + let frame7 = new FrameNode("EnterJIT", { + location: "EnterJIT", + isContent: FrameUtils.isContent({ + location: "EnterJIT" + }) + }, false); + + equal(frame7.getInfo().nodeType, "Frame", + "The seventh frame node has the correct type."); + equal(frame7.getInfo().functionName, "EnterJIT", + "The seventh frame node has the correct function name."); + equal(frame7.getInfo().fileName, null, + "The seventh frame node has the correct file name."); + equal(frame7.getInfo().hostName, null, + "The seventh frame node has the correct host name."); + equal(frame7.getInfo().url, null, + "The seventh frame node has the correct url."); + equal(frame7.getInfo().line, null, + "The seventh frame node has the correct line."); + equal(frame7.getInfo().column, null, + "The seventh frame node has the correct column."); + equal(frame7.getInfo().categoryData.abbrev, "js", + "The seventh frame node has the correct category data."); + equal(frame7.getInfo().isContent, false, + "The seventh frame node has the correct content flag."); + + let frame8 = new FrameNode("chrome://browser/content/content.js", { + location: "chrome://browser/content/content.js", + line: 456, + column: 123 + }, false); + + equal(frame8.getInfo().hostName, null, + "The eighth frame node has the correct host name."); + + let frame9 = new FrameNode("hello/<.world (resource://gre/foo.js:123:434)", { + location: "hello/<.world (resource://gre/foo.js:123:434)", + line: 456 + }, false); + + equal(frame9.getInfo().hostName, null, + "The ninth frame node has the correct host name."); + + let frame10 = new FrameNode("main (http://localhost:8888/file.js:123:987)", { + location: "main (http://localhost:8888/file.js:123:987)", + line: 123, + isContent: FrameUtils.isContent({ + location: "main (http://localhost:8888/file.js:123:987)" + }) + }, false); + + equal(frame10.getInfo().nodeType, "Frame", + "The tenth frame node has the correct type."); + equal(frame10.getInfo().functionName, "main", + "The tenth frame node has the correct function name."); + equal(frame10.getInfo().fileName, "file.js", + "The tenth frame node has the correct file name."); + equal(frame10.getInfo().hostName, "localhost", + "The tenth frame node has the correct host name."); + equal(frame10.getInfo().url, "http://localhost:8888/file.js", + "The tenth frame node has the correct url."); + equal(frame10.getInfo().line, 123, + "The tenth frame node has the correct line."); + equal(frame10.getInfo().column, 987, + "The tenth frame node has the correct column."); + equal(frame10.getInfo().isContent, true, + "The tenth frame node has the correct content flag."); + equal(frame10.getInfo().host, "localhost:8888", + "The tenth frame node has the correct host."); + equal(frame10.getInfo().port, 8888, + "The tenth frame node has the correct port."); +});
new file mode 100644 --- /dev/null +++ b/browser/devtools/performance/test/unit/xpcshell.ini @@ -0,0 +1,16 @@ +[DEFAULT] +tags = devtools +head = head.js +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' || toolkit == 'gonk' + +[test_frame-utils-01.js] +[test_tree-model-01.js] +[test_tree-model-02.js] +[test_tree-model-03.js] +[test_tree-model-04.js] +[test_tree-model-05.js] +[test_tree-model-06.js] +[test_tree-model-07.js] +[test_tree-model-08.js]
--- a/build/mach_bootstrap.py +++ b/build/mach_bootstrap.py @@ -32,16 +32,17 @@ SEARCH_PATHS = [ 'python/blessings', 'python/compare-locales', 'python/configobj', 'python/jsmin', 'python/psutil', 'python/which', 'python/pystache', 'python/pyyaml/lib', + 'python/requests', 'build', 'build/pymake', 'config', 'dom/bindings', 'dom/bindings/parser', 'layout/tools/reftest', 'other-licenses/ply', 'xpcom/idl-parser',
--- a/build/mobile/sutagent/android/fencp/FileCursor.java +++ b/build/mobile/sutagent/android/fencp/FileCursor.java @@ -60,17 +60,16 @@ public class FileCursor extends Abstract if (lLength != 0) { nCount = (int) (lFileSize / BUFSIZE); if ((lFileSize % BUFSIZE) > 0) nCount++; } else { nCount = 1; } - mRowIdColumnIndex = 0; } } } public String getColumnName (int columnIndex) { return theColumns[columnIndex]; }
--- a/build/mobile/sutagent/android/ffxcp/FileCursor.java +++ b/build/mobile/sutagent/android/ffxcp/FileCursor.java @@ -61,17 +61,16 @@ public class FileCursor extends Abstract if (lLength != 0) { nCount = (int) (lFileSize / BUFSIZE); if ((lFileSize % BUFSIZE) > 0) nCount++; } else { nCount = 1; } - mRowIdColumnIndex = 0; } } } public String getColumnName (int columnIndex) { return theColumns[columnIndex]; }
--- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -2223,17 +2223,17 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::G } case JS::GC_CYCLE_END: { PRTime delta = GetCollectionTimeDelta(); if (sPostGCEventsToConsole) { NS_NAMED_LITERAL_STRING(kFmt, "GC(T+%.1f) "); nsString prefix, gcstats; - gcstats.Adopt(aDesc.formatMessage(aRt)); + gcstats.Adopt(aDesc.formatSummaryMessage(aRt)); prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(), double(delta) / PR_USEC_PER_SEC)); nsString msg = prefix + gcstats; nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); if (cs) { cs->LogStringMessage(msg.get()); } } @@ -2299,16 +2299,25 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::G NS_INTERSLICE_GC_DELAY, nsITimer::TYPE_ONE_SHOT); } if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) { nsCycleCollector_dispatchDeferredDeletion(); } + if (sPostGCEventsToConsole) { + nsString gcstats; + gcstats.Adopt(aDesc.formatSliceMessage(aRt)); + nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (cs) { + cs->LogStringMessage(gcstats.get()); + } + } + break; default: MOZ_CRASH("Unexpected GCProgress value"); } if (sPrevGCSliceCallback) { (*sPrevGCSliceCallback)(aRt, aProgress, aDesc);
--- a/dom/base/nsXMLHttpRequest.cpp +++ b/dom/base/nsXMLHttpRequest.cpp @@ -5,16 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsXMLHttpRequest.h" #ifndef XP_WIN #include <unistd.h> #endif #include "mozilla/ArrayUtils.h" +#include "mozilla/CheckedInt.h" #include "mozilla/dom/BlobSet.h" #include "mozilla/dom/File.h" #include "mozilla/dom/XMLHttpRequestUploadBinding.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" #include "mozilla/LoadInfo.h" #include "mozilla/MemoryReporting.h" #include "nsIDOMDocument.h" @@ -3988,36 +3989,40 @@ ArrayBufferBuilder::setCapacity(uint32_t } bool ArrayBufferBuilder::append(const uint8_t *aNewData, uint32_t aDataLen, uint32_t aMaxGrowth) { MOZ_ASSERT(!mMapPtr); + CheckedUint32 neededCapacity = mLength; + neededCapacity += aDataLen; + if (!neededCapacity.isValid()) { + return false; + } if (mLength + aDataLen > mCapacity) { - uint32_t newcap; + CheckedUint32 newcap = mCapacity; // Double while under aMaxGrowth or if not specified. if (!aMaxGrowth || mCapacity < aMaxGrowth) { - newcap = mCapacity * 2; + newcap *= 2; } else { - newcap = mCapacity + aMaxGrowth; + newcap += aMaxGrowth; + } + + if (!newcap.isValid()) { + return false; } // But make sure there's always enough to satisfy our request. - if (newcap < mLength + aDataLen) { - newcap = mLength + aDataLen; + if (newcap.value() < neededCapacity.value()) { + newcap = neededCapacity; } - // Did we overflow? - if (newcap < mCapacity) { - return false; - } - - if (!setCapacity(newcap)) { + if (!setCapacity(newcap.value())) { return false; } } // Assert that the region isn't overlapping so we can memcpy. MOZ_ASSERT(!areOverlappingRegions(aNewData, aDataLen, mDataPtr + mLength, aDataLen));
--- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -3449,27 +3449,31 @@ struct MOZ_STACK_CLASS CanvasBidiProcess } if (!glyphs[i].GetGlyphCount()) { continue; } const gfxTextRun::DetailedGlyph *d = mTextRun->GetDetailedGlyphs(i); - if (glyphs[i].IsMissing() && d->mAdvance > 0) { - newGlyph.mIndex = 0; - if (rtl) { - inlinePos = baselineOriginInline - advanceSum - - d->mAdvance * devUnitsPerAppUnit; - } else { - inlinePos = baselineOriginInline + advanceSum; + if (glyphs[i].IsMissing()) { + if (d->mAdvance > 0) { + // Perhaps we should render a hexbox here, but for now + // we just draw the font's .notdef glyph. (See bug 808288.) + newGlyph.mIndex = 0; + if (rtl) { + inlinePos = baselineOriginInline - advanceSum - + d->mAdvance * devUnitsPerAppUnit; + } else { + inlinePos = baselineOriginInline + advanceSum; + } + blockPos = baselineOriginBlock; + advanceSum += d->mAdvance * devUnitsPerAppUnit; + glyphBuf.push_back(newGlyph); } - blockPos = baselineOriginBlock; - advanceSum += d->mAdvance * devUnitsPerAppUnit; - glyphBuf.push_back(newGlyph); continue; } for (uint32_t c = 0; c < glyphs[i].GetGlyphCount(); c++, d++) { newGlyph.mIndex = d->mGlyphID; if (rtl) { inlinePos = baselineOriginInline - advanceSum - d->mAdvance * devUnitsPerAppUnit;
--- a/dom/smil/nsSMILAnimationFunction.cpp +++ b/dom/smil/nsSMILAnimationFunction.cpp @@ -1,16 +1,18 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsSMILAnimationFunction.h" + #include "mozilla/dom/SVGAnimationElement.h" -#include "nsSMILAnimationFunction.h" +#include "mozilla/Move.h" #include "nsISMILAttr.h" #include "nsSMILParserUtils.h" #include "nsSMILNullType.h" #include "nsSMILTimedElement.h" #include "nsAttrValueInlines.h" #include "nsGkAtoms.h" #include "nsCOMPtr.h" #include "nsCOMArray.h" @@ -262,19 +264,17 @@ nsSMILAnimationFunction::ComposeResult(c return; if (NS_FAILED(AccumulateResult(values, result))) return; } // If additive animation isn't required or isn't supported, set the value. if (!isAdditive || NS_FAILED(aResult.SandwichAdd(result))) { - aResult.Swap(result); - // Note: The old value of aResult is now in |result|, and it will get - // cleaned up when |result| goes out of scope, when this function returns. + aResult = Move(result); } } int8_t nsSMILAnimationFunction::CompareTo(const nsSMILAnimationFunction* aOther) const { NS_ENSURE_TRUE(aOther, 0);
--- a/dom/smil/nsSMILCSSProperty.cpp +++ b/dom/smil/nsSMILCSSProperty.cpp @@ -2,21 +2,23 @@ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* representation of a SMIL-animatable CSS property on an element */ #include "nsSMILCSSProperty.h" + +#include "mozilla/dom/Element.h" +#include "mozilla/Move.h" #include "nsSMILCSSValueType.h" #include "nsSMILValue.h" #include "nsComputedDOMStyle.h" #include "nsCSSProps.h" -#include "mozilla/dom/Element.h" #include "nsIDOMElement.h" #include "nsIDocument.h" using namespace mozilla::dom; // Helper function static bool GetCSSComputedValue(Element* aElem, @@ -76,17 +78,17 @@ nsSMILCSSProperty::GetBaseValue() const // // Also, although we can look up the base value of the display property, // doing so involves clearing and resetting the property which can cause // frames to be recreated which we'd like to avoid. // // In either case, just return a dummy value (initialized with the right // type, so as not to indicate failure). nsSMILValue tmpVal(&nsSMILCSSValueType::sSingleton); - baseValue.Swap(tmpVal); + Swap(baseValue, tmpVal); return baseValue; } // GENERAL CASE: Non-Shorthands // (1) Put empty string in override style for property mPropID // (saving old override style value, so we can set it again when we're done) nsICSSDeclaration* overrideDecl = mElement->GetSMILOverrideStyle(); nsAutoString cachedOverrideStyleVal;
--- a/dom/smil/nsSMILValue.cpp +++ b/dom/smil/nsSMILValue.cpp @@ -39,38 +39,54 @@ nsSMILValue::operator=(const nsSMILValue DestroyAndReinit(aVal.mType); } mType->Assign(*this, aVal); return *this; } +// Move constructor / reassignment operator: +nsSMILValue::nsSMILValue(nsSMILValue&& aVal) + : mU(aVal.mU), // Copying union is only OK because we clear aVal.mType below. + mType(aVal.mType) +{ + // Leave aVal with a null type, so that it's safely destructible (and won't + // mess with anything referenced by its union, which we've copied). + aVal.mType = nsSMILNullType::Singleton(); +} + +nsSMILValue& +nsSMILValue::operator=(nsSMILValue&& aVal) +{ + if (!IsNull()) { + // Clean up any data we're currently tracking. + DestroyAndCheckPostcondition(); + } + + // Copy the union (which could include a pointer to external memory) & mType: + mU = aVal.mU; + mType = aVal.mType; + + // Leave aVal with a null type, so that it's safely destructible (and won't + // mess with anything referenced by its union, which we've now copied). + aVal.mType = nsSMILNullType::Singleton(); + + return *this; +} + bool nsSMILValue::operator==(const nsSMILValue& aVal) const { if (&aVal == this) return true; return mType == aVal.mType && mType->IsEqual(*this, aVal); } -void -nsSMILValue::Swap(nsSMILValue& aOther) -{ - nsSMILValue tmp; - memcpy(&tmp, &aOther, sizeof(nsSMILValue)); // tmp = aOther - memcpy(&aOther, this, sizeof(nsSMILValue)); // aOther = this - memcpy(this, &tmp, sizeof(nsSMILValue)); // this = tmp - - // |tmp| is about to die -- we need to clear its mType, so that its - // destructor doesn't muck with the data we just transferred out of it. - tmp.mType = nsSMILNullType::Singleton(); -} - nsresult nsSMILValue::Add(const nsSMILValue& aValueToAdd, uint32_t aCount) { if (aValueToAdd.mType != mType) { NS_ERROR("Trying to add incompatible types"); return NS_ERROR_FAILURE; }
--- a/dom/smil/nsSMILValue.h +++ b/dom/smil/nsSMILValue.h @@ -28,31 +28,32 @@ public: ~nsSMILValue() { mType->Destroy(*this); } const nsSMILValue& operator=(const nsSMILValue& aVal); + // Move constructor / reassignment operator: + nsSMILValue(nsSMILValue&& aVal); + nsSMILValue& operator=(nsSMILValue&& aVal); + // Equality operators. These are allowed to be conservative (return false // more than you'd expect) - see comment above nsISMILType::IsEqual. bool operator==(const nsSMILValue& aVal) const; bool operator!=(const nsSMILValue& aVal) const { return !(*this == aVal); } bool IsNull() const { return (mType == nsSMILNullType::Singleton()); } - // Swaps the member data (mU & mPtr) of |this| with |aOther| - void Swap(nsSMILValue& aOther); - nsresult Add(const nsSMILValue& aValueToAdd, uint32_t aCount = 1); nsresult SandwichAdd(const nsSMILValue& aValueToAdd); nsresult ComputeDistance(const nsSMILValue& aTo, double& aDistance) const; nsresult Interpolate(const nsSMILValue& aEndVal, double aUnitDistance, nsSMILValue& aResult) const; union {
--- a/dom/svg/SVGAnimatedLengthList.cpp +++ b/dom/svg/SVGAnimatedLengthList.cpp @@ -1,16 +1,18 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SVGAnimatedLengthList.h" + #include "DOMSVGAnimatedLengthList.h" +#include "mozilla/Move.h" #include "nsSVGElement.h" #include "nsSVGAttrTearoffTable.h" #include "nsSMILValue.h" #include "SVGLengthListSMILType.h" namespace mozilla { nsresult @@ -133,17 +135,17 @@ SVGAnimatedLengthList:: nsSMILValue& aValue, bool& aPreventCachingOfSandwich) const { nsSMILValue val(&SVGLengthListSMILType::sSingleton); SVGLengthListAndInfo *llai = static_cast<SVGLengthListAndInfo*>(val.mU.mPtr); nsresult rv = llai->SetValueFromString(aStr); if (NS_SUCCEEDED(rv)) { llai->SetInfo(mElement, mAxis, mCanZeroPadList); - aValue.Swap(val); + aValue = Move(val); // If any of the lengths in the list depend on their context, then we must // prevent caching of the entire animation sandwich. This is because the // units of a length at a given index can change from sandwich layer to // layer, and indeed even be different within a single sandwich layer. If // any length in the result of an animation sandwich is the result of the // addition of lengths where one or more of those lengths is context // dependent, then naturally the resultant length is also context @@ -176,17 +178,17 @@ SVGAnimatedLengthList::SMILAnimatedLengt // from ALL return points. This function must only return THIS variable: nsSMILValue val; nsSMILValue tmp(&SVGLengthListSMILType::sSingleton); SVGLengthListAndInfo *llai = static_cast<SVGLengthListAndInfo*>(tmp.mU.mPtr); nsresult rv = llai->CopyFrom(mVal->mBaseVal); if (NS_SUCCEEDED(rv)) { llai->SetInfo(mElement, mAxis, mCanZeroPadList); - val.Swap(tmp); + val = Move(tmp); } return val; } nsresult SVGAnimatedLengthList::SMILAnimatedLengthList::SetAnimValue(const nsSMILValue& aValue) { NS_ASSERTION(aValue.mType == &SVGLengthListSMILType::sSingleton,
--- a/dom/svg/SVGAnimatedNumberList.cpp +++ b/dom/svg/SVGAnimatedNumberList.cpp @@ -1,16 +1,18 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SVGAnimatedNumberList.h" + #include "DOMSVGAnimatedNumberList.h" +#include "mozilla/Move.h" #include "nsSVGElement.h" #include "nsSVGAttrTearoffTable.h" #include "nsSMILValue.h" #include "SVGNumberListSMILType.h" namespace mozilla { nsresult @@ -133,17 +135,17 @@ SVGAnimatedNumberList:: nsSMILValue& aValue, bool& aPreventCachingOfSandwich) const { nsSMILValue val(&SVGNumberListSMILType::sSingleton); SVGNumberListAndInfo *nlai = static_cast<SVGNumberListAndInfo*>(val.mU.mPtr); nsresult rv = nlai->SetValueFromString(aStr); if (NS_SUCCEEDED(rv)) { nlai->SetInfo(mElement); - aValue.Swap(val); + aValue = Move(val); } aPreventCachingOfSandwich = false; return rv; } nsSMILValue SVGAnimatedNumberList::SMILAnimatedNumberList::GetBaseValue() const { @@ -152,17 +154,17 @@ SVGAnimatedNumberList::SMILAnimatedNumbe // from ALL return points. This function must only return THIS variable: nsSMILValue val; nsSMILValue tmp(&SVGNumberListSMILType::sSingleton); SVGNumberListAndInfo *nlai = static_cast<SVGNumberListAndInfo*>(tmp.mU.mPtr); nsresult rv = nlai->CopyFrom(mVal->mBaseVal); if (NS_SUCCEEDED(rv)) { nlai->SetInfo(mElement); - val.Swap(tmp); + Swap(val, tmp); } return val; } nsresult SVGAnimatedNumberList::SMILAnimatedNumberList::SetAnimValue(const nsSMILValue& aValue) { NS_ASSERTION(aValue.mType == &SVGNumberListSMILType::sSingleton,
--- a/dom/svg/SVGAnimatedPathSegList.cpp +++ b/dom/svg/SVGAnimatedPathSegList.cpp @@ -1,16 +1,18 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SVGAnimatedPathSegList.h" + #include "DOMSVGPathSegList.h" +#include "mozilla/Move.h" #include "nsSVGElement.h" #include "nsSVGAttrTearoffTable.h" #include "nsSMILValue.h" #include "SVGPathSegListSMILType.h" // See the comments in this file's header! namespace mozilla { @@ -155,17 +157,17 @@ SVGAnimatedPathSegList:: nsSMILValue& aValue, bool& aPreventCachingOfSandwich) const { nsSMILValue val(SVGPathSegListSMILType::Singleton()); SVGPathDataAndInfo *list = static_cast<SVGPathDataAndInfo*>(val.mU.mPtr); nsresult rv = list->SetValueFromString(aStr); if (NS_SUCCEEDED(rv)) { list->SetElement(mElement); - aValue.Swap(val); + aValue = Move(val); } aPreventCachingOfSandwich = false; return rv; } nsSMILValue SVGAnimatedPathSegList::SMILAnimatedPathSegList::GetBaseValue() const { @@ -174,17 +176,17 @@ SVGAnimatedPathSegList::SMILAnimatedPath // from ALL return points. This function must only return THIS variable: nsSMILValue val; nsSMILValue tmp(SVGPathSegListSMILType::Singleton()); SVGPathDataAndInfo *list = static_cast<SVGPathDataAndInfo*>(tmp.mU.mPtr); nsresult rv = list->CopyFrom(mVal->mBaseVal); if (NS_SUCCEEDED(rv)) { list->SetElement(mElement); - val.Swap(tmp); + val = Move(tmp); } return val; } nsresult SVGAnimatedPathSegList::SMILAnimatedPathSegList::SetAnimValue(const nsSMILValue& aValue) { NS_ASSERTION(aValue.mType == SVGPathSegListSMILType::Singleton(),
--- a/dom/svg/SVGAnimatedPointList.cpp +++ b/dom/svg/SVGAnimatedPointList.cpp @@ -1,16 +1,18 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "SVGAnimatedPointList.h" + #include "DOMSVGPointList.h" +#include "mozilla/Move.h" #include "nsSVGElement.h" #include "nsSVGAttrTearoffTable.h" #include "nsSMILValue.h" #include "SVGPointListSMILType.h" // See the comments in this file's header! namespace mozilla { @@ -158,17 +160,17 @@ SVGAnimatedPointList:: nsSMILValue& aValue, bool& aPreventCachingOfSandwich) const { nsSMILValue val(&SVGPointListSMILType::sSingleton); SVGPointListAndInfo *list = static_cast<SVGPointListAndInfo*>(val.mU.mPtr); nsresult rv = list->SetValueFromString(aStr); if (NS_SUCCEEDED(rv)) { list->SetInfo(mElement); - aValue.Swap(val); + aValue = Move(val); } aPreventCachingOfSandwich = false; return rv; } nsSMILValue SVGAnimatedPointList::SMILAnimatedPointList::GetBaseValue() const { @@ -177,17 +179,17 @@ SVGAnimatedPointList::SMILAnimatedPointL // from ALL return points. This function must only return THIS variable: nsSMILValue val; nsSMILValue tmp(&SVGPointListSMILType::sSingleton); SVGPointListAndInfo *list = static_cast<SVGPointListAndInfo*>(tmp.mU.mPtr); nsresult rv = list->CopyFrom(mVal->mBaseVal); if (NS_SUCCEEDED(rv)) { list->SetInfo(mElement); - val.Swap(tmp); + Swap(val, tmp); } return val; } nsresult SVGAnimatedPointList::SMILAnimatedPointList::SetAnimValue(const nsSMILValue& aValue) { NS_ASSERTION(aValue.mType == &SVGPointListSMILType::sSingleton,
--- a/dom/svg/nsSVGAngle.cpp +++ b/dom/svg/nsSVGAngle.cpp @@ -1,18 +1,19 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "mozilla/ArrayUtils.h" +#include "nsSVGAngle.h" -#include "nsSVGAngle.h" +#include "mozilla/ArrayUtils.h" #include "mozilla/dom/SVGMarkerElement.h" +#include "mozilla/Move.h" #include "nsContentUtils.h" // NS_ENSURE_FINITE #include "nsSMILValue.h" #include "nsSVGAttrTearoffTable.h" #include "nsTextFormatter.h" #include "SVGAngle.h" #include "SVGAnimatedAngle.h" #include "SVGOrientSMILType.h" @@ -378,17 +379,17 @@ nsSVGAngle::SMILOrient::ValueFromString( uint16_t unitType; if (!GetValueFromString(aStr, value, &unitType)) { return NS_ERROR_DOM_SYNTAX_ERR; } val.mU.mOrient.mAngle = value; val.mU.mOrient.mUnit = unitType; val.mU.mOrient.mOrientType = SVG_MARKER_ORIENT_ANGLE; } - aValue.Swap(val); + aValue = Move(val); aPreventCachingOfSandwich = false; return NS_OK; } nsSMILValue nsSVGAngle::SMILOrient::GetBaseValue() const {
--- a/dom/svg/nsSVGAnimatedTransformList.cpp +++ b/dom/svg/nsSVGAnimatedTransformList.cpp @@ -1,17 +1,19 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsSVGAnimatedTransformList.h" + #include "mozilla/dom/SVGAnimatedTransformList.h" #include "mozilla/dom/SVGAnimationElement.h" +#include "mozilla/Move.h" #include "nsCharSeparatedTokenizer.h" #include "nsSVGTransform.h" #include "nsSMILValue.h" #include "SVGContentUtils.h" #include "SVGTransformListSMILType.h" #include "nsIDOMMutationEvent.h" namespace mozilla { @@ -243,17 +245,17 @@ nsSVGAnimatedTransformList::SMILAnimated nsSMILValue val(SVGTransformListSMILType::Singleton()); SVGTransformSMILData transform(transformType, params); if (NS_FAILED(SVGTransformListSMILType::AppendTransform(transform, val))) { return; // OOM } // Success! Populate our outparam with parsed value. - aResult.Swap(val); + aResult = Move(val); } int32_t nsSVGAnimatedTransformList::SMILAnimatedTransformList::ParseParameterList( const nsAString& aSpec, float* aVars, int32_t aNVars) {
--- a/dom/svg/nsSVGClass.cpp +++ b/dom/svg/nsSVGClass.cpp @@ -1,19 +1,21 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsSVGClass.h" + +#include "mozilla/dom/SVGAnimatedString.h" +#include "mozilla/Move.h" #include "nsSVGElement.h" #include "nsSMILValue.h" #include "SMILStringType.h" -#include "mozilla/dom/SVGAnimatedString.h" using namespace mozilla; using namespace mozilla::dom; struct DOMAnimatedString final : public SVGAnimatedString { NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DOMAnimatedString) @@ -125,17 +127,17 @@ nsresult nsSVGClass::SMILString::ValueFromString(const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, nsSMILValue& aValue, bool& aPreventCachingOfSandwich) const { nsSMILValue val(SMILStringType::Singleton()); *static_cast<nsAString*>(val.mU.mPtr) = aStr; - aValue.Swap(val); + aValue = Move(val); aPreventCachingOfSandwich = false; return NS_OK; } nsSMILValue nsSVGClass::SMILString::GetBaseValue() const { nsSMILValue val(SMILStringType::Singleton());
--- a/dom/svg/nsSVGString.cpp +++ b/dom/svg/nsSVGString.cpp @@ -1,15 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsSVGString.h" + +#include "mozilla/Move.h" #include "nsSVGAttrTearoffTable.h" #include "nsSMILValue.h" #include "SMILStringType.h" using namespace mozilla; using namespace mozilla::dom; NS_SVG_VAL_IMPL_CYCLE_COLLECTION_WRAPPERCACHED(nsSVGString::DOMAnimatedString, mSVGElement) @@ -105,17 +107,17 @@ nsresult nsSVGString::SMILString::ValueFromString(const nsAString& aStr, const dom::SVGAnimationElement* /*aSrcElement*/, nsSMILValue& aValue, bool& aPreventCachingOfSandwich) const { nsSMILValue val(SMILStringType::Singleton()); *static_cast<nsAString*>(val.mU.mPtr) = aStr; - aValue.Swap(val); + aValue = Move(val); aPreventCachingOfSandwich = false; return NS_OK; } nsSMILValue nsSVGString::SMILString::GetBaseValue() const { nsSMILValue val(SMILStringType::Singleton());
--- a/dom/svg/nsSVGViewBox.cpp +++ b/dom/svg/nsSVGViewBox.cpp @@ -1,15 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsSVGViewBox.h" + +#include "mozilla/Move.h" #include "nsCharSeparatedTokenizer.h" #include "nsSMILValue.h" #include "nsTextFormatter.h" #include "SVGContentUtils.h" #include "SVGViewBoxSMILType.h" #define NUM_VIEWBOX_COMPONENTS 4 using namespace mozilla; @@ -295,17 +297,17 @@ nsSVGViewBox::SMILViewBox { nsSVGViewBoxRect viewBox; nsresult res = ToSVGViewBoxRect(aStr, &viewBox); if (NS_FAILED(res)) { return res; } nsSMILValue val(&SVGViewBoxSMILType::sSingleton); *static_cast<nsSVGViewBoxRect*>(val.mU.mPtr) = viewBox; - aValue.Swap(val); + aValue = Move(val); aPreventCachingOfSandwich = false; return NS_OK; } nsSMILValue nsSVGViewBox::SMILViewBox::GetBaseValue() const {
--- a/dom/xul/templates/nsTemplateMap.h +++ b/dom/xul/templates/nsTemplateMap.h @@ -11,31 +11,22 @@ class nsTemplateMap { protected: struct Entry : public PLDHashEntryHdr { nsIContent* mContent; nsIContent* mTemplate; }; - PLDHashTable mTable; - - void - Init() - { - PL_DHashTableInit(&mTable, PL_DHashGetStubOps(), sizeof(Entry)); - } - - void - Finish() { PL_DHashTableFinish(&mTable); } + PLDHashTable2 mTable; public: - nsTemplateMap() { Init(); } + nsTemplateMap() : mTable(PL_DHashGetStubOps(), sizeof(Entry)) { } - ~nsTemplateMap() { Finish(); } + ~nsTemplateMap() { } void Put(nsIContent* aContent, nsIContent* aTemplate) { NS_ASSERTION(!PL_DHashTableSearch(&mTable, aContent), "aContent already in map"); Entry* entry = static_cast<Entry*> (PL_DHashTableAdd(&mTable, aContent, fallible)); @@ -65,13 +56,13 @@ public: if (entry) NS_IF_ADDREF(*aResult = entry->mTemplate); else *aResult = nullptr; } void - Clear() { Finish(); Init(); } + Clear() { mTable.Clear(); } }; #endif // nsTemplateMap_h__
--- a/embedding/components/build/moz.build +++ b/embedding/components/build/moz.build @@ -21,16 +21,17 @@ LOCAL_INCLUDES += [ ] if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': DEFINES['PROXY_PRINTING'] = 1 LOCAL_INCLUDES += [ '../printingui/win', ] elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + DEFINES['PROXY_PRINTING'] = 1 LOCAL_INCLUDES += [ '../printingui/mac', ] if CONFIG['MOZ_PDF_PRINTING']: DEFINES['PROXY_PRINTING'] = 1 LOCAL_INCLUDES += [ '../printingui/unixshared',
--- a/embedding/components/printingui/ipc/PPrintingTypes.ipdlh +++ b/embedding/components/printingui/ipc/PPrintingTypes.ipdlh @@ -67,16 +67,17 @@ struct PrintData { nsString toFileName; short outputFormat; int32_t printPageDelay; int32_t resolution; int32_t duplex; bool isInitializedFromPrinter; bool isInitializedFromPrefs; bool persistMarginBoxSettings; + int32_t optionFlags; /* Windows-specific things */ nsString driverName; nsString deviceName; bool isFramesetDocument; bool isFramesetFrameSelected; bool isIFrameSelected; bool isRangeSelection; @@ -86,15 +87,26 @@ struct PrintData { * information we're already passing, but the generalized settings that * we hold in nsIPrintSettings don't map perfectly to GTK's GtkPrintSettings, * so there are some nuances. GtkPrintSettings, for example, stores both an * internal name for paper size, as well as the display name. */ CStringKeyValue[] GTKPrintSettings; /** - * TODO: OS X specific things - specifically, an array of names for the - * document to be supplied by nsIWebBrowserPrint::enumerateDocumentNames + * OS X specific things. */ + nsString printJobName; + bool printAllPages; + bool mustCollate; + nsString disposition; + /** TODO: Is there an "unsigned short" primitive? **/ + short pagesAcross; + short pagesDown; + double printTime; + bool detailedErrorReporting; + nsString faxNumber; + bool addHeaderAndFooter; + bool fileNameExtensionHidden; }; } // namespace embedding } // namespace mozilla
--- a/embedding/components/printingui/ipc/PrintDataUtils.cpp +++ b/embedding/components/printingui/ipc/PrintDataUtils.cpp @@ -124,17 +124,33 @@ MockWebBrowserPrint::Cancel() { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP MockWebBrowserPrint::EnumerateDocumentNames(uint32_t* aCount, char16_t*** aResult) { - return NS_ERROR_NOT_IMPLEMENTED; + *aCount = 0; + *aResult = nullptr; + + if (mData.printJobName().IsEmpty()) { + return NS_OK; + } + + // The only consumer that cares about this is the OS X printing + // dialog, and even then, it only cares about the first document + // name. That's why we only send a single document name through + // PrintData. + char16_t** array = (char16_t**) moz_xmalloc(sizeof(char16_t*)); + array[0] = ToNewUnicode(mData.printJobName()); + + *aCount = 1; + *aResult = array; + return NS_OK; } NS_IMETHODIMP MockWebBrowserPrint::ExitPrintPreview() { return NS_ERROR_NOT_IMPLEMENTED; }
--- a/gfx/2d/unittest/TestCairo.cpp +++ b/gfx/2d/unittest/TestCairo.cpp @@ -44,10 +44,53 @@ TEST(Cairo, Bug825721) { // This was the crash in 825721. Note that centerY has to be non-zero, // and radius has to be not only large, but in particular range. // 825721 has a band-aid fix, where the crash is inevitable, but does // not fix the cause. The same code crashes in cairo standalone. TryCircle(0.0, 1.0, 5761126469220696064.0); } +TEST(Cairo, Bug1063486) { + + double x1, y1, x2, y2; + const double epsilon = .01; + + cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + ASSERT_TRUE(surf != nullptr); + + cairo_t *cairo = cairo_create(surf); + ASSERT_TRUE(cairo != nullptr); + + printf("Path 1\n"); + cairo_move_to(cairo, -20, -10); + cairo_line_to(cairo, 20, -10); + cairo_line_to(cairo, 20, 10); + cairo_curve_to(cairo, 10,10, -10,10, -20,10); + cairo_curve_to(cairo, -30,10, -30,-10, -20,-10); + + cairo_path_extents(cairo, &x1, &y1, &x2, &y2); + + ASSERT_LT(std::abs(-27.5 - x1), epsilon); // the failing coordinate + ASSERT_LT(std::abs(-10 - y1), epsilon); + ASSERT_LT(std::abs(20 - x2), epsilon); + ASSERT_LT(std::abs(10 - y2), epsilon); + + printf("Path 2\n"); + cairo_new_path(cairo); + cairo_move_to(cairo, 10, 30); + cairo_line_to(cairo, 90, 30); + cairo_curve_to(cairo, 30,30, 30,30, 10,30); + cairo_curve_to(cairo, 0,30, 0,0, 30,5); + + cairo_path_extents(cairo, &x1, &y1, &x2, &y2); + + ASSERT_LT(std::abs(4.019531 - x1), epsilon); // the failing coordinate + ASSERT_LT(std::abs(4.437500 - y1), epsilon); + ASSERT_LT(std::abs(90. - x2), epsilon); + ASSERT_LT(std::abs(30. - y2), epsilon); + + cairo_surface_destroy(surf); + cairo_destroy(cairo); +} + } }
--- a/gfx/cairo/cairo/src/cairo-path-bounds.c +++ b/gfx/cairo/cairo/src/cairo-path-bounds.c @@ -126,16 +126,17 @@ static cairo_status_t d->y < bounder->extents.p1.y || d->y > bounder->extents.p2.y) { return _cairo_spline_bound (_cairo_path_bounder_line_to, bounder, &bounder->current_point, b, c, d); } else { /* All control points are within the current extents. */ + bounder->current_point = *d; return CAIRO_STATUS_SUCCESS; } } static cairo_status_t _cairo_path_bounder_close_path (void *closure) { return CAIRO_STATUS_SUCCESS;
--- a/gfx/ipc/GfxMessageUtils.h +++ b/gfx/ipc/GfxMessageUtils.h @@ -728,16 +728,17 @@ struct ParamTraits<mozilla::layers::Fram WriteParam(aMsg, aParam.mExtraResolution); WriteParam(aMsg, aParam.mBackgroundColor); WriteParam(aMsg, aParam.mDoSmoothScroll); WriteParam(aMsg, aParam.mSmoothScrollOffset); WriteParam(aMsg, aParam.GetLineScrollAmount()); WriteParam(aMsg, aParam.GetPageScrollAmount()); WriteParam(aMsg, aParam.AllowVerticalScrollWithWheel()); WriteParam(aMsg, aParam.mClipRect); + WriteParam(aMsg, aParam.mIsLayersIdRoot); WriteParam(aMsg, aParam.GetContentDescription()); } static bool ReadContentDescription(const Message* aMsg, void** aIter, paramType* aResult) { nsCString str; if (!ReadParam(aMsg, aIter, &str)) { return false; @@ -771,16 +772,17 @@ struct ParamTraits<mozilla::layers::Fram ReadParam(aMsg, aIter, &aResult->mExtraResolution) && ReadParam(aMsg, aIter, &aResult->mBackgroundColor) && ReadParam(aMsg, aIter, &aResult->mDoSmoothScroll) && ReadParam(aMsg, aIter, &aResult->mSmoothScrollOffset) && ReadParam(aMsg, aIter, &aResult->mLineScrollAmount) && ReadParam(aMsg, aIter, &aResult->mPageScrollAmount) && ReadParam(aMsg, aIter, &aResult->mAllowVerticalScrollWithWheel) && ReadParam(aMsg, aIter, &aResult->mClipRect) && + ReadParam(aMsg, aIter, &aResult->mIsLayersIdRoot) && ReadContentDescription(aMsg, aIter, aResult)); } }; template<> struct ParamTraits<mozilla::layers::TextureFactoryIdentifier> { typedef mozilla::layers::TextureFactoryIdentifier paramType;
--- a/gfx/layers/FrameMetrics.h +++ b/gfx/layers/FrameMetrics.h @@ -62,16 +62,17 @@ public: , mUseDisplayPortMargins(false) , mPresShellId(-1) , mViewport(0, 0, 0, 0) , mExtraResolution() , mBackgroundColor(0, 0, 0, 0) , mLineScrollAmount(0, 0) , mPageScrollAmount(0, 0) , mAllowVerticalScrollWithWheel(false) + , mIsLayersIdRoot(false) { } // Default copy ctor and operator= are fine bool operator==(const FrameMetrics& aOther) const { return mCompositionBounds.IsEqualEdges(aOther.mCompositionBounds) && @@ -95,17 +96,18 @@ public: mUpdateScrollOffset == aOther.mUpdateScrollOffset && mScrollGeneration == aOther.mScrollGeneration && mExtraResolution == aOther.mExtraResolution && mBackgroundColor == aOther.mBackgroundColor && mDoSmoothScroll == aOther.mDoSmoothScroll && mLineScrollAmount == aOther.mLineScrollAmount && mPageScrollAmount == aOther.mPageScrollAmount && mAllowVerticalScrollWithWheel == aOther.mAllowVerticalScrollWithWheel && - mClipRect == aOther.mClipRect; + mClipRect == aOther.mClipRect && + mIsLayersIdRoot == aOther.mIsLayersIdRoot; } bool operator!=(const FrameMetrics& aOther) const { return !operator==(aOther); } bool IsDefault() const { @@ -520,16 +522,23 @@ public: } bool HasClipRect() const { return mClipRect.isSome(); } const ParentLayerIntRect& ClipRect() const { return mClipRect.ref(); } + void SetIsLayersIdRoot(bool aValue) { + mIsLayersIdRoot = aValue; + } + bool IsLayersIdRoot() const { + return mIsLayersIdRoot; + } + private: // The pres-shell resolution that has been induced on the document containing // this scroll frame as a result of zooming this scroll frame (whether via // user action, or choosing an initial zoom level on page load). This can // only be different from 1.0 for frames that are zoomable, which currently // is just the root content document's root scroll frame (mIsRoot = true). // This is a plain float rather than a ScaleFactor because in and of itself @@ -694,16 +703,20 @@ private: LayoutDeviceIntSize mPageScrollAmount; // Whether or not the frame can be vertically scrolled with a mouse wheel. bool mAllowVerticalScrollWithWheel; // The clip rect to use when compositing a layer with this FrameMetrics. Maybe<ParentLayerIntRect> mClipRect; + // Whether these framemetrics are for the root scroll frame (root element if + // we don't have a root scroll frame) for its layers id. + bool mIsLayersIdRoot; + // WARNING!!!! // // When adding new fields to FrameMetrics, the following places should be // updated to include them (as needed): // FrameMetrics::operator == // AsyncPanZoomController::NotifyLayersUpdated // The ParamTraits specialization in GfxMessageUtils.h //
--- a/gfx/layers/LayersLogging.cpp +++ b/gfx/layers/LayersLogging.cpp @@ -208,17 +208,17 @@ AppendToString(std::stringstream& aStrea aStream << sfx; } void AppendToString(std::stringstream& aStream, const ScrollableLayerGuid& s, const char* pfx, const char* sfx) { aStream << pfx - << nsPrintfCString("{ l=%llu, p=%u, v=%llu }", s.mLayersId, s.mPresShellId, s.mScrollId).get() + << nsPrintfCString("{ l=%" PRIu64 ", p=%u, v=%" PRIu64 " }", s.mLayersId, s.mPresShellId, s.mScrollId).get() << sfx; } void AppendToString(std::stringstream& aStream, const Matrix& m, const char* pfx, const char* sfx) { aStream << pfx;
--- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -275,30 +275,31 @@ GetEventRegions(const LayerMetricsWrappe return EventRegions(nsIntRegion(ParentLayerIntRect::ToUntyped( RoundedToInt(aLayer.Metrics().GetCompositionBounds())))); } return aLayer.GetEventRegions(); } already_AddRefed<HitTestingTreeNode> APZCTreeManager::RecycleOrCreateNode(TreeBuildingState& aState, - AsyncPanZoomController* aApzc) + AsyncPanZoomController* aApzc, + uint64_t aLayersId) { // Find a node without an APZC and return it. Note that unless the layer tree // actually changes, this loop should generally do an early-return on the // first iteration, so it should be cheap in the common case. for (size_t i = 0; i < aState.mNodesToDestroy.Length(); i++) { nsRefPtr<HitTestingTreeNode> node = aState.mNodesToDestroy[i]; if (!node->IsPrimaryHolder()) { aState.mNodesToDestroy.RemoveElement(node); - node->RecycleWith(aApzc); + node->RecycleWith(aApzc, aLayersId); return node.forget(); } } - nsRefPtr<HitTestingTreeNode> node = new HitTestingTreeNode(aApzc, false); + nsRefPtr<HitTestingTreeNode> node = new HitTestingTreeNode(aApzc, false, aLayersId); return node.forget(); } static EventRegionsOverride GetEventRegionsOverride(HitTestingTreeNode* aParent, const LayerMetricsWrapper& aLayer) { // Make it so that if the flag is set on the layer tree, it automatically @@ -328,17 +329,17 @@ APZCTreeManager::PrepareNodeForLayer(con const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId); if (!(state && state->mController.get())) { needsApzc = false; } nsRefPtr<HitTestingTreeNode> node = nullptr; if (!needsApzc) { - node = RecycleOrCreateNode(aState, nullptr); + node = RecycleOrCreateNode(aState, nullptr, aLayersId); AttachNodeToTree(node, aParent, aNextSibling); node->SetHitTestData(GetEventRegions(aLayer), aLayer.GetTransform(), aLayer.GetClipRect() ? Some(ParentLayerIntRegion(*aLayer.GetClipRect())) : Nothing(), GetEventRegionsOverride(aParent, aLayer)); return node; } AsyncPanZoomController* apzc = nullptr; @@ -408,17 +409,17 @@ APZCTreeManager::PrepareNodeForLayer(con bool newApzc = (apzc == nullptr || apzc->IsDestroyed()); if (newApzc) { apzc = MakeAPZCInstance(aLayersId, state->mController); apzc->SetCompositorParent(aState.mCompositor); if (state->mCrossProcessParent != nullptr) { apzc->ShareFrameMetricsAcrossProcesses(); } MOZ_ASSERT(node == nullptr); - node = new HitTestingTreeNode(apzc, true); + node = new HitTestingTreeNode(apzc, true, aLayersId); } else { // If we are re-using a node for this layer clear the tree pointers // so that it doesn't continue pointing to nodes that might no longer // be in the tree. These pointers will get reset properly as we continue // building the tree. Also remove it from the set of nodes that are going // to be destroyed, because it's going to remain active. aState.mNodesToDestroy.RemoveElement(node); node->SetPrevSibling(nullptr); @@ -447,28 +448,28 @@ APZCTreeManager::PrepareNodeForLayer(con // For testing, log the parent scroll id of every APZC that has a // parent. This allows test code to reconstruct the APZC tree. // Note that we currently only do this for APZCs in the layer tree // that originated the update, because the only identifying information // we are logging about APZCs is the scroll id, and otherwise we could // confuse APZCs from different layer trees with the same scroll id. if (aLayersId == aState.mOriginatingLayersId) { - if (apzc->IsRootForLayersId()) { + if (apzc->HasNoParentWithSameLayersId()) { aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), - "isRootForLayersId", true); + "hasNoParentWithSameLayersId", true); } else { MOZ_ASSERT(apzc->GetParent()); aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), "parentScrollId", apzc->GetParent()->GetGuid().mScrollId); } } if (newApzc) { - if (apzc->IsRootForLayersId()) { + if (apzc->HasNoParentWithSameLayersId()) { // If we just created a new apzc that is the root for its layers ID, then // we need to update its zoom constraints which might have arrived before this // was created ZoomConstraints constraints; if (state->mController->GetRootZoomConstraints(&constraints)) { apzc->UpdateZoomConstraints(constraints); } } else { @@ -482,17 +483,17 @@ APZCTreeManager::PrepareNodeForLayer(con // Add a guid -> APZC mapping for the newly created APZC. insertResult.first->second = apzc; } else { // We already built an APZC earlier in this tree walk, but we have another layer // now that will also be using that APZC. The hit-test region on the APZC needs // to be updated to deal with the new layer's hit region. - node = RecycleOrCreateNode(aState, apzc); + node = RecycleOrCreateNode(aState, apzc, aLayersId); AttachNodeToTree(node, aParent, aNextSibling); // Even though different layers associated with a given APZC may be at // different levels in the layer tree (e.g. one being an uncle of another), // we require from Layout that the CSS transforms up to their common // ancestor be the same. MOZ_ASSERT(aAncestorTransform == apzc->GetAncestorTransform()); @@ -1034,34 +1035,34 @@ APZCTreeManager::UpdateZoomConstraints(c const ZoomConstraints& aConstraints) { MonitorAutoLock lock(mTreeLock); nsRefPtr<HitTestingTreeNode> node = GetTargetNode(aGuid, nullptr); MOZ_ASSERT(!node || node->GetApzc()); // any node returned must have an APZC // For a given layers id, non-root APZCs inherit the zoom constraints // of their root. - if (node && node->GetApzc()->IsRootForLayersId()) { + if (node && node->GetApzc()->HasNoParentWithSameLayersId()) { UpdateZoomConstraintsRecursively(node.get(), aConstraints); } } void APZCTreeManager::UpdateZoomConstraintsRecursively(HitTestingTreeNode* aNode, const ZoomConstraints& aConstraints) { mTreeLock.AssertCurrentThreadOwns(); if (aNode->IsPrimaryHolder()) { MOZ_ASSERT(aNode->GetApzc()); aNode->GetApzc()->UpdateZoomConstraints(aConstraints); } for (HitTestingTreeNode* child = aNode->GetLastChild(); child; child = child->GetPrevSibling()) { // We can have subtrees with their own layers id - leave those alone. - if (child->GetApzc() && child->GetApzc()->IsRootForLayersId()) { + if (child->GetApzc() && child->GetApzc()->HasNoParentWithSameLayersId()) { continue; } UpdateZoomConstraintsRecursively(child, aConstraints); } } void APZCTreeManager::FlushRepaintsToClearScreenToGeckoTransform() @@ -1338,17 +1339,17 @@ APZCTreeManager::BuildOverscrollHandoffC // but do not follow the expected layer tree structure. If there are no // scroll parent links we just walk up the tree to find the scroll parent. OverscrollHandoffChain* result = new OverscrollHandoffChain; AsyncPanZoomController* apzc = aInitialTarget; while (apzc != nullptr) { result->Add(apzc); if (apzc->GetScrollHandoffParentId() == FrameMetrics::NULL_SCROLL_ID) { - if (!apzc->IsRootForLayersId()) { + if (!apzc->HasNoParentWithSameLayersId()) { // This probably indicates a bug or missed case in layout code NS_WARNING("Found a non-root APZ with no handoff parent"); } apzc = apzc->GetParent(); continue; } // Guard against a possible infinite-loop condition. If we hit this, the @@ -1356,17 +1357,17 @@ APZCTreeManager::BuildOverscrollHandoffC MOZ_ASSERT(apzc->GetScrollHandoffParentId() != apzc->GetGuid().mScrollId); // Find the AsyncPanZoomController instance with a matching layersId and // the scroll id that matches apzc->GetScrollHandoffParentId(). To do this // search the subtree with the same layersId for the apzc with the specified // scroll id. AsyncPanZoomController* scrollParent = nullptr; AsyncPanZoomController* parent = apzc; - while (!parent->IsRootForLayersId()) { + while (!parent->HasNoParentWithSameLayersId()) { parent = parent->GetParent(); // While walking up to find the root of the subtree, if we encounter the // handoff parent, we don't actually need to do the search so we can // just abort here. if (parent->GetGuid().mScrollId == apzc->GetScrollHandoffParentId()) { scrollParent = parent; break; } @@ -1456,33 +1457,66 @@ APZCTreeManager::GetAPZCAtPoint(HitTesti // If we didn't match anything in the subtree, check |node|. if (*aOutHitResult == HitNothing) { APZCTM_LOG("Testing ParentLayer point %s (Layer %s) against node %p\n", Stringify(aHitTestPoint).c_str(), hitTestPointForChildLayers ? Stringify(hitTestPointForChildLayers.ref()).c_str() : "nil", node); HitTestResult hitResult = node->HitTest(aHitTestPoint); if (hitResult != HitTestResult::HitNothing) { - result = node->GetNearestContainingApzc(); + result = node->GetNearestContainingApzcWithSameLayersId(); + if (!result) { + result = FindRootApzcForLayersId(node->GetLayersId()); + MOZ_ASSERT(result); + } APZCTM_LOG("Successfully matched APZC %p via node %p (hit result %d)\n", result, node, hitResult); MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion); // If event regions are disabled, *aOutHitResult will be HitLayer *aOutHitResult = hitResult; } } if (*aOutHitResult != HitNothing) { return result; } } return nullptr; } +AsyncPanZoomController* +APZCTreeManager::FindRootApzcForLayersId(uint64_t aLayersId) const +{ + mTreeLock.AssertCurrentThreadOwns(); + + if (!mRootNode) { + return nullptr; + } + std::deque<const HitTestingTreeNode*> queue; + queue.push_back(mRootNode); + while (!queue.empty()) { + const HitTestingTreeNode* node = queue.front(); + queue.pop_front(); + + AsyncPanZoomController* apzc = node->GetApzc(); + if (apzc && apzc->GetLayersId() == aLayersId && apzc->IsRootForLayersId()) { + return apzc; + } + + for (HitTestingTreeNode* child = node->GetLastChild(); + child; + child = child->GetPrevSibling()) { + queue.push_back(child); + } + } + + return nullptr; +} + /* The methods GetScreenToApzcTransform() and GetApzcToGeckoTransform() return some useful transformations that input events may need applied. This is best illustrated with an example. Consider a chain of layers, L, M, N, O, P, Q, R. Layer L is the layer that corresponds to the argument |aApzc|, and layer R is the root of the layer tree. Layer M is the parent of L, N is the parent of M, and so on. When layer L is displayed to the screen by the compositor, the set of transforms that are applied to L are (in order from top to bottom): @@ -1699,16 +1733,16 @@ APZCTreeManager::CommonAncestor(AsyncPan return ancestor.forget(); } already_AddRefed<AsyncPanZoomController> APZCTreeManager::RootAPZCForLayersId(AsyncPanZoomController* aApzc) const { MonitorAutoLock lock(mTreeLock); nsRefPtr<AsyncPanZoomController> apzc = aApzc; - while (apzc && !apzc->IsRootForLayersId()) { + while (apzc && !apzc->HasNoParentWithSameLayersId()) { apzc = apzc->GetParent(); } return apzc.forget(); } } }
--- a/gfx/layers/apz/src/APZCTreeManager.h +++ b/gfx/layers/apz/src/APZCTreeManager.h @@ -407,16 +407,17 @@ private: already_AddRefed<HitTestingTreeNode> GetTargetNode(const ScrollableLayerGuid& aGuid, GuidComparator aComparator); HitTestingTreeNode* FindTargetNode(HitTestingTreeNode* aNode, const ScrollableLayerGuid& aGuid, GuidComparator aComparator); AsyncPanZoomController* GetAPZCAtPoint(HitTestingTreeNode* aNode, const ParentLayerPoint& aHitTestPoint, HitTestResult* aOutHitResult); + AsyncPanZoomController* FindRootApzcForLayersId(uint64_t aLayersId) const; already_AddRefed<AsyncPanZoomController> GetMultitouchTarget(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const; already_AddRefed<AsyncPanZoomController> CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const; already_AddRefed<AsyncPanZoomController> RootAPZCForLayersId(AsyncPanZoomController* aApzc) const; already_AddRefed<AsyncPanZoomController> GetTouchInputBlockAPZC(const MultiTouchInput& aEvent, HitTestResult* aOutHitResult); nsEventStatus ProcessTouchInput(MultiTouchInput& aInput, ScrollableLayerGuid* aOutTargetGuid, uint64_t* aOutInputBlockId); @@ -428,17 +429,18 @@ private: uint64_t* aOutInputBlockId); void UpdateWheelTransaction(WidgetInputEvent& aEvent); void UpdateZoomConstraintsRecursively(HitTestingTreeNode* aNode, const ZoomConstraints& aConstraints); void FlushRepaintsToClearScreenToGeckoTransform(); void FlushRepaintsRecursively(HitTestingTreeNode* aNode); already_AddRefed<HitTestingTreeNode> RecycleOrCreateNode(TreeBuildingState& aState, - AsyncPanZoomController* aApzc); + AsyncPanZoomController* aApzc, + uint64_t aLayersId); HitTestingTreeNode* PrepareNodeForLayer(const LayerMetricsWrapper& aLayer, const FrameMetrics& aMetrics, uint64_t aLayersId, const gfx::Matrix4x4& aAncestorTransform, HitTestingTreeNode* aParent, HitTestingTreeNode* aNextSibling, TreeBuildingState& aState);
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -2895,16 +2895,17 @@ void AsyncPanZoomController::NotifyLayer mFrameMetrics.SetCompositionBounds(aLayerMetrics.GetCompositionBounds()); mFrameMetrics.SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize()); mFrameMetrics.SetPresShellResolution(aLayerMetrics.GetPresShellResolution()); mFrameMetrics.SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution()); mFrameMetrics.SetHasScrollgrab(aLayerMetrics.GetHasScrollgrab()); mFrameMetrics.SetLineScrollAmount(aLayerMetrics.GetLineScrollAmount()); mFrameMetrics.SetPageScrollAmount(aLayerMetrics.GetPageScrollAmount()); mFrameMetrics.SetClipRect(aLayerMetrics.GetClipRect()); + mFrameMetrics.SetIsLayersIdRoot(aLayerMetrics.IsLayersIdRoot()); if (scrollOffsetUpdated) { APZC_LOG("%p updating scroll offset from %s to %s\n", this, ToString(mFrameMetrics.GetScrollOffset()).c_str(), ToString(aLayerMetrics.GetScrollOffset()).c_str()); mFrameMetrics.CopyScrollInfoFrom(aLayerMetrics);
--- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -894,20 +894,26 @@ public: mParent = aParent; } AsyncPanZoomController* GetParent() const { return mParent; } /* Returns true if there is no APZC higher in the tree with the same - * layers id. + * layers id. Deprecated. New code shouldn't use this. Old code should be + * updated to not use this. */ + bool HasNoParentWithSameLayersId() const { + return !mParent || (mParent->mLayersId != mLayersId); + } + bool IsRootForLayersId() const { - return !mParent || (mParent->mLayersId != mLayersId); + ReentrantMonitorAutoEnter lock(mMonitor); + return mFrameMetrics.IsLayersIdRoot(); } private: // This is a raw pointer to avoid introducing a reference cycle between // AsyncPanZoomController and APZCTreeManager. Since these objects don't // live on the main thread, we can't use the cycle collector with them. // The APZCTreeManager owns the lifetime of the APZCs, so nulling this // pointer out in Destroy() will prevent accessing deleted memory. @@ -1082,16 +1088,21 @@ public: mAsyncTransformAppliedToContent = true; } bool GetAsyncTransformAppliedToContent() const { return mAsyncTransformAppliedToContent; } + uint64_t GetLayersId() const + { + return mLayersId; + } + private: // Extra offset to add in SampleContentTransformForFrame for testing CSSPoint mTestAsyncScrollOffset; // Extra zoom to include in SampleContentTransformForFrame for testing LayerToParentLayerScale mTestAsyncZoom; // Flag to track whether or not the APZ transform is not used. This // flag is recomputed for every composition frame. bool mAsyncTransformAppliedToContent;
--- a/gfx/layers/apz/src/HitTestingTreeNode.cpp +++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp @@ -13,32 +13,38 @@ #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform::operator Matrix4x4() #include "nsPrintfCString.h" // for nsPrintfCString #include "UnitTransforms.h" // for ViewAs namespace mozilla { namespace layers { HitTestingTreeNode::HitTestingTreeNode(AsyncPanZoomController* aApzc, - bool aIsPrimaryHolder) + bool aIsPrimaryHolder, + uint64_t aLayersId) : mApzc(aApzc) , mIsPrimaryApzcHolder(aIsPrimaryHolder) + , mLayersId(aLayersId) , mOverride(EventRegionsOverride::NoOverride) { if (mIsPrimaryApzcHolder) { MOZ_ASSERT(mApzc); } + MOZ_ASSERT(!mApzc || mApzc->GetLayersId() == mLayersId); } void -HitTestingTreeNode::RecycleWith(AsyncPanZoomController* aApzc) +HitTestingTreeNode::RecycleWith(AsyncPanZoomController* aApzc, + uint64_t aLayersId) { MOZ_ASSERT(!mIsPrimaryApzcHolder); Destroy(); // clear out tree pointers mApzc = aApzc; + mLayersId = aLayersId; + MOZ_ASSERT(!mApzc || mApzc->GetLayersId() == mLayersId); // The caller is expected to call SetHitTestData to repopulate the hit-test // fields. } HitTestingTreeNode::~HitTestingTreeNode() { } @@ -52,16 +58,18 @@ HitTestingTreeNode::Destroy() mParent = nullptr; if (mApzc) { if (mIsPrimaryApzcHolder) { mApzc->Destroy(); } mApzc = nullptr; } + + mLayersId = 0; } void HitTestingTreeNode::SetLastChild(HitTestingTreeNode* aChild) { mLastChild = aChild; if (aChild) { aChild->mParent = this; @@ -142,22 +150,41 @@ HitTestingTreeNode::GetNearestContaining for (const HitTestingTreeNode* n = this; n; n = n->GetParent()) { if (n->GetApzc()) { return n->GetApzc(); } } return nullptr; } +AsyncPanZoomController* +HitTestingTreeNode::GetNearestContainingApzcWithSameLayersId() const +{ + for (const HitTestingTreeNode* n = this; + n && n->mLayersId == mLayersId; + n = n->GetParent()) { + if (n->GetApzc()) { + return n->GetApzc(); + } + } + return nullptr; +} + bool HitTestingTreeNode::IsPrimaryHolder() const { return mIsPrimaryApzcHolder; } +uint64_t +HitTestingTreeNode::GetLayersId() const +{ + return mLayersId; +} + void HitTestingTreeNode::SetHitTestData(const EventRegions& aRegions, const gfx::Matrix4x4& aTransform, const Maybe<ParentLayerIntRegion>& aClipRegion, const EventRegionsOverride& aOverride) { mEventRegions = aRegions; mTransform = aTransform; @@ -224,17 +251,18 @@ HitTestingTreeNode::GetEventRegionsOverr void HitTestingTreeNode::Dump(const char* aPrefix) const { if (mPrevSibling) { mPrevSibling->Dump(aPrefix); } printf_stderr("%sHitTestingTreeNode (%p) APZC (%p) g=(%s) %s%sr=(%s) t=(%s) c=(%s)\n", - aPrefix, this, mApzc.get(), mApzc ? Stringify(mApzc->GetGuid()).c_str() : "", + aPrefix, this, mApzc.get(), + mApzc ? Stringify(mApzc->GetGuid()).c_str() : nsPrintfCString("l=%" PRIu64, mLayersId).get(), (mOverride & EventRegionsOverride::ForceDispatchToContent) ? "fdtc " : "", (mOverride & EventRegionsOverride::ForceEmptyHitRegion) ? "fehr " : "", Stringify(mEventRegions).c_str(), Stringify(mTransform).c_str(), mClipRegion ? Stringify(mClipRegion.ref()).c_str() : "none"); if (mLastChild) { mLastChild->Dump(nsPrintfCString("%s ", aPrefix).get()); } }
--- a/gfx/layers/apz/src/HitTestingTreeNode.h +++ b/gfx/layers/apz/src/HitTestingTreeNode.h @@ -48,18 +48,19 @@ class AsyncPanZoomController; * properties into a separate tree. */ class HitTestingTreeNode { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(HitTestingTreeNode); private: ~HitTestingTreeNode(); public: - HitTestingTreeNode(AsyncPanZoomController* aApzc, bool aIsPrimaryHolder); - void RecycleWith(AsyncPanZoomController* aApzc); + HitTestingTreeNode(AsyncPanZoomController* aApzc, bool aIsPrimaryHolder, + uint64_t aLayersId); + void RecycleWith(AsyncPanZoomController* aApzc, uint64_t aLayersId); void Destroy(); /* Tree construction methods */ void SetLastChild(HitTestingTreeNode* aChild); void SetPrevSibling(HitTestingTreeNode* aSibling); void MakeRoot(); @@ -70,17 +71,19 @@ public: HitTestingTreeNode* GetLastChild() const; HitTestingTreeNode* GetPrevSibling() const; HitTestingTreeNode* GetParent() const; /* APZC related methods */ AsyncPanZoomController* GetApzc() const; AsyncPanZoomController* GetNearestContainingApzc() const; + AsyncPanZoomController* GetNearestContainingApzcWithSameLayersId() const; bool IsPrimaryHolder() const; + uint64_t GetLayersId() const; /* Hit test related methods */ void SetHitTestData(const EventRegions& aRegions, const gfx::Matrix4x4& aTransform, const Maybe<ParentLayerIntRegion>& aClipRegion, const EventRegionsOverride& aOverride); bool IsOutsideClip(const ParentLayerPoint& aPoint) const; @@ -101,16 +104,18 @@ private: nsRefPtr<HitTestingTreeNode> mLastChild; nsRefPtr<HitTestingTreeNode> mPrevSibling; nsRefPtr<HitTestingTreeNode> mParent; nsRefPtr<AsyncPanZoomController> mApzc; bool mIsPrimaryApzcHolder; + uint64_t mLayersId; + /* Let {L,M} be the {layer, scrollable metrics} pair that this node * corresponds to in the layer tree. mEventRegions contains the event regions * from L, in the case where event-regions are enabled. If event-regions are * disabled, it will contain the visible region of L, which we use as an * approximation to the hit region for the purposes of obscuring other layers. * This value is in L's LayerPixels. */ EventRegions mEventRegions;
--- a/gfx/layers/apz/test/apz_test_utils.js +++ b/gfx/layers/apz/test/apz_test_utils.js @@ -86,16 +86,16 @@ function addRoot(root, id) { // content process that triggered the paint, is reconstructed (as // the APZ test data only contains information abot this subtree). function buildApzcTree(paint) { // The APZC tree can potentially have multiple root nodes, // so we invent a node that is the parent of all roots. // This 'root' does not correspond to an APZC. var root = makeNode(-1); for (var scrollId in paint) { - if ("isRootForLayersId" in paint[scrollId]) { + if ("hasNoParentWithSameLayersId" in paint[scrollId]) { addRoot(root, scrollId); } else if ("parentScrollId" in paint[scrollId]) { addLink(root, scrollId, paint[scrollId]["parentScrollId"]); } } return root; }
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp +++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp @@ -1889,16 +1889,20 @@ protected: nsRefPtr<TestAPZCTreeManager> manager; protected: static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId, CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) { FrameMetrics metrics; metrics.SetScrollId(aScrollId); + // By convention in this test file, START_SCROLL_ID is the root, so mark it as such. + if (aScrollId == FrameMetrics::START_SCROLL_ID) { + metrics.SetIsLayersIdRoot(true); + } IntRect layerBound = aLayer->GetVisibleRegion().GetBounds(); metrics.SetCompositionBounds(ParentLayerRect(layerBound.x, layerBound.y, layerBound.width, layerBound.height)); metrics.SetScrollableRect(aScrollableRect); metrics.SetScrollOffset(CSSPoint(0, 0)); metrics.SetPageScrollAmount(LayoutDeviceIntSize(50, 100)); metrics.SetAllowVerticalScrollWithWheel(); aLayer->SetFrameMetrics(metrics); @@ -2859,26 +2863,28 @@ protected: registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc); manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); rootApzc = ApzcOf(root); } void CreateBug1119497LayerTree() { const char* layerTreeSyntax = "c(tt)"; // LayerID 0 12 - // 0 is the root and doesn't have an APZC - // 1 is behind 2 and does have an APZC - // 2 entirely covers 1 and should take all the input events + // 0 is the root and has an APZC + // 1 is behind 2 and has an APZC + // 2 entirely covers 1 and should take all the input events, but has no APZC + // so hits to 2 should go to to the root APZC nsIntRegion layerVisibleRegions[] = { nsIntRegion(IntRect(0, 0, 100, 100)), nsIntRegion(IntRect(0, 0, 100, 100)), nsIntRegion(IntRect(0, 0, 100, 100)), }; root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers); + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1); registration = MakeUnique<ScopedLayerTreeRegistration>(0, root, mcc); manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); } void CreateBug1117712LayerTree() { const char* layerTreeSyntax = "c(c(t)t)"; @@ -3001,18 +3007,18 @@ TEST_F(APZEventRegionsTester, Obscuratio } TEST_F(APZEventRegionsTester, Bug1119497) { CreateBug1119497LayerTree(); HitTestResult result; nsRefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result); // We should hit layers[2], so |result| will be HitLayer but there's no - // actual APZC in that parent chain, so |hit| should be nullptr. - EXPECT_EQ(nullptr, hit.get()); + // actual APZC on layers[2], so it will be the APZC of the root layer. + EXPECT_EQ(ApzcOf(layers[0]), hit.get()); EXPECT_EQ(HitTestResult::HitLayer, result); } TEST_F(APZEventRegionsTester, Bug1117712) { CreateBug1117712LayerTree(); TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]);
--- a/gfx/tests/gtest/moz.build +++ b/gfx/tests/gtest/moz.build @@ -27,16 +27,17 @@ UNIFIED_SOURCES += [ ] # Because of gkmedia on windows we won't find these # symbols in xul.dll. if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'windows': UNIFIED_SOURCES += [ '/gfx/2d/unittest/%s' % p for p in [ 'TestBase.cpp', 'TestBugs.cpp', + 'TestCairo.cpp', 'TestPoint.cpp', 'TestScaling.cpp', ]] UNIFIED_SOURCES += [ 'TestMoz2D.cpp', 'TestRect.cpp', ]
--- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -2244,17 +2244,17 @@ gfxFont::Measure(gfxTextRun *aTextRun, x += direction*advance; } else { allGlyphsInvisible = false; uint32_t glyphCount = glyphData->GetGlyphCount(); if (glyphCount > 0) { const gfxTextRun::DetailedGlyph *details = aTextRun->GetDetailedGlyphs(i); NS_ASSERTION(details != nullptr, - "detaiedGlyph record should not be missing!"); + "detailedGlyph record should not be missing!"); uint32_t j; for (j = 0; j < glyphCount; ++j, ++details) { uint32_t glyphIndex = details->mGlyphID; gfxPoint glyphPt(x + details->mXOffset, details->mYOffset); double advance = details->mAdvance; gfxRect glyphRect; if (glyphData->IsMissing() || !extents || !extents->GetTightGlyphExtentsAppUnits(this,
--- a/gfx/thebes/gfxGDIFont.cpp +++ b/gfx/thebes/gfxGDIFont.cpp @@ -332,36 +332,37 @@ gfxGDIFont::Initialize() ROUND((ascent - descent + lineGap) * mFUnitsConvFactor); lineHeight = std::max(lineHeight, mMetrics->maxHeight); mMetrics->externalLeading = lineHeight - mMetrics->maxHeight; } } } - // Cache the width of a single space. + WORD glyph; SIZE size; - GetTextExtentPoint32W(dc.GetDC(), L" ", 1, &size); - mMetrics->spaceWidth = ROUND(size.cx); - - // Cache the width of digit zero. - // XXX MSDN (http://msdn.microsoft.com/en-us/library/ms534223.aspx) - // does not say what the failure modes for GetTextExtentPoint32 are - - // is it safe to assume it will fail iff the font has no '0'? - if (GetTextExtentPoint32W(dc.GetDC(), L"0", 1, &size)) { - mMetrics->zeroOrAveCharWidth = ROUND(size.cx); - } else { - mMetrics->zeroOrAveCharWidth = mMetrics->aveCharWidth; - } - - WORD glyph; DWORD ret = GetGlyphIndicesW(dc.GetDC(), L" ", 1, &glyph, GGI_MARK_NONEXISTING_GLYPHS); if (ret != GDI_ERROR && glyph != 0xFFFF) { mSpaceGlyph = glyph; + // Cache the width of a single space. + GetTextExtentPoint32W(dc.GetDC(), L" ", 1, &size); + mMetrics->spaceWidth = ROUND(size.cx); + } else { + mMetrics->spaceWidth = mMetrics->aveCharWidth; + } + + // Cache the width of digit zero, if available. + ret = GetGlyphIndicesW(dc.GetDC(), L"0", 1, &glyph, + GGI_MARK_NONEXISTING_GLYPHS); + if (ret != GDI_ERROR && glyph != 0xFFFF) { + GetTextExtentPoint32W(dc.GetDC(), L"0", 1, &size); + mMetrics->zeroOrAveCharWidth = ROUND(size.cx); + } else { + mMetrics->zeroOrAveCharWidth = mMetrics->aveCharWidth; } SanitizeMetrics(mMetrics, GetFontEntry()->mIsBadUnderlineFont); } if (IsSyntheticBold()) { mMetrics->aveCharWidth += GetSyntheticBoldOffset(); mMetrics->maxAdvance += GetSyntheticBoldOffset();
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp +++ b/gfx/thebes/gfxHarfBuzzShaper.cpp @@ -1786,20 +1786,28 @@ gfxHarfBuzzShaper::SetGlyphsFromRun(gfxC gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) && charGlyphs[baseCharIndex].IsClusterStart() && iOffset == 0 && b_offset == 0 && b_advance == 0 && bPos == 0) { charGlyphs[baseCharIndex].SetSimpleGlyph(advance, ginfo[glyphStart].codepoint); } else { - // collect all glyphs in a list to be assigned to the first char; + // Collect all glyphs in a list to be assigned to the first char; // there must be at least one in the clump, and we already measured // its advance, hence the placement of the loop-exit test and the - // measurement of the next glyph + // measurement of the next glyph. + // For vertical orientation, we add a "base offset" to compensate + // for the positioning within the cluster being based on horizontal + // glyph origin/offset. + hb_position_t baseIOffset, baseBOffset; + if (aVertical) { + baseIOffset = 2 * (i_offset - i_advance); + baseBOffset = GetGlyphHAdvance(ginfo[glyphStart].codepoint); + } while (1) { gfxTextRun::DetailedGlyph* details = detailedGlyphs.AppendElement(); details->mGlyphID = ginfo[glyphStart].codepoint; details->mXOffset = iOffset; details->mAdvance = advance; @@ -1812,19 +1820,19 @@ gfxHarfBuzzShaper::SetGlyphsFromRun(gfxC roundB ? appUnitsPerDevUnit * FixedToIntRound(b_advance) : floor(hb2appUnits * b_advance + 0.5); } if (++glyphStart >= glyphEnd) { break; } if (aVertical) { - i_offset = posInfo[glyphStart].y_offset; + i_offset = baseIOffset - posInfo[glyphStart].y_offset; i_advance = posInfo[glyphStart].y_advance; - b_offset = posInfo[glyphStart].x_offset; + b_offset = baseBOffset - posInfo[glyphStart].x_offset; b_advance = posInfo[glyphStart].x_advance; } else { i_offset = posInfo[glyphStart].x_offset; i_advance = posInfo[glyphStart].x_advance; b_offset = posInfo[glyphStart].y_offset; b_advance = posInfo[glyphStart].y_advance; }
--- a/js/public/GCAPI.h +++ b/js/public/GCAPI.h @@ -331,17 +331,18 @@ enum GCProgress { struct JS_PUBLIC_API(GCDescription) { bool isCompartment_; JSGCInvocationKind invocationKind_; GCDescription(bool isCompartment, JSGCInvocationKind kind) : isCompartment_(isCompartment), invocationKind_(kind) {} - char16_t* formatMessage(JSRuntime* rt) const; + char16_t* formatSliceMessage(JSRuntime* rt) const; + char16_t* formatSummaryMessage(JSRuntime* rt) const; char16_t* formatJSON(JSRuntime* rt, uint64_t timestamp) const; JS::dbg::GarbageCollectionEvent::Ptr toGCEvent(JSRuntime* rt) const; }; typedef void (* GCSliceCallback)(JSRuntime* rt, GCProgress progress, const GCDescription& desc);
--- a/js/public/RootingAPI.h +++ b/js/public/RootingAPI.h @@ -71,17 +71,17 @@ * then redundant rooting of multiple copies of the GC thing can be avoided. * Second, if the caller does not pass a rooted value a compile error will be * generated, which is quicker and easier to fix than when relying on a * separate rooting analysis. * * - MutableHandle<T> is a non-const reference to Rooted<T>. It is used in the * same way as Handle<T> and includes a |set(const T& v)| method to allow * updating the value of the referenced Rooted<T>. A MutableHandle<T> can be - * created from a Rooted<T> by using |Rooted<T>::operator&()|. + * created with an implicit cast from a Rooted<T>*. * * In some cases the small performance overhead of exact rooting (measured to * be a few nanoseconds on desktop) is too much. In these cases, try the * following: * * - Move all Rooted<T> above inner loops: this allows you to re-use the root * on each iteration of the loop. * @@ -1180,12 +1180,10 @@ CallTraceCallbackOnNonHeap(T* v, const T JS::Heap<T>* asHeapT = reinterpret_cast<JS::Heap<T>*>(v); aCallbacks.Trace(asHeapT, aName, aClosure); } } /* namespace gc */ } /* namespace js */ #undef DELETE_ASSIGNMENT_OPS -#undef DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS -#undef DECLARE_NONPOINTER_ACCESSOR_METHODS #endif /* js_RootingAPI_h */
--- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -28,220 +28,16 @@ using namespace js::gc; using namespace js::gcstats; using mozilla::PodArrayZero; using mozilla::PodZero; /* Except for the first and last, slices of less than 10ms are not reported. */ static const int64_t SLICE_MIN_REPORT_TIME = 10 * PRMJ_USEC_PER_MSEC; -class gcstats::StatisticsSerializer -{ - typedef Vector<char, 128, SystemAllocPolicy> CharBuffer; - CharBuffer buf_; - bool asJSON_; - bool needComma_; - bool oom_; - - static const int MaxFieldValueLength = 128; - - public: - enum Mode { - AsJSON = true, - AsText = false - }; - - explicit StatisticsSerializer(Mode asJSON) - : buf_(), asJSON_(asJSON), needComma_(false), oom_(false) - {} - - bool isJSON() { return asJSON_; } - - bool isOOM() { return oom_; } - - void endLine() { - if (!asJSON_) { - p("\n"); - needComma_ = false; - } - } - - void extra(const char* str) { - if (!asJSON_) { - needComma_ = false; - p(str); - } - } - - void appendString(const char* name, const char* value) { - put(name, value, "", true); - } - - void appendNumber(const char* name, const char* vfmt, const char* units, ...) { - va_list va; - va_start(va, units); - append(name, vfmt, va, units); - va_end(va); - } - - void appendDecimal(const char* name, const char* units, double d) { - if (d < 0) - d = 0; - if (asJSON_) - appendNumber(name, "%d.%03d", units, (int)d, (int)(d * 1000.) % 1000); - else - appendNumber(name, "%.1f", units, d); - } - - void appendIfNonzeroMS(const char* name, double v) { - if (asJSON_ || v >= 0.1) - appendDecimal(name, "ms", v); - } - - void beginObject(const char* name) { - if (needComma_) - pJSON(", "); - if (asJSON_ && name) { - putKey(name); - pJSON(": "); - } - pJSON("{"); - needComma_ = false; - } - - void endObject() { - needComma_ = false; - pJSON("}"); - needComma_ = true; - } - - void beginArray(const char* name) { - if (needComma_) - pJSON(", "); - if (asJSON_) - putKey(name); - pJSON(": ["); - needComma_ = false; - } - - void endArray() { - needComma_ = false; - pJSON("]"); - needComma_ = true; - } - - char16_t* finishJSString() { - char* buf = finishCString(); - if (!buf) - return nullptr; - - size_t nchars = strlen(buf); - char16_t* out = js_pod_malloc<char16_t>(nchars + 1); - if (!out) { - oom_ = true; - js_free(buf); - return nullptr; - } - - CopyAndInflateChars(out, buf, nchars); - js_free(buf); - - out[nchars] = 0; - return out; - } - - char* finishCString() { - if (oom_) - return nullptr; - - buf_.append('\0'); - - char* buf = buf_.extractRawBuffer(); - if (!buf) - oom_ = true; - - return buf; - } - - private: - void append(const char* name, const char* vfmt, - va_list va, const char* units) - { - char val[MaxFieldValueLength]; - JS_vsnprintf(val, MaxFieldValueLength, vfmt, va); - put(name, val, units, false); - } - - void p(const char* cstr) { - if (oom_) - return; - - if (!buf_.append(cstr, strlen(cstr))) - oom_ = true; - } - - void p(const char c) { - if (oom_) - return; - - if (!buf_.append(c)) - oom_ = true; - } - - void pJSON(const char* str) { - if (asJSON_) - p(str); - } - - void put(const char* name, const char* val, const char* units, bool valueIsQuoted) { - if (needComma_) - p(", "); - needComma_ = true; - - putKey(name); - p(": "); - if (valueIsQuoted) - putQuoted(val); - else - p(val); - if (!asJSON_) - p(units); - } - - void putQuoted(const char* str) { - pJSON("\""); - p(str); - pJSON("\""); - } - - void putKey(const char* str) { - if (!asJSON_) { - p(str); - return; - } - - p("\""); - const char* c = str; - while (*c) { - if (*c == ' ' || *c == '\t') - p('_'); - else if (isupper(*c)) - p(tolower(*c)); - else if (*c == '+') - p("added_"); - else if (*c == '-') - p("removed_"); - else if (*c != '(' && *c != ')') - p(*c); - c++; - } - p("\""); - } -}; - /* * If this fails, then you can either delete this assertion and allow all * larger-numbered reasons to pile up in the last telemetry bucket, or switch * to GC_REASON_3 and bump the max value. */ JS_STATIC_ASSERT(JS::gcreason::NUM_TELEMETRY_REASONS >= JS::gcreason::NUM_REASONS); const char* @@ -438,35 +234,21 @@ struct AllPhaseIterator { ++current; } bool done() const { return phases[current].parent == PHASE_MULTI_PARENTS; } }; -static void -FormatPhaseTimes(StatisticsSerializer& ss, const char* name, Statistics::PhaseTimeTable times) -{ - ss.beginObject(name); - - for (AllPhaseIterator iter(times); !iter.done(); iter.advance()) { - Phase phase; - size_t dagSlot; - iter.get(&phase, &dagSlot); - ss.appendIfNonzeroMS(phases[phase].name, t(times[dagSlot][phase])); - } - ss.endObject(); -} - void -Statistics::gcDuration(int64_t* total, int64_t* maxPause) +Statistics::gcDuration(int64_t* total, int64_t* maxPause) const { *total = *maxPause = 0; - for (SliceData* slice = slices.begin(); slice != slices.end(); slice++) { + for (const SliceData* slice = slices.begin(); slice != slices.end(); slice++) { *total += slice->duration(); if (slice->duration() > *maxPause) *maxPause = slice->duration(); } if (*maxPause > maxPauseInInterval) maxPauseInInterval = *maxPause; } @@ -475,100 +257,16 @@ Statistics::sccDurations(int64_t* total, { *total = *maxPause = 0; for (size_t i = 0; i < sccTimes.length(); i++) { *total += sccTimes[i]; *maxPause = Max(*maxPause, sccTimes[i]); } } -bool -Statistics::formatData(StatisticsSerializer& ss, uint64_t timestamp) -{ - MOZ_ASSERT(!aborted); - - int64_t total, longest; - gcDuration(&total, &longest); - - int64_t sccTotal, sccLongest; - sccDurations(&sccTotal, &sccLongest); - - double mmu20 = computeMMU(20 * PRMJ_USEC_PER_MSEC); - double mmu50 = computeMMU(50 * PRMJ_USEC_PER_MSEC); - - ss.beginObject(nullptr); - if (ss.isJSON()) - ss.appendNumber("Timestamp", "%llu", "", (unsigned long long)timestamp); - if (slices.length() > 1 || ss.isJSON()) - ss.appendDecimal("Max Pause", "ms", t(longest)); - else - ss.appendString("Reason", ExplainReason(slices[0].reason)); - ss.appendDecimal("Total Time", "ms", t(total)); - ss.appendNumber("Zones Collected", "%d", "", zoneStats.collectedZoneCount); - ss.appendNumber("Total Zones", "%d", "", zoneStats.zoneCount); - ss.appendNumber("Total Compartments", "%d", "", zoneStats.compartmentCount); - ss.appendNumber("Minor GCs", "%d", "", counts[STAT_MINOR_GC]); - ss.appendNumber("Store Buffer Overflows", "%d", "", counts[STAT_STOREBUFFER_OVERFLOW]); - ss.appendNumber("MMU (20ms)", "%d", "%", int(mmu20 * 100)); - ss.appendNumber("MMU (50ms)", "%d", "%", int(mmu50 * 100)); - ss.appendDecimal("SCC Sweep Total", "ms", t(sccTotal)); - ss.appendDecimal("SCC Sweep Max Pause", "ms", t(sccLongest)); - if (nonincrementalReason_ || ss.isJSON()) { - ss.appendString("Nonincremental Reason", - nonincrementalReason_ ? nonincrementalReason_ : "none"); - } - ss.appendNumber("Allocated", "%u", "MB", unsigned(preBytes / 1024 / 1024)); - ss.appendNumber("+Chunks", "%d", "", counts[STAT_NEW_CHUNK]); - ss.appendNumber("-Chunks", "%d", "", counts[STAT_DESTROY_CHUNK]); - ss.endLine(); - - if (slices.length() > 1 || ss.isJSON()) { - ss.beginArray("Slices"); - for (size_t i = 0; i < slices.length(); i++) { - int64_t width = slices[i].duration(); - if (i != 0 && i != slices.length() - 1 && width < SLICE_MIN_REPORT_TIME && - !slices[i].resetReason && !ss.isJSON()) - { - continue; - } - - char budgetDescription[200]; - slices[i].budget.describe(budgetDescription, sizeof(budgetDescription) - 1); - - ss.beginObject(nullptr); - ss.extra(" "); - ss.appendNumber("Slice", "%d", "", i); - ss.appendDecimal("Pause", "", t(width)); - ss.extra(" ("); - ss.appendDecimal("When", "ms", t(slices[i].start - slices[0].start)); - ss.appendString("Reason", ExplainReason(slices[i].reason)); - ss.appendString("Budget", budgetDescription); - if (ss.isJSON()) { - ss.appendDecimal("Page Faults", "", - double(slices[i].endFaults - slices[i].startFaults)); - - ss.appendNumber("Start Timestamp", "%llu", "", (unsigned long long)slices[i].start); - ss.appendNumber("End Timestamp", "%llu", "", (unsigned long long)slices[i].end); - } - if (slices[i].resetReason) - ss.appendString("Reset", slices[i].resetReason); - ss.extra("): "); - FormatPhaseTimes(ss, "Times", slices[i].phaseTimes); - ss.endLine(); - ss.endObject(); - } - ss.endArray(); - } - ss.extra(" Totals: "); - FormatPhaseTimes(ss, "Totals", phaseTimes); - ss.endObject(); - - return !ss.isOOM(); -} - typedef Vector<UniqueChars, 8, SystemAllocPolicy> FragmentVector; static UniqueChars Join(const FragmentVector& fragments, const char* separator = "") { const size_t separatorLength = strlen(separator); size_t length = 0; for (size_t i = 0; i < fragments.length(); ++i) { length += fragments[i] ? strlen(fragments[i].get()) : 0; @@ -613,16 +311,129 @@ SumChildTimes(size_t phaseSlot, Phase ph total += phaseTimes[dagSlot][child]; } } } return total; } UniqueChars +Statistics::formatCompactSliceMessage() const +{ + // Skip if we OOM'ed. + if (slices.length() == 0) + return UniqueChars(nullptr); + + const size_t index = slices.length() - 1; + const SliceData& slice = slices[index]; + + char budgetDescription[200]; + slice.budget.describe(budgetDescription, sizeof(budgetDescription) - 1); + + const char* format = + "GC Slice %u - Pause: %.3fms of %s budget (@ %.3fms); Reason: %s; Reset: %s%s; Times: "; + char buffer[1024]; + memset(buffer, 0, sizeof(buffer)); + JS_snprintf(buffer, sizeof(buffer), format, index, + t(slice.duration()), budgetDescription, t(slice.start - slices[0].start), + ExplainReason(slice.reason), + slice.resetReason ? "yes - " : "no", slice.resetReason ? slice.resetReason : ""); + + FragmentVector fragments; + if (!fragments.append(make_string_copy(buffer)) || + !fragments.append(formatCompactSlicePhaseTimes(slices[index].phaseTimes))) + { + return UniqueChars(nullptr); + } + return Join(fragments); +} + +UniqueChars +Statistics::formatCompactSummaryMessage() const +{ + const double bytesPerMiB = 1024 * 1024; + + FragmentVector fragments; + if (!fragments.append(make_string_copy("Summary - "))) + return UniqueChars(nullptr); + + int64_t total, longest; + gcDuration(&total, &longest); + + const double mmu20 = computeMMU(20 * PRMJ_USEC_PER_MSEC); + const double mmu50 = computeMMU(50 * PRMJ_USEC_PER_MSEC); + + char buffer[1024]; + if (!nonincrementalReason_) { + JS_snprintf(buffer, sizeof(buffer), + "Max Pause: %.3fms; MMU 20ms: %.1f%%; MMU 50ms: %.1f%%; Total: %.3fms; ", + t(longest), mmu20 * 100., mmu50 * 100., t(total)); + } else { + JS_snprintf(buffer, sizeof(buffer), "Non-Incremental: %.3fms; ", t(total)); + } + if (!fragments.append(make_string_copy(buffer))) + return UniqueChars(nullptr); + + JS_snprintf(buffer, sizeof(buffer), + "Zones: %d of %d; Compartments: %d of %d; HeapSize: %.3f MiB; "\ + "HeapChange (abs): %+d (%d); ", + zoneStats.collectedZoneCount, zoneStats.zoneCount, + zoneStats.collectedCompartmentCount, zoneStats.compartmentCount, + double(preBytes) / bytesPerMiB, + counts[STAT_NEW_CHUNK] - counts[STAT_DESTROY_CHUNK], + counts[STAT_NEW_CHUNK] + counts[STAT_DESTROY_CHUNK]); + if (!fragments.append(make_string_copy(buffer))) + return UniqueChars(nullptr); + + MOZ_ASSERT_IF(counts[STAT_ARENA_RELOCATED], gckind == GC_SHRINK); + if (gckind == GC_SHRINK) { + JS_snprintf(buffer, sizeof(buffer), + "Kind: %s; Relocated: %.3f MiB; ", + ExplainInvocationKind(gckind), + double(ArenaSize * counts[STAT_ARENA_RELOCATED]) / bytesPerMiB); + if (!fragments.append(make_string_copy(buffer))) + return UniqueChars(nullptr); + } + + return Join(fragments); +} + +UniqueChars +Statistics::formatCompactSlicePhaseTimes(PhaseTimeTable phaseTimes) const +{ + static const int64_t MaxUnaccountedTimeUS = 100; + + FragmentVector fragments; + char buffer[128]; + for (AllPhaseIterator iter(phaseTimes); !iter.done(); iter.advance()) { + Phase phase; + size_t dagSlot; + size_t level; + iter.get(&phase, &dagSlot, &level); + MOZ_ASSERT(level < 4); + + int64_t ownTime = phaseTimes[dagSlot][phase]; + int64_t childTime = SumChildTimes(dagSlot, phase, phaseTimes); + if (ownTime > MaxUnaccountedTimeUS) { + JS_snprintf(buffer, sizeof(buffer), "%s: %.3fms", phases[phase].name, t(ownTime)); + if (!fragments.append(make_string_copy(buffer))) + return UniqueChars(nullptr); + + if (childTime && (ownTime - childTime) > MaxUnaccountedTimeUS) { + MOZ_ASSERT(level < 3); + JS_snprintf(buffer, sizeof(buffer), "%s: %.3fms", "Other", t(ownTime - childTime)); + if (!fragments.append(make_string_copy(buffer))) + return UniqueChars(nullptr); + } + } + } + return Join(fragments, ", "); +} + +UniqueChars Statistics::formatDetailedMessage() { FragmentVector fragments; if (!fragments.append(formatDetailedDescription())) return UniqueChars(nullptr); if (slices.length() > 1) { @@ -914,24 +725,16 @@ Statistics::formatJsonPhaseTimes(PhaseTi name.get(), ownTime / 1000, ownTime % 1000); if (!fragments.append(make_string_copy(buffer))) return UniqueChars(nullptr); } return Join(fragments, ","); } -char16_t* -Statistics::formatMessage() -{ - StatisticsSerializer ss(StatisticsSerializer::AsText); - formatData(ss, 0); - return ss.finishJSString(); -} - Statistics::Statistics(JSRuntime* rt) : runtime(rt), startupTime(PRMJ_Now()), fp(nullptr), gcDepth(0), nonincrementalReason_(nullptr), timedGCStart(0), preBytes(0), @@ -1007,28 +810,18 @@ Statistics::Statistics(JSRuntime* rt) if (!fp) MOZ_CRASH("Failed to open MOZ_GCTIMER log file."); } } } Statistics::~Statistics() { - if (fp) { - StatisticsSerializer ss(StatisticsSerializer::AsText); - FormatPhaseTimes(ss, "", phaseTotals); - char* msg = ss.finishCString(); - if (msg) { - fprintf(fp, "TOTALS\n%s\n\n-------\n", msg); - js_free(msg); - } - - if (fp != stdout && fp != stderr) - fclose(fp); - } + if (fp && fp != stdout && fp != stderr) + fclose(fp); } JS::GCSliceCallback Statistics::setSliceCallback(JS::GCSliceCallback newCallback) { JS::GCSliceCallback oldCallback = sliceCallback; sliceCallback = newCallback; return oldCallback; @@ -1309,17 +1102,17 @@ Statistics::endSCC(unsigned scc, int64_t * is affecting the responsiveness of the system. MMU measurements are given * with respect to a certain window size. If we report MMU(50ms) = 80%, then * that means that, for any 50ms window of time, at least 80% of the window is * devoted to the mutator. In other words, the GC is running for at most 20% of * the window, or 10ms. The GC can run multiple slices during the 50ms window * as long as the total time it spends is at most 10ms. */ double -Statistics::computeMMU(int64_t window) +Statistics::computeMMU(int64_t window) const { MOZ_ASSERT(!slices.empty()); int64_t gc = slices[0].end - slices[0].start; int64_t gcMax = gc; if (gc >= window) return 0.0;
--- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -96,18 +96,16 @@ enum Stat { STAT_STOREBUFFER_OVERFLOW, // Number of arenas relocated by compacting GC. STAT_ARENA_RELOCATED, STAT_LIMIT }; -class StatisticsSerializer; - struct ZoneGCStats { /* Number of zones collected in this GC. */ int collectedZoneCount; /* Total number of zones in the Runtime at the start of this GC. */ int zoneCount; @@ -181,17 +179,18 @@ struct Statistics void count(Stat s) { MOZ_ASSERT(s < STAT_LIMIT); counts[s]++; } int64_t beginSCC(); void endSCC(unsigned scc, int64_t start); - char16_t* formatMessage(); + UniqueChars formatCompactSliceMessage() const; + UniqueChars formatCompactSummaryMessage() const; UniqueChars formatJsonMessage(uint64_t timestamp); UniqueChars formatDetailedMessage(); JS::GCSliceCallback setSliceCallback(JS::GCSliceCallback callback); int64_t clearMaxGCPauseAccumulator(); int64_t getMaxGCPauseSinceClear(); @@ -228,17 +227,17 @@ struct Statistics typedef Vector<SliceData, 8, SystemAllocPolicy> SliceDataVector; typedef SliceDataVector::ConstRange SliceRange; SliceRange sliceRange() const { return slices.all(); } size_t slicesLength() const { return slices.length(); } /* Create a convenient typedef for referring tables of phase times. */ - typedef int64_t (*PhaseTimeTable)[PHASE_LIMIT]; + typedef int64_t const (*PhaseTimeTable)[PHASE_LIMIT]; private: JSRuntime* runtime; int64_t startupTime; /* File pointer used for MOZ_GCTIMER output. */ FILE* fp; @@ -272,17 +271,17 @@ struct Statistics /* Number of events of this type for this GC. */ unsigned int counts[STAT_LIMIT]; /* Allocated space before the GC started. */ size_t preBytes; /* Records the maximum GC pause in an API-controlled interval (in us). */ - int64_t maxPauseInInterval; + mutable int64_t maxPauseInInterval; /* Phases that are currently on stack. */ Phase phaseNesting[MAX_NESTING]; size_t phaseNestingDepth; size_t activeDagSlot; /* * To avoid recursive nesting, we discontinue a callback phase when any @@ -304,31 +303,32 @@ struct Statistics */ bool aborted; void beginGC(JSGCInvocationKind kind); void endGC(); void recordPhaseEnd(Phase phase); - void gcDuration(int64_t* total, int64_t* maxPause); + void gcDuration(int64_t* total, int64_t* maxPause) const; void sccDurations(int64_t* total, int64_t* maxPause); void printStats(); - bool formatData(StatisticsSerializer& ss, uint64_t timestamp); + + UniqueChars formatCompactSlicePhaseTimes(PhaseTimeTable phaseTimes) const; UniqueChars formatDetailedDescription(); UniqueChars formatDetailedSliceDescription(unsigned i, const SliceData& slice); UniqueChars formatDetailedPhaseTimes(PhaseTimeTable phaseTimes); UniqueChars formatDetailedTotals(); UniqueChars formatJsonDescription(uint64_t timestamp); UniqueChars formatJsonSliceDescription(unsigned i, const SliceData& slice); UniqueChars formatJsonPhaseTimes(PhaseTimeTable phaseTimes); - double computeMMU(int64_t resolution); + double computeMMU(int64_t resolution) const; }; struct AutoGCSlice { AutoGCSlice(Statistics& stats, const ZoneGCStats& zoneStats, JSGCInvocationKind gckind, SliceBudget budget, JS::gcreason::Reason reason MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : stats(stats)
--- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -3803,17 +3803,17 @@ IsOptimizableElementPropertyName(JSConte return false; return true; } static bool TryAttachNativeGetValueElemStub(JSContext* cx, HandleScript script, jsbytecode* pc, ICGetElem_Fallback* stub, HandleNativeObject obj, - HandleValue key) + HandleValue key, bool* attached) { RootedId id(cx); if (!IsOptimizableElementPropertyName(cx, key, &id)) return true; RootedPropertyName propName(cx, JSID_TO_ATOM(id)->asPropertyName()); bool needsAtomize = !key.toString()->isAtom(); bool isCallElem = (JSOp(*pc) == JSOP_CALLELEM); @@ -3853,48 +3853,49 @@ TryAttachNativeGetValueElemStub(JSContex : ICGetElemNativeStub::DynamicSlot; ICGetElemNativeCompiler compiler(cx, kind, isCallElem, monitorStub, obj, holder, propName, acctype, needsAtomize, offset); ICStub* newStub = compiler.getStub(compiler.getStubSpace(script)); if (!newStub) return false; stub->addNewStub(newStub); + *attached = true; } return true; } static bool TryAttachNativeGetAccessorElemStub(JSContext* cx, HandleScript script, jsbytecode* pc, ICGetElem_Fallback* stub, HandleNativeObject obj, - HandleValue key, bool* attached) + HandleValue key, bool* attached, bool* isTemporarilyUnoptimizable) { MOZ_ASSERT(!*attached); RootedId id(cx); if (!IsOptimizableElementPropertyName(cx, key, &id)) return true; RootedPropertyName propName(cx, JSID_TO_ATOM(id)->asPropertyName()); bool needsAtomize = !key.toString()->isAtom(); bool isCallElem = (JSOp(*pc) == JSOP_CALLELEM); RootedShape shape(cx); RootedObject baseHolder(cx); if (!EffectlesslyLookupProperty(cx, obj, propName, &baseHolder, &shape)) return false; - if(!baseHolder || baseHolder->isNative()) + if (!baseHolder || baseHolder->isNative()) return true; HandleNativeObject holder = baseHolder.as<NativeObject>(); bool getterIsScripted = false; - bool isTemporarilyUnoptimizable = false; if (IsCacheableGetPropCall(cx, obj, baseHolder, shape, &getterIsScripted, - &isTemporarilyUnoptimizable, /*isDOMProxy=*/false)) { + isTemporarilyUnoptimizable, /*isDOMProxy=*/false)) + { RootedFunction getter(cx, &shape->getterObject()->as<JSFunction>()); #if JS_HAS_NO_SUCH_METHOD // It's unlikely that a getter function will be used in callelem locations. // Just don't attach stubs in that case to avoid issues with __noSuchMethod__ handling. if (isCallElem) return true; #endif @@ -3998,33 +3999,34 @@ IsNativeOrUnboxedDenseElementAccess(Hand return false; if (key.isInt32() && key.toInt32() >= 0 && !IsAnyTypedArray(obj.get())) return true; return false; } static bool TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_Fallback* stub, - HandleValue lhs, HandleValue rhs, HandleValue res) + HandleValue lhs, HandleValue rhs, HandleValue res, bool* attached) { bool isCallElem = (JSOp(*pc) == JSOP_CALLELEM); // Check for String[i] => Char accesses. if (lhs.isString() && rhs.isInt32() && res.isString() && !stub->hasStub(ICStub::GetElem_String)) { // NoSuchMethod handling doesn't apply to string targets. JitSpew(JitSpew_BaselineIC, " Generating GetElem(String[Int32]) stub"); ICGetElem_String::Compiler compiler(cx); ICStub* stringStub = compiler.getStub(compiler.getStubSpace(script)); if (!stringStub) return false; stub->addNewStub(stringStub); + *attached = true; return true; } if (lhs.isMagic(JS_OPTIMIZED_ARGUMENTS) && rhs.isInt32() && !ArgumentsGetElemStubExists(stub, ICGetElem_Arguments::Magic)) { // Any script with a CALLPROP on arguments (arguments.foo()) // should not have optimized arguments. @@ -4033,16 +4035,17 @@ TryAttachGetElemStub(JSContext* cx, JSSc JitSpew(JitSpew_BaselineIC, " Generating GetElem(MagicArgs[Int32]) stub"); ICGetElem_Arguments::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), ICGetElem_Arguments::Magic, false); ICStub* argsStub = compiler.getStub(compiler.getStubSpace(script)); if (!argsStub) return false; stub->addNewStub(argsStub); + *attached = true; return true; } // Otherwise, GetElem is only optimized on objects. if (!lhs.isObject()) return true; RootedObject obj(cx, &lhs.toObject()); @@ -4055,54 +4058,59 @@ TryAttachGetElemStub(JSContext* cx, JSSc JitSpew(JitSpew_BaselineIC, " Generating GetElem(ArgsObj[Int32]) stub"); ICGetElem_Arguments::Compiler compiler( cx, stub->fallbackMonitorStub()->firstMonitorStub(), which, isCallElem); ICStub* argsStub = compiler.getStub(compiler.getStubSpace(script)); if (!argsStub) return false; stub->addNewStub(argsStub); + *attached = true; return true; } } // Check for NativeObject[int] dense accesses. if (IsNativeDenseElementAccess(obj, rhs)) { JitSpew(JitSpew_BaselineIC, " Generating GetElem(Native[Int32] dense) stub"); ICGetElem_Dense::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), obj->as<NativeObject>().lastProperty(), isCallElem); ICStub* denseStub = compiler.getStub(compiler.getStubSpace(script)); if (!denseStub) return false; stub->addNewStub(denseStub); + *attached = true; return true; } // Check for NativeObject[id] shape-optimizable accesses. if (obj->isNative() && rhs.isString()) { RootedScript rootedScript(cx, script); if (!TryAttachNativeGetValueElemStub(cx, rootedScript, pc, stub, - obj.as<NativeObject>(), rhs)) + obj.as<NativeObject>(), rhs, attached)) { return false; } + if (*attached) + return true; script = rootedScript; } // Check for UnboxedArray[int] accesses. if (obj->is<UnboxedArrayObject>() && rhs.isInt32() && rhs.toInt32() >= 0) { JitSpew(JitSpew_BaselineIC, " Generating GetElem(UnboxedArray[Int32]) stub"); ICGetElem_UnboxedArray::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), obj->group()); ICStub* unboxedStub = compiler.getStub(compiler.getStubSpace(script)); if (!unboxedStub) return false; stub->addNewStub(unboxedStub); + *attached = true; return true; } // Check for TypedArray[int] => Number and TypedObject[int] => Number accesses. if ((IsAnyTypedArray(obj.get()) || IsPrimitiveArrayTypedObject(obj)) && rhs.isNumber() && res.isNumber() && !TypedArrayGetElemStubExists(stub, obj)) @@ -4126,16 +4134,17 @@ TryAttachGetElemStub(JSContext* cx, JSSc JitSpew(JitSpew_BaselineIC, " Generating GetElem(TypedArray[Int32]) stub"); ICGetElem_TypedArray::Compiler compiler(cx, obj->maybeShape(), TypedThingElementType(obj)); ICStub* typedArrayStub = compiler.getStub(compiler.getStubSpace(script)); if (!typedArrayStub) return false; stub->addNewStub(typedArrayStub); + *attached = true; return true; } // GetElem operations on non-native objects cannot be cached by either // Baseline or Ion. Indicate this in the cache so that Ion does not // generate a cache for this op. if (!obj->isNative()) stub->noteNonNativeAccess(); @@ -4174,25 +4183,30 @@ DoGetElemFallback(JSContext* cx, Baselin if (isOptimizedArgs) TypeScript::Monitor(cx, frame->script(), pc, res); } bool attached = false; if (stub->numOptimizedStubs() >= ICGetElem_Fallback::MAX_OPTIMIZED_STUBS) { // TODO: Discard all stubs in this IC and replace with inert megamorphic stub. // But for now we just bail. + stub->noteUnoptimizableAccess(); attached = true; } // Try to attach an optimized getter stub. + bool isTemporarilyUnoptimizable = false; if (!attached && lhs.isObject() && lhs.toObject().isNative() && rhs.isString()){ RootedScript rootedScript(cx, frame->script()); RootedNativeObject obj(cx, &lhs.toObject().as<NativeObject>()); - if (!TryAttachNativeGetAccessorElemStub(cx, rootedScript, pc, stub, obj, rhs, &attached)) - return false; + if (!TryAttachNativeGetAccessorElemStub(cx, rootedScript, pc, stub, obj, rhs, &attached, + &isTemporarilyUnoptimizable)) + { + return false; + } script = rootedScript; } if (!isOptimizedArgs) { if (!GetElementOperation(cx, op, &lhsCopy, rhs, res)) return false; TypeScript::Monitor(cx, frame->script(), pc, res); } @@ -4204,21 +4218,21 @@ DoGetElemFallback(JSContext* cx, Baselin // Add a type monitor stub for the resulting value. if (!stub->addMonitorStubForValue(cx, frame->script(), res)) return false; if (attached) return true; // Try to attach an optimized stub. - if (!TryAttachGetElemStub(cx, frame->script(), pc, stub, lhs, rhs, res)) - return false; - - // If we ever add a way to note unoptimizable accesses here, propagate the - // isTemporarilyUnoptimizable state from TryAttachNativeGetElemStub to here. + if (!TryAttachGetElemStub(cx, frame->script(), pc, stub, lhs, rhs, res, &attached)) + return false; + + if (!attached && !isTemporarilyUnoptimizable) + stub->noteUnoptimizableAccess(); return true; } typedef bool (*DoGetElemFallbackFn)(JSContext*, BaselineFrame*, ICGetElem_Fallback*, HandleValue, HandleValue, MutableHandleValue); static const VMFunction DoGetElemFallbackInfo = FunctionInfo<DoGetElemFallbackFn>(DoGetElemFallback, TailCall, PopValues(2)); @@ -9943,17 +9957,17 @@ GetTemplateObjectForNative(JSContext* cx ObjectGroup* group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array); if (!group) return false; res->setGroup(group); return true; } - if (native == js::array_concat) { + if (native == js::array_concat || native == js::array_slice) { if (args.thisv().isObject() && !args.thisv().toObject().isSingleton()) { res.set(NewFullyAllocatedArrayTryReuseGroup(cx, &args.thisv().toObject(), 0, TenuredObject, /* forceAnalyze = */ true)); if (!res) return false; } } @@ -12677,21 +12691,25 @@ ICGetIntrinsic_Constant::ICGetIntrinsic_ : ICStub(GetIntrinsic_Constant, stubCode), value_(value) { } ICGetIntrinsic_Constant::~ICGetIntrinsic_Constant() { } ICGetProp_Primitive::ICGetProp_Primitive(JitCode* stubCode, ICStub* firstMonitorStub, - Shape* protoShape, uint32_t offset) + JSValueType primitiveType, Shape* protoShape, + uint32_t offset) : ICMonitoredStub(GetProp_Primitive, stubCode, firstMonitorStub), protoShape_(protoShape), offset_(offset) -{ } +{ + extra_ = uint16_t(primitiveType); + MOZ_ASSERT(JSValueType(extra_) == primitiveType); +} ICGetPropNativeStub::ICGetPropNativeStub(ICStub::Kind kind, JitCode* stubCode, ICStub* firstMonitorStub, ReceiverGuard guard, uint32_t offset) : ICMonitoredStub(kind, stubCode, firstMonitorStub), receiverGuard_(guard), offset_(offset) { }
--- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -2633,16 +2633,17 @@ class ICGetElem_Fallback : public ICMoni friend class ICStubSpace; explicit ICGetElem_Fallback(JitCode* stubCode) : ICMonitoredFallbackStub(ICStub::GetElem_Fallback, stubCode) { } static const uint16_t EXTRA_NON_NATIVE = 0x1; static const uint16_t EXTRA_NEGATIVE_INDEX = 0x2; + static const uint16_t EXTRA_UNOPTIMIZABLE_ACCESS = 0x4; public: static const uint32_t MAX_OPTIMIZED_STUBS = 16; void noteNonNativeAccess() { extra_ |= EXTRA_NON_NATIVE; } bool hasNonNativeAccess() const { @@ -2650,16 +2651,22 @@ class ICGetElem_Fallback : public ICMoni } void noteNegativeIndex() { extra_ |= EXTRA_NEGATIVE_INDEX; } bool hasNegativeIndex() const { return extra_ & EXTRA_NEGATIVE_INDEX; } + void noteUnoptimizableAccess() { + extra_ |= EXTRA_UNOPTIMIZABLE_ACCESS; + } + bool hadUnoptimizableAccess() const { + return extra_ & EXTRA_UNOPTIMIZABLE_ACCESS; + } // Compiler for this stub kind. class Compiler : public ICStubCompiler { protected: bool generateStubCode(MacroAssembler& masm); public: explicit Compiler(JSContext* cx) @@ -4104,23 +4111,27 @@ class ICGetProp_Primitive : public ICMon protected: // Protected to silence Clang warning. // Shape of String.prototype/Number.prototype to check for. HeapPtrShape protoShape_; // Fixed or dynamic slot offset. uint32_t offset_; - ICGetProp_Primitive(JitCode* stubCode, ICStub* firstMonitorStub, + ICGetProp_Primitive(JitCode* stubCode, ICStub* firstMonitorStub, JSValueType primitiveType, Shape* protoShape, uint32_t offset); public: HeapPtrShape& protoShape() { return protoShape_; } + JSValueType primitiveType() const { + return JSValueType(extra_); + } + static size_t offsetOfProtoShape() { return offsetof(ICGetProp_Primitive, protoShape_); } static size_t offsetOfOffset() { return offsetof(ICGetProp_Primitive, offset_); } @@ -4150,17 +4161,17 @@ class ICGetProp_Primitive : public ICMon prototype_(cx, prototype), isFixedSlot_(isFixedSlot), offset_(offset) {} ICStub* getStub(ICStubSpace* space) { RootedShape protoShape(cx, prototype_->as<NativeObject>().lastProperty()); return newStub<ICGetProp_Primitive>(space, getStubCode(), firstMonitorStub_, - protoShape, offset_); + primitiveType_, protoShape, offset_); } }; }; // Stub for accessing a string's length. class ICGetProp_StringLength : public ICStub { friend class ICStubSpace;
--- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -697,16 +697,90 @@ BaselineInspector::commonSetPropFunction } if (!*holder) return false; return true; } +MIRType +BaselineInspector::expectedPropertyAccessInputType(jsbytecode* pc) +{ + if (!hasBaselineScript()) + return MIRType_Value; + + const ICEntry& entry = icEntryFromPC(pc); + MIRType type = MIRType_None; + + for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) { + MIRType stubType; + switch (stub->kind()) { + case ICStub::GetProp_Fallback: + if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) + return MIRType_Value; + continue; + + case ICStub::GetElem_Fallback: + if (stub->toGetElem_Fallback()->hadUnoptimizableAccess()) + return MIRType_Value; + continue; + + case ICStub::GetProp_Generic: + return MIRType_Value; + + case ICStub::GetProp_ArgumentsLength: + case ICStub::GetElem_Arguments: + // Either an object or magic arguments. + return MIRType_Value; + + case ICStub::GetProp_ArrayLength: + case ICStub::GetProp_Native: + case ICStub::GetProp_NativeDoesNotExist: + case ICStub::GetProp_NativePrototype: + case ICStub::GetProp_Unboxed: + case ICStub::GetProp_TypedObject: + case ICStub::GetProp_CallScripted: + case ICStub::GetProp_CallNative: + case ICStub::GetProp_CallDOMProxyNative: + case ICStub::GetProp_CallDOMProxyWithGenerationNative: + case ICStub::GetProp_DOMProxyShadowed: + case ICStub::GetElem_NativeSlot: + case ICStub::GetElem_NativePrototypeSlot: + case ICStub::GetElem_NativePrototypeCallNative: + case ICStub::GetElem_NativePrototypeCallScripted: + case ICStub::GetElem_String: + case ICStub::GetElem_Dense: + case ICStub::GetElem_TypedArray: + stubType = MIRType_Object; + break; + + case ICStub::GetProp_Primitive: + stubType = MIRTypeFromValueType(stub->toGetProp_Primitive()->primitiveType()); + break; + + case ICStub::GetProp_StringLength: + stubType = MIRType_String; + break; + + default: + MOZ_CRASH("Unexpected stub"); + } + + if (type != MIRType_None) { + if (type != stubType) + return MIRType_Value; + } else { + type = stubType; + } + } + + return (type == MIRType_None) ? MIRType_Value : type; +} + bool BaselineInspector::instanceOfData(jsbytecode* pc, Shape** shape, uint32_t* slot, JSObject** prototypeObject) { MOZ_ASSERT(*pc == JSOP_INSTANCEOF); if (!hasBaselineScript()) return false;
--- a/js/src/jit/BaselineInspector.h +++ b/js/src/jit/BaselineInspector.h @@ -99,16 +99,17 @@ class BaselineInspector SetElemICInspector setElemICInspector(jsbytecode* pc) { return makeICInspector<SetElemICInspector>(pc, ICStub::SetElem_Fallback); } MIRType expectedResultType(jsbytecode* pc); MCompare::CompareType expectedCompareType(jsbytecode* pc); MIRType expectedBinaryArithSpecialization(jsbytecode* pc); + MIRType expectedPropertyAccessInputType(jsbytecode* pc); bool hasSeenNonNativeGetElement(jsbytecode* pc); bool hasSeenNegativeIndexGetElement(jsbytecode* pc); bool hasSeenAccessedGetter(jsbytecode* pc); bool hasSeenDoubleResult(jsbytecode* pc); bool hasSeenNonStringIterMore(jsbytecode* pc); bool isOptimizableCallStringSplit(jsbytecode* pc, JSString** stringOut, JSString** stringArg,
--- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -7143,16 +7143,51 @@ CodeGenerator::visitArrayConcat(LArrayCo masm.bind(&call); pushArg(temp1); pushArg(ToRegister(lir->rhs())); pushArg(ToRegister(lir->lhs())); callVM(ArrayConcatDenseInfo, lir); } +typedef JSObject* (*ArraySliceDenseFn)(JSContext*, HandleObject, int32_t, int32_t, HandleObject); +static const VMFunction ArraySliceDenseInfo = FunctionInfo<ArraySliceDenseFn>(array_slice_dense); + +void +CodeGenerator::visitArraySlice(LArraySlice* lir) +{ + Register object = ToRegister(lir->object()); + Register begin = ToRegister(lir->begin()); + Register end = ToRegister(lir->end()); + Register temp1 = ToRegister(lir->temp1()); + Register temp2 = ToRegister(lir->temp2()); + + Label call, fail; + + // Try to allocate an object. + masm.createGCObject(temp1, temp2, lir->mir()->templateObj(), lir->mir()->initialHeap(), &fail); + + // Fixup the group of the result in case it doesn't match the template object. + masm.loadPtr(Address(object, JSObject::offsetOfGroup()), temp2); + masm.storePtr(temp2, Address(temp1, JSObject::offsetOfGroup())); + + masm.jump(&call); + { + masm.bind(&fail); + masm.movePtr(ImmPtr(nullptr), temp1); + } + masm.bind(&call); + + pushArg(temp1); + pushArg(end); + pushArg(begin); + pushArg(object); + callVM(ArraySliceDenseInfo, lir); +} + typedef JSString* (*ArrayJoinFn)(JSContext*, HandleObject, HandleString); static const VMFunction ArrayJoinInfo = FunctionInfo<ArrayJoinFn>(jit::ArrayJoin); void CodeGenerator::visitArrayJoin(LArrayJoin* lir) { pushArg(ToRegister(lir->separator())); pushArg(ToRegister(lir->array()));
--- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -260,16 +260,17 @@ class CodeGenerator : public CodeGenerat Register elementsTemp, Register lengthTemp, TypedOrValueRegister out); void visitArrayPopShiftV(LArrayPopShiftV* lir); void visitArrayPopShiftT(LArrayPopShiftT* lir); void emitArrayPush(LInstruction* lir, const MArrayPush* mir, Register obj, ConstantOrRegister value, Register elementsTemp, Register length); void visitArrayPushV(LArrayPushV* lir); void visitArrayPushT(LArrayPushT* lir); void visitArrayConcat(LArrayConcat* lir); + void visitArraySlice(LArraySlice* lir); void visitArrayJoin(LArrayJoin* lir); void visitLoadUnboxedScalar(LLoadUnboxedScalar* lir); void visitLoadTypedArrayElementHole(LLoadTypedArrayElementHole* lir); void visitStoreUnboxedScalar(LStoreUnboxedScalar* lir); void visitStoreTypedArrayElementHole(LStoreTypedArrayElementHole* lir); void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir); void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir);
--- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -7189,37 +7189,36 @@ IonBuilder::testSingletonProperty(JSObje return nullptr; obj = obj->getProto(); } return nullptr; } -bool -IonBuilder::testSingletonPropertyTypes(MDefinition* obj, JSObject* singleton, PropertyName* name, - bool* testObject, bool* testString) +JSObject* +IonBuilder::testSingletonPropertyTypes(MDefinition* obj, PropertyName* name) { // As for TestSingletonProperty, but the input is any value in a type set - // rather than a specific object. If testObject is set then the constant - // result can only be used after ensuring the input is an object. - - *testObject = false; - *testString = false; + // rather than a specific object. TemporaryTypeSet* types = obj->resultTypeSet(); if (types && types->unknownObject()) - return false; + return nullptr; JSObject* objectSingleton = types ? types->maybeSingleton() : nullptr; if (objectSingleton) - return testSingletonProperty(objectSingleton, name) == singleton; + return testSingletonProperty(objectSingleton, name); + + MIRType objType = obj->type(); + if (objType == MIRType_Value && types) + objType = types->getKnownMIRType(); JSProtoKey key; - switch (obj->type()) { + switch (objType) { case MIRType_String: key = JSProto_String; break; case MIRType_Symbol: key = JSProto_Symbol; break; @@ -7227,71 +7226,67 @@ IonBuilder::testSingletonPropertyTypes(M case MIRType_Double: key = JSProto_Number; break; case MIRType_Boolean: key = JSProto_Boolean; break; - case MIRType_Object: - case MIRType_Value: { + case MIRType_Object: { if (!types) - return false; - - if (types->hasType(TypeSet::StringType())) { - key = JSProto_String; - *testString = true; - break; - } - - if (!types->maybeObject()) - return false; + return nullptr; // For property accesses which may be on many objects, we just need to // find a prototype common to all the objects; if that prototype // has the singleton property, the access will not be on a missing property. + JSObject* singleton = nullptr; for (unsigned i = 0; i < types->getObjectCount(); i++) { TypeSet::ObjectKey* key = types->getObject(i); if (!key) continue; if (analysisContext) key->ensureTrackedProperty(analysisContext, NameToId(name)); const Class* clasp = key->clasp(); if (!ClassHasEffectlessLookup(clasp) || ObjectHasExtraOwnProperty(compartment, key, name)) - return false; + return nullptr; if (key->unknownProperties()) - return false; + return nullptr; HeapTypeSetKey property = key->property(NameToId(name)); if (property.isOwnProperty(constraints())) - return false; + return nullptr; if (JSObject* proto = key->proto().toObjectOrNull()) { // Test this type. - if (testSingletonProperty(proto, name) != singleton) - return false; + JSObject* thisSingleton = testSingletonProperty(proto, name); + if (!thisSingleton) + return nullptr; + if (singleton) { + if (thisSingleton != singleton) + return nullptr; + } else { + singleton = thisSingleton; + } } else { // Can't be on the prototype chain with no prototypes... - return false; + return nullptr; } } - // If this is not a known object, a test will be needed. - *testObject = (obj->type() != MIRType_Object); - return true; + return singleton; } default: - return false; + return nullptr; } JSObject* proto = GetBuiltinPrototypePure(&script()->global(), key); if (proto) - return testSingletonProperty(proto, name) == singleton; - - return false; + return testSingletonProperty(proto, name); + + return nullptr; } bool IonBuilder::pushTypeBarrier(MDefinition* def, TemporaryTypeSet* observed, BarrierKind kind) { MOZ_ASSERT(def == current->peek(-1)); MDefinition* replace = addTypeBarrier(current->pop(), observed, kind); @@ -7726,16 +7721,18 @@ IonBuilder::jsop_getelem() if (!resumeAfter(ins)) return false; TemporaryTypeSet* types = bytecodeTypes(pc); return pushTypeBarrier(ins, types, BarrierKind::TypeSet); } + obj = maybeUnboxForPropertyAccess(obj); + bool emitted = false; trackOptimizationAttempt(TrackedStrategy::GetElem_TypedObject); if (!getElemTryTypedObject(&emitted, obj, index) || emitted) return emitted; trackOptimizationAttempt(TrackedStrategy::GetElem_Dense); if (!getElemTryDense(&emitted, obj, index) || emitted) @@ -10004,16 +10001,31 @@ IonBuilder::shouldAbortOnPreliminaryGrou preliminary = true; } } } return preliminary; } +MDefinition* +IonBuilder::maybeUnboxForPropertyAccess(MDefinition* def) +{ + if (def->type() != MIRType_Value) + return def; + + MIRType type = inspector->expectedPropertyAccessInputType(pc); + if (type == MIRType_Value || !def->mightBeType(type)) + return def; + + MUnbox* unbox = MUnbox::New(alloc(), def, type, MUnbox::Fallible); + current->add(unbox); + return unbox; +} + bool IonBuilder::jsop_getprop(PropertyName* name) { bool emitted = false; startTrackingOptimizations(); MDefinition* obj = current->pop(); TemporaryTypeSet* types = bytecodeTypes(pc); @@ -10029,16 +10041,18 @@ IonBuilder::jsop_getprop(PropertyName* n return emitted; // Try to optimize arguments.callee. trackOptimizationAttempt(TrackedStrategy::GetProp_ArgumentsCallee); if (!getPropTryArgumentsCallee(&emitted, obj, name) || emitted) return emitted; } + obj = maybeUnboxForPropertyAccess(obj); + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, name, types); // Try to optimize to a specific constant. trackOptimizationAttempt(TrackedStrategy::GetProp_InferredConstant); if (barrier == BarrierKind::NoBarrier) { if (!getPropTryInferredConstant(&emitted, obj, name, types) || emitted) return emitted; @@ -10296,34 +10310,29 @@ IonBuilder::getPropTryArgumentsCallee(bo } bool IonBuilder::getPropTryConstant(bool* emitted, MDefinition* obj, PropertyName* name, TemporaryTypeSet* types) { MOZ_ASSERT(*emitted == false); - JSObject* singleton = types ? types->maybeSingleton() : nullptr; - if (!singleton) { - trackOptimizationOutcome(TrackedOutcome::NotSingleton); - return true; - } - - bool testObject, testString; - if (!testSingletonPropertyTypes(obj, singleton, name, &testObject, &testString)) + if (!types->mightBeMIRType(MIRType_Object)) { + // If we have not observed an object result here, don't look for a + // singleton constant. + trackOptimizationOutcome(TrackedOutcome::NotObject); + return true; + } + + JSObject* singleton = testSingletonPropertyTypes(obj, name); + if (!singleton) return true; // Property access is a known constant -- safe to emit. - MOZ_ASSERT(!testString || !testObject); - if (testObject) - current->add(MGuardObject::New(alloc(), obj)); - else if (testString) - current->add(MGuardString::New(alloc(), obj)); - else - obj->setImplicitlyUsedUnchecked(); + obj->setImplicitlyUsedUnchecked(); pushConstant(ObjectValue(*singleton)); trackOptimizationSuccess(); *emitted = true; return true; }
--- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -417,16 +417,17 @@ class IonBuilder bool storeSlot(MDefinition* obj, size_t slot, size_t nfixed, MDefinition* value, bool needsBarrier, MIRType slotType = MIRType_None); bool storeSlot(MDefinition* obj, Shape* shape, MDefinition* value, bool needsBarrier, MIRType slotType = MIRType_None); bool shouldAbortOnPreliminaryGroups(MDefinition *obj); MDefinition* tryInnerizeWindow(MDefinition* obj); + MDefinition* maybeUnboxForPropertyAccess(MDefinition* def); // jsop_getprop() helpers. bool checkIsDefinitelyOptimizedArguments(MDefinition* obj, bool* isOptimizedArgs); bool getPropTryInferredConstant(bool* emitted, MDefinition* obj, PropertyName* name, TemporaryTypeSet* types); bool getPropTryArgumentsLength(bool* emitted, MDefinition* obj); bool getPropTryArgumentsCallee(bool* emitted, MDefinition* obj, PropertyName* name); bool getPropTryConstant(bool* emitted, MDefinition* obj, PropertyName* name, @@ -740,16 +741,17 @@ class IonBuilder // The known MIR type of getInlineReturnTypeSet. MIRType getInlineReturnType(); // Array natives. InliningStatus inlineArray(CallInfo& callInfo); InliningStatus inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode); InliningStatus inlineArrayPush(CallInfo& callInfo); InliningStatus inlineArrayConcat(CallInfo& callInfo); + InliningStatus inlineArraySlice(CallInfo& callInfo); InliningStatus inlineArrayJoin(CallInfo& callInfo); InliningStatus inlineArraySplice(CallInfo& callInfo); // Math natives. InliningStatus inlineMathAbs(CallInfo& callInfo); InliningStatus inlineMathFloor(CallInfo& callInfo); InliningStatus inlineMathCeil(CallInfo& callInfo); InliningStatus inlineMathClz32(CallInfo& callInfo); @@ -935,18 +937,18 @@ class IonBuilder bool annotateGetPropertyCache(MDefinition* obj, MGetPropertyCache* getPropCache, TemporaryTypeSet* objTypes, TemporaryTypeSet* pushedTypes); MGetPropertyCache* getInlineableGetPropertyCache(CallInfo& callInfo); JSObject* testSingletonProperty(JSObject* obj, PropertyName* name); - bool testSingletonPropertyTypes(MDefinition* obj, JSObject* singleton, PropertyName* name, - bool* testObject, bool* testString); + JSObject* testSingletonPropertyTypes(MDefinition* obj, PropertyName* name); + uint32_t getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_t* pnfixed, BaselineInspector::ObjectGroupVector& convertUnboxedGroups); MDefinition* convertUnboxedObjects(MDefinition* obj, const BaselineInspector::ObjectGroupVector& list); uint32_t getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name, JSValueType* punboxedType); MInstruction* loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType, BarrierKind barrier, TemporaryTypeSet* types);
--- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -4940,16 +4940,49 @@ class LArrayConcat : public LCallInstruc const LDefinition* temp1() { return getTemp(0); } const LDefinition* temp2() { return getTemp(1); } }; +class LArraySlice : public LCallInstructionHelper<1, 3, 2> +{ + public: + LIR_HEADER(ArraySlice) + + LArraySlice(const LAllocation& obj, const LAllocation& begin, const LAllocation& end, + const LDefinition& temp1, const LDefinition& temp2) { + setOperand(0, obj); + setOperand(1, begin); + setOperand(2, end); + setTemp(0, temp1); + setTemp(1, temp2); + } + const MArraySlice* mir() const { + return mir_->toArraySlice(); + } + const LAllocation* object() { + return getOperand(0); + } + const LAllocation* begin() { + return getOperand(1); + } + const LAllocation* end() { + return getOperand(2); + } + const LDefinition* temp1() { + return getTemp(0); + } + const LDefinition* temp2() { + return getTemp(1); + } +}; + class LArrayJoin : public LCallInstructionHelper<1, 2, 0> { public: LIR_HEADER(ArrayJoin) LArrayJoin(const LAllocation& array, const LAllocation& sep) { setOperand(0, array); setOperand(1, sep);
--- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -233,16 +233,17 @@ _(StoreUnboxedScalar) \ _(StoreUnboxedPointer) \ _(ConvertUnboxedObjectToNative) \ _(ArrayPopShiftV) \ _(ArrayPopShiftT) \ _(ArrayPushV) \ _(ArrayPushT) \ _(ArrayConcat) \ + _(ArraySlice) \ _(ArrayJoin) \ _(StoreElementHoleV) \ _(StoreElementHoleT) \ _(LoadTypedArrayElementHole) \ _(LoadTypedArrayElementStatic) \ _(StoreTypedArrayElementHole) \ _(StoreTypedArrayElementStatic) \ _(CompareExchangeTypedArrayElement) \
--- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2900,16 +2900,33 @@ LIRGenerator::visitArrayConcat(MArrayCon useFixed(ins->rhs(), CallTempReg2), tempFixed(CallTempReg3), tempFixed(CallTempReg4)); defineReturn(lir, ins); assignSafepoint(lir, ins); } void +LIRGenerator::visitArraySlice(MArraySlice* ins) +{ + MOZ_ASSERT(ins->type() == MIRType_Object); + MOZ_ASSERT(ins->object()->type() == MIRType_Object); + MOZ_ASSERT(ins->begin()->type() == MIRType_Int32); + MOZ_ASSERT(ins->end()->type() == MIRType_Int32); + + LArraySlice* lir = new(alloc()) LArraySlice(useFixed(ins->object(), CallTempReg0), + useFixed(ins->begin(), CallTempReg1), + useFixed(ins->end(), CallTempReg2), + tempFixed(CallTempReg3), + tempFixed(CallTempReg4)); + defineReturn(lir, ins); + assignSafepoint(lir, ins); +} + +void LIRGenerator::visitArrayJoin(MArrayJoin* ins) { MOZ_ASSERT(ins->type() == MIRType_String); MOZ_ASSERT(ins->array()->type() == MIRType_Object); MOZ_ASSERT(ins->sep()->type() == MIRType_String); LArrayJoin* lir = new(alloc()) LArrayJoin(useRegisterAtStart(ins->array()), useRegisterAtStart(ins->sep()));
--- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -202,16 +202,17 @@ class LIRGenerator : public LIRGenerator void visitStoreElementHole(MStoreElementHole* ins); void visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull* ins); void visitStoreUnboxedString(MStoreUnboxedString* ins); void visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative* ins); void visitEffectiveAddress(MEffectiveAddress* ins); void visitArrayPopShift(MArrayPopShift* ins); void visitArrayPush(MArrayPush* ins); void visitArrayConcat(MArrayConcat* ins); + void visitArraySlice(MArraySlice* ins); void visitArrayJoin(MArrayJoin* ins); void visitLoadUnboxedScalar(MLoadUnboxedScalar* ins); void visitLoadTypedArrayElementHole(MLoadTypedArrayElementHole* ins); void visitLoadTypedArrayElementStatic(MLoadTypedArrayElementStatic* ins); void visitStoreUnboxedScalar(MStoreUnboxedScalar* ins); void visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole* ins); void visitClampToUint8(MClampToUint8* ins); void visitLoadFixedSlot(MLoadFixedSlot* ins);
--- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -77,16 +77,18 @@ IonBuilder::inlineNativeCall(CallInfo& c if (native == js::array_pop) return inlineArrayPopShift(callInfo, MArrayPopShift::Pop); if (native == js::array_shift) return inlineArrayPopShift(callInfo, MArrayPopShift::Shift); if (native == js::array_push) return inlineArrayPush(callInfo); if (native == js::array_concat) return inlineArrayConcat(callInfo); + if (native == js::array_slice) + return inlineArraySlice(callInfo); if (native == js::array_splice) return inlineArraySplice(callInfo); // Math natives. if (native == js::math_abs) return inlineMathAbs(callInfo); if (native == js::math_floor) return inlineMathFloor(callInfo); @@ -977,16 +979,117 @@ IonBuilder::inlineArrayConcat(CallInfo& current->push(ins); if (!resumeAfter(ins)) return InliningStatus_Error; return InliningStatus_Inlined; } IonBuilder::InliningStatus +IonBuilder::inlineArraySlice(CallInfo& callInfo) +{ + if (callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // Ensure |this| and result are objects. + if (getInlineReturnType() != MIRType_Object) + return InliningStatus_NotInlined; + if (callInfo.thisArg()->type() != MIRType_Object) + return InliningStatus_NotInlined; + + // Arguments for the sliced region must be integers. + if (callInfo.argc() > 0) { + if (callInfo.getArg(0)->type() != MIRType_Int32) + return InliningStatus_NotInlined; + if (callInfo.argc() > 1) { + if (callInfo.getArg(1)->type() != MIRType_Int32) + return InliningStatus_NotInlined; + } + } + + // |this| must be a dense array. + TemporaryTypeSet* thisTypes = callInfo.thisArg()->resultTypeSet(); + if (!thisTypes) + return InliningStatus_NotInlined; + + const Class* clasp = thisTypes->getKnownClass(constraints()); + if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_) + return InliningStatus_NotInlined; + if (thisTypes->hasObjectFlags(constraints(), OBJECT_FLAG_SPARSE_INDEXES | + OBJECT_FLAG_LENGTH_OVERFLOW)) + { + trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags); + return InliningStatus_NotInlined; + } + + JSValueType unboxedType = JSVAL_TYPE_MAGIC; + if (clasp == &UnboxedArrayObject::class_) { + unboxedType = UnboxedArrayElementType(constraints(), callInfo.thisArg(), nullptr); + if (unboxedType == JSVAL_TYPE_MAGIC) + return InliningStatus_NotInlined; + } + + // Watch out for indexed properties on the prototype. + if (ArrayPrototypeHasIndexedProperty(constraints(), script())) { + trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps); + return InliningStatus_NotInlined; + } + + // The group of the result will be dynamically fixed up to match the input + // object, allowing us to handle 'this' objects that might have more than + // one group. Make sure that no singletons can be sliced here. + for (unsigned i = 0; i < thisTypes->getObjectCount(); i++) { + TypeSet::ObjectKey* key = thisTypes->getObject(i); + if (key && key->isSingleton()) + return InliningStatus_NotInlined; + } + + // Inline the call. + JSObject* templateObj = inspector->getTemplateObjectForNative(pc, js::array_slice); + if (!templateObj) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MDefinition* begin; + if (callInfo.argc() > 0) + begin = callInfo.getArg(0); + else + begin = constant(Int32Value(0)); + + MDefinition* end; + if (callInfo.argc() > 1) { + end = callInfo.getArg(1); + } else if (clasp == &ArrayObject::class_) { + MElements* elements = MElements::New(alloc(), callInfo.thisArg()); + current->add(elements); + + end = MArrayLength::New(alloc(), elements); + current->add(end->toInstruction()); + } else { + end = MUnboxedArrayLength::New(alloc(), callInfo.thisArg()); + current->add(end->toInstruction()); + } + + MArraySlice* ins = MArraySlice::New(alloc(), constraints(), + callInfo.thisArg(), begin, end, + templateObj, + templateObj->group()->initialHeap(constraints()), + unboxedType); + current->add(ins); + current->push(ins); + + if (!resumeAfter(ins)) + return InliningStatus_Error; + return InliningStatus_Inlined; +} + +IonBuilder::InliningStatus IonBuilder::inlineMathAbs(CallInfo& callInfo) { if (callInfo.argc() != 1 || callInfo.constructing()) { trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); return InliningStatus_NotInlined; } MIRType returnType = getInlineReturnType();
--- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -9148,16 +9148,80 @@ class MArrayConcat return AliasSet::Store(AliasSet::BoxedOrUnboxedElements(unboxedType()) | AliasSet::ObjectFields); } bool possiblyCalls() const override { return true; } }; +// Array.prototype.slice on a dense array. +class MArraySlice + : public MTernaryInstruction, + public Mix3Policy<ObjectPolicy<0>, IntPolicy<1>, IntPolicy<2>>::Data +{ + AlwaysTenuredObject templateObj_; + gc::InitialHeap initialHeap_; + JSValueType unboxedType_; + + MArraySlice(CompilerConstraintList* constraints, MDefinition* obj, + MDefinition* begin, MDefinition* end, + JSObject* templateObj, gc::InitialHeap initialHeap, JSValueType unboxedType) + : MTernaryInstruction(obj, begin, end), + templateObj_(templateObj), + initialHeap_(initialHeap), + unboxedType_(unboxedType) + { + setResultType(MIRType_Object); + setResultTypeSet(obj->resultTypeSet()); + } + + public: + INSTRUCTION_HEADER(ArraySlice) + + static MArraySlice* New(TempAllocator& alloc, CompilerConstraintList* constraints, + MDefinition* obj, MDefinition* begin, MDefinition* end, + JSObject* templateObj, gc::InitialHeap initialHeap, + JSValueType unboxedType) + { + return new(alloc) MArraySlice(constraints, obj, begin, end, templateObj, + initialHeap, unboxedType); + } + + MDefinition* object() const { + return getOperand(0); + } + MDefinition* begin() const { + return getOperand(1); + } + MDefinition* end() const { + return getOperand(2); + } + + JSObject* templateObj() const { + return templateObj_; + } + + gc::InitialHeap initialHeap() const { + return initialHeap_; + } + + JSValueType unboxedType() const { + return unboxedType_; + } + + AliasSet getAliasSet() const override { + return AliasSet::Store(AliasSet::BoxedOrUnboxedElements(unboxedType()) | + AliasSet::ObjectFields); + } + bool possiblyCalls() const override { + return true; + } +}; + class MArrayJoin : public MBinaryInstruction, public MixPolicy<ObjectPolicy<0>, StringPolicy<1> >::Data { MArrayJoin(MDefinition* array, MDefinition* sep) : MBinaryInstruction(array, sep) { setResultType(MIRType_String);
--- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -195,16 +195,17 @@ namespace jit { _(StoreElementHole) \ _(StoreUnboxedScalar) \ _(StoreUnboxedObjectOrNull) \ _(StoreUnboxedString) \ _(ConvertUnboxedObjectToNative) \ _(ArrayPopShift) \ _(ArrayPush) \ _(ArrayConcat) \ + _(ArraySlice) \ _(ArrayJoin) \ _(LoadTypedArrayElementHole) \ _(LoadTypedArrayElementStatic) \ _(StoreTypedArrayElementHole) \ _(StoreTypedArrayElementStatic) \ _(CompareExchangeTypedArrayElement) \ _(AtomicTypedArrayElementBinop) \ _(EffectiveAddress) \
--- a/js/src/jit/arm/Assembler-arm.cpp +++ b/js/src/jit/arm/Assembler-arm.cpp @@ -21,22 +21,22 @@ using namespace js; using namespace js::jit; using mozilla::CountLeadingZeroes32; void dbg_break() {} // Note this is used for inter-AsmJS calls and may pass arguments and results in // floating point registers even if the system ABI does not. -ABIArgGenerator::ABIArgGenerator() : - intRegIndex_(0), +ABIArgGenerator::ABIArgGenerator() + : intRegIndex_(0), floatRegIndex_(0), stackOffset_(0), current_() -{} +{ } ABIArg ABIArgGenerator::next(MIRType type) { switch (type) { case MIRType_Int32: case MIRType_Pointer: if (intRegIndex_ == NumIntArgRegs) { @@ -493,25 +493,27 @@ InstMOV::AsTHIS(const Instruction& i) bool InstMOV::IsTHIS(const Instruction& i) { return InstALU::IsTHIS(i) && InstALU::AsTHIS(i)->checkOp1(r0) && InstALU::AsTHIS(i)->checkOp(OpMov); } Op2Reg -Operand2::toOp2Reg() { +Operand2::toOp2Reg() const { return *(Op2Reg*)this; } + O2RegImmShift -Op2Reg::toO2RegImmShift() { +Op2Reg::toO2RegImmShift() const { return *(O2RegImmShift*)this; } + O2RegRegShift -Op2Reg::toO2RegRegShift() { +Op2Reg::toO2RegRegShift() const { return *(O2RegRegShift*)this; } Imm16::Imm16(Instruction& inst) : lower(inst.encode() & 0xfff), upper(inst.encode() >> 16), invalid(0xfff) { } @@ -1252,17 +1254,17 @@ js::jit::VFPImm::VFPImm(uint32_t top) } BOffImm::BOffImm(Instruction& inst) : data(inst.encode() & 0x00ffffff) { } Instruction* -BOffImm::getDest(Instruction* src) +BOffImm::getDest(Instruction* src) const { // TODO: It is probably worthwhile to verify that src is actually a branch. // NOTE: This does not explicitly shift the offset of the destination left by 2, // since it is indexing into an array of instruction sized objects. return &src[(((int32_t)data << 8) >> 8) + 2]; } const js::jit::DoubleEncoder::DoubleEntry js::jit::DoubleEncoder::table[256] = { @@ -1420,130 +1422,129 @@ Assembler::nopAlign(int alignment) BufferOffset Assembler::as_nop() { return writeInst(0xe320f000); } static uint32_t -EncodeAlu(Register dest, Register src1, Operand2 op2, ALUOp op, SetCond_ sc, - Assembler::Condition c) +EncodeAlu(Register dest, Register src1, Operand2 op2, ALUOp op, SBit s, Assembler::Condition c) { - return (int)op | (int)sc | (int) c | op2.encode() | + return (int)op | (int)s | (int)c | op2.encode() | ((dest == InvalidReg) ? 0 : RD(dest)) | ((src1 == InvalidReg) ? 0 : RN(src1)); } BufferOffset Assembler::as_alu(Register dest, Register src1, Operand2 op2, - ALUOp op, SetCond_ sc, Condition c) + ALUOp op, SBit s, Condition c) { - return writeInst(EncodeAlu(dest, src1, op2, op, sc, c)); + return writeInst(EncodeAlu(dest, src1, op2, op, s, c)); } BufferOffset -Assembler::as_mov(Register dest, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_mov(Register dest, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, InvalidReg, op2, OpMov, sc, c); + return as_alu(dest, InvalidReg, op2, OpMov, s, c); } /* static */ void -Assembler::as_alu_patch(Register dest, Register src1, Operand2 op2, ALUOp op, SetCond_ sc, +Assembler::as_alu_patch(Register dest, Register src1, Operand2 op2, ALUOp op, SBit s, Condition c, uint32_t* pos) { - WriteInstStatic(EncodeAlu(dest, src1, op2, op, sc, c), pos); + WriteInstStatic(EncodeAlu(dest, src1, op2, op, s, c), pos); } /* static */ void -Assembler::as_mov_patch(Register dest, Operand2 op2, SetCond_ sc, Condition c, uint32_t* pos) +Assembler::as_mov_patch(Register dest, Operand2 op2, SBit s, Condition c, uint32_t* pos) { - as_alu_patch(dest, InvalidReg, op2, OpMov, sc, c, pos); + as_alu_patch(dest, InvalidReg, op2, OpMov, s, c, pos); } BufferOffset -Assembler::as_mvn(Register dest, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_mvn(Register dest, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, InvalidReg, op2, OpMvn, sc, c); + return as_alu(dest, InvalidReg, op2, OpMvn, s, c); } // Logical operations. BufferOffset -Assembler::as_and(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_and(Register dest, Register src1, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, src1, op2, OpAnd, sc, c); + return as_alu(dest, src1, op2, OpAnd, s, c); } BufferOffset -Assembler::as_bic(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_bic(Register dest, Register src1, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, src1, op2, OpBic, sc, c); + return as_alu(dest, src1, op2, OpBic, s, c); } BufferOffset -Assembler::as_eor(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_eor(Register dest, Register src1, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, src1, op2, OpEor, sc, c); + return as_alu(dest, src1, op2, OpEor, s, c); } BufferOffset -Assembler::as_orr(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_orr(Register dest, Register src1, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, src1, op2, OpOrr, sc, c); + return as_alu(dest, src1, op2, OpOrr, s, c); } // Mathematical operations. BufferOffset -Assembler::as_adc(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_adc(Register dest, Register src1, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, src1, op2, OpAdc, sc, c); + return as_alu(dest, src1, op2, OpAdc, s, c); } BufferOffset -Assembler::as_add(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_add(Register dest, Register src1, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, src1, op2, OpAdd, sc, c); + return as_alu(dest, src1, op2, OpAdd, s, c); } BufferOffset -Assembler::as_sbc(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_sbc(Register dest, Register src1, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, src1, op2, OpSbc, sc, c); + return as_alu(dest, src1, op2, OpSbc, s, c); } BufferOffset -Assembler::as_sub(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_sub(Register dest, Register src1, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, src1, op2, OpSub, sc, c); + return as_alu(dest, src1, op2, OpSub, s, c); } BufferOffset -Assembler::as_rsb(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_rsb(Register dest, Register src1, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, src1, op2, OpRsb, sc, c); + return as_alu(dest, src1, op2, OpRsb, s, c); } BufferOffset -Assembler::as_rsc(Register dest, Register src1, Operand2 op2, SetCond_ sc, Condition c) +Assembler::as_rsc(Register dest, Register src1, Operand2 op2, SBit s, Condition c) { - return as_alu(dest, src1, op2, OpRsc, sc, c); + return as_alu(dest, src1, op2, OpRsc, s, c); } // Test operations. BufferOffset Assembler::as_cmn(Register src1, Operand2 op2, Condition c) { - return as_alu(InvalidReg, src1, op2, OpCmn, SetCond, c); + return as_alu(InvalidReg, src1, op2, OpCmn, SetCC, c); } BufferOffset Assembler::as_cmp(Register src1, Operand2 op2, Condition c) { - return as_alu(InvalidReg, src1, op2, OpCmp, SetCond, c); + return as_alu(InvalidReg, src1, op2, OpCmp, SetCC, c); } BufferOffset Assembler::as_teq(Register src1, Operand2 op2, Condition c) { - return as_alu(InvalidReg, src1, op2, OpTeq, SetCond, c); + return as_alu(InvalidReg, src1, op2, OpTeq, SetCC, c); } BufferOffset Assembler::as_tst(Register src1, Operand2 op2, Condition c) { - return as_alu(InvalidReg, src1, op2, OpTst, SetCond, c); + return as_alu(InvalidReg, src1, op2, OpTst, SetCC, c); } static MOZ_CONSTEXPR_VAR Register NoAddend = { Registers::pc }; static const int SignExtend = 0x06000070; enum SignExtend { SxSxtb = 10 << 20, @@ -1614,69 +1615,69 @@ Assembler::as_movt_patch(Register dest, { WriteInstStatic(EncodeMovT(dest, imm, c), (uint32_t*)pos); } static const int mull_tag = 0x90; BufferOffset Assembler::as_genmul(Register dhi, Register dlo, Register rm, Register rn, - MULOp op, SetCond_ sc, Condition c) + MULOp op, SBit s, Condition c) { - return writeInst(RN(dhi) | maybeRD(dlo) | RM(rm) | rn.code() | op | sc | c | mull_tag); + return writeInst(RN(dhi) | maybeRD(dlo) | RM(rm) | rn.code() | op | s | c | mull_tag); } BufferOffset -Assembler::as_mul(Register dest, Register src1, Register src2, SetCond_ sc, Condition c) +Assembler::as_mul(Register dest, Register src1, Register src2, SBit s, Condition c) { - return as_genmul(dest, InvalidReg, src1, src2, OpmMul, sc, c); + return as_genmul(dest, InvalidReg, src1, src2, OpmMul, s, c); } BufferOffset Assembler::as_mla(Register dest, Register acc, Register src1, Register src2, - SetCond_ sc, Condition c) + SBit s, Condition c) { - return as_genmul(dest, acc, src1, src2, OpmMla, sc, c); + return as_genmul(dest, acc, src1, src2, OpmMla, s, c); } BufferOffset Assembler::as_umaal(Register destHI, Register destLO, Register src1, Register src2, Condition c) { - return as_genmul(destHI, destLO, src1, src2, OpmUmaal, NoSetCond, c); + return as_genmul(destHI, destLO, src1, src2, OpmUmaal, LeaveCC, c); } BufferOffset Assembler::as_mls(Register dest, Register acc, Register src1, Register src2, Condition c) { - return as_genmul(dest, acc, src1, src2, OpmMls, NoSetCond, c); + return as_genmul(dest, acc, src1, src2, OpmMls, LeaveCC, c); } BufferOffset Assembler::as_umull(Register destHI, Register destLO, Register src1, Register src2, - SetCond_ sc, Condition c) + SBit s, Condition c) { - return as_genmul(destHI, destLO, src1, src2, OpmUmull, sc, c); + return as_genmul(destHI, destLO, src1, src2, OpmUmull, s, c); } BufferOffset Assembler::as_umlal(Register destHI, Register destLO, Register src1, Register src2, - SetCond_ sc, Condition c) + SBit s, Condition c) { - return as_genmul(destHI, destLO, src1, src2, OpmUmlal, sc, c); + return as_genmul(destHI, destLO, src1, src2, OpmUmlal, s, c); } BufferOffset Assembler::as_smull(Register destHI, Register destLO, Register src1, Register src2, - SetCond_ sc, Condition c) + SBit s, Condition c) { - return as_genmul(destHI, destLO, src1, src2, OpmSmull, sc, c); + return as_genmul(destHI, destLO, src1, src2, OpmSmull, s, c); } BufferOffset Assembler::as_smlal(Register destHI, Register destLO, Register src1, Register src2, - SetCond_ sc, Condition c) + SBit s, Condition c) { - return as_genmul(destHI, destLO, src1, src2, OpmSmlal, sc, c); + return as_genmul(destHI, destLO, src1, src2, OpmSmlal, s, c); } BufferOffset Assembler::as_sdiv(Register rd, Register rn, Register rm, Condition c) { return writeInst(0x0710f010 | c | RN(rd) | RM(rm) | rn.code()); } @@ -1711,17 +1712,18 @@ Assembler::as_dtr(LoadStore ls, int size /* static */ void Assembler::as_dtr_patch(LoadStore ls, int size, Index mode, Register rt, DTRAddr addr, Condition c, uint32_t* dest) { WriteInstStatic(EncodeDtr(ls, size, mode, rt, addr, c), dest); } -class PoolHintData { +class PoolHintData +{ public: enum LoadType { // Set 0 to bogus, since that is the value most likely to be // accidentally left somewhere. PoolBOGUS = 0, PoolDTR = 1, PoolBranch = 2, PoolVDTR = 3 @@ -1754,57 +1756,58 @@ class PoolHintData { MOZ_ASSERT(index_ == index); cond_ = cond >> 28; MOZ_ASSERT(cond_ == cond >> 28); loadType_ = lt; ONES = ExpectedOnes; destReg_ = destReg.id(); destType_ = destReg.isDouble(); } - Assembler::Condition getCond() { + Assembler::Condition getCond() const { return Assembler::Condition(cond_ << 28); } - Register getReg() { + Register getReg() const { return Register::FromCode(destReg_); } - VFPRegister getVFPReg() { + VFPRegister getVFPReg() const { VFPRegister r = VFPRegister(destReg_, destType_ ? VFPRegister::Double : VFPRegister::Single); return r; } - int32_t getIndex() { + int32_t getIndex() const { return index_; } void setIndex(uint32_t index) { MOZ_ASSERT(ONES == ExpectedOnes && loadType_ != PoolBOGUS); index_ = index; MOZ_ASSERT(index_ == index); } - LoadType getLoadType() { + LoadType getLoadType() const { // If this *was* a PoolBranch, but the branch has already been bound // then this isn't going to look like a real poolhintdata, but we still // want to lie about it so everyone knows it *used* to be a branch. if (ONES != ExpectedOnes) return PoolHintData::PoolBranch; return loadType_; } - bool isValidPoolHint() { + bool isValidPoolHint() const { // Most instructions cannot have a condition that is 0xf. Notable // exceptions are blx and the entire NEON instruction set. For the // purposes of pool loads, and possibly patched branches, the possible // instructions are ldr and b, neither of which can have a condition // code of 0xf. return ONES == ExpectedOnes; } }; -union PoolHintPun { +union PoolHintPun +{ PoolHintData phd; uint32_t raw; }; // Handles all of the other integral data transferring functions: ldrsb, ldrsh, // ldrd, etc. The size is given in bits. BufferOffset Assembler::as_extdtr(LoadStore ls, int size, bool IsSigned, Index mode, @@ -1839,18 +1842,17 @@ Assembler::as_extdtr(LoadStore ls, int s return writeInst(extra_bits2 << 5 | extra_bits1 << 20 | 0x90 | addr.encode() | RT(rt) | mode | c); } BufferOffset Assembler::as_dtm(LoadStore ls, Register rn, uint32_t mask, DTMMode mode, DTMWriteBack wb, Condition c) { - return writeInst(0x08000000 | RN(rn) | ls | - mode | mask | c | wb); + return writeInst(0x08000000 | RN(rn) | ls | mode | mask | c | wb); } BufferOffset Assembler::as_Imm32Pool(Register dest, uint32_t value, Condition c) { PoolHintPun php; php.phd.init(0, c, PoolHintData::PoolDTR, dest); return m_buffer.allocEntry(1, 1, (uint8_t*)&php.raw, (uint8_t*)&value); @@ -2051,22 +2053,24 @@ Assembler::as_isb_trap() // bx can *only* branch to a register, never to an immediate. BufferOffset Assembler::as_bx(Register r, Condition c) { BufferOffset ret = writeInst(((int) c) | OpBx | r.code()); return ret; } + void Assembler::WritePoolGuard(BufferOffset branch, Instruction* dest, BufferOffset afterPool) { BOffImm off = afterPool.diffB<BOffImm>(branch); *dest = InstBImm(off, Always); } + // Branch can branch to an immediate *or* to a register. // Branches to immediates are pc relative, branches to registers are absolute. BufferOffset Assembler::as_b(BOffImm off, Condition c) { BufferOffset ret = writeBranchInst(((int)c) | OpB | off.encode()); return ret; } @@ -2101,16 +2105,17 @@ Assembler::as_b(Label* l, Condition c) old = LabelBase::INVALID_OFFSET; BOffImm inv; ret = as_b(inv, c); } DebugOnly<int32_t> check = l->use(ret.getOffset()); MOZ_ASSERT(check == old); return ret; } + BufferOffset Assembler::as_b(BOffImm off, Condition c, BufferOffset inst) { *editSrc(inst) = InstBImm(off, c); return inst; } // blx can go to either an immediate or a register. @@ -2163,16 +2168,17 @@ Assembler::as_bl(Label* l, Condition c) old = LabelBase::INVALID_OFFSET; BOffImm inv; ret = as_bl(inv, c); } DebugOnly<int32_t> check = l->use(ret.getOffset()); MOZ_ASSERT(check == old); return ret; } + BufferOffset Assembler::as_bl(BOffImm off, Condition c, BufferOffset inst) { *editSrc(inst) = InstBLImm(off, c); return inst; } BufferOffset @@ -2190,16 +2196,17 @@ Assembler::as_msr(Register r, Condition return writeInst(0x012cf000 | int(c) | r.code()); } // VFP instructions! enum vfp_tags { VfpTag = 0x0C000A00, VfpArith = 0x02000000 }; + BufferOffset Assembler::writeVFPInst(vfp_size sz, uint32_t blob) { MOZ_ASSERT((sz & blob) == 0); MOZ_ASSERT((VfpTag & blob) == 0); return writeInst(VfpTag | sz | blob); } @@ -2220,53 +2227,47 @@ Assembler::as_vfp_float(VFPRegister vd, // Make sure we believe that all of our operands are the same kind. MOZ_ASSERT_IF(!vn.isMissing(), vd.equiv(vn)); MOZ_ASSERT_IF(!vm.isMissing(), vd.equiv(vm)); vfp_size sz = vd.isDouble() ? IsDouble : IsSingle; return writeVFPInst(sz, VD(vd) | VN(vn) | VM(vm) | op | VfpArith | c); } BufferOffset -Assembler::as_vadd(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c) +Assembler::as_vadd(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) { return as_vfp_float(vd, vn, vm, OpvAdd, c); } BufferOffset -Assembler::as_vdiv(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c) +Assembler::as_vdiv(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) { return as_vfp_float(vd, vn, vm, OpvDiv, c); } BufferOffset -Assembler::as_vmul(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c) +Assembler::as_vmul(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) { return as_vfp_float(vd, vn, vm, OpvMul, c); } BufferOffset -Assembler::as_vnmul(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c) +Assembler::as_vnmul(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) { return as_vfp_float(vd, vn, vm, OpvMul, c); } BufferOffset -Assembler::as_vnmla(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c) +Assembler::as_vnmla(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) { MOZ_CRASH("Feature NYI"); } BufferOffset -Assembler::as_vnmls(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c) +Assembler::as_vnmls(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) { MOZ_CRASH("Feature NYI"); } BufferOffset Assembler::as_vneg(VFPRegister vd, VFPRegister vm, Condition c) { return as_vfp_float(vd, NoVFPRegister, vm, OpvNeg, c); @@ -2280,40 +2281,40 @@ Assembler::as_vsqrt(VFPRegister vd, VFPR BufferOffset Assembler::as_vabs(VFPRegister vd, VFPRegister vm, Condition c) { return as_vfp_float(vd, NoVFPRegister, vm, OpvAbs, c); } BufferOffset -Assembler::as_vsub(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c) +Assembler::as_vsub(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c) { return as_vfp_float(vd, vn, vm, OpvSub, c); } BufferOffset -Assembler::as_vcmp(VFPRegister vd, VFPRegister vm, - Condition c) +Assembler::as_vcmp(VFPRegister vd, VFPRegister vm, Condition c) { return as_vfp_float(vd, NoVFPRegister, vm, OpvCmp, c); } + BufferOffset Assembler::as_vcmpz(VFPRegister vd, Condition c) { return as_vfp_float(vd, NoVFPRegister, NoVFPRegister, OpvCmpz, c); } // Specifically, a move between two same sized-registers. BufferOffset Assembler::as_vmov(VFPRegister vd, VFPRegister vsrc, Condition c) { return as_vfp_float(vd, NoVFPRegister, vsrc, OpvMov, c); } + // Transfer between Core and VFP. // Unlike the next function, moving between the core registers and vfp registers // can't be *that* properly typed. Namely, since I don't want to munge the type // VFPRegister to also include core registers. Thus, the core and vfp registers // are passed in based on their type, and src/dest is determined by the // float2core. @@ -2334,25 +2335,23 @@ Assembler::as_vxfer(Register vt1, Regist // If we are transferring a single half of the double then it must be // moving a VFP reg to a core reg. MOZ_ASSERT_IF(vt2 == InvalidReg, f2c == FloatToCore); idx = idx << 21; } else { MOZ_ASSERT(idx == 0); } - if (vt2 == InvalidReg) { - return writeVFPInst(sz, WordTransfer | f2c | c | - RT(vt1) | maybeRN(vt2) | VN(vm) | idx); - } else { - // We are doing a 64 bit transfer. - return writeVFPInst(sz, DoubleTransfer | f2c | c | - RT(vt1) | maybeRN(vt2) | VM(vm) | idx); - } + if (vt2 == InvalidReg) + return writeVFPInst(sz, WordTransfer | f2c | c | RT(vt1) | maybeRN(vt2) | VN(vm) | idx); + + // We are doing a 64 bit transfer. + return writeVFPInst(sz, DoubleTransfer | f2c | c | RT(vt1) | maybeRN(vt2) | VM(vm) | idx); } + enum vcvt_destFloatness { VcvtToInteger = 1 << 18, VcvtToFloat = 0 << 18 }; enum vcvt_toZero { VcvtToZero = 1 << 7, // Use the default rounding mode, which rounds truncates. VcvtToFPSCR = 0 << 7 // Use whatever rounding mode the fpscr specifies. }; @@ -2379,19 +2378,19 @@ Assembler::as_vcvt(VFPRegister vd, VFPRe return writeVFPInst(sz, c | 0x02B700C0 | VM(vm) | VD(vd)); } // At least one of the registers should be a float. vcvt_destFloatness destFloat; vcvt_Signedness opSign; vcvt_toZero doToZero = VcvtToFPSCR; MOZ_ASSERT(vd.isFloat() || vm.isFloat()); - if (vd.isSingle() || vm.isSingle()) { + if (vd.isSingle() || vm.isSingle()) sz = IsSingle; - } + if (vd.isFloat()) { destFloat = VcvtToFloat; opSign = (vm.isSInt()) ? VcvtFromSigned : VcvtFromUnsigned; } else { destFloat = VcvtToInteger; opSign = (vd.isSInt()) ? VcvtToSigned : VcvtToUnsigned; doToZero = useFPSCR ? VcvtToFPSCR : VcvtToZero; } @@ -2454,16 +2453,17 @@ Assembler::as_vdtm(LoadStore st, Registe BufferOffset Assembler::as_vimm(VFPRegister vd, VFPImm imm, Condition c) { MOZ_ASSERT(imm.isValid()); vfp_size sz = vd.isDouble() ? IsDouble : IsSingle; return writeVFPInst(sz, c | imm.encode() | VD(vd) | 0x02B00000); } + BufferOffset Assembler::as_vmrs(Register r, Condition c) { return writeInst(c | 0x0ef10a10 | RT(r)); } BufferOffset Assembler::as_vmsr(Register r, Condition c) @@ -2572,17 +2572,16 @@ Assembler::retarget(Label* label, Label* DebugOnly<uint32_t> prev = target->use(label->offset()); MOZ_ASSERT((int32_t)prev == Label::INVALID_OFFSET); } } label->reset(); } - static int stopBKPT = -1; void Assembler::as_bkpt() { // This is a count of how many times a breakpoint instruction has been // generated. It is embedded into the instruction for debugging // purposes. Gdb will print "bkpt xxx" when you attempt to dissassemble a // breakpoint with the number xxx embedded into it. If this breakpoint is @@ -2627,16 +2626,17 @@ ptrdiff_t Assembler::GetBranchOffset(const Instruction* i_) { MOZ_ASSERT(i_->is<InstBranchImm>()); InstBranchImm* i = i_->as<InstBranchImm>(); BOffImm dest; i->extractImm(&dest); return dest.decode(); } + void Assembler::RetargetNearBranch(Instruction* i, int offset, bool final) { Assembler::Condition c; i->extractCond(&c); RetargetNearBranch(i, offset, c, final); } @@ -2662,17 +2662,18 @@ Assembler::RetargetFarBranch(Instruction if (!i->is<InstLDR>()) { new (i) InstLDR(Offset, pc, DTRAddr(pc, DtrOffImm(offset - 8)), cond); AutoFlushICache::flush(uintptr_t(i), 4); } *slot = dest; } -struct PoolHeader : Instruction { +struct PoolHeader : Instruction +{ struct Header { // The size should take into account the pool header. // The size is in units of Instruction (4 bytes), not byte. uint32_t size : 15; bool isNatural : 1; uint32_t ONES : 16; @@ -2703,64 +2704,63 @@ struct PoolHeader : Instruction { uint32_t size() const { Header tmp(this); return tmp.size; } uint32_t isNatural() const { Header tmp(this); return tmp.isNatural; } + static bool IsTHIS(const Instruction& i) { return (*i.raw() & 0xffff0000) == 0xffff0000; } static const PoolHeader* AsTHIS(const Instruction& i) { if (!IsTHIS(i)) return nullptr; return static_cast<const PoolHeader*>(&i); } }; - void Assembler::WritePoolHeader(uint8_t* start, Pool* p, bool isNatural) { - static_assert(sizeof(PoolHeader) == 4, - "PoolHandler must have the correct size."); + static_assert(sizeof(PoolHeader) == 4, "PoolHandler must have the correct size."); uint8_t* pool = start + 4; // Go through the usual rigmarole to get the size of the pool. pool += p->getPoolSize(); uint32_t size = pool - start; MOZ_ASSERT((size & 3) == 0); size = size >> 2; MOZ_ASSERT(size < (1 << 15)); PoolHeader header(size, isNatural); *(PoolHeader*)start = header; } - // The size of an arbitrary 32-bit call in the instruction stream. On ARM this // sequence is |pc = ldr pc - 4; imm32| given that we never reach the imm32. uint32_t Assembler::PatchWrite_NearCallSize() { return sizeof(uint32_t); } + void Assembler::PatchWrite_NearCall(CodeLocationLabel start, CodeLocationLabel toCall) { Instruction* inst = (Instruction*) start.raw(); // Overwrite whatever instruction used to be here with a call. Since the // destination is in the same function, it will be within range of the // 24 << 2 byte bl instruction. uint8_t* dest = toCall.raw(); new (inst) InstBLImm(BOffImm(dest - (uint8_t*)inst) , Always); // Ensure everyone sees the code that was just written into memory. AutoFlushICache::flush(uintptr_t(inst), 4); - } + void Assembler::PatchDataWithValueCheck(CodeLocationLabel label, PatchedImmPtr newValue, PatchedImmPtr expectedValue) { Instruction* ptr = (Instruction*) label.raw(); InstructionIterator iter(ptr); Register dest; Assembler::RelocStyle rs; @@ -2791,17 +2791,16 @@ void Assembler::PatchWrite_Imm32(CodeLocationLabel label, Imm32 imm) { // Raw is going to be the return address. uint32_t* raw = (uint32_t*)label.raw(); // Overwrite the 4 bytes before the return address, which will end up being // the call instruction. *(raw - 1) = imm.value; } - uint8_t* Assembler::NextInstruction(uint8_t* inst_, uint32_t* count) { Instruction* inst = reinterpret_cast<Instruction*>(inst_); if (count != nullptr) *count += sizeof(Instruction); return reinterpret_cast<uint8_t*>(inst->next()); } @@ -2816,17 +2815,18 @@ InstIsGuard(Instruction* inst, const Poo if (!(inst->is<InstBXReg>() || inst->is<InstBImm>())) return false; // See if the next instruction is a pool header. *ph = (inst + 1)->as<const PoolHeader>(); return *ph != nullptr; } static bool -InstIsBNop(Instruction* inst) { +InstIsBNop(Instruction* inst) +{ // In some special situations, it is necessary to insert a NOP into the // instruction stream that nobody knows about, since nobody should know // about it, make sure it gets skipped when Instruction::next() is called. // this generates a very specific nop, namely a branch to the next // instruction. Assembler::Condition c; inst->extractCond(&c); if (c != Assembler::Always) @@ -3019,23 +3019,24 @@ void Assembler::UpdateBoundsCheck(uint32 #ifdef DEBUG Operand2 op = cmp->extractOp2(); MOZ_ASSERT(op.isImm8()); #endif Imm8 imm8 = Imm8(heapSize); MOZ_ASSERT(!imm8.invalid); - *inst = InstALU(InvalidReg, index, imm8, OpCmp, SetCond, Always); + *inst = InstALU(InvalidReg, index, imm8, OpCmp, SetCC, Always); // NOTE: we don't update the Auto Flush Cache! this function is currently // only called from within AsmJSModule::patchHeapAccesses, which does that // for us. Don't call this! } -InstructionIterator::InstructionIterator(Instruction* i_) : i(i_) +InstructionIterator::InstructionIterator(Instruction* i_) + : i(i_) { // Work around pools with an artificial pool guard and around nop-fill. i = i->skipPool(); } uint32_t Assembler::NopFill = 0; uint32_t
--- a/js/src/jit/arm/Assembler-arm.h +++ b/js/src/jit/arm/Assembler-arm.h @@ -69,19 +69,21 @@ class ABIArgGenerator { unsigned intRegIndex_; unsigned floatRegIndex_; uint32_t stackOffset_; ABIArg current_; public: ABIArgGenerator(); + ABIArg next(MIRType argType); ABIArg& current() { return current_; } uint32_t stackBytesConsumedSoFar() const { return stackOffset_; } + static const Register NonArgReturnReg0; static const Register NonArgReturnReg1; static const Register NonReturn_VolatileReg0; static const Register NonReturn_VolatileReg1; }; static MOZ_CONSTEXPR_VAR Register PreBarrierReg = r1; @@ -232,17 +234,16 @@ enum IsImmDTR_ { IsNotImmDTR = 1 << 25 }; // For the extra memory operations, ldrd, ldrsb, ldrh. enum IsImmEDTR_ { IsImmEDTR = 1 << 22, IsNotImmEDTR = 0 << 22 }; - enum ShiftType { LSL = 0, // << 5 LSR = 1, // << 5 ASR = 2, // << 5 ROR = 3, // << 5 RRX = ROR // RRX is encoded as ROR with a 0 offset. }; @@ -268,24 +269,27 @@ enum DTMMode { IB = I | B }; enum DTMWriteBack { WriteBack = 1 << 21, NoWriteBack = 0 << 21 }; -enum SetCond_ { - SetCond = 1 << 20, - NoSetCond = 0 << 20 +// Condition code updating mode. +enum SBit { + SetCC = 1 << 20, // Set condition code. + LeaveCC = 0 << 20 // Leave condition code unchanged. }; + enum LoadStore { IsLoad = 1 << 20, IsStore = 0 << 20 }; + // You almost never want to use this directly. Instead, you wantto pass in a // signed constant, and let this bit be implicitly set for you. This is however, // necessary if we want a negative index. enum IsUp_ { IsUp = 1 << 23, IsDown = 0 << 23 }; enum ALUOp { @@ -336,26 +340,29 @@ enum VFPOp { OpvDiv = 0x8 << 20, OpvMov = 0xB << 20 | 0x1 << 6, OpvAbs = 0xB << 20 | 0x3 << 6, OpvNeg = 0xB << 20 | 0x1 << 6 | 0x1 << 16, OpvSqrt = 0xB << 20 | 0x3 << 6 | 0x1 << 16, OpvCmp = 0xB << 20 | 0x1 << 6 | 0x4 << 16, OpvCmpz = 0xB << 20 | 0x1 << 6 | 0x5 << 16 }; + // Negate the operation, AND negate the immediate that we were passed in. ALUOp ALUNeg(ALUOp op, Register dest, Imm32* imm, Register* negDest); bool can_dbl(ALUOp op); bool condsAreSafe(ALUOp op); + // If there is a variant of op that has a dest (think cmp/sub) return that // variant of it. ALUOp getDestVariant(ALUOp op); static const ValueOperand JSReturnOperand = ValueOperand(JSReturnReg_Type, JSReturnReg_Data); static const ValueOperand softfpReturnOperand = ValueOperand(r1, r0); + // All of these classes exist solely to shuffle data into the various operands. // For example Operand2 can be an imm8, a register-shifted-by-a-constant or a // register-shifted-by-a-register. We represent this in C++ by having a base // class Operand2, which just stores the 32 bits of data as they will be encoded // in the instruction. You cannot directly create an Operand2 since it is // tricky, and not entirely sane to do so. Instead, you create one of its child // classes, e.g. Imm8. Imm8's constructor takes a single integer argument. Imm8 // will verify that its argument can be encoded as an ARM 12 bit imm8, encode it @@ -367,57 +374,62 @@ static const ValueOperand softfpReturnOp // it for its component Imm8data structures. The reason this is so horribly // round-about is we wanted to have Imm8 and RegisterShiftedRegister inherit // directly from Operand2 but have all of them take up only a single word of // storage. We also wanted to avoid passing around raw integers at all since // they are error prone. class Op2Reg; class O2RegImmShift; class O2RegRegShift; + namespace datastore { + struct Reg { // The "second register". uint32_t RM : 4; // Do we get another register for shifting. uint32_t RRS : 1; ShiftType Type : 2; // We'd like this to be a more sensible encoding, but that would need to be // a struct and that would not pack :( uint32_t ShiftAmount : 5; uint32_t pad : 20; Reg(uint32_t rm, ShiftType type, uint32_t rsr, uint32_t shiftamount) : RM(rm), RRS(rsr), Type(type), ShiftAmount(shiftamount), pad(0) { } - uint32_t encode() { - return RM | RRS << 4 | Type << 5 | ShiftAmount << 7; - } explicit Reg(const Op2Reg& op) { memcpy(this, &op, sizeof(*this)); } + + uint32_t encode() const { + return RM | RRS << 4 | Type << 5 | ShiftAmount << 7; + } }; // Op2 has a mode labelled "<imm8m>", which is arm's magical immediate encoding. // Some instructions actually get 8 bits of data, which is called Imm8Data // below. These should have edit distance > 1, but this is how it is for now. struct Imm8mData { private: uint32_t data : 8; uint32_t rot : 4; // Throw in an extra bit that will be 1 if we can't encode this properly. // if we can encode it properly, a simple "|" will still suffice to meld it // into the instruction. uint32_t buff : 19; + public: uint32_t invalid : 1; - uint32_t encode() { + public: + uint32_t encode() const { MOZ_ASSERT(!invalid); return data | rot << 8; }; // Default constructor makes an invalid immediate. Imm8mData() : data(0xff), rot(0xf), invalid(1) { } @@ -433,37 +445,45 @@ struct Imm8mData struct Imm8Data { private: uint32_t imm4L : 4; uint32_t pad : 4; uint32_t imm4H : 4; public: - uint32_t encode() { + Imm8Data(uint32_t imm) + : imm4L(imm & 0xf), imm4H(imm >> 4) + { + MOZ_ASSERT(imm <= 0xff); + } + + public: + uint32_t encode() const { return imm4L | (imm4H << 8); }; - Imm8Data(uint32_t imm) : imm4L(imm & 0xf), imm4H(imm >> 4) { - MOZ_ASSERT(imm <= 0xff); - } }; // VLDR/VSTR take an 8 bit offset, which is implicitly left shifted by 2. struct Imm8VFPOffData { private: uint32_t data; public: - uint32_t encode() { + Imm8VFPOffData(uint32_t imm) + : data (imm) + { + MOZ_ASSERT((imm & ~(0xff)) == 0); + } + + public: + uint32_t encode() const { return data; }; - Imm8VFPOffData(uint32_t imm) : data (imm) { - MOZ_ASSERT((imm & ~(0xff)) == 0); - } }; // ARM can magically encode 256 very special immediates to be moved into a // register. struct Imm8VFPImmData { // This structure's members are public and it has no constructor to // initialize them, for a very special reason. Were this structure to @@ -473,115 +493,128 @@ struct Imm8VFPImmData // the constructor MOZ_CONSTEXPR, but, again, some of our supported // compilers don't support MOZ_CONSTEXPR! So we are reduced to public // members and eschewing a constructor in hopes that the initialization // of DoubleEncoder's table is correct. uint32_t imm4L : 4; uint32_t imm4H : 4; int32_t isInvalid : 24; - uint32_t encode() { + uint32_t encode() const { // This assert is an attempting at ensuring that we don't create random // instances of this structure and then asking to encode() it. MOZ_ASSERT(isInvalid == 0); return imm4L | (imm4H << 16); }; }; struct Imm12Data { uint32_t data : 12; - uint32_t encode() { - return data; - } Imm12Data(uint32_t imm) : data(imm) { MOZ_ASSERT(data == imm); } + uint32_t encode() const { + return data; + } }; struct RIS { uint32_t ShiftAmount : 5; - uint32_t encode () { - return ShiftAmount; - } RIS(uint32_t imm) : ShiftAmount(imm) { MOZ_ASSERT(ShiftAmount == imm); } - explicit RIS(Reg r) : ShiftAmount(r.ShiftAmount) {} + + explicit RIS(Reg r) + : ShiftAmount(r.ShiftAmount) + { } + + uint32_t encode() const { + return ShiftAmount; + } }; struct RRS { uint32_t MustZero : 1; // The register that holds the shift amount. uint32_t RS : 4; RRS(uint32_t rs) : RS(rs) { MOZ_ASSERT(rs == RS); } - uint32_t encode () { + uint32_t encode() const { return RS << 1; } }; } // namespace datastore class MacroAssemblerARM; class Operand; class Operand2 { friend class Operand; friend class MacroAssemblerARM; friend class InstALU; + public: uint32_t oper : 31; uint32_t invalid : 1; - bool isO2Reg() { - return !(oper & IsImmOp2); - } - Op2Reg toOp2Reg(); - bool isImm8() { - return oper & IsImmOp2; - } protected: - Operand2(datastore::Imm8mData base) + explicit Operand2(datastore::Imm8mData base) : oper(base.invalid ? -1 : (base.encode() | (uint32_t)IsImmOp2)), invalid(base.invalid) { } - Operand2(datastore::Reg base) + explicit Operand2(datastore::Reg base) : oper(base.encode() | (uint32_t)IsNotImmOp2) { } private: - Operand2(int blob) + explicit Operand2(int blob) : oper(blob) { } public: - uint32_t encode() { + bool isO2Reg() const { + return !(oper & IsImmOp2); + } + + Op2Reg toOp2Reg() const; + + bool isImm8() const { + return oper & IsImmOp2; + } + + uint32_t encode() const { return oper; } }; class Imm8 : public Operand2 { public: + explicit Imm8(uint32_t imm) + : Operand2(EncodeImm(imm)) + { } + + public: static datastore::Imm8mData EncodeImm(uint32_t imm) { // mozilla::CountLeadingZeroes32(imm) requires imm != 0. if (imm == 0) return datastore::Imm8mData(0, 0); int left = mozilla::CountLeadingZeroes32(imm) & 30; // See if imm is a simple value that can be encoded with a rotate of 0. // This is effectively imm <= 0xff, but I assume this can be optimized // more. @@ -602,372 +635,387 @@ class Imm8 : public Operand2 return datastore::Imm8mData(); // Rather than masking out bits and checking for 0, just rotate the // immediate that we were passed in, and see if it fits into 8 bits. unsigned int mask = imm << (8 - right) | imm >> (24 + right); if (mask <= 0xff) return datastore::Imm8mData(mask, (8 - right) >> 1); return datastore::Imm8mData(); } + // Pair template? struct TwoImm8mData { datastore::Imm8mData fst, snd; TwoImm8mData() : fst(), snd() { } TwoImm8mData(datastore::Imm8mData _fst, datastore::Imm8mData _snd) : fst(_fst), snd(_snd) { } }; static TwoImm8mData EncodeTwoImms(uint32_t); - Imm8(uint32_t imm) - : Operand2(EncodeImm(imm)) - { } }; class Op2Reg : public Operand2 { public: - Op2Reg(Register rm, ShiftType type, datastore::RIS shiftImm) + explicit Op2Reg(Register rm, ShiftType type, datastore::RIS shiftImm) : Operand2(datastore::Reg(rm.code(), type, 0, shiftImm.encode())) { } - Op2Reg(Register rm, ShiftType type, datastore::RRS shiftReg) + explicit Op2Reg(Register rm, ShiftType type, datastore::RRS shiftReg) : Operand2(datastore::Reg(rm.code(), type, 1, shiftReg.encode())) { } - bool isO2RegImmShift() { + + public: + bool isO2RegImmShift() const { datastore::Reg r(*this); return !r.RRS; } - O2RegImmShift toO2RegImmShift(); - bool isO2RegRegShift() { + O2RegImmShift toO2RegImmShift() const; + + bool isO2RegRegShift() const { datastore::Reg r(*this); return r.RRS; } - O2RegRegShift toO2RegRegShift(); + O2RegRegShift toO2RegRegShift() const; - bool checkType(ShiftType type) { + bool checkType(ShiftType type) const { datastore::Reg r(*this); return r.Type == type; } - bool checkRM(Register rm) { + bool checkRM(Register rm) const { datastore::Reg r(*this); return r.RM == rm.code(); } - bool getRM(Register* rm) { + bool getRM(Register* rm) const { datastore::Reg r(*this); *rm = Register::FromCode(r.RM); return true; } }; class O2RegImmShift : public Op2Reg { public: - O2RegImmShift(Register rn, ShiftType type, uint32_t shift) + explicit O2RegImmShift(Register rn, ShiftType type, uint32_t shift) : Op2Reg(rn, type, datastore::RIS(shift)) { } - int getShift() { + + public: + int getShift() const { datastore::Reg r(*this); datastore::RIS ris(r); return ris.ShiftAmount; } }; class O2RegRegShift : public Op2Reg { public: - O2RegRegShift(Register rn, ShiftType type, Register rs) + explicit O2RegRegShift(Register rn, ShiftType type, Register rs) : Op2Reg(rn, type, datastore::RRS(rs.code())) { } }; O2RegImmShift O2Reg(Register r); -O2RegImmShift lsl (Register r, int amt); -O2RegImmShift lsr (Register r, int amt); -O2RegImmShift asr (Register r, int amt); -O2RegImmShift rol (Register r, int amt); -O2RegImmShift ror (Register r, int amt); +O2RegImmShift lsl(Register r, int amt); +O2RegImmShift lsr(Register r, int amt); +O2RegImmShift asr(Register r, int amt); +O2RegImmShift rol(Register r, int amt); +O2RegImmShift ror(Register r, int amt); -O2RegRegShift lsl (Register r, Register amt); -O2RegRegShift lsr (Register r, Register amt); -O2RegRegShift asr (Register r, Register amt); -O2RegRegShift ror (Register r, Register amt); +O2RegRegShift lsl(Register r, Register amt); +O2RegRegShift lsr(Register r, Register amt); +O2RegRegShift asr(Register r, Register amt); +O2RegRegShift ror(Register r, Register amt); // An offset from a register to be used for ldr/str. This should include the // sign bit, since ARM has "signed-magnitude" offsets. That is it encodes an // unsigned offset, then the instruction specifies if the offset is positive or // negative. The +/- bit is necessary if the instruction set wants to be able to // have a negative register offset e.g. ldr pc, [r1,-r2]; class DtrOff { uint32_t data; protected: - DtrOff(datastore::Imm12Data immdata, IsUp_ iu) + explicit DtrOff(datastore::Imm12Data immdata, IsUp_ iu) : data(immdata.encode() | (uint32_t)IsImmDTR | ((uint32_t)iu)) { } - DtrOff(datastore::Reg reg, IsUp_ iu = IsUp) + explicit DtrOff(datastore::Reg reg, IsUp_ iu = IsUp) : data(reg.encode() | (uint32_t) IsNotImmDTR | iu) { } public: - uint32_t encode() { return data; } + uint32_t encode() const { return data; } }; class DtrOffImm : public DtrOff { public: - DtrOffImm(int32_t imm) + explicit DtrOffImm(int32_t imm) : DtrOff(datastore::Imm12Data(mozilla::Abs(imm)), imm >= 0 ? IsUp : IsDown) { MOZ_ASSERT(mozilla::Abs(imm) < 4096); } }; class DtrOffReg : public DtrOff { // These are designed to be called by a constructor of a subclass. // Constructing the necessary RIS/RRS structures are annoying. protected: - DtrOffReg(Register rn, ShiftType type, datastore::RIS shiftImm, IsUp_ iu = IsUp) + explicit DtrOffReg(Register rn, ShiftType type, datastore::RIS shiftImm, IsUp_ iu = IsUp) : DtrOff(datastore::Reg(rn.code(), type, 0, shiftImm.encode()), iu) { } - DtrOffReg(Register rn, ShiftType type, datastore::RRS shiftReg, IsUp_ iu = IsUp) + explicit DtrOffReg(Register rn, ShiftType type, datastore::RRS shiftReg, IsUp_ iu = IsUp) : DtrOff(datastore::Reg(rn.code(), type, 1, shiftReg.encode()), iu) { } }; class DtrRegImmShift : public DtrOffReg { public: - DtrRegImmShift(Register rn, ShiftType type, uint32_t shift, IsUp_ iu = IsUp) + explicit DtrRegImmShift(Register rn, ShiftType type, uint32_t shift, IsUp_ iu = IsUp) : DtrOffReg(rn, type, datastore::RIS(shift), iu) { } }; class DtrRegRegShift : public DtrOffReg { public: - DtrRegRegShift(Register rn, ShiftType type, Register rs, IsUp_ iu = IsUp) + explicit DtrRegRegShift(Register rn, ShiftType type, Register rs, IsUp_ iu = IsUp) : DtrOffReg(rn, type, datastore::RRS(rs.code()), iu) { } }; // We will frequently want to bundle a register with its offset so that we have // an "operand" to a load instruction. class DTRAddr { + friend class Operand; + uint32_t data; public: - DTRAddr(Register reg, DtrOff dtr) + explicit DTRAddr(Register reg, DtrOff dtr) : data(dtr.encode() | (reg.code() << 16)) { } - uint32_t encode() { + private: + explicit DTRAddr(uint32_t blob) + : data(blob) + { } + + public: + uint32_t encode() const { return data; } - Register getBase() { + + Register getBase() const { return Register::FromCode((data >> 16) &0xf); } - private: - friend class Operand; - DTRAddr(uint32_t blob) - : data(blob) - { } }; // Offsets for the extended data transfer instructions: // ldrsh, ldrd, ldrsb, etc. class EDtrOff { uint32_t data; protected: - EDtrOff(datastore::Imm8Data imm8, IsUp_ iu = IsUp) + explicit EDtrOff(datastore::Imm8Data imm8, IsUp_ iu = IsUp) : data(imm8.encode() | IsImmEDTR | (uint32_t)iu) { } - EDtrOff(Register rm, IsUp_ iu = IsUp) + explicit EDtrOff(Register rm, IsUp_ iu = IsUp) : data(rm.code() | IsNotImmEDTR | iu) { } public: - uint32_t encode() { + uint32_t encode() const { return data; } }; class EDtrOffImm : public EDtrOff { public: - EDtrOffImm(int32_t imm) + explicit EDtrOffImm(int32_t imm) : EDtrOff(datastore::Imm8Data(mozilla::Abs(imm)), (imm >= 0) ? IsUp : IsDown) { MOZ_ASSERT(mozilla::Abs(imm) < 256); } }; // This is the most-derived class, since the extended data transfer instructions // don't support any sort of modifying the "index" operand. class EDtrOffReg : public EDtrOff { public: - EDtrOffReg(Register rm) + explicit EDtrOffReg(Register rm) : EDtrOff(rm) { } }; class EDtrAddr { uint32_t data; public: - EDtrAddr(Register r, EDtrOff off) + explicit EDtrAddr(Register r, EDtrOff off) : data(RN(r) | off.encode()) { } - uint32_t encode() { + uint32_t encode() const { return data; } }; class VFPOff { uint32_t data; protected: - VFPOff(datastore::Imm8VFPOffData imm, IsUp_ isup) + explicit VFPOff(datastore::Imm8VFPOffData imm, IsUp_ isup) : data(imm.encode() | (uint32_t)isup) { } public: - uint32_t encode() { + uint32_t encode() const { return data; } }; class VFPOffImm : public VFPOff { public: - VFPOffImm(int32_t imm) + explicit VFPOffImm(int32_t imm) : VFPOff(datastore::Imm8VFPOffData(mozilla::Abs(imm) / 4), imm < 0 ? IsDown : IsUp) { MOZ_ASSERT(mozilla::Abs(imm) <= 255 * 4); } }; + class VFPAddr { friend class Operand; uint32_t data; + public: + explicit VFPAddr(Register base, VFPOff off) + : data(RN(base) | off.encode()) + { } + protected: VFPAddr(uint32_t blob) : data(blob) { } public: - VFPAddr(Register base, VFPOff off) - : data(RN(base) | off.encode()) - { } - - uint32_t encode() { + uint32_t encode() const { return data; } }; -class VFPImm { +class VFPImm +{ uint32_t data; public: + explicit VFPImm(uint32_t topWordOfDouble); + + public: static const VFPImm One; - VFPImm(uint32_t topWordOfDouble); - - uint32_t encode() { + uint32_t encode() const { return data; } - bool isValid() { + bool isValid() const { return data != -1U; } }; // A BOffImm is an immediate that is used for branches. Namely, it is the offset // that will be encoded in the branch instruction. This is the only sane way of // constructing a branch. class BOffImm { + friend class InstBranchImm; + uint32_t data; public: - uint32_t encode() { - return data; - } - int32_t decode() { - return ((((int32_t)data) << 8) >> 6) + 8; - } - explicit BOffImm(int offset) : data ((offset - 8) >> 2 & 0x00ffffff) { MOZ_ASSERT((offset & 0x3) == 0); if (!IsInRange(offset)) CrashAtUnhandlableOOM("BOffImm"); } - static bool IsInRange(int offset) - { + + explicit BOffImm() + : data(INVALID) + { } + + private: + BOffImm(Instruction& inst); + + public: + static const int INVALID = 0x00800000; + + uint32_t encode() const { + return data; + } + int32_t decode() const { + return ((((int32_t)data) << 8) >> 6) + 8; + } + + static bool IsInRange(int offset) { if ((offset - 8) < -33554432) return false; if ((offset - 8) > 33554428) return false; return true; } - static const int INVALID = 0x00800000; - BOffImm() - : data(INVALID) - { } - bool isInvalid() { + bool isInvalid() const { return data == uint32_t(INVALID); } - Instruction* getDest(Instruction* src); - - private: - friend class InstBranchImm; - BOffImm(Instruction& inst); + Instruction* getDest(Instruction* src) const; }; class Imm16 { uint32_t lower : 12; uint32_t pad : 4; uint32_t upper : 4; uint32_t invalid : 12; public: - Imm16(); - Imm16(uint32_t imm); - Imm16(Instruction& inst); + explicit Imm16(); + explicit Imm16(uint32_t imm); + explicit Imm16(Instruction& inst); - uint32_t encode() { + uint32_t encode() const { return lower | upper << 16; } - uint32_t decode() { + uint32_t decode() const { return lower | upper << 12; } - bool isInvalid () { + bool isInvalid () const { return invalid; } }; // I would preffer that these do not exist, since there are essentially no // instructions that would ever take more than one of these, however, the MIR // wants to only have one type of arguments to functions, so bugger. class Operand @@ -983,36 +1031,37 @@ class Operand private: Tag_ Tag : 3; uint32_t reg : 5; int32_t offset; uint32_t data; public: - Operand (Register reg_) + explicit Operand(Register reg_) : Tag(OP2), reg(reg_.code()) { } - Operand (FloatRegister freg) + explicit Operand(FloatRegister freg) : Tag(FOP), reg(freg.code()) { } - Operand (Register base, Imm32 off) + explicit Operand(Register base, Imm32 off) : Tag(MEM), reg(base.code()), offset(off.value) { } - Operand (Register base, int32_t off) + explicit Operand(Register base, int32_t off) : Tag(MEM), reg(base.code()), offset(off) { } - Operand (const Address& addr) + explicit Operand(const Address& addr) : Tag(MEM), reg(addr.base.code()), offset(addr.offset) { } + public: Tag_ getTag() const { return Tag; } Operand2 toOp2() const { MOZ_ASSERT(Tag == OP2); return O2Reg(Register::FromCode(reg)); } @@ -1023,16 +1072,17 @@ class Operand } void toAddr(Register* r, Imm32* dest) const { MOZ_ASSERT(Tag == MEM); *r = Register::FromCode(reg); *dest = Imm32(offset); } Address toAddress() const { + MOZ_ASSERT(Tag == MEM); return Address(Register::FromCode(reg), offset); } int32_t disp() const { MOZ_ASSERT(Tag == MEM); return offset; } int32_t base() const { @@ -1047,16 +1097,17 @@ class Operand } VFPAddr toVFPAddr() const { return VFPAddr(baseReg(), VFPOffImm(offset)); } }; void PatchJump(CodeLocationJump& jump_, CodeLocationLabel label); + static inline void PatchBackedge(CodeLocationJump& jump_, CodeLocationLabel label, JitRuntime::BackedgeTarget target) { PatchJump(jump_, label); } class InstructionIterator; class Assembler; @@ -1174,18 +1225,18 @@ class Assembler : public AssemblerShared uint32_t actualOffset(uint32_t) const; uint32_t actualIndex(uint32_t) const; static uint8_t* PatchableJumpAddress(JitCode* code, uint32_t index); BufferOffset actualOffset(BufferOffset) const; static uint32_t NopFill; static uint32_t GetNopFill(); static uint32_t AsmPoolMaxOffset; static uint32_t GetPoolMaxOffset(); + protected: - // Structure for fixing up pc-relative loads/jumps when a the machine code // gets moved (executable copy, gc, etc.). struct RelativePatch { void* target; Relocation::Kind kind; RelativePatch(void* target, Relocation::Kind kind) : target(target), kind(kind) @@ -1209,18 +1260,17 @@ class Assembler : public AssemblerShared public: // For the alignment fill use NOP: 0x0320f000 or (Always | InstNOP::NopInst). // For the nopFill use a branch to the next instruction: 0xeaffffff. Assembler() : m_buffer(1, 1, 8, GetPoolMaxOffset(), 8, 0xe320f000, 0xeaffffff, GetNopFill()), isFinished(false), dtmActive(false), dtmCond(Always) - { - } + { } // We need to wait until an AutoJitContextAlloc is created by the // MacroAssembler, before allocating any space. void initWithAllocator() { m_buffer.initWithAllocator(); } static Condition InvertCondition(Condition cond); @@ -1313,91 +1363,87 @@ class Assembler : public AssemblerShared public: void writeCodePointer(AbsoluteLabel* label); void haltingAlign(int alignment); void nopAlign(int alignment); BufferOffset as_nop(); BufferOffset as_alu(Register dest, Register src1, Operand2 op2, - ALUOp op, SetCond_ sc = NoSetCond, Condition c = Always); + ALUOp op, SBit s = LeaveCC, Condition c = Always); BufferOffset as_mov(Register dest, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); BufferOffset as_mvn(Register dest, Operand2 op2, - SetCond_ sc = NoSetCond, Condition c = Always); + SBit s = LeaveCC, Condition c = Always); static void as_alu_patch(Register dest, Register src1, Operand2 op2, - ALUOp op, SetCond_ sc, Condition c, uint32_t* pos); + ALUOp op, SBit s, Condition c, uint32_t* pos); static void as_mov_patch(Register dest, - Operand2 op2, SetCond_ sc, Condition c, uint32_t* pos); + Operand2 op2, SBit s, Condition c, uint32_t* pos); // Logical operations: BufferOffset as_and(Register dest, Register src1, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); BufferOffset as_bic(Register dest, Register src1, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); BufferOffset as_eor(Register dest, Register src1, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); BufferOffset as_orr(Register dest, Register src1, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); // Mathematical operations: BufferOffset as_adc(Register dest, Register src1, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); BufferOffset as_add(Register dest, Register src1, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); BufferOffset as_sbc(Register dest, Register src1, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); BufferOffset as_sub(Register dest, Register src1, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); BufferOffset as_rsb(Register dest, Register src1, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); BufferOffset as_rsc(Register dest, Register src1, - Operand2 op2, SetCond_ sc = NoSetCond, Condition c = Always); + Operand2 op2, SBit s = LeaveCC, Condition c = Always); // Test operations: - BufferOffset as_cmn(Register src1, Operand2 op2, - Condition c = Always); - BufferOffset as_cmp(Register src1, Operand2 op2, - Condition c = Always); - BufferOffset as_teq(Register src1, Operand2 op2, - Condition c = Always); - BufferOffset as_tst(Register src1, Operand2 op2, - Condition c = Always); + BufferOffset as_cmn(Register src1, Operand2 op2, Condition c = Always); + BufferOffset as_cmp(Register src1, Operand2 op2, Condition c = Always); + BufferOffset as_teq(Register src1, Operand2 op2, Condition c = Always); + BufferOffset as_tst(Register src1, Operand2 op2, Condition c = Always); + // Sign extension operations: BufferOffset as_sxtb(Register dest, Register src, int rotate, Condition c = Always); BufferOffset as_sxth(Register dest, Register src, int rotate, Condition c = Always); BufferOffset as_uxtb(Register dest, Register src, int rotate, Condition c = Always); BufferOffset as_uxth(Register dest, Register src, int rotate, Condition c = Always); // Not quite ALU worthy, but useful none the less: These also have the issue - // of these being formatted completly differently from the standard ALU - // operations. + // of these being formatted completly differently from the standard ALU operations. BufferOffset as_movw(Register dest, Imm16 imm, Condition c = Always); BufferOffset as_movt(Register dest, Imm16 imm, Condition c = Always); static void as_movw_patch(Register dest, Imm16 imm, Condition c, Instruction* pos); static void as_movt_patch(Register dest, Imm16 imm, Condition c, Instruction* pos); BufferOffset as_genmul(Register d1, Register d2, Register rm, Register rn, - MULOp op, SetCond_ sc, Condition c = Always); + MULOp op, SBit s, Condition c = Always); BufferOffset as_mul(Register dest, Register src1, Register src2, - SetCond_ sc = NoSetCond, Condition c = Always); + SBit s = LeaveCC, Condition c = Always); BufferOffset as_mla(Register dest, Register acc, Register src1, Register src2, - SetCond_ sc = NoSetCond, Condition c = Always); + SBit s = LeaveCC, Condition c = Always); BufferOffset as_umaal(Register dest1, Register dest2, Register src1, Register src2, Condition c = Always); BufferOffset as_mls(Register dest, Register acc, Register src1, Register src2, Condition c = Always); BufferOffset as_umull(Register dest1, Register dest2, Register src1, Register src2, - SetCond_ sc = NoSetCond, Condition c = Always); + SBit s = LeaveCC, Condition c = Always); BufferOffset as_umlal(Register dest1, Register dest2, Register src1, Register src2, - SetCond_ sc = NoSetCond, Condition c = Always); + SBit s = LeaveCC, Condition c = Always); BufferOffset as_smull(Register dest1, Register dest2, Register src1, Register src2, - SetCond_ sc = NoSetCond, Condition c = Always); + SBit s = LeaveCC, Condition c = Always); BufferOffset as_smlal(Register dest1, Register dest2, Register src1, Register src2, - SetCond_ sc = NoSetCond, Condition c = Always); + SBit s = LeaveCC, Condition c = Always); BufferOffset as_sdiv(Register dest, Register num, Register div, Condition c = Always); BufferOffset as_udiv(Register dest, Register num, Register div, Condition c = Always); BufferOffset as_clz(Register dest, Register src, Condition c = Always); // Data transfer instructions: ldr, str, ldrb, strb. // Using an int to differentiate between 8 bits and 32 bits is overkill. BufferOffset as_dtr(LoadStore ls, int size, Index mode, @@ -1415,17 +1461,18 @@ class Assembler : public AssemblerShared DTMMode mode, DTMWriteBack wb, Condition c = Always); // Overwrite a pool entry with new data. static void WritePoolEntry(Instruction* addr, Condition c, uint32_t data); // Load a 32 bit immediate from a pool into a register. BufferOffset as_Imm32Pool(Register dest, uint32_t value, Condition c = Always); // Make a patchable jump that can target the entire 32 bit address space. - BufferOffset as_BranchPool(uint32_t value, RepatchLabel* label, ARMBuffer::PoolEntry* pe = nullptr, Condition c = Always); + BufferOffset as_BranchPool(uint32_t value, RepatchLabel* label, + ARMBuffer::PoolEntry* pe = nullptr, Condition c = Always); // Load a 64 bit floating point immediate from a pool into a register. BufferOffset as_FImm64Pool(VFPRegister dest, double value, Condition c = Always); // Load a 32 bit floating point immediate from a pool into a register. BufferOffset as_FImm32Pool(VFPRegister dest, float value, Condition c = Always); // Atomic instructions: ldrex, ldrexh, ldrexb, strex, strexh, strexb. // @@ -1439,20 +1486,18 @@ class Assembler : public AssemblerShared BufferOffset as_ldrexh(Register rt, Register rn, Condition c = Always); BufferOffset as_ldrexb(Register rt, Register rn, Condition c = Always); // STREX rd, rt, [rn] BufferOffset as_strex(Register rd, Register rt, Register rn, Condition c = Always); BufferOffset as_strexh(Register rd, Register rt, Register rn, Condition c = Always); BufferOffset as_strexb(Register rd, Register rt, Register rn, Condition c = Always); - // Memory synchronization: dmb, dsb, isb. - // + // Memory synchronization. // These are available from ARMv7 forward. - BufferOffset as_dmb(BarrierOption option = BarrierSY); BufferOffset as_dsb(BarrierOption option = BarrierSY); BufferOffset as_isb(); // Memory synchronization for architectures before ARMv7. BufferOffset as_dsb_trap(); BufferOffset as_dmb_trap(); BufferOffset as_isb_trap(); @@ -1482,67 +1527,50 @@ class Assembler : public AssemblerShared BufferOffset as_bl(); // bl #imm can have a condition code, blx #imm cannot. // blx reg can be conditional. BufferOffset as_bl(Label* l, Condition c); BufferOffset as_bl(BOffImm off, Condition c, BufferOffset inst); BufferOffset as_mrs(Register r, Condition c = Always); BufferOffset as_msr(Register r, Condition c = Always); + // VFP instructions! private: - enum vfp_size { IsDouble = 1 << 8, IsSingle = 0 << 8 }; BufferOffset writeVFPInst(vfp_size sz, uint32_t blob); static void WriteVFPInstStatic(vfp_size sz, uint32_t blob, uint32_t* dest); // Unityped variants: all registers hold the same (ieee754 single/double) // notably not included are vcvt; vmov vd, #imm; vmov rt, vn. BufferOffset as_vfp_float(VFPRegister vd, VFPRegister vn, VFPRegister vm, VFPOp op, Condition c = Always); public: - BufferOffset as_vadd(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c = Always); - - BufferOffset as_vdiv(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c = Always); - - BufferOffset as_vmul(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c = Always); - - BufferOffset as_vnmul(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c = Always); - - BufferOffset as_vnmla(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c = Always); - - BufferOffset as_vnmls(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c = Always); - + BufferOffset as_vadd(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c = Always); + BufferOffset as_vdiv(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c = Always); + BufferOffset as_vmul(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c = Always); + BufferOffset as_vnmul(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c = Always); + BufferOffset as_vnmla(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c = Always); + BufferOffset as_vnmls(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c = Always); BufferOffset as_vneg(VFPRegister vd, VFPRegister vm, Condition c = Always); - BufferOffset as_vsqrt(VFPRegister vd, VFPRegister vm, Condition c = Always); - BufferOffset as_vabs(VFPRegister vd, VFPRegister vm, Condition c = Always); - - BufferOffset as_vsub(VFPRegister vd, VFPRegister vn, VFPRegister vm, - Condition c = Always); - - BufferOffset as_vcmp(VFPRegister vd, VFPRegister vm, - Condition c = Always); + BufferOffset as_vsub(VFPRegister vd, VFPRegister vn, VFPRegister vm, Condition c = Always); + BufferOffset as_vcmp(VFPRegister vd, VFPRegister vm, Condition c = Always); BufferOffset as_vcmpz(VFPRegister vd, Condition c = Always); // Specifically, a move between two same sized-registers. BufferOffset as_vmov(VFPRegister vd, VFPRegister vsrc, Condition c = Always); + // Transfer between Core and VFP. enum FloatToCore_ { FloatToCore = 1 << 20, CoreToFloat = 0 << 20 }; private: enum VFPXferSize { @@ -1559,36 +1587,39 @@ class Assembler : public AssemblerShared BufferOffset as_vxfer(Register vt1, Register vt2, VFPRegister vm, FloatToCore_ f2c, Condition c = Always, int idx = 0); // Our encoding actually allows just the src and the dest (and their types) // to uniquely specify the encoding that we are going to use. BufferOffset as_vcvt(VFPRegister vd, VFPRegister vm, bool useFPSCR = false, Condition c = Always); + // Hard coded to a 32 bit fixed width result for now. - BufferOffset as_vcvtFixed(VFPRegister vd, bool isSigned, uint32_t fixedPoint, bool toFixed, Condition c = Always); + BufferOffset as_vcvtFixed(VFPRegister vd, bool isSigned, uint32_t fixedPoint, + bool toFixed, Condition c = Always); // Transfer between VFP and memory. BufferOffset as_vdtr(LoadStore ls, VFPRegister vd, VFPAddr addr, Condition c = Always /* vfp doesn't have a wb option*/); static void as_vdtr_patch(LoadStore ls, VFPRegister vd, VFPAddr addr, - Condition c /* vfp doesn't have a wb option*/, uint32_t* dest); + Condition c /* vfp doesn't have a wb option */, uint32_t* dest); // VFP's ldm/stm work differently from the standard arm ones. You can only // transfer a range. BufferOffset as_vdtm(LoadStore st, Register rn, VFPRegister vd, int length, - /*also has update conditions*/Condition c = Always); + /* also has update conditions */ Condition c = Always); BufferOffset as_vimm(VFPRegister vd, VFPImm imm, Condition c = Always); BufferOffset as_vmrs(Register r, Condition c = Always); BufferOffset as_vmsr(Register r, Condition c = Always); + // Label operations. bool nextLink(BufferOffset b, BufferOffset* next); void bind(Label* label, BufferOffset boff = BufferOffset()); void bind(RepatchLabel* label); uint32_t currentOffset() { return nextOffset().getOffset(); } void retarget(Label* label, Label* target); @@ -1906,16 +1937,17 @@ class InstDTR : public Instruction JS_STATIC_ASSERT(sizeof(InstDTR) == sizeof(Instruction)); class InstLDR : public InstDTR { public: InstLDR(Index mode, Register rt, DTRAddr addr, Assembler::Condition c) : InstDTR(IsLoad, IsWord, mode, rt, addr, c) { } + static bool IsTHIS(const Instruction& i); static InstLDR* AsTHIS(const Instruction& i); }; JS_STATIC_ASSERT(sizeof(InstDTR) == sizeof(InstLDR)); class InstNOP : public Instruction { @@ -1934,78 +1966,87 @@ class InstNOP : public Instruction class InstBranchReg : public Instruction { protected: // Don't use BranchTag yourself, use a derived instruction. enum BranchTag { IsBX = 0x012fff10, IsBLX = 0x012fff30 }; + static const uint32_t IsBRegMask = 0x0ffffff0; + InstBranchReg(BranchTag tag, Register rm, Assembler::Condition c) : Instruction(tag | rm.code(), c) { } + public: static bool IsTHIS (const Instruction& i); static InstBranchReg* AsTHIS (const Instruction& i); + // Get the register that is being branched to void extractDest(Register* dest); // Make sure we are branching to a pre-known register bool checkDest(Register dest); }; JS_STATIC_ASSERT(sizeof(InstBranchReg) == sizeof(Instruction)); // Branching to an immediate offset, or calling an immediate offset class InstBranchImm : public Instruction { protected: enum BranchTag { IsB = 0x0a000000, IsBL = 0x0b000000 }; + static const uint32_t IsBImmMask = 0x0f000000; InstBranchImm(BranchTag tag, BOffImm off, Assembler::Condition c) : Instruction(tag | off.encode(), c) { } public: static bool IsTHIS (const Instruction& i); static InstBranchImm* AsTHIS (const Instruction& i); + void extractImm(BOffImm* dest); }; JS_STATIC_ASSERT(sizeof(InstBranchImm) == sizeof(Instruction)); // Very specific branching instructions. class InstBXReg : public InstBranchReg { public: static bool IsTHIS (const Instruction& i); static InstBXReg* AsTHIS (const Instruction& i); }; + class InstBLXReg : public InstBranchReg { public: InstBLXReg(Register reg, Assembler::Condition c) : InstBranchReg(IsBLX, reg, c) { } static bool IsTHIS (const Instruction& i); static InstBLXReg* AsTHIS (const Instruction& i); }; + class InstBImm : public InstBranchImm { public: InstBImm(BOffImm off, Assembler::Condition c) : InstBranchImm(IsB, off, c) { } static bool IsTHIS (const Instruction& i); static InstBImm* AsTHIS (const Instruction& i); }; + class InstBLImm : public InstBranchImm { public: InstBLImm(BOffImm off, Assembler::Condition c) : InstBranchImm(IsBL, off, c) { } static bool IsTHIS (const Instruction& i); @@ -2051,29 +2092,33 @@ class InstMovW : public InstMovWT }; class InstMovT : public InstMovWT { public: InstMovT (Register rd, Imm16 imm, Assembler::Condition c) : InstMovWT(rd, imm, IsT, c) { } + static bool IsTHIS (const Instruction& i); static InstMovT* AsTHIS (const Instruction& i); }; class InstALU : public Instruction { static const int32_t ALUMask = 0xc << 24; + public: - InstALU (Register rd, Register rn, Operand2 op2, ALUOp op, SetCond_ sc, Assembler::Condition c) - : Instruction(maybeRD(rd) | maybeRN(rn) | op2.encode() | op | sc, c) + InstALU (Register rd, Register rn, Operand2 op2, ALUOp op, SBit s, Assembler::Condition c) + : Instruction(maybeRD(rd) | maybeRN(rn) | op2.encode() | op | s, c) { } + static bool IsTHIS (const Instruction& i); static InstALU* AsTHIS (const Instruction& i); + void extractOp(ALUOp* ret); bool checkOp(ALUOp op); void extractDest(Register* ret); bool checkDest(Register rd); void extractOp1(Register* ret); bool checkOp1(Register rn); Operand2 extractOp2(); }; @@ -2088,60 +2133,67 @@ class InstCMP : public InstALU class InstMOV : public InstALU { public: static bool IsTHIS (const Instruction& i); static InstMOV* AsTHIS (const Instruction& i); }; -class InstructionIterator { +class InstructionIterator +{ private: Instruction* i; + public: - InstructionIterator(Instruction* i_); + explicit InstructionIterator(Instruction* i_); + Instruction* next() { i = i->next(); return cur(); } Instruction* cur() const { return i; } }; static const uint32_t NumIntArgRegs = 4; + // There are 16 *float* registers available for arguments // If doubles are used, only half the number of registers are available. static const uint32_t NumFloatArgRegs = 16; static inline bool GetIntArgReg(uint32_t usedIntArgs, uint32_t usedFloatArgs, Register* out) { if (usedIntArgs >= NumIntArgRegs) return false; + *out = Register::FromCode(usedIntArgs); return true; } // Get a register in which we plan to put a quantity that will be used as an // integer argument. This differs from GetIntArgReg in that if we have no more // actual argument registers to use we will fall back on using whatever // CallTempReg* don't overlap the argument registers, and only fail once those // run out too. static inline bool GetTempRegForIntArg(uint32_t usedIntArgs, uint32_t usedFloatArgs, Register* out) { if (GetIntArgReg(usedIntArgs, usedFloatArgs, out)) return true; + // Unfortunately, we have to assume things about the point at which // GetIntArgReg returns false, because we need to know how many registers it // can allocate. usedIntArgs -= NumIntArgRegs; if (usedIntArgs >= NumCallTempNonArgRegs) return false; + *out = CallTempNonArgRegs[usedIntArgs]; return true; } #if !defined(JS_CODEGEN_ARM_HARDFP) || defined(JS_ARM_SIMULATOR) static inline uint32_t @@ -2215,50 +2267,56 @@ GetDoubleArgStackDisp(uint32_t usedIntAr doubleSlots *= 2; return (intSlots + doubleSlots + *padding) * sizeof(intptr_t); } #endif -class DoubleEncoder { +class DoubleEncoder +{ struct DoubleEntry { uint32_t dblTop; datastore::Imm8VFPImmData data; }; static const DoubleEntry table[256]; public: - bool lookup(uint32_t top, datastore::Imm8VFPImmData* ret) { + bool lookup(uint32_t top, datastore::Imm8VFPImmData* ret) const { for (int i = 0; i < 256; i++) { if (table[i].dblTop == top) { *ret = table[i].data; return true; } } return false; } }; -class AutoForbidPools { +class AutoForbidPools +{ Assembler* masm_; + public: // The maxInst argument is the maximum number of word sized instructions // that will be allocated within this context. It is used to determine if // the pool needs to be dumped before entering this content. The debug code // checks that no more than maxInst instructions are actually allocated. // // Allocation of pool entries is not supported within this content so the // code can not use large integers or float constants etc. - AutoForbidPools(Assembler* masm, size_t maxInst) : masm_(masm) { + AutoForbidPools(Assembler* masm, size_t maxInst) + : masm_(masm) + { masm_->enterNoPool(maxInst); } + ~AutoForbidPools() { masm_->leaveNoPool(); } }; } // namespace jit } // namespace js
--- a/js/src/jit/arm/BaselineIC-arm.cpp +++ b/js/src/jit/arm/BaselineIC-arm.cpp @@ -24,18 +24,18 @@ ICCompare_Int32::Compiler::generateStubC // Guard that R0 is an integer and R1 is an integer. Label failure; masm.branchTestInt32(Assembler::NotEqual, R0, &failure); masm.branchTestInt32(Assembler::NotEqual, R1, &failure); // Compare payload regs of R0 and R1. Assembler::Condition cond = JSOpToCondition(op, /* signed = */true); masm.cmp32(R0.payloadReg(), R1.payloadReg()); - masm.ma_mov(Imm32(1), R0.payloadReg(), NoSetCond, cond); - masm.ma_mov(Imm32(0), R0.payloadReg(), NoSetCond, Assembler::InvertCondition(cond)); + masm.ma_mov(Imm32(1), R0.payloadReg(), LeaveCC, cond); + masm.ma_mov(Imm32(0), R0.payloadReg(), LeaveCC, Assembler::InvertCondition(cond)); // Result is implicitly boxed already. masm.tagValue(JSVAL_TYPE_BOOLEAN, R0.payloadReg(), R0); EmitReturnFromIC(masm); // Failure case - jump to next stub. masm.bind(&failure); EmitStubGuardFailure(masm); @@ -52,17 +52,17 @@ ICCompare_Double::Compiler::generateStub Register dest = R0.scratchReg(); Assembler::DoubleCondition doubleCond = JSOpToDoubleCondition(op); Assembler::Condition cond = Assembler::ConditionFromDoubleCondition(doubleCond); masm.compareDouble(FloatReg0, FloatReg1); masm.ma_mov(Imm32(0), dest); - masm.ma_mov(Imm32(1), dest, NoSetCond, cond); + masm.ma_mov(Imm32(1), dest, LeaveCC, cond); masm.tagValue(JSVAL_TYPE_BOOLEAN, dest, R0); EmitReturnFromIC(masm); // Failure case - jump to next stub. masm.bind(&failure); EmitStubGuardFailure(masm); return true; @@ -88,28 +88,28 @@ ICBinaryArith_Int32::Compiler::generateS // DIV and MOD need an extra non-volatile ValueOperand to hold R0. AllocatableGeneralRegisterSet savedRegs(availableGeneralRegs(2)); savedRegs.set() = GeneralRegisterSet::Intersect(GeneralRegisterSet::NonVolatile(), savedRegs.set()); ValueOperand savedValue = savedRegs.takeAnyValue(); Label maybeNegZero, revertRegister; switch(op_) { case JSOP_ADD: - masm.ma_add(R0.payloadReg(), R1.payloadReg(), scratchReg, SetCond); + masm.ma_add(R0.payloadReg(), R1.payloadReg(), scratchReg, SetCC); // Just jump to failure on overflow. R0 and R1 are preserved, so we can // just jump to the next stub. masm.j(Assembler::Overflow, &failure); // Box the result and return. We know R0.typeReg() already contains the // integer tag, so we just need to move the result value into place. masm.mov(scratchReg, R0.payloadReg()); break; case JSOP_SUB: - masm.ma_sub(R0.payloadReg(), R1.payloadReg(), scratchReg, SetCond); + masm.ma_sub(R0.payloadReg(), R1.payloadReg(), scratchReg, SetCC); masm.j(Assembler::Overflow, &failure); masm.mov(scratchReg, R0.payloadReg()); break; case JSOP_MUL: { Assembler::Condition cond = masm.ma_check_mul(R0.payloadReg(), R1.payloadReg(), scratchReg, Assembler::Overflow); masm.j(cond, &failure);
--- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -130,17 +130,17 @@ CodeGeneratorARM::visitCompare(LCompare* const LAllocation* right = comp->getOperand(1); const LDefinition* def = comp->getDef(0); if (right->isConstant()) masm.ma_cmp(ToRegister(left), Imm32(ToInt32(right))); else masm.ma_cmp(ToRegister(left), ToOperand(right)); masm.ma_mov(Imm32(0), ToRegister(def)); - masm.ma_mov(Imm32(1), ToRegister(def), NoSetCond, cond); + masm.ma_mov(Imm32(1), ToRegister(def), LeaveCC, cond); } void CodeGeneratorARM::visitCompareAndBranch(LCompareAndBranch* comp) { Assembler::Condition cond = JSOpToCondition(comp->cmpMir()->compareType(), comp->jsop()); if (comp->right()->isConstant()) masm.ma_cmp(ToRegister(comp->left()), Imm32(ToInt32(comp->right()))); @@ -375,35 +375,35 @@ CodeGeneratorARM::visitSqrtF(LSqrtF* ins void CodeGeneratorARM::visitAddI(LAddI* ins) { const LAllocation* lhs = ins->getOperand(0); const LAllocation* rhs = ins->getOperand(1); const LDefinition* dest = ins->getDef(0); if (rhs->isConstant()) - masm.ma_add(ToRegister(lhs), Imm32(ToInt32(rhs)), ToRegister(dest), SetCond); + masm.ma_add(ToRegister(lhs), Imm32(ToInt32(rhs)), ToRegister(dest), SetCC); else - masm.ma_add(ToRegister(lhs), ToOperand(rhs), ToRegister(dest), SetCond); + masm.ma_add(ToRegister(lhs), ToOperand(rhs), ToRegister(dest), SetCC); if (ins->snapshot()) bailoutIf(Assembler::Overflow, ins->snapshot()); } void CodeGeneratorARM::visitSubI(LSubI* ins) { const LAllocation* lhs = ins->getOperand(0); const LAllocation* rhs = ins->getOperand(1); const LDefinition* dest = ins->getDef(0); if (rhs->isConstant()) - masm.ma_sub(ToRegister(lhs), Imm32(ToInt32(rhs)), ToRegister(dest), SetCond); + masm.ma_sub(ToRegister(lhs), Imm32(ToInt32(rhs)), ToRegister(dest), SetCC); else - masm.ma_sub(ToRegister(lhs), ToOperand(rhs), ToRegister(dest), SetCond); + masm.ma_sub(ToRegister(lhs), ToOperand(rhs), ToRegister(dest), SetCC); if (ins->snapshot()) bailoutIf(Assembler::Overflow, ins->snapshot()); } void CodeGeneratorARM::visitMulI(LMulI* ins) { @@ -421,27 +421,27 @@ CodeGeneratorARM::visitMulI(LMulI* ins) if (mul->canBeNegativeZero() && constant <= 0) { Assembler::Condition bailoutCond = (constant == 0) ? Assembler::LessThan : Assembler::Equal; masm.ma_cmp(ToRegister(lhs), Imm32(0)); bailoutIf(bailoutCond, ins->snapshot()); } // TODO: move these to ma_mul. switch (constant) { case -1: - masm.ma_rsb(ToRegister(lhs), Imm32(0), ToRegister(dest), SetCond); + masm.ma_rsb(ToRegister(lhs), Imm32(0), ToRegister(dest), SetCC); break; case 0: masm.ma_mov(Imm32(0), ToRegister(dest)); return; // Escape overflow check; case 1: // Nop masm.ma_mov(ToRegister(lhs), ToRegister(dest)); return; // Escape overflow check; case 2: - masm.ma_add(ToRegister(lhs), ToRegister(lhs), ToRegister(dest), SetCond); + masm.ma_add(ToRegister(lhs), ToRegister(lhs), ToRegister(dest), SetCC); // Overflow is handled later. break; default: { bool handled = false; if (constant > 0) { // Try shift and add sequences for a positive constant. if (!mul->canOverflow()) { // If it cannot overflow, we can do lots of optimizations. @@ -641,17 +641,17 @@ CodeGeneratorARM::visitDivPowTwoI(LDivPo Register lhs = ToRegister(ins->numerator()); Register output = ToRegister(ins->output()); int32_t shift = ins->shift(); if (shift != 0) { MDiv* mir = ins->mir(); if (!mir->isTruncated()) { // If the remainder is != 0, bailout since this must be a double. - masm.as_mov(ScratchRegister, lsl(lhs, 32 - shift), SetCond); + masm.as_mov(ScratchRegister, lsl(lhs, 32 - shift), SetCC); bailoutIf(Assembler::NonZero, ins->snapshot()); } if (!mir->canBeNegativeDividend()) { // Numerator is unsigned, so needs no adjusting. Do the shift. masm.as_mov(output, asr(lhs, shift)); return; } @@ -806,21 +806,21 @@ void CodeGeneratorARM::visitModPowTwoI(LModPowTwoI* ins) { Register in = ToRegister(ins->getOperand(0)); Register out = ToRegister(ins->getDef(0)); MMod* mir = ins->mir(); Label fin; // bug 739870, jbramley has a different sequence that may help with speed // here. - masm.ma_mov(in, out, SetCond); + masm.ma_mov(in, out, SetCC); masm.ma_b(&fin, Assembler::Zero); - masm.ma_rsb(Imm32(0), out, NoSetCond, Assembler::Signed); + masm.ma_rsb(Imm32(0), out, LeaveCC, Assembler::Signed); masm.ma_and(Imm32((1 << ins->shift()) - 1), out); - masm.ma_rsb(Imm32(0), out, SetCond, Assembler::Signed); + masm.ma_rsb(Imm32(0), out, SetCC, Assembler::Signed); if (mir->canBeNegativeDividend()) { if (!mir->isTruncated()) { MOZ_ASSERT(mir->fallible()); bailoutIf(Assembler::Zero, ins->snapshot()); } else { // -0|0 == 0 } } @@ -1095,18 +1095,18 @@ CodeGeneratorARM::emitTableSwitchDispatc // switch table before the table actually starts. Since the only other // unhandled case is the default case (both out of range high and out of // range low) I then insert a branch to default case into the extra slot, // which ensures we don't attempt to execute the address table. Label* defaultcase = skipTrivialBlocks(mir->getDefault())->lir()->label(); int32_t cases = mir->numCases(); // Lower value with low value. - masm.ma_sub(index, Imm32(mir->low()), index, SetCond); - masm.ma_rsb(index, Imm32(cases - 1), index, SetCond, Assembler::NotSigned); + masm.ma_sub(index, Imm32(mir->low()), index, SetCC); + masm.ma_rsb(index, Imm32(cases - 1), index, SetCC, Assembler::NotSigned); // Inhibit pools within the following sequence because we are indexing into // a pc relative table. The region will have one instruction for ma_ldr, one // for ma_b, and each table case takes one word. AutoForbidPools afp(&masm, 1 + 1 + cases); masm.ma_ldr(DTRAddr(pc, DtrRegImmShift(index, LSL, 2)), pc, Offset, Assembler::NotSigned); masm.ma_b(defaultcase); // To fill in the CodeLabels for the case entries, we need to first generate @@ -1607,18 +1607,18 @@ CodeGeneratorARM::visitNotD(LNotD* ins) masm.as_vmrs(dest); masm.ma_lsr(Imm32(28), dest, dest); // 28 + 2 = 30 masm.ma_alu(dest, lsr(dest, 2), dest, OpOrr); masm.ma_and(Imm32(1), dest); } else { masm.as_vmrs(pc); masm.ma_mov(Imm32(0), dest); - masm.ma_mov(Imm32(1), dest, NoSetCond, Assembler::Equal); - masm.ma_mov(Imm32(1), dest, NoSetCond, Assembler::Overflow); + masm.ma_mov(Imm32(1), dest, LeaveCC, Assembler::Equal); + masm.ma_mov(Imm32(1), dest, LeaveCC, Assembler::Overflow); } } void CodeGeneratorARM::visitNotF(LNotF* ins) { // Since this operation is not, we want to set a bit if the double is // falsey, which means 0.0, -0.0 or NaN. When comparing with 0, an input of @@ -1635,18 +1635,18 @@ CodeGeneratorARM::visitNotF(LNotF* ins) masm.as_vmrs(dest); masm.ma_lsr(Imm32(28), dest, dest); // 28 + 2 = 30 masm.ma_alu(dest, lsr(dest, 2), dest, OpOrr); masm.ma_and(Imm32(1), dest); } else { masm.as_vmrs(pc); masm.ma_mov(Imm32(0), dest); - masm.ma_mov(Imm32(1), dest, NoSetCond, Assembler::Equal); - masm.ma_mov(Imm32(1), dest, NoSetCond, Assembler::Overflow); + masm.ma_mov(Imm32(1), dest, LeaveCC, Assembler::Equal); + masm.ma_mov(Imm32(1), dest, LeaveCC, Assembler::Overflow); } } void CodeGeneratorARM::visitGuardShape(LGuardShape* guard) { Register obj = ToRegister(guard->input()); Register tmp = ToRegister(guard->tempInt()); @@ -1788,19 +1788,19 @@ CodeGeneratorARM::visitAsmJSLoadHeap(LAs if (ptr->isConstant()) { MOZ_ASSERT(!mir->needsBoundsCheck()); int32_t ptrImm = ptr->toConstant()->toInt32(); MOZ_ASSERT(ptrImm >= 0); if (isFloat) { VFPRegister vd(ToFloatRegister(ins->output())); if (size == 32) - masm.ma_vldr(Operand(HeapReg, ptrImm), vd.singleOverlay(), Assembler::Always); + masm.ma_vldr(Address(HeapReg, ptrImm), vd.singleOverlay(), Assembler::Always); else - masm.ma_vldr(Operand(HeapReg, ptrImm), vd, Assembler::Always); + masm.ma_vldr(Address(HeapReg, ptrImm), vd, Assembler::Always); } else { masm.ma_dataTransferN(IsLoad, size, isSigned, HeapReg, Imm32(ptrImm), ToRegister(ins->output()), Offset, Assembler::Always); } memoryBarrier(mir->barrierAfter()); return; } @@ -1821,27 +1821,27 @@ CodeGeneratorARM::visitAsmJSLoadHeap(LAs return; } BufferOffset bo = masm.ma_BoundsCheck(ptrReg); if (isFloat) { FloatRegister dst = ToFloatRegister(ins->output()); VFPRegister vd(dst); if (size == 32) { - masm.ma_vldr(Operand(GlobalReg, AsmJSNaN32GlobalDataOffset - AsmJSGlobalRegBias), + masm.ma_vldr(Address(GlobalReg, AsmJSNaN32GlobalDataOffset - AsmJSGlobalRegBias), vd.singleOverlay(), Assembler::AboveOrEqual); masm.ma_vldr(vd.singleOverlay(), HeapReg, ptrReg, 0, Assembler::Below); } else { - masm.ma_vldr(Operand(GlobalReg, AsmJSNaN64GlobalDataOffset - AsmJSGlobalRegBias), + masm.ma_vldr(Address(GlobalReg, AsmJSNaN64GlobalDataOffset - AsmJSGlobalRegBias), vd, Assembler::AboveOrEqual); masm.ma_vldr(vd, HeapReg, ptrReg, 0, Assembler::Below); } } else { Register d = ToRegister(ins->output()); - masm.ma_mov(Imm32(0), d, NoSetCond, Assembler::AboveOrEqual); + masm.ma_mov(Imm32(0), d, LeaveCC, Assembler::AboveOrEqual); masm.ma_dataTransferN(IsLoad, size, isSigned, HeapReg, ptrReg, d, Offset, Assembler::Below); } memoryBarrier(mir->barrierAfter()); masm.append(AsmJSHeapAccess(bo.getOffset())); } void CodeGeneratorARM::visitAsmJSStoreHeap(LAsmJSStoreHeap* ins) @@ -1865,19 +1865,19 @@ CodeGeneratorARM::visitAsmJSStoreHeap(LA memoryBarrier(mir->barrierBefore()); if (ptr->isConstant()) { MOZ_ASSERT(!mir->needsBoundsCheck()); int32_t ptrImm = ptr->toConstant()->toInt32(); MOZ_ASSERT(ptrImm >= 0); if (isFloat) { VFPRegister vd(ToFloatRegister(ins->value())); if (size == 32) - masm.ma_vstr(vd.singleOverlay(), Operand(HeapReg, ptrImm), Assembler::Always); + masm.ma_vstr(vd.singleOverlay(), Address(HeapReg, ptrImm), Assembler::Always); else - masm.ma_vstr(vd, Operand(HeapReg, ptrImm), Assembler::Always); + masm.ma_vstr(vd, Address(HeapReg, ptrImm), Assembler::Always); } else { masm.ma_dataTransferN(IsStore, size, isSigned, HeapReg, Imm32(ptrImm), ToRegister(ins->value()), Offset, Assembler::Always); } memoryBarrier(mir->barrierAfter()); return; } @@ -2084,17 +2084,17 @@ CodeGeneratorARM::visitAsmJSAtomicBinopC MOZ_CRASH("Unknown op"); } } void CodeGeneratorARM::visitAsmJSPassStackArg(LAsmJSPassStackArg* ins) { const MAsmJSPassStackArg* mir = ins->mir(); - Operand dst(StackPointer, mir->spOffset()); + Address dst(StackPointer, mir->spOffset()); if (ins->arg()->isConstant()) { //masm.as_bkpt(); masm.ma_storeImm(Imm32(ToInt32(ins->arg())), dst); } else { if (ins->arg()->isGeneralReg()) masm.ma_str(ToRegister(ins->arg()), dst); else masm.ma_vstr(ToFloatRegister(ins->arg()), dst); @@ -2226,38 +2226,38 @@ void CodeGeneratorARM::visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar* ins) { const MAsmJSLoadGlobalVar* mir = ins->mir(); unsigned addr = mir->globalDataOffset() - AsmJSGlobalRegBias; if (mir->type() == MIRType_Int32) { masm.ma_dtr(IsLoad, GlobalReg, Imm32(addr), ToRegister(ins->output())); } else if (mir->type() == MIRType_Float32) { VFPRegister vd(ToFloatRegister(ins->output())); - masm.ma_vldr(Operand(GlobalReg, addr), vd.singleOverlay()); + masm.ma_vldr(Address(GlobalReg, addr), vd.singleOverlay()); } else { - masm.ma_vldr(Operand(GlobalReg, addr), ToFloatRegister(ins->output())); + masm.ma_vldr(Address(GlobalReg, addr), ToFloatRegister(ins->output())); } } void CodeGeneratorARM::visitAsmJSStoreGlobalVar(LAsmJSStoreGlobalVar* ins) { const MAsmJSStoreGlobalVar* mir = ins->mir(); MIRType type = mir->value()->type(); MOZ_ASSERT(IsNumberType(type)); unsigned addr = mir->globalDataOffset() - AsmJSGlobalRegBias; if (type == MIRType_Int32) { masm.ma_dtr(IsStore, GlobalReg, Imm32(addr), ToRegister(ins->value())); } else if (type == MIRType_Float32) { VFPRegister vd(ToFloatRegister(ins->value())); - masm.ma_vstr(vd.singleOverlay(), Operand(GlobalReg, addr)); + masm.ma_vstr(vd.singleOverlay(), Address(GlobalReg, addr)); } else { - masm.ma_vstr(ToFloatRegister(ins->value()), Operand(GlobalReg, addr)); + masm.ma_vstr(ToFloatRegister(ins->value()), Address(GlobalReg, addr)); } } void CodeGeneratorARM::visitAsmJSLoadFuncPtr(LAsmJSLoadFuncPtr* ins) { const MAsmJSLoadFuncPtr* mir = ins->mir(); @@ -2270,17 +2270,17 @@ CodeGeneratorARM::visitAsmJSLoadFuncPtr( masm.ma_ldr(DTRAddr(GlobalReg, DtrRegImmShift(tmp, LSL, 0)), out); } void CodeGeneratorARM::visitAsmJSLoadFFIFunc(LAsmJSLoadFFIFunc* ins) { const MAsmJSLoadFFIFunc* mir = ins->mir(); - masm.ma_ldr(Operand(GlobalReg, mir->globalDataOffset() - AsmJSGlobalRegBias), + masm.ma_ldr(Address(GlobalReg, mir->globalDataOffset() - AsmJSGlobalRegBias), ToRegister(ins->output())); } void CodeGeneratorARM::visitNegI(LNegI* ins) { Register input = ToRegister(ins->input()); masm.ma_neg(input, ToRegister(ins->output()));
--- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -52,17 +52,17 @@ MacroAssemblerARM::convertInt32ToDouble( as_vxfer(src, InvalidReg, dest.sintOverlay(), CoreToFloat); as_vcvt(dest, dest.sintOverlay()); } void MacroAssemblerARM::convertInt32ToDouble(const Address& src, FloatRegister dest) { - ma_vldr(Operand(src), ScratchDoubleReg); + ma_vldr(src, ScratchDoubleReg); as_vcvt(dest, VFPRegister(ScratchDoubleReg).sintOverlay()); } void MacroAssemblerARM::convertInt32ToDouble(const BaseIndex& src, FloatRegister dest) { Register base = src.base; uint32_t scale = Imm32::ShiftOf(src.scale).value; @@ -209,17 +209,17 @@ MacroAssemblerARM::convertInt32ToFloat32 // Direct conversions aren't possible. as_vxfer(src, InvalidReg, dest.sintOverlay(), CoreToFloat); as_vcvt(dest.singleOverlay(), dest.sintOverlay()); } void MacroAssemblerARM::convertInt32ToFloat32(const Address& src, FloatRegister dest) { - ma_vldr(Operand(src), ScratchFloat32Reg); + ma_vldr(src, ScratchFloat32Reg); as_vcvt(dest, VFPRegister(ScratchFloat32Reg).sintOverlay()); } void MacroAssemblerARM::addDouble(FloatRegister src, FloatRegister dest) { ma_vadd(dest, src, dest); } @@ -253,114 +253,114 @@ MacroAssemblerARM::inc64(AbsoluteAddress { ma_strd(r0, r1, EDtrAddr(sp, EDtrOffImm(-8)), PreIndex); ma_mov(Imm32((int32_t)dest.addr), ScratchRegister); ma_ldrd(EDtrAddr(ScratchRegister, EDtrOffImm(0)), r0, r1); - ma_add(Imm32(1), r0, SetCond); - ma_adc(Imm32(0), r1, NoSetCond); + ma_add(Imm32(1), r0, SetCC); + ma_adc(Imm32(0), r1, LeaveCC); ma_strd(r0, r1, EDtrAddr(ScratchRegister, EDtrOffImm(0))); ma_ldrd(EDtrAddr(sp, EDtrOffImm(8)), r0, r1, PostIndex); } bool MacroAssemblerARM::alu_dbl(Register src1, Imm32 imm, Register dest, ALUOp op, - SetCond_ sc, Condition c) -{ - if ((sc == SetCond && ! condsAreSafe(op)) || !can_dbl(op)) + SBit s, Condition c) +{ + if ((s == SetCC && ! condsAreSafe(op)) || !can_dbl(op)) return false; ALUOp interop = getDestVariant(op); Imm8::TwoImm8mData both = Imm8::EncodeTwoImms(imm.value); if (both.fst.invalid) return false; // For the most part, there is no good reason to set the condition codes for // the first instruction. We can do better things if the second instruction // doesn't have a dest, such as check for overflow by doing first operation // don't do second operation if first operation overflowed. This preserves // the overflow condition code. Unfortunately, it is horribly brittle. - as_alu(ScratchRegister, src1, both.fst, interop, NoSetCond, c); - as_alu(dest, ScratchRegister, both.snd, op, sc, c); + as_alu(ScratchRegister, src1, Operand2(both.fst), interop, LeaveCC, c); + as_alu(dest, ScratchRegister, Operand2(both.snd), op, s, c); return true; } void MacroAssemblerARM::ma_alu(Register src1, Imm32 imm, Register dest, ALUOp op, - SetCond_ sc, Condition c) + SBit s, Condition c) { // As it turns out, if you ask for a compare-like instruction you *probably* // want it to set condition codes. if (dest == InvalidReg) - MOZ_ASSERT(sc == SetCond); + MOZ_ASSERT(s == SetCC); // The operator gives us the ability to determine how this can be used. Imm8 imm8 = Imm8(imm.value); // One instruction: If we can encode it using an imm8m, then do so. if (!imm8.invalid) { - as_alu(dest, src1, imm8, op, sc, c); + as_alu(dest, src1, imm8, op, s, c); return; } // One instruction, negated: Imm32 negImm = imm; Register negDest; ALUOp negOp = ALUNeg(op, dest, &negImm, &negDest); Imm8 negImm8 = Imm8(negImm.value); // 'add r1, r2, -15' can be replaced with 'sub r1, r2, 15'. For bonus // points, dest can be replaced (nearly always invalid => ScratchRegister) // This is useful if we wish to negate tst. tst has an invalid (aka not // used) dest, but its negation is bic *requires* a dest. We can accomodate, // but it will need to clobber *something*, and the scratch register isn't // being used, so... if (negOp != OpInvalid && !negImm8.invalid) { - as_alu(negDest, src1, negImm8, negOp, sc, c); + as_alu(negDest, src1, negImm8, negOp, s, c); return; } if (HasMOVWT()) { // If the operation is a move-a-like then we can try to use movw to move // the bits into the destination. Otherwise, we'll need to fall back on // a multi-instruction format :( // movw/movt does not set condition codes, so don't hold your breath. - if (sc == NoSetCond && (op == OpMov || op == OpMvn)) { + if (s == LeaveCC && (op == OpMov || op == OpMvn)) { // ARMv7 supports movw/movt. movw zero-extends its 16 bit argument, // so we can set the register this way. movt leaves the bottom 16 // bits in tact, so it is unsuitable to move a constant that if (op == OpMov && ((imm.value & ~ 0xffff) == 0)) { MOZ_ASSERT(src1 == InvalidReg); - as_movw(dest, (uint16_t)imm.value, c); + as_movw(dest, Imm16((uint16_t)imm.value), c); return; } // If they asked for a mvn rfoo, imm, where ~imm fits into 16 bits // then do it. if (op == OpMvn && (((~imm.value) & ~ 0xffff) == 0)) { MOZ_ASSERT(src1 == InvalidReg); - as_movw(dest, (uint16_t)~imm.value, c); + as_movw(dest, Imm16((uint16_t)~imm.value), c); return; } // TODO: constant dedup may enable us to add dest, r0, 23 *if* we // are attempting to load a constant that looks similar to one that // already exists. If it can't be done with a single movw then we // *need* to use two instructions since this must be some sort of a // move operation, we can just use a movw/movt pair and get the // whole thing done in two moves. This does not work for ops like // add, since we'd need to do: movw tmp; movt tmp; add dest, tmp, // src1. if (op == OpMvn) imm.value = ~imm.value; - as_movw(dest, imm.value & 0xffff, c); - as_movt(dest, (imm.value >> 16) & 0xffff, c); + as_movw(dest, Imm16(imm.value & 0xffff), c); + as_movt(dest, Imm16((imm.value >> 16) & 0xffff), c); return; } // If we weren't doing a movalike, a 16 bit immediate will require 2 // instructions. With the same amount of space and (less)time, we can do // two 8 bit operations, reusing the dest register. e.g. // movw tmp, 0xffff; add dest, src, tmp ror 4 // vs. // add dest, src, 0xff0; add dest, dest, 0xf000000f @@ -375,58 +375,58 @@ MacroAssemblerARM::ma_alu(Register src1, // Either a) this isn't ARMv7 b) this isn't a move start by attempting to // generate a two instruction form. Some things cannot be made into two-inst // forms correctly. Namely, adds dest, src, 0xffff. Since we want the // condition codes (and don't know which ones will be checked), we need to // assume that the overflow flag will be checked and add{,s} dest, src, // 0xff00; add{,s} dest, dest, 0xff is not guaranteed to set the overflow // flag the same as the (theoretical) one instruction variant. - if (alu_dbl(src1, imm, dest, op, sc, c)) + if (alu_dbl(src1, imm, dest, op, s, c)) return; // And try with its negative. if (negOp != OpInvalid && - alu_dbl(src1, negImm, negDest, negOp, sc, c)) + alu_dbl(src1, negImm, negDest, negOp, s, c)) return; // Well, damn. We can use two 16 bit mov's, then do the op or we can do a // single load from a pool then op. if (HasMOVWT()) { // Try to load the immediate into a scratch register then use that - as_movw(ScratchRegister, imm.value & 0xffff, c); + as_movw(ScratchRegister, Imm16(imm.value & 0xffff), c); if ((imm.value >> 16) != 0) - as_movt(ScratchRegister, (imm.value >> 16) & 0xffff, c); + as_movt(ScratchRegister, Imm16((imm.value >> 16) & 0xffff), c); } else { // Going to have to use a load. If the operation is a move, then just // move it into the destination register if (op == OpMov) { as_Imm32Pool(dest, imm.value, c); return; } else { // If this isn't just going into a register, then stick it in a // temp, and then proceed. as_Imm32Pool(ScratchRegister, imm.value, c); } } - as_alu(dest, src1, O2Reg(ScratchRegister), op, sc, c); + as_alu(dest, src1, O2Reg(ScratchRegister), op, s, c); } void MacroAssemblerARM::ma_alu(Register src1, Operand op2, Register dest, ALUOp op, - SetCond_ sc, Assembler::Condition c) + SBit s, Assembler::Condition c) { MOZ_ASSERT(op2.getTag() == Operand::OP2); - as_alu(dest, src1, op2.toOp2(), op, sc, c); -} - -void -MacroAssemblerARM::ma_alu(Register src1, Operand2 op2, Register dest, ALUOp op, SetCond_ sc, Condition c) -{ - as_alu(dest, src1, op2, op, sc, c); + as_alu(dest, src1, op2.toOp2(), op, s, c); +} + +void +MacroAssemblerARM::ma_alu(Register src1, Operand2 op2, Register dest, ALUOp op, SBit s, Condition c) +{ + as_alu(dest, src1, op2, op, s, c); } void MacroAssemblerARM::ma_nop() { as_nop(); } @@ -479,34 +479,34 @@ MacroAssemblerARM::ma_mov_patch(Imm32 im /* static */ void MacroAssemblerARM::ma_mov_patch(ImmPtr imm, Register dest, Assembler::Condition c, RelocStyle rs, Instruction* i) { ma_mov_patch(Imm32(int32_t(imm.value)), dest, c, rs, i); } void -MacroAssemblerARM::ma_mov(Register src, Register dest, SetCond_ sc, Assembler::Condition c) -{ - if (sc == SetCond || dest != src) - as_mov(dest, O2Reg(src), sc, c); +MacroAssemblerARM::ma_mov(Register src, Register dest, SBit s, Assembler::Condition c) +{ + if (s == SetCC || dest != src) + as_mov(dest, O2Reg(src), s, c); } void MacroAssemblerARM::ma_mov(Imm32 imm, Register dest, - SetCond_ sc, Assembler::Condition c) -{ - ma_alu(InvalidReg, imm, dest, OpMov, sc, c); + SBit s, Assembler::Condition c) +{ + ma_alu(InvalidReg, imm, dest, OpMov, s, c); } void MacroAssemblerARM::ma_mov(ImmWord imm, Register dest, - SetCond_ sc, Assembler::Condition c) -{ - ma_alu(InvalidReg, Imm32(imm.value), dest, OpMov, sc, c); + SBit s, Assembler::Condition c) +{ + ma_alu(InvalidReg, Imm32(imm.value), dest, OpMov, s, c); } void MacroAssemblerARM::ma_mov(ImmGCPtr ptr, Register dest) { // As opposed to x86/x64 version, the data relocation has to be executed // before to recover the pointer, and not after. writeDataRelocation(ptr); @@ -571,265 +571,265 @@ void MacroAssemblerARM::ma_rol(Register shift, Register src, Register dst) { ma_rsb(shift, Imm32(32), ScratchRegister); as_mov(dst, ror(src, ScratchRegister)); } // Move not (dest <- ~src) void -MacroAssemblerARM::ma_mvn(Imm32 imm, Register dest, SetCond_ sc, Assembler::Condition c) -{ - ma_alu(InvalidReg, imm, dest, OpMvn, sc, c); -} - -void -MacroAssemblerARM::ma_mvn(Register src1, Register dest, SetCond_ sc, Assembler::Condition c) -{ - as_alu(dest, InvalidReg, O2Reg(src1), OpMvn, sc, c); +MacroAssemblerARM::ma_mvn(Imm32 imm, Register dest, SBit s, Assembler::Condition c) +{ + ma_alu(InvalidReg, imm, dest, OpMvn, s, c); +} + +void +MacroAssemblerARM::ma_mvn(Register src1, Register dest, SBit s, Assembler::Condition c) +{ + as_alu(dest, InvalidReg, O2Reg(src1), OpMvn, s, c); } // Negate (dest <- -src), src is a register, rather than a general op2. void -MacroAssemblerARM::ma_neg(Register src1, Register dest, SetCond_ sc, Assembler::Condition c) -{ - as_rsb(dest, src1, Imm8(0), sc, c); +MacroAssemblerARM::ma_neg(Register src1, Register dest, SBit s, Assembler::Condition c) +{ + as_rsb(dest, src1, Imm8(0), s, c); } // And. void -MacroAssemblerARM::ma_and(Register src, Register dest, SetCond_ sc, Assembler::Condition c) +MacroAssemblerARM::ma_and(Register src, Register dest, SBit s, Assembler::Condition c) { ma_and(dest, src, dest); } void MacroAssemblerARM::ma_and(Register src1, Register src2, Register dest, - SetCond_ sc, Assembler::Condition c) -{ - as_and(dest, src1, O2Reg(src2), sc, c); -} -void -MacroAssemblerARM::ma_and(Imm32 imm, Register dest, SetCond_ sc, Assembler::Condition c) -{ - ma_alu(dest, imm, dest, OpAnd, sc, c); + SBit s, Assembler::Condition c) +{ + as_and(dest, src1, O2Reg(src2), s, c); +} +void +MacroAssemblerARM::ma_and(Imm32 imm, Register dest, SBit s, Assembler::Condition c) +{ + ma_alu(dest, imm, dest, OpAnd, s, c); } void MacroAssemblerARM::ma_and(Imm32 imm, Register src1, Register dest, - SetCond_ sc, Assembler::Condition c) -{ - ma_alu(src1, imm, dest, OpAnd, sc, c); + SBit s, Assembler::Condition c) +{ + ma_alu(src1, imm, dest, OpAnd, s, c); } // Bit clear (dest <- dest & ~imm) or (dest <- src1 & ~src2). void -MacroAssemblerARM::ma_bic(Imm32 imm, Register dest, SetCond_ sc, Assembler::Condition c) -{ - ma_alu(dest, imm, dest, OpBic, sc, c); +MacroAssemblerARM::ma_bic(Imm32 imm, Register dest, SBit s, Assembler::Condition c) +{ + ma_alu(dest, imm, dest, OpBic, s, c); } // Exclusive or. void -MacroAssemblerARM::ma_eor(Register src, Register dest, SetCond_ sc, Assembler::Condition c) -{ - ma_eor(dest, src, dest, sc, c); +MacroAssemblerARM::ma_eor(Register src, Register dest, SBit s, Assembler::Condition c) +{ + ma_eor(dest, src, dest, s, c); } void MacroAssemblerARM::ma_eor(Register src1, Register src2, Register dest, - SetCond_ sc, Assembler::Condition c) -{ - as_eor(dest, src1, O2Reg(src2), sc, c); -} -void -MacroAssemblerARM::ma_eor(Imm32 imm, Register dest, SetCond_ sc, Assembler::Condition c) -{ - ma_alu(dest, imm, dest, OpEor, sc, c); + SBit s, Assembler::Condition c) +{ + as_eor(dest, src1, O2Reg(src2), s, c); +} +void +MacroAssemblerARM::ma_eor(Imm32 imm, Register dest, SBit s, Assembler::Condition c) +{ + ma_alu(dest, imm, dest, OpEor, s, c); } void MacroAssemblerARM::ma_eor(Imm32 imm, Register src1, Register dest, - SetCond_ sc, Assembler::Condition c) -{ - ma_alu(src1, imm, dest, OpEor, sc, c); + SBit s, Assembler::Condition c) +{ + ma_alu(src1, imm, dest, OpEor, s, c); } // Or. void -MacroAssemblerARM::ma_orr(Register src, Register dest, SetCond_ sc, Assembler::Condition c) -{ - ma_orr(dest, src, dest, sc, c); +MacroAssemblerARM::ma_orr(Register src, Register dest, SBit s, Assembler::Condition c) +{ + ma_orr(dest, src, dest, s, c); } void MacroAssemblerARM::ma_orr(Register src1, Register src2, Register dest, - SetCond_ sc, Assembler::Condition c) -{ - as_orr(dest, src1, O2Reg(src2), sc, c); -} -void -MacroAssemblerARM::ma_orr(Imm32 imm, Register dest, SetCond_ sc, Assembler::Condition c) -{ - ma_alu(dest, imm, dest, OpOrr, sc, c); + SBit s, Assembler::Condition c) +{ + as_orr(dest, src1, O2Reg(src2), s, c); +} +void +MacroAssemblerARM::ma_orr(Imm32 imm, Register dest, SBit s, Assembler::Condition c) +{ + ma_alu(dest, imm, dest, OpOrr, s, c); } void MacroAssemblerARM::ma_orr(Imm32 imm, Register src1, Register dest, - SetCond_ sc, Assembler::Condition c) -{ - ma_alu(src1, imm, dest, OpOrr, sc, c); + SBit s, Assembler::Condition c) +{ + ma_alu(src1, imm, dest, OpOrr, s, c); } // Arithmetic-based ops. // Add with carry. void -MacroAssemblerARM::ma_adc(Imm32 imm, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(dest, imm, dest, OpAdc, sc, c); -} -void -MacroAssemblerARM::ma_adc(Register src, Register dest, SetCond_ sc, Condition c) -{ - as_alu(dest, dest, O2Reg(src), OpAdc, sc, c); -} -void -MacroAssemblerARM::ma_adc(Register src1, Register src2, Register dest, SetCond_ sc, Condition c) -{ - as_alu(dest, src1, O2Reg(src2), OpAdc, sc, c); +MacroAssemblerARM::ma_adc(Imm32 imm, Register dest, SBit s, Condition c) +{ + ma_alu(dest, imm, dest, OpAdc, s, c); +} +void +MacroAssemblerARM::ma_adc(Register src, Register dest, SBit s, Condition c) +{ + as_alu(dest, dest, O2Reg(src), OpAdc, s, c); +} +void +MacroAssemblerARM::ma_adc(Register src1, Register src2, Register dest, SBit s, Condition c) +{ + as_alu(dest, src1, O2Reg(src2), OpAdc, s, c); } // Add. void -MacroAssemblerARM::ma_add(Imm32 imm, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(dest, imm, dest, OpAdd, sc, c); -} - -void -MacroAssemblerARM::ma_add(Register src1, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(dest, O2Reg(src1), dest, OpAdd, sc, c); -} -void -MacroAssemblerARM::ma_add(Register src1, Register src2, Register dest, SetCond_ sc, Condition c) -{ - as_alu(dest, src1, O2Reg(src2), OpAdd, sc, c); -} -void -MacroAssemblerARM::ma_add(Register src1, Operand op, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(src1, op, dest, OpAdd, sc, c); -} -void -MacroAssemblerARM::ma_add(Register src1, Imm32 op, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(src1, op, dest, OpAdd, sc, c); +MacroAssemblerARM::ma_add(Imm32 imm, Register dest, SBit s, Condition c) +{ + ma_alu(dest, imm, dest, OpAdd, s, c); +} + +void +MacroAssemblerARM::ma_add(Register src1, Register dest, SBit s, Condition c) +{ + ma_alu(dest, O2Reg(src1), dest, OpAdd, s, c); +} +void +MacroAssemblerARM::ma_add(Register src1, Register src2, Register dest, SBit s, Condition c) +{ + as_alu(dest, src1, O2Reg(src2), OpAdd, s, c); +} +void +MacroAssemblerARM::ma_add(Register src1, Operand op, Register dest, SBit s, Condition c) +{ + ma_alu(src1, op, dest, OpAdd, s, c); +} +void +MacroAssemblerARM::ma_add(Register src1, Imm32 op, Register dest, SBit s, Condition c) +{ + ma_alu(src1, op, dest, OpAdd, s, c); } // Subtract with carry. void -MacroAssemblerARM::ma_sbc(Imm32 imm, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(dest, imm, dest, OpSbc, sc, c); -} -void -MacroAssemblerARM::ma_sbc(Register src1, Register dest, SetCond_ sc, Condition c) -{ - as_alu(dest, dest, O2Reg(src1), OpSbc, sc, c); -} -void -MacroAssemblerARM::ma_sbc(Register src1, Register src2, Register dest, SetCond_ sc, Condition c) -{ - as_alu(dest, src1, O2Reg(src2), OpSbc, sc, c); +MacroAssemblerARM::ma_sbc(Imm32 imm, Register dest, SBit s, Condition c) +{ + ma_alu(dest, imm, dest, OpSbc, s, c); +} +void +MacroAssemblerARM::ma_sbc(Register src1, Register dest, SBit s, Condition c) +{ + as_alu(dest, dest, O2Reg(src1), OpSbc, s, c); +} +void +MacroAssemblerARM::ma_sbc(Register src1, Register src2, Register dest, SBit s, Condition c) +{ + as_alu(dest, src1, O2Reg(src2), OpSbc, s, c); } // Subtract. void -MacroAssemblerARM::ma_sub(Imm32 imm, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(dest, imm, dest, OpSub, sc, c); -} -void -MacroAssemblerARM::ma_sub(Register src1, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(dest, Operand(src1), dest, OpSub, sc, c); -} -void -MacroAssemblerARM::ma_sub(Register src1, Register src2, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(src1, Operand(src2), dest, OpSub, sc, c); -} -void -MacroAssemblerARM::ma_sub(Register src1, Operand op, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(src1, op, dest, OpSub, sc, c); -} -void -MacroAssemblerARM::ma_sub(Register src1, Imm32 op, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(src1, op, dest, OpSub, sc, c); +MacroAssemblerARM::ma_sub(Imm32 imm, Register dest, SBit s, Condition c) +{ + ma_alu(dest, imm, dest, OpSub, s, c); +} +void +MacroAssemblerARM::ma_sub(Register src1, Register dest, SBit s, Condition c) +{ + ma_alu(dest, Operand(src1), dest, OpSub, s, c); +} +void +MacroAssemblerARM::ma_sub(Register src1, Register src2, Register dest, SBit s, Condition c) +{ + ma_alu(src1, Operand(src2), dest, OpSub, s, c); +} +void +MacroAssemblerARM::ma_sub(Register src1, Operand op, Register dest, SBit s, Condition c) +{ + ma_alu(src1, op, dest, OpSub, s, c); +} +void +MacroAssemblerARM::ma_sub(Register src1, Imm32 op, Register dest, SBit s, Condition c) +{ + ma_alu(src1, op, dest, OpSub, s, c); } // Severse subtract. void -MacroAssemblerARM::ma_rsb(Imm32 imm, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(dest, imm, dest, OpRsb, sc, c); -} -void -MacroAssemblerARM::ma_rsb(Register src1, Register dest, SetCond_ sc, Condition c) -{ - as_alu(dest, dest, O2Reg(src1), OpAdd, sc, c); -} -void -MacroAssemblerARM::ma_rsb(Register src1, Register src2, Register dest, SetCond_ sc, Condition c) -{ - as_alu(dest, src1, O2Reg(src2), OpRsb, sc, c); -} -void -MacroAssemblerARM::ma_rsb(Register src1, Imm32 op2, Register dest, SetCond_ sc, Condition c) -{ - ma_alu(src1, op2, dest, OpRsb, sc, c); +MacroAssemblerARM::ma_rsb(Imm32 imm, Register dest, SBit s, Condition c) +{ + ma_alu(dest, imm, dest, OpRsb, s, c); +} +void +MacroAssemblerARM::ma_rsb(Register src1, Register dest, SBit s, Condition c) +{ + as_alu(dest, dest, O2Reg(src1), OpAdd, s, c); +} +void +MacroAssemblerARM::ma_rsb(Register src1, Register src2, Register dest, SBit s, Condition c) +{ + as_alu(dest, src1, O2Reg(src2), OpRsb, s, c); +} +void +MacroAssemblerARM::ma_rsb(Register src1, Imm32 op2, Register dest, SBit s, Condition c) +{ + ma_alu(src1, op2, dest, OpRsb, s, c);