blob: 93f5cf10ff448c4c26fb504c424e1104e0da3909 [file]
.. _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.