Skip to content

[mlir][python] C++ API demo #71133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mlir/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -607,11 +607,13 @@ if(MLIR_INCLUDE_TESTS)
ROOT_DIR "${MLIR_SOURCE_DIR}/test/python/lib"
SOURCES
PythonTestModule.cpp
PythonTestPass.cpp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pulls in mlir c++ libs (built-in and pass) but it's not depending on them here. It appears they just happen to be resolved via MLIRCAPIPythonTestDialect.

I don't think this is going to work, and there is not an easy fix. This is relying in the capi lib incidentally exporting symbols for its backing c++ API. That is dependent on how the project is built: it happens to be true in this dev tree but will not work with hidden visibility, like real packages use. The other option you may be tempted to do is add the things you need to PRIVATE_LINK_LIBS, but that is also fraught. Not only will it duplicate the backing library code in the extension, TypeID linkage will no longer be single, strongly rooted. This will create the dreaded vague linkage issues on Linux and has no path to work on Windows.

I don't have a good suggestion: this is why we did not get adventurous on some of this stuff.

PRIVATE_LINK_LIBS
LLVMSupport
EMBED_CAPI_LINK_LIBS
MLIRCAPIPythonTestDialect
)
set_source_files_properties(${MLIR_SOURCE_DIR}/test/python/lib/PythonTestPass.cpp PROPERTIES COMPILE_FLAGS -fno-rtti)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, anything linked into a C extension module that's being built using pybind cmake helpers. The problem is PythonPass, which inherits from mlir::Pass, will then "depend" on the RTTI for mlir::Pass1. So you can make this example work by compiling just PythonPass.cpp without RTTI2.

Footnotes

  1. See this godbolt where the typeinfo for B dereferences the typeinfo for A even though that typeinfo doesn't appear anywhere.

  2. Or you can make this example work by building MLIR with ENABLE_RTTI=ON but that's a non-starter for us (right...?).

endif()

################################################################################
Expand Down
6 changes: 6 additions & 0 deletions mlir/python/mlir/dialects/python_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ def register_python_test_dialect(context, load=True):
from .._mlir_libs import _mlirPythonTest

_mlirPythonTest.register_python_test_dialect(context, load)


def register_python_test_pass_demo_pass(func):
from .._mlir_libs import _mlirPythonTest

_mlirPythonTest.register_python_test_pass_demo_pass(func)
35 changes: 35 additions & 0 deletions mlir/test/python/dialects/python_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import mlir.dialects.python_test as test
import mlir.dialects.tensor as tensor
import mlir.dialects.arith as arith
from mlir.passmanager import PassManager


def run(f):
Expand Down Expand Up @@ -551,3 +552,37 @@ def testInferTypeOpInterface():
two_operands = test.InferResultsVariadicInputsOp(single=zero, doubled=zero)
# CHECK: f32
print(two_operands.result.type)


# CHECK-LABEL: testPythonPassDemo
@run
def testPythonPassDemo():
def print_ops(op):
print(op.name)

module = """
module {
func.func @main() {
%memref = memref.alloca() : memref<1xi64>
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : i64
memref.store %c1, %memref[%c0] : memref<1xi64>
%u_memref = memref.cast %memref : memref<1xi64> to memref<*xi64>
return
}
}
"""

# CHECK: memref.alloca
# CHECK: arith.constant
# CHECK: arith.constant
# CHECK: memref.store
# CHECK: memref.cast
# CHECK: func.return
# CHECK: func.func
# CHECK: builtin.module
with Context() as ctx, Location.unknown():
test.register_python_test_dialect(ctx)
test.register_python_test_pass_demo_pass(print_ops)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems like a dangerous pattern to me: what happens when print_ops goes out of scope? (I mean it can't here, but you're registering a PyObject so it could...)

Also can you write in the same file a second run of the pipeline with a different print_ops which would print with a prefix for example? I suspect the registration being global that won't work...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems like a dangerous pattern to me: what happens when print_ops goes out of scope? (I mean it can't here, but you're registering a PyObject so it could...)

