summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/greenlet/TGreenlet.cpp
diff options
context:
space:
mode:
authorcyfraeviolae <cyfraeviolae>2024-04-03 03:10:44 -0400
committercyfraeviolae <cyfraeviolae>2024-04-03 03:10:44 -0400
commit6d7ba58f880be618ade07f8ea080fe8c4bf8a896 (patch)
treeb1c931051ffcebd2bd9d61d98d6233ffa289bbce /venv/lib/python3.11/site-packages/greenlet/TGreenlet.cpp
parent4f884c9abc32990b4061a1bb6997b4b37e58ea0b (diff)
venv
Diffstat (limited to 'venv/lib/python3.11/site-packages/greenlet/TGreenlet.cpp')
-rw-r--r--venv/lib/python3.11/site-packages/greenlet/TGreenlet.cpp714
1 files changed, 714 insertions, 0 deletions
diff --git a/venv/lib/python3.11/site-packages/greenlet/TGreenlet.cpp b/venv/lib/python3.11/site-packages/greenlet/TGreenlet.cpp
new file mode 100644
index 0000000..51f8995
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/greenlet/TGreenlet.cpp
@@ -0,0 +1,714 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+/**
+ * Implementation of greenlet::Greenlet.
+ *
+ * Format with:
+ * clang-format -i --style=file src/greenlet/greenlet.c
+ *
+ *
+ * Fix missing braces with:
+ * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
+*/
+
+#include "greenlet_internal.hpp"
+#include "greenlet_greenlet.hpp"
+#include "greenlet_thread_state.hpp"
+
+#include "TGreenletGlobals.cpp"
+#include "TThreadStateDestroy.cpp"
+
+namespace greenlet {
+
+Greenlet::Greenlet(PyGreenlet* p)
+{
+ p ->pimpl = this;
+}
+
+Greenlet::~Greenlet()
+{
+ // XXX: Can't do this. tp_clear is a virtual function, and by the
+ // time we're here, we've sliced off our child classes.
+ //this->tp_clear();
+}
+
+Greenlet::Greenlet(PyGreenlet* p, const StackState& initial_stack)
+ : stack_state(initial_stack)
+{
+ // can't use a delegating constructor because of
+ // MSVC for Python 2.7
+ p->pimpl = this;
+}
+
+bool
+Greenlet::force_slp_switch_error() const noexcept
+{
+ return false;
+}
+
+void
+Greenlet::release_args()
+{
+ this->switch_args.CLEAR();
+}
+
+/**
+ * CAUTION: This will allocate memory and may trigger garbage
+ * collection and arbitrary Python code.
+ */
+OwnedObject
+Greenlet::throw_GreenletExit_during_dealloc(const ThreadState& UNUSED(current_thread_state))
+{
+ // If we're killed because we lost all references in the
+ // middle of a switch, that's ok. Don't reset the args/kwargs,
+ // we still want to pass them to the parent.
+ PyErr_SetString(mod_globs->PyExc_GreenletExit,
+ "Killing the greenlet because all references have vanished.");
+ // To get here it had to have run before
+ return this->g_switch();
+}
+
+inline void
+Greenlet::slp_restore_state() noexcept
+{
+#ifdef SLP_BEFORE_RESTORE_STATE
+ SLP_BEFORE_RESTORE_STATE();
+#endif
+ this->stack_state.copy_heap_to_stack(
+ this->thread_state()->borrow_current()->stack_state);
+}
+
+
+inline int
+Greenlet::slp_save_state(char *const stackref) noexcept
+{
+ // XXX: This used to happen in the middle, before saving, but
+ // after finding the next owner. Does that matter? This is
+ // only defined for Sparc/GCC where it flushes register
+ // windows to the stack (I think)
+#ifdef SLP_BEFORE_SAVE_STATE
+ SLP_BEFORE_SAVE_STATE();
+#endif
+ return this->stack_state.copy_stack_to_heap(stackref,
+ this->thread_state()->borrow_current()->stack_state);
+}
+
+/**
+ * CAUTION: This will allocate memory and may trigger garbage
+ * collection and arbitrary Python code.
+ */
+OwnedObject
+Greenlet::on_switchstack_or_initialstub_failure(
+ Greenlet* target,
+ const Greenlet::switchstack_result_t& err,
+ const bool target_was_me,
+ const bool was_initial_stub)
+{
+ // If we get here, either g_initialstub()
+ // failed, or g_switchstack() failed. Either one of those
+ // cases SHOULD leave us in the original greenlet with a valid stack.
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(
+ PyExc_SystemError,
+ was_initial_stub
+ ? "Failed to switch stacks into a greenlet for the first time."
+ : "Failed to switch stacks into a running greenlet.");
+ }
+ this->release_args();
+
+ if (target && !target_was_me) {
+ target->murder_in_place();
+ }
+
+ assert(!err.the_new_current_greenlet);
+ assert(!err.origin_greenlet);
+ return OwnedObject();
+
+}
+
+OwnedGreenlet
+Greenlet::g_switchstack_success() noexcept
+{
+ PyThreadState* tstate = PyThreadState_GET();
+ // restore the saved state
+ this->python_state >> tstate;
+ this->exception_state >> tstate;
+
+ // The thread state hasn't been changed yet.
+ ThreadState* thread_state = this->thread_state();
+ OwnedGreenlet result(thread_state->get_current());
+ thread_state->set_current(this->self());
+ //assert(thread_state->borrow_current().borrow() == this->_self);
+ return result;
+}
+
+Greenlet::switchstack_result_t
+Greenlet::g_switchstack(void)
+{
+ // if any of these assertions fail, it's likely because we
+ // switched away and tried to switch back to us. Early stages of
+ // switching are not reentrant because we re-use ``this->args()``.
+ // Switching away would happen if we trigger a garbage collection
+ // (by just using some Python APIs that happen to allocate Python
+ // objects) and some garbage had weakref callbacks or __del__ that
+ // switches (people don't write code like that by hand, but with
+ // gevent it's possible without realizing it)
+ assert(this->args() || PyErr_Occurred());
+ { /* save state */
+ if (this->thread_state()->is_current(this->self())) {
+ // Hmm, nothing to do.
+ // TODO: Does this bypass trace events that are
+ // important?
+ return switchstack_result_t(0,
+ this, this->thread_state()->borrow_current());
+ }
+ BorrowedGreenlet current = this->thread_state()->borrow_current();
+ PyThreadState* tstate = PyThreadState_GET();
+
+ current->python_state << tstate;
+ current->exception_state << tstate;
+ this->python_state.will_switch_from(tstate);
+ switching_thread_state = this;
+ current->expose_frames();
+ }
+ assert(this->args() || PyErr_Occurred());
+ // If this is the first switch into a greenlet, this will
+ // return twice, once with 1 in the new greenlet, once with 0
+ // in the origin.
+ int err;
+ if (this->force_slp_switch_error()) {
+ err = -1;
+ }
+ else {
+ err = slp_switch();
+ }
+
+ if (err < 0) { /* error */
+ // Tested by
+ // test_greenlet.TestBrokenGreenlets.test_failed_to_slp_switch_into_running
+ //
+ // It's not clear if it's worth trying to clean up and
+ // continue here. Failing to switch stacks is a big deal which
+ // may not be recoverable (who knows what state the stack is in).
+ // Also, we've stolen references in preparation for calling
+ // ``g_switchstack_success()`` and we don't have a clean
+ // mechanism for backing that all out.
+ Py_FatalError("greenlet: Failed low-level slp_switch(). The stack is probably corrupt.");
+ }
+
+ // No stack-based variables are valid anymore.
+
+ // But the global is volatile so we can reload it without the
+ // compiler caching it from earlier.
+ Greenlet* greenlet_that_switched_in = switching_thread_state; // aka this
+ switching_thread_state = nullptr;
+ // except that no stack variables are valid, we would:
+ // assert(this == greenlet_that_switched_in);
+
+ // switchstack success is where we restore the exception state,
+ // etc. It returns the origin greenlet because its convenient.
+
+ OwnedGreenlet origin = greenlet_that_switched_in->g_switchstack_success();
+ assert(greenlet_that_switched_in->args() || PyErr_Occurred());
+ return switchstack_result_t(err, greenlet_that_switched_in, origin);
+}
+
+
+inline void
+Greenlet::check_switch_allowed() const
+{
+ // TODO: Make this take a parameter of the current greenlet,
+ // or current main greenlet, to make the check for
+ // cross-thread switching cheaper. Surely somewhere up the
+ // call stack we've already accessed the thread local variable.
+
+ // We expect to always have a main greenlet now; accessing the thread state
+ // created it. However, if we get here and cleanup has already
+ // begun because we're a greenlet that was running in a
+ // (now dead) thread, these invariants will not hold true. In
+ // fact, accessing `this->thread_state` may not even be possible.
+
+ // If the thread this greenlet was running in is dead,
+ // we'll still have a reference to a main greenlet, but the
+ // thread state pointer we have is bogus.
+ // TODO: Give the objects an API to determine if they belong
+ // to a dead thread.
+
+ const BorrowedMainGreenlet main_greenlet = this->find_main_greenlet_in_lineage();
+
+ if (!main_greenlet) {
+ throw PyErrOccurred(mod_globs->PyExc_GreenletError,
+ "cannot switch to a garbage collected greenlet");
+ }
+
+ if (!main_greenlet->thread_state()) {
+ throw PyErrOccurred(mod_globs->PyExc_GreenletError,
+ "cannot switch to a different thread (which happens to have exited)");
+ }
+
+ // The main greenlet we found was from the .parent lineage.
+ // That may or may not have any relationship to the main
+ // greenlet of the running thread. We can't actually access
+ // our this->thread_state members to try to check that,
+ // because it could be in the process of getting destroyed,
+ // but setting the main_greenlet->thread_state member to NULL
+ // may not be visible yet. So we need to check against the
+ // current thread state (once the cheaper checks are out of
+ // the way)
+ const BorrowedMainGreenlet current_main_greenlet = GET_THREAD_STATE().state().borrow_main_greenlet();
+ if (
+ // lineage main greenlet is not this thread's greenlet
+ current_main_greenlet != main_greenlet
+ || (
+ // atteched to some thread
+ this->main_greenlet()
+ // XXX: Same condition as above. Was this supposed to be
+ // this->main_greenlet()?
+ && current_main_greenlet != main_greenlet)
+ // switching into a known dead thread (XXX: which, if we get here,
+ // is bad, because we just accessed the thread state, which is
+ // gone!)
+ || (!current_main_greenlet->thread_state())) {
+ // CAUTION: This may trigger memory allocations, gc, and
+ // arbitrary Python code.
+ throw PyErrOccurred(mod_globs->PyExc_GreenletError,
+ "cannot switch to a different thread");
+ }
+}
+
+const OwnedObject
+Greenlet::context() const
+{
+ using greenlet::PythonStateContext;
+ OwnedObject result;
+
+ if (this->is_currently_running_in_some_thread()) {
+ /* Currently running greenlet: context is stored in the thread state,
+ not the greenlet object. */
+ if (GET_THREAD_STATE().state().is_current(this->self())) {
+ result = PythonStateContext::context(PyThreadState_GET());
+ }
+ else {
+ throw ValueError(
+ "cannot get context of a "
+ "greenlet that is running in a different thread");
+ }
+ }
+ else {
+ /* Greenlet is not running: just return context. */
+ result = this->python_state.context();
+ }
+ if (!result) {
+ result = OwnedObject::None();
+ }
+ return result;
+}
+
+
+void
+Greenlet::context(BorrowedObject given)
+{
+ using greenlet::PythonStateContext;
+ if (!given) {
+ throw AttributeError("can't delete context attribute");
+ }
+ if (given.is_None()) {
+ /* "Empty context" is stored as NULL, not None. */
+ given = nullptr;
+ }
+
+ //checks type, incrs refcnt
+ greenlet::refs::OwnedContext context(given);
+ PyThreadState* tstate = PyThreadState_GET();
+
+ if (this->is_currently_running_in_some_thread()) {
+ if (!GET_THREAD_STATE().state().is_current(this->self())) {
+ throw ValueError("cannot set context of a greenlet"
+ " that is running in a different thread");
+ }
+
+ /* Currently running greenlet: context is stored in the thread state,
+ not the greenlet object. */
+ OwnedObject octx = OwnedObject::consuming(PythonStateContext::context(tstate));
+ PythonStateContext::context(tstate, context.relinquish_ownership());
+ }
+ else {
+ /* Greenlet is not running: just set context. Note that the
+ greenlet may be dead.*/
+ this->python_state.context() = context;
+ }
+}
+
+/**
+ * CAUTION: May invoke arbitrary Python code.
+ *
+ * Figure out what the result of ``greenlet.switch(arg, kwargs)``
+ * should be and transfers ownership of it to the left-hand-side.
+ *
+ * If switch() was just passed an arg tuple, then we'll just return that.
+ * If only keyword arguments were passed, then we'll pass the keyword
+ * argument dict. Otherwise, we'll create a tuple of (args, kwargs) and
+ * return both.
+ *
+ * CAUTION: This may allocate a new tuple object, which may
+ * cause the Python garbage collector to run, which in turn may
+ * run arbitrary Python code that switches.
+ */
+OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept
+{
+ // Because this may invoke arbitrary Python code, which could
+ // result in switching back to us, we need to get the
+ // arguments locally on the stack.
+ assert(rhs);
+ OwnedObject args = rhs.args();
+ OwnedObject kwargs = rhs.kwargs();
+ rhs.CLEAR();
+ // We shouldn't be called twice for the same switch.
+ assert(args || kwargs);
+ assert(!rhs);
+
+ if (!kwargs) {
+ lhs = args;
+ }
+ else if (!PyDict_Size(kwargs.borrow())) {
+ lhs = args;
+ }
+ else if (!PySequence_Length(args.borrow())) {
+ lhs = kwargs;
+ }
+ else {
+ // PyTuple_Pack allocates memory, may GC, may run arbitrary
+ // Python code.
+ lhs = OwnedObject::consuming(PyTuple_Pack(2, args.borrow(), kwargs.borrow()));
+ }
+ return lhs;
+}
+
+static OwnedObject
+g_handle_exit(const OwnedObject& greenlet_result)
+{
+ if (!greenlet_result && mod_globs->PyExc_GreenletExit.PyExceptionMatches()) {
+ /* catch and ignore GreenletExit */
+ PyErrFetchParam val;
+ PyErr_Fetch(PyErrFetchParam(), val, PyErrFetchParam());
+ if (!val) {
+ return OwnedObject::None();
+ }
+ return OwnedObject(val);
+ }
+
+ if (greenlet_result) {
+ // package the result into a 1-tuple
+ // PyTuple_Pack increments the reference of its arguments,
+ // so we always need to decref the greenlet result;
+ // the owner will do that.
+ return OwnedObject::consuming(PyTuple_Pack(1, greenlet_result.borrow()));
+ }
+
+ return OwnedObject();
+}
+
+
+
+/**
+ * May run arbitrary Python code.
+ */
+OwnedObject
+Greenlet::g_switch_finish(const switchstack_result_t& err)
+{
+ assert(err.the_new_current_greenlet == this);
+
+ ThreadState& state = *this->thread_state();
+ // Because calling the trace function could do arbitrary things,
+ // including switching away from this greenlet and then maybe
+ // switching back, we need to capture the arguments now so that
+ // they don't change.
+ OwnedObject result;
+ if (this->args()) {
+ result <<= this->args();
+ }
+ else {
+ assert(PyErr_Occurred());
+ }
+ assert(!this->args());
+ try {
+ // Our only caller handles the bad error case
+ assert(err.status >= 0);
+ assert(state.borrow_current() == this->self());
+ if (OwnedObject tracefunc = state.get_tracefunc()) {
+ assert(result || PyErr_Occurred());
+ g_calltrace(tracefunc,
+ result ? mod_globs->event_switch : mod_globs->event_throw,
+ err.origin_greenlet,
+ this->self());
+ }
+ // The above could have invoked arbitrary Python code, but
+ // it couldn't switch back to this object and *also*
+ // throw an exception, so the args won't have changed.
+
+ if (PyErr_Occurred()) {
+ // We get here if we fell of the end of the run() function
+ // raising an exception. The switch itself was
+ // successful, but the function raised.
+ // valgrind reports that memory allocated here can still
+ // be reached after a test run.
+ throw PyErrOccurred::from_current();
+ }
+ return result;
+ }
+ catch (const PyErrOccurred&) {
+ /* Turn switch errors into switch throws */
+ /* Turn trace errors into switch throws */
+ this->release_args();
+ throw;
+ }
+}
+
+void
+Greenlet::g_calltrace(const OwnedObject& tracefunc,
+ const greenlet::refs::ImmortalEventName& event,
+ const BorrowedGreenlet& origin,
+ const BorrowedGreenlet& target)
+{
+ PyErrPieces saved_exc;
+ try {
+ TracingGuard tracing_guard;
+ // TODO: We have saved the active exception (if any) that's
+ // about to be raised. In the 'throw' case, we could provide
+ // the exception to the tracefunction, which seems very helpful.
+ tracing_guard.CallTraceFunction(tracefunc, event, origin, target);
+ }
+ catch (const PyErrOccurred&) {
+ // In case of exceptions trace function is removed,
+ // and any existing exception is replaced with the tracing
+ // exception.
+ GET_THREAD_STATE().state().set_tracefunc(Py_None);
+ throw;
+ }
+
+ saved_exc.PyErrRestore();
+ assert(
+ (event == mod_globs->event_throw && PyErr_Occurred())
+ || (event == mod_globs->event_switch && !PyErr_Occurred())
+ );
+}
+
+void
+Greenlet::murder_in_place()
+{
+ if (this->active()) {
+ assert(!this->is_currently_running_in_some_thread());
+ this->deactivate_and_free();
+ }
+}
+
+inline void
+Greenlet::deactivate_and_free()
+{
+ if (!this->active()) {
+ return;
+ }
+ // Throw away any saved stack.
+ this->stack_state = StackState();
+ assert(!this->stack_state.active());
+ // Throw away any Python references.
+ // We're holding a borrowed reference to the last
+ // frame we executed. Since we borrowed it, the
+ // normal traversal, clear, and dealloc functions
+ // ignore it, meaning it leaks. (The thread state
+ // object can't find it to clear it when that's
+ // deallocated either, because by definition if we
+ // got an object on this list, it wasn't
+ // running and the thread state doesn't have
+ // this frame.)
+ // So here, we *do* clear it.
+ this->python_state.tp_clear(true);
+}
+
+bool
+Greenlet::belongs_to_thread(const ThreadState* thread_state) const
+{
+ if (!this->thread_state() // not running anywhere, or thread
+ // exited
+ || !thread_state) { // same, or there is no thread state.
+ return false;
+ }
+ return true;
+}
+
+
+void
+Greenlet::deallocing_greenlet_in_thread(const ThreadState* current_thread_state)
+{
+ /* Cannot raise an exception to kill the greenlet if
+ it is not running in the same thread! */
+ if (this->belongs_to_thread(current_thread_state)) {
+ assert(current_thread_state);
+ // To get here it had to have run before
+ /* Send the greenlet a GreenletExit exception. */
+
+ // We don't care about the return value, only whether an
+ // exception happened.
+ this->throw_GreenletExit_during_dealloc(*current_thread_state);
+ return;
+ }
+
+ // Not the same thread! Temporarily save the greenlet
+ // into its thread's deleteme list, *if* it exists.
+ // If that thread has already exited, and processed its pending
+ // cleanup, we'll never be able to clean everything up: we won't
+ // be able to raise an exception.
+ // That's mostly OK! Since we can't add it to a list, our refcount
+ // won't increase, and we'll go ahead with the DECREFs later.
+ ThreadState *const thread_state = this->thread_state();
+ if (thread_state) {
+ thread_state->delete_when_thread_running(this->self());
+ }
+ else {
+ // The thread is dead, we can't raise an exception.
+ // We need to make it look non-active, though, so that dealloc
+ // finishes killing it.
+ this->deactivate_and_free();
+ }
+ return;
+}
+
+
+int
+Greenlet::tp_traverse(visitproc visit, void* arg)
+{
+
+ int result;
+ if ((result = this->exception_state.tp_traverse(visit, arg)) != 0) {
+ return result;
+ }
+ //XXX: This is ugly. But so is handling everything having to do
+ //with the top frame.
+ bool visit_top_frame = this->was_running_in_dead_thread();
+ // When true, the thread is dead. Our implicit weak reference to the
+ // frame is now all that's left; we consider ourselves to
+ // strongly own it now.
+ if ((result = this->python_state.tp_traverse(visit, arg, visit_top_frame)) != 0) {
+ return result;
+ }
+ return 0;
+}
+
+int
+Greenlet::tp_clear()
+{
+ bool own_top_frame = this->was_running_in_dead_thread();
+ this->exception_state.tp_clear();
+ this->python_state.tp_clear(own_top_frame);
+ return 0;
+}
+
+bool Greenlet::is_currently_running_in_some_thread() const
+{
+ return this->stack_state.active() && !this->python_state.top_frame();
+}
+
+#if GREENLET_PY312
+void GREENLET_NOINLINE(Greenlet::expose_frames)()
+{
+ if (!this->python_state.top_frame()) {
+ return;
+ }
+
+ _PyInterpreterFrame* last_complete_iframe = nullptr;
+ _PyInterpreterFrame* iframe = this->python_state.top_frame()->f_frame;
+ while (iframe) {
+ // We must make a copy before looking at the iframe contents,
+ // since iframe might point to a portion of the greenlet's C stack
+ // that was spilled when switching greenlets.
+ _PyInterpreterFrame iframe_copy;
+ this->stack_state.copy_from_stack(&iframe_copy, iframe, sizeof(*iframe));
+ if (!_PyFrame_IsIncomplete(&iframe_copy)) {
+ // If the iframe were OWNED_BY_CSTACK then it would always be
+ // incomplete. Since it's not incomplete, it's not on the C stack
+ // and we can access it through the original `iframe` pointer
+ // directly. This is important since GetFrameObject might
+ // lazily _create_ the frame object and we don't want the
+ // interpreter to lose track of it.
+ assert(iframe_copy.owner != FRAME_OWNED_BY_CSTACK);
+
+ // We really want to just write:
+ // PyFrameObject* frame = _PyFrame_GetFrameObject(iframe);
+ // but _PyFrame_GetFrameObject calls _PyFrame_MakeAndSetFrameObject
+ // which is not a visible symbol in libpython. The easiest
+ // way to get a public function to call it is using
+ // PyFrame_GetBack, which is defined as follows:
+ // assert(frame != NULL);
+ // assert(!_PyFrame_IsIncomplete(frame->f_frame));
+ // PyFrameObject *back = frame->f_back;
+ // if (back == NULL) {
+ // _PyInterpreterFrame *prev = frame->f_frame->previous;
+ // prev = _PyFrame_GetFirstComplete(prev);
+ // if (prev) {
+ // back = _PyFrame_GetFrameObject(prev);
+ // }
+ // }
+ // return (PyFrameObject*)Py_XNewRef(back);
+ if (!iframe->frame_obj) {
+ PyFrameObject dummy_frame;
+ _PyInterpreterFrame dummy_iframe;
+ dummy_frame.f_back = nullptr;
+ dummy_frame.f_frame = &dummy_iframe;
+ // force the iframe to be considered complete without
+ // needing to check its code object:
+ dummy_iframe.owner = FRAME_OWNED_BY_GENERATOR;
+ dummy_iframe.previous = iframe;
+ assert(!_PyFrame_IsIncomplete(&dummy_iframe));
+ // Drop the returned reference immediately; the iframe
+ // continues to hold a strong reference
+ Py_XDECREF(PyFrame_GetBack(&dummy_frame));
+ assert(iframe->frame_obj);
+ }
+
+ // This is a complete frame, so make the last one of those we saw
+ // point at it, bypassing any incomplete frames (which may have
+ // been on the C stack) in between the two. We're overwriting
+ // last_complete_iframe->previous and need that to be reversible,
+ // so we store the original previous ptr in the frame object
+ // (which we must have created on a previous iteration through
+ // this loop). The frame object has a bunch of storage that is
+ // only used when its iframe is OWNED_BY_FRAME_OBJECT, which only
+ // occurs when the frame object outlives the frame's execution,
+ // which can't have happened yet because the frame is currently
+ // executing as far as the interpreter is concerned. So, we can
+ // reuse it for our own purposes.
+ assert(iframe->owner == FRAME_OWNED_BY_THREAD
+ || iframe->owner == FRAME_OWNED_BY_GENERATOR);
+ if (last_complete_iframe) {
+ assert(last_complete_iframe->frame_obj);
+ memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0],
+ &last_complete_iframe->previous, sizeof(void *));
+ last_complete_iframe->previous = iframe;
+ }
+ last_complete_iframe = iframe;
+ }
+ // Frames that are OWNED_BY_FRAME_OBJECT are linked via the
+ // frame's f_back while all others are linked via the iframe's
+ // previous ptr. Since all the frames we traverse are running
+ // as far as the interpreter is concerned, we don't have to
+ // worry about the OWNED_BY_FRAME_OBJECT case.
+ iframe = iframe_copy.previous;
+ }
+
+ // Give the outermost complete iframe a null previous pointer to
+ // account for any potential incomplete/C-stack iframes between it
+ // and the actual top-of-stack
+ if (last_complete_iframe) {
+ assert(last_complete_iframe->frame_obj);
+ memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0],
+ &last_complete_iframe->previous, sizeof(void *));
+ last_complete_iframe->previous = nullptr;
+ }
+}
+#else
+void Greenlet::expose_frames()
+{
+
+}
+#endif
+
+}; // namespace greenlet