24.22.2. gem5 Python C++ interaction

The interaction uses the Python C extension interface https://docs.python.org/2/extending/extending.html interface through the pybind11 helper library: https://github.com/pybind/pybind11

The C++ executable both:

  • starts running the Python executable

  • provides Python classes written in C++ for that Python code to use

An example of this can be found at:

then gem5 magic SimObject class adds some crazy stuff on top of it further, is is a mess. In particular, it auto generates params/ headers. TODO: why is this mess needed at all? pybind11 seems to handle constructor arguments just fine:

Let’s study BadDevice for example:

src/dev/BadDevice.py defines devicename:

class BadDevice(BasicPioDevice):
    type = 'BadDevice'
    cxx_header = "dev/baddev.hh"
    devicename = Param.String("Name of device to error on")

The object is created in Python for example from src/dev/alpha/Tsunami.py as:

    fb = BadDevice(pio_addr=0x801fc0003d0, devicename='FrameBuffer')

Since BadDevice has no __init__ method, and neither BasicPioDevice, it all just falls through until the SimObject.__init__ constructor.

This constructor will loop through the inheritance chain and give the Python parameters to the C++ BadDeviceParams class as follows.

The auto-generated build/ARM/params/BadDevice.hh file defines BadDeviceParams in C++:

#ifndef __PARAMS__BadDevice__
#define __PARAMS__BadDevice__

class BadDevice;

#include <cstddef>
#include <string>

#include "params/BasicPioDevice.hh"

struct BadDeviceParams
    : public BasicPioDeviceParams
{
    BadDevice * create();
    std::string devicename;
};

#endif // __PARAMS__BadDevice__

and ./python/_m5/param_BadDevice.cc defines the param Python from C++ with pybind11:

namespace py = pybind11;

static void
module_init(py::module &m_internal)
{
    py::module m = m_internal.def_submodule("param_BadDevice");
    py::class_<BadDeviceParams, BasicPioDeviceParams, std::unique_ptr<BadDeviceParams, py::nodelete>>(m, "BadDeviceParams")
        .def(py::init<>())
        .def("create", &BadDeviceParams::create)
        .def_readwrite("devicename", &BadDeviceParams::devicename)
        ;

    py::class_<BadDevice, BasicPioDevice, std::unique_ptr<BadDevice, py::nodelete>>(m, "BadDevice")
        ;

}

static EmbeddedPyBind embed_obj("BadDevice", module_init, "BasicPioDevice");

src/dev/baddev.hh then uses the parameters on the constructor:

class BadDevice : public BasicPioDevice
{
  private:
    std::string devname;

  public:
    typedef BadDeviceParams Params;

  protected:
    const Params *
    params() const
    {
        return dynamic_cast<const Params *>(_params);
    }

  public:
     /**
      * Constructor for the Baddev Class.
      * @param p object parameters
      * @param a base address of the write
      */
    BadDevice(Params *p);

src/dev/baddev.cc then uses the parameter:

BadDevice::BadDevice(Params *p)
    : BasicPioDevice(p, 0x10), devname(p->devicename)
{
}

It has been found that this usage of pybind11 across hundreds of SimObject files accounted for 50% of the gem5 build time at one point: pybind11 accounts for 50% of gem5 build time.

To get a feeling of how SimObject objects are run, see: gem5 event queue AtomicSimpleCPU syscall emulation freestanding example analysis.

Bibliography:

Tested on gem5 08c79a194d1a3430801c04f37d13216cc9ec1da3.