X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=src%2Fpython%2Fm5%2FSimObject.py;h=9c9a9ed0fe2b9a18a59648076e0b9b82248c018f;hb=392c1ce;hp=26463f6446dbd22716dbfd54e44d09a14f928af8;hpb=d5f5fbb855e8de8c64444dd02f0ed7c27866578c;p=gem5.git diff --git a/src/python/m5/SimObject.py b/src/python/m5/SimObject.py index 26463f644..9c9a9ed0f 100644 --- a/src/python/m5/SimObject.py +++ b/src/python/m5/SimObject.py @@ -1,4 +1,4 @@ -# Copyright (c) 2012 ARM Limited +# Copyright (c) 2017-2020 ARM Limited # All rights reserved. # # The license below extends only to copyright in the software and shall @@ -37,16 +37,25 @@ # 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 + +from __future__ import print_function +from __future__ import absolute_import +from six import add_metaclass +import six +if six.PY3: + long = int import sys from types import FunctionType, MethodType, ModuleType +from functools import wraps +import inspect import m5 from m5.util import * +from m5.util.pybind import * +# Use the pyfdt and not the helper class, because the fdthelper +# relies on the SimObject definition +from m5.ext.pyfdt import pyfdt # Have to import params up top since Param is referenced on initial # load (when SimObject class references Param to create a class @@ -159,10 +168,6 @@ def createCxxConfigDirectoryEntryFile(code, name, simobj, is_header): code('#include "base/str.hh"') code('#include "cxx_config/${name}.hh"') - if simobj._ports.values() != []: - code('#include "mem/mem_object.hh"') - code('#include "mem/port.hh"') - code() code('${member_prefix}DirectoryEntry::DirectoryEntry()'); code('{') @@ -181,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('}') @@ -268,7 +273,6 @@ def createCxxConfigDirectoryEntryFile(code, name, simobj, is_header): code('{') code.indent() code('this->name = name_;') - code('this->pyobj = NULL;') code.dedent() code('}') @@ -393,12 +397,18 @@ def createCxxConfigDirectoryEntryFile(code, name, simobj, is_header): # class are instantiated, and provides inherited instance behavior). class MetaSimObject(type): # Attributes that can be set only at initialization time - init_keywords = { 'abstract' : bool, - 'cxx_class' : str, - 'cxx_type' : str, - 'cxx_header' : str, - 'type' : str, - 'cxx_bases' : list } + init_keywords = { + 'abstract' : bool, + 'cxx_class' : str, + 'cxx_type' : str, + 'cxx_header' : str, + 'type' : str, + 'cxx_base' : (str, type(None)), + '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 } @@ -415,7 +425,13 @@ class MetaSimObject(type): # filtered in __init__. cls_dict = {} value_dict = {} + cxx_exports = [] for key,val in dict.items(): + try: + cxx_exports.append(getattr(val, "__pybind")) + except AttributeError: + pass + if public_value(key, val): cls_dict[key] = val else: @@ -423,8 +439,16 @@ class MetaSimObject(type): value_dict[key] = val if 'abstract' not in value_dict: value_dict['abstract'] = False - if 'cxx_bases' not in value_dict: - value_dict['cxx_bases'] = [] + if 'cxx_extra_bases' not in value_dict: + value_dict['cxx_extra_bases'] = [] + if 'cxx_exports' not in value_dict: + value_dict['cxx_exports'] = cxx_exports + else: + 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: @@ -443,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 @@ -458,7 +488,8 @@ class MetaSimObject(type): if isinstance(c, MetaSimObject): bTotal += 1 if bTotal > 1: - raise TypeError, "SimObjects do not support multiple inheritance" + raise TypeError( + "SimObjects do not support multiple inheritance") base = bases[0] @@ -470,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 @@ -491,20 +523,6 @@ class MetaSimObject(type): noCxxHeader = True warn("No header file specified for SimObject: %s", name) - # Export methods are automatically inherited via C++, so we - # don't want the method declarations to get inherited on the - # python side (and thus end up getting repeated in the wrapped - # versions of derived classes). The code below basicallly - # suppresses inheritance by substituting in the base (null) - # versions of these methods unless a different version is - # explicitly supplied. - for method_name in ('export_methods', 'export_method_cxx_predecls', - 'export_method_swig_predecls'): - if method_name not in cls.__dict__: - base_method = getattr(MetaSimObject, method_name) - m = MethodType(base_method, cls, MetaSimObject) - setattr(cls, method_name, m) - # Now process the _value_dict items. They could be defining # new (or overriding existing) parameters or ports, setting # class keywords (e.g., 'abstract'), or setting parameter @@ -521,8 +539,17 @@ 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 cls.init_keywords.has_key(key): + elif key in cls.init_keywords: cls._set_keyword(key, val, cls.init_keywords[key]) # default: use normal path (ends up in __setattr__) @@ -531,8 +558,8 @@ class MetaSimObject(type): def _set_keyword(cls, keyword, val, kwtype): if not isinstance(val, kwtype): - raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \ - (keyword, type(val), kwtype) + raise TypeError('keyword %s has bad type %s (expecting %s)' % \ + (keyword, type(val), kwtype)) if isinstance(val, FunctionType): val = classmethod(val) type.__setattr__(cls, keyword, val) @@ -550,7 +577,7 @@ class MetaSimObject(type): try: hr_value = value value = param.convert(value) - except Exception, e: + except Exception as e: msg = "%s\nError setting param %s.%s to %s\n" % \ (e, cls.__name__, name, value) e.args = (msg, ) @@ -573,7 +600,8 @@ class MetaSimObject(type): # object is not an orphan and can provide better error # messages. child.set_parent(cls, name) - cls._children[name] = child + if not isNullPointer(child): + cls._children[name] = child def _new_port(cls, name, port): # each port should be uniquely assigned to one variable @@ -592,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): @@ -600,19 +640,19 @@ class MetaSimObject(type): type.__setattr__(cls, attr, value) return - if cls.keywords.has_key(attr): + if attr in cls.keywords: cls._set_keyword(attr, value, cls.keywords[attr]) return - if cls._ports.has_key(attr): + if attr in cls._ports: cls._cls_get_port_ref(attr).connect(value) return if isSimObjectOrSequence(value) and cls._instantiated: - raise RuntimeError, \ + raise RuntimeError( "cannot set SimObject parameter '%s' after\n" \ " class %s has been instantiated or subclassed" \ - % (attr, cls.__name__) + % (attr, cls.__name__)) # check for param param = cls._params.get(attr) @@ -626,8 +666,8 @@ class MetaSimObject(type): return # no valid assignment... raise exception - raise AttributeError, \ - "Class %s has no parameter \'%s\'" % (cls.__name__, attr) + raise AttributeError( + "Class %s has no parameter \'%s\'" % (cls.__name__, attr)) def __getattr__(cls, attr): if attr == 'cxx_class_path': @@ -639,132 +679,130 @@ class MetaSimObject(type): if attr == 'cxx_namespaces': return cls.cxx_class_path[:-1] - if cls._values.has_key(attr): + if attr == 'pybind_class': + return '_COLONS_'.join(cls.cxx_class_path) + + if attr in cls._values: return cls._values[attr] - if cls._children.has_key(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"') - # See ParamValue.swig_predecls for description. - def swig_predecls(cls, code): - code('%import "python/m5/internal/param_$cls.i"') - - # Hook for exporting additional C++ methods to Python via SWIG. - # Default is none, override using @classmethod in class definition. - def export_methods(cls, code): - pass - - # Generate the code needed as a prerequisite for the C++ methods - # exported via export_methods() to be compiled in the _wrap.cc - # file. Typically generates one or more #include statements. If - # any methods are exported, typically at least the C++ header - # declaring the relevant SimObject class must be included. - def export_method_cxx_predecls(cls, code): - pass - - # Generate the code needed as a prerequisite for the C++ methods - # exported via export_methods() to be processed by SWIG. - # Typically generates one or more %include or %import statements. - # If any methods are exported, typically at least the C++ header - # declaring the relevant SimObject class must be included. - def export_method_swig_predecls(cls, code): - pass + def pybind_predecls(cls, code): + code('#include "${{cls.cxx_header}}"') - # Generate the declaration for this object for wrapping with SWIG. - # Generates code that goes into a SWIG .i file. Called from - # src/SConscript. - def swig_decl(cls, code): - class_path = cls.cxx_class.split('::') - classname = class_path[-1] - namespaces = class_path[:-1] + def pybind_decl(cls, code): + 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): v, sorted(cls._params.local.items())) + params = list(map(lambda k_v: k_v[1], sorted(cls._params.local.items()))) ports = cls._ports.local - code('%module(package="m5.internal") param_$cls') - code() - code('%{') - code('#include "sim/sim_object.hh"') - code('#include "params/$cls.hh"') - for param in params: - param.cxx_predecls(code) - code('#include "${{cls.cxx_header}}"') - cls.export_method_cxx_predecls(code) - code('''\ -/** - * This is a workaround for bug in swig. Prior to gcc 4.6.1 the STL - * headers like vector, string, etc. used to automatically pull in - * the cstddef header but starting with gcc 4.6.1 they no longer do. - * This leads to swig generated a file that does not compile so we - * explicitly include cstddef. Additionally, including version 2.0.4, - * swig uses ptrdiff_t without the std:: namespace prefix which is - * required with gcc 4.6.1. We explicitly provide access to it. - */ -#include -using std::ptrdiff_t; -''') - code('%}') - code() + code('''#include "pybind11/pybind11.h" +#include "pybind11/stl.h" - for param in params: - param.swig_predecls(code) - cls.export_method_swig_predecls(code) +#include "params/$cls.hh" +#include "python/pybind11/core.hh" +#include "sim/init.hh" +#include "sim/sim_object.hh" - code() - if cls._base: - code('%import "python/m5/internal/param_${{cls._base}}.i"') - code() +#include "${{cls.cxx_header}}" + +''') - for ns in namespaces: - code('namespace $ns {') + for param in params: + param.pybind_predecls(code) - if namespaces: - code('// avoid name conflicts') - sep_string = '_COLONS_' - flat_name = sep_string.join(class_path) - code('%rename($flat_name) $classname;') + code('''namespace py = pybind11; - code() - code('// stop swig from creating/wrapping default ctor/dtor') - code('%nodefault $classname;') - code('class $classname') +static void +module_init(py::module &m_internal) +{ + py::module m = m_internal.def_submodule("param_${cls}"); +''') + code.indent() if cls._base: - bases = [ cls._base.cxx_class ] + cls.cxx_bases + code('py::class_<${cls}Params, ${{cls._base.type}}Params, ' \ + 'std::unique_ptr<${{cls}}Params, py::nodelete>>(' \ + 'm, "${cls}Params")') else: - bases = cls.cxx_bases - base_first = True - for base in bases: - if base_first: - code(' : public ${{base}}') - base_first = False - else: - code(' , public ${{base}}') + code('py::class_<${cls}Params, ' \ + 'std::unique_ptr<${cls}Params, py::nodelete>>(' \ + 'm, "${cls}Params")') - code('{') - code(' public:') - cls.export_methods(code) - code('};') - - for ns in reversed(namespaces): - code('} // namespace $ns') + code.indent() + if not hasattr(cls, 'abstract') or not cls.abstract: + code('.def(py::init<>())') + code('.def("create", &${cls}Params::create)') + + param_exports = cls.cxx_param_exports + [ + PyBindProperty(k) + for k, v in sorted(cls._params.local.items()) + ] + [ + PyBindProperty("port_%s_connection_count" % port.name) + for port in ports.values() + ] + for exp in param_exports: + exp.export(code, "%sParams" % cls) + + code(';') + code() + code.dedent() + bases = [] + if 'cxx_base' in cls._value_dict: + # If the c++ base class implied by python inheritance was + # overridden, use that value. + if cls.cxx_base: + bases.append(cls.cxx_base) + elif cls._base: + # If not and if there was a SimObject base, use its c++ class + # as this class' base. + bases.append(cls._base.cxx_class) + # Add in any extra bases that were requested. + bases.extend(cls.cxx_extra_bases) + + if bases: + base_str = ", ".join(bases) + code('py::class_<${{cls.cxx_class}}, ${base_str}, ' \ + 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \ + 'm, "${py_class_name}")') + else: + code('py::class_<${{cls.cxx_class}}, ' \ + 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \ + 'm, "${py_class_name}")') + code.indent() + for exp in cls.cxx_exports: + exp.export(code, cls.cxx_class) + code(';') + code.dedent() code() - code('%include "params/$cls.hh"') + code.dedent() + code('}') + code() + 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. @@ -773,16 +811,87 @@ using std::ptrdiff_t; # 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): v, 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] except: - print cls, p, p.ptype_str - print params + print(cls, p, p.ptype_str) + 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}__ @@ -790,30 +899,24 @@ using std::ptrdiff_t; ''') - # 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() # The base SimObject has a couple of params that get # automatically set from Python without being declared through # the normal Param mechanism; we slip them in here (needed # predecls now, actual declarations below) if cls == SimObject: - code(''' -#ifndef PY_VERSION -struct PyObject; -#endif + code('''#include ''') + + 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. + cxx_class.declare(code) -#include -''') for param in params: param.cxx_predecls(code) - for port in ports.itervalues(): + for port in ports.values(): port.cxx_predecls(code) code() @@ -842,11 +945,11 @@ struct PyObject; virtual ~SimObjectParams() {} std::string name; - PyObject *pyobj; ''') + for param in params: param.cxx_decl(code) - for port in ports.itervalues(): + for port in ports.values(): port.cxx_decl(code) code.dedent() @@ -871,6 +974,52 @@ struct PyObject; def isSimObjectOrVector(value): return False +def cxxMethod(*args, **kwargs): + """Decorator to export C++ functions to Python""" + + def decorate(func): + 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: + raise ValueError("Wrapped methods must not contain variable " \ + "arguments") + + # Create tuples of (argument, default) + if defaults: + args = args[:-len(defaults)] + \ + list(zip(args[-len(defaults):], defaults)) + # Don't include self in the argument list to PyBind + args = args[1:] + + + @wraps(func) + def cxx_call(self, *args, **kwargs): + ccobj = self.getCCClass() if static else self.getCCObject() + return getattr(ccobj, name)(*args, **kwargs) + + @wraps(func) + def py_call(self, *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, + return_value_policy=return_value_policy, + static=static) + + return f + + if len(args) == 0: + return decorate + elif len(args) == 1 and len(kwargs) == 0: + return decorate(*args) + else: + raise TypeError("One argument and no kwargs, or only kwargs expected") + # This class holds information about each simobject parameter # that should be displayed on the command line for use in the # configuration system. @@ -893,43 +1042,98 @@ class ParamInfo(object): if not "created" in self.__dict__: self.__dict__[name] = value +class SimObjectCliWrapperException(Exception): + def __init__(self, message): + super(Exception, self).__init__(message) + +class SimObjectCliWrapper(object): + """ + Wrapper class to restrict operations that may be done + from the command line on SimObjects. + + Only parameters may be set, and only children may be accessed. + + Slicing allows for multiple simultaneous assignment of items in + one statement. + """ + + def __init__(self, sim_objects): + self.__dict__['_sim_objects'] = list(sim_objects) + + def __getattr__(self, key): + return SimObjectCliWrapper(sim_object._children[key] + for sim_object in self._sim_objects) + + def __setattr__(self, key, val): + for sim_object in self._sim_objects: + if key in sim_object._params: + if sim_object._params[key].isCmdLineSettable(): + setattr(sim_object, key, val) + else: + raise SimObjectCliWrapperException( + 'tried to set or unsettable' \ + 'object parameter: ' + key) + else: + raise SimObjectCliWrapperException( + 'tried to set or access non-existent' \ + 'object parameter: ' + key) + + def __getitem__(self, idx): + """ + Extends the list() semantics to also allow tuples, + for example object[1, 3] selects items 1 and 3. + """ + out = [] + if isinstance(idx, tuple): + for t in idx: + out.extend(self[t]._sim_objects) + else: + if isinstance(idx, int): + _range = range(idx, idx + 1) + elif not isinstance(idx, slice): + raise SimObjectCliWrapperException( \ + 'invalid index type: ' + repr(idx)) + for sim_object in self._sim_objects: + if isinstance(idx, slice): + _range = range(*idx.indices(len(sim_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_bases = [ "Drainable", "Serializable" ] + cxx_extra_bases = [ "Drainable", "Serializable", "Stats::Group" ] eventq_index = Param.UInt32(Parent.eventq_index, "Event Queue Index") - @classmethod - def export_method_swig_predecls(cls, code): - code(''' -%include - -%import "python/swig/drain.i" -%import "python/swig/serialize.i" -''') - - @classmethod - def export_methods(cls, code): - code(''' - void init(); - void loadState(CheckpointIn &cp); - void initState(); - void memInvalidate(); - void memWriteback(); - void regStats(); - void resetStats(); - void regProbePoints(); - void regProbeListeners(); - void startup(); -''') + cxx_exports = [ + PyBindMethod("init"), + PyBindMethod("initState"), + PyBindMethod("memInvalidate"), + PyBindMethod("memWriteback"), + PyBindMethod("regProbePoints"), + PyBindMethod("regProbeListeners"), + PyBindMethod("startup"), + ] + + cxx_param_exports = [ + PyBindProperty("name"), + ] + + @cxxMethod + def loadState(self, cp): + """Load SimObject state from a checkpoint""" + pass # Returns a dict of all the option strings that can be # generated as command line options for this simobject instance @@ -938,7 +1142,7 @@ class SimObject(object): def enumerateParams(self, flags_dict = {}, cmd_line_str = "", access_str = ""): if hasattr(self, "_paramEnumed"): - print "Cycle detected enumerating params" + print("Cycle detected enumerating params") else: self._paramEnumed = True # Scan the children first to pick up all the objects in this SimObj @@ -971,7 +1175,8 @@ class SimObject(object): if keys in self._hr_values\ and keys in self._values\ - and not isinstance(self._values[keys], m5.proxy.BaseProxy): + and not isinstance(self._values[keys], + m5.proxy.BaseProxy): cmd_str = cmd_line_str + keys acc_str = access_str + keys flags_dict[cmd_str] = ParamInfo(ptype, @@ -1022,7 +1227,7 @@ class SimObject(object): # Do children before parameter values so that children that # are also param values get cloned properly. self._children = {} - for key,val in ancestor._children.iteritems(): + for key,val in ancestor._children.items(): self.add_child(key, val(_memo=memo_dict)) # Inherit parameter values from class using multidict so @@ -1031,7 +1236,7 @@ class SimObject(object): self._values = multidict(ancestor._values) self._hr_values = multidict(ancestor._hr_values) # clone SimObject-valued parameters - for key,val in ancestor._values.iteritems(): + for key,val in ancestor._values.items(): val = tryAsSimObjectOrVector(val) if val is not None: self._values[key] = val(_memo=memo_dict) @@ -1039,10 +1244,10 @@ class SimObject(object): # clone port references. no need to use a multidict here # since we will be creating new references for all ports. self._port_refs = {} - for key,val in ancestor._port_refs.iteritems(): + for key,val in ancestor._port_refs.items(): self._port_refs[key] = val.clone(self, memo_dict) # apply attribute assignments from keyword args, if any - for key,val in kwargs.iteritems(): + for key,val in kwargs.items(): setattr(self, key, val) # "Clone" the current instance by creating another instance of @@ -1056,13 +1261,13 @@ class SimObject(object): # no memo_dict: must be top-level clone operation. # this is only allowed at the root of a hierarchy if self._parent: - raise RuntimeError, "attempt to clone object %s " \ + raise RuntimeError("attempt to clone object %s " \ "not at the root of a tree (parent = %s)" \ - % (self, self._parent) + % (self, self._parent)) # create a new dict and use that. memo_dict = {} kwargs['_memo'] = memo_dict - elif memo_dict.has_key(self): + elif self in memo_dict: # clone already done & memoized return memo_dict[self] return self.__class__(_ancestor = self, **kwargs) @@ -1078,20 +1283,23 @@ class SimObject(object): return ref def __getattr__(self, attr): - if self._ports.has_key(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) - if self._values.has_key(attr): + if attr in self._values: return self._values[attr] - if self._children.has_key(attr): + if attr in self._children: return self._children[attr] # If the attribute exists on the C++ object, transparently # forward the reference there. This is typically used for - # SWIG-wrapped methods such as init(), regStats(), - # resetStats(), startup(), drain(), and - # resume(). + # methods exported to Python (e.g., init(), and startup()) if self._ccObject and hasattr(self._ccObject, attr): return getattr(self._ccObject, attr) @@ -1102,7 +1310,7 @@ class SimObject(object): err_string += "\n (C++ object is not yet constructed," \ " so wrapped C++ methods are unavailable.)" - raise AttributeError, err_string + raise AttributeError(err_string) # Set attribute (called on foo.attr = value when foo is an # instance of class cls). @@ -1112,7 +1320,12 @@ class SimObject(object): object.__setattr__(self, attr, value) return - if self._ports.has_key(attr): + 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) return @@ -1122,7 +1335,7 @@ class SimObject(object): try: hr_value = value value = param.convert(value) - except Exception, e: + except Exception as e: msg = "%s\nError setting param %s.%s to %s\n" % \ (e, self.__class__.__name__, attr, value) e.args = (msg, ) @@ -1146,8 +1359,8 @@ class SimObject(object): return # no valid assignment... raise exception - raise AttributeError, "Class %s has no parameter %s" \ - % (self.__class__.__name__, attr) + raise AttributeError("Class %s has no parameter %s" \ + % (self.__class__.__name__, attr)) # this hack allows tacking a '[0]' onto parameters that may or may @@ -1155,7 +1368,7 @@ class SimObject(object): def __getitem__(self, key): if key == 0: return self - raise IndexError, "Non-zero index '%s' to SimObject" % key + raise IndexError("Non-zero index '%s' to SimObject" % key) # this hack allows us to iterate over a SimObject that may # not be a vector, so we can call a loop over it and get just one @@ -1173,8 +1386,9 @@ class SimObject(object): self._parent = parent self._name = name - # Return parent object of this SimObject, not implemented by SimObjectVector - # because the elements in a SimObjectVector may not share the same parent + # Return parent object of this SimObject, not implemented by + # SimObjectVector because the elements in a SimObjectVector may not share + # the same parent def get_parent(self): return self._parent @@ -1199,14 +1413,15 @@ class SimObject(object): if child.has_parent(): warn("add_child('%s'): child '%s' already has parent", name, child.get_name()) - if self._children.has_key(name): + if name in self._children: # This code path had an undiscovered bug that would make it fail # at runtime. It had been here for a long time and was only # exposed by a buggy script. Changes here will probably not be # exercised without specialized testing. self.clear_child(name) child.set_parent(self, name) - self._children[name] = child + if not isNullPointer(child): + self._children[name] = child # Take SimObject-valued parameters that haven't been explicitly # assigned as children and make them children of the object that @@ -1214,7 +1429,7 @@ class SimObject(object): # that when we instantiate all the parameter objects we're still # inside the configuration hierarchy. def adoptOrphanParams(self): - for key,val in self._values.iteritems(): + for key,val in self._values.items(): if not isSimObjectVector(val) and isSimObjectSequence(val): # need to convert raw SimObject sequences to # SimObjectVector class so we can call has_parent() @@ -1227,11 +1442,21 @@ class SimObject(object): def path(self): if not self._parent: return '' % self.__class__ + elif isinstance(self._parent, MetaSimObject): + return str(self.__class__) + ppath = self._parent.path() if ppath == 'root': 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() @@ -1246,31 +1471,32 @@ class SimObject(object): return self, True found_obj = None - for child in self._children.itervalues(): + for child in self._children.values(): visited = False if hasattr(child, '_visited'): visited = getattr(child, '_visited') if isinstance(child, ptype) and not visited: if found_obj != None and child != found_obj: - raise AttributeError, \ + raise AttributeError( 'parent.any matched more than one: %s %s' % \ - (found_obj.path, child.path) + (found_obj.path, child.path)) found_obj = child # search param space - for pname,pdesc in self._params.iteritems(): + for pname,pdesc in self._params.items(): if issubclass(pdesc.ptype, ptype): match_obj = self._values[pname] if found_obj != None and found_obj != match_obj: - raise AttributeError, \ - 'parent.any matched more than one: %s and %s' % (found_obj.path, match_obj.path) + raise AttributeError( + 'parent.any matched more than one: %s and %s' % \ + (found_obj.path, match_obj.path)) found_obj = match_obj return found_obj, found_obj != None def find_all(self, ptype): all = {} # search children - for child in self._children.itervalues(): + for child in self._children.values(): # a child could be a list, so ensure we visit each item if isinstance(child, list): children = child @@ -1286,7 +1512,7 @@ class SimObject(object): child_all, done = child.find_all(ptype) all.update(dict(zip(child_all, [done] * len(child_all)))) # search param space - for pname,pdesc in self._params.iteritems(): + for pname,pdesc in self._params.items(): if issubclass(pdesc.ptype, ptype): match_obj = self._values[pname] if not isproxy(match_obj) and not isNullPointer(match_obj): @@ -1299,20 +1525,20 @@ class SimObject(object): return self def unproxyParams(self): - for param in self._params.iterkeys(): + for param in self._params.keys(): value = self._values.get(param) if value != None and isproxy(value): try: value = value.unproxy(self) except: - print "Error in unproxying param '%s' of %s" % \ - (param, self.path()) + print("Error in unproxying param '%s' of %s" % + (param, self.path())) raise setattr(self, param, value) # Unproxy ports in sorted order so that 'append' operations on # vector ports are done in a deterministic fashion. - port_names = self._ports.keys() + port_names = list(self._ports.keys()) port_names.sort() for port_name in port_names: port = self._port_refs.get(port_name) @@ -1320,30 +1546,31 @@ class SimObject(object): port.unproxy(self) def print_ini(self, ini_file): - print >>ini_file, '[' + self.path() + ']' # .ini section header + print('[' + self.path() + ']', file=ini_file) # .ini section header instanceDict[self.path()] = self if hasattr(self, 'type'): - print >>ini_file, 'type=%s' % self.type + print('type=%s' % self.type, file=ini_file) if len(self._children.keys()): - print >>ini_file, 'children=%s' % \ - ' '.join(self._children[n].get_name() \ - for n in sorted(self._children.keys())) + print('children=%s' % + ' '.join(self._children[n].get_name() + for n in sorted(self._children.keys())), + file=ini_file) for param in sorted(self._params.keys()): value = self._values.get(param) if value != None: - print >>ini_file, '%s=%s' % (param, - self._values[param].ini_str()) + print('%s=%s' % (param, self._values[param].ini_str()), + file=ini_file) for port_name in sorted(self._ports.keys()): port = self._port_refs.get(port_name, None) if port != None: - print >>ini_file, '%s=%s' % (port_name, port.ini_str()) + print('%s=%s' % (port_name, port.ini_str()), file=ini_file) - print >>ini_file # blank line between objects + print(file=ini_file) # blank line between objects # generate a tree of dictionaries expressing all the parameters in the # instantiated system for use by scripts that want to do power, thermal @@ -1386,10 +1613,9 @@ class SimObject(object): cc_params_struct = getattr(m5.internal.params, '%sParams' % self.type) cc_params = cc_params_struct() - cc_params.pyobj = self cc_params.name = str(self) - param_names = self._params.keys() + param_names = list(self._params.keys()) param_names.sort() for param in param_names: value = self._values.get(param) @@ -1402,12 +1628,18 @@ class SimObject(object): assert isinstance(value, list) vec = getattr(cc_params, param) assert not len(vec) - for v in value: - vec.append(v) + # Some types are exposed as opaque types. They support + # the append operation unlike the automatically + # wrapped types. + if isinstance(vec, list): + setattr(cc_params, param, list(value)) + else: + for v in value: + getattr(cc_params, param).append(v) else: setattr(cc_params, param, value) - port_names = self._ports.keys() + port_names = list(self._ports.keys()) port_names.sort() for port_name in port_names: port = self._port_refs.get(port_name, None) @@ -1427,7 +1659,7 @@ class SimObject(object): if not self._ccObject: # Make sure this object is in the configuration hierarchy if not self._parent and not isRoot(self): - raise RuntimeError, "Attempt to instantiate orphan node" + raise RuntimeError("Attempt to instantiate orphan node") # Cycles in the configuration hierarchy are not supported. This # will catch the resulting recursion and stop. self._ccObject = -1 @@ -1435,8 +1667,8 @@ class SimObject(object): params = self.getCCParams() self._ccObject = params.create() elif self._ccObject == -1: - raise RuntimeError, "%s: Cycle found in configuration hierarchy." \ - % self.path() + raise RuntimeError("%s: Cycle found in configuration hierarchy." \ + % self.path()) return self._ccObject def descendants(self): @@ -1444,7 +1676,7 @@ class SimObject(object): # The order of the dict is implementation dependent, so sort # it based on the key (name) to ensure the order is the same # on all hosts - for (name, child) in sorted(self._children.iteritems()): + for (name, child) in sorted(self._children.items()): for obj in child.descendants(): yield obj @@ -1456,14 +1688,66 @@ 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): # Sort the ports based on their attribute name to ensure the # order is the same on all hosts - for (attr, portRef) in sorted(self._port_refs.iteritems()): + for (attr, portRef) in sorted(self._port_refs.items()): portRef.ccConnect() + # Default function for generating the device structure. + # Can be overloaded by the inheriting class + def generateDeviceTree(self, state): + return # return without yielding anything + yield # make this function a (null) generator + + def recurseDeviceTree(self, state): + for child in self._children.values(): + for item in child: # For looping over SimObjectVectors + for dt in item.generateDeviceTree(state): + yield dt + + # On a separate method otherwise certain buggy Python versions + # would fail with: SyntaxError: unqualified exec is not allowed + # in function 'apply_config' + def _apply_config_get_dict(self): + return { + child_name: SimObjectCliWrapper( + iter(self._children[child_name])) + for child_name in self._children + } + + def apply_config(self, params): + """ + exec a list of Python code strings contained in params. + + The only exposed globals to those strings are the child + SimObjects of this node. + + This function is intended to allow users to modify SimObject + parameters from the command line with Python statements. + """ + d = self._apply_config_get_dict() + 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] @@ -1508,7 +1792,7 @@ def tryAsSimObjectOrVector(value): def coerceSimObjectOrVector(value): value = tryAsSimObjectOrVector(value) if value is None: - raise TypeError, "SimObject or SimObjectVector expected" + raise TypeError("SimObject or SimObjectVector expected") return value baseClasses = allClasses.copy() @@ -1524,4 +1808,9 @@ def clear(): # __all__ defines the list of symbols that get exported when # 'from config import *' is invoked. Try to keep this reasonably # short to avoid polluting other namespaces. -__all__ = [ 'SimObject' ] +__all__ = [ + 'SimObject', + 'cxxMethod', + 'PyBindMethod', + 'PyBindProperty', +]