Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[clang][coroutines] Run-time crash with optimization when using coroutine with co_await #105595

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dgg5503 opened this issue Aug 21, 2024 · 7 comments · Fixed by #139243
Closed
Labels
coroutines C++20 coroutines

Comments

@dgg5503
Copy link
Contributor

dgg5503 commented Aug 21, 2024

Summary

Starting with Clang 17 at commit 54225c4, attempting to execute the provided reduced programs compiled with clang using -O2 results in a crash at runtime.

Specifically, the crash occurs in the function void bx::await_suspend when the result of q, a null pointer, is dereferenced to call x().

I have confirmed that both reproducers run without triggering sanitizers in my environment (address, memory, undefined, etc.).

Reproducers

reduced-Version-A.cpp -- https://godbolt.org/z/bxEf8Tfc6

#include <coroutine>
#include <memory>

struct br;
struct bs {
  bs(std::coroutine_handle<br>);
  int x() { return bt; }
  void bu() { bt = 0; }
  void t() { bv.resume(); }
  std::coroutine_handle<br> bv;
  int bt;
};
struct bw {
  using promise_type = br;
  bw(std::coroutine_handle<promise_type> h) : v(make_shared<bs>(h)) {}
  void t() { v->t(); }
  std::shared_ptr<bs> r() { return v; }
  std::shared_ptr<bs> v;
};
struct bx {
  int await_ready() { return 0; }
  void await_resume() {}
  void await_suspend(std::coroutine_handle<br>);
  bw by;
};
struct br {
  auto initial_suspend() { return std::suspend_always(); }
  auto final_suspend() noexcept { return std::suspend_always(); }
  auto get_return_object() {
    return std::coroutine_handle<br>::from_promise(*this);
  }
  void unhandled_exception() {}
  auto await_transform(bw h) { return bx(h); }
  void return_void() {}
  void u(bs *h) { v = h; }
  bs *r() { return v; }
  bs *v;
};
void bx::await_suspend(std::coroutine_handle<br> h) {
  auto bi = h.promise();
  auto q = bi.r();
  auto s = by.r();
  if (q->x())
    s->bu();
  by.t();
}
bs::bs(std::coroutine_handle<br> h) {
  bt = 0;
  bv = h;
  auto &bz = h.promise();
  bz.u(this);
}
bw ca() {
  fprintf(stderr, "ca (co_return) called\n");
  co_return;
}
bw cb() {
  fprintf(stderr, "cb (co_await) called\n");
  co_await ca();
}
bw cc = cb();
int main() {
  bw cd(cc);
  cd.t();
}

Here is the same reproducer reduced including headers from glibc 2.35 / glibcxx 3.4.30
reduced-Version-B.cpp -- https://godbolt.org/z/fsaKof3EE

