-
Notifications
You must be signed in to change notification settings - Fork 17.9k
[AMDGPU] Add ptr.s.buffer.load intrinsic, use it from Clang #209243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1485,6 +1485,33 @@ def int_amdgcn_s_buffer_load : DefaultAttrsIntrinsic < | |
| [IntrNoMem, ImmArg<ArgIndex<2>>]>, | ||
| AMDGPURsrcIntrinsic<0>; | ||
|
|
||
| // Generate an s_buffer_load instruction, falling back to buffer_load if the offset | ||
| // is not uniform. This operates like the s.buffer.load intrinsic, but uses a buffer | ||
| // resource pointer (`ptr addrspace(8)`) to hold the resource (which should be | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the "should" here a requirement, or a suggestion? |
||
| // wave-uniform) instead of a `<4 x i32>`. Note that some architectures require | ||
| // the offset to be aligned to the width of the type being loaded (or, in some cases | ||
| // the shorter of that alignment and 4 bytes). | ||
| // | ||
| // Also note that this data is loaded via a distinct cache than typical load | ||
| // instructions or other buffer_load operations. Therefore, the data pointed to by | ||
| // this load *must* be invariant to avoid coherence risks. Frontends emitting | ||
| // this intrinsic *should* mark it `!invariant.load`, and the backend may also | ||
| // add such markings. | ||
| def int_amdgcn_ptr_s_buffer_load : DefaultAttrsIntrinsic < | ||
| [llvm_any_ty], | ||
| [AMDGPUBufferRsrcTy, // rsrc(SGPR) | ||
| llvm_i32_ty, // byte offset | ||
| llvm_i32_ty], // auxiliary/cachepolicy(imm): | ||
| // bit 0 = glc, bit 1 = slc, bit 2 = dlc (gfx10/gfx11), | ||
| // bit 3 = swz, bit 4 = scc (gfx90a) | ||
| // gfx942: bit 0 = sc0, bit 1 = nt, bit 3 = swz, bit 4 = sc1 | ||
| // gfx12+: bits [0-2] = th, bits [3-4] = scope, | ||
| // bit 6 = swz | ||
| // Note: volatile bit is **not** permitted here. | ||
| [IntrArgMemOnly, IntrReadMem, ReadOnly<ArgIndex<0>>, | ||
| NoCapture<ArgIndex<0>>, ImmArg<ArgIndex<2>>], "", [SDNPMemOperand]>, | ||
| AMDGPURsrcIntrinsic<0>; | ||
|
|
||
| // Buffer intrinsics with separate raw and struct variants. The raw | ||
| // variant never has an index. The struct variant always has an index, even if | ||
| // it is const 0. A struct intrinsic with constant 0 index is different to the | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -644,19 +644,16 @@ bool RegBankLegalizeHelper::lowerSBufToBuf(MachineInstr &MI, | |
| NumLoads = LoadSize / 128; | ||
| Ty = Ty.divide(NumLoads); | ||
| } | ||
| for (int i = 0; i < NumLoads; ++i) | ||
| LoadParts.emplace_back(MRI.createVirtualRegister({VgprRB, Ty})); | ||
| MachineMemOperand *OrigMMO = *MI.memoperands_begin(); | ||
| for (int I = 0; I < NumLoads; ++I) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a side-effect here that makes moving this snippet meaningful? |
||
| LoadParts.emplace_back(MRI.createVirtualRegister({VgprRB, Ty})); | ||
| const Align Alignment = OrigMMO->getAlign(); | ||
| MachineFunction &MF = B.getMF(); | ||
| Register SOffset; | ||
| Register VOffset; | ||
| int64_t ImmOffset = 0; | ||
| unsigned MMOOffset = setBufferOffsets(B, MI.getOperand(2).getReg(), VOffset, | ||
| SOffset, ImmOffset, Alignment); | ||
| // Use the MMO size from the original instruction rather than the (possibly | ||
| // widened) register type. E.g. 96-bit loads are widened to 128-bit during | ||
| // legalization but the MMO still reflects the original 96-bit access size. | ||
| const unsigned MemSize = divideCeil(OrigMMO->getSize().getValue(), NumLoads); | ||
| MachineMemOperand *BaseMMO = MF.getMachineMemOperand(OrigMMO, 0, MemSize); | ||
| if (MMOOffset != 0) | ||
|
|
@@ -665,6 +662,7 @@ bool RegBankLegalizeHelper::lowerSBufToBuf(MachineInstr &MI, | |
| // instead. We can assume that the buffer is unswizzled. | ||
| Register RSrc = MI.getOperand(1).getReg(); | ||
| Register VIndex = B.buildConstant(VgprRB_I32, 0).getReg(0); | ||
| unsigned CachePolicy = MI.getOperand(3).getImm(); | ||
| unsigned Opc = AMDGPU::G_AMDGPU_BUFFER_LOAD; | ||
| switch (MI.getOpcode()) { | ||
| case AMDGPU::G_AMDGPU_S_BUFFER_LOAD_SBYTE: | ||
|
|
@@ -682,17 +680,17 @@ bool RegBankLegalizeHelper::lowerSBufToBuf(MachineInstr &MI, | |
| default: | ||
| break; | ||
| } | ||
| for (int i = 0; i < NumLoads; ++i) { | ||
| for (int I = 0; I < NumLoads; ++I) { | ||
| B.buildInstr(Opc) | ||
| .addDef(LoadParts[i]) // vdata | ||
| .addDef(LoadParts[I]) // vdata | ||
| .addUse(RSrc) // rsrc | ||
| .addUse(VIndex) // vindex | ||
| .addUse(VOffset) // voffset | ||
| .addUse(SOffset) // soffset | ||
| .addImm(ImmOffset + 16 * i) // offset(imm) | ||
| .addImm(0) // cachepolicy, swizzled buffer(imm) | ||
| .addImm(ImmOffset + 16 * I) // offset(imm) | ||
| .addImm(CachePolicy) // cachepolicy, swizzled buffer(imm) | ||
| .addImm(0) // idxen(imm) | ||
| .addMemOperand(MF.getMachineMemOperand(BaseMMO, 16 * i, MemSize)); | ||
| .addMemOperand(MF.getMachineMemOperand(BaseMMO, 16 * I, MemSize)); | ||
| } | ||
| if (NumLoads == 1) | ||
| B.buildCopy(Dst, LoadParts[0]); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does int_amdgcn_s_buffer_load do the other way around, aka generate buffer_load by default but optimize to s_buffer_load?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the scalar cache isn't coherent with vector, it's difficult to migrate that direction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think s_buffer_load is documented weird, given that it's got those "no memory" semantics and all.
Both of these are an s_buffer_load when all the arguments are uniform and a buffer_load otherwise, I just wrote the docs on this one as "you're explicitly requesting a uniform load".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which is why (though this was underdocumented for the "old form") you must only s_buffer_load invariant things/constants, therefore preventing the coherence issue from coming up in any realistic workload
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably update the old intrinsic wording too, then?
Alternatively it seems like we can define one in terms of the other. I.e. keep your now wording, and update the comment for the old intrinsic to be "an int_amdgcn_ptr_s_buffer_load except (describe the difference)..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'll swap the old wording to point to the new one