Hello!
Context: ⚙ D155978 [SPIRV] Add SPIR-V logical triple.
LLVM has a spirv32
and spirv64
backend.
Those 2 SPIR-V flavors have either a 32-bit addressing, or 64-bit addressing.
Our current goal is to add the logical addressing SPIR-V, so this backend can support graphic shaders for Vulkan.
The issue is: Logical addressing SPIR-V has no pointer size, but as far as I know the targets are either 16, 32 or 64bit.
https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_memory_model
Proposed solution
In the linked review, I cheated a bit. I’m reporting “32bit pointers”, knowing it is wrong.
I believe this is OK for a few reasons:
- Logical SPIR-V forbids pointer arithmetic
- Logical SPIR-V forbids int → pointer cast.
- SPIR-V SSA IDs are 32bit.
But being really new to LLVM, I’d be happy to see if somebody has a better idea?
For the ones unfamiliar with SPIR-V, here are some additional context around Logical SPIR-V and SPIR-V in general:
how you’d do “pointer arithmetic” in Logical SPIR-V:
Given this C-like code:
uint64_t array[10];
uint64_t value = array[5]
The generated SPIR-V would look a bit like this:
# Defines the type for an Integer, 32bit size, 0 means unsigned
type_uint64_t = OpTypeInt 32 0
# Defines the type for an array of 10 uint64_t.
type_array_uint64_t = OpTypeArray %type_uint64_t 10
# Defines a variable of type uint64_t[10]
ptr_array = OpVariable %type_array_uint64_t
# Similar to x86's LEA, computes ptr + sizeof(*ptr) * 10 (including padding, etc if needed).
pointer = OpAccessChain %ptr_array 10
# Deref %pointer assuming it points to an uint64_t.
value = OpLoad %uint64_t %pointer
The whole “pointer arithmetic” thing is handled by an instruction: OpAccessChain
.
This instruction handles arrays, but also structs:
struct MyStruct {
int field1;
int field2;
}
MyStruct array[10];
value = array[5].field2;
# loads the element at index 5 the array, then the element at index 1 in the result (field2).
OpAccessChain %ptr_on_array 5 1
One thing you might wonder: what is the size of the IDs OpAccessChain
can handle?
Answer: anything, from 8
to 64
, depending on the capabilities
. (Maybe 128
bits in the future even?).
And the “issue” is: SPIR-V Logical integer size is not bounded by the ISA itself, but by what’s called capabilities.
What are capabilities
When a SPIR-V needs to use any instruction, operand, it requires capabilities
. It’s a way for the driver to quickly determine if they can run the shader.
Example:
- you need to have
float16
types? You declare theFloat16
capability. - You need atomic operations on 64bit integers, you need to declare the
Int64Atomics
capability.
The issue is: capabilities are not part of the triple. Capabilities are something we derive from the code when generating it. So if we see an integer that can only fit in a 64bit integer, then we declare “this module needs 64bit integers”.
Right now, I don’t think we have any 128-bit integer capabilities. But this is not impossible.
With that in mind, I don’t think we could rely on the maximum index in the OpAccessChain instruction to determine a pointer size.
So I believe we have 2 solution:
The clean, but costly way:
Allow targets to forbid pointer arithmetic, and declare pointer with no size.
This would mean allow the datalayout not to specify a pointer size.
I’m not sure if that’s something desirable?
The easy, but weird way:
Return 32bit (or 64bit, I’m not attached to any of the two), and handle those details in the SPIR-V backend.