[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