Skip to content

Commit 3359cab

Browse files
authored
bpo-43688: Support the limited C API in debug mode (GH-25131)
The limited C API is now supported if Python is built in debug mode (if the Py_DEBUG macro is defined). In the limited C API, the Py_INCREF() and Py_DECREF() functions are now implemented as opaque function calls, rather than accessing directly the PyObject.ob_refcnt member, if Python is built in debug mode and the Py_LIMITED_API macro targets Python 3.10 or newer. It became possible to support the limited C API in debug mode because the PyObject structure is the same in release and debug mode since Python 3.8 (see bpo-36465). The limited C API is still not supported in the --with-trace-refs special build (Py_TRACE_REFS macro).
1 parent 442ad74 commit 3359cab

File tree

6 files changed

+82
-25
lines changed

6 files changed

+82
-25
lines changed

Doc/whatsnew/3.10.rst

+12
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,18 @@ New Features
13151315
to simulate.
13161316
(Contributed by Antoine Pitrou in :issue:`43356`.)
13171317
1318+
* The limited C API is now supported if Python is built in debug mode (if the
1319+
``Py_DEBUG`` macro is defined). In the limited C API, the :c:func:`Py_INCREF`
1320+
and :c:func:`Py_DECREF` functions are now implemented as opaque function
1321+
calls, rather than accessing directly the :c:member:`PyObject.ob_refcnt`
1322+
member, if Python is built in debug mode and the ``Py_LIMITED_API`` macro
1323+
targets Python 3.10 or newer. It became possible to support the limited C API
1324+
in debug mode because the :c:type:`PyObject` structure is the same in release
1325+
and debug mode since Python 3.8 (see :issue:`36465`).
1326+
1327+
The limited C API is still not supported in the ``--with-trace-refs`` special
1328+
build (``Py_TRACE_REFS`` macro).
1329+
(Contributed by Victor Stinner in :issue:`43688`.)
13181330
13191331
Porting to Python 3.10
13201332
----------------------

Include/object.h

+33-14
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ whose size is determined when the object is allocated.
5454

5555
/* Py_DEBUG implies Py_REF_DEBUG. */
5656
#if defined(Py_DEBUG) && !defined(Py_REF_DEBUG)
57-
#define Py_REF_DEBUG
57+
# define Py_REF_DEBUG
5858
#endif
5959

60-
#if defined(Py_LIMITED_API) && defined(Py_REF_DEBUG)
61-
#error Py_LIMITED_API is incompatible with Py_DEBUG, Py_TRACE_REFS, and Py_REF_DEBUG
60+
#if defined(Py_LIMITED_API) && defined(Py_TRACE_REFS)
61+
# error Py_LIMITED_API is incompatible with Py_TRACE_REFS
6262
#endif
6363

