Skip to content

Commit f4b1e3d

Browse files
authored
bpo-38644: Add Py_EnterRecursiveCall() to the limited API (GH-17046)
Provide Py_EnterRecursiveCall() and Py_LeaveRecursiveCall() as regular functions for the limited API. Previously, there were defined as macros, but these macros didn't work with the limited API which cannot access PyThreadState.recursion_depth field. Remove _Py_CheckRecursionLimit from the stable ABI. Add Include/cpython/ceval.h header file.
1 parent 6552563 commit f4b1e3d

File tree

9 files changed

+101
-38
lines changed

9 files changed

+101
-38
lines changed

Doc/c-api/exceptions.rst

+9-3
Original file line numberDiff line numberDiff line change
@@ -715,15 +715,21 @@ recursion depth automatically).
715715
case, a :exc:`RecursionError` is set and a nonzero value is returned.
716716
Otherwise, zero is returned.
717717
718-
*where* should be a string such as ``" in instance check"`` to be
719-
concatenated to the :exc:`RecursionError` message caused by the recursion
718+
*where* should be a UTF-8 encoded string such as ``" in instance check"`` to
719+
be concatenated to the :exc:`RecursionError` message caused by the recursion
720720
depth limit.
721721
722-
.. c:function:: void Py_LeaveRecursiveCall()
722+
.. versionchanged:: 3.9
723+
This function is now also available in the limited API.
724+
725+
.. c:function:: void Py_LeaveRecursiveCall(void)
723726
724727
Ends a :c:func:`Py_EnterRecursiveCall`. Must be called once for each
725728
*successful* invocation of :c:func:`Py_EnterRecursiveCall`.
726729
730+
.. versionchanged:: 3.9
731+
This function is now also available in the limited API.
732+
727733
Properly implementing :c:member:`~PyTypeObject.tp_repr` for container types requires
728734
special recursion handling. In addition to protecting the stack,
729735
:c:member:`~PyTypeObject.tp_repr` also needs to track objects to prevent cycles. The

Doc/whatsnew/3.9.rst

+6
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@ Optimizations
197197
Build and C API Changes
198198
=======================
199199

200+
* Provide :c:func:`Py_EnterRecursiveCall` and :c:func:`Py_LeaveRecursiveCall`
201+
as regular functions for the limited API. Previously, there were defined as
202+
macros, but these macros didn't work with the limited API which cannot access
203+
``PyThreadState.recursion_depth`` field. Remove ``_Py_CheckRecursionLimit``
204+
from the stable ABI.
205+
(Contributed by Victor Stinner in :issue:`38644`.)
200206
* Add a new public :c:func:`PyObject_CallNoArgs` function to the C API, which
201207
calls a callable Python object without any arguments. It is the most efficient
202208
way to call a callable Python object without any argument.

Include/ceval.h

+8-35
Original file line numberDiff line numberDiff line change
@@ -85,41 +85,8 @@ PyAPI_FUNC(int) Py_MakePendingCalls(void);
8585
PyAPI_FUNC(void) Py_SetRecursionLimit(int);
8686
PyAPI_FUNC(int) Py_GetRecursionLimit(void);
8787

88-
#define Py_EnterRecursiveCall(where) \
89-
(_Py_MakeRecCheck(PyThreadState_GET()->recursion_depth) && \
90-
_Py_CheckRecursiveCall(where))
91-
#define Py_LeaveRecursiveCall() \
92-
do{ if(_Py_MakeEndRecCheck(PyThreadState_GET()->recursion_depth)) \
93-
PyThreadState_GET()->overflowed = 0; \
94-
} while(0)
95-
PyAPI_FUNC(int) _Py_CheckRecursiveCall(const char *where);
96-
97-
/* Due to the macros in which it's used, _Py_CheckRecursionLimit is in
98-
the stable ABI. It should be removed therefrom when possible.
99-
*/
100-
PyAPI_DATA(int) _Py_CheckRecursionLimit;
101-
102-
#ifdef USE_STACKCHECK
103-
/* With USE_STACKCHECK, trigger stack checks in _Py_CheckRecursiveCall()
104-
on every 64th call to Py_EnterRecursiveCall.
105-
*/
106-
# define _Py_MakeRecCheck(x) \
107-
(++(x) > _Py_CheckRecursionLimit || \
108-
++(PyThreadState_GET()->stackcheck_counter) > 64)
109-
#else
110-
# define _Py_MakeRecCheck(x) (++(x) > _Py_CheckRecursionLimit)
111-
#endif
112-
113-
/* Compute the "lower-water mark" for a recursion limit. When
114-
* Py_LeaveRecursiveCall() is called with a recursion depth below this mark,
115-
* the overflowed flag is reset to 0. */
116-
#define _Py_RecursionLimitLowerWaterMark(limit) \
117-
(((limit) > 200) \
118-
? ((limit) - 50) \
119-
: (3 * ((limit) >> 2)))
120-
121-
#define _Py_MakeEndRecCheck(x) \
122-
(--(x) < _Py_RecursionLimitLowerWaterMark(_Py_CheckRecursionLimit))
88+
PyAPI_FUNC(int) Py_EnterRecursiveCall(const char *where);
89+
PyAPI_FUNC(void) Py_LeaveRecursiveCall(void);
12390

