Overview
This RFC proposes adding an elementwise modifier to atomic loads and stores:
%result = load atomic elementwise <2 x float>, ptr %p syncscope("agent") monotonic, align 4
store atomic elementwise <2 x float> %value, ptr %p syncscope("agent") monotonic, align 4
For vector atomic loads and stores, the modifier gives the instruction per-element atomic semantics. A vector operation with elementwise behaves as if it were expanded into one scalar atomic operation per fixed-vector element, with no additional ordering between the per-element operations beyond the ordering specified for each scalar operation. This is analogous to the existing proposal: RFC] Add elementwise modifier to atomicrmw.
A draft implementation for load atomic elementwise is available at https://github.com/llvm/llvm-project/pull/204556.
Motivation
Currently, LoadStoreVectorizer does not vectorize atomic loads or stores. It only considers simple loads and stores, and skips instructions for which LoadInst::isSimple() or StoreInst::isSimple() is false. As a result, adjacent scalar atomic loads cannot currently be combined by this pass.
For example, the following adjacent scalar atomic loads remain scalar today:
define void @test(i64 %idx, ptr addrspace(1) %out) {
%p = inttoptr i64 %idx to ptr addrspace(1)
%num1 = load atomic float, ptr addrspace(1) %p syncscope("agent") monotonic, align 4
%p1 = getelementptr inbounds nuw i8, ptr addrspace(1) %p, i64 4
%num2 = load atomic float, ptr addrspace(1) %p1 syncscope("agent") monotonic, align 4
%res = fadd float %num1, %num2
store float %res, ptr addrspace(1) %out, align 4
ret void
}
Without a new IR modifier, vectorizing the example into the following IR would change the semantics:
define void @test(i64 %idx, ptr addrspace(1) %out) #0 {
%p = inttoptr i64 %idx to ptr addrspace(1)
%1 = load atomic <2 x float>, ptr addrspace(1) %p syncscope("agent") monotonic, align 4
%num11 = extractelement <2 x float> %1, i32 0
%num22 = extractelement <2 x float> %1, i32 1
%res = fadd float %num11, %num22
store float %res, ptr addrspace(1) %out, align 4
ret void
}
A vector atomic load without elementwise is a whole-value atomic operation. The original scalar loads are independently atomic, so replacing them with a whole-vector atomic load would change the IR semantics. The same issue applies to stores: replacing independent scalar atomic stores with a vector atomic store would turn independently atomic element stores into one whole-value atomic store.
AMDGPU reports setSupportsUnalignedAtomics(false), so SelectionDAG rejects an atomic load or store whose IR alignment is smaller than the memory value type size. As a result, a whole-vector atomic load or store of <2 x float> with align 4 is rejected as an under-aligned 64-bit atomic memory access:
load atomic <2 x float>, ptr addrspace(1) %p syncscope("agent") monotonic, align 4
However, the intended operation is not a single 64-bit atomic memory access. It is two independent 32-bit atomic element accesses from adjacent addresses. The proposed elementwise modifier makes that distinction explicit in IR:
load atomic elementwise <2 x float>, ptr addrspace(1) %p syncscope("agent") monotonic, align 4
This also gives targets a way to reason about the alignment using the scalar element type rather than the whole vector type. On AMDGPU, ordinary global memory operations can use dword-aligned wide accesses: SITargetLowering::allowsMisalignedMemoryAccessesImpl() allows extended global address space accesses when the alignment is at least 4 bytes, even if the access size is wider. With elementwise, AMDGPU can preserve the per-element atomic semantics while still selecting an appropriate wide global memory operation, such as a 64-bit global load or store, when that is valid for the target.
Proposed Syntax
<result> = load atomic [volatile] [elementwise] <ty>, ptr <pointer> [syncscope("<target-scope>")] <ordering>, align <alignment> [, !invariant.group !<empty_node>]
store atomic [volatile] [elementwise] <ty> <value>, ptr <pointer> [syncscope("<target-scope>")] <ordering>, align <alignment>
Lowering Strategy
Targets may lower an elementwise atomic vector load or store using any instruction sequence that preserves the per-element atomic semantics. For AMDGPU, this may allow adjacent dword-aligned elementwise atomic operations to be represented using an appropriate wide global memory operation when the target can prove that doing so preserves the required semantics.
Implementation Plan
If this idea is approved, I have the following plans:
-
Add IR support for
load atomicelementwise andstore atomicelementwise. -
Teach AMDGPU lowering to preserve per-element atomic semantics for elementwise atomic loads and stores.
-
Extend
LoadStoreVectorizerto combine eligible adjacent scalar atomic loads and stores into elementwise vector atomic operations.