summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Viktorin <encukou@gmail.com>2023-02-28 08:31:01 (GMT)
committerGitHub <noreply@github.com>2023-02-28 08:31:01 (GMT)
commit6b2d7c0ddb4d2afe298ee7c8f6a23c400f1465fd (patch)
treefa34a8d7217783a1e9069144b94d369cfb573052
parentc41af812c948ec3cb07b2ef7a46a238f5cab3dc2 (diff)
downloadcpython-6b2d7c0ddb4d2afe298ee7c8f6a23c400f1465fd.zip
cpython-6b2d7c0ddb4d2afe298ee7c8f6a23c400f1465fd.tar.gz
cpython-6b2d7c0ddb4d2afe298ee7c8f6a23c400f1465fd.tar.bz2
gh-101101: Unstable C API tier (PEP 689) (GH-101102)
-rw-r--r--Doc/c-api/code.rst98
-rw-r--r--Doc/c-api/stable.rst36
-rw-r--r--Doc/tools/extensions/c_annotations.py16
-rw-r--r--Doc/whatsnew/3.12.rst23
-rw-r--r--Include/README.rst6
-rw-r--r--Include/cpython/ceval.h7
-rw-r--r--Include/cpython/code.h47
-rw-r--r--Include/pyport.h9
-rw-r--r--Lib/test/test_code.py6
-rw-r--r--Misc/NEWS.d/next/C API/2022-04-21-17-25-22.gh-issue-91744.FgvaMi.rst3
-rw-r--r--Modules/Setup.stdlib.in2
-rw-r--r--Modules/_testcapi/code.c115
-rw-r--r--Modules/_testcapi/parts.h1
-rw-r--r--Modules/_testcapimodule.c3
-rw-r--r--Objects/codeobject.c9
-rw-r--r--PCbuild/_testcapi.vcxproj1
-rw-r--r--PCbuild/_testcapi.vcxproj.filters3
-rw-r--r--Python/ceval.c2
18 files changed, 358 insertions, 29 deletions
diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst
index ae75d68..062ef3a 100644
--- a/Doc/c-api/code.rst
+++ b/Doc/c-api/code.rst
@@ -33,28 +33,47 @@ 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.
+
+ 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,
@@ -165,3 +184,70 @@ bound into a function.
:c:func:`PyErr_WriteUnraisable`. Otherwise it should return ``0``.
.. versionadded:: 3.12
+
+
+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.
+
+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.
+
+.. 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`.
+
+ .. 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.
+
+ .. 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.
+
+ .. 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 4ae20e9..3721fc0 100644
--- a/Doc/c-api/stable.rst
+++ b/Doc/c-api/stable.rst
@@ -6,9 +6,9 @@
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.
+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.
@@ -18,8 +18,38 @@ 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 C API with different stability exepectations:
+
+- *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.
+
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 <https://discuss.python.org/c/core-dev/c-api/30>`_
+to discuss adding public API for your use case.
+
+.. _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.
+However, it will not change in a bugfix release (e.g. from 3.10.0 to 3.10.1).
+
+It is generally intended for specialized, 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
diff --git a/Doc/tools/extensions/c_annotations.py b/Doc/tools/extensions/c_annotations.py
index 9defb24..e5dc82c 100644
--- a/Doc/tools/extensions/c_annotations.py
+++ b/Doc/tools/extensions/c_annotations.py
@@ -143,6 +143,22 @@ class Annotations:
' (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
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index 1a25ec6..c0c021c 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -810,6 +810,29 @@ C API Changes
New Features
------------
+
+* :pep:`697`: Introduced the :ref:`Unstable C API tier <unstable-c-api>`,
+ 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.
+
+ Code object constructors:
+
+ - ``PyUnstable_Code_New()`` (renamed from ``PyCode_New``)
+ - ``PyUnstable_Code_NewWithPosOnlyArgs()`` (renamed from ``PyCode_NewWithPosOnlyArgs``)
+
+ Extra storage for code objects (:pep:`523`):
+
+ - ``PyUnstable_Eval_RequestCodeExtraIndex()`` (renamed from ``_PyEval_RequestCodeExtraIndex``)
+ - ``PyUnstable_Code_GetExtra()`` (renamed from ``_PyCode_GetExtra``)
+ - ``PyUnstable_Code_SetExtra()`` (renamed from ``_PyCode_SetExtra``)
+
+ The original names will continue to be available until the respective
+ API changes.
+
+ (Contributed by Petr Viktorin in :gh:`101101`.)
+
* Added the new limited C API function :c:func:`PyType_FromMetaclass`,
which generalizes the existing :c:func:`PyType_FromModuleAndSpec` using
an additional metaclass argument.
diff --git a/Include/README.rst b/Include/README.rst
index f52e690..531f096 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 74665c9..0fbbee1 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 fba9296..0e4bd8a 100644
--- a/Include/cpython/code.h
+++ b/Include/cpython/code.h
@@ -178,19 +178,40 @@ static inline int PyCode_GetFirstFree(PyCodeObject *op) {
#define _PyCode_CODE(CO) _Py_RVALUE((_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 *)
@@ -269,11 +290,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 40092c2..eef0fe1 100644
--- a/Include/pyport.h
+++ b/Include/pyport.h
@@ -323,6 +323,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 9c2ac83..0cd1fb3 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -752,15 +752,15 @@ if check_impl_detail(cpython=True) and ctypes is not None:
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/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 0000000..20db25d
--- /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.
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 7551e5b..b12290d 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 _testcapi/exceptions.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/exceptions.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 0000000..588dc67
--- /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 1689f18..c8f31dc 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -37,6 +37,7 @@ int _PyTestCapi_Init_Long(PyObject *module);
int _PyTestCapi_Init_Float(PyObject *module);
int _PyTestCapi_Init_Structmember(PyObject *module);
int _PyTestCapi_Init_Exceptions(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 fc716a3..10e507d 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -4083,6 +4083,9 @@ PyInit__testcapi(void)
if (_PyTestCapi_Init_Exceptions(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/Objects/codeobject.c b/Objects/codeobject.c
index a03b14e..175bd57 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -567,7 +567,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,
@@ -691,7 +692,7 @@ error:
}
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,
@@ -1371,7 +1372,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();
@@ -1392,7 +1393,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/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 742eb3e..4cc184b 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -108,6 +108,7 @@
<ClCompile Include="..\Modules\_testcapi\long.c" />
<ClCompile Include="..\Modules\_testcapi\structmember.c" />
<ClCompile Include="..\Modules\_testcapi\exceptions.c" />
+ <ClCompile Include="..\Modules\_testcapi\code.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index ab5afc1..fbdaf04 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -54,6 +54,9 @@
<ClCompile Include="..\Modules\_testcapi\exceptions.c">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\Modules\_testcapi\code.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\PC\python_nt.rc">
diff --git a/Python/ceval.c b/Python/ceval.c
index b382d21..5540c93 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -2988,7 +2988,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;