Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
TGreenlet.cpp730 linesDownload Raw Back to greenlet
1/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
2/**
3 * Implementation of greenlet::Greenlet.
4 *
5 * Format with:
6 *  clang-format -i --style=file src/greenlet/greenlet.c
7 *
8 *
9 * Fix missing braces with:
10 *   clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
11*/
12#ifndef TGREENLET_CPP
13#define TGREENLET_CPP
14#include "greenlet_internal.hpp"
15#include "TGreenlet.hpp"
16
17
18#include "TGreenletGlobals.cpp"
19#include "TThreadStateDestroy.cpp"
20
21namespace greenlet {
22
23Greenlet::Greenlet(PyGreenlet* p)
24    :  Greenlet(p, StackState())
25{
26}
27
28Greenlet::Greenlet(PyGreenlet* p, const StackState& initial_stack)
29    :  _self(p), stack_state(initial_stack)
30{
31    assert(p->pimpl == nullptr);
32    p->pimpl = this;
33}
34
35Greenlet::~Greenlet()
36{
37    // XXX: Can't do this. tp_clear is a virtual function, and by the
38    // time we're here, we've sliced off our child classes.
39    //this->tp_clear();
40    this->_self->pimpl = nullptr;
41}
42
43bool
44Greenlet::force_slp_switch_error() const noexcept
45{
46    return false;
47}
48
49void
50Greenlet::release_args()
51{
52    this->switch_args.CLEAR();
53}
54
55/**
56 * CAUTION: This will allocate memory and may trigger garbage
57 * collection and arbitrary Python code.
58 */
59OwnedObject
60Greenlet::throw_GreenletExit_during_dealloc(const ThreadState& UNUSED(current_thread_state))
61{
62    // If we're killed because we lost all references in the
63    // middle of a switch, that's ok. Don't reset the args/kwargs,
64    // we still want to pass them to the parent.
65    PyErr_SetString(mod_globs->PyExc_GreenletExit,
66                    "Killing the greenlet because all references have vanished.");
67    // To get here it had to have run before
68    return this->g_switch();
69}
70
71inline void
72Greenlet::slp_restore_state() noexcept
73{
74#ifdef SLP_BEFORE_RESTORE_STATE
75    SLP_BEFORE_RESTORE_STATE();
76#endif
77    this->stack_state.copy_heap_to_stack(
78           this->thread_state()->borrow_current()->stack_state);
79}
80
81
82inline int
83Greenlet::slp_save_state(char *const stackref) noexcept
84{
85    // XXX: This used to happen in the middle, before saving, but
86    // after finding the next owner. Does that matter? This is
87    // only defined for Sparc/GCC where it flushes register
88    // windows to the stack (I think)
89#ifdef SLP_BEFORE_SAVE_STATE
90    SLP_BEFORE_SAVE_STATE();
91#endif
92    return this->stack_state.copy_stack_to_heap(stackref,
93                                                this->thread_state()->borrow_current()->stack_state);
94}
95
96/**
97 * CAUTION: This will allocate memory and may trigger garbage
98 * collection and arbitrary Python code.
99 */
100OwnedObject
101Greenlet::on_switchstack_or_initialstub_failure(
102    Greenlet* target,
103    const Greenlet::switchstack_result_t& err,
104    const bool target_was_me,
105    const bool was_initial_stub)
106{
107    // If we get here, either g_initialstub()
108    // failed, or g_switchstack() failed. Either one of those
109    // cases SHOULD leave us in the original greenlet with a valid stack.
110    if (!PyErr_Occurred()) {
111        PyErr_SetString(
112            PyExc_SystemError,
113            was_initial_stub
114            ? "Failed to switch stacks into a greenlet for the first time."
115            : "Failed to switch stacks into a running greenlet.");
116    }
117    this->release_args();
118
119    if (target && !target_was_me) {
120        target->murder_in_place();
121    }
122
123    assert(!err.the_new_current_greenlet);
124    assert(!err.origin_greenlet);
125    return OwnedObject();
126
127}
128
129OwnedGreenlet
130Greenlet::g_switchstack_success() noexcept
131{
132    PyThreadState* tstate = PyThreadState_GET();
133    // restore the saved state
134    this->python_state >> tstate;
135    this->exception_state >> tstate;
136
137    // The thread state hasn't been changed yet.
138    ThreadState* thread_state = this->thread_state();
139    OwnedGreenlet result(thread_state->get_current());
140    thread_state->set_current(this->self());
141    //assert(thread_state->borrow_current().borrow() == this->_self);
142    return result;
143}
144
145Greenlet::switchstack_result_t
146Greenlet::g_switchstack(void)
147{
148    // if any of these assertions fail, it's likely because we
149    // switched away and tried to switch back to us. Early stages of
150    // switching are not reentrant because we re-use ``this->args()``.
151    // Switching away would happen if we trigger a garbage collection
152    // (by just using some Python APIs that happen to allocate Python
153    // objects) and some garbage had weakref callbacks or __del__ that
154    // switches (people don't write code like that by hand, but with
155    // gevent it's possible without realizing it)
156    assert(this->args() || PyErr_Occurred());
157    { /* save state */
158        if (this->thread_state()->is_current(this->self())) {
159            // Hmm, nothing to do.
160            // TODO: Does this bypass trace events that are
161            // important?
162            return switchstack_result_t(0,
163                                        this, this->thread_state()->borrow_current());
164        }
165        BorrowedGreenlet current = this->thread_state()->borrow_current();
166        PyThreadState* tstate = PyThreadState_GET();
167
168        current->python_state << tstate;
169        current->exception_state << tstate;
170        this->python_state.will_switch_from(tstate);
171        switching_thread_state = this;
172        current->expose_frames();
173    }
174    assert(this->args() || PyErr_Occurred());
175    // If this is the first switch into a greenlet, this will
176    // return twice, once with 1 in the new greenlet, once with 0
177    // in the origin.
178    int err;
179    if (this->force_slp_switch_error()) {
180        err = -1;
181    }
182    else {
183        err = slp_switch();
184    }
185
186    if (err < 0) { /* error */
187        // Tested by
188        // test_greenlet.TestBrokenGreenlets.test_failed_to_slp_switch_into_running
189        //
190        // It's not clear if it's worth trying to clean up and
191        // continue here. Failing to switch stacks is a big deal which
192        // may not be recoverable (who knows what state the stack is in).
193        // Also, we've stolen references in preparation for calling
194        // ``g_switchstack_success()`` and we don't have a clean
195        // mechanism for backing that all out.
196        Py_FatalError("greenlet: Failed low-level slp_switch(). The stack is probably corrupt.");
197    }
198
199    // No stack-based variables are valid anymore.
200
201    // But the global is thread_local volatile so we can reload it without the
202    // compiler caching it from earlier.
203    Greenlet* greenlet_that_switched_in = switching_thread_state; // aka this
204    switching_thread_state = nullptr;
205    // except that no stack variables are valid, we would:
206    // assert(this == greenlet_that_switched_in);
207
208    // switchstack success is where we restore the exception state,
209    // etc. It returns the origin greenlet because its convenient.
210
211    OwnedGreenlet origin = greenlet_that_switched_in->g_switchstack_success();
212    assert(greenlet_that_switched_in->args() || PyErr_Occurred());
213    return switchstack_result_t(err, greenlet_that_switched_in, origin);
214}
215
216
217inline void
218Greenlet::check_switch_allowed() const
219{
220    // TODO: Make this take a parameter of the current greenlet,
221    // or current main greenlet, to make the check for
222    // cross-thread switching cheaper. Surely somewhere up the
223    // call stack we've already accessed the thread local variable.
224
225    // We expect to always have a main greenlet now; accessing the thread state
226    // created it. However, if we get here and cleanup has already
227    // begun because we're a greenlet that was running in a
228    // (now dead) thread, these invariants will not hold true. In
229    // fact, accessing `this->thread_state` may not even be possible.
230
231    // If the thread this greenlet was running in is dead,
232    // we'll still have a reference to a main greenlet, but the
233    // thread state pointer we have is bogus (should be nullptr)
234    // TODO: Give the objects an API to determine if they belong
235    // to a dead thread.
236
237    const BorrowedMainGreenlet my_main_greenlet = this->find_main_greenlet_in_lineage();
238
239    if (!my_main_greenlet) {
240        throw PyErrOccurred(mod_globs->PyExc_GreenletError,
241                            "cannot switch to a garbage collected greenlet");
242    }
243
244    if (!my_main_greenlet->thread_state()) {
245        throw PyErrOccurred(mod_globs->PyExc_GreenletError,
246                            "cannot switch to a different thread (which happens to have exited)");
247    }
248
249    // The main greenlet we found was from the .parent lineage.
250    // That may or may not have any relationship to the main
251    // greenlet of the running thread. We can't actually access
252    // our this->thread_state members to try to check that,
253    // because it could be in the process of getting destroyed,
254    // but setting the main_greenlet->thread_state member to NULL
255    // may not be visible yet. So we need to check against the
256    // current thread state (once the cheaper checks are out of
257    // the way)
258    const BorrowedMainGreenlet main_greenlet_cur_thread = GET_THREAD_STATE().state().borrow_main_greenlet();
259    if (
260        // lineage main greenlet is not this thread's greenlet
261        main_greenlet_cur_thread != my_main_greenlet
262        || (
263            // atteched to some thread
264            this->main_greenlet()
265            // XXX: Same condition as above. Was this supposed to be
266            // this->main_greenlet()?
267            && main_greenlet_cur_thread != my_main_greenlet)
268        // switching into a known dead thread (XXX: which, if we get here,
269        // is bad, because we just accessed the thread state, which is
270        // gone!)
271        || (!main_greenlet_cur_thread->thread_state())) {
272        // CAUTION: This may trigger memory allocations, gc, and
273        // arbitrary Python code.
274        throw PyErrOccurred(
275            mod_globs->PyExc_GreenletError,
276            "Cannot switch to a different thread\n\tCurrent:  %R\n\tExpected: %R",
277            main_greenlet_cur_thread, my_main_greenlet);
278    }
279}
280
281const OwnedObject
282Greenlet::context() const
283{
284    using greenlet::PythonStateContext;
285    OwnedObject result;
286
287    if (this->is_currently_running_in_some_thread()) {
288        /* Currently running greenlet: context is stored in the thread state,
289           not the greenlet object. */
290        if (GET_THREAD_STATE().state().is_current(this->self())) {
291            result = PythonStateContext::context(PyThreadState_GET());
292        }
293        else {
294            throw ValueError(
295                            "cannot get context of a "
296                            "greenlet that is running in a different thread");
297        }
298    }
299    else {
300        /* Greenlet is not running: just return context. */
301        result = this->python_state.context();
302    }
303    if (!result) {
304        result = OwnedObject::None();
305    }
306    return result;
307}
308
309
310void
311Greenlet::context(BorrowedObject given)
312{
313    using greenlet::PythonStateContext;
314    if (!given) {
315        throw AttributeError("can't delete context attribute");
316    }
317    if (given.is_None()) {
318        /* "Empty context" is stored as NULL, not None. */
319        given = nullptr;
320    }
321
322    //checks type, incrs refcnt
323    greenlet::refs::OwnedContext context(given);
324    PyThreadState* tstate = PyThreadState_GET();
325
326    if (this->is_currently_running_in_some_thread()) {
327        if (!GET_THREAD_STATE().state().is_current(this->self())) {
328            throw ValueError("cannot set context of a greenlet"
329                             " that is running in a different thread");
330        }
331
332        /* Currently running greenlet: context is stored in the thread state,
333           not the greenlet object. */
334        OwnedObject octx = OwnedObject::consuming(PythonStateContext::context(tstate));
335        PythonStateContext::context(tstate, context.relinquish_ownership());
336    }
337    else {
338        /* Greenlet is not running: just set context. Note that the
339           greenlet may be dead.*/
340        this->python_state.context() = context;
341    }
342}
343
344/**
345 * CAUTION: May invoke arbitrary Python code.
346 *
347 * Figure out what the result of ``greenlet.switch(arg, kwargs)``
348 * should be and transfers ownership of it to the left-hand-side.
349 *
350 * If switch() was just passed an arg tuple, then we'll just return that.
351 * If only keyword arguments were passed, then we'll pass the keyword
352 * argument dict. Otherwise, we'll create a tuple of (args, kwargs) and
353 * return both.
354 *
355 * CAUTION: This may allocate a new tuple object, which may
356 * cause the Python garbage collector to run, which in turn may
357 * run arbitrary Python code that switches.
358 */
359OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept
360{
361    // Because this may invoke arbitrary Python code, which could
362    // result in switching back to us, we need to get the
363    // arguments locally on the stack.
364    assert(rhs);
365    OwnedObject args = rhs.args();
366    OwnedObject kwargs = rhs.kwargs();
367    rhs.CLEAR();
368    // We shouldn't be called twice for the same switch.
369    assert(args || kwargs);
370    assert(!rhs);
371
372    if (!kwargs) {
373        lhs = args;
374    }
375    else if (!PyDict_Size(kwargs.borrow())) {
376        lhs = args;
377    }
378    else if (!PySequence_Length(args.borrow())) {
379        lhs = kwargs;
380    }
381    else {
382        // PyTuple_Pack allocates memory, may GC, may run arbitrary
383        // Python code.
384        lhs = OwnedObject::consuming(PyTuple_Pack(2, args.borrow(), kwargs.borrow()));
385    }
386    return lhs;
387}
388
389static OwnedObject
390g_handle_exit(const OwnedObject& greenlet_result)
391{
392    if (!greenlet_result && mod_globs->PyExc_GreenletExit.PyExceptionMatches()) {
393        /* catch and ignore GreenletExit */
394        PyErrFetchParam val;
395        // TODO: When we run on 3.12+ only (GREENLET_312), switch to the
396        // ``PyErr_GetRaisedException`` family of functions. The
397        // ``PyErr_Fetch`` family is deprecated on 3.12+, but is part
398        // of the stable ABI so it's not going anywhere.
399        PyErr_Fetch(PyErrFetchParam(), val, PyErrFetchParam());
400        if (!val) {
401            return OwnedObject::None();
402        }
403        return OwnedObject(val);
404    }
405
406    if (greenlet_result) {
407        // package the result into a 1-tuple
408        // PyTuple_Pack increments the reference of its arguments,
409        // so we always need to decref the greenlet result;
410        // the owner will do that.
411        return OwnedObject::consuming(PyTuple_Pack(1, greenlet_result.borrow()));
412    }
413
414    return OwnedObject();
415}
416
417
418
419/**
420 * May run arbitrary Python code.
421 */
422OwnedObject
423Greenlet::g_switch_finish(const switchstack_result_t& err)
424{
425    assert(err.the_new_current_greenlet == this);
426
427    ThreadState& state = *this->thread_state();
428    // Because calling the trace function could do arbitrary things,
429    // including switching away from this greenlet and then maybe
430    // switching back, we need to capture the arguments now so that
431    // they don't change.
432    OwnedObject result;
433    if (this->args()) {
434        result <<= this->args();
435    }
436    else {
437        assert(PyErr_Occurred());
438    }
439    assert(!this->args());
440    try {
441        // Our only caller handles the bad error case
442        assert(err.status >= 0);
443        assert(state.borrow_current() == this->self());
444        if (OwnedObject tracefunc = state.get_tracefunc()) {
445            assert(result || PyErr_Occurred());
446            g_calltrace(tracefunc,
447                        result ? mod_globs->event_switch : mod_globs->event_throw,
448                        err.origin_greenlet,
449                        this->self());
450        }
451        // The above could have invoked arbitrary Python code, but
452        // it couldn't switch back to this object and *also*
453        // throw an exception, so the args won't have changed.
454
455        if (PyErr_Occurred()) {
456            // We get here if we fell of the end of the run() function
457            // raising an exception. The switch itself was
458            // successful, but the function raised.
459            // valgrind reports that memory allocated here can still
460            // be reached after a test run.
461            throw PyErrOccurred::from_current();
462        }
463        return result;
464    }
465    catch (const PyErrOccurred&) {
466        /* Turn switch errors into switch throws */
467        /* Turn trace errors into switch throws */
468        this->release_args();
469        throw;
470    }
471}
472
473void
474Greenlet::g_calltrace(const OwnedObject& tracefunc,
475                      const greenlet::refs::ImmortalEventName& event,
476                      const BorrowedGreenlet& origin,
477                      const BorrowedGreenlet& target)
478{
479    PyErrPieces saved_exc;
480    try {
481        TracingGuard tracing_guard;
482        // TODO: We have saved the active exception (if any) that's
483        // about to be raised. In the 'throw' case, we could provide
484        // the exception to the tracefunction, which seems very helpful.
485        tracing_guard.CallTraceFunction(tracefunc, event, origin, target);
486    }
487    catch (const PyErrOccurred&) {
488        // In case of exceptions trace function is removed,
489        // and any existing exception is replaced with the tracing
490        // exception.
491        GET_THREAD_STATE().state().set_tracefunc(Py_None);
492        throw;
493    }
494
495    saved_exc.PyErrRestore();
496    assert(
497        (event == mod_globs->event_throw && PyErr_Occurred())
498        || (event == mod_globs->event_switch && !PyErr_Occurred())
499    );
500}
501
502void
503Greenlet::murder_in_place()
504{
505    if (this->active()) {
506        assert(!this->is_currently_running_in_some_thread());
507        this->deactivate_and_free();
508    }
509}
510
511inline void
512Greenlet::deactivate_and_free()
513{
514    if (!this->active()) {
515        return;
516    }
517    // Throw away any saved stack.
518    this->stack_state = StackState();
519    assert(!this->stack_state.active());
520    // Throw away any Python references.
521    // We're holding a borrowed reference to the last
522    // frame we executed. Since we borrowed it, the
523    // normal traversal, clear, and dealloc functions
524    // ignore it, meaning it leaks. (The thread state
525    // object can't find it to clear it when that's
526    // deallocated either, because by definition if we
527    // got an object on this list, it wasn't
528    // running and the thread state doesn't have
529    // this frame.)
530    // So here, we *do* clear it.
531    this->python_state.tp_clear(true);
532}
533
534bool
535Greenlet::belongs_to_thread(const ThreadState* thread_state) const
536{
537    if (!this->thread_state() // not running anywhere, or thread
538                              // exited
539        || !thread_state) { // same, or there is no thread state.
540        return false;
541    }
542    return true;
543}
544
545
546void
547Greenlet::deallocing_greenlet_in_thread(const ThreadState* current_thread_state)
548{
549    /* Cannot raise an exception to kill the greenlet if
550       it is not running in the same thread! */
551    if (this->belongs_to_thread(current_thread_state)) {
552        assert(current_thread_state);
553        // To get here it had to have run before
554        /* Send the greenlet a GreenletExit exception. */
555
556        // We don't care about the return value, only whether an
557        // exception happened.
558        this->throw_GreenletExit_during_dealloc(*current_thread_state);
559        return;
560    }
561
562    // Not the same thread! Temporarily save the greenlet
563    // into its thread's deleteme list, *if* it exists.
564    // If that thread has already exited, and processed its pending
565    // cleanup, we'll never be able to clean everything up: we won't
566    // be able to raise an exception.
567    // That's mostly OK! Since we can't add it to a list, our refcount
568    // won't increase, and we'll go ahead with the DECREFs later.
569
570    ThreadState *const  thread_state = this->thread_state();
571    if (thread_state) {
572        thread_state->delete_when_thread_running(this->self());
573    }
574    else {
575        // The thread is dead, we can't raise an exception.
576        // We need to make it look non-active, though, so that dealloc
577        // finishes killing it.
578        this->deactivate_and_free();
579    }
580    return;
581}
582
583
584int
585Greenlet::tp_traverse(visitproc visit, void* arg)
586{
587
588    int result;
589    if ((result = this->exception_state.tp_traverse(visit, arg)) != 0) {
590        return result;
591    }
592    //XXX: This is ugly. But so is handling everything having to do
593    //with the top frame.
594    bool visit_top_frame = this->was_running_in_dead_thread();
595    // When true, the thread is dead. Our implicit weak reference to the
596    // frame is now all that's left; we consider ourselves to
597    // strongly own it now.
598    if ((result = this->python_state.tp_traverse(visit, arg, visit_top_frame)) != 0) {
599        return result;
600    }
601    return 0;
602}
603
604int
605Greenlet::tp_clear()
606{
607    bool own_top_frame = this->was_running_in_dead_thread();
608    this->exception_state.tp_clear();
609    this->python_state.tp_clear(own_top_frame);
610    return 0;
611}
612
613bool Greenlet::is_currently_running_in_some_thread() const
614{
615    return this->stack_state.active() && !this->python_state.top_frame();
616}
617
618#if GREENLET_PY312
619void GREENLET_NOINLINE(Greenlet::expose_frames)()
620{
621    if (!this->python_state.top_frame()) {
622        return;
623    }
624
625    _PyInterpreterFrame* last_complete_iframe = nullptr;
626    _PyInterpreterFrame* iframe = this->python_state.top_frame()->f_frame;
627    while (iframe) {
628        // We must make a copy before looking at the iframe contents,
629        // since iframe might point to a portion of the greenlet's C stack
630        // that was spilled when switching greenlets.
631        _PyInterpreterFrame iframe_copy;
632        this->stack_state.copy_from_stack(&iframe_copy, iframe, sizeof(*iframe));
633        if (!_PyFrame_IsIncomplete(&iframe_copy)) {
634            // If the iframe were OWNED_BY_CSTACK then it would always be
635            // incomplete. Since it's not incomplete, it's not on the C stack
636            // and we can access it through the original `iframe` pointer
637            // directly.  This is important since GetFrameObject might
638            // lazily _create_ the frame object and we don't want the
639            // interpreter to lose track of it.
640            //
641            #if !GREENLET_PY315
642            // This enum value was removed in
643            //    https://github.com/python/cpython/pull/141108
644
645            assert(iframe_copy.owner != FRAME_OWNED_BY_CSTACK);
646            #endif
647
648            // We really want to just write:
649            //     PyFrameObject* frame = _PyFrame_GetFrameObject(iframe);
650            // but _PyFrame_GetFrameObject calls _PyFrame_MakeAndSetFrameObject
651            // which is not a visible symbol in libpython. The easiest
652            // way to get a public function to call it is using
653            // PyFrame_GetBack, which is defined as follows:
654            //     assert(frame != NULL);
655            //     assert(!_PyFrame_IsIncomplete(frame->f_frame));
656            //     PyFrameObject *back = frame->f_back;
657            //     if (back == NULL) {
658            //         _PyInterpreterFrame *prev = frame->f_frame->previous;
659            //         prev = _PyFrame_GetFirstComplete(prev);
660            //         if (prev) {
661            //             back = _PyFrame_GetFrameObject(prev);
662            //         }
663            //     }
664            //     return (PyFrameObject*)Py_XNewRef(back);
665            if (!iframe->frame_obj) {
666                PyFrameObject dummy_frame;
667                _PyInterpreterFrame dummy_iframe;
668                dummy_frame.f_back = nullptr;
669                dummy_frame.f_frame = &dummy_iframe;
670                // force the iframe to be considered complete without
671                // needing to check its code object:
672                dummy_iframe.owner = FRAME_OWNED_BY_GENERATOR;
673                dummy_iframe.previous = iframe;
674                assert(!_PyFrame_IsIncomplete(&dummy_iframe));
675                // Drop the returned reference immediately; the iframe
676                // continues to hold a strong reference
677                Py_XDECREF(PyFrame_GetBack(&dummy_frame));
678                assert(iframe->frame_obj);
679            }
680
681            // This is a complete frame, so make the last one of those we saw
682            // point at it, bypassing any incomplete frames (which may have
683            // been on the C stack) in between the two. We're overwriting
684            // last_complete_iframe->previous and need that to be reversible,
685            // so we store the original previous ptr in the frame object
686            // (which we must have created on a previous iteration through
687            // this loop). The frame object has a bunch of storage that is
688            // only used when its iframe is OWNED_BY_FRAME_OBJECT, which only
689            // occurs when the frame object outlives the frame's execution,
690            // which can't have happened yet because the frame is currently
691            // executing as far as the interpreter is concerned. So, we can
692            // reuse it for our own purposes.
693            assert(iframe->owner == FRAME_OWNED_BY_THREAD
694                   || iframe->owner == FRAME_OWNED_BY_GENERATOR);
695            if (last_complete_iframe) {
696                assert(last_complete_iframe->frame_obj);
697                memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0],
698                       &last_complete_iframe->previous, sizeof(void *));
699                last_complete_iframe->previous = iframe;
700            }
701            last_complete_iframe = iframe;
702        }
703        // Frames that are OWNED_BY_FRAME_OBJECT are linked via the
704        // frame's f_back while all others are linked via the iframe's
705        // previous ptr. Since all the frames we traverse are running
706        // as far as the interpreter is concerned, we don't have to
707        // worry about the OWNED_BY_FRAME_OBJECT case.
708        iframe = iframe_copy.previous;
709    }
710
711    // Give the outermost complete iframe a null previous pointer to
712    // account for any potential incomplete/C-stack iframes between it
713    // and the actual top-of-stack
714    if (last_complete_iframe) {
715        assert(last_complete_iframe->frame_obj);
716        memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0],
717               &last_complete_iframe->previous, sizeof(void *));
718        last_complete_iframe->previous = nullptr;
719    }
720}
721#else
722void Greenlet::expose_frames()
723{
724
725}
726#endif
727
728}; // namespace greenlet
729#endif
730 
codekingpro/portable-devtools · Team Ai