[cfe-dev] [Coroutines] How to handle arguments passed by value semantically?

chuanqi.xcq via cfe-dev cfe-dev at lists.llvm.org
Wed May 12 00:43:04 PDT 2021


Hi all,

Recently I am trying to work on coroutine bug here: https://reviews.llvm.org/D101980.
This patch is trying to copy the arguments who had been marked with `byval` into the coroutine frame. 
There summary in the link tells some background. 
Simply,
(1) By the language standard, the arguments of coroutine should be copied into the `coroutine state`.
(2) The implementation now in LLVM would copy the arguments of coroutine into the coroutine frame if needed.
(3) However, the arguments in IR are not equal to the arguments in the source. For example, the following code in C++
```
void foo() {
    coro_func(A());
}
```
would be translated into something like:
```
void foo() {
     %a = alloca A
     ; initializing for %a
     coro_func(%a);
}
```
So the actual argument type in IR becomes A* instead of A. And the current implementation in LLVM coroutine 
module would only copy the pointer to A into the coroutine frame instead of A, which is the problem I want to solve.

I had thought we could handle these case by handling the arguments marked with `byval`. But as @rjmccall said, there are
cases where arguments are passed by value semantically and passed by reference actually. 

So here is the decision we need to make. Should we mark all they arguments passed by value semantically in the frontend?
Or should we just emit the copy in the callee site for coroutines in the frontend?

It looks like the both solution would take many efforts. Then there are drawbacks for each of them.
First, for the solution based on attribute marks. The drawbacks are:
(1) What's the attribute should we use? The `byval` is deprecated actually. So we need to design a new attribute to mark the arguments
     of coroutine. I am not sure if it is a good idea to use a new attribute when we meet a new problem. It looks like there are too many
     attributes and metadata in Clang/LLVM now.
(2) The extra copy. There would be an extra copy in the coroutine to copy the arguments to the frame. Maybe we can do some peephole
     optimization to mitigate the problem.
(3) Multiple destruction problem. I am not sure if this the same problem @rjmccall mentioned about `move semantics`. My understanding is
```
class A{
     void* data_ptr;
     A(A&&);
     ~A() { if(data_ptr) delete data_ptr; }
};
void foo() {
    A a;
    coro_func(std::move(a));
}
```
     The problem here is that the data_ptr of A copied in the coroutine frame may be invalided by the destruction in the temporary produced
in the caller site if we choose to copy A into the frame instead of move. But we can't get the semantics about move in the middle end.
     The final problem about destruction looks the hardest to handle for the attribute based solutions.

     And for the solution based on emitting the copy in the callee site for coroutines,
(1) It would violate the principle of LLVM to handle the copy of arguments. LLVM would emit the copy in the caller site instead of callee site.
     I believe there must be some reasons that LLVM decides to emit the copy in the caller site. I hope someone could tell me if any one knows.
     And it would make the styles to handle arguments broken in Clang/LLVM.
(2) It looks not easy to refactor this even only for coroutine. This is not a strong argument. I just say that we need a long time to do this refactoring.
     And there may be some bugs remained during this process.
(3) We need to teach other passes. If we choose to emit the copy in the callee site, the IR for coroutine now would look like:
```
void coro_func(A* %a) {
    %a.copy = alloca A
    copy %a to %a.copy
    ; use %a.copy instead of %a
}
```
     This pattern should be eliminated by LLVM passes. So we need to teach LLVM passes to remain this structure, which means we need to insear guard codes
to many other passes.
     BTW, if we compile C++ code in debug mode, we would see a similar pattern:
```
void coro_func(A* %a) {
    %a.addr = alloca A*
    store %a, %a.addr
    %a.val = load %a.addr

    ; use %a.copy instead of %a.val
}
```
     This pattern only copies the address of %a into the new allocated variables instead of copying the contents. I don't know the meaning of doing so now.

     So it is clearly that both solutions had some drawbacks and not easy to implement. Do you guys have any thoughts?

Thanks,
Chuanqi
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20210512/c0b33c97/attachment.html>


More information about the cfe-dev mailing list