llvm/clang/test/CodeGenCoroutines/pr56329.cpp

// Test for PR56919. Tests the we won't contain the resumption of final suspend point.
//
// RUN: %clang_cc1 -triple %itanium_abi_triple -std=c++20 %s -O3 -emit-llvm -o - | FileCheck %s
// This test is expected to fail on PowerPC.
// XFAIL: target=powerpc{{.*}}

#include "Inputs/coroutine.h"

void _exit(int status) __attribute__ ((__noreturn__));

class Promise;

// An object that can be co_awaited, but we always resume immediately from
// await_suspend.
struct ResumeFromAwaitSuspend{};

struct Task {
  using promise_type = Promise;
  Promise& promise;
};

struct Promise {
  static std::coroutine_handle<Promise> GetHandle(Promise& promise) {
    return std::coroutine_handle<Promise>::from_promise(promise);
  }

  void unhandled_exception() {}
  Task get_return_object() { return Task{*this}; }
  void return_void() {}

  // Always suspend before starting the coroutine body. We actually run the body
  // when we are co_awaited.
  std::suspend_always initial_suspend() { return {}; }

  // We support awaiting tasks. We do so by configuring them to resume us when
  // they are finished, and then resuming them from their initial suspend.
  auto await_transform(Task&& task) {
    struct Awaiter {
      bool await_ready() { return false; }

      std::coroutine_handle<> await_suspend(
          const std::coroutine_handle<> handle) {
        // Tell the child to resume the parent once it finishes.
        child.resume_at_final_suspend = GetHandle(parent);

        // Run the child.
        return GetHandle(child);
      }

      void await_resume() {
        // The child is now at its final suspend point, and can be destroyed.
        return GetHandle(child).destroy();
      }

      Promise& parent;
      Promise& child;
    };

    return Awaiter{
        .parent = *this,
        .child = task.promise,
    };
  }

  // Make evaluation of `co_await ResumeFromAwaitSuspend{}` go through the
  // await_suspend path, but cause it to resume immediately by returning our own
  // handle to resume.
  auto await_transform(ResumeFromAwaitSuspend) {
    struct Awaiter {
      bool await_ready() { return false; }

      std::coroutine_handle<> await_suspend(const std::coroutine_handle<> h) {
        return h;
      }

      void await_resume() {}
    };

    return Awaiter{};
  }

  // Always suspend at the final suspend point, transferring control back to our
  // caller. We expect never to be resumed from the final suspend.
  auto final_suspend() noexcept {
    struct FinalSuspendAwaitable final {
      bool await_ready() noexcept { return false; }

      std::coroutine_handle<> await_suspend(std::coroutine_handle<>) noexcept {
        return promise.resume_at_final_suspend;
      }

      void await_resume() noexcept {
        _exit(1);
      }

      Promise& promise;
    };

    return FinalSuspendAwaitable{.promise = *this};
  }

  // The handle we will resume once we hit final suspend.
  std::coroutine_handle<> resume_at_final_suspend;
};

Task Inner();

Task Outer() {
  co_await ResumeFromAwaitSuspend();
  co_await Inner();
}

// CHECK: define{{.*}}@_Z5Outerv.resume(
// CHECK-NOT: }
// CHECK-NOT: _exit
// CHECK: musttail call
// CHECK: musttail call
// CHECK: musttail call
// CHECK-NEXT: ret void
// CHECK-EMPTY:
// CHECK-NEXT: unreachable:
// CHECK-NEXT: unreachable
// CHECK-NEXT: }