Qucs-S S-parameter Viewer & RF Synthesis Tools
Loading...
Searching...
No Matches
TPythonState.cpp
1#ifndef GREENLET_PYTHON_STATE_CPP
2#define GREENLET_PYTHON_STATE_CPP
3
4#include <Python.h>
5#include "TGreenlet.hpp"
6
7namespace greenlet {
8
9PythonState::PythonState()
10 : _top_frame()
11#if GREENLET_USE_CFRAME
12 ,cframe(nullptr)
13 ,use_tracing(0)
14#endif
15#if GREENLET_PY314
16 ,py_recursion_depth(0)
17 ,current_executor(nullptr)
18 ,stackpointer(nullptr)
19 #ifdef Py_GIL_DISABLED
20 ,c_stack_refs(nullptr)
21 #endif
22#elif GREENLET_PY312
23 ,py_recursion_depth(0)
24 ,c_recursion_depth(0)
25#else
26 ,recursion_depth(0)
27#endif
28#if GREENLET_PY313
29 ,delete_later(nullptr)
30#else
31 ,trash_delete_nesting(0)
32#endif
33#if GREENLET_PY311
34 ,current_frame(nullptr)
35 ,datastack_chunk(nullptr)
36 ,datastack_top(nullptr)
37 ,datastack_limit(nullptr)
38#endif
39{
40#if GREENLET_USE_CFRAME
41 /*
42 The PyThreadState->cframe pointer usually points to memory on
43 the stack, alloceted in a call into PyEval_EvalFrameDefault.
44
45 Initially, before any evaluation begins, it points to the
46 initial PyThreadState object's ``root_cframe`` object, which is
47 statically allocated for the lifetime of the thread.
48
49 A greenlet can last for longer than a call to
50 PyEval_EvalFrameDefault, so we can't set its ``cframe`` pointer
51 to be the current ``PyThreadState->cframe``; nor could we use
52 one from the greenlet parent for the same reason. Yet a further
53 no: we can't allocate one scoped to the greenlet and then
54 destroy it when the greenlet is deallocated, because inside the
55 interpreter the _PyCFrame objects form a linked list, and that too
56 can result in accessing memory beyond its dynamic lifetime (if
57 the greenlet doesn't actually finish before it dies, its entry
58 could still be in the list).
59
60 Using the ``root_cframe`` is problematic, though, because its
61 members are never modified by the interpreter and are set to 0,
62 meaning that its ``use_tracing`` flag is never updated. We don't
63 want to modify that value in the ``root_cframe`` ourself: it
64 *shouldn't* matter much because we should probably never get
65 back to the point where that's the only cframe on the stack;
66 even if it did matter, the major consequence of an incorrect
67 value for ``use_tracing`` is that if its true the interpreter
68 does some extra work --- however, it's just good code hygiene.
69
70 Our solution: before a greenlet runs, after its initial
71 creation, it uses the ``root_cframe`` just to have something to
72 put there. However, once the greenlet is actually switched to
73 for the first time, ``g_initialstub`` (which doesn't actually
74 "return" while the greenlet is running) stores a new _PyCFrame on
75 its local stack, and copies the appropriate values from the
76 currently running _PyCFrame; this is then made the _PyCFrame for the
77 newly-minted greenlet. ``g_initialstub`` then proceeds to call
78 ``glet.run()``, which results in ``PyEval_...`` adding the
79 _PyCFrame to the list. Switches continue as normal. Finally, when
80 the greenlet finishes, the call to ``glet.run()`` returns and
81 the _PyCFrame is taken out of the linked list and the stack value
82 is now unused and free to expire.
83
84 XXX: I think we can do better. If we're deallocing in the same
85 thread, can't we traverse the list and unlink our frame?
86 Can we just keep a reference to the thread state in case we
87 dealloc in another thread? (Is that even possible if we're still
88 running and haven't returned from g_initialstub?)
89 */
90 this->cframe = &PyThreadState_GET()->root_cframe;
91#endif
92}
93
94
95inline void PythonState::may_switch_away() noexcept
96{
97#if GREENLET_PY311
98 // PyThreadState_GetFrame is probably going to have to allocate a
99 // new frame object. That may trigger garbage collection. Because
100 // we call this during the early phases of a switch (it doesn't
101 // matter to which greenlet, as this has a global effect), if a GC
102 // triggers a switch away, two things can happen, both bad:
103 // - We might not get switched back to, halting forward progress.
104 // this is pathological, but possible.
105 // - We might get switched back to with a different set of
106 // arguments or a throw instead of a switch. That would corrupt
107 // our state (specifically, PyErr_Occurred() and this->args()
108 // would no longer agree).
109 //
110 // Thus, when we call this API, we need to have GC disabled.
111 // This method serves as a bottleneck we call when maybe beginning
112 // a switch. In this way, it is always safe -- no risk of GC -- to
113 // use ``_GetFrame()`` whenever we need to, just as it was in
114 // <=3.10 (because subsequent calls will be cached and not
115 // allocate memory).
116
117 GCDisabledGuard no_gc;
118 Py_XDECREF(PyThreadState_GetFrame(PyThreadState_GET()));
119#endif
120}
121
122void PythonState::operator<<(const PyThreadState *const tstate) noexcept
123{
124 this->_context.steal(tstate->context);
125#if GREENLET_USE_CFRAME
126 /*
127 IMPORTANT: ``cframe`` is a pointer into the STACK. Thus, because
128 the call to ``slp_switch()`` changes the contents of the stack,
129 you cannot read from ``ts_current->cframe`` after that call and
130 necessarily get the same values you get from reading it here.
131 Anything you need to restore from now to then must be saved in a
132 global/threadlocal variable (because we can't use stack
133 variables here either). For things that need to persist across
134 the switch, use `will_switch_from`.
135 */
136 this->cframe = tstate->cframe;
137 #if !GREENLET_PY312
138 this->use_tracing = tstate->cframe->use_tracing;
139 #endif
140#endif // GREENLET_USE_CFRAME
141#if GREENLET_PY311
142 #if GREENLET_PY314
143 this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
144 this->current_executor = tstate->current_executor;
145 #ifdef Py_GIL_DISABLED
146 this->c_stack_refs = ((_PyThreadStateImpl*)tstate)->c_stack_refs;
147 #endif
148 #elif GREENLET_PY312
149 this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
150 this->c_recursion_depth = Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining;
151 #else // not 312
152 this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
153 #endif // GREENLET_PY312
154 #if GREENLET_PY313
155 this->current_frame = tstate->current_frame;
156 #elif GREENLET_USE_CFRAME
157 this->current_frame = tstate->cframe->current_frame;
158 #endif
159 this->datastack_chunk = tstate->datastack_chunk;
160 this->datastack_top = tstate->datastack_top;
161 this->datastack_limit = tstate->datastack_limit;
162
163 PyFrameObject *frame = PyThreadState_GetFrame((PyThreadState *)tstate);
164 Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new
165 // reference.
166 this->_top_frame.steal(frame);
167 #if GREENLET_PY314
168 if (this->top_frame()) {
169 this->stackpointer = this->_top_frame->f_frame->stackpointer;
170 }
171 else {
172 this->stackpointer = nullptr;
173 }
174 #endif
175 #if GREENLET_PY313
176 this->delete_later = Py_XNewRef(tstate->delete_later);
177 #elif GREENLET_PY312
178 this->trash_delete_nesting = tstate->trash.delete_nesting;
179 #else // not 312
180 this->trash_delete_nesting = tstate->trash_delete_nesting;
181 #endif // GREENLET_PY312
182#else // Not 311
183 this->recursion_depth = tstate->recursion_depth;
184 this->_top_frame.steal(tstate->frame);
185 this->trash_delete_nesting = tstate->trash_delete_nesting;
186#endif // GREENLET_PY311
187}
188
189#if GREENLET_PY312
190void GREENLET_NOINLINE(PythonState::unexpose_frames)()
191{
192 if (!this->top_frame()) {
193 return;
194 }
195
196 // See GreenletState::expose_frames() and the comment on frames_were_exposed
197 // for more information about this logic.
198 _PyInterpreterFrame *iframe = this->_top_frame->f_frame;
199 while (iframe != nullptr) {
200 _PyInterpreterFrame *prev_exposed = iframe->previous;
201 assert(iframe->frame_obj);
202 memcpy(&iframe->previous, &iframe->frame_obj->_f_frame_data[0],
203 sizeof(void *));
204 iframe = prev_exposed;
205 }
206}
207#else
208void PythonState::unexpose_frames()
209{}
210#endif
211
212void PythonState::operator>>(PyThreadState *const tstate) noexcept
213{
214 tstate->context = this->_context.relinquish_ownership();
215 /* Incrementing this value invalidates the contextvars cache,
216 which would otherwise remain valid across switches */
217 tstate->context_ver++;
218#if GREENLET_USE_CFRAME
219 tstate->cframe = this->cframe;
220 /*
221 If we were tracing, we need to keep tracing.
222 There should never be the possibility of hitting the
223 root_cframe here. See note above about why we can't
224 just copy this from ``origin->cframe->use_tracing``.
225 */
226 #if !GREENLET_PY312
227 tstate->cframe->use_tracing = this->use_tracing;
228 #endif
229#endif // GREENLET_USE_CFRAME
230#if GREENLET_PY311
231 #if GREENLET_PY314
232 tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth;
233 tstate->current_executor = this->current_executor;
234 #ifdef Py_GIL_DISABLED
235 ((_PyThreadStateImpl*)tstate)->c_stack_refs = this->c_stack_refs;
236 #endif
237 this->unexpose_frames();
238 #elif GREENLET_PY312
239 tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth;
240 tstate->c_recursion_remaining = Py_C_RECURSION_LIMIT - this->c_recursion_depth;
241 this->unexpose_frames();
242 #else // \/ 3.11
243 tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth;
244 #endif // GREENLET_PY312
245 #if GREENLET_PY313
246 tstate->current_frame = this->current_frame;
247 #elif GREENLET_USE_CFRAME
248 tstate->cframe->current_frame = this->current_frame;
249 #endif
250 tstate->datastack_chunk = this->datastack_chunk;
251 tstate->datastack_top = this->datastack_top;
252 tstate->datastack_limit = this->datastack_limit;
253#if GREENLET_PY314 && defined(Py_GIL_DISABLED)
254 if (this->top_frame()) {
255 this->_top_frame->f_frame->stackpointer = this->stackpointer;
256 }
257#endif
258 this->_top_frame.relinquish_ownership();
259 #if GREENLET_PY313
260 Py_XDECREF(tstate->delete_later);
261 tstate->delete_later = this->delete_later;
262 Py_CLEAR(this->delete_later);
263 #elif GREENLET_PY312
264 tstate->trash.delete_nesting = this->trash_delete_nesting;
265 #else // not 3.12
266 tstate->trash_delete_nesting = this->trash_delete_nesting;
267 #endif // GREENLET_PY312
268#else // not 3.11
269 tstate->frame = this->_top_frame.relinquish_ownership();
270 tstate->recursion_depth = this->recursion_depth;
271 tstate->trash_delete_nesting = this->trash_delete_nesting;
272#endif // GREENLET_PY311
273}
274
275inline void PythonState::will_switch_from(PyThreadState *const origin_tstate) noexcept
276{
277#if GREENLET_USE_CFRAME && !GREENLET_PY312
278 // The weird thing is, we don't actually save this for an
279 // effect on the current greenlet, it's saved for an
280 // effect on the target greenlet. That is, we want
281 // continuity of this setting across the greenlet switch.
282 this->use_tracing = origin_tstate->cframe->use_tracing;
283#endif
284}
285
286void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept
287{
288 this->_top_frame = nullptr;
289#if GREENLET_PY314
290 this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
291 this->current_executor = tstate->current_executor;
292#elif GREENLET_PY312
293 this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
294 // XXX: TODO: Comment from a reviewer:
295 // Should this be ``Py_C_RECURSION_LIMIT - tstate->c_recursion_remaining``?
296 // But to me it looks more like that might not be the right
297 // initialization either?
298 this->c_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
299#elif GREENLET_PY311
300 this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
301#else
302 this->recursion_depth = tstate->recursion_depth;
303#endif
304}
305// TODO: Better state management about when we own the top frame.
306int PythonState::tp_traverse(visitproc visit, void* arg, bool own_top_frame) noexcept
307{
308 Py_VISIT(this->_context.borrow());
309 if (own_top_frame) {
310 Py_VISIT(this->_top_frame.borrow());
311 }
312#if GREENLET_PY314
313 // TODO: Should we be visiting the c_stack_refs objects?
314 // CPython uses a specific macro to do that which takes into
315 // account boxing and null values and then calls
316 // ``_PyGC_VisitStackRef``, but we don't have access to that, and
317 // we can't duplicate it ourself (because it compares
318 // ``visitproc`` to another function we can't access).
319 // The naive way of looping over c_stack_refs->ref and visiting
320 // those crashes the process (at least with GIL disabled).
321#endif
322 return 0;
323}
324
325void PythonState::tp_clear(bool own_top_frame) noexcept
326{
327 PythonStateContext::tp_clear();
328 // If we get here owning a frame,
329 // we got dealloc'd without being finished. We may or may not be
330 // in the same thread.
331 if (own_top_frame) {
332 this->_top_frame.CLEAR();
333 }
334}
335
336#if GREENLET_USE_CFRAME
337void PythonState::set_new_cframe(_PyCFrame& frame) noexcept
338{
339 frame = *PyThreadState_GET()->cframe;
340 /* Make the target greenlet refer to the stack value. */
341 this->cframe = &frame;
342 /*
343 And restore the link to the previous frame so this one gets
344 unliked appropriately.
345 */
346 this->cframe->previous = &PyThreadState_GET()->root_cframe;
347}
348#endif
349
350const PythonState::OwnedFrame& PythonState::top_frame() const noexcept
351{
352 return this->_top_frame;
353}
354
355void PythonState::did_finish(PyThreadState* tstate) noexcept
356{
357#if GREENLET_PY311
358 // See https://github.com/gevent/gevent/issues/1924 and
359 // https://github.com/python-greenlet/greenlet/issues/328. In
360 // short, Python 3.11 allocates memory for frames as a sort of
361 // linked list that's kept as part of PyThreadState in the
362 // ``datastack_chunk`` member and friends. These are saved and
363 // restored as part of switching greenlets.
364 //
365 // When we initially switch to a greenlet, we set those to NULL.
366 // That causes the frame management code to treat this like a
367 // brand new thread and start a fresh list of chunks, beginning
368 // with a new "root" chunk. As we make calls in this greenlet,
369 // those chunks get added, and as calls return, they get popped.
370 // But the frame code (pystate.c) is careful to make sure that the
371 // root chunk never gets popped.
372 //
373 // Thus, when a greenlet exits for the last time, there will be at
374 // least a single root chunk that we must be responsible for
375 // deallocating.
376 //
377 // The complex part is that these chunks are allocated and freed
378 // using ``_PyObject_VirtualAlloc``/``Free``. Those aren't public
379 // functions, and they aren't exported for linking. It so happens
380 // that we know they are just thin wrappers around the Arena
381 // allocator, so we can use that directly to deallocate in a
382 // compatible way.
383 //
384 // CAUTION: Check this implementation detail on every major version.
385 //
386 // It might be nice to be able to do this in our destructor, but
387 // can we be sure that no one else is using that memory? Plus, as
388 // described below, our pointers may not even be valid anymore. As
389 // a special case, there is one time that we know we can do this,
390 // and that's from the destructor of the associated UserGreenlet
391 // (NOT main greenlet)
392 PyObjectArenaAllocator alloc;
393 _PyStackChunk* chunk = nullptr;
394 if (tstate) {
395 // We really did finish, we can never be switched to again.
396 chunk = tstate->datastack_chunk;
397 // Unfortunately, we can't do much sanity checking. Our
398 // this->datastack_chunk pointer is out of date (evaluation may
399 // have popped down through it already) so we can't verify that
400 // we deallocate it. I don't think we can even check datastack_top
401 // for the same reason.
402
403 PyObject_GetArenaAllocator(&alloc);
404 tstate->datastack_chunk = nullptr;
405 tstate->datastack_limit = nullptr;
406 tstate->datastack_top = nullptr;
407
408 }
409 else if (this->datastack_chunk) {
410 // The UserGreenlet (NOT the main greenlet!) is being deallocated. If we're
411 // still holding a stack chunk, it's garbage because we know
412 // we can never switch back to let cPython clean it up.
413 // Because the last time we got switched away from, and we
414 // haven't run since then, we know our chain is valid and can
415 // be dealloced.
416 chunk = this->datastack_chunk;
417 PyObject_GetArenaAllocator(&alloc);
418 }
419
420 if (alloc.free && chunk) {
421 // In case the arena mechanism has been torn down already.
422 while (chunk) {
423 _PyStackChunk *prev = chunk->previous;
424 chunk->previous = nullptr;
425 alloc.free(alloc.ctx, chunk, chunk->size);
426 chunk = prev;
427 }
428 }
429
430 this->datastack_chunk = nullptr;
431 this->datastack_limit = nullptr;
432 this->datastack_top = nullptr;
433#endif
434}
435
436
437}; // namespace greenlet
438
439#endif // GREENLET_PYTHON_STATE_CPP
Definition __init__.py:1