GitOrigin-RevId: 5b45bded1d
tags/v1.6.0
| @@ -0,0 +1,7 @@ | |||
| # MegEngine is Licensed under the Apache License, Version 2.0 (the "License") | |||
| # | |||
| # Copyright (c) 2014-2021 Megvii Inc. All rights reserved. | |||
| # | |||
| # Unless required by applicable law or agreed to in writing, | |||
| # software distributed under the License is distributed on an | |||
| # "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| @@ -0,0 +1,34 @@ | |||
| # MegEngine is Licensed under the Apache License, Version 2.0 (the "License") | |||
| # | |||
| # Copyright (c) 2014-2021 Megvii Inc. All rights reserved. | |||
| # | |||
| # Unless required by applicable law or agreed to in writing, | |||
| # software distributed under the License is distributed on an | |||
| # "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| from typing import Dict | |||
| from ...core._imperative_rt import OpDef | |||
| from ...core.ops import builtin | |||
| from ...version import __version__ | |||
| OPDEF_PARAM_LOADER = {} | |||
| def get_opdef_state(obj: OpDef) -> Dict: | |||
| state = obj.__getstate__() | |||
| state["type"] = type(obj) | |||
| state["version"] = __version__ | |||
| return state | |||
| def load_opdef_from_state(state: Dict) -> OpDef: | |||
| assert "type" in state and issubclass(state["type"], OpDef) | |||
| assert "version" in state | |||
| opdef_type = state.pop("type") | |||
| if opdef_type in OPDEF_PARAM_LOADER: | |||
| loader = OPDEF_PARAM_LOADER[opdef_type] | |||
| state = loader(state) | |||
| state.pop("version") | |||
| opdef_obj = opdef_type() | |||
| opdef_obj.__setstate__(state) | |||
| return opdef_obj | |||
| @@ -0,0 +1,41 @@ | |||
| /** | |||
| * \file imperative/python/src/module_trace.cpp | |||
| * MegEngine is Licensed under the Apache License, Version 2.0 (the "License") | |||
| * | |||
| * Copyright (c) 2014-2021 Megvii Inc. All rights reserved. | |||
| * | |||
| * Unless required by applicable law or agreed to in writing, | |||
| * software distributed under the License is distributed on an | |||
| * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
| * implied. | |||
| */ | |||
| #include "./module_trace.h" | |||
| #include "./helper.h" // include op pybind11 caster | |||
| namespace py = pybind11; | |||
| namespace mgb::imperative::python { | |||
| apply_result_t apply_module_trace(ApplyContext& ctx) { | |||
| apply_result_t outputs; | |||
| auto args = py::tuple(ctx.nargs + 1); | |||
| args[0] = py::cast(ctx.op); | |||
| for (size_t i = 0; i < ctx.nargs; i++) { | |||
| args[i + 1] = TensorWrapper::make(ctx.args[i]->shared_from_this()); | |||
| } | |||
| auto pyout = PyObject_Call(cpp_apply_module_trace, args.ptr(), nullptr); | |||
| if (!pyout) throw py::error_already_set(); | |||
| auto ret = py::reinterpret_steal<py::object>(pyout); | |||
| // assumption: python function always returns PyList | |||
| auto tup = py::reinterpret_borrow<py::list>(ret); | |||
| for (auto i = 0; i < tup.size(); i++) { | |||
| auto tw = TensorWrapper::try_cast(tup[i].ptr()); | |||
| outputs.emplace_back(tw->m_tensor); | |||
| } | |||
| return outputs; | |||
| } | |||
| } // namespace mgb::imperative::python | |||
| @@ -0,0 +1,20 @@ | |||
| /** | |||
| * \file imperative/python/src/module_trace.h | |||
| * MegEngine is Licensed under the Apache License, Version 2.0 (the "License") | |||
| * | |||
| * Copyright (c) 2014-2021 Megvii Inc. All rights reserved. | |||
| * | |||
| * Unless required by applicable law or agreed to in writing, | |||
| * software distributed under the License is distributed on an | |||
| * "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| */ | |||
| #pragma once | |||
| #include "./tensor.h" | |||
| namespace mgb::imperative::python { | |||
| apply_result_t apply_module_trace(ApplyContext& ctx); | |||
| } // namespace mgb::imperative::python | |||
| @@ -88,6 +88,19 @@ PyObject* py_new_generic(PyTypeObject* type, PyObject*, PyObject*) { | |||
| return obj; | |||
| } | |||
| template<typename T, typename SNIFAE=void> | |||
| struct serialization { | |||
| static T load(py::object obj) { | |||
| return py::cast<T>(obj); | |||
| } | |||
| template<typename U, | |||
| typename = std::enable_if_t<std::is_same_v<T, std::decay_t<U>>>> | |||
| static py::object dump(U&& t) { | |||
| return py::cast(std::forward<U>(t)); | |||
| } | |||
| }; | |||
| template<typename T> | |||
| void py_dealloc_generic(PyObject* obj) { | |||
| reinterpret_cast<T*>(obj)->op.reset(); | |||
| @@ -127,6 +140,13 @@ struct PyOpDef { | |||
| static PyGetSetDef py_getsetters[]; | |||
| static Py_hash_t tp_hash(PyObject *obj); | |||
| static PyObject* tp_richcompare(PyObject *self, PyObject *other, int op); | |||
| static PyObject* py_repr(PyObject* self) { | |||
| return py::cast( | |||
| reinterpret_cast<PyOpDef*>(self)->op->make_name()) | |||
| .release() | |||
| .ptr(); | |||
| } | |||
| }; | |||
| PyTypeObject PyOpType(OpDef); | |||
| std::unordered_map<mgb::Typeinfo*, PyTypeObject*> PyOp(OpDef)::ctype2pytype; | |||
| @@ -191,6 +211,13 @@ struct EnumWrapper { | |||
| std::string(name) + "." + reinterpret_cast<EnumWrapper*>(self)->to_string()) | |||
| .release().ptr(); | |||
| } | |||
| static PyObject* py_dump(PyObject* self) { | |||
| return py::cast(reinterpret_cast<EnumWrapper*>(self)->to_string()) | |||
| .release() | |||
| .ptr(); | |||
| } | |||
| static PyObject* tp_richcompare(PyObject *self, PyObject *other, int op) { | |||
| if (op == Py_EQ || op == Py_NE) { | |||
| T lhs, rhs; | |||
| @@ -279,6 +306,19 @@ struct BitCombinedEnumWrapper { | |||
| reinterpret_cast<BitCombinedEnumWrapper*>(self)->to_string()) | |||
| .release().ptr(); | |||
| } | |||
| static PyObject* py_dump(PyObject* self) { | |||
| std::vector<std::string> result; | |||
| auto value = reinterpret_cast<BitCombinedEnumWrapper*>(self)->value; | |||
| uint32_t value_int = static_cast<uint32_t>(value); | |||
| for (uint32_t i = 0; i < 32; i++) { | |||
| if (value_int >> i & 1) { | |||
| result.push_back(members[i]); | |||
| } | |||
| } | |||
| return py::tuple(py::cast(result)).release().ptr(); | |||
| } | |||
| static PyObject* py_or(PyObject* self, PyObject* other) { | |||
| if(!(self->ob_type == other->ob_type)){ | |||
| return PyErr_Format( | |||
| @@ -326,6 +366,24 @@ struct BitCombinedEnumWrapper { | |||
| return false; | |||
| } | |||
| } | |||
| if (py::isinstance<py::tuple>(src)) { | |||
| auto params = py::cast<std::vector<std::string>>(src); | |||
| bool first = true; | |||
| for (auto s : params){ | |||
| auto&& iter = mem2value.find(normalize_enum(s)); | |||
| if (iter != mem2value.end()) { | |||
| if (first) { | |||
| value = iter->second; | |||
| first = false; | |||
| } else { | |||
| value |= iter->second; | |||
| } | |||
| } else { | |||
| return false; | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| if (py::isinstance<py::int_>(obj)) { | |||
| auto v = py::cast<std::underlying_type_t<T>>(src); | |||
| if(v > EnumTrait<T>::max) { | |||
| @@ -351,6 +409,25 @@ struct BitCombinedEnumWrapper { | |||
| } | |||
| }; | |||
| template<typename T> | |||
| struct serialization<T, | |||
| std::enable_if_t<std::is_enum_v<std::decay_t<T>>>> { | |||
| static T load(py::object obj) { | |||
| auto caster = pybind11::detail::type_caster<T>(); | |||
| if (caster.load(obj, true)) { | |||
| return caster; | |||
| } else { | |||
| PyErr_SetString(PyExc_RuntimeError, | |||
| "load faild \n"); | |||
| return caster; | |||
| } | |||
| } | |||
| static py::object dump(T t) { | |||
| return py::cast(t).attr("dump")(); | |||
| } | |||
| }; | |||
| void _init_py_op_def(py::module m) { | |||
| using py_op = PyOp(OpDef); | |||
| auto& py_type = PyOpType(OpDef); | |||
| @@ -363,6 +440,7 @@ void _init_py_op_def(py::module m) { | |||
| py_type.tp_hash = PyOp(OpDef)::tp_hash; | |||
| py_type.tp_richcompare = PyOp(OpDef)::tp_richcompare; | |||
| py_type.tp_getset = py_op::py_getsetters; | |||
| py_type.tp_repr = py_op::py_repr; | |||
| mgb_assert(PyType_Ready(&py_type) >= 0); | |||
| m.add_object("OpDef", reinterpret_cast<PyObject*>(&py_type)); | |||
| } | |||
| @@ -451,18 +451,11 @@ public: | |||
| template<typename... Args> | |||
| static PyObject* cnew(Args&&... args) { | |||
| auto* pytype = type().operator->(); | |||
| auto* self = pytype->tp_alloc(pytype, 0); | |||
| auto* inst = reinterpret_cast<wrap_t*>(self)->inst(); | |||
| if constexpr (has_vectorcall && tp_vectorcall::valid) { | |||
| reinterpret_cast<wrap_t*>(self)->vectorcall_slot = &tp_vectorcall::template impl<>; | |||
| } | |||
| new(inst) T(std::forward<Args>(args)...); | |||
| return self; | |||
| return cnew_with_type(pytype, std::forward<Args>(args)...); | |||
| } | |||
| template<typename... Args> | |||
| static PyObject* cnew_with_type(PyTypeObject* pytype, Args&&... args) { | |||
| auto* self = pytype->tp_alloc(pytype, 0); | |||
| auto* inst = reinterpret_cast<wrap_t*>(self)->inst(); | |||
| if constexpr (has_vectorcall && tp_vectorcall::valid) { | |||
| @@ -20,6 +20,7 @@ | |||
| #include "./tensor.h" | |||
| #include "./grad.h" | |||
| #include "./trace.h" | |||
| #include "./module_trace.h" | |||
| #include "./common.h" | |||
| #include "./numpy_dtypes.h" | |||
| #include "./graph_rt.h" | |||
| @@ -41,6 +42,7 @@ interpreter::Interpreter::Channel* interpreter_for_py; | |||
| PyObject *cpp_apply_with_tracing, *cpp_apply_const_with_tracing; | |||
| PyObject *cpp_apply_backward_varnode; | |||
| PyObject *cpp_apply_module_trace; | |||
| std::shared_ptr<Tensor> make_const(imperative::TensorPtr value) { | |||
| if (!(ApplyContext::global_enable & Tensor::Flags::TRACE)) { | |||
| @@ -70,6 +72,7 @@ std::shared_ptr<Tensor> make_const(imperative::TensorPtr value) { | |||
| REGISTE_APPLY_FUNC(cpp_apply_with_tracing) | |||
| REGISTE_APPLY_FUNC(cpp_apply_const_with_tracing) | |||
| REGISTE_APPLY_FUNC(cpp_apply_backward_varnode) | |||
| REGISTE_APPLY_FUNC(cpp_apply_module_trace) | |||
| #undef REGISTE_APPLY_FUNC | |||
| @@ -79,6 +82,14 @@ Tensor::flags_t ApplyContext::global_enable = 0; | |||
| void set_tracing() { ApplyContext::global_enable |= Tensor::Flags::TRACE; } | |||
| void unset_tracing() { ApplyContext::global_enable &= ~Tensor::Flags::TRACE; } | |||
| void set_module_tracing() { ApplyContext::global_enable |= Tensor::Flags::MODULE_TRACE; } | |||
| void unset_module_tracing() { ApplyContext::global_enable &= ~Tensor::Flags::MODULE_TRACE; } | |||
| bool is_tracing_module() { | |||
| return ApplyContext::global_enable & Tensor::Flags::MODULE_TRACE; | |||
| } | |||
| bool skip_tracing = false; | |||
| apply_result_t apply(ApplyContext& ctx) { | |||
| @@ -117,6 +128,11 @@ apply_result_t apply(ApplyContext& ctx) { | |||
| return ret; | |||
| } | |||
| if (flags & Tensor::Flags::MODULE_TRACE) { | |||
| return apply_module_trace(ctx); | |||
| } | |||
| if (flags & Tensor::Flags::TRACE) { | |||
| return apply_trace(ctx); | |||
| } else { | |||
| @@ -310,6 +326,21 @@ REGISTE_TENSORWRAPPER_FUNC(bool, recording) | |||
| #undef REGISTE_TENSORWRAPPER_FUNC | |||
| PyObject* TensorWrapper::module_trace_info() { | |||
| if (!m_tensor->m_module_trace_info.ptr()) { | |||
| PyErr_SetString(PyExc_AttributeError, | |||
| "Has no attribute named \'_NodeMixin__node\', please " | |||
| "set it first"); | |||
| return nullptr; | |||
| } | |||
| return m_tensor->m_module_trace_info.inc_ref().ptr(); | |||
| } | |||
| void TensorWrapper::set_module_trace_info(PyObject* obj) { | |||
| m_tensor->m_module_trace_info = py::reinterpret_borrow<py::object>(obj); | |||
| } | |||
| #define REGISTE_TENSORWRAPPER_PYOBJECT_FUNC(member) \ | |||
| PyObject* TensorWrapper::member() { \ | |||
| @@ -495,7 +526,9 @@ void TensorWrapper::reset(PyObject* tensor) { | |||
| } | |||
| std::string user_custom_name = m_tensor->user_custom_name; | |||
| std::string automatic_name = m_tensor->automatic_name; | |||
| auto module_trace_info = m_tensor->m_module_trace_info; | |||
| m_tensor = t->m_tensor; | |||
| m_tensor->m_module_trace_info = module_trace_info; | |||
| m_tensor->user_custom_name = user_custom_name; | |||
| m_tensor->automatic_name = automatic_name; | |||
| } | |||
| @@ -856,6 +889,7 @@ void init_tensor(py::module m) { | |||
| .def_getset<&TensorWrapper::trace_mixin_info, &TensorWrapper::set_trace_mixin_info>("_trace_mixin_info") | |||
| .def_getset<&TensorWrapper::user_custom_name, &TensorWrapper::set_user_custom_name>("c_name") | |||
| .def_getset<&TensorWrapper::automatic_name, &TensorWrapper::set_automatic_name>("_name") | |||
| .def_getset<&TensorWrapper::module_trace_info, &TensorWrapper::set_module_trace_info>("_NodeMixin__node") | |||
| .finalize(); | |||
| if (!tensor_type) throw py::error_already_set(); | |||
| py::setattr(m, "Tensor", tensor_type); | |||
| @@ -998,7 +1032,7 @@ void init_tensor(py::module m) { | |||
| m.def("set_cpp_apply_with_tracing", &set_cpp_apply_with_tracing); | |||
| m.def("set_cpp_apply_const_with_tracing", &set_cpp_apply_const_with_tracing); | |||
| m.def("set_cpp_apply_backward_varnode", &set_cpp_apply_backward_varnode); | |||
| m.def("set_cpp_apply_module_trace", &set_cpp_apply_module_trace); | |||
| m.attr("skip_tracing") = &skip_tracing; | |||
| py::class_<SharedHandle>(m, "SharedHandle") | |||
| @@ -1016,6 +1050,9 @@ void init_tensor(py::module m) { | |||
| m.def("set_allow_higher_order_directive", [](bool value){ | |||
| GradKey::allow_higher_order_directive = value; | |||
| }); | |||
| m.def("set_module_tracing", &set_module_tracing); | |||
| m.def("unset_module_tracing", &unset_module_tracing); | |||
| m.def("is_tracing_module", &is_tracing_module); | |||
| } | |||
| #undef MGE_PY_INTERFACE | |||
| @@ -96,6 +96,7 @@ struct Tensor : std::enable_shared_from_this<Tensor>, NonCopyableObj { | |||
| static constexpr flags_t SCALAR = 1; | |||
| static constexpr flags_t GRAD = 1 << 1; | |||
| static constexpr flags_t TRACE = 1 << 2; | |||
| static constexpr flags_t MODULE_TRACE = 1 << 3; | |||
| }; | |||
| flags_t m_flags = 0; | |||
| @@ -106,6 +107,7 @@ struct Tensor : std::enable_shared_from_this<Tensor>, NonCopyableObj { | |||
| std::string user_custom_name; | |||
| std::string automatic_name; | |||
| cg::VarNode* m_var; | |||
| pybind11::object m_module_trace_info; | |||
| using Handle = interpreter::Interpreter::Handle; | |||
| @@ -158,10 +160,10 @@ struct TensorWrapper { | |||
| using wrap_t = pyext17::wrap<TensorWrapper>; | |||
| friend wrap_t; | |||
| inline static TensorWrapper* cast(PyObject* op) {return reinterpret_cast<wrap_t*>(op)->inst();} | |||
| inline static TensorWrapper* try_cast(PyObject* op) { | |||
| if (!wrap_t::type().isinstance(op)) return nullptr; | |||
| return cast(op); | |||
| inline static TensorWrapper* cast(PyObject* obj) {return reinterpret_cast<wrap_t*>(obj)->inst();} | |||
| inline static TensorWrapper* try_cast(PyObject* obj) { | |||
| if (!wrap_t::type().isinstance(obj)) return nullptr; | |||
| return cast(obj); | |||
| } | |||
| inline ObjectPtr<TensorWrapper, pybind11::handle> self() {return wrap_t::pycast(this);} | |||
| @@ -206,6 +208,8 @@ struct TensorWrapper { | |||
| void set_compiled_info(PyObject *); | |||
| PyObject* trace_mixin_info(); | |||
| void set_trace_mixin_info(PyObject *); | |||
| PyObject* module_trace_info(); | |||
| void set_module_trace_info(PyObject *); | |||
| PyObject* user_custom_name(); | |||
| void set_user_custom_name(PyObject *); | |||
| PyObject* automatic_name(); | |||
| @@ -331,6 +335,7 @@ void init_tensor(pybind11::module); | |||
| extern PyObject *cpp_apply_with_tracing; | |||
| extern PyObject *cpp_apply_backward_varnode; | |||
| extern PyObject *cpp_apply_module_trace; | |||
| } // namespace mgb::imperative::python | |||
| @@ -14,6 +14,11 @@ import numpy as np | |||
| import megengine as mge | |||
| from megengine import Parameter, Tensor | |||
| from megengine.core.ops import builtin | |||
| from megengine.experimental.traced_module.serialization import ( | |||
| get_opdef_state, | |||
| load_opdef_from_state, | |||
| ) | |||
| def test_tensor_serialization(): | |||
| @@ -86,3 +91,25 @@ def test_compatibility(): | |||
| test_old_tensor("tensor_v1_1.mge") | |||
| test_old_tensor("tensor_v1_2.mge") | |||
| def test_opdef_serialization(): | |||
| with TemporaryFile() as f: | |||
| x = builtin.Elemwise(mode="Add") | |||
| pickle.dump(get_opdef_state(x), f) | |||
| f.seek(0) | |||
| load_x = load_opdef_from_state(pickle.load(f)) | |||
| assert x == load_x | |||
| with TemporaryFile() as f: | |||
| x = builtin.Convolution(stride_h=9, compute_mode="float32") | |||
| x.strategy = ( | |||
| builtin.Convolution.Strategy.PROFILE | |||
| | builtin.Convolution.Strategy.HEURISTIC | |||
| | builtin.Convolution.Strategy.REPRODUCIBLE | |||
| ) | |||
| pickle.dump(get_opdef_state(x), f) | |||
| f.seek(0) | |||
| load_x = load_opdef_from_state(pickle.load(f)) | |||
| assert x.strategy == load_x.strategy | |||
| assert x == load_x | |||
| @@ -34,6 +34,7 @@ private: | |||
| void emit_class(); | |||
| void emit_py_init(); | |||
| void emit_py_getsetters(); | |||
| void emit_py_methods(); | |||
| Initproc emit_initproc(); | |||
| MgbOp& op; | |||
| @@ -133,9 +134,16 @@ void $0(PyTypeObject& py_type) { | |||
| if (firstOccur) { | |||
| os << tgfmt(R"( | |||
| static PyMethodDef tp_methods[] = { | |||
| {const_cast<char*>("dump"), (PyCFunction)$enumTpl<$opClass::$enumClass>::py_dump, METH_NOARGS, NULL}, | |||
| {NULL} /* Sentinel */ | |||
| }; | |||
| )", &ctx); | |||
| os << tgfmt(R"( | |||
| static PyType_Slot slots[] = { | |||
| {Py_tp_repr, (void*)$enumTpl<$opClass::$enumClass>::py_repr}, | |||
| {Py_tp_richcompare, (void*)$enumTpl<$opClass::$enumClass>::tp_richcompare}, | |||
| {Py_tp_methods, tp_methods}, | |||
| )", &ctx); | |||
| if (attr->getEnumCombinedFlag()) { | |||
| // only bit combined enum could new instance because bitwise operation, | |||
| @@ -212,17 +220,62 @@ Initproc OpDefEmitter::emit() { | |||
| emit_class(); | |||
| emit_py_init(); | |||
| emit_py_getsetters(); | |||
| emit_py_methods(); | |||
| return emit_initproc(); | |||
| } | |||
| void OpDefEmitter::emit_class() { | |||
| auto&& className = op.getCppClassName(); | |||
| std::string method_defs; | |||
| std::vector<std::string> body; | |||
| llvm::for_each(op.getMgbAttributes(), [&](auto&& attr) { | |||
| body.push_back(formatv(R"( | |||
| {{"{0}", serialization<decltype(opdef.{0})>::dump(opdef.{0})})" | |||
| , attr.name)); | |||
| }); | |||
| method_defs += formatv(R"( | |||
| static PyObject* getstate(PyObject* self, PyObject*) {{ | |||
| auto& opdef = reinterpret_cast<PyOp({0})*>(self)->inst(); | |||
| static_cast<void>(opdef); | |||
| std::unordered_map<std::string, py::object> state {{ | |||
| {1} | |||
| }; | |||
| return py::cast(state).release().ptr(); | |||
| })", className, llvm::join(body, ",")); | |||
| body.clear(); | |||
| llvm::for_each(op.getMgbAttributes(), [&](auto&& attr) { | |||
| body.push_back(formatv(R"( | |||
| {{ | |||
| auto&& iter = state.find("{0}"); | |||
| if (iter != state.end()) { | |||
| opdef.{0} = serialization<decltype(opdef.{0})>::load(iter->second); | |||
| } | |||
| })", attr.name)); | |||
| }); | |||
| method_defs += formatv(R"( | |||
| static PyObject* setstate(PyObject* self, PyObject* args) {{ | |||
| PyObject* dict = PyTuple_GetItem(args, 0); | |||
| if (!dict) return NULL; | |||
| auto state = py::cast<std::unordered_map<std::string, py::object>>(dict); | |||
| auto& opdef = reinterpret_cast<PyOp({0})*>(self)->inst(); | |||
| static_cast<void>(opdef); | |||
| {1} | |||
| Py_RETURN_NONE; | |||
| })", className, llvm::join(body, "\n")); | |||
| os << tgfmt(R"( | |||
| PyOpDefBegin($_self) // { | |||
| static PyGetSetDef py_getsetters[]; | |||
| static PyMethodDef tp_methods[]; | |||
| $0 | |||
| static int py_init(PyObject *self, PyObject *args, PyObject *kwds); | |||
| // }; | |||
| PyOpDefEnd($_self) | |||
| )", &ctx); | |||
| )", &ctx, method_defs); | |||
| } | |||
| void OpDefEmitter::emit_py_init() { | |||
| @@ -302,6 +355,33 @@ PyGetSetDef PyOp($_self)::py_getsetters[] = { | |||
| )", &ctx, llvm::join(llvm::map_range(op.getMgbAttributes(), f), "\n ")); | |||
| } | |||
| void OpDefEmitter::emit_py_methods(){ | |||
| // generate methods | |||
| std::string method_defs; | |||
| std::vector<std::string> method_items; | |||
| { | |||
| auto&& className = op.getCppClassName(); | |||
| // generate getstate | |||
| method_items.push_back(formatv( | |||
| "{{const_cast<char*>(\"__getstate__\"), PyOp({0})::getstate, METH_NOARGS, \"{0} getstate\"},", | |||
| className)); | |||
| // generate setstate | |||
| method_items.push_back(formatv( | |||
| "{{const_cast<char*>(\"__setstate__\"), PyOp({0})::setstate, METH_VARARGS, \"{0} setstate\"},", | |||
| className)); | |||
| } | |||
| os << tgfmt(R"( | |||
| PyMethodDef PyOp($_self)::tp_methods[] = { | |||
| $0 | |||
| {NULL} /* Sentinel */ | |||
| }; | |||
| )", &ctx, llvm::join(method_items, "\n ")); | |||
| } | |||
| Initproc OpDefEmitter::emit_initproc() { | |||
| std::string initproc = formatv("_init_py_{0}", op.getCppClassName()); | |||
| std::string subclass_init_call; | |||
| @@ -321,6 +401,7 @@ void $0(py::module m) { | |||
| py_type.tp_dealloc = py_dealloc_generic<py_op>; | |||
| py_type.tp_new = py_new_generic<py_op>; | |||
| py_type.tp_init = py_op::py_init; | |||
| py_type.tp_methods = py_op::tp_methods; | |||
| py_type.tp_getset = py_op::py_getsetters; | |||
| mgb_assert(PyType_Ready(&py_type) >= 0); | |||
| $1 | |||