# Copyright (c) 2012 ARM Limited # All rights reserved. # # The license below extends only to copyright in the software and shall # not be construed as granting a license to any other intellectual # property including but not limited to intellectual property relating # to a hardware implementation of the functionality of the software # licensed hereunder. You may use the software subject to the license # terms below provided that you ensure that this notice is replicated # unmodified and in its entirety in all distributions of the software, # modified or unmodified, in source code or in binary form. # # Copyright (c) 2004-2006 The Regents of The University of Michigan # Copyright (c) 2010-20013 Advanced Micro Devices, Inc. # Copyright (c) 2013 Mark D. Hill and David A. Wood # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer; # redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution; # neither the name of the copyright holders nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # 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 import sys from types import FunctionType, MethodType, ModuleType import m5 from m5.util import * # Have to import params up top since Param is referenced on initial # load (when SimObject class references Param to create a class # variable, the 'name' param)... from m5.params import * # There are a few things we need that aren't in params.__all__ since # normal users don't need them from m5.params import ParamDesc, VectorParamDesc, \ isNullPointer, SimObjectVector, Port from m5.proxy import * from m5.proxy import isproxy ##################################################################### # # M5 Python Configuration Utility # # The basic idea is to write simple Python programs that build Python # objects corresponding to M5 SimObjects for the desired simulation # configuration. For now, the Python emits a .ini file that can be # parsed by M5. In the future, some tighter integration between M5 # and the Python interpreter may allow bypassing the .ini file. # # Each SimObject class in M5 is represented by a Python class with the # same name. The Python inheritance tree mirrors the M5 C++ tree # (e.g., SimpleCPU derives from BaseCPU in both cases, and all # SimObjects inherit from a single SimObject base class). To specify # an instance of an M5 SimObject in a configuration, the user simply # instantiates the corresponding Python object. The parameters for # that SimObject are given by assigning to attributes of the Python # object, either using keyword assignment in the constructor or in # separate assignment statements. For example: # # cache = BaseCache(size='64KB') # cache.hit_latency = 3 # cache.assoc = 8 # # The magic lies in the mapping of the Python attributes for SimObject # classes to the actual SimObject parameter specifications. This # allows parameter validity checking in the Python code. Continuing # the example above, the statements "cache.blurfl=3" or # "cache.assoc='hello'" would both result in runtime errors in Python, # since the BaseCache object has no 'blurfl' parameter and the 'assoc' # parameter requires an integer, respectively. This magic is done # primarily by overriding the special __setattr__ method that controls # assignment to object attributes. # # Once a set of Python objects have been instantiated in a hierarchy, # calling 'instantiate(obj)' (where obj is the root of the hierarchy) # will generate a .ini file. # ##################################################################### # list of all SimObject classes allClasses = {} # dict to look up SimObjects based on path instanceDict = {} # Did any of the SimObjects lack a header file? noCxxHeader = False def public_value(key, value): return key.startswith('_') or \ 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"') if simobj._ports.values() != []: code('#include "mem/mem_object.hh"') code('#include "mem/port.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('this->pyobj = NULL;') 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 } # Attributes that can be set any time keywords = { 'check' : FunctionType } # __new__ is called before __init__, and is where the statements # in the body of the class definition get loaded into the class's # __dict__. We intercept this to filter out parameter & port assignments # and only allow "private" attributes to be passed to the base # __new__ (starting with underscore). def __new__(mcls, name, bases, dict): assert name not in allClasses, "SimObject %s already present" % name # Copy "private" attributes, functions, and classes to the # official dict. Everything else goes in _init_dict to be # filtered in __init__. cls_dict = {} value_dict = {} for key,val in dict.items(): if public_value(key, val): cls_dict[key] = val else: # must be a param/port setting 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'] = [] cls_dict['_value_dict'] = value_dict cls = super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict) if 'type' in value_dict: allClasses[name] = cls return cls # subclass initialization def __init__(cls, name, bases, dict): # calls type.__init__()... I think that's a no-op, but leave # it here just in case it's not. super(MetaSimObject, cls).__init__(name, bases, dict) # initialize required attributes # class-only attributes cls._params = multidict() # param descriptions cls._ports = multidict() # port descriptions # 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 # We don't support multiple inheritance of sim objects. If you want # to, you must fix multidict to deal with it properly. Non sim-objects # are ok, though bTotal = 0 for c in bases: if isinstance(c, MetaSimObject): bTotal += 1 if bTotal > 1: raise TypeError, "SimObjects do not support multiple inheritance" base = bases[0] # Set up general inheritance via multidicts. A subclass will # inherit all its settings from the base class. The only time # the following is not true is when we define the SimObject # class itself (in which case the multidicts have no parent). if isinstance(base, MetaSimObject): cls._base = base 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 base._instantiated = True else: cls._base = None # default keyword values if 'type' in cls._value_dict: if 'cxx_class' not in cls._value_dict: cls._value_dict['cxx_class'] = cls._value_dict['type'] cls._value_dict['cxx_type'] = '%s *' % cls._value_dict['cxx_class'] if 'cxx_header' not in cls._value_dict: global noCxxHeader 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 # values or port bindings. The first 3 can only be set when # the class is defined, so we handle them here. The others # can be set later too, so just emulate that by calling # setattr(). for key,val in cls._value_dict.items(): # param descriptions if isinstance(val, ParamDesc): cls._new_param(key, val) # port objects elif isinstance(val, Port): cls._new_port(key, val) # init-time-only keywords elif cls.init_keywords.has_key(key): cls._set_keyword(key, val, cls.init_keywords[key]) # default: use normal path (ends up in __setattr__) else: setattr(cls, key, val) 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) if isinstance(val, FunctionType): val = classmethod(val) type.__setattr__(cls, keyword, val) def _new_param(cls, name, pdesc): # each param desc should be uniquely assigned to one variable assert(not hasattr(pdesc, 'name')) pdesc.name = name cls._params[name] = pdesc if hasattr(pdesc, 'default'): cls._set_param(name, pdesc.default, pdesc) def _set_param(cls, name, value, param): assert(param.name == name) try: hr_value = value value = param.convert(value) except Exception, e: msg = "%s\nError setting param %s.%s to %s\n" % \ (e, cls.__name__, name, value) e.args = (msg, ) raise cls._values[name] = value # if param value is a SimObject, make it a child too, so that # 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 # objects should never be instantiated (only cloned, which # clears the parent pointer), and this makes it clear that the # object is not an orphan and can provide better error # messages. child.set_parent(cls, name) cls._children[name] = child def _new_port(cls, name, port): # each port should be uniquely assigned to one variable assert(not hasattr(port, 'name')) port.name = name cls._ports[name] = port # same as _get_port_ref, effectively, but for classes def _cls_get_port_ref(cls, attr): # Return reference that can be assigned to another port # via __setattr__. There is only ever one reference # object per port, but we create them lazily here. ref = cls._port_refs.get(attr) if not ref: ref = cls._ports[attr].makeRef(cls) cls._port_refs[attr] = ref return ref # Set attribute (called on foo.attr = value when foo is an # instance of class cls). def __setattr__(cls, attr, value): # normal processing for private attributes if public_value(attr, value): type.__setattr__(cls, attr, value) return if cls.keywords.has_key(attr): cls._set_keyword(attr, value, cls.keywords[attr]) return if cls._ports.has_key(attr): cls._cls_get_port_ref(attr).connect(value) return if isSimObjectOrSequence(value) and cls._instantiated: raise RuntimeError, \ "cannot set SimObject parameter '%s' after\n" \ " class %s has been instantiated or subclassed" \ % (attr, cls.__name__) # check for param param = cls._params.get(attr) if param: cls._set_param(attr, value, param) return if isSimObjectOrSequence(value): # If RHS is a SimObject, it's an implicit child assignment. cls._add_cls_child(attr, coerceSimObjectOrVector(value)) return # no valid assignment... raise exception raise AttributeError, \ "Class %s has no parameter \'%s\'" % (cls.__name__, attr) def __getattr__(cls, attr): if attr == 'cxx_class_path': return cls.cxx_class.split('::') if attr == 'cxx_class_name': return cls.cxx_class_path[-1] if attr == 'cxx_namespaces': return cls.cxx_class_path[:-1] if cls._values.has_key(attr): return cls._values[attr] if cls._children.has_key(attr): return cls._children[attr] raise AttributeError, \ "object '%s' has no attribute '%s'" % (cls.__name__, attr) def __str__(cls): return cls.__name__ # 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 # 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] # 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() 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() for param in params: param.swig_predecls(code) cls.export_method_swig_predecls(code) code() if cls._base: code('%import "python/m5/internal/param_${{cls._base}}.i"') code() 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;') code() code('// stop swig from creating/wrapping default ctor/dtor') code('%nodefault $classname;') code('class $classname') if cls._base: bases = [ cls._base.cxx_class ] + cls.cxx_bases 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('};') for ns in reversed(namespaces): code('} // namespace $ns') code() code('%include "params/$cls.hh"') # Generate the C++ declaration (.hh file) for this SimObject's # param struct. Called from src/SConscript. def cxx_param_decl(cls, code): # 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() ports = cls._ports.local try: ptypes = [p.ptype for p in params] except: print cls, p, p.ptype_str print params raise class_path = cls._value_dict['cxx_class'].split('::') code('''\ #ifndef __PARAMS__${cls}__ #define __PARAMS__${cls}__ ''') # 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 #include ''') for param in params: param.cxx_predecls(code) for port in ports.itervalues(): port.cxx_predecls(code) code() if cls._base: code('#include "params/${{cls._base.type}}.hh"') code() for ptype in ptypes: if issubclass(ptype, Enum): code('#include "enums/${{ptype.__name__}}.hh"') code() # now generate the actual param struct code("struct ${cls}Params") if cls._base: code(" : public ${{cls._base.type}}Params") code("{") if not hasattr(cls, 'abstract') or not cls.abstract: if 'type' in cls.__dict__: code(" ${{cls.cxx_type}} create();") code.indent() if cls == SimObject: code(''' SimObjectParams() {} virtual ~SimObjectParams() {} std::string name; PyObject *pyobj; ''') for param in params: param.cxx_decl(code) for port in ports.itervalues(): port.cxx_decl(code) code.dedent() code('};') code() 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 # particular _set_param, which gets called for parameters with default # values defined on the SimObject class itself). It will get # overridden by the permanent definition (which requires that # SimObject be defined) lower in this file. def isSimObjectOrVector(value): return False # 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 # 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). 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" ] 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(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 # by those param values as well in a consistent "deep copy"-style # fashion. That is, we want to make sure that each instance is # cloned only once, and that if there are multiple references to # the same original object, we end up with the corresponding # cloned references all pointing to the same cloned instance. def __init__(self, **kwargs): ancestor = kwargs.get('_ancestor') memo_dict = kwargs.get('_memo') if memo_dict is None: # prepare to memoize any recursively instantiated objects memo_dict = {} elif ancestor: # memoize me now to avoid problems with recursive calls memo_dict[ancestor] = self if not ancestor: ancestor = self.__class__ ancestor._instantiated = True # initialize required attributes self._parent = None self._name = None self._ccObject = None # pointer to C++ object self._ccParams = None self._instantiated = False # really "cloned" # Clone children specified at class level. No need for a # multidict here since we will be cloning everything. # 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(): 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(): val = tryAsSimObjectOrVector(val) if val is not None: self._values[key] = val(_memo=memo_dict) # 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(): self._port_refs[key] = val.clone(self, memo_dict) # apply attribute assignments from keyword args, if any for key,val in kwargs.iteritems(): setattr(self, key, val) # "Clone" the current instance by creating another instance of # this instance's class, but that inherits its parameter values # and port mappings from the current instance. If we're in a # "deep copy" recursive clone, check the _memo dict to see if # we've already cloned this instance. def __call__(self, **kwargs): memo_dict = kwargs.get('_memo') if memo_dict is None: # 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 " \ "not at the root of a tree (parent = %s)" \ % (self, self._parent) # create a new dict and use that. memo_dict = {} kwargs['_memo'] = memo_dict elif memo_dict.has_key(self): # clone already done & memoized return memo_dict[self] return self.__class__(_ancestor = self, **kwargs) def _get_port_ref(self, attr): # Return reference that can be assigned to another port # via __setattr__. There is only ever one reference # object per port, but we create them lazily here. ref = self._port_refs.get(attr) if ref == None: ref = self._ports[attr].makeRef(self) self._port_refs[attr] = ref return ref def __getattr__(self, attr): if self._ports.has_key(attr): return self._get_port_ref(attr) if self._values.has_key(attr): return self._values[attr] if self._children.has_key(attr): 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(). if self._ccObject and hasattr(self._ccObject, attr): return getattr(self._ccObject, attr) err_string = "object '%s' has no attribute '%s'" \ % (self.__class__.__name__, attr) if not self._ccObject: err_string += "\n (C++ object is not yet constructed," \ " so wrapped C++ methods are unavailable.)" raise AttributeError, err_string # Set attribute (called on foo.attr = value when foo is an # instance of class cls). def __setattr__(self, attr, value): # normal processing for private attributes if attr.startswith('_'): object.__setattr__(self, attr, value) return if self._ports.has_key(attr): # set up port connection self._get_port_ref(attr).connect(value) return param = self._params.get(attr) if param: try: hr_value = value value = param.convert(value) except Exception, e: msg = "%s\nError setting param %s.%s to %s\n" % \ (e, self.__class__.__name__, attr, value) e.args = (msg, ) raise self._values[attr] = value # 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 if isSimObjectOrSequence(value): self.add_child(attr, value) return # no valid assignment... raise exception raise AttributeError, "Class %s has no parameter %s" \ % (self.__class__.__name__, attr) # this hack allows tacking a '[0]' onto parameters that may or may # not be vectors, and always getting the first element (e.g. cpus) def __getitem__(self, key): if key == 0: return self 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): assert self._parent is old_parent self._parent = None # Also implemented by SimObjectVector def set_parent(self, parent, name): 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 def get_parent(self): return self._parent # Also implemented by SimObjectVector def get_name(self): return self._name # Also implemented by SimObjectVector def has_parent(self): return self._parent is not None # clear out child with given name. This code is not likely to be exercised. # See comment in add_child. def clear_child(self, name): child = self._children[name] child.clear_parent(self) del self._children[name] # Add a new child to this object. def add_child(self, name, child): child = coerceSimObjectOrVector(child) if child.has_parent(): warn("add_child('%s'): child '%s' already has parent", name, child.get_name()) if self._children.has_key(name): # 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 # Take SimObject-valued parameters that haven't been explicitly # assigned as children and make them children of the object that # they were assigned to as a parameter value. This guarantees # 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(): if not isSimObjectVector(val) and isSimObjectSequence(val): # need to convert raw SimObject sequences to # SimObjectVector class so we can call has_parent() val = SimObjectVector(val) self._values[key] = val if isSimObjectOrVector(val) and not val.has_parent(): warn("%s adopting orphan SimObject param '%s'", self, key) self.add_child(key, val) def path(self): if not self._parent: return '' % self.__class__ ppath = self._parent.path() if ppath == 'root': return self._name return ppath + "." + self._name def __str__(self): return self.path() def config_value(self): return self.path() def ini_str(self): return self.path() def find_any(self, ptype): if isinstance(self, ptype): return self, True found_obj = None for child in self._children.itervalues(): 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, \ 'parent.any matched more than one: %s %s' % \ (found_obj.path, child.path) found_obj = child # search param space for pname,pdesc in self._params.iteritems(): 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) found_obj = match_obj return found_obj, found_obj != None def find_all(self, ptype): all = {} # search children for child in self._children.itervalues(): # a child could be a list, so ensure we visit each item if isinstance(child, list): children = child else: children = [child] for child in children: if isinstance(child, ptype) and not isproxy(child) and \ not isNullPointer(child): all[child] = True if isSimObject(child): # also add results from the child itself 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(): 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 def unproxy(self, base): return self def unproxyParams(self): for param in self._params.iterkeys(): 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()) 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.sort() for port_name in port_names: port = self._port_refs.get(port_name) if port != None: port.unproxy(self) def print_ini(self, ini_file): print >>ini_file, '[' + self.path() + ']' # .ini section header instanceDict[self.path()] = self if hasattr(self, 'type'): print >>ini_file, 'type=%s' % self.type if len(self._children.keys()): print >>ini_file, 'children=%s' % \ ' '.join(self._children[n].get_name() \ for n in sorted(self._children.keys())) 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()) 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 >>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 # visualization, and other similar tasks def get_config_as_dict(self): d = attrdict() if hasattr(self, 'type'): d.type = self.type if hasattr(self, 'cxx_class'): d.cxx_class = self.cxx_class # Add the name and path of this object to be able to link to # the stats d.name = self.get_name() d.path = self.path() for param in sorted(self._params.keys()): value = self._values.get(param) if value != None: d[param] = value.config_value() for n in sorted(self._children.keys()): child = self._children[n] # Use the name of the attribute (and not get_name()) as # the key in the JSON dictionary to capture the hierarchy # in the Python code that assembled this system d[n] = child.get_config_as_dict() for port_name in sorted(self._ports.keys()): port = self._port_refs.get(port_name, None) if port != None: # Represent each port with a dictionary containing the # prominent attributes d[port_name] = port.get_config_as_dict() return d def getCCParams(self): if self._ccParams: return self._ccParams 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.sort() for param in param_names: value = self._values.get(param) if value is None: fatal("%s.%s without default or user set value", self.path(), param) value = value.getValue() if isinstance(self._params[param], VectorParamDesc): assert isinstance(value, list) vec = getattr(cc_params, param) assert not len(vec) for v in value: vec.append(v) else: setattr(cc_params, param, value) port_names = self._ports.keys() port_names.sort() for port_name in port_names: port = self._port_refs.get(port_name, None) if port != None: port_count = len(port) else: port_count = 0 setattr(cc_params, 'port_' + port_name + '_connection_count', port_count) self._ccParams = cc_params return self._ccParams # Get C++ object corresponding to this object, calling C++ if # necessary to construct it. Does *not* recursively create # children. def getCCObject(self): 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" # Cycles in the configuration hierarchy are not supported. This # will catch the resulting recursion and stop. self._ccObject = -1 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() return self._ccObject def descendants(self): yield self for child in self._children.itervalues(): for obj in child.descendants(): yield obj # Call C++ to create C++ object corresponding to this object def createCCObject(self): self.getCCParams() self.getCCObject() # force creation def getValue(self): return self.getCCObject() # Create C++ port connections corresponding to the connections in # _port_refs def connectPorts(self): for portRef in self._port_refs.itervalues(): portRef.ccConnect() # Function to provide to C++ so it can look up instances based on paths def resolveSimObject(name): obj = instanceDict[name] return obj.getCCObject() def isSimObject(value): return isinstance(value, SimObject) def isSimObjectClass(value): return issubclass(value, SimObject) def isSimObjectVector(value): return isinstance(value, SimObjectVector) def isSimObjectSequence(value): if not isinstance(value, (list, tuple)) or len(value) == 0: return False for val in value: if not isNullPointer(val) and not isSimObject(val): return False return True def isSimObjectOrSequence(value): return isSimObject(value) or isSimObjectSequence(value) def isRoot(obj): from m5.objects import Root return obj and obj is Root.getInstance() def isSimObjectOrVector(value): return isSimObject(value) or isSimObjectVector(value) def tryAsSimObjectOrVector(value): if isSimObjectOrVector(value): return value if isSimObjectSequence(value): return SimObjectVector(value) return None def coerceSimObjectOrVector(value): value = tryAsSimObjectOrVector(value) if value is None: raise TypeError, "SimObject or SimObjectVector expected" return value baseClasses = allClasses.copy() baseInstances = instanceDict.copy() def clear(): global allClasses, instanceDict, noCxxHeader allClasses = baseClasses.copy() instanceDict = baseInstances.copy() noCxxHeader = False # __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' ]