[coro] pre-split handling of the 'suspend' edge

I think we need a new struct/intrinsic to refer %coro.end to avoid it gets optimized out. Then, it may be fine if we don’t worrying about the refactoring.

I don’t understand: why do we need %coro.end to not be optimized out? What is the reason the block must preserved, even if nothing reaches it?

Otherwise we’ll have a @lllvm.coro.begin() but not a @llvm.coro.end() explicitly… I feel bad about it… and if we want to do something in the ending of the coroutines someday, it may matter.

(for my understanding) llvm.coro.begin produces the handle - so logically it pairs with llvm.coro.free rather than llvm.coro.end?

No, llvm.coro.free pairs with llvm.coro.alloc. llvm.coro.begin() marks the begin of the coroutine while the llvm.coro.end() marks the end of the coroutine.

It makes sense to me to have a begin but no end, if the end is in fact unreachable, because the coroutine always aborts before reaching the end.

Are there concrete problems this is expected to cause, or are you only saying it feels weird?

because the coroutine always aborts before reaching the end.

This is not true. The coro.ends are always reached. They are just lowered to non-op after compilations.

Are there concrete problems this is expected to cause

Currently, no. But it requires more work to refactor the existing code than adding a new explicit destination BB in an instruction personally.

IIUC the callbr alternative with @llvm.coro.suspend implying @llvm.coro.end seems to address cleanly (meaning, maintainably) the problem the thread started with. Let’s worry about implementation effort separately - if we get general agreement this is the better, more sustainable design (for LLVM in total - coro & other scenarios), I’m happy to help, and I’m sure there are other folks in the community also interested in doing that.

Except implementation effort, the only concern is the semantics of callbr. Since currently it is only used to implement goto. See LLVM Language Reference Manual — LLVM 18.0.0git documentation. The semantics of fallthrough label and indirect label sounds not good. But if we are able to extend/refactor that cleanly as @efriedma-quic mentioned. Then yes, no more concerns.

@mtrofin pointed out that [coro] Lower `llvm.coro.await.suspend.handle` to resume with tail call by zmodem · Pull Request #89751 · llvm/llvm-project · GitHub is related to this topic

I’ve been re-reading this thread since @jyknight asked if the problem has been addressed yet.

Going back to the original post:

I think the only part of coro-split which assumed that the %suspend block contains only @llvm.coro.end and ret was the code that turned symmetric transfers into tail calls.

That code no longer exists after [coro] Lower `llvm.coro.await.suspend.handle` to resume with tail cal… · llvm/llvm-project@3bb3969 · GitHub

If a pass were to split the edge to the suspend block, or even insert a printf("Coroutine suspending") call, I don’t believe that would be a problem anymore.

I’m guessing those cases were also instances of instructions in the path from @llvm.coro.suspend to ret blocking the tail call transformation. At least from looking at the lit tests, I see that there were a number of such issues. I believe my patch fixed that category of problems altogether.

If I understand the original callbr proposal correctly, that wouldn’t change the control flow of the function — the callbr @llvm.coro.suspend would branch to the same %suspend, %resume, or %destroy blocks as before — it would just be more opaque to other passes. (But tail calls could previously still be broken if unexpected instructions ended up in the %suspend block.)

This seems like the real crux of the thread, and also orthogonal(?) to whether we use a call+switch or callbr for @llvm.coro.suspend:

It seems the point of the %suspend block is to have control flow to the @llvm.coro.end intrinsic, which seems to act like a marker rather than have any effect in itself (in returned-continuation lowering it seems to do more).

I don’t fully understand @llvm.coro.end but it sounds like at least in the switched-resume lowering, maybe we don’t need it if @llvm.coro.suspend is also treated as an implicit @llvm.coro.end.

But since the original problem in the thread was resolved, is there any point in exploring that option further? Do we think it would bring more benefits?