Skip to content

[mlir][py] Mark all type caster from_{cpp,python} methods as noexcept #143866

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

Merged
merged 5 commits into from
Jul 15, 2025

Conversation

nicholasjng
Copy link
Contributor

This is mentioned as a "must" in https://nanobind.readthedocs.io/en/latest/porting.html#type-casters when implementing type casters.

While most of the existing from_cpp methods were already marked noexcept, many of the from_python methods were not. This commit adds the missing noexcept declarations to all type casters found in NanobindAdaptors.h.

This is mentioned as a "must" in https://nanobind.readthedocs.io/en/latest/porting.html#type-casters when implementing type casters.

While most of the existing `from_cpp` methods were already marked noexcept, many of the `from_python` methods were not.
This commit adds the missing noexcept declarations to all type casters found in `NanobindAdaptors.h`.
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added the mlir label Jun 12, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 12, 2025

@llvm/pr-subscribers-mlir

Author: Nicholas Junge (nicholasjng)

Changes

This is mentioned as a "must" in https://nanobind.readthedocs.io/en/latest/porting.html#type-casters when implementing type casters.

While most of the existing from_cpp methods were already marked noexcept, many of the from_python methods were not. This commit adds the missing noexcept declarations to all type casters found in NanobindAdaptors.h.


Full diff: https://github.com/llvm/llvm-project/pull/143866.diff

1 Files Affected:

  • (modified) mlir/include/mlir/Bindings/Python/NanobindAdaptors.h (+15-14)