The bandaid is to put print_ops into threading.local() right?

Also can you write in the same file a second run of the pipeline with a different print_ops which would print with a prefix for example? I suspect the registration being global that won't work...

Yea sure but you can't register a single pass (in asserts mode...) multiple times anyway so we shouldn't expect that to work "afortiori".

mlir_module = Module.parse(module)
PassManager.parse("builtin.module(python-pass-demo)").run(mlir_module.operation)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is intended as a demo, can you make it into its own file, with extensive documentation: "example style"

2 changes: 1 addition & 1 deletion mlir/test/python/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ set(LLVM_OPTIONAL_SOURCES
PythonTestCAPI.cpp
PythonTestDialect.cpp
PythonTestModule.cpp
PythonTestPass.cpp
)

add_mlir_library(MLIRPythonTestDialect
Expand Down Expand Up @@ -29,4 +30,3 @@ add_mlir_public_c_api_library(MLIRCAPIPythonTestDialect
MLIRCAPIIR
MLIRPythonTestDialect
)

6 changes: 6 additions & 0 deletions mlir/test/python/lib/PythonTestModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//

#include "PythonTestCAPI.h"
#include "PythonTestPass.h"

#include "mlir-c/BuiltinAttributes.h"
#include "mlir-c/BuiltinTypes.h"
#include "mlir-c/IR.h"
Expand Down Expand Up @@ -34,6 +36,10 @@ PYBIND11_MODULE(_mlirPythonTest, m) {
},
py::arg("context"), py::arg("load") = true);

m.def("register_python_test_pass_demo_pass", [](py::function func) {
registerPythonTestPassDemoPassWithFunc(func.ptr());
});

mlir_attribute_subclass(m, "TestAttr",
mlirAttributeIsAPythonTestTestAttribute)
.def_classmethod(
Expand Down
53 changes: 53 additions & 0 deletions mlir/test/python/lib/PythonTestPass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//===- PythonTestPassDemo.cpp -----------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "PythonTestPass.h"

#include "mlir-c/Bindings/Python/Interop.h"
#include "mlir/CAPI/IR.h"
#include "mlir/IR/BuiltinDialect.h"
#include "mlir/Pass/Pass.h"

using namespace mlir;

namespace {

struct PythonTestPassDemo
: public PassWrapper<PythonTestPassDemo, OperationPass<ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(PythonTestPassDemo)

PythonTestPassDemo(PyObject *func) : func(func) {}
StringRef getArgument() const final { return "python-pass-demo"; }

void runOnOperation() override {
this->getOperation()->walk([this](Operation *op) {
PyObject *mlirModule =
PyImport_ImportModule(MAKE_MLIR_PYTHON_QUALNAME("ir"));
PyObject *cAPIFactory = PyObject_GetAttrString(
PyObject_GetAttrString(mlirModule, "Operation"),
MLIR_PYTHON_CAPI_FACTORY_ATTR);
PyObject *opApiObject = PyObject_CallFunction(
cAPIFactory, "(O)", mlirPythonOperationToCapsule(wrap(op)));
(void)PyObject_CallFunction(func, "(O)", opApiObject);
Py_DECREF(opApiObject);
});
}

PyObject *func;
};

std::unique_ptr<OperationPass<ModuleOp>>
createPythonTestPassDemoPassWithFunc(PyObject *func) {
return std::make_unique<PythonTestPassDemo>(func);
}

} // namespace

void registerPythonTestPassDemoPassWithFunc(PyObject *func) {
registerPass([func]() { return createPythonTestPassDemoPassWithFunc(func); });
}
16 changes: 16 additions & 0 deletions mlir/test/python/lib/PythonTestPass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//===- PythonTestPassDemo.h ---------------------------------------*- C -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef MLIR_TEST_PYTHON_PASS_PYTHONTESTCAPI_H
#define MLIR_TEST_PYTHON_PASS_PYTHONTESTCAPI_H

#include <Python.h>

void registerPythonTestPassDemoPassWithFunc(PyObject *func);

#endif