6464
/* PyTypeObject structure is defined in cpython/object.h.
@@ -74,8 +74,8 @@ typedef struct _typeobject PyTypeObject;
7474
#define _PyObject_EXTRA_INIT 0, 0,
7575

7676
#else
77-
#define _PyObject_HEAD_EXTRA
78-
#define _PyObject_EXTRA_INIT
77+
# define _PyObject_HEAD_EXTRA
78+
# define _PyObject_EXTRA_INIT
7979
#endif
8080

8181
/* PyObject_HEAD defines the initial segment of every PyObject. */
@@ -427,21 +427,46 @@ PyAPI_FUNC(void) _Py_NegativeRefcount(const char *filename, int lineno,
427427

428428
PyAPI_FUNC(void) _Py_Dealloc(PyObject *);
429429

430+
/*
431+
These are provided as conveniences to Python runtime embedders, so that
432+
they can have object code that is not dependent on Python compilation flags.
433+
*/
434+
PyAPI_FUNC(void) Py_IncRef(PyObject *);
435+
PyAPI_FUNC(void) Py_DecRef(PyObject *);
436+
437+
// Similar to Py_IncRef() and Py_DecRef() but the argument must be non-NULL.
438+
// Private functions used by Py_INCREF() and Py_DECREF().
439+
PyAPI_FUNC(void) _Py_IncRef(PyObject *);
440+
PyAPI_FUNC(void) _Py_DecRef(PyObject *);
441+
430442
static inline void _Py_INCREF(PyObject *op)
431443
{
444+
#if defined(Py_REF_DEBUG) && defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030A0000
445+
// Stable ABI for Python 3.10 built in debug mode.
446+
_Py_IncRef(op);
447+
#else
448+
// Non-limited C API and limited C API for Python 3.9 and older access
449+
// directly PyObject.ob_refcnt.
432450
#ifdef Py_REF_DEBUG
433451
_Py_RefTotal++;
434452
#endif
435453
op->ob_refcnt++;
454+
#endif
436455
}
437456
#define Py_INCREF(op) _Py_INCREF(_PyObject_CAST(op))
438457

439458
static inline void _Py_DECREF(
440-
#ifdef Py_REF_DEBUG
459+
#if defined(Py_REF_DEBUG) && !(defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030A0000)
441460
const char *filename, int lineno,
442461
#endif
443462
PyObject *op)
444463
{
464+
#if defined(Py_REF_DEBUG) && defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030A0000
465+
// Stable ABI for Python 3.10 built in debug mode.
466+
_Py_DecRef(op);
467+
#else
468+
// Non-limited C API and limited C API for Python 3.9 and older access
469+
// directly PyObject.ob_refcnt.
445470
#ifdef Py_REF_DEBUG
446471
_Py_RefTotal--;
447472
#endif
@@ -455,8 +480,9 @@ static inline void _Py_DECREF(
455480
else {
456481
_Py_Dealloc(op);
457482
}
483+
#endif
458484
}
459-
#ifdef Py_REF_DEBUG
485+
#if defined(Py_REF_DEBUG) && !(defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030A0000)
460486
# define Py_DECREF(op) _Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
461487
#else
462488
# define Py_DECREF(op) _Py_DECREF(_PyObject_CAST(op))
@@ -525,13 +551,6 @@ static inline void _Py_XDECREF(PyObject *op)
525551

526552
#define Py_XDECREF(op) _Py_XDECREF(_PyObject_CAST(op))
527553

528-
/*
529-
These are provided as conveniences to Python runtime embedders, so that
530-
they can have object code that is not dependent on Python compilation flags.
531-
*/
532-
PyAPI_FUNC(void) Py_IncRef(PyObject *);
533-
PyAPI_FUNC(void) Py_DecRef(PyObject *);
534-
535554
// Create a new strong reference to an object:
536555
// increment the reference count of the object and return the object.
537556
PyAPI_FUNC(PyObject*) Py_NewRef(PyObject *obj);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
The limited C API is now supported if Python is built in debug mode (if the
2+
``Py_DEBUG`` macro is defined). In the limited C API, the :c:func:`Py_INCREF`
3+
and :c:func:`Py_DECREF` functions are now implemented as opaque function calls,
4+
rather than accessing directly the :c:member:`PyObject.ob_refcnt` member, if
5+
Python is built in debug mode and the ``Py_LIMITED_API`` macro targets Python
6+
3.10 or newer. It became possible to support the limited C API in debug mode
7+
because the :c:type:`PyObject` structure is the same in release and debug mode
8+
since Python 3.8 (see :issue:`36465`).
9+
10+
The limited C API is still not supported in the ``--with-trace-refs`` special
11+
build (``Py_TRACE_REFS`` macro).
12+
13+
Patch by Victor Stinner.

Objects/object.c

+17
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
#include "frameobject.h"
1414
#include "interpreteridobject.h"
1515

16+
#ifdef Py_LIMITED_API
17+
// Prevent recursive call _Py_IncRef() <=> Py_INCREF()
18+
# error "Py_LIMITED_API macro must not be defined"
19+
#endif
20+
1621
#ifdef __cplusplus
1722
extern "C" {
1823
#endif
@@ -136,6 +141,18 @@ Py_DecRef(PyObject *o)
136141
Py_XDECREF(o);
137142
}
138143

144+
void
145+
_Py_IncRef(PyObject *o)
146+
{
147+
Py_INCREF(o);
148+
}
149+
150+
void
151+
_Py_DecRef(PyObject *o)
152+
{
153+
Py_DECREF(o);
154+
}
155+
139156
PyObject *
140157
PyObject_Init(PyObject *op, PyTypeObject *tp)
141158
{

PC/python3dll.c

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ EXPORT_FUNC(_PyTrash_deposit_object)
3636
EXPORT_FUNC(_PyTrash_destroy_chain)
3737
EXPORT_FUNC(_PyTrash_thread_deposit_object)
3838
EXPORT_FUNC(_PyTrash_thread_destroy_chain)
39+
EXPORT_FUNC(_Py_IncRef)
40+
EXPORT_FUNC(_Py_DecRef)
3941
EXPORT_FUNC(Py_AddPendingCall)
4042
EXPORT_FUNC(Py_AtExit)
4143
EXPORT_FUNC(Py_BuildValue)

setup.py

+5-11
Original file line numberDiff line numberDiff line change
@@ -1864,17 +1864,11 @@ def detect_modules(self):
18641864
## # Uncomment these lines if you want to play with xxmodule.c
18651865
## self.add(Extension('xx', ['xxmodule.c']))
18661866

1867-
if 'd' not in sysconfig.get_config_var('ABIFLAGS'):
1868-
# Non-debug mode: Build xxlimited with limited API
1869-
self.add(Extension('xxlimited', ['xxlimited.c'],
1870-
define_macros=[('Py_LIMITED_API', '0x030a0000')]))
1871-
self.add(Extension('xxlimited_35', ['xxlimited_35.c'],
1872-
define_macros=[('Py_LIMITED_API', '0x03050000')]))
1873-
else:
1874-
# Debug mode: Build xxlimited with the full API
1875-
# (which is compatible with the limited one)
1876-
self.add(Extension('xxlimited', ['xxlimited.c']))
1877-
self.add(Extension('xxlimited_35', ['xxlimited_35.c']))
1867+
# Limited C API
1868+
self.add(Extension('xxlimited', ['xxlimited.c'],
1869+
define_macros=[('Py_LIMITED_API', '0x030a0000')]))
1870+
self.add(Extension('xxlimited_35', ['xxlimited_35.c'],
1871+
define_macros=[('Py_LIMITED_API', '0x03050000')]))
18781872

18791873
def detect_tkinter_fromenv(self):
18801874
# Build _tkinter using the Tcl/Tk locations specified by

0 commit comments

Comments
 (0)