dcac805
From 31ccde2be3eeefca06277e20c3a06022e5ab2e62 Mon Sep 17 00:00:00 2001
dcac805
From: Victor Stinner <vstinner@python.org>
dcac805
Date: Mon, 6 Jun 2022 16:14:52 +0200
dcac805
Subject: [PATCH] Closes #305: Add Python 3.11 support
dcac805
MIME-Version: 1.0
dcac805
Content-Type: text/plain; charset=UTF-8
dcac805
Content-Transfer-Encoding: 8bit
dcac805
dcac805
* Add GREENLET_PY311 macro
dcac805
* PyGreenlet structure:
dcac805
dcac805
  * Add 3 members for the "data stack": 'datastack_chunk',
dcac805
    'datastack_top' and 'datastack_limit'.
dcac805
  * Add 'current_frame' member.
dcac805
dcac805
* Rename CFrame to _PyCFrame
dcac805
* tox.ini: Add py311 environment.
dcac805
dcac805
Changes partially backport from the master branch:
dcac805
commit 63e1099acc3677e614532bea0fa2e1967b69125f.
dcac805
dcac805
Co-Authored-By: Miro HronĨok <miro@hroncok.cz>
dcac805
---
dcac805
 src/greenlet/greenlet.c | 61 ++++++++++++++++++++++++++++++++---------
dcac805
 src/greenlet/greenlet.h | 17 +++++++++++-
dcac805
 tox.ini                 |  2 +-
dcac805
 3 files changed, 65 insertions(+), 15 deletions(-)
dcac805
dcac805
diff --git a/src/greenlet/greenlet.c b/src/greenlet/greenlet.c
dcac805
index f47bbf88..2f3ad6e9 100644
dcac805
--- a/src/greenlet/greenlet.c
dcac805
+++ b/src/greenlet/greenlet.c
dcac805
@@ -170,9 +170,11 @@ green_clear_exc(PyGreenlet* g)
dcac805
 {
dcac805
 #if GREENLET_PY37
dcac805
     g->exc_info = NULL;
dcac805
-    g->exc_state.exc_type = NULL;
dcac805
     g->exc_state.exc_value = NULL;
dcac805
+#if !GREENLET_PY311
dcac805
+    g->exc_state.exc_type = NULL;
dcac805
     g->exc_state.exc_traceback = NULL;
dcac805
+#endif
dcac805
     g->exc_state.previous_item = NULL;
dcac805
 #else
dcac805
     g->exc_type = NULL;
dcac805
@@ -525,8 +527,13 @@ g_switchstack(void)
dcac805
     { /* save state */
dcac805
         PyGreenlet* current = ts_current;
dcac805
         PyThreadState* tstate = PyThreadState_GET();
dcac805
+#if GREENLET_PY311
dcac805
+        current->recursion_depth = (tstate->recursion_limit
dcac805
+                                    - tstate->recursion_remaining);
dcac805
+#else
dcac805
         current->recursion_depth = tstate->recursion_depth;
dcac805
         current->top_frame = tstate->frame;
dcac805
+#endif
dcac805
 #if GREENLET_PY37
dcac805
         current->context = tstate->context;
dcac805
 #endif
dcac805
@@ -551,6 +558,15 @@ g_switchstack(void)
dcac805
          */
dcac805
         current->cframe = tstate->cframe;
dcac805
         ts__g_switchstack_use_tracing = tstate->cframe->use_tracing;
dcac805
+#if GREENLET_PY311
dcac805
+        current->current_frame = tstate->cframe->current_frame;
dcac805
+        current->datastack_chunk = tstate->datastack_chunk;
dcac805
+        current->datastack_top = tstate->datastack_top;
dcac805
+        current->datastack_limit = tstate->datastack_limit;
dcac805
+        PyFrameObject *frame = PyThreadState_GetFrame(tstate);
dcac805
+        Py_XDECREF(frame); /* PyThreadState_GetFrame gives us a new reference. */
dcac805
+        current->top_frame = frame;
dcac805
+#endif
dcac805
 #endif
dcac805
     }
dcac805
 
dcac805
@@ -574,9 +590,6 @@ g_switchstack(void)
dcac805
         PyGreenlet* target = ts_target;
dcac805
         PyGreenlet* origin = ts_current;
dcac805
         PyThreadState* tstate = PyThreadState_GET();
dcac805
-        tstate->recursion_depth = target->recursion_depth;
dcac805
-        tstate->frame = target->top_frame;
dcac805
-        target->top_frame = NULL;
dcac805
 
dcac805
 #if GREENLET_PY37
dcac805
         tstate->context = target->context;
dcac805
@@ -607,7 +620,18 @@ g_switchstack(void)
dcac805
         */
dcac805
         tstate->cframe->use_tracing = ts__g_switchstack_use_tracing;
dcac805
 #endif
dcac805
-
dcac805
+#if GREENLET_PY311
dcac805
+        tstate->recursion_remaining = (tstate->recursion_limit
dcac805
+                                       - target->recursion_depth);
dcac805
+        tstate->cframe->current_frame = target->current_frame;
dcac805
+        tstate->datastack_chunk = target->datastack_chunk;
dcac805
+        tstate->datastack_top = target->datastack_top;
dcac805
+        tstate->datastack_limit = target->datastack_limit;
dcac805
+#else
dcac805
+        tstate->recursion_depth = target->recursion_depth;
dcac805
+        tstate->frame = target->top_frame;
dcac805
+#endif
dcac805
+        target->top_frame = NULL;
dcac805
         assert(ts_origin == NULL);
dcac805
         Py_INCREF(target);
dcac805
         ts_current = target;
dcac805
@@ -810,7 +834,7 @@ static int GREENLET_NOINLINE(g_initialstub)(void* mark)
dcac805
       We want to defer copying the state info until we're sure
dcac805
       we need it and are in a stable place to do so.
dcac805
     */
dcac805
-    CFrame trace_info;
dcac805
+    _PyCFrame trace_info;
dcac805
 #endif
dcac805
     /* save exception in case getattr clears it */
dcac805
     PyErr_Fetch(&exc, &val, &tb);
dcac805
@@ -875,7 +899,12 @@ static int GREENLET_NOINLINE(g_initialstub)(void* mark)
dcac805
     }
