[RFC][SPIR-V] Allow architectures with no set pointer size?

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 the Float16 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.

Hi Nathan! Thanks for starting this thread! Even though we may not have much traction here, I believe there might be other targets/architectures in the future with a similar dilemma.

I believe going with the “easy, but weird way” is not as weird as it may sound at first. The cost of handling the details in the backend should be much smaller than trying to implement new logic for defining target triples and scheme for datalayout.

I accepted your patch on Phabricator.