[llvm-bugs] [Bug 45130] New: miscompile of coroutines using symmetric control transfer

via llvm-bugs llvm-bugs at lists.llvm.org
Thu Mar 5 16:51:06 PST 2020


https://bugs.llvm.org/show_bug.cgi?id=45130

            Bug ID: 45130
           Summary: miscompile of coroutines using symmetric control
                    transfer
           Product: libraries
           Version: trunk
          Hardware: PC
                OS: Linux
            Status: NEW
          Severity: normal
          Priority: P
         Component: Common Code Generator Code
          Assignee: unassignedbugs at nondot.org
          Reporter: richard-llvm at metafoo.co.uk
                CC: gornishanov at gmail.com, llvm-bugs at lists.llvm.org

Testcase:


#include <iostream>

#include <experimental/coroutine>
namespace std { using namespace experimental; }

struct coro {
  struct promise_type;
  using coro_handle = std::coroutine_handle<promise_type>;

  struct promise_type {
    coro_handle handle;
    auto initial_suspend() {
      struct awaitable {
        promise_type &promise;
        bool await_ready() { return false; }
        void await_suspend(coro_handle h) { promise.handle = h; }
        void await_resume() {}
      };
      return awaitable{*this};
    }
    auto final_suspend() {
      struct awaitable {
        bool await_ready() { return false; }
        void await_suspend(coro_handle h) { h.destroy(); }
        void await_resume() { __builtin_unreachable(); }
      };
      return awaitable{};
    }
    coro get_return_object() { return {this}; }
    void unhandled_exception() { throw; }
    void return_void() {}
  };
  promise_type *promise;

  bool await_ready() { return false; }
  coro_handle await_suspend(coro_handle) { return promise->handle; }
  coro await_resume() { return *this; }

  struct self_t {
    coro_handle handle;
    bool await_ready() { return false; }
    bool await_suspend(coro_handle h) { handle = h; return false; }
    coro await_resume() { return {&handle.promise()}; }
  };
  static inline self_t self;

  void run() { promise->handle(); }
  void destroy() { promise->handle.destroy(); }
};

coro g(coro c) {
  std::cout << "g1" << std::endl;
  co_await c;
  std::cout << "g2" << std::endl;
  co_await c;
  std::cout << "g3 returning" << std::endl;
  c.destroy();
}

coro f() {
  std::cout << "f1" << std::endl;
  coro c = g(co_await coro::self);
  std::cout << "f2" << std::endl;
  co_await c;
  std::cout << "f3" << std::endl;
  co_await c;
  std::cout << "f4" << std::endl;
  co_await c;
  std::cout << "f5 returning" << std::endl;
  c.destroy();
}

int main() {
  f().run();
}


At -O0, this prints out:

f1
f2
g1
f3
g2
f4
g3 returning

... which I believe to be correct. At -O2, this instead prints:

f1
f2
g1
f3
g2
f4
g2
f1
f2
g1
f3
g2
f4
g2
f1
f2
g1
f3
g2
f4
g2
f1
f2
g1
f3
g2
f4
g2
[... endless cycle ...]

Something seems to be messing up the state number handling in both coroutines.
For example, here's the resume function for g:


define internal fastcc void @_Z1g4coro.resume(%_Z1g4coro.Frame* noalias nonnull
%0) #2 {
  %2 = getelementptr inbounds %_Z1g4coro.Frame, %_Z1g4coro.Frame* %0, i64 0,
i32 3
  %3 = load i2, i2* %2, align 1
  %4 = icmp eq i2 %3, 0
  br i1 %4, label %14, label %5

5:                                                ; preds = %1
  tail call void @_Z2g2v()
  store i2 -2, i2* %2, align 1
  %6 = getelementptr inbounds %_Z1g4coro.Frame, %_Z1g4coro.Frame* %0, i64 0,
i32 4
  %7 = load %"struct.coro::promise_type"*, %"struct.coro::promise_type"** %6,
align 8
  %8 = getelementptr inbounds %"struct.coro::promise_type",
%"struct.coro::promise_type"* %7, i64 0, i32 0, i32 0, i32 0
  %9 = load i8*, i8** %8, align 8
  %10 = bitcast i8* %9 to { i8*, i8* }*
  %11 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %10, i32 0, i32 0
  %12 = load i8*, i8** %11
  %13 = bitcast i8* %12 to void (i8*)*
  musttail call fastcc void %13(i8* %9) #4
  ret void

14:                                               ; preds = %1
  tail call void @_Z2g1v()
  store i2 1, i2* %2, align 1
  %15 = getelementptr inbounds %_Z1g4coro.Frame, %_Z1g4coro.Frame* %0, i64 0,
i32 4
  %16 = load %"struct.coro::promise_type"*, %"struct.coro::promise_type"** %15,
align 8
  %17 = getelementptr inbounds %"struct.coro::promise_type",
%"struct.coro::promise_type"* %16, i64 0, i32 0, i32 0, i32 0
  %18 = load i8*, i8** %17, align 8
  %19 = bitcast i8* %18 to { i8*, i8* }*
  %20 = getelementptr inbounds { i8*, i8* }, { i8*, i8* }* %19, i32 0, i32 0
  %21 = load i8*, i8** %20
  %22 = bitcast i8* %21 to void (i8*)*
  tail call fastcc void %22(i8* %18) #4
  ret void
}


Note that we store -2 and 1 to the state (%2), but we only branch on *%2 != 0,
and the code for the *%2 == -2 case (that calls g3) is gone.

If I add an extra couple of unreachable await points to 'g', the problem goes
away. Perhaps LLVM miscompiles 'switch i2'?

-- 
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20200306/ffcb9045/attachment-0001.html>


More information about the llvm-bugs mailing list