Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

### Changed
* Multi-iterator constructors now return a status corresponding to allocation success or failure, and raise `MemoryError` instead of `ValueError` [gh-373](https://github.com/IntelPython/mkl_fft/pull/373)
* `_direct_fftnd` now also checks the status returned by the backend instead of discarding it [gh-373](https://github.com/IntelPython/mkl_fft/pull/373)

### Fixed
* Declared `f_ndim` as a C `int` in `_allocate_result` so the buffer size is computed in C rather than through a Python object, resolving a Coverity out-of-bounds (OVERRUN) false positive [gh-364](https://github.com/IntelPython/mkl_fft/pull/364)
* Silenced a Coverity `UNUSED_VALUE` finding in `__create_descriptor_1d` by marking the `DftiFreeDescriptor` status (used only by a debug-only `assert`) as intentionally unused [gh-365](https://github.com/IntelPython/mkl_fft/pull/365)
* Fixed possible memory leaks when `PyMem_Malloc` fails, and raise `MemoryError` [gh-373](https://github.com/IntelPython/mkl_fft/pull/373)
* Fixed possible memory leaks when multi-iterator constructors fail [gh-373](https://github.com/IntelPython/mkl_fft/pull/373)
* Fixed N-D transforms returning a success status when a scratch allocation fails [gh-373](https://github.com/IntelPython/mkl_fft/pull/373)

## [2.3.2] - 2026-08-04

Expand Down
127 changes: 76 additions & 51 deletions mkl_fft/_pydfti.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,27 @@ cdef void _capsule_destructor(object caps) noexcept:


def _tls_dfti_cache_capsule():
cdef DftiCache *_cache_struct
cdef DftiCache *_cache_struct = NULL

init = getattr(_tls, "initialized", None)
if (init is None):
capsule = getattr(_tls, "capsule", None)
if (capsule is None):
_cache_struct = <DftiCache *> PyMem_Malloc(sizeof(DftiCache))
# important to initialized
if (_cache_struct is NULL):
raise MemoryError(
"Failed to allocate memory for DFTI descriptor cache"
)
# important to initialize
_cache_struct.initialized = 0
_cache_struct.hand = NULL
_tls.initialized = True
_tls.capsule = cpython.pycapsule.PyCapsule_New(
<void *>_cache_struct, capsule_name, &_capsule_destructor
)
capsule = getattr(_tls, "capsule", None)
try:
capsule = cpython.pycapsule.PyCapsule_New(
<void *>_cache_struct, capsule_name, &_capsule_destructor
)
finally:
if capsule is None:
PyMem_Free(_cache_struct)
# only publish to TLS once the capsule owns the allocation
_tls.capsule = capsule
if (not cpython.pycapsule.PyCapsule_IsValid(capsule, capsule_name)):
raise ValueError("Internal Error: invalid capsule stored in TLS")
return capsule
Expand Down Expand Up @@ -157,6 +165,17 @@ cdef extern from "src/mklfft.h":
int float_cfloat_mkl_ifftnd_out(cnp.ndarray, cnp.ndarray, double)
int double_cdouble_mkl_ifftnd_out(cnp.ndarray, cnp.ndarray, double)
char * mkl_dfti_error(int)
int DFTI_MEMORY_ERROR


cdef _raise_dfti_error(int status):
"""Raise the exception described by a non-zero MKL DFTI status"""
cdef bytes error_msg = mkl_dfti_error(status)

message = f"Internal error occurred: {error_msg.decode()}"
if status == DFTI_MEMORY_ERROR:
raise MemoryError(message)
raise ValueError(message)


# Initialize numpy
Expand Down Expand Up @@ -199,15 +218,24 @@ cdef cnp.ndarray _pad_array(
b_ndim = cnp.PyArray_NDIM(x_arr)

b_shape = <cnp.npy_intp*> PyMem_Malloc(b_ndim * sizeof(cnp.npy_intp))
memcpy(b_shape, cnp.PyArray_DIMS(x_arr), b_ndim * sizeof(cnp.npy_intp))
b_shape[axis] = n
if (b_shape is NULL):
raise MemoryError("Failed to allocate memory for the array shape")

try:
memcpy(
b_shape, cnp.PyArray_DIMS(x_arr), b_ndim * sizeof(cnp.npy_intp)
)
b_shape[axis] = n

# allocating temporary buffer
x_arr_is_fortran = cnp.PyArray_CHKFLAGS(x_arr, cnp.NPY_ARRAY_F_CONTIGUOUS)
b_arr = <cnp.ndarray> cnp.PyArray_EMPTY(
b_ndim, b_shape, <cnp.NPY_TYPES> b_type, x_arr_is_fortran
) # 0 for C-contiguous
PyMem_Free(b_shape)
# allocating temporary buffer
x_arr_is_fortran = cnp.PyArray_CHKFLAGS(
x_arr, cnp.NPY_ARRAY_F_CONTIGUOUS
)
b_arr = <cnp.ndarray> cnp.PyArray_EMPTY(
b_ndim, b_shape, <cnp.NPY_TYPES> b_type, x_arr_is_fortran
) # 0 for C-contiguous
finally:
PyMem_Free(b_shape)

ind = [slice(0, None, None), ] * b_ndim
ind[axis] = slice(0, cnp.PyArray_DIM(x_arr, axis), None)
Expand Down Expand Up @@ -307,17 +335,26 @@ cdef cnp.ndarray _allocate_result(
f_ndim = cnp.PyArray_NDIM(x_arr)

f_shape = <cnp.npy_intp*> PyMem_Malloc(f_ndim * sizeof(cnp.npy_intp))
memcpy(f_shape, cnp.PyArray_DIMS(x_arr), f_ndim * sizeof(cnp.npy_intp))
# if dimension is negative, do not alter the dimension
if n_ > 0:
f_shape[axis_] = n_

# allocating output buffer
x_arr_is_fortran = cnp.PyArray_CHKFLAGS(x_arr, cnp.NPY_ARRAY_F_CONTIGUOUS)
f_arr = <cnp.ndarray> cnp.PyArray_EMPTY(
f_ndim, f_shape, <cnp.NPY_TYPES> f_type, x_arr_is_fortran
) # 0 for C-contiguous
PyMem_Free(f_shape)
if (f_shape is NULL):
raise MemoryError("Failed to allocate memory for the array shape")

try:
memcpy(
f_shape, cnp.PyArray_DIMS(x_arr), f_ndim * sizeof(cnp.npy_intp)
)
# if dimension is negative, do not alter the dimension
if n_ > 0:
f_shape[axis_] = n_

# allocating output buffer
x_arr_is_fortran = cnp.PyArray_CHKFLAGS(
x_arr, cnp.NPY_ARRAY_F_CONTIGUOUS
)
f_arr = <cnp.ndarray> cnp.PyArray_EMPTY(
f_ndim, f_shape, <cnp.NPY_TYPES> f_type, x_arr_is_fortran
) # 0 for C-contiguous
finally:
PyMem_Free(f_shape)

return f_arr

Expand Down Expand Up @@ -380,8 +417,6 @@ def _c2c_fft1d_impl(x, n=None, axis=-1, direction=+1, double fsc=1.0, out=None):
cdef long n_, axis_
cdef int x_type, f_type, status = 0
cdef int ALL_HARMONICS = 1
cdef char * c_error_msg = NULL
cdef bytes py_error_msg
cdef DftiCache *_cache

if direction not in [-1, +1]:
Expand Down Expand Up @@ -444,9 +479,7 @@ def _c2c_fft1d_impl(x, n=None, axis=-1, direction=+1, double fsc=1.0, out=None):
status = 1

if status:
c_error_msg = mkl_dfti_error(status)
py_error_msg = c_error_msg
raise ValueError("Internal error occurred: {}".format(py_error_msg))
_raise_dfti_error(status)

n_max = <long> cnp.PyArray_DIM(x_arr, axis_)
if (n_ < n_max):
Expand Down Expand Up @@ -543,9 +576,7 @@ def _c2c_fft1d_impl(x, n=None, axis=-1, direction=+1, double fsc=1.0, out=None):
)

if (status):
c_error_msg = mkl_dfti_error(status)
py_error_msg = c_error_msg
raise ValueError("Internal error occurred: {}".format(py_error_msg))
_raise_dfti_error(status)

if out is not None and f_arr is not out:
out[...] = f_arr
Expand All @@ -569,8 +600,6 @@ def _r2c_fft1d_impl(
cdef long n_, axis_
cdef int x_type, f_type, status, requirement
cdef int HALF_HARMONICS = 0 # give only positive index harmonics
cdef char * c_error_msg = NULL
cdef bytes py_error_msg
cdef DftiCache *_cache

x_arr = _process_arguments(x, n, axis, &axis_, &n_, &in_place, &xnd, 1)
Expand Down Expand Up @@ -639,11 +668,7 @@ def _r2c_fft1d_impl(
)

if (status):
c_error_msg = mkl_dfti_error(status)
py_error_msg = c_error_msg
raise ValueError(
"Internal error occurred: {}".format(str(py_error_msg))
)
_raise_dfti_error(status)

if out is not None and f_arr is not out:
out[...] = f_arr
Expand All @@ -666,8 +691,6 @@ def _c2r_fft1d_impl(
cdef int xnd, in_place, int_n
cdef long n_, axis_
cdef int x_type, f_type, status
cdef char * c_error_msg = NULL
cdef bytes py_error_msg
cdef DftiCache *_cache

int_n = _is_integral(n)
Expand Down Expand Up @@ -742,11 +765,7 @@ def _c2r_fft1d_impl(
)

if (status):
c_error_msg = mkl_dfti_error(status)
py_error_msg = c_error_msg
raise ValueError(
"Internal error occurred: {}".format(str(py_error_msg))
)
_raise_dfti_error(status)

if out is not None and f_arr is not out:
out[...] = f_arr
Expand All @@ -759,7 +778,7 @@ def _direct_fftnd(
x, direction=+1, double fsc=1.0, out=None
):
"""Perform n-dimensional FFT over all axes"""
cdef int err
cdef int err = 0
cdef cnp.ndarray x_arr "xxnd_arrayObject"
cdef cnp.ndarray f_arr "ffnd_arrayObject"
cdef int in_place, x_type, f_type
Expand Down Expand Up @@ -822,6 +841,9 @@ def _direct_fftnd(
else:
raise ValueError("An input argument x is not complex type array")

if err:
_raise_dfti_error(err)

return x_arr
else:
if x_type == cnp.NPY_CDOUBLE or x_type == cnp.NPY_DOUBLE:
Expand Down Expand Up @@ -864,6 +886,9 @@ def _direct_fftnd(
else:
raise ValueError("An input argument x is not complex type array")

if err:
_raise_dfti_error(err)

if out is not None and f_arr is not out:
out[...] = f_arr
return out
Expand Down
Loading
Loading