Only allow SimObject classes to be instantiated (no cloning!).
authorSteve Reinhardt <stever@eecs.umich.edu>
Sat, 10 Jun 2006 23:58:36 +0000 (19:58 -0400)
committerSteve Reinhardt <stever@eecs.umich.edu>
Sat, 10 Jun 2006 23:58:36 +0000 (19:58 -0400)
Provide a makeClass() method to generate a new class using
a SimObject instance as a template.
All instantiation, subclassing, and class generation is done
recursively using "deep copy"-style memoization to maintain
object relationships in the face of multiple references to
shared objects/classes.

src/python/m5/multidict.py:
    Rename local dict attribute from 'dict' to 'local'
    for clarity.

--HG--
extra : convert_revision : 73ed6836216308709d7bb68d09f8131acd5f1822

src/python/m5/config.py
src/python/m5/multidict.py

index f6a2a84fbeadda75ee1cc4ff1e2e8392769361d4..9de768d18d341b5ef78cebd7052084bc75b0545e 100644 (file)
 # Authors: Steve Reinhardt
 #          Nathan Binkert
 
-from __future__ import generators
 import os, re, sys, types, inspect
 
 import m5
-panic = m5.panic
+from m5 import panic
 from convert import *
 from multidict import multidict
 
@@ -137,6 +136,13 @@ class Singleton(type):
 def isSimObject(value):
     return isinstance(value, SimObject)
 
+def isSimObjectClass(value):
+    try:
+        return issubclass(value, SimObject)
+    except TypeError:
+        # happens if value is not a class at all
+        return False
+
 def isSimObjSequence(value):
     if not isinstance(value, (list, tuple)):
         return False
@@ -147,6 +153,16 @@ def isSimObjSequence(value):
 
     return True
 
+def isSimObjClassSequence(value):
+    if not isinstance(value, (list, tuple)):
+        return False
+
+    for val in value:
+        if not isNullPointer(val) and not isSimObjectClass(val):
+            return False
+
+    return True
+
 def isNullPointer(value):
     return isinstance(value, NullSimObject)
 
@@ -170,19 +186,26 @@ class MetaSimObject(type):
     # and only allow "private" attributes to be passed to the base
     # __new__ (starting with underscore).
     def __new__(mcls, name, bases, dict):
-        # Copy "private" attributes (including special methods such as __new__)
-        # to the official dict.  Everything else goes in _init_dict to be
-        # filtered in __init__.
-        cls_dict = {}
-        for key,val in dict.items():
-            if key.startswith('_'):
-                cls_dict[key] = val
-                del dict[key]
-        cls_dict['_init_dict'] = dict
+        if dict.has_key('_init_dict'):
+            # must have been called from makeSubclass() rather than
+            # via Python class declaration; bypass filtering process.
+            cls_dict = dict
+        else:
+            # Copy "private" attributes (including special methods
+            # such as __new__) to the official dict.  Everything else
+            # goes in _init_dict to be filtered in __init__.
+            cls_dict = {}
+            for key,val in dict.items():
+                if key.startswith('_'):
+                    cls_dict[key] = val
+                    del dict[key]
+            cls_dict['_init_dict'] = dict
         return super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
 
-    # initialization
+    # 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
@@ -197,27 +220,13 @@ class MetaSimObject(type):
 
         base = bases[0]
 
+        # the only time the following is not true is when we define
+        # the SimObject class itself
         if isinstance(base, MetaSimObject):
             cls._params.parent = base._params
             cls._values.parent = base._values
 
-            # If your parent has a value in it that's a config node, clone
-            # it.  Do this now so if we update any of the values'
-            # attributes we are updating the clone and not the original.
-            for key,val in base._values.iteritems():
-
-                # don't clone if (1) we're about to overwrite it with
-                # a local setting or (2) we've already cloned a copy
-                # from an earlier (more derived) base
-                if cls._init_dict.has_key(key) or cls._values.has_key(key):
-                    continue
-
-                if isSimObject(val):
-                    cls._values[key] = val()
-                elif isSimObjSequence(val) and len(val):
-                    cls._values[key] = [ v() for v in val ]
-
-        # now process remaining _init_dict items
+        # now process the _init_dict items
         for key,val in cls._init_dict.items():
             if isinstance(val, (types.FunctionType, types.TypeType)):
                 type.__setattr__(cls, key, val)
@@ -234,6 +243,33 @@ class MetaSimObject(type):
             else:
                 setattr(cls, key, val)
 
