[clang] [llvm] [profcheck][coro] Adding Branch weights PGO in Coroutines Passes (PR #184466)

Jin Huang via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 29 02:18:38 PDT 2026


jinhuang1102 wrote:

> I'd put the explanation from [here](https://github.com/llvm/llvm-project/issues/185949#issuecomment-4703198841) in the commit message. IMHO the way `resume` is handled is the important thing (for performance), and how that's handled deserves focus, rather than a side-note.
> 
> Can you also add an explanation as to why the 3 places (Coro{Split|Frame|Cleanup}) cover profile propagation for coroutines - or is there anything major left after this? I.e. it's less interesting that 3 files got patched (since one can just read the diff), and more interesting as to how the overall change addresses the overall problem (==profile propagation for coroutines) - since that's design information not immediately apparent from the change itself, and, thus, worth communicating.

Thanks for the comment!  You are right that explaining the design of profile propagation is much more valuable than just listing the changes files.

The PGO profiles propagation is mainly happened in `SwitchCoroutineSplitter::split`. Let's look at how the IR is transformed in the 1) `createResumeEntryBlock(F, Shape);` and 2) the creation of `ResumeClone` (`.resume`). 

We use [coro-await-suspend-lower-invoke.ll](https://github.com/llvm/llvm-project/blob/main/llvm/test/Transforms/Coroutines/coro-await-suspend-lower-invoke.ll) in the following discussion.

1. The `createResumeEntryBlock` prepares the coroutine's control flow for splitting by setting up a unified resume dispatch block (`resume.entry`) and refactoring the suspend points. 
a. Adding the Resume Dispatcher:
It appends the `resume.entry` block at the end of the original function `f`. This block loads the suspend index from the coroutine frame and dispatches control flow to the resume points:
    ```
    define void @f() personality i32 0 !prof !0 {
    ...
    resume.entry:                                     ; No predecessors!
      %index.addr = getelementptr inbounds i8, ptr %hdl, i64 16
      %index = load i2, ptr %index.addr, align 1
      switch i2 %index, label %unreachable [
        i2 0, label %resume.0
        i2 1, label %resume.1
        i2 -2, label %resume.2
        i2 -1, label %resume.3
      ], !prof !3
    
    unreachable:                                      ; preds = %resume.entry
      unreachable
    }
    ```
b. Before the splitting, a typical suspend point looks like this:
  ```
    CoroSave:
      %0 = call token @llvm.coro.save(ptr %hdl)
      br label %CoroSuspend
    CoroSuspend:
      %suspend.init = call i8 @llvm.coro.suspend(token %0, i1 false)
      br label %AfterCoroSuspend
    AfterCoroSuspend:
      switch i8 %suspend.init, label %ret [
        i8 0, label %CoroSave1
        i8 1, label %cleanup
      ]
  ```
`createResumeEntryBlock` refactors this into a landing BB to unify the suspend path(initial execution) and resume path when resume() is called:
  ```
  CoroSave:
    %index.addr12 = getelementptr inbounds i8, ptr %hdl, i64 16
    store i2 0, ptr %index.addr12, align 1          ; Save the index (0) before suspending
    br label %CoroSuspend
  
  CoroSuspend:
    br label %resume.0.landing                      ; Jump to landing pad to return -1
  
  resume.0:                                         ; Target of the dispatch switch
    %suspend.init = call i8 @llvm.coro.suspend(token none, i1 false)
    br label %resume.0.landing
  
  resume.0.landing:
    ; PHI merges: -1 (from suspension path) vs. %suspend.init (from resumption path)
    %0 = phi i8 [ -1, %CoroSuspend ], [ %suspend.init, %resume.0 ]
    br label %AfterCoroSuspend
  
  AfterCoroSuspend:
    switch i8 %0, label %ret [
      i8 0, label %CoroSave1
      i8 1, label %cleanup
    ]
  ```

(1/n - More to come in the next comment)

https://github.com/llvm/llvm-project/pull/184466


More information about the llvm-commits mailing list