Page MenuHomePhabricator

[RFC][Coroutines] Add the newly generated SCCs back to the CGSCC work queue after CoroSplit actually happened
Changes PlannedPublic

Authored by lxfind on Feb 1 2021, 11:28 AM.

Details

Summary

Relevant discussion can be found at: https://lists.llvm.org/pipermail/llvm-dev/2021-January/148197.html
In the existing design, An SCC that contains a coroutine will go through the folloing passes:
Inliner -> CoroSplitPass (fake) -> FunctionSimplificationPipeline -> Inliner -> CoroSplitPass (real) -> FunctionSimplificationPipeline

The first CoroSplitPass doesn't do anything other than putting the SCC back to the queue so that the entire pipeline can repeat.
As you can see, we run Inliner twice on the SCC consecutively without doing any real split, which is unnecessary and likely unintended.
What we really wanted is this:
Inliner -> FunctionSimplificationPipeline -> CoroSplitPass -> Inliner -> FunctionSimplificationPipeline

Hence the way we do it here is to move CoroSplitPass to the end of the CGSCC pipeline, make it once for real, and then insert the newly generated SCCs back to the pipeline so that they can be optimized again.
From here we can then reason about whether we actually want to run it a second time (maybe add an option to turn it off if the cost is over benefit).

This approach also conforms to how the new pass manager works instead of relying on an adhoc post split cleanup, making it ready for full switch to new pass manager eventually.

A lot of tests need to be updated because we now no longer clean things up in O0 after CoroSplit.
I will clean up the tests latter but for now this is RFC to see if people have any objects on doing this.

Diff Detail

Unit TestsFailed

TimeTest
130 msx64 debian > Clang.CodeGenCoroutines::coro-newpm-pipeline.cpp
Script: -- : 'RUN: at line 4'; /mnt/disks/ssd0/agent/llvm-project/build/bin/clang -cc1 -internal-isystem /mnt/disks/ssd0/agent/llvm-project/build/lib/clang/12.0.0/include -nostdsysteminc -triple x86_64-unknown-linux-gnu -emit-llvm-bc -o /dev/null -fexperimental-new-pass-manager -fdebug-pass-manager -fcoroutines-ts -O0 /mnt/disks/ssd0/agent/llvm-project/clang/test/CodeGenCoroutines/coro-newpm-pipeline.cpp 2>&1 | /mnt/disks/ssd0/agent/llvm-project/build/bin/FileCheck /mnt/disks/ssd0/agent/llvm-project/clang/test/CodeGenCoroutines/coro-newpm-pipeline.cpp
70 msx64 debian > LLVM.Transforms/Coroutines::coro-alloca-06.ll
Script: -- : 'RUN: at line 3'; /mnt/disks/ssd0/agent/llvm-project/build/bin/opt < /mnt/disks/ssd0/agent/llvm-project/llvm/test/Transforms/Coroutines/coro-alloca-06.ll -coro-split -S | /mnt/disks/ssd0/agent/llvm-project/build/bin/FileCheck --allow-unused-prefixes=false /mnt/disks/ssd0/agent/llvm-project/llvm/test/Transforms/Coroutines/coro-alloca-06.ll
90 msx64 debian > LLVM.Transforms/Coroutines::coro-async.ll
Script: -- : 'RUN: at line 1'; /mnt/disks/ssd0/agent/llvm-project/build/bin/opt < /mnt/disks/ssd0/agent/llvm-project/llvm/test/Transforms/Coroutines/coro-async.ll -enable-coroutines -O2 -S | /mnt/disks/ssd0/agent/llvm-project/build/bin/FileCheck --allow-unused-prefixes=false --check-prefixes=CHECK /mnt/disks/ssd0/agent/llvm-project/llvm/test/Transforms/Coroutines/coro-async.ll
50 msx64 debian > LLVM.Transforms/Coroutines::coro-catchswitch-cleanuppad.ll
Script: -- : 'RUN: at line 3'; /mnt/disks/ssd0/agent/llvm-project/build/bin/opt < /mnt/disks/ssd0/agent/llvm-project/llvm/test/Transforms/Coroutines/coro-catchswitch-cleanuppad.ll -coro-split -S | /mnt/disks/ssd0/agent/llvm-project/build/bin/FileCheck --allow-unused-prefixes=false /mnt/disks/ssd0/agent/llvm-project/llvm/test/Transforms/Coroutines/coro-catchswitch-cleanuppad.ll
60 msx64 debian > LLVM.Transforms/Coroutines::coro-catchswitch.ll
Script: -- : 'RUN: at line 2'; /mnt/disks/ssd0/agent/llvm-project/build/bin/opt < /mnt/disks/ssd0/agent/llvm-project/llvm/test/Transforms/Coroutines/coro-catchswitch.ll -coro-split -S | /mnt/disks/ssd0/agent/llvm-project/build/bin/FileCheck --allow-unused-prefixes=false /mnt/disks/ssd0/agent/llvm-project/llvm/test/Transforms/Coroutines/coro-catchswitch.ll
View Full Test Results (68 Failed)