+        # Pull the deep-copy memoization dict out of the class dict if
+        # it's there...
+        memo = cls.__dict__.get('_memo', {})
+
+        # Handle SimObject values
+        for key,val in cls._values.iteritems():
+            # SimObject instances need to be promoted to classes.
+            # Existing classes should not have any instance values, so
+            # these can only occur at the lowest level dict (the
+            # parameters just being set in this class definition).
+            if isSimObject(val):
+                assert(val == cls._values.local[key])
+                cls._values[key] = val.makeClass(memo)
+            elif isSimObjSequence(val) and len(val):
+                assert(val == cls._values.local[key])
+                cls._values[key] = [ v.makeClass(memo) for v in val ]
+            # SimObject classes need to be subclassed so that
+            # parameters that get set at this level only affect this
+            # level and derivatives.
+            elif isSimObjectClass(val):
+                assert(not cls._values.local.has_key(key))
+                cls._values[key] = val.makeSubclass({}, memo)
+            elif isSimObjClassSequence(val) and len(val):
+                assert(not cls._values.local.has_key(key))
+                cls._values[key] = [ v.makeSubclass({}, memo) for v in val ]
+
+
     def _set_keyword(cls, keyword, val, kwtype):
         if not isinstance(val, kwtype):
             raise TypeError, 'keyword %s has bad type %s (expecting %s)' % \
@@ -284,6 +320,22 @@ class MetaSimObject(type):
         raise AttributeError, \
               "object '%s' has no attribute '%s'" % (cls.__name__, attr)
 
+    # Create a subclass of this class.  Basically a function interface
+    # to the standard Python class definition mechanism, primarily for
+    # internal use.  'memo' dict param supports "deep copy" (really
+    # "deep subclass") operations... within a given operation,
+    # multiple references to a class should result in a single
+    # subclass object with multiple references to it (as opposed to
+    # mutiple unique subclasses).
+    def makeSubclass(cls, init_dict, memo = {}):
+        subcls = memo.get(cls)
+        if not subcls:
+            name = cls.__name__ + '_' + str(cls._anon_subclass_counter)
+            cls._anon_subclass_counter += 1
+            subcls = MetaSimObject(name, (cls,),
+                                   { '_init_dict': init_dict, '_memo': memo })
+        return subcls
+
 # The ConfigNode 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).
@@ -292,27 +344,78 @@ class SimObject(object):
     # get this metaclass.
     __metaclass__ = MetaSimObject
 
-    def __init__(self, _value_parent = None, **kwargs):
+    # __new__ operator allocates new instances of the class.  We
+    # override it here just to support "deep instantiation" operation
+    # via the _memo dict.  When recursively instantiating an object
+    # hierarchy we want to make sure that each class is instantiated
+    # only once, and that if there are multiple references to the same
+    # original class, we end up with the corresponding instantiated
+    # references all pointing to the same instance.
+    def __new__(cls, _memo = None, **kwargs):
+        if _memo is not None and _memo.has_key(cls):
+            # return previously instantiated object
+            assert(len(kwargs) == 0)
+            return _memo[cls]
+        else:
+            # Need a new one... if it needs to be memoized, this will
+            # happen in __init__.  We defer the insertion until then
+            # so __init__ can use the memo dict to tell whether or not
+            # to perform the initialization.
+            return super(SimObject, cls).__new__(cls, **kwargs)
+
+    # Initialize new instance previously allocated by __new__.  For
+    # objects with SimObject-valued params, we need to recursively
+    # instantiate the classes represented by those param values as
+    # well (in a consistent "deep copy"-style fashion; see comment
+    # above).
+    def __init__(self, _memo = None, **kwargs):
+        if _memo is not None:
+            # We're inside a "deep instantiation"
+            assert(isinstance(_memo, dict))
+            assert(len(kwargs) == 0)
+            if _memo.has_key(self.__class__):
+                # __new__ returned an existing, already initialized
+                # instance, so there's nothing to do here
+                assert(_memo[self.__class__] == self)
+                return
+            # no pre-existing object, so remember this one here
+            _memo[self.__class__] = self
+        else:
+            # This is a new top-level instantiation... don't memoize
+            # this objcet, but prepare to memoize any recursively
+            # instantiated objects.
+            _memo = {}
+
         self._children = {}