12491
#define Py_ALLOW_RECURSION \
12592
do { unsigned char _old = PyThreadState_GET()->recursion_critical;\
@@ -224,6 +191,12 @@ PyAPI_FUNC(int) _PyEval_SliceIndexNotNone(PyObject *, Py_ssize_t *);
224191
#define FVS_MASK 0x4
225192
#define FVS_HAVE_SPEC 0x4
226193

194+
#ifndef Py_LIMITED_API
195+
# define Py_CPYTHON_CEVAL_H
196+
# include "cpython/ceval.h"
197+
# undef Py_CPYTHON_CEVAL_H
198+
#endif
199+
227200
#ifdef __cplusplus
228201
}
229202
#endif

Include/cpython/ceval.h

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#ifndef Py_CPYTHON_CEVAL_H
2+
# error "this header file must not be included directly"
3+
#endif
4+
5+
#ifdef __cplusplus
6+
extern "C" {
7+
#endif
8+
9+
PyAPI_DATA(int) _Py_CheckRecursionLimit;
10+
11+
#ifdef USE_STACKCHECK
12+
/* With USE_STACKCHECK macro defined, trigger stack checks in
13+
_Py_CheckRecursiveCall() on every 64th call to Py_EnterRecursiveCall. */
14+
# define _Py_MakeRecCheck(x) \
15+
(++(x) > _Py_CheckRecursionLimit || \
16+
++(PyThreadState_GET()->stackcheck_counter) > 64)
17+
#else
18+
# define _Py_MakeRecCheck(x) (++(x) > _Py_CheckRecursionLimit)
19+
#endif
20+
21+
PyAPI_FUNC(int) _Py_CheckRecursiveCall(const char *where);
22+
23+
#define _Py_EnterRecursiveCall_macro(where) \
24+
(_Py_MakeRecCheck(PyThreadState_GET()->recursion_depth) && \
25+
_Py_CheckRecursiveCall(where))
26+
27+
#define Py_EnterRecursiveCall(where) _Py_EnterRecursiveCall_macro(where)
28+
29+
30+
/* Compute the "lower-water mark" for a recursion limit. When
31+
* Py_LeaveRecursiveCall() is called with a recursion depth below this mark,
32+
* the overflowed flag is reset to 0. */
33+
#define _Py_RecursionLimitLowerWaterMark(limit) \
34+
(((limit) > 200) \
35+
? ((limit) - 50) \
36+
: (3 * ((limit) >> 2)))
37+
38+
#define _Py_MakeEndRecCheck(x) \
39+
(--(x) < _Py_RecursionLimitLowerWaterMark(_Py_CheckRecursionLimit))
40+
41+
#define _Py_LeaveRecursiveCall_macro() \
42+
do{ if(_Py_MakeEndRecCheck(PyThreadState_GET()->recursion_depth)) \
43+
PyThreadState_GET()->overflowed = 0; \
44+
} while(0)
45+
46+
#define Py_LeaveRecursiveCall() _Py_LeaveRecursiveCall_macro()
47+
48+
#ifdef __cplusplus
49+
}
50+
#endif

Makefile.pre.in

+1
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,7 @@ PYTHON_HEADERS= \
10571057
$(srcdir)/Include/Python-ast.h \
10581058
\
10591059
$(srcdir)/Include/cpython/abstract.h \
1060+
$(srcdir)/Include/cpython/ceval.h \
10601061
$(srcdir)/Include/cpython/dictobject.h \
10611062
$(srcdir)/Include/cpython/fileobject.h \
10621063
$(srcdir)/Include/cpython/import.h \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Provide :c:func:`Py_EnterRecursiveCall` and :c:func:`Py_LeaveRecursiveCall`
2+
as regular functions for the limited API. Previously, there were defined as
3+
macros, but these macros didn't work with the limited API which cannot access
4+
``PyThreadState.recursion_depth`` field. Remove ``_Py_CheckRecursionLimit``
5+
from the stable ABI.

PCbuild/pythoncore.vcxproj

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
<ClInclude Include="..\Include\complexobject.h" />
128128
<ClInclude Include="..\Include\context.h" />
129129
<ClInclude Include="..\Include\cpython\abstract.h" />
130+
<ClInclude Include="..\Include\cpython\ceval.h" />
130131
<ClInclude Include="..\Include\cpython\dictobject.h" />
131132
<ClInclude Include="..\Include\cpython\fileobject.h" />
132133
<ClInclude Include="..\Include\cpython\import.h" />

PCbuild/pythoncore.vcxproj.filters

+3
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@
8484
<ClInclude Include="..\Include\cpython\abstract.h">
8585
<Filter>Include</Filter>
8686
</ClInclude>
87+
<ClInclude Include="..\Include\cpython\ceval.h">
88+
<Filter>Include</Filter>
89+
</ClInclude>
8790
<ClInclude Include="..\Include\cpython\dictobject.h">
8891
<Filter>Include</Filter>
8992
</ClInclude>

Python/ceval.c

+18
Original file line numberDiff line numberDiff line change
@@ -5632,3 +5632,21 @@ maybe_dtrace_line(PyFrameObject *frame,
56325632
}
56335633
*instr_prev = frame->f_lasti;
56345634
}
5635+
5636+
5637+
/* Implement Py_EnterRecursiveCall() and Py_LeaveRecursiveCall() as functions
5638+
for the limited API. */
5639+
5640+
#undef Py_EnterRecursiveCall
5641+
5642+
int Py_EnterRecursiveCall(const char *where)
5643+
{
5644+
return _Py_EnterRecursiveCall_macro(where);
5645+
}
5646+
5647+
#undef Py_LeaveRecursiveCall
5648+
5649+
void Py_LeaveRecursiveCall(void)
5650+
{
5651+
_Py_LeaveRecursiveCall_macro();
5652+
}

0 commit comments

Comments
 (0)