From b08008c7b0e6fd1fc4eb184cb893d95af78faa1b Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 20 Apr 2022 17:24:26 +0200 Subject: [PATCH 01/11] Add the semi-stable C-API tier, with PEP-523 functions & PyCode_New* --- Include/README.rst | 6 ++++-- Include/cpython/ceval.h | 7 +++++- Include/cpython/code.h | 47 ++++++++++++++++++++++++++++++++++------- Include/pyport.h | 9 ++++++++ Lib/test/test_code.py | 6 +++--- Objects/codeobject.c | 9 ++++---- Python/ceval.c | 2 +- 7 files changed, 67 insertions(+), 19 deletions(-) diff --git a/Include/README.rst b/Include/README.rst index f52e690eac9a91..531f09692f783f 100644 --- a/Include/README.rst +++ b/Include/README.rst @@ -1,11 +1,13 @@ The Python C API ================ -The C API is divided into three sections: +The C API is divided into these sections: 1. ``Include/``: Limited API 2. ``Include/cpython/``: CPython implementation details -3. ``Include/internal/``: The internal API +3. ``Include/cpython/``, names with the ``PyUnstable_`` prefix: API that can + change between minor releases +4. ``Include/internal/``, and any name with ``_`` prefix: The internal API Information on changing the C API is available `in the developer guide`_ diff --git a/Include/cpython/ceval.h b/Include/cpython/ceval.h index 74665c9fa10580..0fbbee10c2edce 100644 --- a/Include/cpython/ceval.h +++ b/Include/cpython/ceval.h @@ -22,7 +22,12 @@ PyAPI_FUNC(PyObject *) _PyEval_EvalFrameDefault(PyThreadState *tstate, struct _P PyAPI_FUNC(void) _PyEval_SetSwitchInterval(unsigned long microseconds); PyAPI_FUNC(unsigned long) _PyEval_GetSwitchInterval(void); -PyAPI_FUNC(Py_ssize_t) _PyEval_RequestCodeExtraIndex(freefunc); +PyAPI_FUNC(Py_ssize_t) PyUnstable_Eval_RequestCodeExtraIndex(freefunc); +// Old name -- remove when this API changes: +_Py_DEPRECATED_EXTERNALLY(3.12) static inline Py_ssize_t +_PyEval_RequestCodeExtraIndex(freefunc f) { + return PyUnstable_Eval_RequestCodeExtraIndex(f); +} PyAPI_FUNC(int) _PyEval_SliceIndex(PyObject *, Py_ssize_t *); PyAPI_FUNC(int) _PyEval_SliceIndexNotNone(PyObject *, Py_ssize_t *); diff --git a/Include/cpython/code.h b/Include/cpython/code.h index ebac0b12a461bc..8aaecdce1eb20f 100644 --- a/Include/cpython/code.h +++ b/Include/cpython/code.h @@ -151,19 +151,40 @@ PyAPI_DATA(PyTypeObject) PyCode_Type; #define _PyCode_CODE(CO) ((_Py_CODEUNIT *)(CO)->co_code_adaptive) #define _PyCode_NBYTES(CO) (Py_SIZE(CO) * (Py_ssize_t)sizeof(_Py_CODEUNIT)) -/* Public interface */ -PyAPI_FUNC(PyCodeObject *) PyCode_New( +/* Unstable public interface */ +PyAPI_FUNC(PyCodeObject *) PyUnstable_Code_New( int, int, int, int, int, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *, int, PyObject *, PyObject *); -PyAPI_FUNC(PyCodeObject *) PyCode_NewWithPosOnlyArgs( +PyAPI_FUNC(PyCodeObject *) PyUnstable_Code_NewWithPosOnlyArgs( int, int, int, int, int, int, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *, PyObject *, int, PyObject *, PyObject *); /* same as struct above */ +// Old names -- remove when this API changes: +_Py_DEPRECATED_EXTERNALLY(3.12) static inline PyCodeObject * +PyCode_New( + int a, int b, int c, int d, int e, PyObject *f, PyObject *g, + PyObject *h, PyObject *i, PyObject *j, PyObject *k, + PyObject *l, PyObject *m, PyObject *n, int o, PyObject *p, + PyObject *q) +{ + return PyUnstable_Code_New( + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q); +} +_Py_DEPRECATED_EXTERNALLY(3.12) static inline PyCodeObject * +PyCode_NewWithPosOnlyArgs( + int a, int poac, int b, int c, int d, int e, PyObject *f, PyObject *g, + PyObject *h, PyObject *i, PyObject *j, PyObject *k, + PyObject *l, PyObject *m, PyObject *n, int o, PyObject *p, + PyObject *q) +{ + return PyUnstable_Code_NewWithPosOnlyArgs( + a, poac, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q); +} /* Creates a new empty code object with the specified source location. */ PyAPI_FUNC(PyCodeObject *) @@ -207,11 +228,21 @@ PyAPI_FUNC(PyObject*) _PyCode_ConstantKey(PyObject *obj); PyAPI_FUNC(PyObject*) PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names, PyObject *lnotab); - -PyAPI_FUNC(int) _PyCode_GetExtra(PyObject *code, Py_ssize_t index, - void **extra); -PyAPI_FUNC(int) _PyCode_SetExtra(PyObject *code, Py_ssize_t index, - void *extra); +PyAPI_FUNC(int) PyUnstable_Code_GetExtra( + PyObject *code, Py_ssize_t index, void **extra); +PyAPI_FUNC(int) PyUnstable_Code_SetExtra( + PyObject *code, Py_ssize_t index, void *extra); +// Old names -- remove when this API changes: +_Py_DEPRECATED_EXTERNALLY(3.12) static inline int +_PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra) +{ + return PyUnstable_Code_GetExtra(code, index, extra); +} +_Py_DEPRECATED_EXTERNALLY(3.12) static inline int +_PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra) +{ + return PyUnstable_Code_SetExtra(code, index, extra); +} /* Equivalent to getattr(code, 'co_code') in Python. Returns a strong reference to a bytes object. */ diff --git a/Include/pyport.h b/Include/pyport.h index b3ff2f4882e90f..0becfe8f0e866a 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -319,6 +319,15 @@ extern "C" { #define Py_DEPRECATED(VERSION_UNUSED) #endif +// _Py_DEPRECATED_EXTERNALLY(version) +// Deprecated outside CPython core. +#ifdef Py_BUILD_CORE +#define _Py_DEPRECATED_EXTERNALLY(VERSION_UNUSED) +#else +#define _Py_DEPRECATED_EXTERNALLY(version) Py_DEPRECATED(version) +#endif + + #if defined(__clang__) #define _Py_COMP_DIAG_PUSH _Pragma("clang diagnostic push") #define _Py_COMP_DIAG_IGNORE_DEPR_DECLS \ diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py index 4e4d82314a9fb8..cccf8d1766923d 100644 --- a/Lib/test/test_code.py +++ b/Lib/test/test_code.py @@ -720,15 +720,15 @@ def f(): py = ctypes.pythonapi freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp) - RequestCodeExtraIndex = py._PyEval_RequestCodeExtraIndex + RequestCodeExtraIndex = py.PyUnstable_Eval_RequestCodeExtraIndex RequestCodeExtraIndex.argtypes = (freefunc,) RequestCodeExtraIndex.restype = ctypes.c_ssize_t - SetExtra = py._PyCode_SetExtra + SetExtra = py.PyUnstable_Code_SetExtra SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp) SetExtra.restype = ctypes.c_int - GetExtra = py._PyCode_GetExtra + GetExtra = py.PyUnstable_Code_GetExtra GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.POINTER(ctypes.c_voidp)) GetExtra.restype = ctypes.c_int diff --git a/Objects/codeobject.c b/Objects/codeobject.c index fc1db72977aa01..c1cef8c6136fcd 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -503,7 +503,8 @@ _PyCode_New(struct _PyCodeConstructor *con) ******************/ PyCodeObject * -PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount, +PyUnstable_Code_NewWithPosOnlyArgs( + int argcount, int posonlyargcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, @@ -627,7 +628,7 @@ PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount, } PyCodeObject * -PyCode_New(int argcount, int kwonlyargcount, +PyUnstable_Code_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, @@ -1321,7 +1322,7 @@ typedef struct { int -_PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra) +PyUnstable_Code_GetExtra(PyObject *code, Py_ssize_t index, void **extra) { if (!PyCode_Check(code)) { PyErr_BadInternalCall(); @@ -1342,7 +1343,7 @@ _PyCode_GetExtra(PyObject *code, Py_ssize_t index, void **extra) int -_PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra) +PyUnstable_Code_SetExtra(PyObject *code, Py_ssize_t index, void *extra) { PyInterpreterState *interp = _PyInterpreterState_GET(); diff --git a/Python/ceval.c b/Python/ceval.c index 80bfa21ad0b6f0..3b2d07114ccabd 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3452,7 +3452,7 @@ format_awaitable_error(PyThreadState *tstate, PyTypeObject *type, int oparg) Py_ssize_t -_PyEval_RequestCodeExtraIndex(freefunc free) +PyUnstable_Eval_RequestCodeExtraIndex(freefunc free) { PyInterpreterState *interp = _PyInterpreterState_GET(); Py_ssize_t new_index; From 15e978b3ef871e897b88b43937889699492c0a92 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 20 Apr 2022 18:49:59 +0200 Subject: [PATCH 02/11] Document unstable API and functions that need it --- Doc/c-api/code.rst | 113 ++++++++++++++++++++++++++++++++++++++++--- Doc/c-api/stable.rst | 19 +++++++- 2 files changed, 124 insertions(+), 8 deletions(-) diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 9054e7ee3181a5..72b13f8f8198a1 100644 --- a/Doc/c-api/code.rst +++ b/Doc/c-api/code.rst @@ -33,28 +33,49 @@ bound into a function. Return the number of free variables in *co*. -.. c:function:: PyCodeObject* PyCode_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable) +.. c:function:: PyCodeObject* PyUnstable_Code_New(int argcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable) Return a new code object. If you need a dummy code object to create a frame, - use :c:func:`PyCode_NewEmpty` instead. Calling :c:func:`PyCode_New` directly - will bind you to a precise Python version since the definition of the bytecode - changes often. The many arguments of this function are inter-dependent in complex + use :c:func:`PyCode_NewEmpty` instead. + + Since the definition of the bytecode changes often, calling + :c:func:`PyCode_New` directly can bind you to a precise Python version. + This function is part of the semi-stable C API. + See :c:macro:`Py_USING_SEMI_STABLE_API` for usage. + + The many arguments of this function are inter-dependent in complex ways, meaning that subtle changes to values are likely to result in incorrect execution or VM crashes. Use this function only with extreme care. .. versionchanged:: 3.11 Added ``exceptiontable`` parameter. -.. c:function:: PyCodeObject* PyCode_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable) + .. index:: single: PyCode_New + + .. versionchanged:: 3.12 + + Renamed from ``PyCode_New`` as part of :ref:`unstable-c-api`. + The old name is deprecated, but will remain available until the + signature changes again. + +.. c:function:: PyCodeObject* PyUnstable_Code_NewWithPosOnlyArgs(int argcount, int posonlyargcount, int kwonlyargcount, int nlocals, int stacksize, int flags, PyObject *code, PyObject *consts, PyObject *names, PyObject *varnames, PyObject *freevars, PyObject *cellvars, PyObject *filename, PyObject *name, int firstlineno, PyObject *linetable, PyObject *exceptiontable) Similar to :c:func:`PyCode_New`, but with an extra "posonlyargcount" for positional-only arguments. The same caveats that apply to ``PyCode_New`` also apply to this function. - .. versionadded:: 3.8 + .. index:: single: PyCode_NewWithPosOnlyArgs + + .. versionadded:: 3.8 as ``PyCode_NewWithPosOnlyArgs`` .. versionchanged:: 3.11 Added ``exceptiontable`` parameter. + .. versionchanged:: 3.12 + + Renamed to ``PyUnstable_Code_NewWithPosOnlyArgs``. + The old name is deprecated, but will remain available until the + signature changes again. + .. c:function:: PyCodeObject* PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno) Return a new empty code object with the specified filename, @@ -115,3 +136,83 @@ bound into a function. the free variables. On error, ``NULL`` is returned and an exception is raised. .. versionadded:: 3.11 + + +Extra information +----------------- + +To support low-level extensions to frame evaluation, such as external +just-in-time compilers, it is possible to attach arbitrary extra data to +code objects. + +This functionality is a CPython implementation detail, and the API +may change without deprecation warnings. +These functions are part of the semi-stable C API. +See :c:macro:`Py_USING_SEMI_STABLE_API` for details. + +See :pep:`523` for motivation and initial specification behind this API. + + +.. c:function:: Py_ssize_t PyUnstable_Eval_RequestCodeExtraIndex(freefunc free) + + Return a new an opaque index value used to adding data to code objects. + + You generally call this function once (per interpreter) and use the result + with ``PyCode_GetExtra`` and ``PyCode_SetExtra`` to manipulate + data on individual code objects. + + If *free* is not ``NULL``: when a code object is deallocated, + *free* will be called on non-``NULL`` data stored under the new index. + Use :c:func:`Py_DecRef` when storing :c:type:`PyObject`. + + Part of the semi-stable API, see :c:macro:`Py_USING_SEMI_STABLE_API` + for usage. + + .. index:: single: _PyEval_RequestCodeExtraIndex + + .. versionadded:: 3.6 as ``_PyEval_RequestCodeExtraIndex`` + + .. versionchanged:: 3.12 + + Renamed to ``PyUnstable_Eval_RequestCodeExtraIndex``. + The old private name is deprecated, but will be available until the API + changes. + +.. c:function:: int PyUnstable_Code_GetExtra(PyObject *code, Py_ssize_t index, void **extra) + + Set *extra* to the extra data stored under the given index. + Return 0 on success. Set an exception and return -1 on failure. + + If no data was set under the index, set *extra* to ``NULL`` and return + 0 without setting an exception. + + Part of the semi-stable API, see :c:macro:`Py_USING_SEMI_STABLE_API` + for usage. + + .. index:: single: _PyCode_GetExtra + + .. versionadded:: 3.6 as ``_PyCode_GetExtra`` + + .. versionchanged:: 3.12 + + Renamed to ``PyUnstable_Code_GetExtra``. + The old private name is deprecated, but will be available until the API + changes. + +.. c:function:: int PyUnstable_Code_SetExtra(PyObject *code, Py_ssize_t index, void *extra) + + Set the extra data stored under the given index to *extra*. + Return 0 on success. Set an exception and return -1 on failure. + + Part of the semi-stable API, see :c:macro:`Py_USING_SEMI_STABLE_API` + for usage. + + .. index:: single: _PyCode_SetExtra + + .. versionadded:: 3.6 as ``_PyCode_SetExtra`` + + .. versionchanged:: 3.12 + + Renamed to ``PyUnstable_Code_SetExtra``. + The old private name is deprecated, but will be available until the API + changes. diff --git a/Doc/c-api/stable.rst b/Doc/c-api/stable.rst index 4ae20e93e36785..1348ddde5d6724 100644 --- a/Doc/c-api/stable.rst +++ b/Doc/c-api/stable.rst @@ -7,8 +7,7 @@ C API Stability *************** Python's C API is covered by the Backwards Compatibility Policy, :pep:`387`. -While the C API will change with every minor release (e.g. from 3.9 to 3.10), -most changes will be source-compatible, typically by only adding new API. +Most changes to it are source-compatible (typically by only adding new API). Changing existing API or removing API is only done after a deprecation period or to fix serious issues. @@ -21,6 +20,22 @@ but will need to be compiled separately for 3.9.x and 3.10.x. Names prefixed by an underscore, such as ``_Py_InternalState``, are private API that can change without notice even in patch releases. +.. _unstable-c-api:: + +Unstable C API +================= + +.. index:: single: PyUnstable + +Any API named with the ``PyUnstable`` prefix exposes CPython implementation +details, and may change in every minor release (e.g. from 3.9 to 3.10) without +any deprecation warnings. + +It is generally intended for low-level tools like debuggers. + +Projects that use this API are expected to follow +CPython development and spend extra effort adjusting to changes. + Stable Application Binary Interface =================================== From 290e405b468ca98329db611d37919933c349055d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 21 Apr 2022 15:29:57 +0200 Subject: [PATCH 03/11] Add PyUnstable_FrameEvalFunction & related functions to unstable API --- Doc/c-api/init.rst | 34 ++++++++++++++++++++++++++++------ Include/cpython/pystate.h | 23 +++++++++++++++++++---- Python/pystate.c | 6 +++--- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index afb17719a77ab2..944ac343e4b40e 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1350,34 +1350,56 @@ All of the following functions must be called after :c:func:`Py_Initialize`. .. versionadded:: 3.8 -.. c:type:: PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag) +.. c:type:: PyObject* (*PyUnstable_FrameEvalFunction)(PyThreadState *tstate, PyUnstable_InterpreterFrame *frame, int throwflag) Type of a frame evaluation function. The *throwflag* parameter is used by the ``throw()`` method of generators: if non-zero, handle the current exception. + .. index:: single: _PyFrameEvalFunction + + .. versionadded:: 3.6 as ``_PyFrameEvalFunction`` + .. versionchanged:: 3.9 The function now takes a *tstate* parameter. .. versionchanged:: 3.11 - The *frame* parameter changed from ``PyFrameObject*`` to ``_PyInterpreterFrame*``. + The *frame* parameter changed from ``PyFrameObject*`` to ``PyUnstable_InterpreterFrame*`` + (named ``_PyInterpreterFrame*`` in Python 3.11). + + .. versionchanged:: 3.12 + Renamed to ``PyUnstable_FrameEvalFunction``. + The old private name is deprecated, but will be available until the API + changes. -.. c:function:: _PyFrameEvalFunction _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) +.. c:function:: PyUnstable_FrameEvalFunction PyUnstable_InterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) Get the frame evaluation function. See the :pep:`523` "Adding a frame evaluation API to CPython". - .. versionadded:: 3.9 + .. versionadded:: 3.9 as ``_PyInterpreterState_GetEvalFrameFunc`` + + .. versionchanged:: 3.12 -.. c:function:: void _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, _PyFrameEvalFunction eval_frame) + Renamed to ``PyUnstable_InterpreterState_GetEvalFrameFunc``. + The old private name is deprecated, but will be available until the API + changes. + +.. c:function:: void PyUnstable_InterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, PyUnstable_FrameEvalFunction eval_frame) Set the frame evaluation function. See the :pep:`523` "Adding a frame evaluation API to CPython". - .. versionadded:: 3.9 + .. versionadded:: 3.9 as ``_PyInterpreterState_SetEvalFrameFunc`` + + .. versionchanged:: 3.12 + + Renamed to ``PyUnstable_InterpreterState_SetEvalFrameFunc``. + The old private name is deprecated, but will be available until the API + changes. .. c:function:: PyObject* PyThreadState_GetDict() diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index c51542bcc895cb..18105da1e71c2c 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -298,13 +298,28 @@ PyAPI_FUNC(void) PyThreadState_DeleteCurrent(void); /* Frame evaluation API */ -typedef PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, struct _PyInterpreterFrame *, int); +typedef PyObject* (*PyUnstable_FrameEvalFunction)(PyThreadState *tstate, struct _PyInterpreterFrame *, int); +// Old name, remove when the API changes: +_Py_DEPRECATED_EXTERNALLY(3.12) typedef PyUnstable_FrameEvalFunction _PyFrameEvalFunction; -PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc( +PyAPI_FUNC(PyUnstable_FrameEvalFunction) PyUnstable_InterpreterState_GetEvalFrameFunc( PyInterpreterState *interp); -PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc( +PyAPI_FUNC(void) PyUnstable_InterpreterState_SetEvalFrameFunc( PyInterpreterState *interp, - _PyFrameEvalFunction eval_frame); + PyUnstable_FrameEvalFunction eval_frame); +// Old names, remove when the API changes: +_Py_DEPRECATED_EXTERNALLY(3.12) static inline PyUnstable_FrameEvalFunction +_PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) { + return PyUnstable_InterpreterState_GetEvalFrameFunc(interp); +} +_Py_DEPRECATED_EXTERNALLY(3.12) static inline void +_PyInterpreterState_SetEvalFrameFunc( + PyInterpreterState *interp, + PyUnstable_FrameEvalFunction eval_frame) +{ + PyUnstable_InterpreterState_SetEvalFrameFunc(interp, eval_frame); +} + PyAPI_FUNC(const PyConfig*) _PyInterpreterState_GetConfig(PyInterpreterState *interp); diff --git a/Python/pystate.c b/Python/pystate.c index 19fd9a6ae4497b..ff13c6f68df3a3 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2133,7 +2133,7 @@ _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry) _PyFrameEvalFunction -_PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) +PyUnstable_InterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) { if (interp->eval_frame == NULL) { return _PyEval_EvalFrameDefault; @@ -2143,8 +2143,8 @@ _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) void -_PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, - _PyFrameEvalFunction eval_frame) +PyUnstable_InterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, + _PyFrameEvalFunction eval_frame) { if (eval_frame == _PyEval_EvalFrameDefault) { interp->eval_frame = NULL; From fce0d99e7a9792c62ab38b59172c9334c10670bd Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 21 Apr 2022 15:30:57 +0200 Subject: [PATCH 04/11] Docs: Expand the intro for C API stability --- Doc/c-api/stable.rst | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Doc/c-api/stable.rst b/Doc/c-api/stable.rst index 1348ddde5d6724..9a1e04eebec170 100644 --- a/Doc/c-api/stable.rst +++ b/Doc/c-api/stable.rst @@ -6,7 +6,8 @@ C API Stability *************** -Python's C API is covered by the Backwards Compatibility Policy, :pep:`387`. +Unless documented otherwise, Python's C API is covered by the Backwards +Compatibility Policy, :pep:`387`. Most changes to it are source-compatible (typically by only adding new API). Changing existing API or removing API is only done after a deprecation period or to fix serious issues. @@ -17,13 +18,25 @@ way; see :ref:`stable-abi-platform` below). So, code compiled for Python 3.10.0 will work on 3.10.8 and vice versa, but will need to be compiled separately for 3.9.x and 3.10.x. +There are two tiers of API with different stability exepectations: + +- *Unstable API*, ``PyUnstable`` prefix, may change in minor versions + without a deprecation period. +- *Limited API*, enabled by defining :c:macro:`Py_LIMITED_API`, + is compatible across several minor releases. + +These are discussed in more detail below. + Names prefixed by an underscore, such as ``_Py_InternalState``, are private API that can change without notice even in patch releases. +If you need to use this API, consider reaching out to +`CPython developers `_ to discusss adding +external API for your use case. -.. _unstable-c-api:: +.. _unstable-c-api: Unstable C API -================= +============== .. index:: single: PyUnstable From 2d4c2d4ba96a8e1560f8baba51a9a73fdc6f67dd Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 21 Apr 2022 17:26:46 +0200 Subject: [PATCH 05/11] Add blurb --- .../next/C API/2022-04-21-17-25-22.gh-issue-91744.FgvaMi.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/C API/2022-04-21-17-25-22.gh-issue-91744.FgvaMi.rst diff --git a/Misc/NEWS.d/next/C API/2022-04-21-17-25-22.gh-issue-91744.FgvaMi.rst b/Misc/NEWS.d/next/C API/2022-04-21-17-25-22.gh-issue-91744.FgvaMi.rst new file mode 100644 index 00000000000000..20db25ddd0c1f6 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-04-21-17-25-22.gh-issue-91744.FgvaMi.rst @@ -0,0 +1,3 @@ +Introduced the *Unstable C API tier*, marking APi that is allowed to change +in minor releases without a deprecation period. +See :pep:`689` for details. From 22c48fe6af95fb4ad87d327bc6444c3764205e79 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 21 Apr 2022 17:41:11 +0200 Subject: [PATCH 06/11] Add What's New entry --- Doc/whatsnew/3.12.rst | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index a9b69c2ebf43bf..b5fe29f5da2ed6 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -705,6 +705,41 @@ C API Changes New Features ------------ + +* :pep:`697`: Introduced the :ref:`Unstable C API tier `, + intended for low-level tools like debuggers and JIT compilers. + This API may change in each minor release of CPython without deprecation + warnings. + Its contents are marked by the ``PyUnstable_`` prefix in names. + + The following API will be moved to the Unstable tier in the initial + implementation as proof of the concept. + + Code object constructors: + + - ``PyUnstable_Code_New()`` (renamed from ``PyCode_New``) + - ``PyUnstable_Code_NewWithPosOnlyArgs()`` (renamed from ``PyCode_NewWithPosOnlyArgs``) + + Frame evaluation API (:pep:`523`): + + - ``PyUnstable_FrameEvalFunction`` (renamed from ``_PyFrameEvalFunction``) + - ``PyUnstable_InterpreterState_GetEvalFrameFunc()`` (renamed from ``_PyInterpreterState_GetEvalFrameFunc``) + - ``PyUnstable_InterpreterState_SetEvalFrameFunc()`` (renamed from ``_PyInterpreterState_SetEvalFrameFunc``) + - ``PyUnstable_Eval_RequestCodeExtraIndex()`` (renamed from ``_PyEval_RequestCodeExtraIndex``) + - ``PyUnstable_Code_GetExtra()`` (renamed from ``_PyCode_GetExtra``) + - ``PyUnstable_Code_SetExtra()`` (renamed from ``_PyCode_SetExtra``) + - ``PyUnstable_InterpreterFrame`` (typedef for ``_PyInterpreterFrame``, as an opaque struct) + - ``PyUnstable_Frame_GetFrameObject`` (renamed from ``_PyFrame_GetFrameObject``) + - ``PyUnstable_EvalFrameDefault`` + (new function that calls ``_PyEval_EvalFrameDefault``, but takes + ``PyFrameObject`` rather than ``_PyInterpreterFrame``) + + The original names will continue to be available until the respective + API changes. + + (Contributed by Petr Viktorin, Victor Stinner and Nick Coghlan in + :gh:`91744`.) + * Added the new limited C API function :c:func:`PyType_FromMetaclass`, which generalizes the existing :c:func:`PyType_FromModuleAndSpec` using an additional metaclass argument. From 8b6910d66ab67e356c107e8630da89f1e7e4b864 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 16 Nov 2022 17:18:36 +0100 Subject: [PATCH 07/11] Address some review suggestions --- Doc/c-api/code.rst | 2 +- Doc/c-api/stable.rst | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 72b13f8f8198a1..7fe35bc8fdc678 100644 --- a/Doc/c-api/code.rst +++ b/Doc/c-api/code.rst @@ -40,7 +40,7 @@ bound into a function. Since the definition of the bytecode changes often, calling :c:func:`PyCode_New` directly can bind you to a precise Python version. - This function is part of the semi-stable C API. + This function is part of the semi-stable C API. See :c:macro:`Py_USING_SEMI_STABLE_API` for usage. The many arguments of this function are inter-dependent in complex diff --git a/Doc/c-api/stable.rst b/Doc/c-api/stable.rst index 9a1e04eebec170..0d75d431bb600b 100644 --- a/Doc/c-api/stable.rst +++ b/Doc/c-api/stable.rst @@ -6,7 +6,7 @@ C API Stability *************** -Unless documented otherwise, Python's C API is covered by the Backwards +Unless documented otherwise, Python's C API is covered by the Backwards Compatibility Policy, :pep:`387`. Most changes to it are source-compatible (typically by only adding new API). Changing existing API or removing API is only done after a deprecation period @@ -18,12 +18,13 @@ way; see :ref:`stable-abi-platform` below). So, code compiled for Python 3.10.0 will work on 3.10.8 and vice versa, but will need to be compiled separately for 3.9.x and 3.10.x. -There are two tiers of API with different stability exepectations: +There are two tiers of C API with different stability exepectations: -- *Unstable API*, ``PyUnstable`` prefix, may change in minor versions - without a deprecation period. -- *Limited API*, enabled by defining :c:macro:`Py_LIMITED_API`, - is compatible across several minor releases. +- *Unstable API*, may change in minor versions without a deprecation period. + It is marked by the ``PyUnstable`` prefix in names. +- *Limited API*, is compatible across several minor releases. + When :c:macro:`Py_LIMITED_API` is defined, only this subset is exposed + from ``Python.h``. These are discussed in more detail below. From 0cc5ad17592e1e56340b1896796191e014e9811c Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 16 Nov 2022 17:35:01 +0100 Subject: [PATCH 08/11] Fix some leftovers from the previous PEP version --- Doc/c-api/code.rst | 19 ++----------------- Doc/c-api/stable.rst | 2 +- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 7fe35bc8fdc678..f6dd9b51d05ac7 100644 --- a/Doc/c-api/code.rst +++ b/Doc/c-api/code.rst @@ -40,8 +40,6 @@ bound into a function. Since the definition of the bytecode changes often, calling :c:func:`PyCode_New` directly can bind you to a precise Python version. - This function is part of the semi-stable C API. - See :c:macro:`Py_USING_SEMI_STABLE_API` for usage. The many arguments of this function are inter-dependent in complex ways, meaning that subtle changes to values are likely to result in incorrect @@ -145,13 +143,9 @@ To support low-level extensions to frame evaluation, such as external just-in-time compilers, it is possible to attach arbitrary extra data to code objects. -This functionality is a CPython implementation detail, and the API +These functions are part of the unstable C API tier: +this functionality is a CPython implementation detail, and the API may change without deprecation warnings. -These functions are part of the semi-stable C API. -See :c:macro:`Py_USING_SEMI_STABLE_API` for details. - -See :pep:`523` for motivation and initial specification behind this API. - .. c:function:: Py_ssize_t PyUnstable_Eval_RequestCodeExtraIndex(freefunc free) @@ -165,9 +159,6 @@ See :pep:`523` for motivation and initial specification behind this API. *free* will be called on non-``NULL`` data stored under the new index. Use :c:func:`Py_DecRef` when storing :c:type:`PyObject`. - Part of the semi-stable API, see :c:macro:`Py_USING_SEMI_STABLE_API` - for usage. - .. index:: single: _PyEval_RequestCodeExtraIndex .. versionadded:: 3.6 as ``_PyEval_RequestCodeExtraIndex`` @@ -186,9 +177,6 @@ See :pep:`523` for motivation and initial specification behind this API. If no data was set under the index, set *extra* to ``NULL`` and return 0 without setting an exception. - Part of the semi-stable API, see :c:macro:`Py_USING_SEMI_STABLE_API` - for usage. - .. index:: single: _PyCode_GetExtra .. versionadded:: 3.6 as ``_PyCode_GetExtra`` @@ -204,9 +192,6 @@ See :pep:`523` for motivation and initial specification behind this API. Set the extra data stored under the given index to *extra*. Return 0 on success. Set an exception and return -1 on failure. - Part of the semi-stable API, see :c:macro:`Py_USING_SEMI_STABLE_API` - for usage. - .. index:: single: _PyCode_SetExtra .. versionadded:: 3.6 as ``_PyCode_SetExtra`` diff --git a/Doc/c-api/stable.rst b/Doc/c-api/stable.rst index 0d75d431bb600b..748a694d9f9f97 100644 --- a/Doc/c-api/stable.rst +++ b/Doc/c-api/stable.rst @@ -32,7 +32,7 @@ Names prefixed by an underscore, such as ``_Py_InternalState``, are private API that can change without notice even in patch releases. If you need to use this API, consider reaching out to `CPython developers `_ to discusss adding -external API for your use case. +public API for your use case. .. _unstable-c-api: From 9756a55a30c54636502d7f1a9586c27b3ec4cee2 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 16 Nov 2022 17:59:31 +0100 Subject: [PATCH 09/11] Automatically add admonitions to unstable C API --- Doc/tools/extensions/c_annotations.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Doc/tools/extensions/c_annotations.py b/Doc/tools/extensions/c_annotations.py index 9defb24a0331ef..e5dc82cf0dc2d9 100644 --- a/Doc/tools/extensions/c_annotations.py +++ b/Doc/tools/extensions/c_annotations.py @@ -143,6 +143,22 @@ def add_annotations(self, app, doctree): ' (Only some members are part of the stable ABI.)') node.insert(0, emph_node) + # Unstable API annotation. + if name.startswith('PyUnstable'): + warn_node = nodes.admonition( + classes=['unstable-c-api', 'warning']) + message = 'This is ' + emph_node = nodes.emphasis(message, message) + ref_node = addnodes.pending_xref( + 'Unstable API', refdomain="std", + reftarget='unstable-c-api', + reftype='ref', refexplicit="False") + ref_node += nodes.Text('Unstable API') + emph_node += ref_node + emph_node += nodes.Text('. It may change without warning in minor releases.') + warn_node += emph_node + node.insert(0, warn_node) + # Return value annotation if objtype != 'function': continue From dbeadf65f5765d1c43d4023263e9d8c36087d9ee Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Tue, 22 Nov 2022 16:02:23 +0100 Subject: [PATCH 10/11] Revert frame eval changes --- Doc/c-api/init.rst | 34 ++++++---------------------------- Doc/whatsnew/3.12.rst | 10 +--------- Include/cpython/pystate.h | 24 +++--------------------- Python/pystate.c | 6 +++--- 4 files changed, 13 insertions(+), 61 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 944ac343e4b40e..afb17719a77ab2 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -1350,56 +1350,34 @@ All of the following functions must be called after :c:func:`Py_Initialize`. .. versionadded:: 3.8 -.. c:type:: PyObject* (*PyUnstable_FrameEvalFunction)(PyThreadState *tstate, PyUnstable_InterpreterFrame *frame, int throwflag) +.. c:type:: PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag) Type of a frame evaluation function. The *throwflag* parameter is used by the ``throw()`` method of generators: if non-zero, handle the current exception. - .. index:: single: _PyFrameEvalFunction - - .. versionadded:: 3.6 as ``_PyFrameEvalFunction`` - .. versionchanged:: 3.9 The function now takes a *tstate* parameter. .. versionchanged:: 3.11 - The *frame* parameter changed from ``PyFrameObject*`` to ``PyUnstable_InterpreterFrame*`` - (named ``_PyInterpreterFrame*`` in Python 3.11). - - .. versionchanged:: 3.12 - Renamed to ``PyUnstable_FrameEvalFunction``. - The old private name is deprecated, but will be available until the API - changes. + The *frame* parameter changed from ``PyFrameObject*`` to ``_PyInterpreterFrame*``. -.. c:function:: PyUnstable_FrameEvalFunction PyUnstable_InterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) +.. c:function:: _PyFrameEvalFunction _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) Get the frame evaluation function. See the :pep:`523` "Adding a frame evaluation API to CPython". - .. versionadded:: 3.9 as ``_PyInterpreterState_GetEvalFrameFunc`` - - .. versionchanged:: 3.12 - - Renamed to ``PyUnstable_InterpreterState_GetEvalFrameFunc``. - The old private name is deprecated, but will be available until the API - changes. + .. versionadded:: 3.9 -.. c:function:: void PyUnstable_InterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, PyUnstable_FrameEvalFunction eval_frame) +.. c:function:: void _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, _PyFrameEvalFunction eval_frame) Set the frame evaluation function. See the :pep:`523` "Adding a frame evaluation API to CPython". - .. versionadded:: 3.9 as ``_PyInterpreterState_SetEvalFrameFunc`` - - .. versionchanged:: 3.12 - - Renamed to ``PyUnstable_InterpreterState_SetEvalFrameFunc``. - The old private name is deprecated, but will be available until the API - changes. + .. versionadded:: 3.9 .. c:function:: PyObject* PyThreadState_GetDict() diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index b5fe29f5da2ed6..68be87c5ed46c9 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -720,19 +720,11 @@ New Features - ``PyUnstable_Code_New()`` (renamed from ``PyCode_New``) - ``PyUnstable_Code_NewWithPosOnlyArgs()`` (renamed from ``PyCode_NewWithPosOnlyArgs``) - Frame evaluation API (:pep:`523`): + Extra storage for code objects (:pep:`523`): - - ``PyUnstable_FrameEvalFunction`` (renamed from ``_PyFrameEvalFunction``) - - ``PyUnstable_InterpreterState_GetEvalFrameFunc()`` (renamed from ``_PyInterpreterState_GetEvalFrameFunc``) - - ``PyUnstable_InterpreterState_SetEvalFrameFunc()`` (renamed from ``_PyInterpreterState_SetEvalFrameFunc``) - ``PyUnstable_Eval_RequestCodeExtraIndex()`` (renamed from ``_PyEval_RequestCodeExtraIndex``) - ``PyUnstable_Code_GetExtra()`` (renamed from ``_PyCode_GetExtra``) - ``PyUnstable_Code_SetExtra()`` (renamed from ``_PyCode_SetExtra``) - - ``PyUnstable_InterpreterFrame`` (typedef for ``_PyInterpreterFrame``, as an opaque struct) - - ``PyUnstable_Frame_GetFrameObject`` (renamed from ``_PyFrame_GetFrameObject``) - - ``PyUnstable_EvalFrameDefault`` - (new function that calls ``_PyEval_EvalFrameDefault``, but takes - ``PyFrameObject`` rather than ``_PyInterpreterFrame``) The original names will continue to be available until the respective API changes. diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index 18105da1e71c2c..9295211261e0bb 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -298,28 +298,10 @@ PyAPI_FUNC(void) PyThreadState_DeleteCurrent(void); /* Frame evaluation API */ -typedef PyObject* (*PyUnstable_FrameEvalFunction)(PyThreadState *tstate, struct _PyInterpreterFrame *, int); -// Old name, remove when the API changes: -_Py_DEPRECATED_EXTERNALLY(3.12) typedef PyUnstable_FrameEvalFunction _PyFrameEvalFunction; - -PyAPI_FUNC(PyUnstable_FrameEvalFunction) PyUnstable_InterpreterState_GetEvalFrameFunc( - PyInterpreterState *interp); -PyAPI_FUNC(void) PyUnstable_InterpreterState_SetEvalFrameFunc( - PyInterpreterState *interp, - PyUnstable_FrameEvalFunction eval_frame); -// Old names, remove when the API changes: -_Py_DEPRECATED_EXTERNALLY(3.12) static inline PyUnstable_FrameEvalFunction -_PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) { - return PyUnstable_InterpreterState_GetEvalFrameFunc(interp); -} -_Py_DEPRECATED_EXTERNALLY(3.12) static inline void -_PyInterpreterState_SetEvalFrameFunc( - PyInterpreterState *interp, - PyUnstable_FrameEvalFunction eval_frame) -{ - PyUnstable_InterpreterState_SetEvalFrameFunc(interp, eval_frame); -} +typedef PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, struct _PyInterpreterFrame *, int); +PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp); +PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, _PyFrameEvalFunction eval_frame); PyAPI_FUNC(const PyConfig*) _PyInterpreterState_GetConfig(PyInterpreterState *interp); diff --git a/Python/pystate.c b/Python/pystate.c index ff13c6f68df3a3..19fd9a6ae4497b 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -2133,7 +2133,7 @@ _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry) _PyFrameEvalFunction -PyUnstable_InterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) +_PyInterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) { if (interp->eval_frame == NULL) { return _PyEval_EvalFrameDefault; @@ -2143,8 +2143,8 @@ PyUnstable_InterpreterState_GetEvalFrameFunc(PyInterpreterState *interp) void -PyUnstable_InterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, - _PyFrameEvalFunction eval_frame) +_PyInterpreterState_SetEvalFrameFunc(PyInterpreterState *interp, + _PyFrameEvalFunction eval_frame) { if (eval_frame == _PyEval_EvalFrameDefault) { interp->eval_frame = NULL; From 42e60cddd9efd10db0788e2937d1eb71fcd406c1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 23 Nov 2022 16:30:37 +0100 Subject: [PATCH 11/11] Test code extra storage --- Modules/Setup.stdlib.in | 2 +- Modules/_testcapi/code.c | 115 ++++++++++++++++++++++++++++++ Modules/_testcapi/parts.h | 1 + Modules/_testcapimodule.c | 3 + PCbuild/_testcapi.vcxproj | 1 + PCbuild/_testcapi.vcxproj.filters | 3 + 6 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 Modules/_testcapi/code.c diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index d64752e8ca9609..3cdf7ac92d232e 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -169,7 +169,7 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c _testcapi/code.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c # Some testing modules MUST be built as shared libraries. diff --git a/Modules/_testcapi/code.c b/Modules/_testcapi/code.c new file mode 100644 index 00000000000000..588dc67971ea5a --- /dev/null +++ b/Modules/_testcapi/code.c @@ -0,0 +1,115 @@ +#include "parts.h" + +static Py_ssize_t +get_code_extra_index(PyInterpreterState* interp) { + Py_ssize_t result = -1; + + static const char *key = "_testcapi.frame_evaluation.code_index"; + + PyObject *interp_dict = PyInterpreterState_GetDict(interp); // borrowed + assert(interp_dict); // real users would handle missing dict... somehow + + PyObject *index_obj = PyDict_GetItemString(interp_dict, key); // borrowed + Py_ssize_t index = 0; + if (!index_obj) { + if (PyErr_Occurred()) { + goto finally; + } + index = PyUnstable_Eval_RequestCodeExtraIndex(NULL); + if (index < 0 || PyErr_Occurred()) { + goto finally; + } + index_obj = PyLong_FromSsize_t(index); // strong ref + if (!index_obj) { + goto finally; + } + int res = PyDict_SetItemString(interp_dict, key, index_obj); + Py_DECREF(index_obj); + if (res < 0) { + goto finally; + } + } + else { + index = PyLong_AsSsize_t(index_obj); + if (index == -1 && PyErr_Occurred()) { + goto finally; + } + } + + result = index; +finally: + return result; +} + +static PyObject * +test_code_extra(PyObject* self, PyObject *Py_UNUSED(callable)) +{ + PyObject *result = NULL; + PyObject *test_module = NULL; + PyObject *test_func = NULL; + + // Get or initialize interpreter-specific code object storage index + PyInterpreterState *interp = PyInterpreterState_Get(); + if (!interp) { + return NULL; + } + Py_ssize_t code_extra_index = get_code_extra_index(interp); + if (PyErr_Occurred()) { + goto finally; + } + + // Get a function to test with + // This can be any Python function. Use `test.test_misc.testfunction`. + test_module = PyImport_ImportModule("test.test_capi.test_misc"); + if (!test_module) { + goto finally; + } + test_func = PyObject_GetAttrString(test_module, "testfunction"); + if (!test_func) { + goto finally; + } + PyObject *test_func_code = PyFunction_GetCode(test_func); // borrowed + if (!test_func_code) { + goto finally; + } + + // Check the value is initially NULL + void *extra; + int res = PyUnstable_Code_GetExtra(test_func_code, code_extra_index, &extra); + if (res < 0) { + goto finally; + } + assert (extra == NULL); + + // Set another code extra value + res = PyUnstable_Code_SetExtra(test_func_code, code_extra_index, (void*)(uintptr_t)77); + if (res < 0) { + goto finally; + } + // Assert it was set correctly + res = PyUnstable_Code_GetExtra(test_func_code, code_extra_index, &extra); + if (res < 0) { + goto finally; + } + assert ((uintptr_t)extra == 77); + + result = Py_NewRef(Py_None); +finally: + Py_XDECREF(test_module); + Py_XDECREF(test_func); + return result; +} + +static PyMethodDef TestMethods[] = { + {"test_code_extra", test_code_extra, METH_NOARGS}, + {NULL}, +}; + +int +_PyTestCapi_Init_Code(PyObject *m) { + if (PyModule_AddFunctions(m, TestMethods) < 0) { + return -1; + } + + return 0; +} diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index 7ba3c4ebff8cde..822aacd7d25862 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -36,6 +36,7 @@ int _PyTestCapi_Init_Watchers(PyObject *module); int _PyTestCapi_Init_Long(PyObject *module); int _PyTestCapi_Init_Float(PyObject *module); int _PyTestCapi_Init_Structmember(PyObject *module); +int _PyTestCapi_Init_Code(PyObject *module); #ifdef LIMITED_API_AVAILABLE int _PyTestCapi_Init_VectorcallLimited(PyObject *module); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 83eef73a875d9d..6f894d2f883e38 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4054,6 +4054,9 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Structmember(m) < 0) { return NULL; } + if (_PyTestCapi_Init_Code(m) < 0) { + return NULL; + } #ifndef LIMITED_API_AVAILABLE PyModule_AddObjectRef(m, "LIMITED_API_AVAILABLE", Py_False); diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index 58bf4e1eacbf21..0e4d4b92e6c834 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -107,6 +107,7 @@ + diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index 101c5322761634..da4dfa764cf109 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -51,6 +51,9 @@ Source Files + + Source Files +