-        if _value_parent and type(_value_parent) != type(self):
-            # this was called as a type conversion rather than a clone
-            raise TypeError, "Cannot convert %s to %s" % \
-                  (_value_parent.__class__.__name__, self.__class__.__name__)
-        if not _value_parent:
-            _value_parent = self.__class__
-        # clone values
-        self._values = multidict(_value_parent._values)
-        for key,val in _value_parent._values.iteritems():
-            if isSimObject(val):
-                setattr(self, key, val())
-            elif isSimObjSequence(val) and len(val):
-                setattr(self, key, [ v() for v in val ])
+        # Inherit parameter values from class using multidict so
+        # individual value settings can be overridden.
+        self._values = multidict(self.__class__._values)
+        # For SimObject-valued parameters, the class should have
+        # classes (not instances) for the values.  We need to
+        # instantiate these classes rather than just inheriting the
+        # class object.
+        for key,val in self.__class__._values.iteritems():
+            if isSimObjectClass(val):
+                setattr(self, key, val(_memo))
+            elif isSimObjClassSequence(val) and len(val):
+                setattr(self, key, [ v(_memo) for v in val ])
         # apply attribute assignments from keyword args, if any
         for key,val in kwargs.iteritems():
             setattr(self, key, val)
 
+    # Use this instance as a template to create a new class.
+    def makeClass(self, memo = {}):
+        cls = memo.get(self)
+        if not cls:
+            cls =  self.__class__.makeSubclass(self._values.local)
+            memo[self] = cls
+        return cls
+
+    # Direct instantiation of instances (cloning) is no longer
+    # allowed; must generate class from instance first.
     def __call__(self, **kwargs):
-        return self.__class__(_value_parent = self, **kwargs)
+        raise TypeError, "cannot instantiate SimObject; "\
+              "use makeClass() to make class first"
 
     def __getattr__(self, attr):
         if self._values.has_key(attr):
@@ -1069,7 +1172,10 @@ class EthernetAddr(ParamValue):
 
     def __str__(self):
         if self.value == NextEthernetAddr:
-            return self.addr
+            if hasattr(self, 'addr'):
+                return self.addr
+            else:
+                return "NextEthernetAddr (unresolved)"
         else:
             return self.value
 
index 698e85b26410b2260819bc5a3990f2f8e30df8f4..34fc3139bcc9615d17c1354262670df6311e0dca 100644 (file)
@@ -31,7 +31,7 @@ __all__ = [ 'multidict' ]
 class multidict(object):
     __nodefault = object()
     def __init__(self, parent = {}, **kwargs):
-        self.dict = dict(**kwargs)
+        self.local = dict(**kwargs)
         self.parent = parent
         self.deleted = {}
 
@@ -42,11 +42,11 @@ class multidict(object):
         return `dict(self.items())`
 
     def __contains__(self, key):
-        return self.dict.has_key(key) or self.parent.has_key(key)
+        return self.local.has_key(key) or self.parent.has_key(key)
 
     def __delitem__(self, key):
         try:
-            del self.dict[key]
+            del self.local[key]
         except KeyError, e:
             if key in self.parent:
                 self.deleted[key] = True
@@ -55,11 +55,11 @@ class multidict(object):
 
     def __setitem__(self, key, value):
         self.deleted.pop(key, False)
-        self.dict[key] = value
+        self.local[key] = value
 
     def __getitem__(self, key):
         try:
-            return self.dict[key]
+            return self.local[key]
         except KeyError, e:
             if not self.deleted.get(key, False) and key in self.parent:
                 return self.parent[key]
@@ -67,15 +67,15 @@ class multidict(object):
                 raise KeyError, e
 
     def __len__(self):
-        return len(self.dict) + len(self.parent)
+        return len(self.local) + len(self.parent)
 
     def next(self):
-        for key,value in self.dict.items():
+        for key,value in self.local.items():
             yield key,value
 
         if self.parent:
             for key,value in self.parent.next():
-                if key not in self.dict and key not in self.deleted:
+                if key not in self.local and key not in self.deleted:
                     yield key,value
 
     def has_key(self, key):
@@ -116,22 +116,22 @@ class multidict(object):
             return self[key]
         except KeyError:
             self.deleted.pop(key, False)
-            self.dict[key] = default
+            self.local[key] = default
             return default
 
     def _dump(self):
         print 'multidict dump'
         node = self
         while isinstance(node, multidict):
-            print '    ', node.dict
+            print '    ', node.local
             node = node.parent
 
     def _dumpkey(self, key):
         values = []
         node = self
         while isinstance(node, multidict):
-            if key in node.dict:
-                values.append(node.dict[key])
+            if key in node.local:
+                values.append(node.local[key])
             node = node.parent
         print key, values