BUG: fix crash on 32 bit systems using abi3t - #31771
Conversation
|
The lint problem is unrelated and fixed in main. |
| * object. | ||
| */ | ||
| PyTypeObject *typeobj; | ||
| NPY_DECL_ALIGNED(8) PyTypeObject *typeobj; |
There was a problem hiding this comment.
This is a neat idea! Inspired I had a bit of a brainstorming search:
How about we embed the "public" fields here as an anonymous struct? I feel like that may be even cleaner (it doesn't encode the actual alignment guarantees yet, see below).
That is C11 but major compilers support it earlier, that might be somewhat annoying (I hope not, but...). However, NPY_DECL_ALIGNED is also not defined in a fully compatible way, currently (i.e. also would need fixing).
With an anonymous struct, but also in general, I think it might be good to do this for all our structs maybe. And it would be nice to add static asserts that would tell us in case we ever break our assumptions.
(I think a static_assert(alignof(object_struct) <= 8/16) would do the trick. It is a bit more strict than needed, but that could be addressed if it ever breaks...)
There was a problem hiding this comment.
How about we embed the "public" fields here as an anonymous struct? I feel like that may be even cleaner (it doesn't encode the actual alignment guarantees yet, see below).
That is C11 but major compilers support it earlier, that might be somewhat annoying (I hope not, but...).
I think we have considered anonymous struct before but avoided that because it is incompatible with C++ and requires C11. See #31091 (comment) where it was discussed previously.
However, NPY_DECL_ALIGNED is also not defined in a fully compatible way, currently (i.e. also would need fixing).
Yeah, I'll fix it, thanks!
There was a problem hiding this comment.
I've added the asserts
There was a problem hiding this comment.
Thanks, I forgot about that and we do need C++ of course.
Three points, mostly because I felt they are worth noting/considering once:
_Alignasis also C11, so either we require C11 for downstream for compilers other than gcc/clang/msvc, or we'd have to omit it and stop compiling for free-thread and opaque builds.
I am not exactly sure how unreasonable C11 + fun compiler is?!- Reducing alignment is a one way road for public fields (i.e. the asserts I asked for). We aren't reducing it on 32bit systems but are doing so on 64bit systems.
I don't know that there is a realistic need for 16 byte aligned types public types but if we do this we can only add them by telling the compiler to not only align them to 8 bytes. - As this is an ABI choice, it also (potentially) affects structs that are public but not exposed on opaque builds.
I have not checked, I suspect this is only the scalars and I think that is fine. So this may mostly be about diffusing knowledge: Adding this alignment is vital for all new types we might want to expose on the opaque API (sure for many it does nothing but that isn't obvious).
EDIT: I think we can make such structs public actually, but it is limited to future Python versions (i.e. right now we can for 3.15+ which is all there is anyway for opaque builds).
There was a problem hiding this comment.
_Alignas is also C11, so either we require C11 for downstream for compilers other than gcc/clang/msvc, or we'd have to omit it and stop compiling for free-thread and opaque builds.
I am not exactly sure how unreasonable C11 + fun compiler is?!
I think it is reasonable to use C11 _Alignas because CPython itself require it for defining PyObject layout. https://github.com/python/cpython/blob/2670cb062c9ec31cd6df7be645f929a8398601c7/Include/pymacro.h#L65-L86
Reducing alignment is a one way road for public fields (i.e. the asserts I asked for). We aren't reducing it on 32bit systems but are doing so on 64bit systems.
I don't think we are actually reducing it on 64 bits, as noted in the CPython implementation, alignment can only be increased and the macros becomes no-op when it will reduce the alignment.
There was a problem hiding this comment.
I think it is reasonable to use C11 _Alignas because CPython itself require it for defining PyObject layout.
An nice, shouldn't we match CPython and add that #ifndef to allow compilers that don't do this right? (Not that I expect this is actually used.)
I don't think we are actually reducing it on 64 bits, as noted in the CPython implementation, alignment can only be increased and the macros becomes no-op when it will reduce the alignment.
This assumes that Python promises that on 64bit systems the size of PyObject_HEAD will always remain a multiple of 16. Unless that is a promise that Python is willing to make officially, I don't think we should rely on it.
(Sure, we could just add a static_assert in our code with a comment "if this fails we have to think about ABI" in practice.)
Sorry if I seem pedantic, but I feel we need to be very clear about what ABI decisions we are making here that we cannot change easily.
EDIT: I'll note that I think there are one or two more issues to think about. But I need a pause from quick iteration and probably Nathan will find them as well anyway.
There was a problem hiding this comment.
Sure, we could just add a static_assert in our code with a comment "if this fails we have to think about ABI" in practice.
IMO this is the most practical thing to do. There is overlap between the CPython team and the NumPy team (me and Kumar) and IMO we would be able to handle that static assert failing if it ever does come up in the future.
|
I've changed the code to only align the first field for 3.15+ as we discussed in community meeting. I tested it with https://github.com/kumaraditya303/np-test against 3.12 stable ABI extensions and 3.14t extensions and it passes on all of them.
|
Sorry, but this is winding me up: Can you explain what changed now? About it: I was OK with just adding an assert to defer the decision to the future poor sods if it ever happens (hopefully us still, but who knows). However, this is not the assert we have. FWIW, I am wondering if we can't add a bit more docs around the core problem we have to ensure: so that others don't have to think backwards from the bug condition. I am thinking there are likely cargo-culters and we spend enough time on this to leave more breadcrumbs for them/us (e.g. point out that for new objects From there we can explain all of our choices:
|
Sorry for the noise, that comment is not correct. I messed up the testing ignore that part.
Honestly given how difficult it is to get this right, I would prefer if for new objects, we do not expose it the way it is done here. For new objects you are correct and we should use max_align_t as you suggest but also I would suggest to not expose the struct at all in the ABI instead add functions to access the fields. We should also define a separate fields struct and object struct which embed the fields structs to be standard compliant, we couldn't do it for existing types because we did not want break extensions which access fields directly. |
I mean something like this for new types: struct NewNumpyStructFields {
... // Define the fields of the new struct here
};
struct NewNumpyStruct {
PyObject_HEAD
alignas(max_align_t) struct NewNumpyStructFields fields;
}; |
|
Review wise, there are still two things mentioned earlier that I am not sure we aligned on (maybe, maybe not); I may also be forgetting something, but hopefully nothing big:
But, I guess I'll just let you and Nathan mop that up unless you wonder e.g. if I think a struct should get the padding. |
|
Thanks @kumaraditya303! |
Co-authored-by: Sebastian Berg <sebastianb@nvidia.com> Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
BUG: fix crash on 32 bit systems using abi3t (#31771)
PR summary
Fixes #31762
This PR fixes the crash on 32 bit systems when using abi3t ABI. In this PR, the first member of the
PyArrayDescris now aligned to 8 bytes so that regardless of the size of the actualPyObject, the field accesses remain correct. 8 bytes is used instead ofalignof(max_align_t)to preserve backwards compatibility with exisiting abi3 extensions wheresizeof(PyObject)is a multiple of 8 thereby the added alignment is no-op and does not changes the offset for such extensions.AI Disclosure
AI was used to create the test.