namespace std {
template <int a> struct b {
  struct c {
    int d[a];
  };
};
inline namespace {
template <typename e> struct coroutine_traits : e {};
template <typename = void> struct coroutine_handle;
template <> struct coroutine_handle<> {};
template <typename f> struct coroutine_handle {
  static coroutine_handle g(f &h) {
    coroutine_handle i;
    i.k = __builtin_coro_promise(&h, 0, 1);
    return i;
  }
  static coroutine_handle from_address(void *h) {
    coroutine_handle i;
    i.k = h;
    return i;
  }
  coroutine_handle<> j;
  operator coroutine_handle<>() { return j; }
  void l() { __builtin_coro_resume(k); }
  f &m() {
    void *aa = __builtin_coro_promise(k, 0, 0);
    return *static_cast<f *>(aa);
  }
  void *k;
};
struct ab {
  int await_ready() noexcept { return 0; }
  void await_suspend(coroutine_handle<>) noexcept {}
  void await_resume() noexcept {}
};
} // namespace
template <typename, typename> struct n;
template <template <typename...> class w, typename y, typename ac,
          typename... ad>
struct n<w<ac, ad...>, y> {
  using c = w<y>;
};
} // namespace std
void *operator new(unsigned long, void *);
template <typename ac, typename... ae> void af(ac *h, ae... o) {
  new (h) ac(o...);
}
template <typename ac> struct ag {
  ac *ah(int) { return static_cast<ac *>(operator new(sizeof(ac))); }
};
namespace std {
template <typename ai, typename y> using aj = n<ai, y>::c;
template <typename> struct p;
template <typename ac> struct p<ag<ac>> {
  using ak = ag<ac>;
  using al = ac;
  using am = ac *;
  using an = int;
  static am ah(ak h, an) { return h.ah(0); }
  template <typename y, typename... ae> static void ao(ak, y o, ae... ap) {
    af(o, ap...);
  }
};
} // namespace std
namespace std {
template <typename ai> struct aq {
  using am = p<ai>::am;
  using al = p<ai>::al;
  aq(ai, am o) : ar(o) {}
  al *as() { return ar; }
  am ar;
};
template <typename ai> aq<ai> at(ai h) { return {h, p<ai>::ah(h, 0)}; }
} // namespace std
template <typename ac> struct au {
  std::b<sizeof(ac)>::c av;
  ac *ar() {
    void *j = &av;
    return static_cast<ac *>(j);
  }
};
namespace std {
template <typename> struct aw;
template <typename ac> struct ax {
  ax(ac) {}
};
template <typename ai> struct ay {
  ai az;
};
template <typename ac, typename ai> struct ba {
  struct bb : ax<ai> {
    au<ac> av;
  };
  using bc = aj<ai, ba>;
  template <typename... ae> ba(ai h, ae... o) : bd(h) {
    p<ai>::ao(h, ar(), o...);
  }
  ac *ar() { return bd.av.ar(); }
  bb bd;
};
struct be {
  template <typename ac, typename ai, typename... ae>
  be(ac *&h, ay<ai> o, ae... ap) {
    typedef ba<ac, ai> bf;
    typename bf::bc bg;
    auto bh = at(bg);
    bf *bj = bh.as();
    auto bk = new (bj) bf(o.az, ap...);
    h = bk->ar();
  }
};
template <typename ac> struct z {
  using bl = ac;
  bl *operator->() {
    bl *bm = static_cast<aw<ac> *>(this)->as();
    return bm;
  }
};
template <typename ac> struct aw : z<ac> {
  using bl = ac;
  bl *as() { return ar; }
  template <typename ai, typename... ae> aw(ai h, ae... o) : bn(ar, h, o...) {}
  bl *ar;
  be bn;
};
template <typename ac> struct bo : aw<ac> {
  template <typename ai, typename... ae> bo(ai h, ae... o) : aw<ac>(h, o...) {}
};
template <typename ac, typename ai, typename... ae> bo<ac> bp(ai h, ae... o) {
  return bo<ac>(ay<ai>{h}, o...);
}
template <typename ac, typename... ae> bo<ac> bq(ae... h) {
  return bp<ac>(ag<int>(), h...);
}
} // namespace std
struct br;
struct bs {
  bs(std::coroutine_handle<br>);
  int x() { return bt; }
  void bu() { bt = 0; }
  void t() { bv.l(); }
  std::coroutine_handle<br> bv;
  int bt;
};
struct bw {
  using promise_type = br;
  bw(std::coroutine_handle<promise_type> h) : v(bq<bs>(h)) {}
  void t() { v->t(); }
  std::bo<bs> r() { return v; }
  std::bo<bs> v;
};
struct bx {
  int await_ready() { return 0; }
  void await_resume() {}
  void await_suspend(std::coroutine_handle<br>);
  bw by;
};
struct br {
  auto initial_suspend() { return std::ab(); }
  auto final_suspend() noexcept { return std::ab(); }
  auto get_return_object() { return std::coroutine_handle<br>::g(*this); }
  void unhandled_exception() {}
  auto await_transform(bw h) { return bx(h); }
  void return_void() {}
  void u(bs *h) { v = h; }
  bs *r() { return v; }
  bs *v;
};

// When reduced including headers, the following is required under
// clang -O2 @ 54225c457a336b1609c6d064b2b606a9238a28b9, otherwise this
// entire function is optimized out which masks the problem since the crash
// occurs at `q->x()` (q is nullptr).
#pragma clang optimize off
void bx::await_suspend(std::coroutine_handle<br> h) {
  auto bi = h.m();
  auto q = bi.r();
  auto s = by.r();
  if (q->x())
    s->bu();
  by.t();
}
#pragma clang optimize on

bs::bs(std::coroutine_handle<br> h) {
  bt = 0;
  bv = h;
  auto &bz = h.m();
  bz.u(this);
}
bw ca() { co_return; }
bw cb() { co_await ca(); }
bw cc = cb();
int main() {
  bw cd(cc);
  cd.t();
}

Reproduction Steps

Latest Clang -- Crashes

