Repository navigation
Conversation
729b98f to
4e8fd27
Compare
| * `npy_uint64 flags` member would be over-aligned and packing bytes would shift | ||
| * every later field, so expose it as two 32-bit halves. | ||
| */ | ||
| #if defined(Py_TARGET_ABI3T) |
There was a problem hiding this comment.
Maybe this condition should also include e.g. && IS_32BIT.
There was a problem hiding this comment.
Hmm, on second thought I bet this doesn't work at all on a 32-bit GIL-enabled interpreter (which might run an extension built with Py_TARGET_ABI3T, which implies Py_GIL_DISABLED), since PyObject is 8 bytes there and there's no padding to correct for...
There was a problem hiding this comment.
OK, I think what we actually need to fix 32 bit builds is new explicit accessor API functions for all of the fields after flags that might be impacted by this alignment/packing issue. We can handle all of this in NumPy, we just can't handle it in the single GET_ITEM_DATA API function we have now.
That also probably means 32bit builds abi3t can't be supported until NumPy 2.6 because we need API functions to fix this
Ping @seberg.
|
I'm going to close this PR because the approach is unsound and open a followup issue. |
All of this sounds like a Python bug though? Python has to guarantee EDIT: OK, it does guarantee for the whole object, but if Or in other words: The only real solution to me seems for Python to ensure |
I believe that with will align to max_align_t (https://github.com/python/cpython/blob/285d96dd784620fe8afb02688abe72a916555061/Objects/typeobject.c#L5133 and https://github.com/python/cpython/blob/285d96dd784620fe8afb02688abe72a916555061/Objects/typeobject.c#L6088). That makes But I think it makes the assumption that is safe to access through a pointer dodgy. (I think I did claim it was dodgy at the time, but failed to identify this as the reason). So it really means that it's only safe if you consistently treat a |
|
Ah, thanks for the note @da-woods, right. If Python does insert the extra alignment for such types, then a type defined via the opaque API can talk to others safely. But it is still super annoying :(. Sure, using accessors functions works, but it means that we are far worse off then stable ABI objects here. I don't find it very enticing. Part of me wonders if the right thing wouldn't be to just add the extra padding in NumPy if running on Python 3.15+. That breaks all existing Python 3.15 downstream that are already build against NumPy 2.5 which are also:
But 🤷, waiting for 3.16+ wouldn't be better, it's not like NumPy has a release for 3.15+ available yet, so in a sense we can do that? Of course... We can also rip this all out and introduce a giant family of getter functions... In theory this affects pretty much all our objects (in practice it may not but for those others adding padding would also do nothing). (EDIT: I dunno, maybe that is crazy and Cython might be a bit annoying about it as well if the size is Python dependent? And I just really need to warm up to "accessors all the way" even if everything is designed to not require this. |
We were discussing it being dodgy because of aliasing rules mostly, IIRC. If you still think that, then we would have to just stop this all. For the alignment issue, manual alignment is at least conceivable (i.e. align the normal ABI with what an opaque API would do in practice for its objects as I said above). |
Yes indeed. I can't claim to have had an really useful advanced insight here.
For the moment I'm assuming that it's hard to mix opaque and non-opaque modules useful (or at least, they can only mix one way because we can always make things opaque). The only complication is the memoryview internal classes. I'll have to go back and check if they have this alignment issue on 32 bits. If not, it'll be luck rather than judgment. |
Over in PyO3/rust-numpy#556 work is ongoing to add abi3t support for crates that depend on PyO3 and rust-numpy.
There is a failing test run for win32 3.15t, in a test that accesses the
metadatafield for a datetime dtype instance.I was able to track this down to the uint64
flagsfield on thePyArray_Descr_fieldsand_PyArray_LegacyDescr_fieldsstructs.On a 32 bit free-threaded build,
sizeof(PyObject)is 20 bytes. That places theflagsfield at a 4-byte aligned address instead of an 8-byte aligned address, the compiler inserts some padding bytes, and suddenly the fields access function is returning addresses at the wrong offset for all fields after theflagsentry.The fix I landed on was to define the
flagsas two 32 bit integers. Each gets read individually, and the compiler inserts no more packing bytes into the struct. Inside theflagsaccess macro, we re-assemble the lo and hi parts of the 64 bit int.Also adds a static assert in case a future change to
sizeof(PyObject)breaks our assumptions here. Also adds a regression test to access the datetime metadata field, like the rust-numpy test did.I used Claude to help identify and fix this bug.