Skip to content

bpo-38644: Add Py_EnterRecursiveCall() to the limited API #17046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions Doc/c-api/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -715,15 +715,21 @@ recursion depth automatically).
case, a :exc:`RecursionError` is set and a nonzero value is returned.
Otherwise, zero is returned.

*where* should be a string such as ``" in instance check"`` to be
concatenated to the :exc:`RecursionError` message caused by the recursion
*where* should be a UTF-8 encoded string such as ``" in instance check"`` to
be concatenated to the :exc:`RecursionError` message caused by the recursion
depth limit.

.. c:function:: void Py_LeaveRecursiveCall()
.. versionchanged:: 3.9
This function is now also available in the limited API.

.. c:function:: void Py_LeaveRecursiveCall(void)

Ends a :c:func:`Py_EnterRecursiveCall`. Must be called once for each
*successful* invocation of :c:func:`Py_EnterRecursiveCall`.

.. versionchanged:: 3.9
This function is now also available in the limited API.

Properly implementing :c:member:`~PyTypeObject.tp_repr` for container types requires
special recursion handling. In addition to protecting the stack,
:c:member:`~PyTypeObject.tp_repr` also needs to track objects to prevent cycles. The
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.9.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ Optimizations
Build and C API Changes
=======================

* Provide :c:func:`Py_EnterRecursiveCall` and :c:func:`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.
(Contributed by Victor Stinner in :issue:`38644`.)
* Add a new public :c:func:`PyObject_CallNoArgs` function to the C API, which
calls a callable Python object without any arguments. It is the most efficient
way to call a callable Python object without any argument.
Expand Down
43 changes: 8 additions & 35 deletions Include/ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,41 +85,8 @@ PyAPI_FUNC(int) Py_MakePendingCalls(void);
PyAPI_FUNC(void) Py_SetRecursionLimit(int);
PyAPI_FUNC(int) Py_GetRecursionLimit(void);

#define Py_EnterRecursiveCall(where) \
(_Py_MakeRecCheck(PyThreadState_GET()->recursion_depth) && \
_Py_CheckRecursiveCall(where))
#define Py_LeaveRecursiveCall() \
do{ if(_Py_MakeEndRecCheck(PyThreadState_GET()->recursion_depth)) \
PyThreadState_GET()->overflowed = 0; \
} while(0)
PyAPI_FUNC(int) _Py_CheckRecursiveCall(const char *where);

/* Due to the macros in which it's used, _Py_CheckRecursionLimit is in
the stable ABI. It should be removed therefrom when possible.
*/
PyAPI_DATA(int) _Py_CheckRecursionLimit;

#ifdef USE_STACKCHECK
/* With USE_STACKCHECK, trigger stack checks in _Py_CheckRecursiveCall()
on every 64th call to Py_EnterRecursiveCall.
*/
# define _Py_MakeRecCheck(x) \
(++(x) > _Py_CheckRecursionLimit || \
++(PyThreadState_GET()->stackcheck_counter) > 64)
#else
# define _Py_MakeRecCheck(x) (++(x) > _Py_CheckRecursionLimit)
#endif

/* Compute the "lower-water mark" for a recursion limit. When
* Py_LeaveRecursiveCall() is called with a recursion depth below this mark,
* the overflowed flag is reset to 0. */
#define _Py_RecursionLimitLowerWaterMark(limit) \
(((limit) > 200) \
? ((limit) - 50) \
: (3 * ((limit) >> 2)))

#define _Py_MakeEndRecCheck(x) \
(--(x) < _Py_RecursionLimitLowerWaterMark(_Py_CheckRecursionLimit))
PyAPI_FUNC(int) Py_EnterRecursiveCall(const char *where);
PyAPI_FUNC(void) Py_LeaveRecursiveCall(void);

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

#ifndef Py_LIMITED_API
# define Py_CPYTHON_CEVAL_H
# include "cpython/ceval.h"
# undef Py_CPYTHON_CEVAL_H
#endif

#ifdef __cplusplus
}
#endif
Expand Down
50 changes: 50 additions & 0 deletions Include/cpython/ceval.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#ifndef Py_CPYTHON_CEVAL_H
# error "this header file must not be included directly"
#endif

#ifdef __cplusplus
extern "C" {
#endif

PyAPI_DATA(int) _Py_CheckRecursionLimit;

#ifdef USE_STACKCHECK
/* With USE_STACKCHECK macro defined, trigger stack checks in
_Py_CheckRecursiveCall() on every 64th call to Py_EnterRecursiveCall. */
# define _Py_MakeRecCheck(x) \
(++(x) > _Py_CheckRecursionLimit || \
++(PyThreadState_GET()->stackcheck_counter) > 64)
#else
# define _Py_MakeRecCheck(x) (++(x) > _Py_CheckRecursionLimit)
#endif

PyAPI_FUNC(int) _Py_CheckRecursiveCall(const char *where);

#define _Py_EnterRecursiveCall_macro(where) \
(_Py_MakeRecCheck(PyThreadState_GET()->recursion_depth) && \
_Py_CheckRecursiveCall(where))

#define Py_EnterRecursiveCall(where) _Py_EnterRecursiveCall_macro(where)


/* Compute the "lower-water mark" for a recursion limit. When
* Py_LeaveRecursiveCall() is called with a recursion depth below this mark,
* the overflowed flag is reset to 0. */
#define _Py_RecursionLimitLowerWaterMark(limit) \
(((limit) > 200) \
? ((limit) - 50) \
: (3 * ((limit) >> 2)))

#define _Py_MakeEndRecCheck(x) \
(--(x) < _Py_RecursionLimitLowerWaterMark(_Py_CheckRecursionLimit))

#define _Py_LeaveRecursiveCall_macro() \
do{ if(_Py_MakeEndRecCheck(PyThreadState_GET()->recursion_depth)) \
PyThreadState_GET()->overflowed = 0; \
} while(0)

#define Py_LeaveRecursiveCall() _Py_LeaveRecursiveCall_macro()

#ifdef __cplusplus
}
#endif
1 change: 1 addition & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -1057,6 +1057,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/Python-ast.h \
\
$(srcdir)/Include/cpython/abstract.h \
$(srcdir)/Include/cpython/ceval.h \
$(srcdir)/Include/cpython/dictobject.h \
$(srcdir)/Include/cpython/fileobject.h \
$(srcdir)/Include/cpython/import.h \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Provide :c:func:`Py_EnterRecursiveCall` and :c:func:`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.
1 change: 1 addition & 0 deletions PCbuild/pythoncore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
<ClInclude Include="..\Include\complexobject.h" />
<ClInclude Include="..\Include\context.h" />
<ClInclude Include="..\Include\cpython\abstract.h" />
<ClInclude Include="..\Include\cpython\ceval.h" />
<ClInclude Include="..\Include\cpython\dictobject.h" />
<ClInclude Include="..\Include\cpython\fileobject.h" />
<ClInclude Include="..\Include\cpython\import.h" />
Expand Down
3 changes: 3 additions & 0 deletions PCbuild/pythoncore.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@
<ClInclude Include="..\Include\cpython\abstract.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\cpython\ceval.h">
<Filter>Include</Filter>
</ClInclude>
<ClInclude Include="..\Include\cpython\dictobject.h">
<Filter>Include</Filter>
</ClInclude>
Expand Down
18 changes: 18 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -5632,3 +5632,21 @@ maybe_dtrace_line(PyFrameObject *frame,
}
*instr_prev = frame->f_lasti;
}


/* Implement Py_EnterRecursiveCall() and Py_LeaveRecursiveCall() as functions
for the limited API. */

#undef Py_EnterRecursiveCall

int Py_EnterRecursiveCall(const char *where)
{
return _Py_EnterRecursiveCall_macro(where);
}

#undef Py_LeaveRecursiveCall

void Py_LeaveRecursiveCall(void)
{
_Py_LeaveRecursiveCall_macro();
}