From 545cbec5f711ba36899e97fbcdcd26aa9a611c99 Mon Sep 17 00:00:00 2001 From: Steve Reinhardt Date: Tue, 5 Sep 2006 22:04:34 -0700 Subject: Enable proxies (Self/Parent) for specifying ports. Significant revamp of Port code. Some cleanup of SimObject code too, particularly to make the SimObject and MetaSimObject implementations of __setattr__ more consistent. Unproxy code split out of print_ini(). src/python/m5/multidict.py: Make get() return None by default, to match semantics of built-in dictionary objects. --HG-- extra : convert_revision : db73b6cdd004a82a08b2402afd1e16544cb902a4 --- src/python/m5/SimObject.py | 178 ++++++++++++++++++++++++-------------- src/python/m5/__init__.py | 2 + src/python/m5/multidict.py | 8 +- src/python/m5/objects/Device.py | 2 +- src/python/m5/objects/Pci.py | 2 +- src/python/m5/params.py | 183 +++++++++++++++++++++++++++------------- src/python/m5/proxy.py | 3 +- 7 files changed, 249 insertions(+), 129 deletions(-) (limited to 'src/python/m5') diff --git a/src/python/m5/SimObject.py b/src/python/m5/SimObject.py index c30a0c623..281352e0d 100644 --- a/src/python/m5/SimObject.py +++ b/src/python/m5/SimObject.py @@ -161,7 +161,7 @@ class MetaSimObject(type): # class or instance attributes cls._values = multidict() # param values - cls._port_map = multidict() # port bindings + cls._port_refs = multidict() # port ref objects cls._instantiated = False # really instantiated, cloned, or subclassed # We don't support multiple inheritance. If you want to, you @@ -179,7 +179,7 @@ class MetaSimObject(type): cls._params.parent = base._params cls._ports.parent = base._ports cls._values.parent = base._values - cls._port_map.parent = base._port_map + cls._port_refs.parent = base._port_refs # mark base as having been subclassed base._instantiated = True @@ -197,7 +197,7 @@ class MetaSimObject(type): # port objects elif isinstance(val, Port): - cls._ports[key] = val + cls._new_port(key, val) # init-time-only keywords elif cls.init_keywords.has_key(key): @@ -227,7 +227,36 @@ class MetaSimObject(type): pdesc.name = name cls._params[name] = pdesc if hasattr(pdesc, 'default'): - setattr(cls, name, pdesc.default) + cls._set_param(name, pdesc.default, pdesc) + + def _set_param(cls, name, value, param): + assert(param.name == name) + try: + cls._values[name] = 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 + + 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 + if hasattr(port, 'default'): + cls._cls_get_port_ref(name).connect(port.default) + + # 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). @@ -242,7 +271,7 @@ class MetaSimObject(type): return if cls._ports.has_key(attr): - self._ports[attr].connect(self, attr, value) + cls._cls_get_port_ref(attr).connect(value) return if isSimObjectOrSequence(value) and cls._instantiated: @@ -252,21 +281,23 @@ class MetaSimObject(type): % (attr, cls.__name__) # check for param - param = cls._params.get(attr, None) + param = cls._params.get(attr) if param: - try: - cls._values[attr] = param.convert(value) - except Exception, e: - msg = "%s\nError setting param %s.%s to %s\n" % \ - (e, cls.__name__, attr, value) - e.args = (msg, ) - raise - elif isSimObjectOrSequence(value): - # if RHS is a SimObject, it's an implicit child assignment + cls._set_param(attr, value, param) + return + + if isSimObjectOrSequence(value): + # If RHS is a SimObject, it's an implicit child assignment. + # Classes don't have children, so we just put this object + # in _values; later, each instance will do a 'setattr(self, + # attr, _values[attr])' in SimObject.__init__ which will + # add this object as a child. cls._values[attr] = value - else: - raise AttributeError, \ - "Class %s has no parameter \'%s\'" % (cls.__name__, attr) + return + + # no valid assignment... raise exception + raise AttributeError, \ + "Class %s has no parameter \'%s\'" % (cls.__name__, attr) def __getattr__(cls, attr): if cls._values.has_key(attr): @@ -422,9 +453,9 @@ class SimObject(object): setattr(self, key, [ v(_memo=memo_dict) for v in val ]) # clone port references. no need to use a multidict here # since we will be creating new references for all ports. - self._port_map = {} - for key,val in ancestor._port_map.iteritems(): - self._port_map[key] = applyOrMap(val, 'clone', memo_dict) + 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) @@ -451,11 +482,19 @@ class SimObject(object): 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 not ref: + ref = self._ports[attr].makeRef(self) + self._port_refs[attr] = ref + return ref + def __getattr__(self, attr): if self._ports.has_key(attr): - # return reference that can be assigned to another port - # via __setattr__ - return self._ports[attr].makeRef(self, attr) + return self._get_port_ref(attr) if self._values.has_key(attr): return self._values[attr] @@ -473,7 +512,7 @@ class SimObject(object): if self._ports.has_key(attr): # set up port connection - self._ports[attr].connect(self, attr, value) + self._get_port_ref(attr).connect(value) return if isSimObjectOrSequence(value) and self._instantiated: @@ -482,7 +521,7 @@ class SimObject(object): " instance been cloned %s" % (attr, `self`) # must be SimObject param - param = self._params.get(attr, None) + param = self._params.get(attr) if param: try: value = param.convert(value) @@ -491,22 +530,17 @@ class SimObject(object): (e, self.__class__.__name__, attr, value) e.args = (msg, ) raise - elif isSimObjectOrSequence(value): - pass - else: - raise AttributeError, "Class %s has no parameter %s" \ - % (self.__class__.__name__, attr) + self._set_child(attr, value) + return - # clear out old child with this name, if any - self.clear_child(attr) + if isSimObjectOrSequence(value): + self._set_child(attr, value) + return - if isSimObject(value): - value.set_path(self, attr) - elif isSimObjectSequence(value): - value = SimObjVector(value) - [v.set_path(self, "%s%d" % (attr, i)) for i,v in enumerate(value)] + # no valid assignment... raise exception + raise AttributeError, "Class %s has no parameter %s" \ + % (self.__class__.__name__, attr) - self._values[attr] = value # this hack allows tacking a '[0]' onto parameters that may or may # not be vectors, and always getting the first element (e.g. cpus) @@ -528,12 +562,26 @@ class SimObject(object): def add_child(self, name, value): self._children[name] = value - def set_path(self, parent, name): + def _maybe_set_parent(self, parent, name): if not self._parent: self._parent = parent self._name = name parent.add_child(name, self) + def _set_child(self, attr, value): + # if RHS is a SimObject, it's an implicit child assignment + # clear out old child with this name, if any + self.clear_child(attr) + + if isSimObject(value): + value._maybe_set_parent(self, attr) + elif isSimObjectSequence(value): + value = SimObjVector(value) + [v._maybe_set_parent(self, "%s%d" % (attr, i)) + for i,v in enumerate(value)] + + self._values[attr] = value + def path(self): if not self._parent: return 'root' @@ -573,6 +621,26 @@ class SimObject(object): def unproxy(self, base): return self + def unproxy_all(self): + for param in self._params.iterkeys(): + value = self._values.get(param) + if value != None and proxy.isproxy(value): + try: + value = value.unproxy(self) + except: + print "Error in unproxying param '%s' of %s" % \ + (param, self.path()) + raise + setattr(self, param, value) + + for port_name in self._ports.iterkeys(): + port = self._port_refs.get(port_name) + if port != None: + port.unproxy(self) + + for child in self._children.itervalues(): + child.unproxy_all() + def print_ini(self): print '[' + self.path() + ']' # .ini section header @@ -591,32 +659,16 @@ class SimObject(object): param_names = self._params.keys() param_names.sort() for param in param_names: - value = self._values.get(param, None) + value = self._values.get(param) if value != None: - if proxy.isproxy(value): - try: - value = value.unproxy(self) - except: - print >> sys.stderr, \ - "Error in unproxying param '%s' of %s" % \ - (param, self.path()) - raise - setattr(self, param, value) print '%s=%s' % (param, self._values[param].ini_str()) port_names = self._ports.keys() port_names.sort() for port_name in port_names: - port = self._port_map.get(port_name, None) - if port == None: - default = getattr(self._ports[port_name], 'default', None) - if default == None: - # port is unbound... that's OK, go to next port - continue - else: - print port_name, default - port = m5.makeList(port) # make list even if it's a scalar port - print '%s=%s' % (port_name, ' '.join([str(p) for p in port])) + port = self._port_refs.get(port_name, None) + if port != None: + print '%s=%s' % (port_name, port.ini_str()) print # blank line between objects @@ -643,10 +695,10 @@ class SimObject(object): return self._ccObject # Create C++ port connections corresponding to the connections in - # _port_map (& recursively for all children) + # _port_refs (& recursively for all children) def connectPorts(self): - for portRef in self._port_map.itervalues(): - applyOrMap(portRef, 'ccConnect') + for portRef in self._port_refs.itervalues(): + portRef.ccConnect() for child in self._children.itervalues(): child.connectPorts() diff --git a/src/python/m5/__init__.py b/src/python/m5/__init__.py index c37abbac9..5717b49b6 100644 --- a/src/python/m5/__init__.py +++ b/src/python/m5/__init__.py @@ -44,6 +44,7 @@ def panic(string): print >>sys.stderr, 'panic:', string sys.exit(1) +# force scalars to one-element lists for uniformity def makeList(objOrList): if isinstance(objOrList, list): return objOrList @@ -75,6 +76,7 @@ env.update(os.environ) # once the config is built. def instantiate(root): params.ticks_per_sec = float(root.clock.frequency) + root.unproxy_all() # ugly temporary hack to get output to config.ini sys.stdout = file(os.path.join(options.outdir, 'config.ini'), 'w') root.print_ini() diff --git a/src/python/m5/multidict.py b/src/python/m5/multidict.py index 34fc3139b..b5cd700ef 100644 --- a/src/python/m5/multidict.py +++ b/src/python/m5/multidict.py @@ -29,7 +29,6 @@ __all__ = [ 'multidict' ] class multidict(object): - __nodefault = object() def __init__(self, parent = {}, **kwargs): self.local = dict(**kwargs) self.parent = parent @@ -102,14 +101,11 @@ class multidict(object): def values(self): return [ value for key,value in self.next() ] - def get(self, key, default=__nodefault): + def get(self, key, default=None): try: return self[key] except KeyError, e: - if default != self.__nodefault: - return default - else: - raise KeyError, e + return default def setdefault(self, key, default): try: diff --git a/src/python/m5/objects/Device.py b/src/python/m5/objects/Device.py index 3e9094e25..4672d1065 100644 --- a/src/python/m5/objects/Device.py +++ b/src/python/m5/objects/Device.py @@ -18,4 +18,4 @@ class BasicPioDevice(PioDevice): class DmaDevice(PioDevice): type = 'DmaDevice' abstract = True - dma = Port("DMA port") + dma = Port(Self.pio.peerObj.port, "DMA port") diff --git a/src/python/m5/objects/Pci.py b/src/python/m5/objects/Pci.py index 7c239d069..9872532ab 100644 --- a/src/python/m5/objects/Pci.py +++ b/src/python/m5/objects/Pci.py @@ -50,7 +50,7 @@ class PciConfigAll(PioDevice): class PciDevice(DmaDevice): type = 'PciDevice' abstract = True - config = Port("PCI configuration space port") + config = Port(Self.pio.peerObj.port, "PCI configuration space port") pci_bus = Param.Int("PCI bus") pci_dev = Param.Int("PCI device number") pci_func = Param.Int("PCI function code") diff --git a/src/python/m5/params.py b/src/python/m5/params.py index 3323766bd..e2aea2301 100644 --- a/src/python/m5/params.py +++ b/src/python/m5/params.py @@ -752,61 +752,72 @@ AllMemory = AddrRange(0, MaxAddr) # Port reference: encapsulates a reference to a particular port on a # particular SimObject. class PortRef(object): - def __init__(self, simobj, name, isVec): - assert(isSimObject(simobj)) + def __init__(self, simobj, name): + assert(isSimObject(simobj) or isSimObjectClass(simobj)) self.simobj = simobj self.name = name - self.index = -1 - self.isVec = isVec # is this a vector port? self.peer = None # not associated with another port yet self.ccConnected = False # C++ port connection done? + self.index = -1 # always -1 for non-vector ports def __str__(self): - ext = '' - if self.isVec: - ext = '[%d]' % self.index - return '%s.%s%s' % (self.simobj.path(), self.name, ext) - - # Set peer port reference. Called via __setattr__ as a result of - # a port assignment, e.g., "obj1.port1 = obj2.port2". - def setPeer(self, other): - if self.isVec: - curMap = self.simobj._port_map.get(self.name, []) - self.index = len(curMap) - curMap.append(other) - else: - curMap = self.simobj._port_map.get(self.name) - if curMap and not self.isVec: - print "warning: overwriting port", self.simobj, self.name - curMap = other - self.simobj._port_map[self.name] = curMap + return '%s.%s' % (self.simobj, self.name) + + # for config.ini, print peer's name (not ours) + def ini_str(self): + return str(self.peer) + + def __getattr__(self, attr): + if attr == 'peerObj': + # shorthand for proxies + return self.peer.simobj + raise AttributeError, "'%s' object has no attribute '%s'" % \ + (self.__class__.__name__, attr) + + # Full connection is symmetric (both ways). Called via + # SimObject.__setattr__ as a result of a port assignment, e.g., + # "obj1.portA = obj2.portB", or via VectorPortRef.__setitem__, + # e.g., "obj1.portA[3] = obj2.portB". + def connect(self, other): + if isinstance(other, VectorPortRef): + # reference to plain VectorPort is implicit append + other = other._get_next() + if not (isinstance(other, PortRef) or proxy.isproxy(other)): + raise TypeError, \ + "assigning non-port reference '%s' to port '%s'" \ + % (other, self) + if self.peer and not proxy.isproxy(self.peer): + print "warning: overwriting port", self, \ + "value", self.peer, "with", other self.peer = other + assert(not isinstance(self.peer, VectorPortRef)) + if isinstance(other, PortRef) and other.peer is not self: + other.connect(self) - def clone(self, memo): + def clone(self, simobj, memo): + if memo.has_key(self): + return memo[self] newRef = copy.copy(self) + memo[self] = newRef + newRef.simobj = simobj assert(isSimObject(newRef.simobj)) - newRef.simobj = newRef.simobj(_memo=memo) - # Tricky: if I'm the *second* PortRef in the pair to be - # cloned, then my peer is still in the middle of its clone - # method, and thus hasn't returned to its owner's - # SimObject.__init__ to get installed in _port_map. As a - # result I have no way of finding the *new* peer object. So I - # mark myself as "waiting" for my peer, and I let the *first* - # PortRef clone call set up both peer pointers after I return. - newPeer = newRef.simobj._port_map.get(self.name) - if newPeer: - if self.isVec: - assert(self.index != -1) - newPeer = newPeer[self.index] - # other guy is all set up except for his peer pointer - assert(newPeer.peer == -1) # peer must be waiting for handshake - newPeer.peer = newRef - newRef.peer = newPeer - else: - # other guy is in clone; just wait for him to do the work - newRef.peer = -1 # mark as waiting for handshake + if self.peer and not proxy.isproxy(self.peer): + peerObj = memo[self.peer.simobj] + newRef.peer = self.peer.clone(peerObj, memo) + assert(not isinstance(newRef.peer, VectorPortRef)) return newRef + def unproxy(self, simobj): + assert(simobj is self.simobj) + if proxy.isproxy(self.peer): + try: + realPeer = self.peer.unproxy(self.simobj) + except: + print "Error in unproxying port '%s' of %s" % \ + (self.name, self.simobj.path()) + raise + self.connect(realPeer) + # Call C++ to create corresponding port connection between C++ objects def ccConnect(self): if self.ccConnected: # already done this @@ -817,36 +828,94 @@ class PortRef(object): self.ccConnected = True peer.ccConnected = True +# A reference to an individual element of a VectorPort... much like a +# PortRef, but has an index. +class VectorPortElementRef(PortRef): + def __init__(self, simobj, name, index): + PortRef.__init__(self, simobj, name) + self.index = index + + def __str__(self): + return '%s.%s[%d]' % (self.simobj, self.name, self.index) + +# A reference to a complete vector-valued port (not just a single element). +# Can be indexed to retrieve individual VectorPortElementRef instances. +class VectorPortRef(object): + def __init__(self, simobj, name): + assert(isSimObject(simobj) or isSimObjectClass(simobj)) + self.simobj = simobj + self.name = name + self.elements = [] + + # for config.ini, print peer's name (not ours) + def ini_str(self): + return ' '.join([el.ini_str() for el in self.elements]) + + def __getitem__(self, key): + if not isinstance(key, int): + raise TypeError, "VectorPort index must be integer" + if key >= len(self.elements): + # need to extend list + ext = [VectorPortElementRef(self.simobj, self.name, i) + for i in range(len(self.elements), key+1)] + self.elements.extend(ext) + return self.elements[key] + + def _get_next(self): + return self[len(self.elements)] + + def __setitem__(self, key, value): + if not isinstance(key, int): + raise TypeError, "VectorPort index must be integer" + self[key].connect(value) + + def connect(self, other): + # reference to plain VectorPort is implicit append + self._get_next().connect(other) + + def unproxy(self, simobj): + [el.unproxy(simobj) for el in self.elements] + + def ccConnect(self): + [el.ccConnect() for el in self.elements] + # Port description object. Like a ParamDesc object, this represents a # logical port in the SimObject class, not a particular port on a # SimObject instance. The latter are represented by PortRef objects. class Port(object): - def __init__(self, desc): - self.desc = desc - self.isVec = False + # Port("description") or Port(default, "description") + def __init__(self, *args): + if len(args) == 1: + self.desc = args[0] + elif len(args) == 2: + self.default = args[0] + self.desc = args[1] + else: + raise TypeError, 'wrong number of arguments' + # self.name is set by SimObject class on assignment + # e.g., pio_port = Port("blah") sets self.name to 'pio_port' # Generate a PortRef for this port on the given SimObject with the # given name - def makeRef(self, simobj, name): - return PortRef(simobj, name, self.isVec) + def makeRef(self, simobj): + return PortRef(simobj, self.name) # Connect an instance of this port (on the given SimObject with # the given name) with the port described by the supplied PortRef - def connect(self, simobj, name, ref): - if not isinstance(ref, PortRef): - raise TypeError, \ - "assigning non-port reference port '%s'" % name - myRef = self.makeRef(simobj, name) - myRef.setPeer(ref) - ref.setPeer(myRef) + def connect(self, simobj, ref): + self.makeRef(simobj).connect(ref) # VectorPort description object. Like Port, but represents a vector # of connections (e.g., as on a Bus). class VectorPort(Port): - def __init__(self, desc): - Port.__init__(self, desc) + def __init__(self, *args): + Port.__init__(self, *args) self.isVec = True + def makeRef(self, simobj): + return VectorPortRef(simobj, self.name) + + __all__ = ['Param', 'VectorParam', 'Enum', 'Bool', 'String', 'Float', diff --git a/src/python/m5/proxy.py b/src/python/m5/proxy.py index 5be50481c..36995397b 100644 --- a/src/python/m5/proxy.py +++ b/src/python/m5/proxy.py @@ -41,7 +41,8 @@ class BaseProxy(object): def __setattr__(self, attr, value): if not attr.startswith('_'): - raise AttributeError, 'cannot set attribute on proxy object' + raise AttributeError, \ + "cannot set attribute '%s' on proxy object" % attr super(BaseProxy, self).__setattr__(attr, value) # support multiplying proxies by constants -- cgit v1.2.3