[Mlir-commits] [mlir] [mlir][python] auto-locs (PR #151246)
Maksim Levental
llvmlistbot at llvm.org
Tue Jul 29 18:51:14 PDT 2025
https://github.com/makslevental updated https://github.com/llvm/llvm-project/pull/151246
>From 34d5f14cb2ba9677ce577c125e1d995fbb9166db Mon Sep 17 00:00:00 2001
From: max <maksim.levental at gmail.com>
Date: Tue, 29 Jul 2025 18:21:53 -0400
Subject: [PATCH] [mlir][python] auto-locs
---
mlir/lib/Bindings/Python/Globals.h | 66 +++++++++++
mlir/lib/Bindings/Python/IRCore.cpp | 139 ++++++++++++++++++++++--
mlir/lib/Bindings/Python/IRModule.h | 5 +-
mlir/lib/Bindings/Python/MainModule.cpp | 11 +-
mlir/test/python/ir/location.py | 30 ++++-
5 files changed, 233 insertions(+), 18 deletions(-)
diff --git a/mlir/lib/Bindings/Python/Globals.h b/mlir/lib/Bindings/Python/Globals.h
index 826a34a535176..0a4cf6a836e6b 100644
--- a/mlir/lib/Bindings/Python/Globals.h
+++ b/mlir/lib/Bindings/Python/Globals.h
@@ -10,6 +10,7 @@
#define MLIR_BINDINGS_PYTHON_GLOBALS_H
#include <optional>
+#include <regex>
#include <string>
#include <vector>
@@ -17,8 +18,12 @@
#include "mlir-c/IR.h"
#include "mlir/CAPI/Support.h"
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallVectorExtras.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
+#include "llvm/Support/Regex.h"
namespace mlir {
namespace python {
@@ -114,11 +119,72 @@ class PyGlobals {
std::optional<nanobind::object>
lookupOperationClass(llvm::StringRef operationName);
+ bool locTracebacksEnabled() {
+ nanobind::ft_lock_guard lock(mutex);
+ return locTracebackEnabled_;
+ }
+
+ void setLocTracebacksEnabled(bool value) {
+ nanobind::ft_lock_guard lock(mutex);
+ locTracebackEnabled_ = value;
+ }
+
+ bool locTracebackFramesLimit() {
+ nanobind::ft_lock_guard lock(mutex);
+ return locTracebackFramesLimit_;
+ }
+
+ void setLocTracebackFramesLimit(size_t value) {
+ nanobind::ft_lock_guard lock(mutex);
+ locTracebackFramesLimit_ = value;
+ }
+
+ void registerTracebackFileInclusion(llvm::StringRef file) {
+ nanobind::ft_lock_guard lock(mutex);
+ userTracebackIncludeFiles.insert(file);
+ userTracebackIncludeRegex.assign(
+ llvm::join(llvm::map_range(userTracebackIncludeFiles,
+ [](llvm::StringRef path) {
+ return "^" + llvm::Regex::escape(path);
+ }),
+ "|"));
+ }
+
+ void registerTracebackFileExclusion(llvm::StringRef file) {
+ nanobind::ft_lock_guard lock(mutex);
+ userTracebackExcludeFiles.insert(file);
+ userTracebackExcludeRegex.assign(
+ llvm::join(llvm::map_range(userTracebackExcludeFiles,
+ [](llvm::StringRef path) {
+ return "^" + llvm::Regex::escape(path);
+ }),
+ "|"));
+ }
+
+ bool isUserTracebackFilename(llvm::StringRef file) {
+ nanobind::ft_lock_guard lock(mutex);
+ if (!isUserTracebackFilenameCache.contains(file)) {
+ std::string fileStr = file.str();
+ isUserTracebackFilenameCache[file] =
+ !std::regex_search(fileStr, userTracebackIncludeRegex) ||
+ std::regex_search(fileStr, userTracebackExcludeRegex);
+ }
+ return isUserTracebackFilenameCache[file];
+ }
+
private:
static PyGlobals *instance;
nanobind::ft_mutex mutex;
+ bool locTracebackEnabled_ = false;
+ size_t locTracebackFramesLimit_ = 10;
+ llvm::DenseSet<llvm::StringRef> userTracebackIncludeFiles;
+ llvm::DenseSet<llvm::StringRef> userTracebackExcludeFiles;
+ std::regex userTracebackIncludeRegex;
+ std::regex userTracebackExcludeRegex;
+ llvm::StringMap<bool> isUserTracebackFilenameCache;
+
/// Module name prefixes to search under for dialect implementation modules.
std::vector<std::string> dialectSearchPrefixes;
/// Map of dialect namespace to external dialect class object.
diff --git a/mlir/lib/Bindings/Python/IRCore.cpp b/mlir/lib/Bindings/Python/IRCore.cpp
index 5feed95f96f53..621f50e77fddb 100644
--- a/mlir/lib/Bindings/Python/IRCore.cpp
+++ b/mlir/lib/Bindings/Python/IRCore.cpp
@@ -1523,7 +1523,7 @@ nb::object PyOperation::create(std::string_view name,
llvm::ArrayRef<MlirValue> operands,
std::optional<nb::dict> attributes,
std::optional<std::vector<PyBlock *>> successors,
- int regions, DefaultingPyLocation location,
+ int regions, PyLocation location,
const nb::object &maybeIp, bool inferType) {
llvm::SmallVector<MlirType, 4> mlirResults;
llvm::SmallVector<MlirBlock, 4> mlirSuccessors;
@@ -1627,7 +1627,7 @@ nb::object PyOperation::create(std::string_view name,
if (!operation.ptr)
throw nb::value_error("Operation creation failed");
PyOperationRef created =
- PyOperation::createDetached(location->getContext(), operation);
+ PyOperation::createDetached(location.getContext(), operation);
maybeInsertOperation(created, maybeIp);
return created.getObject();
@@ -1937,9 +1937,9 @@ nb::object PyOpView::buildGeneric(
std::optional<nb::list> resultTypeList, nb::list operandList,
std::optional<nb::dict> attributes,
std::optional<std::vector<PyBlock *>> successors,
- std::optional<int> regions, DefaultingPyLocation location,
+ std::optional<int> regions, PyLocation location,
const nb::object &maybeIp) {
- PyMlirContextRef context = location->getContext();
+ PyMlirContextRef context = location.getContext();
// Class level operation construction metadata.
// Operand and result segment specs are either none, which does no
@@ -2789,6 +2789,94 @@ class PyOpAttributeMap {
PyOperationRef operation;
};
+// bpo-40421 added PyFrame_GetLasti() to Python 3.11.0b1
+#if PY_VERSION_HEX < 0x030b00b1 && !defined(PYPY_VERSION)
+int PyFrame_GetLasti(PyFrameObject *frame) {
+#if PY_VERSION_HEX >= 0x030a00a7
+ // bpo-27129: Since Python 3.10.0a7, f_lasti is an instruction offset,
+ // not a bytes offset anymore. Python uses 16-bit "wordcode" (2 bytes)
+ // instructions.
+ if (frame->f_lasti < 0) {
+ return -1;
+ }
+ return frame->f_lasti * 2;
+#else
+ return frame->f_lasti;
+#endif
+}
+#endif
+
+std::optional<MlirLocation> tracebackToLocation(MlirContext ctx) {
+ size_t framesLimit = PyGlobals::get().locTracebackFramesLimit();
+ // We use a thread_local here mostly to avoid requiring a large amount of
+ // space.
+ thread_local std::vector<MlirLocation> frames;
+ frames.reserve(framesLimit);
+ size_t count = 0;
+
+ assert(PyGILState_Check());
+
+ if (!PyGlobals::get().locTracebacksEnabled())
+ return std::nullopt;
+
+ PyThreadState *tstate = PyThreadState_GET();
+
+ PyFrameObject *next;
+ for (PyFrameObject *pyFrame = PyThreadState_GetFrame(tstate);
+ pyFrame != nullptr && count < framesLimit; pyFrame = next) {
+ PyCodeObject *code = PyFrame_GetCode(pyFrame);
+ auto fileNameStr =
+ nb::cast<std::string>(nb::borrow<nb::str>(code->co_filename));
+ llvm::StringRef fileName(fileNameStr);
+ if (!PyGlobals::get().isUserTracebackFilename(fileName))
+ continue;
+
+#if PY_VERSION_HEX < 0x030b00f0
+ std::string name =
+ nb::cast<std::string>(nb::borrow<nb::str>(code->co_name));
+ llvm::StringRef funcName(name);
+ int startLine = PyFrame_GetLineNumber(pyFrame);
+ MlirLocation loc =
+ mlirLocationFileLineColGet(ctx, wrap(fileName), startLine, 0);
+#else
+ // co_qualname added in py3.11
+ std::string name =
+ nb::cast<std::string>(nb::borrow<nb::str>(code->co_qualname));
+ llvm::StringRef funcName(name);
+ int startLine, startCol, endLine, endCol;
+ int lasti = PyFrame_GetLasti(pyFrame);
+ if (!PyCode_Addr2Location(code, lasti, &startLine, &startCol, &endLine,
+ &endCol)) {
+ throw nb::python_error();
+ }
+ MlirLocation loc = mlirLocationFileLineColRangeGet(
+ ctx, wrap(fileName), startCol, startCol, endLine, endCol);
+#endif
+
+ frames.push_back(mlirLocationNameGet(ctx, wrap(funcName), loc));
+ ++count;
+ next = PyFrame_GetBack(pyFrame);
+ Py_XDECREF(pyFrame);
+
+ if (frames.size() > framesLimit)
+ break;
+ }
+
+ if (frames.empty())
+ return mlirLocationUnknownGet(ctx);
+ if (frames.size() == 1)
+ return frames.front();
+
+ MlirLocation callee = frames.front();
+ frames.erase(frames.begin());
+ MlirLocation caller = frames.back();
+ for (const MlirLocation &frame :
+ llvm::reverse(llvm::ArrayRef(frames).drop_back()))
+ caller = mlirLocationCallSiteGet(frame, caller);
+
+ return mlirLocationCallSiteGet(callee, caller);
+}
+
} // namespace
//------------------------------------------------------------------------------
@@ -3241,7 +3329,11 @@ void mlir::python::populateIRCore(nb::module_ &m) {
.def_static(
"create",
[](DefaultingPyLocation loc) {
- MlirModule module = mlirModuleCreateEmpty(loc);
+ PyMlirContextRef ctx = loc->getContext();
+ MlirLocation mlirLoc = loc;
+ if (auto tloc = tracebackToLocation(ctx->get()))
+ mlirLoc = *tloc;
+ MlirModule module = mlirModuleCreateEmpty(mlirLoc);
return PyModule::forModule(module).releaseObject();
},
nb::arg("loc").none() = nb::none(), "Creates an empty module")
@@ -3467,9 +3559,15 @@ void mlir::python::populateIRCore(nb::module_ &m) {
}
}
+ PyMlirContextRef ctx = location->getContext();
+ if (auto loc = tracebackToLocation(ctx->get())) {
+ return PyOperation::create(name, results, mlirOperands,
+ attributes, successors, regions,
+ {ctx, *loc}, maybeIp, inferType);
+ }
return PyOperation::create(name, results, mlirOperands, attributes,
- successors, regions, location, maybeIp,
- inferType);
+ successors, regions, *location.get(),
+ maybeIp, inferType);
},
nb::arg("name"), nb::arg("results").none() = nb::none(),
nb::arg("operands").none() = nb::none(),
@@ -3514,10 +3612,19 @@ void mlir::python::populateIRCore(nb::module_ &m) {
std::optional<std::vector<PyBlock *>> successors,
std::optional<int> regions, DefaultingPyLocation location,
const nb::object &maybeIp) {
- new (self) PyOpView(PyOpView::buildGeneric(
- name, opRegionSpec, operandSegmentSpecObj,
- resultSegmentSpecObj, resultTypeList, operandList,
- attributes, successors, regions, location, maybeIp));
+ PyMlirContextRef ctx = location->getContext();
+ if (auto loc = tracebackToLocation(ctx->get())) {
+ new (self) PyOpView(PyOpView::buildGeneric(
+ name, opRegionSpec, operandSegmentSpecObj,
+ resultSegmentSpecObj, resultTypeList, operandList,
+ attributes, successors, regions, {ctx, *loc}, maybeIp));
+ } else {
+ new (self) PyOpView(PyOpView::buildGeneric(
+ name, opRegionSpec, operandSegmentSpecObj,
+ resultSegmentSpecObj, resultTypeList, operandList,
+ attributes, successors, regions, *location.get(),
+ maybeIp));
+ }
},
nb::arg("name"), nb::arg("opRegionSpec"),
nb::arg("operandSegmentSpecObj").none() = nb::none(),
@@ -3558,10 +3665,18 @@ void mlir::python::populateIRCore(nb::module_ &m) {
nb::cast<std::tuple<int, bool>>(cls.attr("_ODS_REGIONS"));
nb::object operandSegmentSpec = cls.attr("_ODS_OPERAND_SEGMENTS");
nb::object resultSegmentSpec = cls.attr("_ODS_RESULT_SEGMENTS");
+
+ PyMlirContextRef ctx = location->getContext();
+ if (auto loc = tracebackToLocation(ctx->get())) {
+ return PyOpView::buildGeneric(name, opRegionSpec, operandSegmentSpec,
+ resultSegmentSpec, resultTypeList,
+ operandList, attributes, successors,
+ regions, {ctx, *loc}, maybeIp);
+ }
return PyOpView::buildGeneric(name, opRegionSpec, operandSegmentSpec,
resultSegmentSpec, resultTypeList,
operandList, attributes, successors,
- regions, location, maybeIp);
+ regions, *location.get(), maybeIp);
},
nb::arg("cls"), nb::arg("results").none() = nb::none(),
nb::arg("operands").none() = nb::none(),
diff --git a/mlir/lib/Bindings/Python/IRModule.h b/mlir/lib/Bindings/Python/IRModule.h
index 9c22dea157c06..87e1a0b12da00 100644
--- a/mlir/lib/Bindings/Python/IRModule.h
+++ b/mlir/lib/Bindings/Python/IRModule.h
@@ -722,8 +722,7 @@ class PyOperation : public PyOperationBase, public BaseContextObject {
llvm::ArrayRef<MlirValue> operands,
std::optional<nanobind::dict> attributes,
std::optional<std::vector<PyBlock *>> successors, int regions,
- DefaultingPyLocation location, const nanobind::object &ip,
- bool inferType);
+ PyLocation location, const nanobind::object &ip, bool inferType);
/// Creates an OpView suitable for this operation.
nanobind::object createOpView();
@@ -781,7 +780,7 @@ class PyOpView : public PyOperationBase {
nanobind::list operandList,
std::optional<nanobind::dict> attributes,
std::optional<std::vector<PyBlock *>> successors,
- std::optional<int> regions, DefaultingPyLocation location,
+ std::optional<int> regions, PyLocation location,
const nanobind::object &maybeIp);
/// Construct an instance of a class deriving from OpView, bypassing its
diff --git a/mlir/lib/Bindings/Python/MainModule.cpp b/mlir/lib/Bindings/Python/MainModule.cpp
index 6f49431006605..25174dff4f9b8 100644
--- a/mlir/lib/Bindings/Python/MainModule.cpp
+++ b/mlir/lib/Bindings/Python/MainModule.cpp
@@ -6,7 +6,6 @@
//
//===----------------------------------------------------------------------===//
-
#include "Globals.h"
#include "IRModule.h"
#include "NanobindUtils.h"
@@ -44,7 +43,15 @@ NB_MODULE(_mlir, m) {
.def("_register_operation_impl", &PyGlobals::registerOperationImpl,
"operation_name"_a, "operation_class"_a, nb::kw_only(),
"replace"_a = false,
- "Testing hook for directly registering an operation");
+ "Testing hook for directly registering an operation")
+ .def("loc_tracebacks_enabled", &PyGlobals::locTracebacksEnabled)
+ .def("set_loc_tracebacks_enabled", &PyGlobals::setLocTracebacksEnabled)
+ .def("set_loc_tracebacks_frame_limit",
+ &PyGlobals::setLocTracebackFramesLimit)
+ .def("register_traceback_file_inclusion",
+ &PyGlobals::registerTracebackFileInclusion)
+ .def("register_traceback_file_exclusion",
+ &PyGlobals::registerTracebackFileExclusion);
// Aside from making the globals accessible to python, having python manage
// it is necessary to make sure it is destroyed (and releases its python
diff --git a/mlir/test/python/ir/location.py b/mlir/test/python/ir/location.py
index 3e54dc922cd67..71dfcd41f9a51 100644
--- a/mlir/test/python/ir/location.py
+++ b/mlir/test/python/ir/location.py
@@ -1,14 +1,18 @@
# RUN: %PYTHON %s | FileCheck %s
import gc
+from contextlib import contextmanager
+
from mlir.ir import *
+from mlir.dialects._ods_common import _cext
+from mlir.dialects import arith
def run(f):
print("\nTEST:", f.__name__)
f()
gc.collect()
- assert Context._get_live_count() == 0
+ # assert Context._get_live_count() == 0
# CHECK-LABEL: TEST: testUnknown
@@ -27,6 +31,30 @@ def testUnknown():
run(testUnknown)
+ at contextmanager
+def with_infer_location():
+ _cext.globals.set_loc_tracebacks_enabled(True)
+ yield
+ _cext.globals.set_loc_tracebacks_enabled(False)
+
+
+# CHECK-LABEL: TEST: testInferLocations
+def testInferLocations():
+ with Context() as ctx, Location.unknown(), with_infer_location():
+ ctx.allow_unregistered_dialects = True
+ op = Operation.create("custom.op1")
+ print(op.location)
+ # CHECK: loc(
+ # CHECK-SAME callsite("testInferLocations"("{{.*}}/test/python/ir/location.py":13:13 to 44:43)
+ # CHECK-SAME at callsite("run"("{{.*}}/test/python/ir/location.py":4:4 to 12:7)
+ # CHECK-SAME at "<module>"("{{.*}}/test/python/ir/location.py":0:0 to 50:23))))
+ one = arith.constant(IndexType.get(), 1)
+
+
+
+run(testInferLocations)
+
+
# CHECK-LABEL: TEST: testLocationAttr
def testLocationAttr():
with Context() as ctxt:
More information about the Mlir-commits
mailing list