Bug 1118978 part 1. Introduce "Affects" and "DependsOn" state for IDL attributes and operations and desugar [Pure] and [Constant] into that state. r=peterv
authorBoris Zbarsky <bzbarsky@mit.edu>
Thu, 15 Jan 2015 17:39:01 -0500
changeset 224351 ded8921cf389246c120ec135c4f6d0d567eb1a0c
parent 224350 0640a60fdba5d585d0bb8f6adf95629a764b405d
child 224352 c20c84c93efc7a4d36b6320ab6ff411dafbc8bce
push id28123
push userphilringnalda@gmail.com
push dateSat, 17 Jan 2015 20:51:39 +0000
treeherdermozilla-central@b86864fd9d60 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1118978
milestone38.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
Bug 1118978 part 1. Introduce "Affects" and "DependsOn" state for IDL attributes and operations and desugar [Pure] and [Constant] into that state. r=peterv This does not change the generated binding code in any way for our existing IDL files.
dom/bindings/Codegen.py
dom/bindings/parser/WebIDL.py
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -5,17 +5,17 @@
 # Common codegen classes.
 
 import os
 import re
 import string
 import math
 import textwrap
 
-from WebIDL import BuiltinTypes, IDLBuiltinType, IDLNullValue, IDLSequenceType, IDLType, IDLAttribute, IDLUndefinedValue, IDLEmptySequenceValue, IDLDictionary
+from WebIDL import BuiltinTypes, IDLBuiltinType, IDLNullValue, IDLSequenceType, IDLType, IDLAttribute, IDLInterfaceMember, IDLUndefinedValue, IDLEmptySequenceValue, IDLDictionary
 from Configuration import NoSuchDescriptorError, getTypesFromDescriptor, getTypesFromDictionary, getTypesFromCallback, getAllTypes, Descriptor
 
 AUTOGENERATED_WARNING_COMMENT = \
     "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
 ADDPROPERTY_HOOK_NAME = '_addProperty'
 FINALIZE_HOOK_NAME = '_finalize'
 OBJECT_MOVED_HOOK_NAME = '_objectMoved'
 CONSTRUCT_HOOK_NAME = '_constructor'
@@ -2109,18 +2109,17 @@ def isMaybeExposedIn(member, descriptor)
     # All we can say for sure is that if this is a worker descriptor
     # and member is only exposed in windows, then it's not exposed.
     return not descriptor.workers or member.exposureSet != set(["Window"])
 
 def clearableCachedAttrs(descriptor):
     return (m for m in descriptor.interface.members if
             m.isAttr() and
             # Constants should never need clearing!
-            not m.getExtendedAttribute("Constant") and
-            not m.getExtendedAttribute("SameObject") and
+            m.dependsOn != "Nothing" and
             m.slotIndex is not None)
 
 def MakeClearCachedValueNativeName(member):
     return "ClearCached%sValue" % MakeNativeName(member.identifier.name)
 
 def MakeJSImplClearCachedValueNativeName(member):
     return "_" + MakeClearCachedValueNativeName(member)
 
@@ -8075,26 +8074,19 @@ class CGMemberJITInfo(CGThing):
         if self.member.isAttr():
             getterinfo = ("%s_getterinfo" %
                           IDLToCIdentifier(self.member.identifier.name))
             # We need the cast here because JSJitGetterOp has a "void* self"
             # while we have the right type.
             getter = ("(JSJitGetterOp)get_%s" %
                       IDLToCIdentifier(self.member.identifier.name))
             getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True)
-            getterconst = (self.member.getExtendedAttribute("SameObject") or
-                           self.member.getExtendedAttribute("Constant"))
-            getterpure = getterconst or self.member.getExtendedAttribute("Pure")
-            if getterconst:
-                aliasSet = "AliasNone"
-            elif getterpure:
-                aliasSet = "AliasDOMSets"
-            else:
-                aliasSet = "AliasEverything"
-            movable = getterpure and getterinfal
+
+            movable = self.mayBeMovable() and getterinfal
+            aliasSet = self.aliasSet()
 
             getterinfal = getterinfal and infallibleForMember(self.member, self.member.type, self.descriptor)
             isAlwaysInSlot = self.member.getExtendedAttribute("StoreInSlot")
             if self.member.slotIndex is not None:
                 assert isAlwaysInSlot or self.member.getExtendedAttribute("Cached")
                 isLazilyCachedInSlot = not isAlwaysInSlot
                 slotIndex = memberReservedSlot(self.member)
                 # We'll statically assert that this is not too big in
