diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst
index c7ba74cc8d5..a042c6eee0a 100644
--- a/Doc/c-api/exceptions.rst
+++ b/Doc/c-api/exceptions.rst
@@ -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
diff --git a/Doc/whatsnew/3.9.rst b/Doc/whatsnew/3.9.rst
index 7d7c502459a..3cac9c5eedb 100644
--- a/Doc/whatsnew/3.9.rst
+++ b/Doc/whatsnew/3.9.rst
@@ -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.
diff --git a/Include/ceval.h b/Include/ceval.h
index 61db777cc4d..8da779ba97b 100644
--- a/Include/ceval.h
+++ b/Include/ceval.h
@@ -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;\
@@ -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
diff --git a/Include/cpython/ceval.h b/Include/cpython/ceval.h
new file mode 100644
index 00000000000..61bbc4f69d5
--- /dev/null
+++ b/Include/cpython/ceval.h
@@ -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
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 1c0958ec974..3c607d08ae7 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -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 \
diff --git a/Misc/NEWS.d/next/C API/2019-11-04-17-59-46.bpo-38644.euO_RR.rst b/Misc/NEWS.d/next/C API/2019-11-04-17-59-46.bpo-38644.euO_RR.rst
new file mode 100644
index 00000000000..b94f505568f
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2019-11-04-17-59-46.bpo-38644.euO_RR.rst
@@ -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.
diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj
index 1c055b6a334..b72474060a3 100644
--- a/PCbuild/pythoncore.vcxproj
+++ b/PCbuild/pythoncore.vcxproj
@@ -127,6 +127,7 @@
+
diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters
index dbff89fbff6..80908135550 100644
--- a/PCbuild/pythoncore.vcxproj.filters
+++ b/PCbuild/pythoncore.vcxproj.filters
@@ -84,6 +84,9 @@
Include
+
+ Include
+
Include
diff --git a/Python/ceval.c b/Python/ceval.c
index a7d2ea80069..881a7dd629b 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -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();
+}