Skip to content

Update to target ABI v2#537

Open
mattiapenati wants to merge 6 commits intoPyO3:mainfrom
mattiapenati:target-abi-v2
Open

Update to target ABI v2#537
mattiapenati wants to merge 6 commits intoPyO3:mainfrom
mattiapenati:target-abi-v2

Conversation

@mattiapenati
Copy link
Contributor

@mattiapenati mattiapenati commented Mar 13, 2026

This PR contains the implementation discussed in #534. The npyffi module has been updated to match the NumPy implementation of ABI v2 when the NPY_FEATURE_VERSION macro is set to 0x0000000c (v1.15). Some new macros are required for the implementation, I prefer to follow the same layout of NumPy to make easier future changes.

The major changes of this PR are:

  • NonNull marker is used to store the pointer contained in capsules.
  • Multiarray and UFunc APIs have been updated to match the NumPy public API.
  • A consistent way to define opaque data types is used, T([u8; 0]).
  • Types related to npy_longdouble have been commented; previously they were related to f64, but on some platforms long double is 80-bit extended precision.
  • The runtime compatibility is now checked when the NumPy API is initialized and this check has been ported from NumPy.

During the implementation, some doubts arose:

  • Why is std::os::raw used? Usually std::ffi is preferred.
  • Why are all the type definitions related to functions wrapped in Option<...>? I found that the usage of Option<...> is more common in member definitions, probably even in pyo3. For example:
    type Callback = extern "C" fn (*mut c_void) -> *mut c_void;
    #[repr(C)]
    struct Operator {
      data: *mut c_void,
      callback: Option<Callback>,
    }
    // in place of
    type Callback = Option<extern "C" fn (*mut c_void) -> *mut c_void>;
    #[repr(C)]
    struct Operator {
      data: *mut c_void,
      callback: Callback,
    }

@Icxolu
Copy link
Member

Icxolu commented Mar 13, 2026

Not a full review, will try to give this a read tomorrow.

  • Why is std::os::raw used? Usually std::ffi is preferred.

Most likely a left over from the times before std::ffi was stabilized on MSRV. 👍 to changing that.

  • Why are all the type definitions related to functions wrapped in Option<...>? I found that the usage of Option<...> is more common in member definitions, probably even in pyo3

Good question, not sure. I think I'd also prefer having the Option on the member definition. We can change that, but as a separate PR either before or after this one lands.

Copy link
Member

@Icxolu Icxolu left a comment

Choose a reason for hiding this comment

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

Thank you ❤️ I think this is a great start already. I left a few comments, mostly documentation related. We should also make sure to add a changelog entry for this.

@@ -94,8 +146,8 @@ impl PyArrayAPI {

impl PyArrayAPI {
Copy link
Member

Choose a reason for hiding this comment

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

Is there some page that we can link to where this mapping is documented for future reference?

impl_api![0; PyArray_GetNDArrayCVersion() -> c_uint];
impl_api![40; PyArray_SetNumericOps(dict: *mut PyObject) -> c_int];
impl_api![41; PyArray_GetNumericOps() -> *mut PyObject];
// Unused slot 40, was `PyArray_SetNumericOps`
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can add a comment here that slots 1-39 are type objects


/// See [PY_ARRAY_API] for more.
pub struct PyArrayAPI(PyOnceLock<*const *const c_void>);
pub struct PyArrayAPI(PyOnceLock<NonNull<*const c_void>>);
Copy link
Member

Choose a reason for hiding this comment

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

Very nice 👍 Always great to have invariant encoded in the type system.

}

/// Returns whether the runtime `numpy` version is 2.0 or greater.
pub fn is_numpy_2<'py>(py: Python<'py>) -> bool {
Copy link
Member

Choose a reason for hiding this comment

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

I think we need to look at the usages of this. These are used to conditionally cast structures, which we should not do anymore now that we are targeting a single ABI, right?

@@ -1,2 +1,2 @@
//! Low-Level bindings for NumPy C API.
//!
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can add a comment here that we are targeting the numpy 2 abi, and that is is abi compatible with numpy 1 for the api subset that numpy v2 exposes.

}

};
impl_array_type! {
Copy link
Member

Choose a reason for hiding this comment

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

Smart, I like the syntax and that this can now support returning type objects from different APIs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants