https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115908

--- Comment #13 from GCC Commits <cvs-commit at gcc dot gnu.org> ---
The master branch has been updated by Iain D Sandoe <ia...@gcc.gnu.org>:

https://gcc.gnu.org/g:43e408f675f8e998c94462346f4306cd4716e138

commit r16-1564-g43e408f675f8e998c94462346f4306cd4716e138
Author: Iain Sandoe <i...@sandoe.co.uk>
Date:   Sat May 31 19:59:04 2025 +0100

    c++, coroutines: CWG2563 promise lifetime extension [PR115908].

    This implements the final piece of the revised CWG2563 wording;
    "It exits the scope of promise only if the coroutine completed
     without suspending."

    Considering the coroutine to be made up of two components; a
    'ramp' and a 'body' where the body represents the user's original
    code and the ramp is responsible for setup of that and for
    returning some object to the original caller.

    Coroutine state, and responsibility for its release.

    A coroutine has some state that persists across suspensions.

    The state has two components:
      * State that is specified by the standard and persists for the entire
        life of the coroutine.
      * Local state that is constructed/destructed as scopes in the original
        function body are entered/exited.  The destruction of local state is
        always the responsibility of the body code.

    The persistent state (and the overall storage for the state) must be
    managed in two places:
      * The ramp function (which allocates and builds this - and can, in some
        cases, be responsible for destroying it)
      * The re-written function body which can destroy it when that body
        completes its final suspend - or when the handle.destroy () is called.

    In all cases the ramp holds responsibility for constructing the standard-
    mandated persistent state.

    There are four ways in which the ramp might be re-entered after starting
    the function body:
      A The body could suspend (one might expect that to be the 'normal' case
        for most coroutines).
      B The body might complete either synchronously or via continuations.
      C An exception might be thrown during the setup of the initial await
        expression, before the initial awaiter resumes.
      D An exception might be processed by promise.unhandled_exception () and
        that, in turn, might re-throw it (or throw something else).  In this
        case, the coroutine is considered suspended at the final suspension
        point.

    Once the coroutine has passed initial suspend (i.e. the initial awaiter
    await_resume() has been called) the body is considered to have a use of
    the state.

    Until the ramp return value has been constructed, the ramp is considered
    to have a use of the state.

    To manage these interacting conditions we allocate a reference counter
    for the frame state.  This is initialised to 1 by the ramp as part of its
    startup (note that failures/exceptions in the startup code are handled
    locally to the ramp).

    When the body returns (either normally, or by exception) the ramp releases
    its use.

    Once the rewritten coroutine body is started, the body is considered to
    have a use of the frame.  This use (potentially) needs to be released if
    an exception is thrown from the body.  We implement this using an eh-only
    cleanup around the initial await.  If we have the case D above, then we
    do not release the body use.

    In case:

      A, typically the ramp would be re-entered with the body holding a use,
      and therefore the ramp should not destroy the state.

      B, both the body and ramp will have released their uses, and the ramp
      should destroy the state.

      C, we must arrange for the body to release its use, because we require
      the ramp to cleanup in this circumstance.

      D is an outlier, since the responsibility for destruction of the state
      now rests with the user's code (via a handle.destroy() call).

      NOTE: In the case that the body has never suspended before such an
      exception occurs, the only reasonable way for the user code to obtain the
      necessary handle is if unhandled_exception() throws the handle or some
      object that contains the handle.  That is outside of the designs here -
      if the user code might need this corner-case, then such provision will
      have to be made.

    In the ramp, we implement destruction for the persistent frame state by
    means of cleanups.  These are run conditionally when the reference count
    is 0 signalling that both the body and the ramp have completed.

    In the body, once we pass the final suspend, then we test the use and
    delete the state if the use is 0.

            PR c++/115908
            PR c++/118074
            PR c++/95615

    gcc/cp/ChangeLog:

            * coroutines.cc (coro_frame_refcount_id): New.
            (coro_init_identifiers): Initialise coro_frame_refcount_id.
            (build_actor_fn): Set up initial_await_resume_called.  Handle
            decrementing of the frame reference count.  Return directly to
            the caller if that is non-zero.
            (cp_coroutine_transform::wrap_original_function_body): Use a
            conditional eh-only cleanup around the initial await expression
            to release the body use on exception before initial await
            resume.
            (cp_coroutine_transform::build_ramp_function): Wrap the called
            body in a cleanup that releases a use of the frame when we
            return to the ramp.  Implement frame, promise and argument copy
            destruction via conditional cleanups when the frame use count
            is zero.

    gcc/testsuite/ChangeLog:

            * g++.dg/coroutines/pr115908.C: Move to...
            * g++.dg/coroutines/torture/pr115908.C: ...here.
            * g++.dg/coroutines/torture/pr95615-02.C: Move to...
            * g++.dg/coroutines/torture/pr95615-01-promise-ctor-throws.C:
...here.
            * g++.dg/coroutines/torture/pr95615-03.C: Move to...
            * g++.dg/coroutines/torture/pr95615-02-get-return-object-throws.C:
...here.
            * g++.dg/coroutines/torture/pr95615-01.C: Move to...
            * g++.dg/coroutines/torture/pr95615-03-initial-suspend-throws.C:
...here.
            * g++.dg/coroutines/torture/pr95615-04.C: Move to...
            *
g++.dg/coroutines/torture/pr95615-04-initial-await-ready-throws.C: ...here.
            * g++.dg/coroutines/torture/pr95615-05.C: Move to...
            *
g++.dg/coroutines/torture/pr95615-05-initial-await-suspend-throws.C: ...here.
            * g++.dg/coroutines/torture/pr95615.inc: Add more cases and ensure
that the
            code completes properly when no exceptions are thrown.
            * g++.dg/coroutines/torture/pr95615-00-nothing-throws.C: New test.
            *
g++.dg/coroutines/torture/pr95615-06-initial-await-resume-throws.C: New test.
            * g++.dg/coroutines/torture/pr95615-07-body-throws.C: New test.
            *
g++.dg/coroutines/torture/pr95615-08-initial-suspend-throws-uhe-throws.C: New
test.
            * g++.dg/coroutines/torture/pr95615-09-body-throws-uhe-throws.C:
New test.

    Signed-off-by: Iain Sandoe <i...@sandoe.co.uk>

Reply via email to