From f66c857572a308822c70fd25e0197b6e0dec6e34 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 2 Feb 2022 17:03:10 +0200 Subject: [PATCH] bpo-45459: Add Py_buffer to limited API (GH-29991) - [x] ``Py_buffer`` struct - [x] ``PyBuffer_*()`` API functions - [x] ``PyBUF_*`` constants - [x] ``Py_bf_getbuffer`` and ``Py_bf_releasebuffer`` type slots - [x] ``PyMemoryView_FromBuffer()`` API - [x] tests for limited API - [x] ``make regen-limited-abi`` - [x] documentation update - [ ] export ``PyPickleBuffer*()`` API ??? --- Doc/c-api/buffer.rst | 7 + Doc/c-api/type.rst | 11 +- Doc/data/stable_abi.dat | 13 ++ Doc/whatsnew/3.11.rst | 20 +++ Include/Python.h | 1 + Include/buffer.h | 142 ++++++++++++++++++ Include/cpython/abstract.h | 68 --------- Include/cpython/object.h | 51 +------ Include/memoryobject.h | 2 +- Include/typeslots.h | 6 - Lib/test/test_stable_abi_ctypes.py | 12 ++ Lib/test/test_xxlimited.py | 11 ++ Makefile.pre.in | 1 + .../2021-10-18-16-54-24.bpo-45459.Y1pEZs.rst | 2 + Misc/stable_abi.txt | 28 ++++ Modules/xxlimited.c | 52 ++++++- PC/python3dll.c | 12 ++ 17 files changed, 308 insertions(+), 131 deletions(-) create mode 100644 Include/buffer.h create mode 100644 Misc/NEWS.d/next/C API/2021-10-18-16-54-24.bpo-45459.Y1pEZs.rst diff --git a/Doc/c-api/buffer.rst b/Doc/c-api/buffer.rst index 820a3a6f990..05e131d06b9 100644 --- a/Doc/c-api/buffer.rst +++ b/Doc/c-api/buffer.rst @@ -499,6 +499,13 @@ Buffer-related functions This function fails if *len* != *src->len*. +.. c:function:: int PyObject_CopyData(Py_buffer *dest, Py_buffer *src) + + Copy data from *src* to *dest* buffer. Can convert between C-style and + or Fortran-style buffers. + + ``0`` is returned on success, ``-1`` on error. + .. c:function:: void PyBuffer_FillContiguousStrides(int ndims, Py_ssize_t *shape, Py_ssize_t *strides, int itemsize, char order) Fill the *strides* array with byte-strides of a :term:`contiguous` (C-style if diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index f96886985e9..97a818ab2cc 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -272,12 +272,6 @@ The following functions and structs are used to create * :c:member:`~PyTypeObject.tp_vectorcall_offset` (see :ref:`PyMemberDef `) - The following fields cannot be set using :c:type:`PyType_Spec` and - :c:type:`PyType_Slot` under the limited API: - - * :c:member:`~PyBufferProcs.bf_getbuffer` - * :c:member:`~PyBufferProcs.bf_releasebuffer` - Setting :c:data:`Py_tp_bases` or :c:data:`Py_tp_base` may be problematic on some platforms. To avoid issues, use the *bases* argument of @@ -287,6 +281,11 @@ The following functions and structs are used to create Slots in :c:type:`PyBufferProcs` in may be set in the unlimited API. + .. versionchanged:: 3.11 + :c:member:`~PyBufferProcs.bf_getbuffer` and + :c:member:`~PyBufferProcs.bf_releasebuffer` are now available + under limited API. + .. c:member:: void *PyType_Slot.pfunc The desired value of the slot. In most cases, this is a pointer diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 02e54e5d7f1..18bbf03187b 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -10,6 +10,14 @@ function,PyArg_ValidateKeywordArguments,3.2, var,PyBaseObject_Type,3.2, function,PyBool_FromLong,3.2, var,PyBool_Type,3.2, +function,PyBuffer_FillContiguousStrides,3.11, +function,PyBuffer_FillInfo,3.11, +function,PyBuffer_FromContiguous,3.11, +function,PyBuffer_GetPointer,3.11, +function,PyBuffer_IsContiguous,3.11, +function,PyBuffer_Release,3.11, +function,PyBuffer_SizeFromFormat,3.11, +function,PyBuffer_ToContiguous,3.11, var,PyByteArrayIter_Type,3.2, function,PyByteArray_AsString,3.2, function,PyByteArray_Concat,3.2, @@ -375,6 +383,7 @@ function,PyMem_Malloc,3.2, function,PyMem_Realloc,3.2, type,PyMemberDef,3.2, var,PyMemberDescr_Type,3.2, +function,PyMemoryView_FromBuffer,3.11, function,PyMemoryView_FromMemory,3.7, function,PyMemoryView_FromObject,3.2, function,PyMemoryView_GetContiguous,3.2, @@ -476,8 +485,10 @@ function,PyObject_CallMethodObjArgs,3.2, function,PyObject_CallNoArgs,3.10, function,PyObject_CallObject,3.2, function,PyObject_Calloc,3.7, +function,PyObject_CheckBuffer,3.11, function,PyObject_CheckReadBuffer,3.2, function,PyObject_ClearWeakRefs,3.2, +function,PyObject_CopyData,3.11, function,PyObject_DelItem,3.2, function,PyObject_DelItemString,3.2, function,PyObject_Dir,3.2, @@ -495,6 +506,7 @@ function,PyObject_GenericSetDict,3.7, function,PyObject_GetAIter,3.10, function,PyObject_GetAttr,3.2, function,PyObject_GetAttrString,3.2, +function,PyObject_GetBuffer,3.11, function,PyObject_GetItem,3.2, function,PyObject_GetIter,3.2, function,PyObject_HasAttr,3.2, @@ -832,6 +844,7 @@ var,Py_UTF8Mode,3.8, function,Py_VaBuildValue,3.2, var,Py_Version,3.11, function,Py_XNewRef,3.10, +type,Py_buffer,3.11, type,Py_intptr_t,3.2, type,Py_ssize_t,3.2, type,Py_uintptr_t,3.2, diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index e7f3dab2b51..3458ad63c9d 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -657,6 +657,26 @@ New Features :c:macro:`PY_VERSION_HEX`. (Contributed by Gabriele N. Tornetta in :issue:`43931`.) +* :c:type:`Py_buffer` and APIs are now part of the limited API and the stable + ABI: + + * :c:func:`PyObject_CheckBuffer` + * :c:func:`PyObject_GetBuffer` + * :c:func:`PyBuffer_GetPointer` + * :c:func:`PyBuffer_SizeFromFormat` + * :c:func:`PyBuffer_ToContiguous` + * :c:func:`PyBuffer_FromContiguous` + * :c:func:`PyBuffer_CopyData` + * :c:func:`PyBuffer_IsContiguous` + * :c:func:`PyBuffer_FillContiguousStrides` + * :c:func:`PyBuffer_FillInfo` + * :c:func:`PyBuffer_Release` + * :c:func:`PyMemoryView_FromBuffer` + * :c:member:`~PyBufferProcs.bf_getbuffer` and + :c:member:`~PyBufferProcs.bf_releasebuffer` type slots + + (Contributed by Christian Heimes in :issue:`45459`.) + Porting to Python 3.11 ---------------------- diff --git a/Include/Python.h b/Include/Python.h index 7260ae5cd0b..5416b04e4bf 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -50,6 +50,7 @@ #include "longobject.h" #include "cpython/longintrepr.h" #include "boolobject.h" +#include "buffer.h" #include "floatobject.h" #include "complexobject.h" #include "rangeobject.h" diff --git a/Include/buffer.h b/Include/buffer.h new file mode 100644 index 00000000000..6893505e66e --- /dev/null +++ b/Include/buffer.h @@ -0,0 +1,142 @@ +/* Public Py_buffer API */ + +#ifndef Py_BUFFER_H +#define Py_BUFFER_H +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030b0000 + +/* === New Buffer API ============================================ + * Limited API and stable ABI since Python 3.11 + * + * Py_buffer struct layout and size is now part of the stable abi3. The + * struct layout and size must not be changed in any way, as it would + * break the ABI. + * + */ + +typedef struct { + void *buf; + PyObject *obj; /* owned reference */ + Py_ssize_t len; + Py_ssize_t itemsize; /* This is Py_ssize_t so it can be + pointed to by strides in simple case.*/ + int readonly; + int ndim; + char *format; + Py_ssize_t *shape; + Py_ssize_t *strides; + Py_ssize_t *suboffsets; + void *internal; +} Py_buffer; + +/* Return 1 if the getbuffer function is available, otherwise return 0. */ +PyAPI_FUNC(int) PyObject_CheckBuffer(PyObject *obj); + +/* This is a C-API version of the getbuffer function call. It checks + to make sure object has the required function pointer and issues the + call. + + Returns -1 and raises an error on failure and returns 0 on success. */ +PyAPI_FUNC(int) PyObject_GetBuffer(PyObject *obj, Py_buffer *view, + int flags); + +/* Get the memory area pointed to by the indices for the buffer given. + Note that view->ndim is the assumed size of indices. */ +PyAPI_FUNC(void *) PyBuffer_GetPointer(const Py_buffer *view, const Py_ssize_t *indices); + +/* Return the implied itemsize of the data-format area from a + struct-style description. */ +PyAPI_FUNC(Py_ssize_t) PyBuffer_SizeFromFormat(const char *format); + +/* Implementation in memoryobject.c */ +PyAPI_FUNC(int) PyBuffer_ToContiguous(void *buf, const Py_buffer *view, + Py_ssize_t len, char order); + +PyAPI_FUNC(int) PyBuffer_FromContiguous(const Py_buffer *view, const void *buf, + Py_ssize_t len, char order); + +/* Copy len bytes of data from the contiguous chunk of memory + pointed to by buf into the buffer exported by obj. Return + 0 on success and return -1 and raise a PyBuffer_Error on + error (i.e. the object does not have a buffer interface or + it is not working). + + If fort is 'F', then if the object is multi-dimensional, + then the data will be copied into the array in + Fortran-style (first dimension varies the fastest). If + fort is 'C', then the data will be copied into the array + in C-style (last dimension varies the fastest). If fort + is 'A', then it does not matter and the copy will be made + in whatever way is more efficient. */ +PyAPI_FUNC(int) PyObject_CopyData(PyObject *dest, PyObject *src); + +/* Copy the data from the src buffer to the buffer of destination. */ +PyAPI_FUNC(int) PyBuffer_IsContiguous(const Py_buffer *view, char fort); + +/*Fill the strides array with byte-strides of a contiguous + (Fortran-style if fort is 'F' or C-style otherwise) + array of the given shape with the given number of bytes + per element. */ +PyAPI_FUNC(void) PyBuffer_FillContiguousStrides(int ndims, + Py_ssize_t *shape, + Py_ssize_t *strides, + int itemsize, + char fort); + +/* Fills in a buffer-info structure correctly for an exporter + that can only share a contiguous chunk of memory of + "unsigned bytes" of the given length. + + Returns 0 on success and -1 (with raising an error) on error. */ +PyAPI_FUNC(int) PyBuffer_FillInfo(Py_buffer *view, PyObject *o, void *buf, + Py_ssize_t len, int readonly, + int flags); + +/* Releases a Py_buffer obtained from getbuffer ParseTuple's "s*". */ +PyAPI_FUNC(void) PyBuffer_Release(Py_buffer *view); + +/* Maximum number of dimensions */ +#define PyBUF_MAX_NDIM 64 + +/* Flags for getting buffers */ +#define PyBUF_SIMPLE 0 +#define PyBUF_WRITABLE 0x0001 + +#ifndef Py_LIMITED_API +/* we used to include an E, backwards compatible alias */ +#define PyBUF_WRITEABLE PyBUF_WRITABLE +#endif + +#define PyBUF_FORMAT 0x0004 +#define PyBUF_ND 0x0008 +#define PyBUF_STRIDES (0x0010 | PyBUF_ND) +#define PyBUF_C_CONTIGUOUS (0x0020 | PyBUF_STRIDES) +#define PyBUF_F_CONTIGUOUS (0x0040 | PyBUF_STRIDES) +#define PyBUF_ANY_CONTIGUOUS (0x0080 | PyBUF_STRIDES) +#define PyBUF_INDIRECT (0x0100 | PyBUF_STRIDES) + +#define PyBUF_CONTIG (PyBUF_ND | PyBUF_WRITABLE) +#define PyBUF_CONTIG_RO (PyBUF_ND) + +#define PyBUF_STRIDED (PyBUF_STRIDES | PyBUF_WRITABLE) +#define PyBUF_STRIDED_RO (PyBUF_STRIDES) + +#define PyBUF_RECORDS (PyBUF_STRIDES | PyBUF_WRITABLE | PyBUF_FORMAT) +#define PyBUF_RECORDS_RO (PyBUF_STRIDES | PyBUF_FORMAT) + +#define PyBUF_FULL (PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT) +#define PyBUF_FULL_RO (PyBUF_INDIRECT | PyBUF_FORMAT) + + +#define PyBUF_READ 0x100 +#define PyBUF_WRITE 0x200 + +#endif /* !Py_LIMITED_API || Py_LIMITED_API >= 3.11 */ + +#ifdef __cplusplus +} +#endif +#endif /* Py_BUFFER_H */ diff --git a/Include/cpython/abstract.h b/Include/cpython/abstract.h index 2876a7bb84f..b5a31392f2e 100644 --- a/Include/cpython/abstract.h +++ b/Include/cpython/abstract.h @@ -168,74 +168,6 @@ PyAPI_FUNC(int) _PyObject_HasLen(PyObject *o); value. If one of the calls fails, this function returns -1. */ PyAPI_FUNC(Py_ssize_t) PyObject_LengthHint(PyObject *o, Py_ssize_t); -/* === New Buffer API ============================================ */ - -/* Return 1 if the getbuffer function is available, otherwise return 0. */ -PyAPI_FUNC(int) PyObject_CheckBuffer(PyObject *obj); - -/* This is a C-API version of the getbuffer function call. It checks - to make sure object has the required function pointer and issues the - call. - - Returns -1 and raises an error on failure and returns 0 on success. */ -PyAPI_FUNC(int) PyObject_GetBuffer(PyObject *obj, Py_buffer *view, - int flags); - -/* Get the memory area pointed to by the indices for the buffer given. - Note that view->ndim is the assumed size of indices. */ -PyAPI_FUNC(void *) PyBuffer_GetPointer(const Py_buffer *view, const Py_ssize_t *indices); - -/* Return the implied itemsize of the data-format area from a - struct-style description. */ -PyAPI_FUNC(Py_ssize_t) PyBuffer_SizeFromFormat(const char *format); - -/* Implementation in memoryobject.c */ -PyAPI_FUNC(int) PyBuffer_ToContiguous(void *buf, const Py_buffer *view, - Py_ssize_t len, char order); - -PyAPI_FUNC(int) PyBuffer_FromContiguous(const Py_buffer *view, const void *buf, - Py_ssize_t len, char order); - -/* Copy len bytes of data from the contiguous chunk of memory - pointed to by buf into the buffer exported by obj. Return - 0 on success and return -1 and raise a PyBuffer_Error on - error (i.e. the object does not have a buffer interface or - it is not working). - - If fort is 'F', then if the object is multi-dimensional, - then the data will be copied into the array in - Fortran-style (first dimension varies the fastest). If - fort is 'C', then the data will be copied into the array - in C-style (last dimension varies the fastest). If fort - is 'A', then it does not matter and the copy will be made - in whatever way is more efficient. */ -PyAPI_FUNC(int) PyObject_CopyData(PyObject *dest, PyObject *src); - -/* Copy the data from the src buffer to the buffer of destination. */ -PyAPI_FUNC(int) PyBuffer_IsContiguous(const Py_buffer *view, char fort); - -/*Fill the strides array with byte-strides of a contiguous - (Fortran-style if fort is 'F' or C-style otherwise) - array of the given shape with the given number of bytes - per element. */ -PyAPI_FUNC(void) PyBuffer_FillContiguousStrides(int ndims, - Py_ssize_t *shape, - Py_ssize_t *strides, - int itemsize, - char fort); - -/* Fills in a buffer-info structure correctly for an exporter - that can only share a contiguous chunk of memory of - "unsigned bytes" of the given length. - - Returns 0 on success and -1 (with raising an error) on error. */ -PyAPI_FUNC(int) PyBuffer_FillInfo(Py_buffer *view, PyObject *o, void *buf, - Py_ssize_t len, int readonly, - int flags); - -/* Releases a Py_buffer obtained from getbuffer ParseTuple's "s*". */ -PyAPI_FUNC(void) PyBuffer_Release(Py_buffer *view); - /* === Sequence protocol ================================================ */ /* Assume tp_as_sequence and sq_item exist and that 'i' does not diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 7b9f3acbc43..1554ac8aef1 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -2,6 +2,8 @@ # error "this header file must not be included directly" #endif +#include "buffer.h" // for Py_buffer, included after PyObject has been defined + PyAPI_FUNC(void) _Py_NewReference(PyObject *op); #ifdef Py_TRACE_REFS @@ -45,61 +47,12 @@ typedef struct _Py_Identifier { #define _Py_static_string(varname, value) static _Py_Identifier varname = _Py_static_string_init(value) #define _Py_IDENTIFIER(varname) _Py_static_string(PyId_##varname, #varname) -/* buffer interface */ -typedef struct bufferinfo { - void *buf; - PyObject *obj; /* owned reference */ - Py_ssize_t len; - Py_ssize_t itemsize; /* This is Py_ssize_t so it can be - pointed to by strides in simple case.*/ - int readonly; - int ndim; - char *format; - Py_ssize_t *shape; - Py_ssize_t *strides; - Py_ssize_t *suboffsets; - void *internal; -} Py_buffer; - typedef int (*getbufferproc)(PyObject *, Py_buffer *, int); typedef void (*releasebufferproc)(PyObject *, Py_buffer *); typedef PyObject *(*vectorcallfunc)(PyObject *callable, PyObject *const *args, size_t nargsf, PyObject *kwnames); -/* Maximum number of dimensions */ -#define PyBUF_MAX_NDIM 64 - -/* Flags for getting buffers */ -#define PyBUF_SIMPLE 0 -#define PyBUF_WRITABLE 0x0001 -/* we used to include an E, backwards compatible alias */ -#define PyBUF_WRITEABLE PyBUF_WRITABLE -#define PyBUF_FORMAT 0x0004 -#define PyBUF_ND 0x0008 -#define PyBUF_STRIDES (0x0010 | PyBUF_ND) -#define PyBUF_C_CONTIGUOUS (0x0020 | PyBUF_STRIDES) -#define PyBUF_F_CONTIGUOUS (0x0040 | PyBUF_STRIDES) -#define PyBUF_ANY_CONTIGUOUS (0x0080 | PyBUF_STRIDES) -#define PyBUF_INDIRECT (0x0100 | PyBUF_STRIDES) - -#define PyBUF_CONTIG (PyBUF_ND | PyBUF_WRITABLE) -#define PyBUF_CONTIG_RO (PyBUF_ND) - -#define PyBUF_STRIDED (PyBUF_STRIDES | PyBUF_WRITABLE) -#define PyBUF_STRIDED_RO (PyBUF_STRIDES) - -#define PyBUF_RECORDS (PyBUF_STRIDES | PyBUF_WRITABLE | PyBUF_FORMAT) -#define PyBUF_RECORDS_RO (PyBUF_STRIDES | PyBUF_FORMAT) - -#define PyBUF_FULL (PyBUF_INDIRECT | PyBUF_WRITABLE | PyBUF_FORMAT) -#define PyBUF_FULL_RO (PyBUF_INDIRECT | PyBUF_FORMAT) - - -#define PyBUF_READ 0x100 -#define PyBUF_WRITE 0x200 -/* End buffer interface */ - typedef struct { /* Number implementations must check *both* diff --git a/Include/memoryobject.h b/Include/memoryobject.h index 0298cc93730..154397ce1e5 100644 --- a/Include/memoryobject.h +++ b/Include/memoryobject.h @@ -25,7 +25,7 @@ PyAPI_FUNC(PyObject *) PyMemoryView_FromObject(PyObject *base); PyAPI_FUNC(PyObject *) PyMemoryView_FromMemory(char *mem, Py_ssize_t size, int flags); #endif -#ifndef Py_LIMITED_API +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030b0000 PyAPI_FUNC(PyObject *) PyMemoryView_FromBuffer(const Py_buffer *info); #endif PyAPI_FUNC(PyObject *) PyMemoryView_GetContiguous(PyObject *base, diff --git a/Include/typeslots.h b/Include/typeslots.h index 5800d0158bc..506b05580de 100644 --- a/Include/typeslots.h +++ b/Include/typeslots.h @@ -1,12 +1,6 @@ /* Do not renumber the file; these numbers are part of the stable ABI. */ -#if defined(Py_LIMITED_API) -/* Disabled, see #10181 */ -#undef Py_bf_getbuffer -#undef Py_bf_releasebuffer -#else #define Py_bf_getbuffer 1 #define Py_bf_releasebuffer 2 -#endif #define Py_mp_ass_subscript 3 #define Py_mp_length 4 #define Py_mp_subscript 5 diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 9fd6b14b023..a49235b81c1 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -28,6 +28,14 @@ SYMBOL_NAMES = ( "PyBaseObject_Type", "PyBool_FromLong", "PyBool_Type", + "PyBuffer_FillContiguousStrides", + "PyBuffer_FillInfo", + "PyBuffer_FromContiguous", + "PyBuffer_GetPointer", + "PyBuffer_IsContiguous", + "PyBuffer_Release", + "PyBuffer_SizeFromFormat", + "PyBuffer_ToContiguous", "PyByteArrayIter_Type", "PyByteArray_AsString", "PyByteArray_Concat", @@ -381,6 +389,7 @@ SYMBOL_NAMES = ( "PyMemberDescr_Type", "PyMember_GetOne", "PyMember_SetOne", + "PyMemoryView_FromBuffer", "PyMemoryView_FromMemory", "PyMemoryView_FromObject", "PyMemoryView_GetContiguous", @@ -470,8 +479,10 @@ SYMBOL_NAMES = ( "PyObject_CallNoArgs", "PyObject_CallObject", "PyObject_Calloc", + "PyObject_CheckBuffer", "PyObject_CheckReadBuffer", "PyObject_ClearWeakRefs", + "PyObject_CopyData", "PyObject_DelItem", "PyObject_DelItemString", "PyObject_Dir", @@ -489,6 +500,7 @@ SYMBOL_NAMES = ( "PyObject_GetAIter", "PyObject_GetAttr", "PyObject_GetAttrString", + "PyObject_GetBuffer", "PyObject_GetItem", "PyObject_GetIter", "PyObject_HasAttr", diff --git a/Lib/test/test_xxlimited.py b/Lib/test/test_xxlimited.py index e3f521d9b04..6dbfb3f4393 100644 --- a/Lib/test/test_xxlimited.py +++ b/Lib/test/test_xxlimited.py @@ -58,6 +58,17 @@ class TestXXLimited(CommonTests, unittest.TestCase): with self.assertRaises(self.module.Error): raise self.module.Error + def test_buffer(self): + xxo = self.module.Xxo() + self.assertEqual(xxo.x_exports, 0) + b1 = memoryview(xxo) + self.assertEqual(xxo.x_exports, 1) + b2 = memoryview(xxo) + self.assertEqual(xxo.x_exports, 2) + b1[0] = 1 + self.assertEqual(b1[0], 1) + self.assertEqual(b2[0], 1) + class TestXXLimited35(CommonTests, unittest.TestCase): module = xxlimited_35 diff --git a/Makefile.pre.in b/Makefile.pre.in index edc5fc3b680..4dcedd684aa 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1439,6 +1439,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/abstract.h \ $(srcdir)/Include/bltinmodule.h \ $(srcdir)/Include/boolobject.h \ + $(srcdir)/Include/buffer.h \ $(srcdir)/Include/bytearrayobject.h \ $(srcdir)/Include/bytesobject.h \ $(srcdir)/Include/ceval.h \ diff --git a/Misc/NEWS.d/next/C API/2021-10-18-16-54-24.bpo-45459.Y1pEZs.rst b/Misc/NEWS.d/next/C API/2021-10-18-16-54-24.bpo-45459.Y1pEZs.rst new file mode 100644 index 00000000000..a8d93227817 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2021-10-18-16-54-24.bpo-45459.Y1pEZs.rst @@ -0,0 +1,2 @@ +:c:type:`Py_buffer` and various ``Py_buffer`` related functions are now +part of the limited API and stable ABI. diff --git a/Misc/stable_abi.txt b/Misc/stable_abi.txt index c4f5318712a..cc3cc56d472 100644 --- a/Misc/stable_abi.txt +++ b/Misc/stable_abi.txt @@ -2191,6 +2191,34 @@ function PyType_GetQualName data PyStructSequence_UnnamedField added 3.11 +# Add stable Py_buffer API in Python 3.11 (https://bugs.python.org/issue45459) +struct Py_buffer + added 3.11 +function PyObject_CheckBuffer + added 3.11 +function PyObject_GetBuffer + added 3.11 +function PyBuffer_GetPointer + added 3.11 +function PyBuffer_SizeFromFormat + added 3.11 +function PyBuffer_ToContiguous + added 3.11 +function PyBuffer_FromContiguous + added 3.11 +function PyObject_CopyData + added 3.11 +function PyBuffer_IsContiguous + added 3.11 +function PyBuffer_FillContiguousStrides + added 3.11 +function PyBuffer_FillInfo + added 3.11 +function PyBuffer_Release + added 3.11 +function PyMemoryView_FromBuffer + added 3.11 + # (Detailed comments aren't really needed for further entries: from here on # we can use version control logs.) diff --git a/Modules/xxlimited.c b/Modules/xxlimited.c index 93895c4f121..16d1b8311c6 100644 --- a/Modules/xxlimited.c +++ b/Modules/xxlimited.c @@ -19,6 +19,7 @@ def __init__(self): # In the C class, "_x_attr" is not accessible from Python code self._x_attr = {} + self._x_exports = 0 def __getattr__(self, name): return self._x_attr[name] @@ -29,6 +30,13 @@ def __delattr__(self, name): del self._x_attr[name] + @property + def x_exports(self): + """Return the number of times an internal buffer is exported.""" + # Each Xxo instance has a 10-byte buffer that can be + # accessed via the buffer interface (e.g. `memoryview`). + return self._x_exports + def demo(o, /): if isinstance(o, str): return o @@ -57,6 +65,9 @@ #define Py_LIMITED_API 0x030b0000 #include "Python.h" +#include + +#define BUFSIZE 10 // Module state typedef struct { @@ -70,7 +81,9 @@ typedef struct { // Instance state typedef struct { PyObject_HEAD - PyObject *x_attr; /* Attributes dictionary */ + PyObject *x_attr; /* Attributes dictionary */ + char x_buffer[BUFSIZE]; /* buffer for Py_buffer */ + Py_ssize_t x_exports; /* how many buffer are exported */ } XxoObject; // XXX: no good way to do this yet @@ -89,6 +102,8 @@ newXxoObject(PyObject *module) return NULL; } self->x_attr = NULL; + memset(self->x_buffer, 0, BUFSIZE); + self->x_exports = 0; return self; } @@ -212,11 +227,43 @@ static PyMethodDef Xxo_methods[] = { {NULL, NULL} /* sentinel */ }; +/* Xxo buffer interface */ + +static int +Xxo_getbuffer(XxoObject *self, Py_buffer *view, int flags) +{ + int res = PyBuffer_FillInfo(view, (PyObject*)self, + (void *)self->x_buffer, BUFSIZE, + 0, flags); + if (res == 0) { + self->x_exports++; + } + return res; +} + +static void +Xxo_releasebuffer(XxoObject *self, Py_buffer *view) +{ + self->x_exports--; +} + +static PyObject * +Xxo_get_x_exports(XxoObject *self, void *c) +{ + return PyLong_FromSsize_t(self->x_exports); +} + /* Xxo type definition */ PyDoc_STRVAR(Xxo_doc, "A class that explicitly stores attributes in an internal dict"); +static PyGetSetDef Xxo_getsetlist[] = { + {"x_exports", (getter) Xxo_get_x_exports, NULL, NULL}, + {NULL}, +}; + + static PyType_Slot Xxo_Type_slots[] = { {Py_tp_doc, (char *)Xxo_doc}, {Py_tp_traverse, Xxo_traverse}, @@ -226,6 +273,9 @@ static PyType_Slot Xxo_Type_slots[] = { {Py_tp_getattro, Xxo_getattro}, {Py_tp_setattro, Xxo_setattro}, {Py_tp_methods, Xxo_methods}, + {Py_bf_getbuffer, Xxo_getbuffer}, + {Py_bf_releasebuffer, Xxo_releasebuffer}, + {Py_tp_getset, Xxo_getsetlist}, {0, 0}, /* sentinel */ }; diff --git a/PC/python3dll.c b/PC/python3dll.c index b2bb1706c4a..70f11dc1905 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -94,6 +94,14 @@ EXPORT_FUNC(PyArg_ValidateKeywordArguments) EXPORT_FUNC(PyArg_VaParse) EXPORT_FUNC(PyArg_VaParseTupleAndKeywords) EXPORT_FUNC(PyBool_FromLong) +EXPORT_FUNC(PyBuffer_FillContiguousStrides) +EXPORT_FUNC(PyBuffer_FillInfo) +EXPORT_FUNC(PyBuffer_FromContiguous) +EXPORT_FUNC(PyBuffer_GetPointer) +EXPORT_FUNC(PyBuffer_IsContiguous) +EXPORT_FUNC(PyBuffer_Release) +EXPORT_FUNC(PyBuffer_SizeFromFormat) +EXPORT_FUNC(PyBuffer_ToContiguous) EXPORT_FUNC(PyByteArray_AsString) EXPORT_FUNC(PyByteArray_Concat) EXPORT_FUNC(PyByteArray_FromObject) @@ -352,6 +360,7 @@ EXPORT_FUNC(PyMem_Malloc) EXPORT_FUNC(PyMem_Realloc) EXPORT_FUNC(PyMember_GetOne) EXPORT_FUNC(PyMember_SetOne) +EXPORT_FUNC(PyMemoryView_FromBuffer) EXPORT_FUNC(PyMemoryView_FromMemory) EXPORT_FUNC(PyMemoryView_FromObject) EXPORT_FUNC(PyMemoryView_GetContiguous) @@ -426,8 +435,10 @@ EXPORT_FUNC(PyObject_CallMethodObjArgs) EXPORT_FUNC(PyObject_CallNoArgs) EXPORT_FUNC(PyObject_CallObject) EXPORT_FUNC(PyObject_Calloc) +EXPORT_FUNC(PyObject_CheckBuffer) EXPORT_FUNC(PyObject_CheckReadBuffer) EXPORT_FUNC(PyObject_ClearWeakRefs) +EXPORT_FUNC(PyObject_CopyData) EXPORT_FUNC(PyObject_DelItem) EXPORT_FUNC(PyObject_DelItemString) EXPORT_FUNC(PyObject_Dir) @@ -445,6 +456,7 @@ EXPORT_FUNC(PyObject_GenericSetDict) EXPORT_FUNC(PyObject_GetAIter) EXPORT_FUNC(PyObject_GetAttr) EXPORT_FUNC(PyObject_GetAttrString) +EXPORT_FUNC(PyObject_GetBuffer) EXPORT_FUNC(PyObject_GetItem) EXPORT_FUNC(PyObject_GetIter) EXPORT_FUNC(PyObject_HasAttr)