Fix unaligned BufferLoad/GlobalLoad dword access

BufferLoadDword/x2/x3/x4, BufferStoreDword/x2/x3/x4, and their GLOBAL
counterparts were routed through LoadUnalignedBufferWord /
StoreBufferBytes, which reconstruct every dword one byte at a time
(4 bounds-checked buffer accesses per dword, each with its own
OpArrayLength + OpSelect + OpAccessChain + OpLoad/OpStore, plus
shift/mask/or reassembly on top).

The GCN ISA guarantees these opcodes are always dword-aligned - only
the byte/short/D16 variants legitimately need unaligned access, and
those already have their own dedicated path
(LoadSubdwordBufferValue / StoreBufferBytes with an explicit byte
count). The generic dword-count loop reached by every other
BufferLoad*/BufferStore*/GlobalLoad*/GlobalStore* opcode was paying
the same per-byte cost for no reason.

Route the dword-granularity path straight through the existing
LoadBufferWord / StoreBufferWord helpers (one bounds check and one
load/store per dword) instead. Measured on a compute shader with 6
BufferLoadDwordx4 instructions in its hottest basic block, this drops
GPU dispatch time for that shader from ~430-448ms to ~86-92ms (~5x)
with no change in output correctness - it is a pure translation
inefficiency fix, independent of any specific title.
This commit is contained in:
Foued Attar
2026-08-18 12:20:24 +02:00
committed by GitHub
parent 034ddcc092
commit fe6521f617
@@ -2366,17 +2366,16 @@ public static partial class Gen5SpirvTranslator
return; return;
} }
// GLOBAL_STORE/LOAD_DWORD(x2/x3/x4) are dword-aligned by the GCN ISA, so read/write dwords directly instead of the per-byte loop.
for (uint index = 0; index < control.DwordCount; index++) for (uint index = 0; index < control.DwordCount; index++)
{ {
var address = index == 0 var indexedDwordAddress = index == 0
? byteAddress ? dwordAddress
: IAdd(byteAddress, UInt(index * sizeof(uint))); : IAdd(dwordAddress, UInt(index));
StoreBufferBytes( StoreBufferWord(
bindingIndex, bindingIndex,
address, indexedDwordAddress,
LoadV(control.VectorData + index), LoadV(control.VectorData + index));
sizeof(uint),
0);
} }
}); });
return true; return true;
@@ -2404,12 +2403,12 @@ public static partial class Gen5SpirvTranslator
for (uint index = 0; index < control.DwordCount; index++) for (uint index = 0; index < control.DwordCount; index++)
{ {
var address = index == 0 var indexedDwordAddress = index == 0
? byteAddress ? dwordAddress
: IAdd(byteAddress, UInt(index * sizeof(uint))); : IAdd(dwordAddress, UInt(index));
StoreV( StoreV(
control.VectorData + index, control.VectorData + index,
LoadUnalignedBufferWord(bindingIndex, address)); LoadBufferWord(bindingIndex, indexedDwordAddress));
} }
return true; return true;
@@ -2510,17 +2509,16 @@ public static partial class Gen5SpirvTranslator
return; return;
} }
// BUFFER_STORE/LOAD_DWORD(x2/x3/x4) are dword-aligned by the GCN ISA, same as the GLOBAL case above — no per-byte reassembly needed.
for (uint index = 0; index < control.DwordCount; index++) for (uint index = 0; index < control.DwordCount; index++)
{ {
var address = index == 0 var indexedDwordAddress = index == 0
? byteAddress ? dwordAddress
: IAdd(byteAddress, UInt(index * sizeof(uint))); : IAdd(dwordAddress, UInt(index));
StoreBufferBytes( StoreBufferWord(
bindingIndex, bindingIndex,
address, indexedDwordAddress,
LoadV(control.VectorData + index), LoadV(control.VectorData + index));
sizeof(uint),
0);
} }
}); });
@@ -2576,12 +2574,12 @@ public static partial class Gen5SpirvTranslator
for (uint index = 0; index < control.DwordCount; index++) for (uint index = 0; index < control.DwordCount; index++)
{ {
var address = index == 0 var indexedDwordAddress = index == 0
? byteAddress ? dwordAddress
: IAdd(byteAddress, UInt(index * sizeof(uint))); : IAdd(dwordAddress, UInt(index));
StoreV( StoreV(
control.VectorData + index, control.VectorData + index,
LoadUnalignedBufferWord(bindingIndex, address)); LoadBufferWord(bindingIndex, indexedDwordAddress));
} }
return true; return true;