misc: Replaced master/slave terminology
[gem5.git] / src / python / m5 / SimObject.py
index 7f19c0776465a424163b3cb430dd2cb43587be31..9c9a9ed0fe2b9a18a59648076e0b9b82248c018f 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2017-2018 ARM Limited
+# Copyright (c) 2017-2020 ARM Limited
 # All rights reserved.
 #
 # The license below extends only to copyright in the software and shall
 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-# Authors: Steve Reinhardt
-#          Nathan Binkert
-#          Andreas Hansson
-#          Andreas Sandberg
 
 from __future__ import print_function
 from __future__ import absolute_import
+from six import add_metaclass
 import six
 if six.PY3:
     long = int
@@ -172,10 +168,6 @@ def createCxxConfigDirectoryEntryFile(code, name, simobj, is_header):
         code('#include "base/str.hh"')
         code('#include "cxx_config/${name}.hh"')
 
-        if simobj._ports:
-            code('#include "mem/mem_object.hh"')
-            code('#include "mem/port.hh"')
-
         code()
         code('${member_prefix}DirectoryEntry::DirectoryEntry()');
         code('{')
@@ -194,11 +186,11 @@ def createCxxConfigDirectoryEntryFile(code, name, simobj, is_header):
 
         for port in simobj._ports.values():
             is_vector = isinstance(port, m5.params.VectorPort)
-            is_master = port.role == 'MASTER'
+            is_requestor = port.role == 'GEM5 REQUESTOR'
 
             code('ports["%s"] = new PortDesc("%s", %s, %s);' %
                 (port.name, port.name, cxx_bool(is_vector),
-                cxx_bool(is_master)))
+                cxx_bool(is_requestor)))
 
         code.dedent()
         code('}')
@@ -415,6 +407,7 @@ class MetaSimObject(type):
         'cxx_extra_bases' : list,
         'cxx_exports' : list,
         'cxx_param_exports' : list,
+        'cxx_template_params' : list,
     }
     # Attributes that can be set any time
     keywords = { 'check' : FunctionType }
@@ -454,6 +447,8 @@ class MetaSimObject(type):
             value_dict['cxx_exports'] += cxx_exports
         if 'cxx_param_exports' not in value_dict:
             value_dict['cxx_param_exports'] = []
+        if 'cxx_template_params' not in value_dict:
+            value_dict['cxx_template_params'] = []
         cls_dict['_value_dict'] = value_dict
         cls = super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
         if 'type' in value_dict:
@@ -472,6 +467,12 @@ class MetaSimObject(type):
         cls._params = multidict() # param descriptions
         cls._ports = multidict()  # port descriptions
 
+        # Parameter names that are deprecated. Dict[str, DeprecatedParam]
+        # The key is the "old_name" so that when the old_name is used in
+        # python config files, we will use the DeprecatedParam object to
+        # translate to the new type.
+        cls._deprecated_params = multidict()
+
         # class or instance attributes
         cls._values = multidict()   # param values
         cls._hr_values = multidict() # human readable param values
@@ -500,6 +501,7 @@ class MetaSimObject(type):
             cls._base = base
             cls._params.parent = base._params
             cls._ports.parent = base._ports
+            cls._deprecated_params.parent = base._deprecated_params
             cls._values.parent = base._values
             cls._hr_values.parent = base._hr_values
             cls._children.parent = base._children
@@ -537,6 +539,15 @@ class MetaSimObject(type):
             elif isinstance(val, Port):
                 cls._new_port(key, val)
 
+            # Deprecated variable names
+            elif isinstance(val, DeprecatedParam):
+                new_name, new_val = cls._get_param_by_value(val.newParam)
+                # Note: We don't know the (string) name of this variable until
+                # here, so now we can finish setting up the dep_param.
+                val.oldName = key
+                val.newName = new_name
+                cls._deprecated_params[key] = val
+
             # init-time-only keywords
             elif key in cls.init_keywords:
                 cls._set_keyword(key, val, cls.init_keywords[key])
@@ -609,6 +620,18 @@ class MetaSimObject(type):
             cls._port_refs[attr] = ref
         return ref
 
+    def _get_param_by_value(cls, value):
+        """Given an object, value, return the name and the value from the
+        internal list of parameter values. If this value can't be found, raise
+        a runtime error. This will search both the current object and its
+        parents.
+        """
+        for k,v in cls._value_dict.items():
+            if v == value:
+                return k,v
+        raise RuntimeError("Cannot find parameter {} in parameter list"
+                           .format(value))
+
     # Set attribute (called on foo.attr = value when foo is an
     # instance of class cls).
     def __setattr__(cls, attr, value):
@@ -656,18 +679,27 @@ class MetaSimObject(type):
         if attr == 'cxx_namespaces':
             return cls.cxx_class_path[:-1]
 
+        if attr == 'pybind_class':
+            return  '_COLONS_'.join(cls.cxx_class_path)
+
         if attr in cls._values:
             return cls._values[attr]
 
         if attr in cls._children:
             return cls._children[attr]
 