@@ -8129,62 +8121,89 @@ class CGMemberJITInfo(CGThing):
             methodinfo = ("%s_methodinfo" %
                           IDLToCIdentifier(self.member.identifier.name))
             name = CppKeywords.checkMethodName(
                 IDLToCIdentifier(self.member.identifier.name))
             if self.member.returnsPromise():
                 name = CGMethodPromiseWrapper.makeName(name)
             # Actually a JSJitMethodOp, but JSJitGetterOp is first in the union.
             method = ("(JSJitGetterOp)%s" % name)
-            methodPure = self.member.getExtendedAttribute("Pure")
 
             # Methods are infallible if they are infallible, have no arguments
             # to unwrap, and have a return type that's infallible to wrap up for
             # return.
             sigs = self.member.signatures()
             if len(sigs) != 1:
                 # Don't handle overloading.  If there's more than one signature,
                 # one of them must take arguments.
                 methodInfal = False
                 args = None
                 movable = False
             else:
                 sig = sigs[0]
-                # For pure methods, it's OK to set movable to our notion of
-                # infallible on the C++ side, without considering argument
-                # conversions, since argument conversions that can reliably
-                # throw would be effectful anyway and the jit doesn't move
-                # effectful things.
+                # For methods that affect nothing, it's OK to set movable to our
+                # notion of infallible on the C++ side, without considering
+                # argument conversions, since argument conversions that can
+                # reliably throw would be effectful anyway and the jit doesn't
+                # move effectful things.
                 hasInfallibleImpl = "infallible" in self.descriptor.getExtendedAttributes(self.member)
-                movable = methodPure and hasInfallibleImpl
+                movable = self.mayBeMovable() and hasInfallibleImpl
                 # XXXbz can we move the smarts about fallibility due to arg
                 # conversions into the JIT, using our new args stuff?
                 if (len(sig[1]) != 0 or
                     not infallibleForMember(self.member, sig[0], self.descriptor)):
                     # We have arguments or our return-value boxing can fail
                     methodInfal = False
                 else:
                     methodInfal = hasInfallibleImpl
-                # For now, only bother to output args if we're pure
-                if methodPure:
+                # For now, only bother to output args if we're side-effect-free.
+                if self.member.affects == "Nothing":
                     args = sig[1]
                 else:
                     args = None
 
-            if args is not None:
-                aliasSet = "AliasDOMSets"
-            else:
-                aliasSet = "AliasEverything"
+            aliasSet = self.aliasSet()
             result = self.defineJitInfo(methodinfo, method, "Method",
                                         methodInfal, movable, aliasSet,
                                         False, False, "0",
                                         [s[0] for s in sigs], args)
             return result
         raise TypeError("Illegal member type to CGPropertyJITInfo")
 
