Skip to content

Commit 78bd124

Browse files
committed
Revert "[mlir][python] Make the Context/Operation capsule creation methods work as documented. (#76010)"
This reverts commit bbc2976. This change seems to be at odds with the non-owning part semantics of MlirOperation in C API. Since downstream clients can only take and return MlirOperation, it does not sound correct to force all returns of MlirOperation transfer ownership. Specifically, this makes it impossible for downstreams to implement IR-traversing functions that, e.g., look at neighbors of an operation. The following patch triggers the exception, and there does not seem to be an alternative way for a downstream binding writer to express this: ``` diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp index 39757df..2ce640674245 100644 --- a/mlir/lib/Bindings/Python/IRCore.cpp +++ b/mlir/lib/Bindings/Python/IRCore.cpp @@ -3071,6 +3071,11 @@ void mlir::python::populateIRCore(py::module &m) { py::arg("successors") = py::none(), py::arg("regions") = 0, py::arg("loc") = py::none(), py::arg("ip") = py::none(), py::arg("infer_type") = false, kOperationCreateDocstring) + .def("_get_first_in_block", [](PyOperation &self) -> MlirOperation { + MlirBlock block = mlirOperationGetBlock(self.get()); + MlirOperation first = mlirBlockGetFirstOperation(block); + return first; + }) .def_static( "parse", [](const std::string &sourceStr, const std::string &sourceName, diff --git a/mlir/test/python/ir/operation.py b/mlir/test/python/ir/operation.py index f59b1a2..6b12b8da5c24 100644 --- a/mlir/test/python/ir/operation.py +++ b/mlir/test/python/ir/operation.py @@ -24,6 +24,25 @@ def expect_index_error(callback): except IndexError: pass +@run +def testCustomBind(): + ctx = Context() + ctx.allow_unregistered_dialects = True + module = Module.parse( + r""" + func.func @F1(%arg0: i32) -> i32 { + %1 = "custom.addi"(%arg0, %arg0) : (i32, i32) -> i32 + return %1 : i32 + } + """, + ctx, + ) + add = module.body.operations[0].regions[0].blocks[0].operations[0] + op = add.operation + # This will get a reference to itself. + f1 = op._get_first_in_block() + + # Verify iterator based traversal of the op/region/block hierarchy. # CHECK-LABEL: TEST: testTraverseOpRegionBlockIterators ```
1 parent cb3a893 commit 78bd124

File tree

4 files changed

+26
-129
lines changed

4 files changed

+26
-129
lines changed

mlir/lib/Bindings/Python/IRCore.cpp

+10-68
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ py::object PyMlirContext::createFromCapsule(py::object capsule) {
602602
MlirContext rawContext = mlirPythonCapsuleToContext(capsule.ptr());
603603
if (mlirContextIsNull(rawContext))
604604
throw py::error_already_set();
605-
return stealExternalContext(rawContext).releaseObject();
605+
return forContext(rawContext).releaseObject();
606606
}
607607