-        raise AttributeError(
-              "object '%s' has no attribute '%s'" % (cls.__name__, attr))
+        try:
+            return getattr(cls.getCCClass(), attr)
+        except AttributeError:
+            raise AttributeError(
+                "object '%s' has no attribute '%s'" % (cls.__name__, attr))
 
     def __str__(cls):
         return cls.__name__
 
+    def getCCClass(cls):
+        return getattr(m5.internal.params, cls.pybind_class)
+
     # See ParamValue.cxx_predecls for description.
     def cxx_predecls(cls, code):
         code('#include "params/$cls.hh"')
@@ -676,16 +708,13 @@ class MetaSimObject(type):
         code('#include "${{cls.cxx_header}}"')
 
     def pybind_decl(cls, code):
-        class_path = cls.cxx_class.split('::')
-        namespaces, classname = class_path[:-1], class_path[-1]
-        py_class_name = '_COLONS_'.join(class_path) if namespaces else \
-                        classname;
+        py_class_name = cls.pybind_class
 
         # The 'local' attribute restricts us to the params declared in
         # the object itself, not including inherited params (which
         # will also be inherited from the base class's param struct
         # here). Sort the params based on their key
-        params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
+        params = list(map(lambda k_v: k_v[1], sorted(cls._params.local.items())))
         ports = cls._ports.local
 
         code('''#include "pybind11/pybind11.h"
@@ -773,6 +802,7 @@ module_init(py::module &m_internal)
         code('static EmbeddedPyBind embed_obj("${0}", module_init, "${1}");',
              cls, cls._base.type if cls._base else "")
 
+    _warned_about_nested_templates = False
 
     # Generate the C++ declaration (.hh file) for this SimObject's
     # param struct.  Called from src/SConscript.
@@ -781,7 +811,7 @@ module_init(py::module &m_internal)
         # the object itself, not including inherited params (which
         # will also be inherited from the base class's param struct
         # here). Sort the params based on their key
-        params = map(lambda k_v: k_v[1], sorted(cls._params.local.items()))
+        params = list(map(lambda k_v: k_v[1], sorted(cls._params.local.items())))
         ports = cls._ports.local
         try:
             ptypes = [p.ptype for p in params]
@@ -790,7 +820,78 @@ module_init(py::module &m_internal)
             print(params)
             raise
 
-        class_path = cls._value_dict['cxx_class'].split('::')
+        class CxxClass(object):
+            def __init__(self, sig, template_params=[]):
+                # Split the signature into its constituent parts. This could
+                # potentially be done with regular expressions, but
+                # it's simple enough to pick appart a class signature
+                # manually.
+                parts = sig.split('<', 1)
+                base = parts[0]
+                t_args = []
+                if len(parts) > 1:
+                    # The signature had template arguments.
+                    text = parts[1].rstrip(' \t\n>')
+                    arg = ''
+                    # Keep track of nesting to avoid splitting on ","s embedded
+                    # in the arguments themselves.
+                    depth = 0
+                    for c in text:
+                        if c == '<':
+                            depth = depth + 1
+                            if depth > 0 and not \
+                                    self._warned_about_nested_templates:
+                                self._warned_about_nested_templates = True
+                                print('Nested template argument in cxx_class.'
+                                      ' This feature is largely untested and '
+                                      ' may not work.')
+                        elif c == '>':
+                            depth = depth - 1
+                        elif c == ',' and depth == 0:
+                            t_args.append(arg.strip())
+                            arg = ''
+                        else:
+                            arg = arg + c
+                    if arg:
+                        t_args.append(arg.strip())
+                # Split the non-template part on :: boundaries.
+                class_path = base.split('::')
+
+                # The namespaces are everything except the last part of the
+                # class path.
+                self.namespaces = class_path[:-1]
+                # And the class name is the last part.
+                self.name = class_path[-1]
+
+                self.template_params = template_params
+                self.template_arguments = []
+                # Iterate through the template arguments and their values. This
+                # will likely break if parameter packs are used.
+                for arg, param in zip(t_args, template_params):
+                    type_keys = ('class', 'typename')
+                    # If a parameter is a type, parse it recursively. Otherwise
+                    # assume it's a constant, and store it verbatim.
+                    if any(param.strip().startswith(kw) for kw in type_keys):
+                        self.template_arguments.append(CxxClass(arg))
+                    else:
+                        self.template_arguments.append(arg)
+
+            def declare(self, code):
+                # First declare any template argument types.
+                for arg in self.template_arguments:
+                    if isinstance(arg, CxxClass):
+                        arg.declare(code)
+                # Re-open the target namespace.
+                for ns in self.namespaces:
+                    code('namespace $ns {')
+                # If this is a class template...
+                if self.template_params:
+                    code('template <${{", ".join(self.template_params)}}>')
+                # The actual class declaration.
+                code('class ${{self.name}};')
+                # Close the target namespaces.
+                for ns in reversed(self.namespaces):
+                    code('} // namespace $ns')
 
         code('''\
 #ifndef __PARAMS__${cls}__
@@ -806,14 +907,12 @@ module_init(py::module &m_internal)
         if cls == SimObject:
             code('''#include <string>''')
 
+        cxx_class = CxxClass(cls._value_dict['cxx_class'],
+                             cls._value_dict['cxx_template_params'])
+
         # A forward class declaration is sufficient since we are just
         # declaring a pointer.
-        for ns in class_path[:-1]:
-            code('namespace $ns {')
-        code('class $0;', class_path[-1])
-        for ns in reversed(class_path[:-1]):
-            code('} // namespace $ns')
-        code()
+        cxx_class.declare(code)
 
         for param in params:
             param.cxx_predecls(code)
@@ -882,6 +981,8 @@ def cxxMethod(*args, **kwargs):
         name = func.__name__
         override = kwargs.get("override", False)
         cxx_name = kwargs.get("cxx_name", name)
+        return_value_policy = kwargs.get("return_value_policy", None)
+        static = kwargs.get("static", False)
 
         args, varargs, keywords, defaults = inspect.getargspec(func)
         if varargs or keywords:
@@ -898,7 +999,7 @@ def cxxMethod(*args, **kwargs):
 
         @wraps(func)
         def cxx_call(self, *args, **kwargs):
-            ccobj = self.getCCObject()
+            ccobj = self.getCCClass() if static else self.getCCObject()
             return getattr(ccobj, name)(*args, **kwargs)
 
         @wraps(func)
@@ -906,7 +1007,9 @@ def cxxMethod(*args, **kwargs):
             return func(self, *args, **kwargs)
 
         f = py_call if override else cxx_call
-        f.__pybind = PyBindMethod(name, cxx_name=cxx_name, args=args)
+        f.__pybind = PyBindMethod(name, cxx_name=cxx_name, args=args,
+                                  return_value_policy=return_value_policy,
+                                  static=static)
 
         return f
 
@@ -996,18 +1099,21 @@ class SimObjectCliWrapper(object):
                 out.extend(sim_object[i] for i in _range)
         return SimObjectCliWrapper(out)
 
+    def __iter__(self):
+        return iter(self._sim_objects)
+
 # The SimObject class is the root of the special hierarchy.  Most of
 # the code in this class deals with the configuration hierarchy itself
 # (parent/child node relationships).
+@add_metaclass(MetaSimObject)
 class SimObject(object):
     # Specify metaclass.  Any class inheriting from SimObject will
     # get this metaclass.
-    __metaclass__ = MetaSimObject
     type = 'SimObject'
     abstract = True
 
     cxx_header = "sim/sim_object.hh"
-    cxx_extra_bases = [ "Drainable", "Serializable" ]
+    cxx_extra_bases = [ "Drainable", "Serializable", "Stats::Group" ]
     eventq_index = Param.UInt32(Parent.eventq_index, "Event Queue Index")
 
     cxx_exports = [
@@ -1015,8 +1121,6 @@ class SimObject(object):
         PyBindMethod("initState"),
         PyBindMethod("memInvalidate"),
         PyBindMethod("memWriteback"),
-        PyBindMethod("regStats"),
-        PyBindMethod("resetStats"),
         PyBindMethod("regProbePoints"),
         PyBindMethod("regProbeListeners"),
         PyBindMethod("startup"),
@@ -1179,6 +1283,11 @@ class SimObject(object):
         return ref
 
     def __getattr__(self, attr):
+        if attr in self._deprecated_params:
+            dep_param = self._deprecated_params[attr]
+            dep_param.printWarning(self._name, self.__class__.__name__)
+            return getattr(self, self._deprecated_params[attr].newName)
+
         if attr in self._ports:
             return self._get_port_ref(attr)
 
@@ -1211,6 +1320,11 @@ class SimObject(object):
             object.__setattr__(self, attr, value)
             return
 
+        if attr in self._deprecated_params:
+            dep_param = self._deprecated_params[attr]
+            dep_param.printWarning(self._name, self.__class__.__name__)
+            return setattr(self, self._deprecated_params[attr].newName, value)
+
         if attr in self._ports:
             # set up port connection
             self._get_port_ref(attr).connect(value)
@@ -1336,6 +1450,13 @@ class SimObject(object):
             return self._name
         return ppath + "." + self._name
 
+    def path_list(self):
+        if self._parent:
+            return self._parent.path_list() + [ self._name, ]
+        else:
+            # Don't include the root node
+            return []
+
     def __str__(self):
         return self.path()
 
@@ -1567,6 +1688,10 @@ class SimObject(object):
     def getValue(self):
         return self.getCCObject()
 
+    @cxxMethod(return_value_policy="reference")
+    def getPort(self, if_name, idx):
+        pass
+
     # Create C++ port connections corresponding to the connections in
     # _port_refs
     def connectPorts(self):
@@ -1611,6 +1736,18 @@ class SimObject(object):
         for param in params:
             exec(param, d)
 
+    def get_simobj(self, simobj_path):
+        """
+        Get all sim objects that match a given string.
+
+        The format is the same as that supported by SimObjectCliWrapper.
+
+        :param simobj_path: Current state to be in.
+        :type simobj_path: str
+        """
+        d = self._apply_config_get_dict()
+        return eval(simobj_path, d)
+
 # Function to provide to C++ so it can look up instances based on paths
 def resolveSimObject(name):
     obj = instanceDict[name]