Event Timeline

lxfind created this revision.Feb 1 2021, 11:28 AM
lxfind requested review of this revision.Feb 1 2021, 11:28 AM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 1 2021, 11:28 AM
aeubanks added inline comments.Feb 1 2021, 12:33 PM
llvm/lib/Passes/PassBuilder.cpp
625

we can still keep this here right? since we'll run the function simplification pipeline on the split coroutine
also, this should be duplicated in buildFunctionSimplificationPipeline()

llvm/lib/Transforms/Coroutines/CoroSplit.cpp
1710

this could regress coroutines under the legacy PM behavior since we don't run postSplitCleanup() here? but maybe we don't care about the legacy PM so much anymore.

2004

are all Clones guaranteed to be in different SCC?

lxfind added inline comments.Feb 1 2021, 12:46 PM
llvm/lib/Transforms/Coroutines/CoroSplit.cpp
1710

Not really. CoroSplit for legacy PM works in a similar way. Function simplifications happen after CoroSplit (if not O0) as well.

2004

No. But CWorklist is a map so redundant insertions won't happen.

I can't really review the subtle pass-management stuff here, but if you're going to be able to eliminate the hacky post-pass cleanup pipeline, I'm overjoyed.

lxfind added inline comments.Feb 1 2021, 1:41 PM
llvm/lib/Passes/PassBuilder.cpp
625

We could. It does create a logically strange situation, that CoroElide will run once before CoroSplit.
How to decide whether CoroElide should go into simplification pipeline or optimization pipeline?

ChuanqiXu added inline comments.Feb 1 2021, 6:21 PM
llvm/lib/Passes/PassBuilder.cpp
625

I agree with that it is strange that CoroElide would run once before CoroSplit. But if we only run CoroElide after CoroSplit, we may miss many optimization point. Given that a coroutine A was inlined into a coroutine B, and CoroElide run on B before the CoroSplit for B.

define B(...) {
    entry:
         %A.handle = ...; The handle for A
         ;... ; Some operations with A
        call @llvm.coro.suspend(%B.handle, false); which is possible if the implementation of initial_suspend of the promise type of B isn't std::experimental::suspend_always
        ;... Some other operations with A
        call @llvm.subfn.addr(%A.handle, 1) ; means the end for the lifetime of A
}

In this example, CoroElide would find that every path from the definition of %A.handle to the exit point of B would pass llvm.subfn.addr(%A.handle, 1) which means the end for the lifetime of %A.handle so that CoroElide may find that %A.handle isn't escaped in B. So it is possible that A maybe elided in B.

However if we run CoroElide only if B have been split, we may miss the opportunity to elide A in B. I would give the example if needed.

I agree with that the design of CoroElide seems to be a little strange and need to be refactored likely. But I don't get a clear and feasible solution now.

llvm/lib/Transforms/Coroutines/CoroSplit.cpp
2001

I am not familiar with the Shape.ABI other than coro::ABI:switch. But the diff line seems strange, it looks like that condition gets weaker.

ChuanqiXu added inline comments.Feb 1 2021, 6:26 PM
llvm/lib/Transforms/Coroutines/CoroSplit.cpp
1032

Is it necessary to remain the verify process?

rjmccall added inline comments.Feb 1 2021, 9:51 PM
llvm/lib/Transforms/Coroutines/CoroSplit.cpp
1032

It's probably okay for it to go away now.

ChuanqiXu added inline comments.Feb 2 2021, 1:27 AM
llvm/lib/Transforms/Coroutines/CoroSplit.cpp
1032

I prefer to remain the verification *personally* since it had reported many errors for me :).

