| .. _amdgpu-async-operations: | |
| =============================== | |
| AMDGPU Asynchronous Operations | |
| =============================== | |
| .. contents:: | |
| :local: | |
| Introduction | |
| ============ | |
| Asynchronous operations are operations whose completion is not tracked | |
| internally by the compiler. A thread that initiates one or more async operations can use | |
| *asyncmarks* to track their completion. | |
| - Most :ref:`DMA operations<amdgpu-dma-operations>` are asynchronous. | |
| Asyncmarks | |
| ========== | |
| An *asyncmark* created by a thread can be used to track async operations | |
| initiated by that thread. The abstract machine maintains a sequence of | |
| asyncmarks during the execution of a function body, which excludes any | |
| asyncmarks produced by calls to other functions encountered in the currently | |
| executing function. The state of this sequence at each program point in the | |
| function is called the *current sequence*. | |
| ``@llvm.amdgcn.asyncmark()`` | |
| ---------------------------- | |
| Produces an asyncmark and appends it to the current sequence. | |
| ``@llvm.amdgcn.wait.asyncmark(i16 %N)`` | |
| --------------------------------------- | |
| Ensures that the length of the current sequence is at most ``N`` by removing | |
| asyncmarks from the start of the sequence if it is more than ``N``. | |
| .. _amdgpu-asyncmark-memory-model: | |
| Memory Model | |
| ============ | |
| An ``asyncmark()`` operation ``X`` that produces an asyncmark ``M`` is | |
| *completed-at* a ``wait.asyncmark()`` operation ``Y`` in the same function body | |
| if: | |
| - ``X`` is *program-ordered* before ``Y``, and | |
| - ``M`` is not in the current sequence at any operation ``Z`` that immediately | |
| follows ``Y`` in *program-order*. | |
| Each dynamic instance ``I`` of an async *instruction* initiates a corresponding | |
| async *operation* ``A`` such that ``I`` *happens-before* ``A``. Then ``A`` | |
| *happens-before* a ``wait.asyncmark()`` operation ``Y`` if there exists an | |
| ``asyncmark()`` operation ``X`` such that: | |
| - ``I`` is *program-ordered* before ``X``, and | |
| - ``X`` is *completed-at* ``Y``. | |
| Examples | |
| ======== | |
| Uneven blocks of async operations | |
| --------------------------------- | |
| .. code-block:: c++ | |
| void foo(global int *g, local int *l) { | |
| // first block | |
| async_load_to_lds(l, g); | |
| async_load_to_lds(l, g); | |
| async_load_to_lds(l, g); | |
| asyncmark(); | |
| // second block; longer | |
| async_load_to_lds(l, g); | |
| async_load_to_lds(l, g); | |
| async_load_to_lds(l, g); | |
| async_load_to_lds(l, g); | |
| async_load_to_lds(l, g); | |
| asyncmark(); | |
| // third block; shorter | |
| async_load_to_lds(l, g); | |
| async_load_to_lds(l, g); | |
| asyncmark(); | |
| // Wait for first block | |
| wait.asyncmark(2); | |
| } | |
| Software pipeline | |
| ----------------- | |
| .. code-block:: c++ | |
| void foo(global int *g, local int *l) { | |
| // first block | |
| asyncmark(); | |
| // second block | |
| asyncmark(); | |
| // third block | |
| asyncmark(); | |
| for (;;) { | |
| wait.asyncmark(2); | |
| // use data | |
| // next block | |
| asyncmark(); | |
| } | |
| // flush one block | |
| wait.asyncmark(2); | |
| // flush one more block | |
| wait.asyncmark(1); | |
| // flush last block | |
| wait.asyncmark(0); | |
| } | |
| Ordinary function call | |
| ---------------------- | |
| .. code-block:: c++ | |
| extern void bar(); // may or may not initiate async operations | |
| void foo(global int *g, local int *l) { | |
| // first block | |
| asyncmark(); | |
| // second block | |
| asyncmark(); | |
| // function call | |
| bar(); | |
| // third block | |
| asyncmark(); | |
| // wait for the second block | |
| wait.asyncmark(1); | |
| // wait for the third block, including bar() | |
| wait.asyncmark(0); | |
| } | |
| Implementation notes | |
| ==================== | |
| [This section is informational.] | |
| Function Calls | |
| -------------- | |
| In general, at a function call, if the caller uses sufficient waits to track | |
| its own async operations, the actions performed by the callee cannot affect | |
| correctness. But inlining such a call may result in redundant waits. | |
| .. code-block:: c++ | |
| void foo() { | |
| ... | |
| asyncmark(); // X | |
| ... // no wait.asyncmark() | |
| } | |
| void bar() { | |
| asyncmark(); // B | |
| asyncmark(); // C | |
| foo(); | |
| wait.asyncmark(1); // D | |
| } | |
| Before inlining, it is unspecified whether ``X`` is *completed-at* ``D``, while | |
| ``C`` is **not** *completed-at* ``D``. The programmer can only rely on ``B`` | |
| being *completed-at* ``D``. | |
| .. code-block:: c++ | |
| void bar() { | |
| asyncmark(); // B | |
| asyncmark(); // C | |
| ... | |
| asyncmark(); // X | |
| ... // no wait.asyncmark() | |
| wait.asyncmark(1); // D | |
| } | |
| After inlining, ``C`` is also *completed-at* ``D`` and ``X`` is **not** | |
| *completed-at* ``D``. | |
| Conversely, a ``wait.asyncmark`` call inside a callee cannot be used to track | |
| asyncmarks from the caller, since this ``wait.asyncmark`` can only | |
| observe the current sequence of the callee. | |
| .. code-block:: c++ | |
| void foo() { | |
| ... // no asyncmark() | |
| wait.asyncmark(0); // Y | |
| ... | |
| } | |
| void bar() { | |
| asyncmark(); // B | |
| asyncmark(); // C | |
| foo(); | |
| wait.asyncmark(1); // D | |
| } | |
| In the above example, it is unspecified whether ``B`` and ``C`` in ``bar()`` are | |
| *completed-at* ``Y``, because they are not included in the sequence that can be | |
| examined at ``Y``. | |
| .. code-block:: c++ | |
| void bar() { | |
| asyncmark(); // B | |
| asyncmark(); // C | |
| ... // no asyncmark() | |
| wait.asyncmark(0); // Y | |
| ... | |
| wait.asyncmark(1); // D | |
| } | |
| After inlining, both ``B`` and ``C`` are *completed-at* ``Y``. | |
| Optimization | |
| ------------ | |
| The implementation may eliminate asyncmark/wait intrinsics in the following | |
| cases. These are just examples and not meant to be an exhaustive list. | |
| 1. An ``asyncmark`` operation which remains in the current sequence along every | |
| path that reaches the function exit. | |
| .. code-block:: c++ | |
| void foo() { | |
| ... | |
| asyncmark(); // X | |
| ... // no wait.asyncmark() | |
| } | |
| Here, ``X`` can be eliminated. | |
| 2. A ``wait.asyncmark`` which sees an empty sequence of asyncmarks along every | |
| path that reaches it. | |
| .. code-block:: c++ | |
| void foo() { | |
| ... // no asyncmark() | |
| wait.asyncmark(0); // Y | |
| ... | |
| } | |
| Here, ``Y`` can be eliminated. |