[llvm-dev] [RFC] LLVM Coroutines
Eli Friedman via llvm-dev
llvm-dev at lists.llvm.org
Fri Jun 10 19:40:45 PDT 2016
On Fri, Jun 10, 2016 at 5:25 PM, Gor Nishanov <gornishanov at gmail.com> wrote:
> Hi Eli:
>
> >> Naively, you would expect that it would be legal to hoist the store...
> >> but that breaks your coroutine semantics because the global could be
> mutated
> >> between the first return and the resume.
>
> Hmmm... I don't see the problem. I think hoisting the store is perfectly
> legal
> transformation with all semantics preserved.
>
> Let's look at your example:
>
> >> block1:
> >> suspend
> >> switch (resume, destroy, return)
> >>
> >> resume:
> >> store zero to global @g
> >> doA()
> >> [...]
> >>
> >> destroy:
> >> store zero to global @g
> >> doB()
> >> [...]
> >>
> >> return:
> >> store zero to global @g
> >> doC
> >> [...]
>
> As written the behavior is:
>
> 1) when we encounter a suspend during the first pass through the
> function,
> store 0 to @g and doC()
>
> 2) when we encounter a suspend after coroutine was resumed
> ret void
>
> 3) When coroutine is resumed:
> store 0 to @g and doA()
>
> 4) When coroutine is destroyed:
> store 0 to @g and doB()
>
> If this is a coroutine that can be resumed asynchronously from a different
> thread there is a data race. For example, if resume happens before 'f'
> returns,
> doA() can write something useful to @g, and 'f' will clobber it with zero.
> But, this race is already present in the code and is not introduced by
> LLVM.
>
> Let's hoist the store and see what happens:
>
> >> block1:
> >> suspend
> >> store zero to global @g
> >> switch (resume, destroy, return)
> >>
> >> resume:
> >> doA()
> >> [...]
> >>
> >> destroy:
> >> doB()
> >> [...]
> >>
> >> return:
> >> doC()
> >> [...]
>
> Now, let's split it up:
> 1. RAUW coro.suspend with -1,0,1 in f, f.resume and f.destroy
> 2. remove coro.suspend in f, replace with ret void in f.resume
>
> void f() {
> [...]
> store zero to global @g
> doC();
> [...]
> }
>
> void @f.resume() {
> entry:
> store zero to global @g
> doA();
> [....]
> }
>
> void @f.destroy() {
> entry:
> store zero to global @g
> doB();
> [....]
> }
>
> Behavior looks exactly as before. What am I missing?
>
>
Err... I guess nothing; sorry, I got myself confused. Essentially
executing a return statement, then jumping back, seems wrong, but I'm
having trouble coming up with a good example of how it would actually break
something. I guess there aren't really any issues on a pure control-flow
basis: you can't execute a global side-effect a different number of times
than it would otherwise happen.
You might run into problems with allocas: LLVM's optimizer will assume the
lifetime of any alloca in the current function ends when you hit a "ret"
instruction, so jumping back from the ret to the middle of the function
could lead to wrong results. Silly example:
x = alloca...
block1:
suspend
; increment could get moved here.
switch (resume, destroy, return)
resume:
x += 1
[...]
destroy:
x += 1
[...]
return:
(don't touch x)
[...]
The increment is only supposed to execute once, but instead executes twice.
This isn't a great example... and I'm not sure issues are limited to
allocas... but that's the idea, anyway.
-Eli
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20160610/39f19df7/attachment.html>
More information about the llvm-dev
mailing list