dcac805
     self->top_frame = NULL;
dcac805
     green_clear_exc(self);
dcac805
+#if GREENLET_PY311
dcac805
+    self->recursion_depth = (PyThreadState_GET()->recursion_limit
dcac805
+                             - PyThreadState_GET()->recursion_remaining);
dcac805
+#else
dcac805
     self->recursion_depth = PyThreadState_GET()->recursion_depth;
dcac805
+#endif
dcac805
 
dcac805
     /* restore arguments in case they are clobbered */
dcac805
     ts_target = self;
dcac805
@@ -1006,13 +1035,13 @@ green_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
dcac805
           it uses the ``root_cframe`` just to have something to put there.
dcac805
           However, once the greenlet is actually switched to for the first
dcac805
           time, ``g_initialstub`` (which doesn't actually "return" while the
dcac805
-          greenlet is running) stores a new CFrame on its local stack, and
dcac805
+          greenlet is running) stores a new _PyCFrame on its local stack, and
dcac805
           copies the appropriate values from the currently running CFrame;
dcac805
-          this is then made the CFrame for the newly-minted greenlet.
dcac805
+          this is then made the _PyCFrame for the newly-minted greenlet.
dcac805
           ``g_initialstub`` then proceeds to call ``glet.run()``, which
dcac805
-          results in ``PyEval_...`` adding the CFrame to the list. Switches
dcac805
+          results in ``PyEval_...`` adding the _PyCFrame to the list. Switches
dcac805
           continue as normal. Finally, when the greenlet finishes, the call to
dcac805
-          ``glet.run()`` returns and the CFrame is taken out of the linked
dcac805
+          ``glet.run()`` returns and the _PyCFrame is taken out of the linked
dcac805
           list and the stack value is now unused and free to expire.
dcac805
         */
dcac805
         ((PyGreenlet*)o)->cframe = &PyThreadState_GET()->root_cframe;
dcac805
@@ -1121,9 +1150,11 @@ green_traverse(PyGreenlet* self, visitproc visit, void* arg)
dcac805
     Py_VISIT(self->context);
dcac805
 #endif
dcac805
 #if GREENLET_PY37
dcac805
-    Py_VISIT(self->exc_state.exc_type);
dcac805
     Py_VISIT(self->exc_state.exc_value);
dcac805
+#if !GREENLET_PY311
dcac805
+    Py_VISIT(self->exc_state.exc_type);
dcac805
     Py_VISIT(self->exc_state.exc_traceback);