+    def mayBeMovable(self):
+        """
+        Returns whether this attribute or method may be movable, just
+        based on Affects/DependsOn annotations.
+        """
+        affects = self.member.affects
+        dependsOn = self.member.dependsOn
+        assert affects in IDLInterfaceMember.AffectsValues
+        assert dependsOn in IDLInterfaceMember.DependsOnValues
+        return affects == "Nothing" and dependsOn != "Everything"
+
+    def aliasSet(self):
+        """Returns the alias set to store in the jitinfo.  This may not be the
+        effective alias set the JIT uses, depending on whether we have enough
+        information about our args to allow the JIT to prove that effectful
+        argument conversions won't happen.
+
+        """
+        dependsOn = self.member.dependsOn
+        assert dependsOn in IDLInterfaceMember.DependsOnValues
+
+        if dependsOn == "Nothing":
+            assert self.member.affects == "Nothing"
+            return "AliasNone"
+
+        if dependsOn == "DOMState":
+            assert self.member.affects == "Nothing"
+            return "AliasDOMSets"
+
+        return "AliasEverything"
+
     @staticmethod
     def getJSReturnTypeTag(t):
         if t.nullable():
             # Sometimes it might return null, sometimes not
             return "JSVAL_TYPE_UNKNOWN"
         if t.isVoid():
             # No return, every time
             return "JSVAL_TYPE_UNDEFINED"
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -3038,16 +3038,19 @@ class IDLInterfaceMember(IDLObjectWithId
         'Method'
     )
 
     Special = enum(
         'Static',
         'Stringifier'
     )
 
+    AffectsValues = ("Nothing", "Everything")
+    DependsOnValues = ("Nothing", "DOMState", "Everything")
+
     def __init__(self, location, identifier, tag):
         IDLObjectWithIdentifier.__init__(self, location, None, identifier)
         self.tag = tag
         self._extendedAttrDict = {}
         # _exposureGlobalNames are the global names listed in our [Exposed]
         # extended attribute.  exposureSet is the exposure set as defined in the
         # Web IDL spec: it contains interface names.
         self._exposureGlobalNames = set()
