Conversation
6f02c65 to
c29564a
Compare
c29564a to
30dc6e9
Compare
|
Thanks for the PR and for looking into this. As I understand this tries to add version specific condition compilation similar to Additionally Python12 (and numpy as well) is slowly moving towards a new stable abi build around opaque So currently I'm inclined to say we should keep the current design. cc @davidhewitt @ngoldbaum Maybe you have some opinions/comments for this one as well. Footnotes |
ngoldbaum
left a comment
There was a problem hiding this comment.
I've personally been responsible for adding new structs and functions to the NumPy C API and it would be a shame if those functions can't be used by rust-numpy.
That said, this does introduce a significant amount of complexity. I also don't think it makes much sense to try to support very old versions of NumPy.
I would instead try to support only NumPy 1.25 and NumPy 2.1 and newer. I would also tend not to care much about supporting NumPy 1.x but I understand if that's important for people.
| pub legacy_inner_loop_selector: PyUFunc_LegacyInnerLoopSelectionFunc, | ||
| pub reserved2: *mut c_void, | ||
| pub masked_inner_loop_selector: PyUFunc_MaskedInnerLoopSelectionFunc, | ||
| pub dict: *mut PyObject, |
There was a problem hiding this comment.
This was changed to dict only relatively recently in 2024, see numpy/numpy#27735. It was first available in NumPy 2.2.
| #[cfg(all(Py_3_8, not(Py_LIMITED_API)))] | ||
| pub vectorcall: Option<vectorcallfunc>, | ||
| #[cfg(not(all(Py_3_8, not(Py_LIMITED_API))))] | ||
| pub vectorcall: *mut c_void, |
There was a problem hiding this comment.
This was changed to vectorcall in numpy/numpy#15271 which I believe was first included in NumPy 1.20.0.
| #[cfg(not(all(Py_3_8, not(Py_LIMITED_API))))] | ||
| pub vectorcall: *mut c_void, | ||
|
|
||
| pub reserved3: *mut c_void, |
There was a problem hiding this comment.
Separately from the discussion in this PR, it makes sense to fix these incorrect bindings. Can you fix the issues I pointed out above and send in a PR that doesn't depend on details that changed in later NumPy versions? I think that means wrapping the fields that changed as *mut c_void but maybe @Icxolu has a way they'd prefer to see it spelled.
30dc6e9 to
b1f11de
Compare
|
Thank you for the feedback! I've updated this PR based on @ngoldbaum's comments. The definition of One thought: I believe supporting newer API versions would have a greater beneficial impact than having a support of wider set of versions. Additionally, the code could be significantly simplified if only newer versions of NumPy were supported, like >=1.25, but this choice is out of my responsibility. Sorry @ngoldbaum, but I'm not sure I understand your last comment. Could you clarify what you mean by "details that changed in later NumPy versions"? The definition of |
e.g. following what @Icxolu was saying and not having any conditional compilation or version-dependent bindings. Another thing that I realized after writing my earlier comments is that NumPy itself, as of NumPy 2.0, supports building binaries that work with arbitrary older or newer numpy versions found at runtime. This is tricky and it requires a lot of conditional compilation in NumPy's headers. If it's possible to replicate that and allow setting something analogous to the |
|
@seberg knows more about how numpy handles forward and backward compatibility in its headers and might be interested in this thread too. |
|
Now I have a clearer understanding of what you meant. The mechanism you're referring to is the backward compatibility built into the NumPy API. Each version of NumPy exposes an API that remains compatible with older versions, and users can override the default by defining the This PR aims to replicate that mechanism. By default, the FFI exposed by rust-numpy targets NumPy v1.25, which is the same default used by NumPy v2.0. This may not be the ideal choice, and I'd welcome your input on what would work best. A set of features named Consistency is ensured by the fact that all conditional compilation instructions have been written to mirror the conditionals involving the |
|
I think there's no reason to try to support any numpy version in rust-numpy older than 1.25, so I'd suggest simplifying things here and removing the targets for older versions. It's also not clear to me from the diff that you are handling forward and backward compatibility for different runtime NumPy versions like I think you just claimed - can you explain how you handle that? I'd think you'd need some functions to emulate the macros and static inline functions in the numpy headers. How are you handling the fact that the descriptor object, ufunc object and array object have different layouts in different numpy versions at runtime? |
|
I would think you'd need some sort of automated testing where you build with one numpy version and run with another to validate all this. |
Some of these fields are a bit "why would you ever need them", and I guess those may be sloppy in NumPy (i.e. the dict is probably OK as it was NULL before, but just like vectorcall, you should use the Python APIs...). The thing I think seems missing here is the runtime check (I am sure this already exists for the major C version, but that check needs to be expanded). Validating all fields seems rather tedious, but it seems important to try and validate the runtime version check. (Tedious because most of them just shouldn't be used and many that may be used are still niche to the point that my guess is there is maybe a single external user.) (E.g. a good general candidate is the descr |
We currently support the same Python versions as PyO3 itself. The latest numpy version for 3.7 is 1.21 and for 3.8 is is 1.24.
This is also something that I thought about. Some motivation for APIs that we want to build on top of these would be great. It would maybe that outline a more specific path to a design we could move towards.
This is also unclear to me. One way that I think could maybe work if we want too only add a few APIs that require only a small number of specific fields is to basically define both layouts like so: #[repr(C)]
struct PyArray_Descr {
... // Like currently
}
#[repr(C)]
struct PyArray_DescrV2 {
base: PyArray_Descr,
... // additional V2 fields
}After a runtime version check we could then perform a pointer cast to access the fields or return an error for older numpy versions. But this blows up quite quickly if need a big range of versions and fields, so would be feasible for a small number of combinations. |
This PR introduces the build configuration to select the target Numpy C API version and adjusts struct definitions to match the user selected version.
The default configuration has been chosen to match the setting in the first release of Numpy v2.
The only potentially breaking change is the new definition of
PyUFuncObject. The structPyUFuncObjecthas been updated to match the definition given in Numpy source. The previous definition matched the definition contained in documentation. I think the former is more accurate.I understand that this PR is not small and that not all changes can be easily reviewed, it wasn't easy to put everything together either. If the PR is too large, I could split it into smaller changes. Anyway, I hope it will help improve the FFI.