Skip to content

Commit 113893f

Browse files
committed
Check compatibility with chosen ABI/API versions
1 parent 821f1ed commit 113893f

4 files changed

Lines changed: 84 additions & 4 deletions

File tree

src/npyffi/array.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::{os::raw::*, ptr::NonNull};
88

99
use libc::FILE;
1010
use pyo3::{
11+
exceptions::PyRuntimeError,
1112
ffi::{self, PyObject, PyTypeObject},
1213
sync::PyOnceLock,
1314
};
@@ -85,7 +86,58 @@ impl PyArrayAPI {
8586
pub(super) unsafe fn get<'py>(&self, py: Python<'py>, offset: isize) -> NonNull<*const c_void> {
8687
let api = self
8788
.0
88-
.get_or_try_init(py, || get_numpy_api(py, mod_name(py)?, CAPSULE_NAME))
89+
.get_or_try_init(py, || -> PyResult<_> {
90+
let api = get_numpy_api(py, mod_name(py)?, CAPSULE_NAME)?;
91+
92+
let module_version = {
93+
// unsigned int PyArray_GetNDArrayCVersion();
94+
let get_abi_version: extern "C" fn() -> c_uint =
95+
api.add(0).cast().read();
96+
get_abi_version()
97+
};
98+
if NPY_VERSION < module_version {
99+
return Err(PyRuntimeError::new_err(format!(
100+
"module compiled against ABI version 0x{:x} but this version of numpy is 0x{:x}",
101+
NPY_VERSION, module_version
102+
)));
103+
}
104+
105+
let module_feature_version = unsafe {
106+
// unsigned int PyArray_GetNDArrayCFeatureVersion();
107+
let get_runtime_version: extern "C" fn() -> c_uint =
108+
api.add(211).cast().read();
109+
get_runtime_version()
110+
};
111+
if NPY_FEATURE_VERSION > module_feature_version {
112+
return Err(PyRuntimeError::new_err(format!(
113+
"module was compiled against NumPy C-API version 0x{:x} (NumPy {}) but the running NumPy has C-API version 0x{:x}",
114+
NPY_FEATURE_VERSION, NPY_FEATURE_VERSION_STRING, module_feature_version
115+
)));
116+
}
117+
118+
let endianess = unsafe {
119+
// int PyArray_GetEndianness();
120+
let get_endianess: extern "C" fn() -> c_int =
121+
api.add(210).cast().read();
122+
get_endianess()
123+
};
124+
125+
#[cfg(target_endian = "big")]
126+
if endianess != NPY_CPU_BIG {
127+
return Err(PyRuntimeError::new_err(
128+
"module compiled as big endian, but detected different endianess at runtime",
129+
));
130+
}
131+
132+
#[cfg(target_endian = "little")]
133+
if endianess != NPY_CPU_LITTLE {
134+
return Err(PyRuntimeError::new_err(
135+
"module compiled as little endian, but detected different endianess at runtime",
136+
));
137+
}
138+
139+
Ok(api)
140+
})
89141
.expect("Failed to access NumPy array API capsule");
90142

91143
api.offset(offset)

src/npyffi/mod.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ use pyo3::{
2020
PyResult, Python,
2121
};
2222

23-
pub const API_VERSION_2_0: c_uint = 0x00000012;
24-
2523
static API_VERSION: PyOnceLock<c_uint> = PyOnceLock::new();
2624

2725
fn get_numpy_api<'py>(
@@ -46,7 +44,7 @@ pub fn is_numpy_2<'py>(py: Python<'py>) -> bool {
4644
let api_version = *API_VERSION.get_or_init(py, || unsafe {
4745
PY_ARRAY_API.PyArray_GetNDArrayCFeatureVersion(py)
4846
});
49-
api_version >= API_VERSION_2_0
47+
api_version >= NPY_2_0_API_VERSION
5048
}
5149

5250
// Implements wrappers for NumPy's Array and UFunc API
@@ -129,12 +127,16 @@ impl_array_type! {
129127

130128
pub mod array;
131129
pub mod flags;
130+
mod npy_common;
131+
mod numpyconfig;
132132
pub mod objects;
133133
pub mod types;
134134
pub mod ufunc;
135135

136136
pub use self::array::*;
137137
pub use self::flags::*;
138+
pub use self::npy_common::*;
139+
pub use self::numpyconfig::*;
138140
pub use self::objects::*;
139141
pub use self::types::*;
140142
pub use self::ufunc::*;

src/npyffi/npy_common.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use std::ffi::c_int;
2+
3+
/// Unknown CPU endianness.
4+
pub const NPY_CPU_UNKNOWN_ENDIAN: c_int = 0;
5+
/// CPU is little-endian.
6+
pub const NPY_CPU_LITTLE: c_int = 1;
7+
/// CPU is big-endian.
8+
pub const NPY_CPU_BIG: c_int = 2;

src/npyffi/numpyconfig.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// This file matches the numpyconfig.h header.
2+
3+
use std::ffi::c_uint;
4+
5+
/// The current target ABI version
6+
const NPY_ABI_VERSION: c_uint = 0x02000000;
7+
8+
/// The current target API version (v1.15)
9+
const NPY_API_VERSION: c_uint = 0x0000000c;
10+
11+
pub(super) const NPY_2_0_API_VERSION: c_uint = 0x00000012;
12+
13+
/// The current version of the `ndarray` object (ABI version).
14+
pub const NPY_VERSION: c_uint = NPY_ABI_VERSION;
15+
/// The current version of C API.
16+
pub const NPY_FEATURE_VERSION: c_uint = NPY_API_VERSION;
17+
/// The string representation of current version C API.
18+
pub const NPY_FEATURE_VERSION_STRING: &str = "1.15";

0 commit comments

Comments
 (0)