@@ -3092,16 +3095,32 @@ class IDLInterfaceMember(IDLObjectWithId
 
         if (self.getExtendedAttribute("CheckPermissions") and
             self.exposureSet != set([self._scope.primaryGlobalName])):
             raise WebIDLError("[CheckPermissions] used on an interface member "
                               "that is not %s-only" %
                               self._scope.primaryGlobalName,
                               [self.location])
 
+    def _setDependsOn(self, dependsOn):
+        if self.dependsOn != "Everything":
+            raise WebIDLError("Trying to specify multiple different "
+                              "Pure, or Constant extended attributes for "
+                              "attribute", [self.location])
+        assert dependsOn in IDLInterfaceMember.DependsOnValues:
+        self.dependsOn = dependsOn
+
+    def _setAffects(self, affects):
+        if self.affects != "Everything":
+            raise WebIDLError("Trying to specify multiple different "
+                              "Pure, or Constant extended attributes for "
+                              "attribute", [self.location])
+        assert affects in IDLInterfaceMember.AffectsValues:
+        self.affects = affects
+
 class IDLConst(IDLInterfaceMember):
     def __init__(self, location, identifier, type, value):
         IDLInterfaceMember.__init__(self, location, identifier,
                                     IDLInterfaceMember.Tags.Const)
 
         assert isinstance(type, IDLType)
         if type.isDictionary():
             raise WebIDLError("A constant cannot be of a dictionary type",
@@ -3170,16 +3189,18 @@ class IDLAttribute(IDLInterfaceMember):
         self.inherit = inherit
         self.static = static
         self.lenientThis = False
         self._unforgeable = False
         self.stringifier = stringifier
         self.enforceRange = False
         self.clamp = False
         self.slotIndex = None
+        self.dependsOn = "Everything"
+        self.affects = "Everything"
 
         if static and identifier.name == "prototype":
             raise WebIDLError("The identifier of a static attribute must not be 'prototype'",
                               [location])
 
         if readonly and inherit:
             raise WebIDLError("An attribute cannot be both 'readonly' and 'inherit'",
                               [self.location])
@@ -3238,20 +3259,19 @@ class IDLAttribute(IDLInterfaceMember):
             raise WebIDLError("An attribute with [SameObject] must have an "
                               "interface type as its type", [self.location])
 
     def validate(self):
         IDLInterfaceMember.validate(self)
 
         if ((self.getExtendedAttribute("Cached") or
              self.getExtendedAttribute("StoreInSlot")) and
-            not self.getExtendedAttribute("Constant") and
-            not self.getExtendedAttribute("Pure")):
+            not self.affects == "Nothing"):
             raise WebIDLError("Cached attributes and attributes stored in "
-                              "slots must be constant or pure, since the "
+                              "slots must be Constant or Pure, since the "
                               "getter won't always be called.",
                               [self.location])
         if self.getExtendedAttribute("Frozen"):
             if (not self.type.isSequence() and not self.type.isDictionary() and
                 not self.type.isMozMap()):
                 raise WebIDLError("[Frozen] is only allowed on "
                                   "sequence-valued, dictionary-valued, and "
                                   "MozMap-valued attributes",
@@ -3367,24 +3387,33 @@ class IDLAttribute(IDLInterfaceMember):
                                   "attributes" % identifier,
                                   [attr.location, self.location])
             if self.getExtendedAttribute("LenientThis"):
                 raise WebIDLError("[LenientThis] is not allowed in combination "
                                   "with [%s]" % identifier,
                                   [attr.location, self.location])
         elif identifier == "Exposed":
             convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
+        elif identifier == "Pure":
+            if not attr.noArguments():
+                raise WebIDLError("[Pure] must take no arguments",
+                                  [attr.location])
+            self._setDependsOn("DOMState")
+            self._setAffects("Nothing")
+        elif identifier == "Constant" or identifier == "SameObject":
+            if not attr.noArguments():
+                raise WebIDLError("[%s] must take no arguments" % identifier,
+                                  [attr.location])
+            self._setDependsOn("Nothing")
+            self._setAffects("Nothing")
         elif (identifier == "Pref" or
               identifier == "SetterThrows" or
-              identifier == "Pure" or
               identifier == "Throws" or
               identifier == "GetterThrows" or
               identifier == "ChromeOnly" or
-              identifier == "SameObject" or
-              identifier == "Constant" or
               identifier == "Func" or
               identifier == "Frozen" or
               identifier == "AvailableIn" or
               identifier == "NewObject" or
               identifier == "UnsafeInPrerendering" or
               identifier == "CheckPermissions" or
               identifier == "BinaryName"):
             # Known attributes that we don't need to do anything with here
@@ -3658,16 +3687,18 @@ class IDLMethod(IDLInterfaceMember, IDLS
         assert isinstance(legacycaller, bool)
         self._legacycaller = legacycaller
         assert isinstance(stringifier, bool)
         self._stringifier = stringifier
         assert isinstance(jsonifier, bool)
         self._jsonifier = jsonifier
         self._specialType = specialType
         self._unforgeable = False
+        self.dependsOn = "Everything"
+        self.affects = "Everything"
 
         if static and identifier.name == "prototype":
             raise WebIDLError("The identifier of a static operation must not be 'prototype'",
                               [location])
 
         self.assertSignatureConstraints()
 
     def __str__(self):
@@ -3969,23 +4000,28 @@ class IDLMethod(IDLInterfaceMember, IDLS
                 raise WebIDLError("[LenientFloat] used on a non-void method",
                                   [attr.location, self.location])
             if not any(arg.type.includesRestrictedFloat() for arg in sig[1]):
                 raise WebIDLError("[LenientFloat] used on an operation with no "
                                   "restricted float type arguments",
                                   [attr.location, self.location])
         elif identifier == "Exposed":
             convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
-        elif (identifier == "Pure" or
-              identifier == "CrossOriginCallable" or
+        elif (identifier == "CrossOriginCallable" or
               identifier == "WebGLHandlesContextLoss"):
             # Known no-argument attributes.
             if not attr.noArguments():
                 raise WebIDLError("[%s] must take no arguments" % identifier,
                                   [attr.location])
+        elif identifier == "Pure":
+            if not attr.noArguments():
+                raise WebIDLError("[Pure] must take no arguments",
+                                  [attr.location])
+            self._setDependsOn("DOMState")
+            self._setAffects("Nothing")
         elif (identifier == "Throws" or
               identifier == "NewObject" or
               identifier == "ChromeOnly" or
               identifier == "UnsafeInPrerendering" or
               identifier == "Pref" or
               identifier == "Func" or
               identifier == "AvailableIn" or
               identifier == "CheckPermissions" or