python: Make standard Params::create() optional.
authorGabe Black <gabeblack@google.com>
Tue, 13 Oct 2020 10:46:00 +0000 (03:46 -0700)
committerGabe Black <gabe.black@gmail.com>
Thu, 29 Oct 2020 04:12:12 +0000 (04:12 +0000)
The *vast* majority of SimObjects use the standard boilerplate version
of their Params::create() method which just returns new
ClassName(*this); Rather than force every class to define this method,
or annoy and frustrate users who forget and then get linker errors, this
change automates the default while leaving the possibility of defining a
custom create() method for non-default cases.

The situations this mechanism handles can be first broken down by
whether the SimObject class has a constructor of the normal form, ie one
that takes a const Params reference as its only parameter.

If no, then no default create() implementation is defined, and one
*must* be defined by the user.

If yes, then a default create() implementation is defined as a weak
symbol. If the user still wants to define their own create method for
some reason, perhaps to add debugging info, to keep track of instances
in c++, etc., then they can and it will override the weak symbol and
take precedence.

The way this is implemented is not straightforward. A set of classes are
defined which use SFINAE which either map in the real Params type or a
dummy based on whether the normal constructor exists in the SimObject
class. Then those classes are used to define *a* create method.
Depending on how the SFINAE works out, that will either be *the* create
method on the real Params struct, or a create method on a dummy class
set up to just absorb the definition and then go away. In either case the
create() method is a weak symbol, but in the dummy case it
doesn't/shouldn't matter.

Annoyingly the compiler insists that the weak symbol be visible. While
that makes total sense normally, we don't actually care what happens to
the weak symbol if it's attached to the dummy class. Unfortunately that
means we need to make the dummy class globally visible, although we put
it in a namespace to keep it from colliding with anything useful.

Change-Id: I3767a8dc8dc03665a72d5e8c294550d96466f741
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/35942
Reviewed-by: Gabe Black <gabe.black@gmail.com>
Reviewed-by: Richard Cooper <richard.cooper@arm.com>
Maintainer: Gabe Black <gabe.black@gmail.com>
Tested-by: kokoro <noreply+kokoro@google.com>
src/python/m5/SimObject.py
src/sim/sim_object.hh

index 458942103ce430407aa27958fcee7e6c4461e861..f8d6c27e6e8e3ec234756ee0979ff4abf72370c6 100644 (file)
@@ -720,6 +720,9 @@ class MetaSimObject(type):
         code('''#include "pybind11/pybind11.h"
 #include "pybind11/stl.h"
 
+#include <type_traits>
+
+#include "base/compiler.hh"
 #include "params/$cls.hh"
 #include "python/pybind11/core.hh"
 #include "sim/init.hh"
@@ -801,6 +804,76 @@ module_init(py::module &m_internal)
         code()
         code('static EmbeddedPyBind embed_obj("${0}", module_init, "${1}");',
              cls, cls._base.type if cls._base else "")
+        if not hasattr(cls, 'abstract') or not cls.abstract:
+            if 'type' in cls.__dict__:
+                code()
+                # This namespace can't *actually* be anonymous, or the compiler
+                # gets upset about having a weak symbol init.
+                code('namespace anonymous_params')
+                code('{')
+                code()
+                # If we can't define a default create() method for this params
+                # struct because the SimObject doesn't have the right
+                # constructor, use template magic to make it so we're actually
+                # defining a create method for this class instead.
+                code('class Dummy${cls}ParamsClass')
+                code('{')
+                code('  public:')
+                code('    ${{cls.cxx_class}} *create() const;')
+                code('};')
+                code()
+                code('template <class CxxClass, class Enable=void>')
+                code('class DummyShunt;')
+                code()
+                # This version directs to the real Params struct and the
+                # default behavior of create if there's an appropriate
+                # constructor.
+                code('template <class CxxClass>')
+                code('class DummyShunt<CxxClass, std::enable_if_t<')
+                code('    std::is_constructible<CxxClass,')
+                code('        const ${cls}Params &>::value>>')
+                code('{')
+                code('  public:')
+                code('    using Params = ${cls}Params;')
+                code('    static ${{cls.cxx_class}} *')
+                code('    create(const Params &p)')
+                code('    {')
+                code('        return new CxxClass(p);')
+                code('    }')
+                code('};')
+                code()
+                # This version diverts to the DummyParamsClass and a dummy
+                # implementation of create if the appropriate constructor does
+                # not exist.
+                code('template <class CxxClass>')
+                code('class DummyShunt<CxxClass, std::enable_if_t<')
+                code('    !std::is_constructible<CxxClass,')
+                code('        const ${cls}Params &>::value>>')
+                code('{')
+                code('  public:')
+                code('    using Params = Dummy${cls}ParamsClass;')
+                code('    static ${{cls.cxx_class}} *')
+                code('    create(const Params &p)')
+                code('    {')
+                code('        return nullptr;')
+                code('    }')
+                code('};')
+                code()
+                code('} // namespace anonymous_params')
+                code()
+                code('using namespace anonymous_params;')
+                code()
+                # A weak implementation of either the real Params struct's
+                # create method, or the Dummy one if we don't want to have
+                # any default implementation. Either an implementation is
+                # mandantory since this was shunted off to the dummy class, or
+                # one is optional which will override this weak version.
+                code('M5_WEAK ${{cls.cxx_class}} *')
+                code('DummyShunt<${{cls.cxx_class}}>::Params::create() const')
+                code('{')
+                code('    return DummyShunt<${{cls.cxx_class}}>::')
+                code('        create(*this);')
+                code('}')
 
     _warned_about_nested_templates = False
 
index ca2e1d5c3d563c177f3e13a4de2634df66eeadee..a75f8dde79ffbfd3c2b85556e967b89975349f59 100644 (file)
@@ -88,6 +88,35 @@ class ProbeManager;
  * depth-first traversal is performed (see descendants() in
  * SimObject.py). This has the effect of calling the method on the
  * parent node <i>before</i> its children.
+ *
+ * The python version of a SimObject class actually represents its Params
+ * structure which holds all its parameter settings and its name. When python
+ * needs to create a C++ instance of one of those classes, it uses the Params
+ * struct's create() method which returns one instance, set up with the
+ * parameters in the struct.
+ *
+ * When writing a SimObject class, there are three different cases as far as
+ * what you need to do to support the create() method, for hypothetical class
+ * Foo.
+ *
+ * If you have a constructor with a signature like this:
+ *
+ * Foo(const FooParams &)
+ *
+ * you don't have to do anything, a create method will be automatically
+ * defined which will call your constructor and return that instance. You
+ * should use this option most of the time.
+ *
+ * If you have a constructor with that signature but still want to define
+ * your own create method for some reason, you can do that by providing an
+ * alternative implementation which will override the default. It should have
+ * this signature:
+ *
+ * Foo *FooParams::create() const;
+ *
+ * If you don't have a constructor with that signature at all, then you must
+ * implement the create method with that signature which will build your
+ * object in some other way.
  */
 class SimObject : public EventManager, public Serializable, public Drainable,
                   public Stats::Group