$ clang++ --version
clang version 20.0.0git (https://github.com/llvm/llvm-project.git 381a803da253b75c8b7b10bb732e9e90925185e8)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: <redacted>
$ clang++ -O0 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O2 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
Segmentation fault (core dumped)
$ clang++ -O0 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
$ clang++ -O2 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
Segmentation fault (core dumped)

Clang At Bisected Commit -- Crashes

$ clang++ --version
clang version 17.0.0 (https://github.com/llvm/llvm-project.git 54225c457a336b1609c6d064b2b606a9238a28b9)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: <redacted>
$ clang++ -O0 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O2 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
Segmentation fault (core dumped)
$ clang++ -O0 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
$ clang++ -O2 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
Segmentation fault (core dumped)

Clang Before Bisected Commit -- Does not crash

$ clang++ --version
clang version 17.0.0 (https://github.com/llvm/llvm-project.git 32be3405f57f1e4d0ec0da943434113450583e89)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: <redacted>
$ clang++ -O0 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O2 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O0 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
$ clang++ -O2 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
@github-actions github-actions bot added the clang Clang issues not falling into any other category label Aug 21, 2024
@EugeneZelenko EugeneZelenko added coroutines C++20 coroutines and removed clang Clang issues not falling into any other category labels Aug 21, 2024
@llvmbot
Copy link
Member

llvmbot commented Aug 21, 2024

@llvm/issue-subscribers-coroutines

Author: Douglas (dgg5503)

## Summary Starting with Clang 17 at commit 54225c4, attempting to execute the provided reduced programs compiled with `clang` using `-O2` results in a crash at runtime.

Specifically, the crash occurs in the function void bx::await_suspend when the result of q, a null pointer, is dereferenced to call x().

I have confirmed that both reproducers run without triggering sanitizers in my environment (address, memory, undefined, etc.).

Reproducers

reduced-Version-A.cpp -- https://godbolt.org/z/bxEf8Tfc6

#include &lt;coroutine&gt;
#include &lt;memory&gt;

struct br;
struct bs {
  bs(std::coroutine_handle&lt;br&gt;);
  int x() { return bt; }
  void bu() { bt = 0; }
  void t() { bv.resume(); }
  std::coroutine_handle&lt;br&gt; bv;
  int bt;
};
struct bw {
  using promise_type = br;
  bw(std::coroutine_handle&lt;promise_type&gt; h) : v(make_shared&lt;bs&gt;(h)) {}
  void t() { v-&gt;t(); }
  std::shared_ptr&lt;bs&gt; r() { return v; }
  std::shared_ptr&lt;bs&gt; v;
};
struct bx {
  int await_ready() { return 0; }
  void await_resume() {}
  void await_suspend(std::coroutine_handle&lt;br&gt;);
  bw by;
};
struct br {
  auto initial_suspend() { return std::suspend_always(); }
  auto final_suspend() noexcept { return std::suspend_always(); }
  auto get_return_object() {
    return std::coroutine_handle&lt;br&gt;::from_promise(*this);
  }
  void unhandled_exception() {}
  auto await_transform(bw h) { return bx(h); }
  void return_void() {}
  void u(bs *h) { v = h; }
  bs *r() { return v; }
  bs *v;
};
void bx::await_suspend(std::coroutine_handle&lt;br&gt; h) {
  auto bi = h.promise();
  auto q = bi.r();
  auto s = by.r();
  if (q-&gt;x())
    s-&gt;bu();
  by.t();
}
bs::bs(std::coroutine_handle&lt;br&gt; h) {
  bt = 0;
  bv = h;
  auto &amp;bz = h.promise();
  bz.u(this);
}
bw ca() {
  fprintf(stderr, "ca (co_return) called\n");
  co_return;
}
bw cb() {
  fprintf(stderr, "cb (co_await) called\n");
  co_await ca();
}
bw cc = cb();
int main() {
  bw cd(cc);
  cd.t();
}

Here is the same reproducer reduced including headers from glibc 2.35 / glibcxx 3.4.30
reduced-Version-B.cpp -- https://godbolt.org/z/fsaKof3EE

namespace std {
template &lt;int a&gt; struct b {
  struct c {
    int d[a];
  };
};
inline namespace {
template &lt;typename e&gt; struct coroutine_traits : e {};
template &lt;typename = void&gt; struct coroutine_handle;
template &lt;&gt; struct coroutine_handle&lt;&gt; {};
template &lt;typename f&gt; struct coroutine_handle {
  static coroutine_handle g(f &amp;h) {
    coroutine_handle i;
    i.k = __builtin_coro_promise(&amp;h, 0, 1);
    return i;
  }
  static coroutine_handle from_address(void *h) {
    coroutine_handle i;
    i.k = h;
    return i;
  }
  coroutine_handle&lt;&gt; j;
  operator coroutine_handle&lt;&gt;() { return j; }
  void l() { __builtin_coro_resume(k); }
  f &amp;m() {
    void *aa = __builtin_coro_promise(k, 0, 0);
    return *static_cast&lt;f *&gt;(aa);
  }
  void *k;
};
struct ab {
  int await_ready() noexcept { return 0; }
  void await_suspend(coroutine_handle&lt;&gt;) noexcept {}
  void await_resume() noexcept {}
};
} // namespace
template &lt;typename, typename&gt; struct n;
template &lt;template &lt;typename...&gt; class w, typename y, typename ac,
          typename... ad&gt;
struct n&lt;w&lt;ac, ad...&gt;, y&gt; {
  using c = w&lt;y&gt;;
};
} // namespace std
void *operator new(unsigned long, void *);
template &lt;typename ac, typename... ae&gt; void af(ac *h, ae... o) {
  new (h) ac(o...);
}
template &lt;typename ac&gt; struct ag {
  ac *ah(int) { return static_cast&lt;ac *&gt;(operator new(sizeof(ac))); }
};
namespace std {
template &lt;typename ai, typename y&gt; using aj = n&lt;ai, y&gt;::c;
template &lt;typename&gt; struct p;
template &lt;typename ac&gt; struct p&lt;ag&lt;ac&gt;&gt; {
  using ak = ag&lt;ac&gt;;
  using al = ac;
  using am = ac *;
  using an = int;
  static am ah(ak h, an) { return h.ah(0); }
  template &lt;typename y, typename... ae&gt; static void ao(ak, y o, ae... ap) {
    af(o, ap...);
  }
};
} // namespace std
namespace std {
template &lt;typename ai&gt; struct aq {
  using am = p&lt;ai&gt;::am;
  using al = p&lt;ai&gt;::al;
  aq(ai, am o) : ar(o) {}
  al *as() { return ar; }
  am ar;
};
template &lt;typename ai&gt; aq&lt;ai&gt; at(ai h) { return {h, p&lt;ai&gt;::ah(h, 0)}; }
} // namespace std
template &lt;typename ac&gt; struct au {
  std::b&lt;sizeof(ac)&gt;::c av;
  ac *ar() {
    void *j = &amp;av;
    return static_cast&lt;ac *&gt;(j);
  }
};
namespace std {
template &lt;typename&gt; struct aw;
template &lt;typename ac&gt; struct ax {
  ax(ac) {}
};
template &lt;typename ai&gt; struct ay {
  ai az;
};
template &lt;typename ac, typename ai&gt; struct ba {
  struct bb : ax&lt;ai&gt; {
    au&lt;ac&gt; av;
  };
  using bc = aj&lt;ai, ba&gt;;
  template &lt;typename... ae&gt; ba(ai h, ae... o) : bd(h) {
    p&lt;ai&gt;::ao(h, ar(), o...);
  }
  ac *ar() { return bd.av.ar(); }
  bb bd;
};
struct be {
  template &lt;typename ac, typename ai, typename... ae&gt;
  be(ac *&amp;h, ay&lt;ai&gt; o, ae... ap) {
    typedef ba&lt;ac, ai&gt; bf;
    typename bf::bc bg;
    auto bh = at(bg);
    bf *bj = bh.as();
    auto bk = new (bj) bf(o.az, ap...);
    h = bk-&gt;ar();
  }
};
template &lt;typename ac&gt; struct z {
  using bl = ac;
  bl *operator-&gt;() {
    bl *bm = static_cast&lt;aw&lt;ac&gt; *&gt;(this)-&gt;as();
    return bm;
  }
};
template &lt;typename ac&gt; struct aw : z&lt;ac&gt; {
  using bl = ac;
  bl *as() { return ar; }
  template &lt;typename ai, typename... ae&gt; aw(ai h, ae... o) : bn(ar, h, o...) {}
  bl *ar;
  be bn;
};
template &lt;typename ac&gt; struct bo : aw&lt;ac&gt; {
  template &lt;typename ai, typename... ae&gt; bo(ai h, ae... o) : aw&lt;ac&gt;(h, o...) {}
};
template &lt;typename ac, typename ai, typename... ae&gt; bo&lt;ac&gt; bp(ai h, ae... o) {
  return bo&lt;ac&gt;(ay&lt;ai&gt;{h}, o...);
}
template &lt;typename ac, typename... ae&gt; bo&lt;ac&gt; bq(ae... h) {
  return bp&lt;ac&gt;(ag&lt;int&gt;(), h...);
}
} // namespace std
struct br;
struct bs {
  bs(std::coroutine_handle&lt;br&gt;);
  int x() { return bt; }
  void bu() { bt = 0; }
  void t() { bv.l(); }
  std::coroutine_handle&lt;br&gt; bv;
  int bt;
};
struct bw {
  using promise_type = br;
  bw(std::coroutine_handle&lt;promise_type&gt; h) : v(bq&lt;bs&gt;(h)) {}
  void t() { v-&gt;t(); }
  std::bo&lt;bs&gt; r() { return v; }
  std::bo&lt;bs&gt; v;
};
struct bx {
  int await_ready() { return 0; }
  void await_resume() {}
  void await_suspend(std::coroutine_handle&lt;br&gt;);
  bw by;
};
struct br {
  auto initial_suspend() { return std::ab(); }
  auto final_suspend() noexcept { return std::ab(); }
  auto get_return_object() { return std::coroutine_handle&lt;br&gt;::g(*this); }
  void unhandled_exception() {}
  auto await_transform(bw h) { return bx(h); }
  void return_void() {}
  void u(bs *h) { v = h; }
  bs *r() { return v; }
  bs *v;
};

// When reduced including headers, the following is required under
// clang -O2 @ 54225c457a336b1609c6d064b2b606a9238a28b9, otherwise this
// entire function is optimized out which masks the problem since the crash
// occurs at `q-&gt;x()` (q is nullptr).
#pragma clang optimize off
void bx::await_suspend(std::coroutine_handle&lt;br&gt; h) {
  auto bi = h.m();
  auto q = bi.r();
  auto s = by.r();
  if (q-&gt;x())
    s-&gt;bu();
  by.t();
}
#pragma clang optimize on

bs::bs(std::coroutine_handle&lt;br&gt; h) {
  bt = 0;
  bv = h;
  auto &amp;bz = h.m();
  bz.u(this);
}
bw ca() { co_return; }
bw cb() { co_await ca(); }
bw cc = cb();
int main() {
  bw cd(cc);
  cd.t();
}

Reproduction Steps

Latest Clang -- Crashes

$ clang++ --version
clang version 20.0.0git (https://github.com/llvm/llvm-project.git 381a803da253b75c8b7b10bb732e9e90925185e8)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: &lt;redacted&gt;
$ clang++ -O0 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O2 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
Segmentation fault (core dumped)
$ clang++ -O0 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
$ clang++ -O2 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
Segmentation fault (core dumped)

Clang At Bisected Commit -- Crashes

$ clang++ --version
clang version 17.0.0 (https://github.com/llvm/llvm-project.git 54225c457a336b1609c6d064b2b606a9238a28b9)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: &lt;redacted&gt;
$ clang++ -O0 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O2 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
Segmentation fault (core dumped)
$ clang++ -O0 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
$ clang++ -O2 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
Segmentation fault (core dumped)

Clang Before Bisected Commit -- Does not crash

$ clang++ --version
clang version 17.0.0 (https://github.com/llvm/llvm-project.git 32be3405f57f1e4d0ec0da943434113450583e89)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: &lt;redacted&gt;
$ clang++ -O0 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O2 -std=c++20 reduced-Version-A.cpp -o reduced-Version-A.out
$ ./reduced-Version-A.out
cb (co_await) called
ca (co_return) called
$ clang++ -O0 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out
$ clang++ -O2 -std=c++20 reduced-Version-B.cpp -o reduced-Version-B.out
$ ./reduced-Version-B.out

dgg5503 referenced this issue Aug 22, 2024
Fix #56532

Effectively, this reverts behavior introduced in https://reviews.llvm.org/D117087,
which did two things:

1. Change delayed to early conversion of return object.
2. Introduced RVO possibilities because of early conversion.

This patches fixes (1) and removes (2). I already worked on a follow up for (2)
in a separated patch. I believe it's important to split these two because if the RVO
causes any problems we can explore reverting (2) while maintaining (1).

Notes on some testcase changes:
- `pr59221.cpp` changed to `-O1` so we can check that the front-end honors
  the value checked for. Sounds like `-O3` without RVO is more likely
  to work with LLVM optimizations...
- Comment out delete members `coroutine-no-move-ctor.cpp` since behavior
  now requires copies again.

Differential Revision: https://reviews.llvm.org/D145639
@dgg5503
Copy link
Contributor Author

dgg5503 commented Oct 4, 2024

Hi,

Just a small update on this issue. I noticed that if I replace auto get_return_object() with bw get_return_object(), the return type of the coroutine functions, the runtime crash no longer occurs for both reproducers.

Inspection of an AST dump shows that when providing the return type explicitly, the implicit VarDecl for __coro_gro is not created as per these lines since the return type ends up being matched.

Is it possible some aspect of the AST is misconfigured when using 'auto' as I provide in my reproducers that's leading to a runtime crash when optimizations are enabled? Or is this simply a misunderstanding of the coroutine specification on my part (i.e. explicit return type for get_return_object is required under certain conditions)?

@rnk rnk assigned rnk, RoboTux and ilya-biryukov and unassigned rnk and RoboTux Oct 8, 2024
@rnk
Copy link
Collaborator

rnk commented Oct 8, 2024

cc @bricknerb @usx95 this seems like a coro frontend issue that we might wish to investigate

@bricknerb bricknerb assigned bricknerb and unassigned ilya-biryukov Oct 18, 2024
@bricknerb
Copy link
Contributor

@dgg5503, can you perhaps clarify why do you think the logic in the code should is valid and what do you think is wrong with the way it runs?
Spending some time debugging this, I've noticed that if you make the u() method not inline, it doesn't crash, but address sanitizer does show it leaks. Same for replacing auto get_return_object() with bw get_return_object().
However, looking at the code, it's not easy to be convinced it is valid.

@bricknerb
Copy link
Contributor

bricknerb commented Oct 23, 2024

I've tried to debug this for a while and simplified the logic to https://godbolt.org/z/9jq75vq6c.
It's enough to make the Coroutine constructor noinline, or disable optimizations, to make this work as intended.
Given that, it doesn't seem like a frontend issue, so I'm unassigning myself.

Some more context that might be relevant: #56532.

@bricknerb bricknerb removed their assignment Oct 23, 2024
@kadircet
Copy link
Member

so I was playing around a little bit just OOC.

https://godbolt.org/z/9jYdTzsY4 demonstrates another reproducer, one important highlight is despite setting two variables in the same function, only the non-atomic read fails.

So I guess we're missing some write/read dependencies and some backend pass is just eliminating this dead write. Unfortunately I don't know much about the backend to be more useful :(

@rnk
Copy link
Collaborator

rnk commented Oct 29, 2024

Thanks, I think I thought this was a frontend issue because it seemed to depend on auto deduced return types, but I guess it doesn't.

You can clearly see in the godbolt example that DSE deletes the non-atomic store before returning from the pre-split coroutine representation.

I think the following IR snippets explain the issue:

define dso_local nonnull ptr @cor()() #2 personality ptr @__gxx_personality_v0 {
entry:
  %__promise = alloca %"struct.Co::promise_type", align 8
...
coro.ret:                                         ; preds = %final.suspend, %coro.free, %cleanup33, %invoke.cont14, %init.suspend
  %18 = getelementptr inbounds i8, ptr %__promise, i64 -16
  %19 = call i1 @llvm.coro.end(ptr null, i1 false, token none)
  store atomic i64 555555, ptr %__promise seq_cst, align 8
  %non_atomic_data.i = getelementptr inbounds i8, ptr %__promise, i64 8  // DSE DELETES
  store ptr inttoptr (i64 5555555 to ptr), ptr %non_atomic_data.i, align 8    // DSE DELETES
  ret ptr %18
...

It looks like C++ coroutines model the promise type as an alloca (local variable) called __promise. DSE (reasonably) concludes that all allocas are dead at the end of the function, so deleting this store is legal according to its model.

The correct fix is... not to model things that outlive the function as allocas. cc @zmodem @ChuanqiXu9 @alanzhao1 @alinas

jarl-haggerty pushed a commit to cajigaslab/Thalamus that referenced this issue Feb 8, 2025
…/llvm-project#105595.  Add clang to prepare.py.  Default to clang on windows, fix missing debug info in clang release build
@NewSigma NewSigma marked this as a duplicate of #123347 Apr 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
coroutines C++20 coroutines
Projects
None yet
8 participants