dcac805
+#endif
dcac805
 #else
dcac805
     Py_VISIT(self->exc_type);
dcac805
     Py_VISIT(self->exc_value);
dcac805
@@ -1159,9 +1190,11 @@ green_clear(PyGreenlet* self)
dcac805
     Py_CLEAR(self->context);
dcac805
 #endif
dcac805
 #if GREENLET_PY37
dcac805
-    Py_CLEAR(self->exc_state.exc_type);
dcac805
     Py_CLEAR(self->exc_state.exc_value);
dcac805
+#if !GREENLET_PY311
dcac805
+    Py_CLEAR(self->exc_state.exc_type);
dcac805
     Py_CLEAR(self->exc_state.exc_traceback);
dcac805
+#endif
dcac805
 #else
dcac805
     Py_CLEAR(self->exc_type);
dcac805
     Py_CLEAR(self->exc_value);
dcac805
@@ -1253,9 +1286,11 @@ green_dealloc(PyGreenlet* self)
dcac805
     Py_CLEAR(self->context);
dcac805
 #endif
dcac805
 #if GREENLET_PY37
dcac805
-    Py_CLEAR(self->exc_state.exc_type);
dcac805
     Py_CLEAR(self->exc_state.exc_value);
dcac805
+#if !GREENLET_PY311
dcac805
+    Py_CLEAR(self->exc_state.exc_type);
dcac805
     Py_CLEAR(self->exc_state.exc_traceback);
dcac805
+#endif
dcac805
 #else
dcac805
     Py_CLEAR(self->exc_type);
dcac805
     Py_CLEAR(self->exc_value);
dcac805
diff --git a/src/greenlet/greenlet.h b/src/greenlet/greenlet.h
dcac805
index 830bef8d..c788b2fe 100644
dcac805
--- a/src/greenlet/greenlet.h
dcac805
+++ b/src/greenlet/greenlet.h
dcac805
@@ -14,6 +14,15 @@ extern "C" {
dcac805
 /* This is deprecated and undocumented. It does not change. */
dcac805
 #define GREENLET_VERSION "1.0.0"
dcac805
 
dcac805
+#if PY_VERSION_HEX >= 0x30B00A6
dcac805
+#  define GREENLET_PY311 1
dcac805
+   /* _PyInterpreterFrame moved to the internal C API in Python 3.11 */
dcac805
+#  include <internal/pycore_frame.h>
dcac805
+#else
dcac805
+#  define GREENLET_PY311 0
dcac805
+#  define _PyCFrame CFrame
dcac805
+#endif
dcac805
+
dcac805
 typedef struct _greenlet {
dcac805
     PyObject_HEAD
dcac805
     char* stack_start;
dcac805
@@ -25,6 +34,12 @@ typedef struct _greenlet {
dcac805
     PyObject* run_info;
dcac805
     struct _frame* top_frame;
dcac805
     int recursion_depth;
dcac805
+#if GREENLET_PY311
dcac805
+    _PyInterpreterFrame *current_frame;
dcac805
+    _PyStackChunk *datastack_chunk;
dcac805
+    PyObject **datastack_top;
dcac805
+    PyObject **datastack_limit;
dcac805
+#endif
dcac805
     PyObject* weakreflist;
dcac805
 #if PY_VERSION_HEX >= 0x030700A3
dcac805
     _PyErr_StackItem* exc_info;
dcac805
@@ -39,7 +54,7 @@ typedef struct _greenlet {
dcac805
     PyObject* context;
dcac805
 #endif
dcac805
 #if PY_VERSION_HEX >= 0x30A00B1
dcac805
-    CFrame* cframe;
dcac805
+    _PyCFrame* cframe;
dcac805
 #endif
dcac805
 } PyGreenlet;
dcac805
 
dcac805
diff --git a/tox.ini b/tox.ini
dcac805
index 21ecbbb2..efec08bc 100644
dcac805
--- a/tox.ini
dcac805
+++ b/tox.ini
dcac805
@@ -1,6 +1,6 @@
dcac805
 [tox]
dcac805
 envlist =
dcac805
-    py27,py35,py36,py37,py38,py39,py310,docs
dcac805
+    py27,py35,py36,py37,py38,py39,py310,py311,docs
dcac805
 
dcac805
 [testenv]
dcac805
 commands =