bpo-39947: Remove old private trashcan C API functions
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index 84c60e55d5c9df..75cd0f9002215b 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -493,8 +493,8 @@ without deallocating anything (and so unbounded call-stack depth is avoided).
 When the call stack finishes unwinding again, code generated by the END macro
 notices this, and calls another routine to deallocate all the objects that
 may have been added to the list of deferred deallocations.  In effect, a
-chain of N deallocations is broken into (N-1)/(PyTrash_UNWIND_LEVEL-1) pieces,
-with the call stack never exceeding a depth of PyTrash_UNWIND_LEVEL.
+chain of N deallocations is broken into (N-1)/(_PyTrash_UNWIND_LEVEL-1) pieces,
+with the call stack never exceeding a depth of _PyTrash_UNWIND_LEVEL.
 
 Since the tp_dealloc of a subclass typically calls the tp_dealloc of the base
 class, we need to ensure that the trashcan is only triggered on the tp_dealloc
@@ -503,16 +503,6 @@ partially-deallocated object. To check this, the tp_dealloc function must be
 passed as second argument to Py_TRASHCAN_BEGIN().
 */
 
-/* This is the old private API, invoked by the macros before 3.2.4.
-   Kept for binary compatibility of extensions using the stable ABI. */
-PyAPI_FUNC(void) _PyTrash_deposit_object(PyObject*);
-PyAPI_FUNC(void) _PyTrash_destroy_chain(void);
-
-/* This is the old private API, invoked by the macros before 3.9.
-   Kept for binary compatibility of extensions using the stable ABI. */
-PyAPI_FUNC(void) _PyTrash_thread_deposit_object(PyObject*);
-PyAPI_FUNC(void) _PyTrash_thread_destroy_chain(void);
-
 /* Forward declarations for PyThreadState */
 struct _ts;
 
@@ -522,8 +512,6 @@ PyAPI_FUNC(void) _PyTrash_end(struct _ts *tstate);
 /* Python 3.10 private API, invoked by the Py_TRASHCAN_BEGIN(). */
 PyAPI_FUNC(int) _PyTrash_cond(PyObject *op, destructor dealloc);
 
