Skip to content

Commit 289021a

Browse files
committed
[mlir] fix crash in PybindAdaptors.h
The constructor function was being defined without indicating its "__init__" name, which made it interpret it as a regular fuction rather than a constructor. When overload resolution failed, Pybind would attempt to print the arguments actually passed to the function, including "self", which is not initialized since the constructor couldn't be called. This would result in "__repr__" being called with "self" referencing an uninitialized MLIR C API object, which in turn would cause undefined behavior when attempting to print in C++. Fix this by specifying the correct name. This in turn uncovers the fact the the mechanism used by PybindAdaptors.h to bind constructors directly as "__init__" functions taking "self" is deprecated by Pybind. Instead, leverage the fact that the adaptors are intended for attrbutes/types that cannot have additional data members and are all ultimately instances of "PyAttribute"/"PyType" C++ class. In constructors of derived classes, construct an instance of the base class first, then steal its internal pointer to the C++ object to construct the instance of the derived class. On top of that, the definition of the function was incorrectly indicated as the method on the "None" object instead of being the method of its parent class. This would result in a second problem when Pybind would attempt to print warnings pointing to the parent class since the "None" does not have a "__name__" field or its C API equivalent. Fix this by specifying the correct parent class by looking it up by name in the parent module. Reviewed By: stellaraccident Differential Revision: https://reviews.llvm.org/D117325
1 parent fd598e1 commit 289021a

File tree

1 file changed

+47
-10
lines changed

1 file changed

+47
-10
lines changed

mlir/include/mlir/Bindings/Python/PybindAdaptors.h

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,29 @@ class pure_subclass {
291291
py::object get_class() const { return thisClass; }
292292

293293
protected:
294+
/// Defers to the constructor of the superClass the configuration of the
295+
/// pybind11 object from the given arguments. Pybind11 has a special handling
296+
/// for constructors such that they don't accept a "self" reference, unlike
297+
/// Python "__init__" calls. Therefore, one cannot just call the "__init__" of
298+
/// the parent class, which would require access to "self". Instead, create an
299+
/// instance of the superclass and take its instance pointer to the base C++
300+
/// object to populate the instance pointer of the constructed object. Since
301+
/// we only deal with _pure_ subclasses, this should be sufficient as derived
302+
/// classes cannot have more data fields.
303+
template <typename... Args>
304+
static void deferToSuperclassConstructor(py::detail::value_and_holder &vh,
305+
py::object superClass,
306+
Args &&...args) {
307+
py::object super = superClass(std::forward<Args>(args)...);
308+
py::detail::type_info *ti =
309+
py::detail::get_type_info((PyTypeObject *)superClass.ptr());
310+
auto *instance = reinterpret_cast<py::detail::instance *>(super.ptr());
311+
312+
// Take ownership of the value pointer from the base class.
313+
vh.value_ptr() = instance->get_value_and_holder(ti, true).value_ptr();
314+
super.release();
315+
}
316+
294317
py::object superClass;
295318
py::object thisClass;
296319
};
@@ -320,12 +343,16 @@ class mlir_attribute_subclass : public pure_subclass {
320343
// Casting constructor. Note that defining an __init__ method is special
321344
// and not yet generalized on pure_subclass (it requires a somewhat
322345
// different cpp_function and other requirements on chaining to super
323-
// __init__ make it more awkward to do generally).
346+
// __init__ make it more awkward to do generally). It is marked as
347+
// `is_new_style_constructor` to suppress the deprecation warning from
348+
// pybind11 related to placement-new since we are not doing any allocation
349+
// here but relying on the superclass constructor that does "new-style"
350+
// allocation for pybind11.
324351
std::string captureTypeName(
325352
typeClassName); // As string in case if typeClassName is not static.
326353
py::cpp_function initCf(
327-
[superClass, isaFunction, captureTypeName](py::object self,
328-
py::object otherType) {
354+
[superClass, isaFunction, captureTypeName](
355+
py::detail::value_and_holder &vh, py::object otherType) {
329356
MlirAttribute rawAttribute = py::cast<MlirAttribute>(otherType);
330357
if (!isaFunction(rawAttribute)) {
331358
auto origRepr = py::repr(otherType).cast<std::string>();
@@ -334,9 +361,12 @@ class mlir_attribute_subclass : public pure_subclass {
334361
" (from " + origRepr + ")")
335362
.str());
336363
}
337-
superClass.attr("__init__")(self, otherType);
364+
pure_subclass::deferToSuperclassConstructor(vh, superClass,
365+
otherType);
338366
},
339-
py::arg("cast_from_type"), py::is_method(py::none()),
367+
py::name("__init__"), py::arg("cast_from_type"),
368+
py::is_method(scope.attr(typeClassName)),
369+
py::detail::is_new_style_constructor(),
340370
"Casts the passed type to this specific sub-type.");
341371
thisClass.attr("__init__") = initCf;
342372

@@ -371,12 +401,16 @@ class mlir_type_subclass : public pure_subclass {
371401
// Casting constructor. Note that defining an __init__ method is special
372402
// and not yet generalized on pure_subclass (it requires a somewhat
373403
// different cpp_function and other requirements on chaining to super
374-
// __init__ make it more awkward to do generally).
404+
// __init__ make it more awkward to do generally). It is marked as
405+
// `is_new_style_constructor` to suppress the deprecation warning from
406+
// pybind11 related to placement-new since we are not doing any allocation
407+
// here but relying on the superclass constructor that does "new-style"
408+
// allocation for pybind11.
375409
std::string captureTypeName(
376410
typeClassName); // As string in case if typeClassName is not static.
377411
py::cpp_function initCf(
378-
[superClass, isaFunction, captureTypeName](py::object self,
379-
py::object otherType) {
412+
[superClass, isaFunction, captureTypeName](
413+
py::detail::value_and_holder &vh, py::object otherType) {
380414
MlirType rawType = py::cast<MlirType>(otherType);
381415
if (!isaFunction(rawType)) {
382416
auto origRepr = py::repr(otherType).cast<std::string>();
@@ -385,9 +419,12 @@ class mlir_type_subclass : public pure_subclass {
385419
origRepr + ")")
386420
.str());
387421
}
388-
superClass.attr("__init__")(self, otherType);
422+
pure_subclass::deferToSuperclassConstructor(vh, superClass,
423+
otherType);
389424
},
390-
py::arg("cast_from_type"), py::is_method(py::none()),
425+
py::name("__init__"), py::arg("cast_from_type"),
426+
py::is_method(scope.attr(typeClassName)),
427+
py::detail::is_new_style_constructor(),
391428
"Casts the passed type to this specific sub-type.");
392429
thisClass.attr("__init__") = initCf;
393430

0 commit comments

Comments
 (0)