By the way, is everyone comfortable with dropping the test lines using the legacy pass manager for Coroutine?
It's really painful to update the tests to keep both legacy pass manager and new pass manager, now that the pass sequence becomes much different. (CoroSplit runs late in CGSCC pipeline for NewPM, but early in legacy pm).
If we want to keep the legacy PM tests for a while for coroutine, I could wait until everyone is comfortable before updating this patch again.

By the way, is everyone comfortable with dropping the test lines using the legacy pass manager for Coroutine?
It's really painful to update the tests to keep both legacy pass manager and new pass manager, now that the pass sequence becomes much different. (CoroSplit runs late in CGSCC pipeline for NewPM, but early in legacy pm).
If we want to keep the legacy PM tests for a while for coroutine, I could wait until everyone is comfortable before updating this patch again.

What's most important is to test the pass. If you want to port most of the tests to unconditionally test under the new PM, that's fine with me. We should keep some tests that validate basic full-coroutine-pipeline behavior under the old PM until we actually retire the old PM, though.

By the way, is everyone comfortable with dropping the test lines using the legacy pass manager for Coroutine?
It's really painful to update the tests to keep both legacy pass manager and new pass manager, now that the pass sequence becomes much different. (CoroSplit runs late in CGSCC pipeline for NewPM, but early in legacy pm).
If we want to keep the legacy PM tests for a while for coroutine, I could wait until everyone is comfortable before updating this patch again.

I think testing is more important than the develop experience. It is more suitable to remove the tests for legacy pass manager when the old pass manager got replaced entirely.

aeubanks added inline comments.Feb 3 2021, 11:37 AM
llvm/lib/Passes/PassBuilder.cpp
625

The simplification pipeline is anything that simplifies/canonicalizes IR. Passes in the optimization may add complexity. If CoroElide mostly ends up cleaning things, it should definitely be in the simplification pipeline.

I'm not super familiar with the coroutine passes, but given that we now unconditionally re-add the current SCC to the queue, CoroElide should run before and after CoroSplit on any given function that is split. If that's good enough then I'd say keep this here.

llvm/lib/Transforms/Coroutines/CoroSplit.cpp
2001

I believe that's intentional, and a big part of this patch. We want to re-add the current SCC (and the split SCCs) any time we split an SCC. Before we weren't properly doing that.

2004

Oh I didn't know that, thanks!

ChuanqiXu added inline comments.Feb 3 2021, 6:30 PM
llvm/lib/Passes/PassBuilder.cpp
625

CoroElide pass would try to transform heap allocation to stack allocation, which is a major optimization in Coroutine Passes. To my understanding, it isn't equal to simplification. I am not pretty sure about this.

I prefer to run CoroElide before and after CoroSplit for now even it is strange.

aeubanks added inline comments.Feb 3 2021, 7:30 PM
llvm/lib/Passes/PassBuilder.cpp
625

Your description of the pass sounds like simplification/canonicalization to me.

By the way, is everyone comfortable with dropping the test lines using the legacy pass manager for Coroutine?
It's really painful to update the tests to keep both legacy pass manager and new pass manager, now that the pass sequence becomes much different. (CoroSplit runs late in CGSCC pipeline for NewPM, but early in legacy pm).
If we want to keep the legacy PM tests for a while for coroutine, I could wait until everyone is comfortable before updating this patch again.

I think testing is more important than the develop experience. It is more suitable to remove the tests for legacy pass manager when the old pass manager got replaced entirely.

By the way, since the new PM has been enabled by default, all of the tests are no longer testing Legacy PM behavior anymore (unless explicitly specified --enable-new-pm=0)

By the way, is everyone comfortable with dropping the test lines using the legacy pass manager for Coroutine?
It's really painful to update the tests to keep both legacy pass manager and new pass manager, now that the pass sequence becomes much different. (CoroSplit runs late in CGSCC pipeline for NewPM, but early in legacy pm).
If we want to keep the legacy PM tests for a while for coroutine, I could wait until everyone is comfortable before updating this patch again.

I think testing is more important than the develop experience. It is more suitable to remove the tests for legacy pass manager when the old pass manager got replaced entirely.

By the way, since the new PM has been enabled by default, all of the tests are no longer testing Legacy PM behavior anymore (unless explicitly specified --enable-new-pm=0)

To my understand, this means that it is OK to remain the Legacy PM test lines? Since the legacy PM test line would be ignored?

lxfind planned changes to this revision.Feb 18 2021, 8:57 AM

Since we seem to be nearly done with the legacy PM, I think it's acceptable to no longer test it specifically for the coro passes.