X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=src%2Fpython%2Fm5%2FSimObject.py;h=dca7d4095d34e008707f969dcd07aca4f767fe8b;hb=d00aa3658498968f7dc2b586347771734af0d24a;hp=3784c716e141938f6bfb9f93b87825cb9408dfa0;hpb=0c1913336af42b6d789fa7738690e64b7784d9df;p=gem5.git diff --git a/src/python/m5/SimObject.py b/src/python/m5/SimObject.py index 3784c716e..dca7d4095 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-2018 ARM Limited # All rights reserved. # # The license below extends only to copyright in the software and shall @@ -41,12 +41,25 @@ # Authors: Steve Reinhardt # Nathan Binkert # Andreas Hansson +# Andreas Sandberg + +from __future__ import print_function +from __future__ import absolute_import +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 @@ -114,18 +127,292 @@ def public_value(key, value): isinstance(value, (FunctionType, MethodType, ModuleType, classmethod, type)) +def createCxxConfigDirectoryEntryFile(code, name, simobj, is_header): + entry_class = 'CxxConfigDirectoryEntry_%s' % name + param_class = '%sCxxConfigParams' % name + + code('#include "params/%s.hh"' % name) + + if not is_header: + for param in simobj._params.values(): + if isSimObjectClass(param.ptype): + code('#include "%s"' % param.ptype._value_dict['cxx_header']) + code('#include "params/%s.hh"' % param.ptype.__name__) + else: + param.ptype.cxx_ini_predecls(code) + + if is_header: + member_prefix = '' + end_of_decl = ';' + code('#include "sim/cxx_config.hh"') + code() + code('class ${param_class} : public CxxConfigParams,' + ' public ${name}Params') + code('{') + code(' private:') + code.indent() + code('class DirectoryEntry : public CxxConfigDirectoryEntry') + code('{') + code(' public:') + code.indent() + code('DirectoryEntry();'); + code() + code('CxxConfigParams *makeParamsObject() const') + code('{ return new ${param_class}; }') + code.dedent() + code('};') + code() + code.dedent() + code(' public:') + code.indent() + else: + member_prefix = '%s::' % param_class + end_of_decl = '' + code('#include "%s"' % simobj._value_dict['cxx_header']) + code('#include "base/str.hh"') + code('#include "cxx_config/${name}.hh"') + + code() + code('${member_prefix}DirectoryEntry::DirectoryEntry()'); + code('{') + + def cxx_bool(b): + return 'true' if b else 'false' + + code.indent() + for param in simobj._params.values(): + is_vector = isinstance(param, m5.params.VectorParamDesc) + is_simobj = issubclass(param.ptype, m5.SimObject.SimObject) + + code('parameters["%s"] = new ParamDesc("%s", %s, %s);' % + (param.name, param.name, cxx_bool(is_vector), + cxx_bool(is_simobj))); + + for port in simobj._ports.values(): + is_vector = isinstance(port, m5.params.VectorPort) + is_master = port.role == 'MASTER' + + code('ports["%s"] = new PortDesc("%s", %s, %s);' % + (port.name, port.name, cxx_bool(is_vector), + cxx_bool(is_master))) + + code.dedent() + code('}') + code() + + code('bool ${member_prefix}setSimObject(const std::string &name,') + code(' SimObject *simObject)${end_of_decl}') + + if not is_header: + code('{') + code.indent() + code('bool ret = true;') + code() + code('if (false) {') + for param in simobj._params.values(): + is_vector = isinstance(param, m5.params.VectorParamDesc) + is_simobj = issubclass(param.ptype, m5.SimObject.SimObject) + + if is_simobj and not is_vector: + code('} else if (name == "${{param.name}}") {') + code.indent() + code('this->${{param.name}} = ' + 'dynamic_cast<${{param.ptype.cxx_type}}>(simObject);') + code('if (simObject && !this->${{param.name}})') + code(' ret = false;') + code.dedent() + code('} else {') + code(' ret = false;') + code('}') + code() + code('return ret;') + code.dedent() + code('}') + + code() + code('bool ${member_prefix}setSimObjectVector(' + 'const std::string &name,') + code(' const std::vector &simObjects)${end_of_decl}') + + if not is_header: + code('{') + code.indent() + code('bool ret = true;') + code() + code('if (false) {') + for param in simobj._params.values(): + is_vector = isinstance(param, m5.params.VectorParamDesc) + is_simobj = issubclass(param.ptype, m5.SimObject.SimObject) + + if is_simobj and is_vector: + code('} else if (name == "${{param.name}}") {') + code.indent() + code('this->${{param.name}}.clear();') + code('for (auto i = simObjects.begin(); ' + 'ret && i != simObjects.end(); i ++)') + code('{') + code.indent() + code('${{param.ptype.cxx_type}} object = ' + 'dynamic_cast<${{param.ptype.cxx_type}}>(*i);') + code('if (*i && !object)') + code(' ret = false;') + code('else') + code(' this->${{param.name}}.push_back(object);') + code.dedent() + code('}') + code.dedent() + code('} else {') + code(' ret = false;') + code('}') + code() + code('return ret;') + code.dedent() + code('}') + + code() + code('void ${member_prefix}setName(const std::string &name_)' + '${end_of_decl}') + + if not is_header: + code('{') + code.indent() + code('this->name = name_;') + code.dedent() + code('}') + + if is_header: + code('const std::string &${member_prefix}getName()') + code('{ return this->name; }') + + code() + code('bool ${member_prefix}setParam(const std::string &name,') + code(' const std::string &value, const Flags flags)${end_of_decl}') + + if not is_header: + code('{') + code.indent() + code('bool ret = true;') + code() + code('if (false) {') + for param in simobj._params.values(): + is_vector = isinstance(param, m5.params.VectorParamDesc) + is_simobj = issubclass(param.ptype, m5.SimObject.SimObject) + + if not is_simobj and not is_vector: + code('} else if (name == "${{param.name}}") {') + code.indent() + param.ptype.cxx_ini_parse(code, + 'value', 'this->%s' % param.name, 'ret =') + code.dedent() + code('} else {') + code(' ret = false;') + code('}') + code() + code('return ret;') + code.dedent() + code('}') + + code() + code('bool ${member_prefix}setParamVector(' + 'const std::string &name,') + code(' const std::vector &values,') + code(' const Flags flags)${end_of_decl}') + + if not is_header: + code('{') + code.indent() + code('bool ret = true;') + code() + code('if (false) {') + for param in simobj._params.values(): + is_vector = isinstance(param, m5.params.VectorParamDesc) + is_simobj = issubclass(param.ptype, m5.SimObject.SimObject) + + if not is_simobj and is_vector: + code('} else if (name == "${{param.name}}") {') + code.indent() + code('${{param.name}}.clear();') + code('for (auto i = values.begin(); ' + 'ret && i != values.end(); i ++)') + code('{') + code.indent() + code('${{param.ptype.cxx_type}} elem;') + param.ptype.cxx_ini_parse(code, + '*i', 'elem', 'ret =') + code('if (ret)') + code(' this->${{param.name}}.push_back(elem);') + code.dedent() + code('}') + code.dedent() + code('} else {') + code(' ret = false;') + code('}') + code() + code('return ret;') + code.dedent() + code('}') + + code() + code('bool ${member_prefix}setPortConnectionCount(' + 'const std::string &name,') + code(' unsigned int count)${end_of_decl}') + + if not is_header: + code('{') + code.indent() + code('bool ret = true;') + code() + code('if (false)') + code(' ;') + for port in simobj._ports.values(): + code('else if (name == "${{port.name}}")') + code(' this->port_${{port.name}}_connection_count = count;') + code('else') + code(' ret = false;') + code() + code('return ret;') + code.dedent() + code('}') + + code() + code('SimObject *${member_prefix}simObjectCreate()${end_of_decl}') + + if not is_header: + code('{') + if hasattr(simobj, 'abstract') and simobj.abstract: + code(' return NULL;') + else: + code(' return this->create();') + code('}') + + if is_header: + code() + code('static CxxConfigDirectoryEntry' + ' *${member_prefix}makeDirectoryEntry()') + code('{ return new DirectoryEntry; }') + + if is_header: + code.dedent() + code('};') + # The metaclass for SimObject. This class controls how new classes # that derive from SimObject are instantiated, and provides inherited # class behavior (just like a class controls how instances of that # 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 } @@ -142,7 +429,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: @@ -150,8 +443,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: @@ -172,6 +473,7 @@ class MetaSimObject(type): # class or instance attributes cls._values = multidict() # param values + cls._hr_values = multidict() # human readable param values cls._children = multidict() # SimObject children cls._port_refs = multidict() # port ref objects cls._instantiated = False # really instantiated, cloned, or subclassed @@ -184,7 +486,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] @@ -197,6 +500,7 @@ class MetaSimObject(type): cls._params.parent = base._params cls._ports.parent = base._ports cls._values.parent = base._values + cls._hr_values.parent = base._hr_values cls._children.parent = base._children cls._port_refs.parent = base._port_refs # mark base as having been subclassed @@ -216,20 +520,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 @@ -247,7 +537,7 @@ class MetaSimObject(type): cls._new_port(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__) @@ -256,8 +546,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) @@ -273,8 +563,9 @@ class MetaSimObject(type): def _set_param(cls, name, value, param): assert(param.name == name) 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, ) @@ -284,6 +575,11 @@ class MetaSimObject(type): # it gets cloned properly when the class is instantiated if isSimObjectOrVector(value) and not value.has_parent(): cls._add_cls_child(name, value) + # update human-readable values of the param if it has a literal + # value and is not an object or proxy. + if not (isSimObjectOrVector(value) or\ + isinstance(value, m5.proxy.BaseProxy)): + cls._hr_values[name] = hr_value def _add_cls_child(cls, name, child): # It's a little funky to have a class as a parent, but these @@ -292,7 +588,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 @@ -319,19 +616,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) @@ -345,8 +642,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': @@ -358,14 +655,14 @@ class MetaSimObject(type): if attr == 'cxx_namespaces': return cls.cxx_class_path[:-1] - if cls._values.has_key(attr): + 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) + raise AttributeError( + "object '%s' has no attribute '%s'" % (cls.__name__, attr)) def __str__(cls): return cls.__name__ @@ -374,116 +671,108 @@ class MetaSimObject(type): 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): + def pybind_decl(cls, code): class_path = cls.cxx_class.split('::') - classname = class_path[-1] - namespaces = class_path[:-1] + namespaces, classname = class_path[:-1], class_path[-1] + py_class_name = '_COLONS_'.join(class_path) if namespaces else \ + classname; # 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). - params = cls._params.local.values() + # here). Sort the params based on their key + params = 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 {') +''') - if namespaces: - code('// avoid name conflicts') - sep_string = '_COLONS_' - flat_name = sep_string.join(class_path) - code('%rename($flat_name) $classname;') + for param in params: + param.pybind_predecls(code) - code() - code('// stop swig from creating/wrapping default ctor/dtor') - code('%nodefault $classname;') - code('class $classname') + code('''namespace py = pybind11; + +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('{') - code(' public:') - cls.export_methods(code) - code('};') + code('py::class_<${cls}Params, ' \ + 'std::unique_ptr<${cls}Params, py::nodelete>>(' \ + 'm, "${cls}Params")') - 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.dedent() + code('}') code() - code('%include "params/$cls.hh"') + 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. @@ -491,17 +780,88 @@ using std::ptrdiff_t; # 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). - params = cls._params.local.values() + # here). Sort the params based on their key + params = 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}__ @@ -509,30 +869,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() @@ -561,11 +915,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() @@ -575,6 +929,11 @@ struct PyObject; code('#endif // __PARAMS__${cls}__') return code + # Generate the C++ declaration/definition files for this SimObject's + # param struct to allow C++ initialisation + def cxx_config_param_file(cls, code, is_header): + createCxxConfigDirectoryEntryFile(code, cls.__name__, cls, is_header) + return code # This *temporary* definition is required to support calls from the # SimObject class definition to the MetaSimObject methods (in @@ -585,6 +944,129 @@ 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) + + 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.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) + + 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. +class ParamInfo(object): + def __init__(self, type, desc, type_str, example, default_val, access_str): + self.type = type + self.desc = desc + self.type_str = type_str + self.example_str = example + self.default_val = default_val + # The string representation used to access this param through python. + # The method to access this parameter presented on the command line may + # be different, so this needs to be stored for later use. + self.access_str = access_str + self.created = True + + # Make it so we can only set attributes at initialization time + # and effectively make this a const object. + def __setattr__(self, name, value): + 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) + # 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). @@ -596,30 +1078,88 @@ class SimObject(object): abstract = True cxx_header = "sim/sim_object.hh" - cxx_bases = [ "Drainable", "Serializable" ] + cxx_extra_bases = [ "Drainable", "Serializable" ] 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" -''') + cxx_exports = [ + PyBindMethod("init"), + PyBindMethod("initState"), + PyBindMethod("memInvalidate"), + PyBindMethod("memWriteback"), + PyBindMethod("regStats"), + PyBindMethod("resetStats"), + PyBindMethod("regProbePoints"), + PyBindMethod("regProbeListeners"), + PyBindMethod("startup"), + ] + + cxx_param_exports = [ + PyBindProperty("name"), + ] + + @cxxMethod + def loadState(self, cp): + """Load SimObject state from a checkpoint""" + pass - @classmethod - def export_methods(cls, code): - code(''' - void init(); - void loadState(Checkpoint *cp); - void initState(); - void regStats(); - void resetStats(); - void regProbePoints(); - void regProbeListeners(); - void startup(); -''') + # Returns a dict of all the option strings that can be + # generated as command line options for this simobject instance + # by tracing all reachable params in the top level instance and + # any children it contains. + def enumerateParams(self, flags_dict = {}, + cmd_line_str = "", access_str = ""): + if hasattr(self, "_paramEnumed"): + print("Cycle detected enumerating params") + else: + self._paramEnumed = True + # Scan the children first to pick up all the objects in this SimObj + for keys in self._children: + child = self._children[keys] + next_cmdline_str = cmd_line_str + keys + next_access_str = access_str + keys + if not isSimObjectVector(child): + next_cmdline_str = next_cmdline_str + "." + next_access_str = next_access_str + "." + flags_dict = child.enumerateParams(flags_dict, + next_cmdline_str, + next_access_str) + + # Go through the simple params in the simobject in this level + # of the simobject hierarchy and save information about the + # parameter to be used for generating and processing command line + # options to the simulator to set these parameters. + for keys,values in self._params.items(): + if values.isCmdLineSettable(): + type_str = '' + ex_str = values.example_str() + ptype = None + if isinstance(values, VectorParamDesc): + type_str = 'Vector_%s' % values.ptype_str + ptype = values + else: + type_str = '%s' % values.ptype_str + ptype = values.ptype + + if keys in self._hr_values\ + and keys in self._values\ + 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, + self._params[keys].desc, type_str, ex_str, + values.pretty_print(self._hr_values[keys]), + acc_str) + elif not keys in self._hr_values\ + and not keys in self._values: + # Empty param + cmd_str = cmd_line_str + keys + acc_str = access_str + keys + flags_dict[cmd_str] = ParamInfo(ptype, + self._params[keys].desc, + type_str, ex_str, '', acc_str) + + return flags_dict # Initialize new instance. For objects with SimObject-valued # children, we need to recursively clone the classes represented @@ -654,15 +1194,16 @@ 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 # individual value settings can be overridden but we still # inherit late changes to non-overridden class values. 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) @@ -670,10 +1211,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 @@ -687,13 +1228,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) @@ -709,20 +1250,18 @@ class SimObject(object): return ref def __getattr__(self, attr): - if self._ports.has_key(attr): + 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) @@ -733,7 +1272,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). @@ -743,7 +1282,7 @@ class SimObject(object): object.__setattr__(self, attr, value) return - if self._ports.has_key(attr): + if attr in self._ports: # set up port connection self._get_port_ref(attr).connect(value) return @@ -751,8 +1290,9 @@ class SimObject(object): param = self._params.get(attr) if param: 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, ) @@ -761,6 +1301,13 @@ class SimObject(object): # implicitly parent unparented objects assigned as params if isSimObjectOrVector(value) and not value.has_parent(): self.add_child(attr, value) + # set the human-readable value dict if this is a param + # with a literal value and is not being set as an object + # or proxy. + if not (isSimObjectOrVector(value) or\ + isinstance(value, m5.proxy.BaseProxy)): + self._hr_values[attr] = hr_value + return # if RHS is a SimObject, it's an implicit child assignment @@ -769,8 +1316,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 @@ -778,7 +1325,13 @@ class SimObject(object): def __getitem__(self, key): if key == 0: return self - raise TypeError, "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 + # element. + def __len__(self): + return 1 # Also implemented by SimObjectVector def clear_parent(self, old_parent): @@ -790,8 +1343,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 @@ -816,14 +1370,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 @@ -831,7 +1386,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() @@ -844,6 +1399,9 @@ 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 @@ -852,6 +1410,9 @@ class SimObject(object): def __str__(self): return self.path() + def config_value(self): + return self.path() + def ini_str(self): return self.path() @@ -860,31 +1421,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 @@ -900,31 +1462,33 @@ 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): all[match_obj] = True - return all.keys(), True + # Also make sure to sort the keys based on the objects' path to + # ensure that the order is the same on all hosts + return sorted(all.keys(), key = lambda o: o.path()), True def unproxy(self, base): 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) @@ -932,30 +1496,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 @@ -974,18 +1539,7 @@ class SimObject(object): for param in sorted(self._params.keys()): value = self._values.get(param) if value != None: - try: - # Use native type for those supported by JSON and - # strings for everything else. skipkeys=True seems - # to not work as well as one would hope - if type(self._values[param].value) in \ - [str, unicode, int, long, float, bool, None]: - d[param] = self._values[param].value - else: - d[param] = str(self._values[param]) - - except AttributeError: - pass + d[param] = value.config_value() for n in sorted(self._children.keys()): child = self._children[n] @@ -1009,10 +1563,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) @@ -1025,12 +1578,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) @@ -1050,20 +1609,24 @@ 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 - params = self.getCCParams() - self._ccObject = params.create() + if not self.abstract: + 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): yield self - for child in self._children.itervalues(): + # 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.items()): for obj in child.descendants(): yield obj @@ -1075,12 +1638,54 @@ 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): - for portRef in self._port_refs.itervalues(): + # 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.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) + # Function to provide to C++ so it can look up instances based on paths def resolveSimObject(name): obj = instanceDict[name] @@ -1125,7 +1730,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() @@ -1141,4 +1746,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', +]