-#define PyTrash_UNWIND_LEVEL 50
-
 #define Py_TRASHCAN_BEGIN_CONDITION(op, cond) \
     do { \
         PyThreadState *_tstate = NULL; \
diff --git a/Misc/NEWS.d/next/C API/2021-06-23-10-31-45.bpo-39947.je_HMo.rst b/Misc/NEWS.d/next/C API/2021-06-23-10-31-45.bpo-39947.je_HMo.rst
new file mode 100644
index 00000000000000..43adbffc7cce24
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2021-06-23-10-31-45.bpo-39947.je_HMo.rst	
@@ -0,0 +1,20 @@
+Remove 4 private trashcan C API functions which were only kept for the backward
+compatibility of the stable ABI with Python 3.8 and older, since the trashcan
+API was not usable with the limited C API on Python 3.8 and older. The
+trashcan API was excluded from the limited C API in Python 3.9.
+
+Removed functions:
+
+* _PyTrash_deposit_object()
+* _PyTrash_destroy_chain()
+* _PyTrash_thread_deposit_object()
+* _PyTrash_thread_destroy_chain()
+
+The trashcan C API was never usable with the limited C API, since old trashcan
+macros accessed directly :c:type:`PyThreadState` members like
+``_tstate->trash_delete_nesting``, whereas the :c:type:`PyThreadState`
+structure is opaque in the limited C API.
+
+Exclude also the the ``PyTrash_UNWIND_LEVEL`` constant from the C API.
+
+Patch by Victor Stinner.
diff --git a/Misc/stable_abi.txt b/Misc/stable_abi.txt
index 24c71d12e3ba75..f104f84e451da1 100644
--- a/Misc/stable_abi.txt
+++ b/Misc/stable_abi.txt
@@ -1602,12 +1602,6 @@ function _PyThreadState_Init
 function _PyThreadState_Prealloc
     added 3.2
     abi_only
-function _PyTrash_deposit_object
-    added 3.2
-    abi_only
-function _PyTrash_destroy_chain
-    added 3.2
-    abi_only
 data _PyWeakref_CallableProxyType
     added 3.2
     abi_only
@@ -1920,12 +1914,6 @@ function Py_EncodeLocale
     added 3.7  # (and 3.6.1 and 3.5.3)
 function Py_SetPath
     added 3.7  # (and 3.6.1 and 3.5.3)
-function _PyTrash_thread_deposit_object
-    added 3.7  # (and 3.6.1 and 3.5.3)
-    abi_only
-function _PyTrash_thread_destroy_chain
-    added 3.7  # (and 3.6.1 and 3.5.3)
-    abi_only
 function PyErr_SetExcFromWindowsErr
     added 3.7  # (and 3.6.1 and 3.5.3)
     ifdef MS_WINDOWS
diff --git a/Objects/object.c b/Objects/object.c
index 854cc85b1cfa46..c87a83f225f14b 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -2092,25 +2092,13 @@ Py_ReprLeave(PyObject *obj)
 
 /* Trashcan support. */
 
-/* Add op to the _PyTrash_delete_later list.  Called when the current
+#define _PyTrash_UNWIND_LEVEL 50
+
+/* Add op to the gcstate->trash_delete_later list.  Called when the current
  * call-stack depth gets large.  op must be a currently untracked gc'ed
  * object, with refcount 0.  Py_DECREF must already have been called on it.
  */
-void
-_PyTrash_deposit_object(PyObject *op)
-{
-    PyInterpreterState *interp = _PyInterpreterState_GET();
-    struct _gc_runtime_state *gcstate = &interp->gc;
-
-    _PyObject_ASSERT(op, _PyObject_IS_GC(op));
-    _PyObject_ASSERT(op, !_PyObject_GC_IS_TRACKED(op));
-    _PyObject_ASSERT(op, Py_REFCNT(op) == 0);
-    _PyGCHead_SET_PREV(_Py_AS_GC(op), gcstate->trash_delete_later);
-    gcstate->trash_delete_later = op;
-}
-
-/* The equivalent API, using per-thread state recursion info */
-void
+static void
 _PyTrash_thread_deposit_object(PyObject *op)
 {
     PyThreadState *tstate = _PyThreadState_GET();
@@ -2121,37 +2109,9 @@ _PyTrash_thread_deposit_object(PyObject *op)
     tstate->trash_delete_later = op;
 }
 
-/* Deallocate all the objects in the _PyTrash_delete_later list.  Called when
- * the call-stack unwinds again.
- */
-void
-_PyTrash_destroy_chain(void)
-{
-    PyInterpreterState *interp = _PyInterpreterState_GET();
-    struct _gc_runtime_state *gcstate = &interp->gc;
-
-    while (gcstate->trash_delete_later) {
-        PyObject *op = gcstate->trash_delete_later;
-        destructor dealloc = Py_TYPE(op)->tp_dealloc;
-
-        gcstate->trash_delete_later =
-            (PyObject*) _PyGCHead_PREV(_Py_AS_GC(op));
-
-        /* Call the deallocator directly.  This used to try to
-         * fool Py_DECREF into calling it indirectly, but
-         * Py_DECREF was already called on this object, and in
-         * assorted non-release builds calling Py_DECREF again ends
-         * up distorting allocation statistics.
-         */
-        _PyObject_ASSERT(op, Py_REFCNT(op) == 0);
-        ++gcstate->trash_delete_nesting;
-        (*dealloc)(op);
-        --gcstate->trash_delete_nesting;
-    }
-}
-
-/* The equivalent API, using per-thread state recursion info */
-void
+/* Deallocate all the objects in the gcstate->trash_delete_later list.
+ * Called when the call-stack unwinds again. */
+static void
 _PyTrash_thread_destroy_chain(void)
 {
     PyThreadState *tstate = _PyThreadState_GET();
@@ -2192,7 +2152,7 @@ _PyTrash_thread_destroy_chain(void)
 int
 _PyTrash_begin(PyThreadState *tstate, PyObject *op)
 {
-    if (tstate->trash_delete_nesting >= PyTrash_UNWIND_LEVEL) {
+    if (tstate->trash_delete_nesting >= _PyTrash_UNWIND_LEVEL) {
         /* Store the object (to be deallocated later) and jump past
          * Py_TRASHCAN_END, skipping the body of the deallocator */
         _PyTrash_thread_deposit_object(op);
diff --git a/PC/python3dll.c b/PC/python3dll.c
index 378669c27f0544..0ebb56efaecb2c 100755
--- a/PC/python3dll.c
+++ b/PC/python3dll.c
@@ -37,10 +37,6 @@ EXPORT_FUNC(_PyObject_NewVar)
 EXPORT_FUNC(_PyState_AddModule)
 EXPORT_FUNC(_PyThreadState_Init)
 EXPORT_FUNC(_PyThreadState_Prealloc)
-EXPORT_FUNC(_PyTrash_deposit_object)
-EXPORT_FUNC(_PyTrash_destroy_chain)
-EXPORT_FUNC(_PyTrash_thread_deposit_object)
-EXPORT_FUNC(_PyTrash_thread_destroy_chain)
 EXPORT_FUNC(Py_AddPendingCall)
 EXPORT_FUNC(Py_AtExit)
 EXPORT_FUNC(Py_BuildValue)

Remove 4 C API private trashcan functions which were only kept for
the backward compatibility of the stable ABI with Python 3.8 and
older, since the trashcan API was not usable with the limited C API
on Python 3.8 and older. The trashcan API was excluded from the
limited C API in Python 3.9.

The trashcan C API was never usable with the limited C API, since old
trashcan macros accessed directly PyThreadState members like
"_tstate->trash_delete_nesting", whereas the PyThreadState structure
is opaque in the limited C API.

Exclude also the PyTrash_UNWIND_LEVEL constant from the C API.

The trashcan C API was modified in Python 3.9 by commit
38965ec5411da60d312b59be281f3510d58e0cf1 and in Python 3.10 by commit
ed1a5a5baca8f61e9a99c5be3adc16b1801514fe to hide implementation
details.

<!--
Thanks for your contribution!
Please read this comment in its entirety. It's quite important.

# Pull Request title

It should be in the following format:

```
bpo-NNNN: Summary of the changes made
```

Where: bpo-NNNN refers to the issue number in the https://bugs.python.org.

Most PRs will require an issue number. Trivial changes, like fixing a typo, do not need an issue.

# Backport Pull Request title

If this is a backport PR (PR made against branches other than `main`),
please ensure that the PR title is in the following format:

```
[X.Y] <title from the original PR> (GH-NNNN)
```

Where: [X.Y] is the branch name, e.g. [3.6].

GH-NNNN refers to the PR number from `main`.

-->


<!-- issue-number: [bpo-39947](https://bugs.python.org/issue39947) -->
https://bugs.python.org/issue39947
<!-- /issue-number -->
