-# Copyright (c) 2012 ARM Limited
+# Copyright (c) 2017 ARM Limited
# All rights reserved.
#
# The license below extends only to copyright in the software and shall
# Authors: Steve Reinhardt
# Nathan Binkert
# Andreas Hansson
+# Andreas Sandberg
import sys
from types import FunctionType, MethodType, ModuleType
+from functools import wraps
+import inspect
import m5
from m5.util import *
+from m5.util.pybind import *
# Have to import params up top since Param is referenced on initial
# load (when SimObject class references Param to create a class
code('{')
code.indent()
code('this->name = name_;')
- code('this->pyobj = NULL;')
code.dedent()
code('}')
# 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 }
+ init_keywords = {
+ 'abstract' : bool,
+ 'cxx_class' : str,
+ 'cxx_type' : str,
+ 'cxx_header' : str,
+ 'type' : str,
+ 'cxx_bases' : list,
+ 'cxx_exports' : list,
+ 'cxx_param_exports' : list,
+ }
# Attributes that can be set any time
keywords = { 'check' : FunctionType }
# filtered in __init__.
cls_dict = {}
value_dict = {}
+ cxx_exports = []
for key,val in dict.items():
+ try:
+ cxx_exports.append(getattr(val, "__pybind"))
+ except AttributeError:
+ pass
+
if public_value(key, val):
cls_dict[key] = val
else:
value_dict['abstract'] = False
if 'cxx_bases' not in value_dict:
value_dict['cxx_bases'] = []
+ if 'cxx_exports' not in value_dict:
+ value_dict['cxx_exports'] = cxx_exports
+ else:
+ value_dict['cxx_exports'] += cxx_exports
+ if 'cxx_param_exports' not in value_dict:
+ value_dict['cxx_param_exports'] = []
cls_dict['_value_dict'] = value_dict
cls = super(MetaSimObject, mcls).__new__(mcls, name, bases, cls_dict)
if 'type' in value_dict:
if isinstance(c, MetaSimObject):
bTotal += 1
if bTotal > 1:
- raise TypeError, "SimObjects do not support multiple inheritance"
+ raise TypeError, \
+ "SimObjects do not support multiple inheritance"
base = bases[0]
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
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
+ def pybind_predecls(cls, code):
+ code('#include "${{cls.cxx_header}}"')
- # 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):
+ def pybind_decl(cls, code):
class_path = cls.cxx_class.split('::')
- classname = class_path[-1]
- namespaces = class_path[:-1]
+ namespaces, classname = class_path[:-1], class_path[-1]
+ py_class_name = '_COLONS_'.join(class_path) if namespaces else \
+ classname;
# The 'local' attribute restricts us to the params declared in
# the object itself, not including inherited params (which
params = map(lambda (k, v): v, sorted(cls._params.local.items()))
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 <cstddef>
-using std::ptrdiff_t;
-''')
- code('%}')
- code()
+ code('''#include "pybind11/pybind11.h"
+#include "pybind11/stl.h"
- for param in params:
- param.swig_predecls(code)
- cls.export_method_swig_predecls(code)
+#include "params/$cls.hh"
+#include "python/pybind11/core.hh"
+#include "sim/init.hh"
+#include "sim/sim_object.hh"
- code()
- if cls._base:
- code('%import "python/m5/internal/param_${{cls._base}}.i"')
- code()
+#include "${{cls.cxx_header}}"
- 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;')
+ for param in params:
+ param.pybind_predecls(code)
- code()
- code('// stop swig from creating/wrapping default ctor/dtor')
- code('%nodefault $classname;')
- code('class $classname')
+ code('''namespace py = pybind11;
+
+static void
+module_init(py::module &m_internal)
+{
+ py::module m = m_internal.def_submodule("param_${cls}");
+''')
+ code.indent()
if cls._base:
- bases = [ cls._base.cxx_class ] + cls.cxx_bases
+ code('py::class_<${cls}Params, ${{cls._base.type}}Params, ' \
+ 'std::unique_ptr<${{cls}}Params, py::nodelete>>(' \
+ 'm, "${cls}Params")')
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('};')
+ code('py::class_<${cls}Params, ' \
+ 'std::unique_ptr<${cls}Params, py::nodelete>>(' \
+ 'm, "${cls}Params")')
- for ns in reversed(namespaces):
- code('} // namespace $ns')
+ code.indent()
+ if not hasattr(cls, 'abstract') or not cls.abstract:
+ code('.def(py::init<>())')
+ code('.def("create", &${cls}Params::create)')
+
+ param_exports = cls.cxx_param_exports + [
+ PyBindProperty(k)
+ for k, v in sorted(cls._params.local.items())
+ ] + [
+ PyBindProperty("port_%s_connection_count" % port.name)
+ for port in ports.itervalues()
+ ]
+ for exp in param_exports:
+ exp.export(code, "%sParams" % cls)
+
+ code(';')
+ code()
+ code.dedent()
+ bases = [ cls._base.cxx_class ] + cls.cxx_bases if cls._base else \
+ cls.cxx_bases
+ if bases:
+ base_str = ", ".join(bases)
+ code('py::class_<${{cls.cxx_class}}, ${base_str}, ' \
+ 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
+ 'm, "${py_class_name}")')
+ else:
+ code('py::class_<${{cls.cxx_class}}, ' \
+ 'std::unique_ptr<${{cls.cxx_class}}, py::nodelete>>(' \
+ 'm, "${py_class_name}")')
+ code.indent()
+ for exp in cls.cxx_exports:
+ exp.export(code, cls.cxx_class)
+ code(';')
+ code.dedent()
+ code()
+ code.dedent()
+ code('}')
code()
- code('%include "params/$cls.hh"')
+ code('static EmbeddedPyBind embed_obj("${0}", module_init, "${1}");',
+ cls, cls._base.type if cls._base else "")
# Generate the C++ declaration (.hh file) for this SimObject's
''')
+
+ # 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('''#include <string>''')
+
# A forward class declaration is sufficient since we are just
# declaring a pointer.
for ns in 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 <string>
-''')
for param in params:
param.cxx_predecls(code)
for port in ports.itervalues():
virtual ~SimObjectParams() {}
std::string name;
- PyObject *pyobj;
''')
+
for param in params:
param.cxx_decl(code)
for port in ports.itervalues():
def isSimObjectOrVector(value):
return False
+def cxxMethod(*args, **kwargs):
+ """Decorator to export C++ functions to Python"""
+
+ def decorate(func):
+ name = func.func_name
+ override = kwargs.get("override", False)
+ cxx_name = kwargs.get("cxx_name", name)
+
+ args, varargs, keywords, defaults = inspect.getargspec(func)
+ if varargs or keywords:
+ raise ValueError("Wrapped methods must not contain variable " \
+ "arguments")
+
+ # Create tuples of (argument, default)
+ if defaults:
+ args = args[:-len(defaults)] + zip(args[-len(defaults):], defaults)
+ # Don't include self in the argument list to PyBind
+ args = args[1:]
+
+
+ @wraps(func)
+ def cxx_call(self, *args, **kwargs):
+ ccobj = self.getCCObject()
+ return getattr(ccobj, name)(*args, **kwargs)
+
+ @wraps(func)
+ def py_call(self, *args, **kwargs):
+ return self.func(*args, **kwargs)
+
+ f = py_call if override else cxx_call
+ f.__pybind = PyBindMethod(name, cxx_name=cxx_name, args=args)
+
+ return f
+
+ if len(args) == 0:
+ return decorate
+ elif len(args) == 1 and len(kwargs) == 0:
+ return decorate(*args)
+ else:
+ raise TypeError("One argument and no kwargs, or only kwargs expected")
+
# This class holds information about each simobject parameter
# that should be displayed on the command line for use in the
# configuration system.
cxx_bases = [ "Drainable", "Serializable" ]
eventq_index = Param.UInt32(Parent.eventq_index, "Event Queue Index")
- @classmethod
- def export_method_swig_predecls(cls, code):
- code('''
-%include <std_string.i>
-
-%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();
-''')
+ cxx_exports = [
+ PyBindMethod("init"),
+ PyBindMethod("initState"),
+ PyBindMethod("memInvalidate"),
+ PyBindMethod("memWriteback"),
+ PyBindMethod("regStats"),
+ PyBindMethod("resetStats"),
+ PyBindMethod("regProbePoints"),
+ PyBindMethod("regProbeListeners"),
+ PyBindMethod("startup"),
+ ]
+
+ cxx_param_exports = [
+ PyBindProperty("name"),
+ ]
+
+ @cxxMethod
+ def loadState(self, cp):
+ """Load SimObject state from a checkpoint"""
+ pass
# Returns a dict of all the option strings that can be
# generated as command line options for this simobject instance
if keys in self._hr_values\
and keys in self._values\
- and not isinstance(self._values[keys], m5.proxy.BaseProxy):
+ 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,
# 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().
+ # methods exported to Python (e.g., init(), and startup())
if self._ccObject and hasattr(self._ccObject, attr):
return getattr(self._ccObject, attr)
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
+ # 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
def path(self):
if not self._parent:
return '<orphan %s>' % self.__class__
+ elif isinstance(self._parent, MetaSimObject):
+ return str(self.__class__)
+
ppath = self._parent.path()
if ppath == 'root':
return self._name
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)
+ '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
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()
assert isinstance(value, list)
vec = getattr(cc_params, param)
assert not len(vec)
- for v in value:
- vec.append(v)
+ # Some types are exposed as opaque types. They support
+ # the append operation unlike the automatically
+ # wrapped types.
+ if isinstance(vec, list):
+ setattr(cc_params, param, list(value))
+ else:
+ for v in value:
+ getattr(cc_params, param).append(v)
else:
setattr(cc_params, param, value)
# __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' ]
+__all__ = [
+ 'SimObject',
+ 'cxxMethod',
+ 'PyBindMethod',
+ 'PyBindProperty',
+]