608608
PyMlirContext *PyMlirContext::createNewContextForInit() {
@@ -615,35 +615,18 @@ PyMlirContextRef PyMlirContext::forContext(MlirContext context) {
615615
auto &liveContexts = getLiveContexts();
616616
auto it = liveContexts.find(context.ptr);
617617
if (it == liveContexts.end()) {
618-
throw std::runtime_error(
619-
"Cannot use a context that is not owned by the Python bindings.");
618+
// Create.
619+
PyMlirContext *unownedContextWrapper = new PyMlirContext(context);
620+
py::object pyRef = py::cast(unownedContextWrapper);
621+
assert(pyRef && "cast to py::object failed");
622+
liveContexts[context.ptr] = unownedContextWrapper;
623+
return PyMlirContextRef(unownedContextWrapper, std::move(pyRef));
620624
}
621-
622625
// Use existing.
623626
py::object pyRef = py::cast(it->second);
624627
return PyMlirContextRef(it->second, std::move(pyRef));
625628
}
626629

627-
PyMlirContextRef PyMlirContext::stealExternalContext(MlirContext context) {
628-
py::gil_scoped_acquire acquire;
629-
auto &liveContexts = getLiveContexts();
630-
auto it = liveContexts.find(context.ptr);
631-
if (it != liveContexts.end()) {
632-
throw std::runtime_error(
633-
"Cannot transfer ownership of the context to Python "
634-
"as it is already owned by Python.");
635-
}
636-
637-
PyMlirContext *unownedContextWrapper = new PyMlirContext(context);
638-
// Note that the default return value policy on cast is automatic_reference,
639-
// which does not take ownership (delete will not be called).
640-
// Just be explicit.
641-
py::object pyRef =
642-
py::cast(unownedContextWrapper, py::return_value_policy::take_ownership);
643-
assert(pyRef && "cast to py::object failed");
644-
return PyMlirContextRef(unownedContextWrapper, std::move(pyRef));
645-
}
646-
647630
PyMlirContext::LiveContextMap &PyMlirContext::getLiveContexts() {
648631
static LiveContextMap liveContexts;
649632
return liveContexts;
@@ -1162,18 +1145,6 @@ PyOperationRef PyOperation::forOperation(PyMlirContextRef contextRef,
11621145
return PyOperationRef(existing, std::move(pyRef));
11631146
}
11641147

1165-
PyOperationRef PyOperation::stealExternalOperation(PyMlirContextRef contextRef,
1166-
MlirOperation operation) {
1167-
auto &liveOperations = contextRef->liveOperations;
1168-
auto it = liveOperations.find(operation.ptr);
1169-
if (it != liveOperations.end()) {
1170-
throw std::runtime_error(
1171-
"Cannot transfer ownership of the operation to Python "
1172-
"as it is already owned by Python.");
1173-
}
1174-
return createInstance(std::move(contextRef), operation, py::none());
1175-
}
1176-
11771148
PyOperationRef PyOperation::createDetached(PyMlirContextRef contextRef,
11781149
MlirOperation operation,
11791150
py::object parentKeepAlive) {
@@ -1345,8 +1316,7 @@ py::object PyOperation::createFromCapsule(py::object capsule) {
13451316
if (mlirOperationIsNull(rawOperation))
13461317
throw py::error_already_set();
13471318
MlirContext rawCtxt = mlirOperationGetContext(rawOperation);
1348-
return stealExternalOperation(PyMlirContext::forContext(rawCtxt),
1349-
rawOperation)
1319+
return forOperation(PyMlirContext::forContext(rawCtxt), rawOperation)
13501320
.releaseObject();
13511321
}
13521322

@@ -2578,16 +2548,6 @@ void mlir::python::populateIRCore(py::module &m) {
25782548
.def("_get_live_operation_count", &PyMlirContext::getLiveOperationCount)
25792549
.def("_clear_live_operations", &PyMlirContext::clearLiveOperations)
25802550
.def("_get_live_module_count", &PyMlirContext::getLiveModuleCount)
2581-
.def_static("_testing_create_raw_context_capsule",
2582-
[]() {
2583-
// Creates an MlirContext not known to the Python bindings
2584-
// and puts it in a capsule. Used to test interop. Using
2585-
// this without passing it back to the capsule creation
2586-
// API will leak.
2587-
return py::reinterpret_steal<py::object>(
2588-
mlirPythonContextToCapsule(
2589-
mlirContextCreateWithThreading(false)));
2590-
})
25912551
.def_property_readonly(MLIR_PYTHON_CAPI_PTR_ATTR,
25922552
&PyMlirContext::getCapsule)
25932553
.def(MLIR_PYTHON_CAPI_FACTORY_ATTR, &PyMlirContext::createFromCapsule)
@@ -3013,7 +2973,8 @@ void mlir::python::populateIRCore(py::module &m) {
30132973
py::arg("binary") = false, kOperationPrintStateDocstring)
30142974
.def("print",
30152975
py::overload_cast<std::optional<int64_t>, bool, bool, bool, bool,
3016-
bool, py::object, bool>(&PyOperationBase::print),
2976+
bool, py::object, bool>(
2977+
&PyOperationBase::print),
30172978
// Careful: Lots of arguments must match up with print method.
30182979
py::arg("large_elements_limit") = py::none(),
30192980
py::arg("enable_debug_info") = false,
@@ -3085,25 +3046,6 @@ void mlir::python::populateIRCore(py::module &m) {
30853046
.def_property_readonly(MLIR_PYTHON_CAPI_PTR_ATTR,
30863047
&PyOperation::getCapsule)
30873048
.def(MLIR_PYTHON_CAPI_FACTORY_ATTR, &PyOperation::createFromCapsule)
3088-
.def_static(
3089-
"_testing_create_raw_capsule",
3090-
[](std::string sourceStr) {
3091-
// Creates a raw context and an operation via parsing the given
3092-
// source and returns them in a capsule. Error handling is
3093-
// minimal as this is purely intended for testing interop with
3094-
// operation creation from capsule functions.
3095-
MlirContext context = mlirContextCreateWithThreading(false);
3096-
MlirOperation op = mlirOperationCreateParse(
3097-
context, toMlirStringRef(sourceStr), toMlirStringRef("temp"));
3098-
if (mlirOperationIsNull(op)) {
3099-
mlirContextDestroy(context);
3100-
throw std::invalid_argument("Failed to parse");
3101-
}
3102-
return py::make_tuple(py::reinterpret_steal<py::object>(
3103-
mlirPythonContextToCapsule(context)),
3104-
py::reinterpret_steal<py::object>(
3105-
mlirPythonOperationToCapsule(op)));
3106-
})
31073049
.def_property_readonly("operation", [](py::object self) { return self; })
31083050
.def_property_readonly("opview", &PyOperation::createOpView)
31093051
.def_property_readonly(

mlir/lib/Bindings/Python/IRModule.h

+1-18
Original file line numberDiff line numberDiff line change
@@ -176,19 +176,8 @@ class PyMlirContext {
176176
static PyMlirContext *createNewContextForInit();
177177

178178
/// Returns a context reference for the singleton PyMlirContext wrapper for
179-
/// the given context. It is only valid to call this on an MlirContext that
180-
/// is already owned by the Python bindings. Typically this will be because
181-
/// it came in some fashion from createNewContextForInit(). However, it
182-
/// is also possible to explicitly transfer ownership of an existing
183-
/// MlirContext to the Python bindings via stealExternalContext().
179+
/// the given context.
184180
static PyMlirContextRef forContext(MlirContext context);
185-
186-
/// Explicitly takes ownership of an MlirContext that must not already be
187-
/// known to the Python bindings. Once done, the life-cycle of the context
188-
/// will be controlled by the Python bindings, and it will be destroyed
189-
/// when the reference count goes to zero.
190-
static PyMlirContextRef stealExternalContext(MlirContext context);
191-
192181
~PyMlirContext();
193182

194183
/// Accesses the underlying MlirContext.
@@ -617,12 +606,6 @@ class PyOperation : public PyOperationBase, public BaseContextObject {
617606
forOperation(PyMlirContextRef contextRef, MlirOperation operation,
618607
pybind11::object parentKeepAlive = pybind11::object());
619608

620-
/// Explicitly takes ownership of an operation that must not already be known
621-
/// to the Python bindings. Once done, the life-cycle of the operation
622-
/// will be controlled by the Python bindings.
623-
static PyOperationRef stealExternalOperation(PyMlirContextRef contextRef,
624-
MlirOperation operation);
625-
626609
/// Creates a detached operation. The operation must not be associated with
627610
/// any existing live operation.
628611
static PyOperationRef

mlir/test/python/ir/context_lifecycle.py

+2-43
Original file line numberDiff line numberDiff line change
@@ -45,46 +45,5 @@
4545
c4 = mlir.ir.Context()
4646
c4_capsule = c4._CAPIPtr
4747
assert '"mlir.ir.Context._CAPIPtr"' in repr(c4_capsule)
48-
# Because the context is already owned by Python, it cannot be created
49-
# a second time.
50-
try:
51-
c5 = mlir.ir.Context._CAPICreate(c4_capsule)
52-
except RuntimeError:
53-
pass
54-
else:
55-
raise AssertionError(
56-
"Should have gotten a RuntimeError when attempting to "
57-
"re-create an already owned context"
58-
)
59-
c4 = None
60-
c4_capsule = None
61-
gc.collect()
62-
assert mlir.ir.Context._get_live_count() == 0
63-
64-
# Use a private testing method to create an unowned context capsule and
65-
# import it.
66-
c6_capsule = mlir.ir.Context._testing_create_raw_context_capsule()
67-
c6 = mlir.ir.Context._CAPICreate(c6_capsule)
68-
assert mlir.ir.Context._get_live_count() == 1
69-
c6_capsule = None
70-
c6 = None
71-
gc.collect()
72-
assert mlir.ir.Context._get_live_count() == 0
73-
74-
# Also test operation import/export as it is tightly coupled to the context.
75-
(
76-
raw_context_capsule,
77-
raw_operation_capsule,
78-
) = mlir.ir.Operation._testing_create_raw_capsule("builtin.module {}")
79-
assert '"mlir.ir.Operation._CAPIPtr"' in repr(raw_operation_capsule)
80-
# Attempting to import an operation for an unknown context should fail.
81-
try:
82-
mlir.ir.Operation._CAPICreate(raw_operation_capsule)
83-
except RuntimeError:
84-
pass
85-
else:
86-
raise AssertionError("Expected exception for unknown context")
87-
88-
# Try again having imported the context.
89-
c7 = mlir.ir.Context._CAPICreate(raw_context_capsule)
90-
op7 = mlir.ir.Operation._CAPICreate(raw_operation_capsule)
48+
c5 = mlir.ir.Context._CAPICreate(c4_capsule)
49+
assert c4 is c5

mlir/test/python/ir/operation.py

+13
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,19 @@ def testOperationName():
844844
print(op.operation.name)
845845

846846

847+
# CHECK-LABEL: TEST: testCapsuleConversions
848+
@run
849+
def testCapsuleConversions():
850+
ctx = Context()
851+
ctx.allow_unregistered_dialects = True
852+
with Location.unknown(ctx):
853+
m = Operation.create("custom.op1").operation
854+
m_capsule = m._CAPIPtr
855+
assert '"mlir.ir.Operation._CAPIPtr"' in repr(m_capsule)
856+
m2 = Operation._CAPICreate(m_capsule)
857+
assert m2 is m
858+
859+
847860
# CHECK-LABEL: TEST: testOperationErase
848861
@run
849862
def testOperationErase():

0 commit comments

Comments
 (0)