Repository navigation
BUG: fix a number of issues around iterators and StringDType - #32156
Conversation
|
@ikrommyd I requested for you to give this a look but feel free to ping me if you're unsure about details and want me to walk you through this |
|
@MaanasArora any chance you can give this one a look? Thanks!! |
ikrommyd
left a comment
There was a problem hiding this comment.
Looks overall good to me, left a few comments though
| NPY_OP_ITFLAG_READ) && | ||
| PyArray_NDIM(op[iop]) == 0) { | ||
| PyArray_NDIM(op[iop]) == 0 && | ||
| !NPY_DT_has_finalize(NPY_DTYPE(op_dtype[iop]))) { |
There was a problem hiding this comment.
Why doesn't this branch resync as well? Reproducer by claude below:
import numpy as np
s = np.array("Z" * 30, dtype="T")
dt = np.dtypes.StringDType()
np.empty(1, dtype=dt) # attach dt, so a temp created from it gets a replaced descriptor
it = np.nditer([s], flags=["refs_ok"], op_dtypes=[dt], casting="unsafe")
str(next(it))
raises TypeError: Iterator operand required copying or buffering, but neither copying nor buffering was enabled. Wouldn't resyncing fix that?
There was a problem hiding this comment.
Yes it should. After adding resyncing here I also realized that the fancy-index assignment was also subtly broken, see the change at line 2148 of array_assign_subscript.
| /* | ||
| * descrs[0] and descrs[2] both describe op[0], which is used as both | ||
| * the previous-value input and the output (see `dataptr_copy` below). | ||
| */ | ||
| npy_resync_finalized_descr(&descrs[0], op[0]); | ||
| npy_resync_finalized_descr(&descrs[1], op[1]); | ||
| npy_resync_finalized_descr(&descrs[2], op[0]); | ||
|
|
There was a problem hiding this comment.
This is in PyUFunc_Accumulate. Does PyUFunc_Reduce (probably inside PyUFunc_ReduceWrapper) or others like reduceat etc need the same treatment?
There was a problem hiding this comment.
I added some code to re-sync the ArrayMethod contexts in PyUFunc_ReduceWrapper. It can't use this pattern of the new NpyIter_GetArrayMethodContext I also added because reductions alias descriptors. However I could use NpyIter_GetArrayMethodContext in PyUFunc_GeneralizedFunctionInternal and execute_ufunc_loop where it nicely simplifies things.
There was a problem hiding this comment.
I did not look at this unseen code in the diff but fable mentioned that these two should probably be reversed such that NpyIter_Deallocate should see that PyErr_Occurred since it branches on it in line 657 now.
There was a problem hiding this comment.
Missed this comment on my first pass but I just pushed a fix in the last commit.
| assert_array_equal(out, a) | ||
|
|
||
|
|
||
| def test_nditer_copy_and_writeback(): |
There was a problem hiding this comment.
it.copy() is not called tested in this test
There was a problem hiding this comment.
See test_nditer_distinct_allocators, unless you were thinking of something different
There was a problem hiding this comment.
The ufunc.accumulate changes are untested.
There was a problem hiding this comment.
see test_reduce_distinct_allocators
|
@mhvk you may be interested in this too. The bulk of the PR is tests and the C changs are all in the guts of the iterator internals. If we get one more review on this I think we can merge this in time for the bugfix release this weekend. |
mhvk
left a comment
There was a problem hiding this comment.
Yikes, this really gets into the weeds. Two possible relevant queries, two suggestions that can be ignored...
| if (PyArray_GetDTypeTransferFunction( | ||
| 1, itemsize, itemsize, | ||
| descr, PyArray_DESCR(self), | ||
| NpyIter_GetDescrArray(mit->outer)[mit->num_fancy], |
There was a problem hiding this comment.
Can this never happen in the subspace_iter above?
There was a problem hiding this comment.
FWIW, seems that the subspace path in PyArray_MapIterNew replaces descr (in this case PyArray_DESCR(self)), so hopefully not!
numpy/numpy/_core/src/multiarray/mapping.c
Lines 3048 to 3057 in ba165bb
There was a problem hiding this comment.
Yeah, subspace_iter is unbuffered so the descriptors match. I added StringDType tests for this though.
|
|
||
| /* | ||
| * op_dtypes tracks the loop's descriptors so the initial-value buffers | ||
| * use the same ones. The context must not outlive `iter`. |
There was a problem hiding this comment.
Maybe "...not outlive iter, as we are filled with borrowed references" (maybe, "like for PyIter_GetArrayMethodContext")
Also perhap: "Reminder: inputs have nout inputs that hold results
and one input that provides the values, i.e., nin = nout+1"
| } | ||
| } | ||
|
|
||
| /* |
There was a problem hiding this comment.
Can anything happen to descrs outside of the iterator block? And even then, what can happen to 0 and 1? If not, I'd prefer moving it there (and have assert here if you wish). But I may be missing something.
There was a problem hiding this comment.
You're right, descrs was used for two different purposes. I gave the loiop a separate borrowed descriptor array built from the final operands, which resolves the need to resync here.
ikrommyd
left a comment
There was a problem hiding this comment.
All good from my side, I don't see anything else. There's just the remaining comments from Marten left.
Some conflicts may come from this PR to my reduction loops in reduceat and accumulate PRs but shouldn't be anything particularly annoying.
|
Sorry, could only get through a brief pass, this gets very deep! But looks good to me. |
|
Thanks all! Marten pointed out something that let me simplify this even more. |
|
@charris just in case there's still time for one more backport, I'm merging this |
|
Looking at the backport, the remaining diff of descriptor.c with main is: Note that |
|
I am tempted to bring down the whole |
|
@charris I'd drop the descriptor.c changes from the backport and remove |
|
OK, I'll leave |
|
There is one leak fix, however, I'll put that back in. |
|
There are also problems with |
|
Fair enough |
|
I am responsible for the changes in |
…2156) Signed-off-by: Iason Krommydas <[email protected]> Co-authored-by: Nathan Goldbaum <[email protected]>
PR summary
Fixes #32153. Also fixes a number of other issues that are caused by the same problem: array creation can replace descriptors. The iterator code in particular had a number of spots that didn't handle this case.
I added a new
NPY_DT_has_finalizepredicate to branch based on whether or not there is afinalize_descrimplementation, which might replace dtype instances.I also added a new
npy_resync_finalized_descr, which does the necessary reference bookkeeping when a descriptor has been replaced.Also deletes an assert in the
np.concatenateimplementation that could trigger forStringDTypeif a descriptor is replaced.There are also a few minor unrelated C error checking fixes included as well.
AI Disclosure
An AI model found the issue and I used it to help develop and debug the fix and new tests.