<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml" xmlns="http://www.w3.org/TR/REC-html40">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Windows-1252">
<meta name="Generator" content="Microsoft Word 15 (filtered medium)">
<style><!--
/* Font Definitions */
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
{font-family:Calibri;
panose-1:2 15 5 2 2 2 4 3 2 4;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin:0cm;
font-size:11.0pt;
font-family:"Calibri",sans-serif;
mso-fareast-language:EN-US;}
a:link, span.MsoHyperlink
{mso-style-priority:99;
color:#0563C1;
text-decoration:underline;}
span.EmailStyle18
{mso-style-type:personal-reply;
font-family:"Calibri",sans-serif;
color:windowtext;}
.MsoChpDefault
{mso-style-type:export-only;
font-size:10.0pt;}
@page WordSection1
{size:612.0pt 792.0pt;
margin:72.0pt 72.0pt 72.0pt 72.0pt;}
div.WordSection1
{page:WordSection1;}
--></style>
</head>
<body lang="en-DE" link="#0563C1" vlink="purple" style="word-wrap:break-word">
<div class="WordSection1">
<p class="MsoNormal"><span lang="EN-US">Small correction: The link is </span><span lang="EN-US"><a href="https://godbolt.org/z/Weod78">https://godbolt.org/z/Weod78</a> without the trailing “.”. It seems some regex
interpreted the “.” from the end of the sentence as part of the URL </span><span lang="EN-US"><o:p></o:p></span></p>
<p class="MsoNormal"><o:p> </o:p></p>
<div style="border:none;border-top:solid #B5C4DF 1.0pt;padding:3.0pt 0cm 0cm 0cm">
<p class="MsoNormal" style="margin-bottom:12.0pt"><b><span style="font-size:12.0pt;color:black">From:
</span></b><span style="font-size:12.0pt;color:black">llvm-dev <llvm-dev-bounces@lists.llvm.org> on behalf of Adrian Vogelsgesang via llvm-dev <llvm-dev@lists.llvm.org><br>
<b>Date: </b>Saturday, 23. January 2021 at 01:13<br>
<b>To: </b>llvm-dev@lists.llvm.org <llvm-dev@lists.llvm.org><br>
<b>Subject: </b>[llvm-dev] Guidance requested: Fixing missed optimization for coroutines</span><span style="font-size:12.0pt;color:black;mso-fareast-language:EN-GB"><o:p></o:p></span></p>
</div>
<p class="MsoNormal"><span lang="EN-US">Dear Clang community,<br>
<br>
TLDR: I came across a few missed optimization for coroutines; would be happy to contribute a patch/an improvement; need guidance, though, as I am new to coroutines in LLVM<br>
<br>
You can find the input C++ program in <a href="https://godbolt.org/z/Weod78">
https://godbolt.org/z/Weod78</a>.<br>
The coroutines in that snippet can finish both synchronously and asynchronously. In case a coroutine finishes synchronously, I want to avoid the allocation of the coroutine frame.<br>
<br>
Looking at the produced assembly, this snippet shows the following missed optimizations:<br>
1. lines 12 - 14: The call to “constant12() [clone .destroy]” is not devirtualized<br>
2. line 5: The coroutine frame for `constant12` is not elided (in a trivial, simple case without resumption points)<br>
3. line 15-16: the call to “.LNoopCoro.ResumeDestroy” is not devirtualized; it should be de-virtualized and inlined<br>
4. line 5: The coroutine frame for `sum` is not “conditionally” elided (less trivial)</span><o:p></o:p></p>
<p class="MsoNormal"><span lang="EN-US"><br>
I already did some digging in the coroutine-related optimization passes and I think I identified the following root causes/solutions<br>
<br>
<br>
# CoroElide disabled for “own“ coroutine frame</span><o:p></o:p></p>
<p class="MsoNormal"><span lang="EN-US"> </span><o:p></o:p></p>
<p class="MsoNormal"><span lang="EN-US">CoroElide.cpp, line 271 (see link [1]) explicitly disables the CoroElide pass for the own coroutine frame. The CoroElide pass currently only modifies CoroIds which were inlined from other coroutines and leaves the own
CoroId alone. Due to this, the call to “constant12() [clone .destroy]” is not devirtualized, and the coroutine frame cannot be elided. After removing this check and unconditionally applying the CoroElide to all CoroIds, issues (1) and (2) from my example are
fixed.<br>
<br>
My question: Is this check necessary because the CoroElide pass would otherwise be incorrect? Or is it a performance optimization, i.e. we didn’t expect the CoroElide to be useful when applied to the function’s own CoroId and hence disabled it in this case?<br>
<br>
<br>
# “@llvm.coro.subfn.addr” instrinsic not devirtualized if applied to constants<br>
<br>
Issue (3), i.e. the call to “.LNoopCoro.ResumeDestroy” not being devirtualized, seems to be due to the usage of the “@llvm.coro.subfn.addr” instrinsic. CoroElide devirtualizes this instrinsic if applied on a “coro.begin” intrinsic. But there is no devirtualization
for subfn.addr calls on constants. Afaict, the lowering in CoroCleanup happens too late in the pipeline, such that the remaining passes won’t remove the load.<br>
<br>
I see multiple ways to fix this issue:<br>
1. In the CoroEarly pass, lower “coro.destroy” and “coro.resume” directly to the corresponding load, instead of lowering to “coro.subfn.addr”. The normal “memory constant folding pass” (mem2reg? not sure which pass does this…) would see the loads and could
constant fold them, thereby devirtualizing the call. Downside: CoroElide would now need to do more complicated pattern matching to identify accesses to the resume/destroy function pointers.<br>
2. In the CoroEarly pass, lower “coro.destroy/resume” to memory operations, *except* if they are applied on a “coro.begin”. If they are applied on a “coro.begin”, keep using “coro.subfn.addr”. Benefit: We can still use mem2reg (?) to devirtualize calls on constant
coroutine frames. At the same time, CoroElide can keep using “coro.subfn.addr” to devirtualize non-constant coroutine frames.<br>
3. In the CoroCleanup pass, special case the lowering of “coro.subfn.addr” when applied to constants. In that case, don’t generate loads, but rather produce the corresponding constant. Downside: probably too late in the pipeline such that the de-virtualized
function would not be inlined.<br>
<br>
My question: Which of those potential ways would be preferred?<br>
<br>
<br>
# CoroElide does not support to defer the coroutine frame allocation</span><o:p></o:p></p>
<p class="MsoNormal"><span lang="EN-US"> </span><o:p></o:p></p>
<p class="MsoNormal"><span lang="EN-US">Issue (4), i.e. that coroutine frame for the function `sum` being allocated unconditionally, seems to be the most challenging to fix.<br>
I am still kind of lost how to even approach this…<br>
<br>
My questions: Does anyone of you have an idea how to approach this? Is there maybe even already some relevant literature/research on this topic?<br>
<br>
<br>
Cheers,<br>
Adrian<br>
<br>
<br>
[1] <a href="https://github.com/llvm/llvm-project/blob/607bec0bb9f787acca95f53dabe6a5c227f6b6b2/llvm/lib/Transforms/Coroutines/CoroElide.cpp#L271">
https://github.com/llvm/llvm-project/blob/607bec0bb9f787acca95f53dabe6a5c227f6b6b2/llvm/lib/Transforms/Coroutines/CoroElide.cpp#L271</a></span><o:p></o:p></p>
</div>
</body>
</html>