diff --git a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
index 2dd35c097c796..e39b1a752f8d6 100644
--- a/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
+++ b/mlir/include/mlir/Bindings/Python/NanobindAdaptors.h
@@ -67,7 +67,7 @@ static nanobind::object mlirApiObjectToCapsule(nanobind::handle apiObject) {
 template <>
 struct type_caster<MlirAffineMap> {
   NB_TYPE_CASTER(MlirAffineMap, const_name("MlirAffineMap"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToAffineMap(capsule.ptr());
     if (mlirAffineMapIsNull(value)) {
@@ -90,7 +90,7 @@ struct type_caster<MlirAffineMap> {
 template <>
 struct type_caster<MlirAttribute> {
   NB_TYPE_CASTER(MlirAttribute, const_name("MlirAttribute"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToAttribute(capsule.ptr());
     return !mlirAttributeIsNull(value);
@@ -111,7 +111,7 @@ struct type_caster<MlirAttribute> {
 template <>
 struct type_caster<MlirBlock> {
   NB_TYPE_CASTER(MlirBlock, const_name("MlirBlock"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToBlock(capsule.ptr());
     return !mlirBlockIsNull(value);
@@ -122,7 +122,7 @@ struct type_caster<MlirBlock> {
 template <>
 struct type_caster<MlirContext> {
   NB_TYPE_CASTER(MlirContext, const_name("MlirContext"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (src.is_none()) {
       // Gets the current thread-bound context.
       // TODO: This raises an error of "No current context" currently.
@@ -142,7 +142,7 @@ struct type_caster<MlirContext> {
 template <>
 struct type_caster<MlirDialectRegistry> {
   NB_TYPE_CASTER(MlirDialectRegistry, const_name("MlirDialectRegistry"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToDialectRegistry(capsule.ptr());
     return !mlirDialectRegistryIsNull(value);
@@ -162,7 +162,7 @@ struct type_caster<MlirDialectRegistry> {
 template <>
 struct type_caster<MlirLocation> {
   NB_TYPE_CASTER(MlirLocation, const_name("MlirLocation"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     if (src.is_none()) {
       // Gets the current thread-bound context.
       src = nanobind::module_::import_(MAKE_MLIR_PYTHON_QUALNAME("ir"))
@@ -188,7 +188,7 @@ struct type_caster<MlirLocation> {
 template <>
 struct type_caster<MlirModule> {
   NB_TYPE_CASTER(MlirModule, const_name("MlirModule"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToModule(capsule.ptr());
     return !mlirModuleIsNull(value);
@@ -209,12 +209,13 @@ template <>
 struct type_caster<MlirFrozenRewritePatternSet> {
   NB_TYPE_CASTER(MlirFrozenRewritePatternSet,
                  const_name("MlirFrozenRewritePatternSet"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToFrozenRewritePatternSet(capsule.ptr());
     return value.ptr != nullptr;
   }
-  static handle from_cpp(MlirFrozenRewritePatternSet v, rv_policy, handle) {
+  static handle from_cpp(MlirFrozenRewritePatternSet v, rv_policy,
+                         handle) noexcept {
     nanobind::object capsule = nanobind::steal<nanobind::object>(
         mlirPythonFrozenRewritePatternSetToCapsule(v));
     return nanobind::module_::import_(MAKE_MLIR_PYTHON_QUALNAME("rewrite"))
@@ -228,7 +229,7 @@ struct type_caster<MlirFrozenRewritePatternSet> {
 template <>
 struct type_caster<MlirOperation> {
   NB_TYPE_CASTER(MlirOperation, const_name("MlirOperation"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToOperation(capsule.ptr());
     return !mlirOperationIsNull(value);
@@ -250,7 +251,7 @@ struct type_caster<MlirOperation> {
 template <>
 struct type_caster<MlirValue> {
   NB_TYPE_CASTER(MlirValue, const_name("MlirValue"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToValue(capsule.ptr());
     return !mlirValueIsNull(value);
@@ -273,7 +274,7 @@ struct type_caster<MlirValue> {
 template <>
 struct type_caster<MlirPassManager> {
   NB_TYPE_CASTER(MlirPassManager, const_name("MlirPassManager"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToPassManager(capsule.ptr());
     return !mlirPassManagerIsNull(value);
@@ -284,7 +285,7 @@ struct type_caster<MlirPassManager> {
 template <>
 struct type_caster<MlirTypeID> {
   NB_TYPE_CASTER(MlirTypeID, const_name("MlirTypeID"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToTypeID(capsule.ptr());
     return !mlirTypeIDIsNull(value);
@@ -306,7 +307,7 @@ struct type_caster<MlirTypeID> {
 template <>
 struct type_caster<MlirType> {
   NB_TYPE_CASTER(MlirType, const_name("MlirType"))
-  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) {
+  bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
     nanobind::object capsule = mlirApiObjectToCapsule(src);
     value = mlirPythonCapsuleToType(capsule.ptr());
     return !mlirTypeIsNull(value);

Copy link
Member

@ftynse ftynse left a comment

Choose a reason for hiding this comment

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

Thanks!

@ftynse
Copy link
Member

ftynse commented Jun 20, 2025

Please check if the test failure is relevant. I see python_test involved but don't have time to investigate further.

@nicholasjng
Copy link
Contributor Author

Thanks for the review. It is relevant, since we're now terminating because of an exception thrown in one of the type casters. The mlirApiObjectToCapsule() API seems responsible:

throw nanobind::type_error(
(llvm::Twine("Expected an MLIR object (got ") + repr + ").")
.str()
.c_str());
}

I'm unsure how to proceed - on the one hand, the docs are pretty clear that noexcept is required (and e.g. XLA changed all of its type casters to noexcept as well), but I don't think I can fix this without a bit of refactoring.

cc @hawkinsp for a review (hope that's okay).

@ftynse
Copy link
Member

ftynse commented Jul 5, 2025

It doesn't look like you will get a review from hawkinsp, they are not an active contributor to MLIR.

I'm unsure how to proceed - on the one hand, the docs are pretty clear that noexcept is required (and e.g. XLA changed all of its type casters to noexcept as well), but I don't think I can fix this without a bit of refactoring.

If you have a particular refactoring in mind, feel free to propose it.

@ftynse ftynse self-requested a review July 5, 2025 08:49
@ftynse
Copy link
Member

ftynse commented Jul 5, 2025

Please do ping me if a review is needed.

@nicholasjng
Copy link
Contributor Author

Fair. I thought since he authored that code, I might get some insights about what I have to watch out for.

I propose to make mlirApiObjectToCapsule return nullptr on failure instead of throwing a TypeError, and then returning false in the type casters if the obtained capsule is null.

The error handling on the Python side should stay about the same, since nanobind itself will throw a type error after it exhaused all type caster overloads for a given type. But I should implement it first before making assumptions.

I'll ping you when I'm ready with the refactoring. Thanks for coming back to this!

@nicholasjng
Copy link
Contributor Author

nicholasjng commented Jul 9, 2025

Hey, I'm still on this.

My current idea is to return an invalid handle from mlirApiObjectToCapsule (i.e. one the wraps nullptr), but either the mlirCapsuleTo* family or the PyCapsule_GetPointer Python C API doesn't like that. I'm getting a RuntimeError: std::bad_cast in the Python test, which I'm not sure how to address (I'll probably need to debug the bindings directly in C++).

Otherwise, my idea is to just return nanobind::none from mlirApiObjectToCapsule, and return early in from_python if capsule.is_none(). That is not as nice of a solution since it requires that logic to be inserted in all from_python type caster methods, and I'm not sure if the results play nice with the current testing assumptions.

I'll have to read up on the Python lit testing as well, since I don't know how to interpret the failed test output correctly (see below). But I'm working on it.

Test output
Command Output (stdout):
--
# RUN: at line 1
/Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/.venv/bin/python3 /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py pybind11 | /Users/nicholasjunge/Workspaces/c++/llvm-project/build/bin/FileCheck /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py
# executed command: /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/.venv/bin/python3 /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py pybind11
# executed command: /Users/nicholasjunge/Workspaces/c++/llvm-project/build/bin/FileCheck /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py
# RUN: at line 2
/Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/.venv/bin/python3 /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py nanobind | /Users/nicholasjunge/Workspaces/c++/llvm-project/build/bin/FileCheck /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py
# executed command: /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/.venv/bin/python3 /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py nanobind
# .---command stderr------------
# | Traceback (most recent call last):
# |   File "/Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py", line 329, in <module>
# |     @run
# |      ^^^
# |   File "/Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py", line 35, in run
# |     f()
# |   File "/Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py", line 362, in testCustomAttribute
# |     TestAttr(42)
# | RuntimeError: std::bad_cast
# `-----------------------------
# error: command failed with exit status: 1
# executed command: /Users/nicholasjunge/Workspaces/c++/llvm-project/build/bin/FileCheck /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py
# .---command stderr------------
# | /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py:424:16: error: CHECK-LABEL: expected string not found in input
# | # CHECK-LABEL: TEST: testTensorValue
# |                ^
# | <stdin>:50:26: note: scanning from here
# | TEST: testCustomAttribute
# |                          ^
# | <stdin>:51:3: note: possible intended match here
# | #python_test.test_attr
# |   ^
# | 
# | Input file: <stdin>
# | Check file: /Users/nicholasjunge/Workspaces/c++/llvm-project/mlir/test/python/dialects/python_test.py
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |              .
# |              .
# |              .
# |             45:  
# |             46: TEST: testOptionalOperandOp 
# |             47: op1.input is None: True 
# |             48: op2.input is None: False 
# |             49:  
# |             50: TEST: testCustomAttribute 
# | label:424'0                              X error: no match found
# |             51: #python_test.test_attr 
# | label:424'0     ~~~~~~~~~~~~~~~~~~~~~~~
# | label:424'1       ?                     possible intended match
# |             52: python_test.custom_attributed_op {test_attr = #python_test.test_attr} () -> () 
# | label:424'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             53:  
# | label:424'0     ~
# |             54: #python_test.test_attr 
# | label:424'0     ~~~~~~~~~~~~~~~~~~~~~~~
# |             55: TestAttr(#python_test.test_attr) 
# | label:424'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

********************
********************

@kuhar kuhar requested a review from makslevental July 10, 2025 10:40
@makslevental
Copy link
Contributor

I would say just use std::optional<object> (instead of py::none()) and take the hit on refactoring all the call sites.

@makslevental
Copy link
Contributor

I can try it and evaluate the change in the tests.

Instead of raising a `nanobind::type_error()`. This is necessary to honor the
nanobind type caster API contract, which requires `from_python` and `from_cpp` methods
to be marked `noexcept`.
value = mlirPythonCapsuleToAffineMap(capsule.ptr());
if (mlirAffineMapIsNull(value)) {
std::optional<nanobind::object> capsule = mlirApiObjectToCapsule(src);
if (!capsule)
Copy link
Contributor

Choose a reason for hiding this comment

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

I did

    if (auto capsule = mlirApiObjectToCapsule(src))
      value = mlirPythonCapsuleToPassManager(capsule->ptr());

@makslevental
Copy link
Contributor

makslevental commented Jul 10, 2025

@nicholasjng
Copy link
Contributor Author

Thanks, that's pretty cool. Although now I'm wondering why I didn't achieve the same with my previous capsule.is_none() checks, or with the current changeset.

Would you like to commit your version instead, then? Or how should we proceed?

@makslevental
Copy link
Contributor

Would you like to commit your version instead, then? Or how should we proceed?

Just apply the patch your PR here - it's fine (I don't care "about" credit, just that it gets landed 😄)

@nicholasjng
Copy link
Contributor Author

Thank you! I'm happy to credit you in any way you choose, should you change your mind.

The patch gives me a segmentation violation on my local machine (M1 Mac), though:

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   libMLIRPythonCAPI.dylib       	       0x16d5382a8 mlirAttributeIsAPythonTestTestAttribute + 8
1   _mlirPythonTestNanobind.cpython-312-darwin.so	       0x103ef40b8 0x103ef0000 + 16568
2   _mlirPythonTestNanobind.cpython-312-darwin.so	       0x103ef400c 0x103ef0000 + 16396
3   _mlirPythonTestNanobind.cpython-312-darwin.so	       0x103ef9d9c 0x103ef0000 + 40348
4   Python                        	       0x1033fd934 _PyObject_FastCallDictTstate + 92
5   Python                        	       0x103474bf0 slot_tp_new + 180
6   Python                        	       0x10346bb2c type_call + 96
7   Python                        	       0x1033fdb08 _PyObject_MakeTpCall + 124
8   Python                        	       0x1034f0f40 _PyEval_EvalFrameDefault + 23304
9   Python                        	       0x1034eb1c8 PyEval_EvalCode + 184
10  Python                        	       0x10354b8bc run_eval_code_obj + 88
11  Python                        	       0x103549994 run_mod + 132
12  Python                        	       0x103548e08 pyrun_file + 156
13  Python                        	       0x10354816c _PyRun_SimpleFileObject + 288
14  Python                        	       0x103547d64 _PyRun_AnyFileObject + 80
15  Python                        	       0x10356e194 pymain_run_file_obj + 164
16  Python                        	       0x10356df08 pymain_run_file + 72
17  Python                        	       0x10356d4ec Py_RunMain + 852
18  Python                        	       0x10356d950 pymain_main + 304
19  Python                        	       0x10356d9f0 Py_BytesMain + 40
20  dyld                          	       0x18ce9eb98 start + 6076

Also, the test still does not pass. Is something wrong with my rebuilding workflow? I just run ninja check-mlir-python after saving the changes in the files, like you suggested in the Discourse thread I opened.

@makslevental
Copy link
Contributor

The patch gives me a segmentation violation on my local machine (M1 Mac), though:

weird because i wrote and tested the patch on my m1... you're gonna have to build in debug mode to figure out where this is coming from

0   libMLIRPythonCAPI.dylib       	       0x16d5382a8 mlirAttributeIsAPythonTestTestAttribute + 8
1   _mlirPythonTestNanobind.cpython-312-darwin.so	       0x103ef40b8 0x103ef0000 + 16568
2   _mlirPythonTestNanobind.cpython-312-darwin.so	       0x103ef400c 0x103ef0000 + 16396
3   _mlirPythonTestNanobind.cpython-312-darwin.so	       0x103ef9d9c 0x103ef0000 + 40348

@makslevental
Copy link
Contributor

FYI I appreciate the shoutout in the message but can you actually remove my handle from the commit message (otherwise I get pings for weeks when people rebase/merge in the change 😅).

@nicholasjng
Copy link
Contributor Author

Absolutely, give me a second.

@makslevental
Copy link
Contributor

makslevental commented Jul 10, 2025

Lol okay it failed in CI - gotta whip out a linux machine to test.

@nicholasjng
Copy link
Contributor Author

Interesting. It started passing locally for me when I rebuilt in debug mode. I guess it was a caching issue on my end (I set ccache to be on for the build).

@makslevental
Copy link
Contributor

makslevental commented Jul 10, 2025

It's coming from here https://github.com/wjakob/nanobind/blob/master/include/nanobind/nb_cast.h#L576 which is a std::bad_cast https://github.com/wjakob/nanobind/blob/master/include/nanobind/nb_error.h#L90

It's not actually a crash right - it's RuntimeError: std::bad_cast

@makslevental
Copy link
Contributor

It's funny - what's happening is the cast is failing here

            MlirAttribute rawAttribute =
              nanobind::cast<MlirAttribute>(otherAttribute);
          if (!isaFunction(rawAttribute)) {
            auto origRepr =
                nanobind::cast<std::string>(nanobind::repr(otherAttribute));
            throw std::invalid_argument(
                (llvm::Twine("Cannot cast attribute to ") + captureTypeName +
                 " (from " + origRepr + ")")
                    .str());
          }

specifically nanobind::cast<MlirAttribute>(otherAttribute).

After stepping through a bunch I believe we're facing the fact that since type_casters don't throw, we can't ourselves uses nanobind::cast without catching std::bad_cast... I feel like that's very annoying...

@makslevental
Copy link
Contributor

Yea this bears further investigation. I can't get to this until late next week so @nicholasjng feel free to keep pushing (but don't worry if you can't figure it out).

@makslevental
Copy link
Contributor

I guess the solution, if we do this, is to replace all the casts with try_casts

@makslevental
Copy link
Contributor

here you go this works - i tested it here but you should land it here. we also need to check the rest of the casts to make sure if they're going through these type casters (if they're casting to nb::cast<MlirSOMETHING>) that either catch or get converted to try_cast.

This avoids a nb::cast_error throw in certain class conversions.
@nicholasjng
Copy link
Contributor Author

nicholasjng commented Jul 11, 2025

Thanks! I started checking the casts, and found two more nanobind::cast<MlirTypeID> in lib/Bindings/Python/IRModule.h:

cls.def_prop_ro("typeid", [](PyType &self) {
return nanobind::cast<MlirTypeID>(nanobind::cast(self).attr("typeid"));
});

cls.def_prop_ro("typeid", [](PyAttribute &self) {
return nanobind::cast<MlirTypeID>(nanobind::cast(self).attr("typeid"));
});

However, they are casting from an nb::object coming out of a nanobind::cast(v), where v is the class instance, which is always a PyType/PyAttribute, so I think those can never fail.

It might also be necessary to check some of the other nanobind::cast(v) calls though, where v is some C++ type convertible to a MlirSOMETHING.

@makslevental
Copy link
Contributor

Thanks! I started checking the casts, and found two more nanobind::cast<MlirTypeID> in lib/Bindings/Python/IRModule.h:

Yea I saw these and I think they're safe according to exact your same logic.

It might also be necessary to check some of the other nanobind::cast(v) calls though, where v is some C++ type convertible to a MlirSOMETHING.

Yup those are exactly the ones I was worried about. Note though (I believe!) any of the casts like nanobind::cast<PySOMETHING>() should be safe because they do not go through an intermediate MlirSOMETHING step.

Also btw we're gonna need a PSA about this because it's a breaking change (even though it's a fix) for downstream projects.

@nicholasjng
Copy link
Contributor Author

Sure. Is there a process documentation for PSAs?

Since there are no other observable breakages with the current available tests, would you consider this otherwise complete for now?

@makslevental
Copy link
Contributor

Sure. Is there a process documentation for PSAs?

I just mean you should make a post on the discourse like https://discourse.llvm.org/t/psa-opty-create-now-with-100-more-tab-complete/87339

Since there are no other observable breakages with the current available tests, would you consider this otherwise complete for now?

Yup - should I merge it for you?

@nicholasjng
Copy link
Contributor Author

That would be nice. I'll craft a PSA sometime later today. Thanks for all the help!

@makslevental makslevental merged commit 6350bb3 into llvm:main Jul 15, 2025
9 checks passed
Copy link

@nicholasjng Congratulations on having your first Pull Request (PR) merged into the LLVM Project!

Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR.

Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.

How to do this, and the rest of the post-merge process, is covered in detail here.

If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again.

If you don't get any reports, no action is required from you. Your changes are working as expected, well done!

rupprecht added a commit to rupprecht/llvm-project that referenced this pull request Jul 15, 2025
After llvm#143866, we no longer always write to `value`, causing it to be uninitialized. This can lead to mysterious crashes, e.g. in `testCustomAttribute` when we attempt to evaluate `TestAttr(42)`, it does not set `value`, but `mlirAttributeIsNull(value)` happens to return false for garbage memory, and we end up trying to interpret it as a function instead of skipping it.
@rupprecht
Copy link
Collaborator

FYI, this commit causes some crashes in python_test.py, at least when running downstream -- haven't checked buildbots. Created #148944 to address the msan issues.

rupprecht added a commit that referenced this pull request Jul 15, 2025
After #143866, we no longer always write to `value`, causing it to be
uninitialized. This can lead to mysterious crashes, e.g. in
`python_test.py` / `testCustomAttribute` when we attempt to evaluate
`TestAttr(42)`, it does not set `value`, but
`mlirAttributeIsNull(value)` happens to return false for garbage memory,
and we end up trying to interpret it as a function instead of skipping
it.

Fix this by only reading `value` if it has been assigned. If it hasn't,
`return false` seems the right choice for all these methods, i.e.
indicate that `from_python` failed.
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jul 15, 2025

LLVM Buildbot has detected a new failure on builder premerge-monolithic-linux running on premerge-linux-1 while building mlir at step 7 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/153/builds/38192

Here is the relevant piece of the build log for the reference
Step 7 (test-build-unified-tree-check-all) failure: test (failure)
******************** TEST 'MLIR :: python/dialects/python_test.py' FAILED ********************
Exit Code: 2

Command Output (stdout):
--
# RUN: at line 1
/usr/bin/python3.10 /build/buildbot/premerge-monolithic-linux/llvm-project/mlir/test/python/dialects/python_test.py pybind11 | /build/buildbot/premerge-monolithic-linux/build/bin/FileCheck /build/buildbot/premerge-monolithic-linux/llvm-project/mlir/test/python/dialects/python_test.py
# executed command: /usr/bin/python3.10 /build/buildbot/premerge-monolithic-linux/llvm-project/mlir/test/python/dialects/python_test.py pybind11
# note: command had no output on stdout or stderr
# executed command: /build/buildbot/premerge-monolithic-linux/build/bin/FileCheck /build/buildbot/premerge-monolithic-linux/llvm-project/mlir/test/python/dialects/python_test.py
# note: command had no output on stdout or stderr
# RUN: at line 2
/usr/bin/python3.10 /build/buildbot/premerge-monolithic-linux/llvm-project/mlir/test/python/dialects/python_test.py nanobind | /build/buildbot/premerge-monolithic-linux/build/bin/FileCheck /build/buildbot/premerge-monolithic-linux/llvm-project/mlir/test/python/dialects/python_test.py
# executed command: /usr/bin/python3.10 /build/buildbot/premerge-monolithic-linux/llvm-project/mlir/test/python/dialects/python_test.py nanobind
# note: command had no output on stdout or stderr
# error: command failed with exit status: -11
# executed command: /build/buildbot/premerge-monolithic-linux/build/bin/FileCheck /build/buildbot/premerge-monolithic-linux/llvm-project/mlir/test/python/dialects/python_test.py
# .---command stderr------------
# | FileCheck error: '<stdin>' is empty.
# | FileCheck command line:  /build/buildbot/premerge-monolithic-linux/build/bin/FileCheck /build/buildbot/premerge-monolithic-linux/llvm-project/mlir/test/python/dialects/python_test.py
# `-----------------------------
# error: command failed with exit status: 2

--

********************


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants