Skip to content
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

The issue with pointers in SPIR-V #15230

Open
Snektron opened this issue Apr 10, 2023 · 1 comment
Open

The issue with pointers in SPIR-V #15230

Snektron opened this issue Apr 10, 2023 · 1 comment
Labels
arch-spirv SPIR-V architecture
Milestone

Comments

@Snektron
Copy link
Collaborator

Snektron commented Apr 10, 2023

SPIR-V has two main types of execution models: Kernels and shaders. Kernels are intended for OpenCL-ingestion with clCreateProgramWithIL. Shaders, which includes the execution models Vertex, Fragment, and GLCompute (Vulkan/OpenGL compute shaders) are intended for use with Vulkan (and OpenGL). Each execution model in SPIR-V comes with different rules, but the most notable changes are between Kernels and non-Kernels.

One of the main differences is that non-kernels do, by default, not allow (useful) pointers. Pointers may be declared and used, however, they must always be resolvable to a static location. This is a consequence of the Logical addressing model (see OpMemoryModel), which is only address models that shaders are allowed to use without extensions (more on this later). Quoting SPIR-V spec; 2.18 "Memory Model":

The Logical addressing model means pointers are abstract, having no physical size or numeric value. In this mode, pointers must be created only from existing objects, and they must not be stored into an object, unless additional capabilities, e.g., VariablePointers, are declared to add such functionality.

This also means no pointers casting, no pointer arithmetic, etc.

This is quite a limitation for a general-purpose language like Zig, where the use of pointers is pretty normal. For this reason, the SPIR-V backend currently focusses on OpenCL, where all these operations are allowed.

Another problem related to this is is that shaders do not support "generic" pointers: All pointers must be ascribed with the real storage class that they are placed in, and the equivalent of @addrSpaceCast is not allowed. I think that it would be best if there are no surprises regarding the result of taking the address of variables that don't have an explicit address space: these should all result in a generic pointer, because too much existing code (mainly hinting at support code in the standard library etc) would break otherwise. Related: #15232.

In order for Zig to support shaders for Vulkan or OpenGL, there needs to be some way to work with or around these limitations. I can see a few ways forward.

Forbidding "dynamic pointers" pointers at runtime

This option consists of pulling in the SPIR-V limitations into Zig semantics: During semantic analysis, Zig would check whether we are "confusing" the origin location of any pointers, and emit an error if so. One positive aspect of this is that it may integrate well with some comptime processing for the other backends (for example, comptime memory islands?).

If code that does not use dynamic pointers can be made to adhere to the SPIR-V semantics, I think that this solution is not so bad as it may sound. Basically "just don't use pointers".

One thing that should be noted here is that any comptime processing should not interfere with these dynamic pointers at runtime. The goal here is to prevent as little existing Zig code to break as possible.

VK_KHR_buffer_device_address

This Vulkan extension (and corresponding SPIR-V extension) allows emitting some pointer operations, specifically those which affect pointers placed in Physical Storage Buffers. Pointer arithmetic, casting, etc, are all allowed for these pointers. Note that this only partially mitigates the problem, as for example function locals, global variables, and (instance-) private variables will not be affected by this. This would essentially be an extension to the above.

Lifting pointers during code generation

Most pointer operations can be partially lifted out, and we can essentially pretend that these operations are supported while they are implemented using integer operations instead. The original object that a pointer points to can be found by inlining functions. This would be a little bit impractical to the size of generated code (though can be partially mitigated by monomorphizing), but most GPU code is inlined anyway and so should in principle have little effect on the final shader binary. I believe that this is the approach that clspv takes. This would still have its limitations, of course: This type of processing can cause combinatoric explosion, and does not handle all situations. It also means applying for example the following transformations:

var a: i32 = undefined;
var b: i32 = undefined;
var c: *i32 = if (dynamic) &a else &b;
c.* = 123;

to

var a: i32 = undefined;
var b: i32 = undefined;
if (dynamic) { a = 123; } else { b = 123; }

Needless to say this solution is relatively complicated to implement.

@andrewrk andrewrk added the arch-spirv SPIR-V architecture label Apr 10, 2023
@andrewrk
Copy link
Member

I suggest to start moving forward with

Forbidding "dynamic pointers" pointers at runtime

@andrewrk andrewrk added this to the 0.12.0 milestone Apr 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch-spirv SPIR-V architecture
Projects
None yet
Development

No branches or pull requests

2 participants