Skip to content

#30 - Coherent Pointers & Pointer Access Proposal #24

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

Merged
merged 12 commits into from
Jul 9, 2025

Conversation

ArielG-NV
Copy link
Contributor

@ArielG-NV ArielG-NV commented Jun 12, 2025

Resolves: shader-slang/slang#7408

This proposal is for implementing (only for SPIR-V targets) coherent loads and stores through pointers. This proposal also implements ReadOnly (immutable) pointers & ReadWrite pointers.

A Slang playground example is provided here to test how MakePointerVisibleKHR and related operands function. This is not an implementation of the proposal.

public enum PointerFlags
{
None = 0,
Coherent = 0b1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be done in another proposal, but I expect PointerFlags to be extended to also cover Const.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would worry about overloading a single flags enum to handle too many different axes here. The following all seem, to me, to be reasonably orthogonal (and thus merit distinct generic arguments)

  • The type of the storage location being pointed to

  • The address space where the storage location resides

  • The access modes that are allowed to be performed through the pointer (read-only, write-only, or read-write)

  • The coherence scope for operations performed through the pointer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After asking Yong (separately from this PR), there was agreement on the design proposed.


### Support The `globallycoherent` Keyword

Slang will implement support for use of the `globallycoherent T*` keyword. This will be an alias to `Ptr<T, PointerFlags.Coherent, MemoryScope.Device>`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We likely will need some type aliases e.g. CoherentPtr<T, MemoryScope> so that the user does not need to specify the AddressSpace.UserPointer in their Ptr type signature.

Also need to handle the syntax of:

groupshared int * // a pointer to groupshared mem
int groupshared * // a same as above
coherent int groupshared* // a pointer to groupshared coherent mem
groupshared coherent int * // same as above
coherent groupshared int* // same as above
...
int * groupshared // a groupshared variable that is a global pointer
int * coherent groupshared * // a pointer to coherent groupshared mem
int * coherent * groupshared // a groupshared variable that is a pointer to coherent global mem
...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This syntax is complex enough that we may want to completely disallow it in the initial implementation with an error on things like globallycoherent int*, and then support it in a separate languauge proposal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CoherentPtr<T, MemoryScope> worries me a bit since down the line there may be situations where a pointer abides to 2 different memory scopes for whatever reason SPIR-V requires.

Maybe the eventual solution here is named generic parameters: Ptr<type=T, flags=PointerFlags.Coherent, scope=MemoryScope.Device> ptr;

Copy link
Contributor

@tangent-vector tangent-vector left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is important stuff to expose, but it also touches on a lot of big-picture topics in the language design that I wouldn't want us to bake too soon.

public enum PointerFlags
{
None = 0,
Coherent = 0b1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would worry about overloading a single flags enum to handle too many different axes here. The following all seem, to me, to be reasonably orthogonal (and thus merit distinct generic arguments)

  • The type of the storage location being pointed to

  • The address space where the storage location resides

  • The access modes that are allowed to be performed through the pointer (read-only, write-only, or read-write)

  • The coherence scope for operations performed through the pointer.

Coherent = 0b1
}

__generic<T, uint64_t addrSpace=AddressSpace::UserPointer, PointerFlags flags=PointerFlags.None, MemoryScope coherentMemoryScope=MemoryScope.Device>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to fold the PointerFlags.Coherent part together with the coherentMemoryScope part, because there's a strong relationship between the two:

  • It is meaningless to specify a scope without also specifying the Coherent flag, but this is allowed and means that code could look like it does something it doesn't. E.g., if they write Ptr<T, PointerFlags.None, MemoryScope.WorkGroup> that might look like there's something meaningful being communicated via the WorkGroup argument, but there really isn't.

  • A programmer really shouldn't be saying something is Coherent without taking a second to think about the scope of coherency they want.

I would propose that the simplest fix is to support a "scope" that represents operations only being guaranteed coherent within the current thread/work-item/lane/whatever-you-like-to-call-it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be implemented (per param, 1 unique enum) as per suggested in #24 (comment)

example (not final):

enum CoherentPtrFlags
{
    NotCoherent,
    CoherentToDevice,
    CoherentToWorkgroup,
    ...
}

Comment on lines 130 to 141
groupshared T* // a pointer to groupshared memory: Ptr<T, AddressSpace::GroupShared>
T groupshared* // a same as above

coherent T groupshared* // a pointer to groupshared coherent memory: Ptr<T, AddressSpace::GroupShared, PointerFlags.Coherent, MemoryScope::WorkGroup>
groupshared coherent T* // same as above
coherent groupshared T* // same as above
...
int* groupshared // a groupshared variable that is a global pointer: groupshared Ptr<T, MemoryScope::Device>
int* coherent groupshared* // a pointer to coherent groupshared memory: Ptr<T, AddressSpace::GroupShared, PointerFlags.Coherent, MemoryScope::WorkGroup>
int* coherent* groupshared // a groupshared variable that is a pointer to coherent global memory: groupshared Ptr<T, PointerFlags.Coherent, MemoryScope::Device>

globallycoherent T* // pointer which is coherent to global memory: groupshared Ptr<T, PointerFlags.Coherent, MemoryScope::Device>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly object to a lot of this, just because it adds more modifiers and more keyword soup to Slang. We should be very cautious about adding tons of keywords and especially about keywords that can be used to qualify pointers...

Copy link
Contributor Author

@ArielG-NV ArielG-NV Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally have a conflicting opinion of these sorts of things:

  • people moving from HLSL->Slang would want the soup of modifiers/keywords because it looks like HLSL
  • This mess of modifiers/keywords is antagonistic to writing good generic code & is confusing.
    • keywords promote not thinking about the non-keyword memory-scopes, this is the same problem we have with HLSL/GLSL/...
    • keywords promote not using generics with Ptr<> types since the underlying type is obfuscated
    • int* coherent groupshared* != int* coherent* groupshared is bound to confuse people due to the small and nuanced differences in meaning.

@csyonghe had originally proposed the addition here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final decision made: We are to remove this feature as a "next step". We should not have a stance on this currently.


```c#
RWStructuredBuffer<int> val; // Texture works as well.
Ptr<int, PointerFlags.Coherent, MemoryScope.Subgroup> p = &val[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line of code already opens up a line of questions that I think is really important for this proposal to address: what are the conversion rules for Ptr values under this proposal? Can a pointer with Coherent be converted to one without? Vice-versa? Can one with a narrow coherency scope be converted to one with a wider scope? Why or why not?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it more, the big question I have is: what are the circumstances where a function would use a Ptr type in its signature that has the Coherent flag set, and in those circumstances what is the functions signature actually communicating?

If pointers with/without coherency and with different scopes are inter-convertible, then putting any of that information in a function signature is effectively meaningless to the code that wants to call it. It would be one of those things like marking a function parameter const (not a pointer-to-const, but just a const parameter) where it is sitting there in the signature and thus gets exposed to callers, but in practice it is only significant to the implementer of a function, for the code they write in the function body.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this gets into the bigger question on whether or not we should encode coherency in the pointer type itself.

The convenience of encoding it as part of the type is such that it propagates through pointer access chains, and the load/store site is automatically obeying to coherent access rules.

The downside is it complicates type coercion in a way that doesn't provide any actual meaning regarding caller/callee contract. I can't think of a case where a caller needs to know if a pointer is being accessed in a coherent way or not.

Copy link
Contributor Author

@ArielG-NV ArielG-NV Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think allowing to freely cast is a give and take here (rather than just all bad as with const).

  • Users are likely to mix Coherent and Non-Coherent operations (expected behavior), hence casting freely will be useful. It is expected that a user will always need access to 2 variations of a CoherentPtr
  • This is a contrast to const since for all const variables, casting away const is always a bad idea and almost always a source of bugs when passing around as parameters.
  • Coherence is much more comparable to atomic operations, "do we want the types to be atomic? Do we want per operation atomics?". Both approaches are used.


### Additional Pointer Arguments

`Volatile` and `Const` are planned features for `Ptr`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Volatile when there is already coherent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Volatile is functionally different than Coherent: "This access cannot be eliminated, duplicated, or combined with other accesses.".

Therefore Volatile is a potential future addition (although I doubt high priority).


```c#
__generic<T, CoherentScope coherentScope>
typealias CoherentPtr = Ptr<T, AddressSpace::UserPointer, coherentScope>;
Copy link
Contributor

@csyonghe csyonghe Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be:

typealias CoherentPtr<T> = Ptr<T, AddressSpace::UserPointer, MemoryScope::Device>;

For anything else, the user should just use Ptr<> type.

Copy link
Contributor Author

@ArielG-NV ArielG-NV Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should move the AddressSpace::... param to the end of Ptr given this change (although this would make the change a breaking-change)?


### Support Casting Pointers With Different `CoherentScope`

We will allow pointers with different `CoherentPtr` to be explicitly castable to each other. For example, `CoherentPtr<int, CoherentScope.Device>` will be castable to `CoherentPtr<int, MemoryScope.Workgroup>`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... with different CoherentScope to be explicitly...

4. Support for coherent workgroup memory
5. Support for coherent cooperative matrix & cooperative vector
6. Support casting pointers with different coherent `MemoryScope`
7. Support the `globallycoherent` keyword
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably delete 7.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make globallycoherent T* an error.

Copy link
Contributor

@csyonghe csyonghe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, some minor comments.

@csyonghe
Copy link
Contributor

csyonghe commented Jul 1, 2025

I think we want access to appear before coherency scope, so people can do Ptr<int, AddressSpace.Device, Access.Read> to refer to non-coherent, immutable pointer. So in the implementation, we may just want to put in the access parameter even when we don't implement it at first.

@csyonghe
Copy link
Contributor

csyonghe commented Jul 1, 2025

Perhaps we should just extend this proposal to also introduce access.

@ArielG-NV ArielG-NV changed the title #30 - Coherent Pointers Proposal #30 - Coherent Pointers & Access Proposal Jul 1, 2025
@ArielG-NV ArielG-NV changed the title #30 - Coherent Pointers & Access Proposal #30 - Coherent Pointers & Pointer Access Proposal Jul 1, 2025
Subgroup = MemoryScope::Subgroup,
Invocation = MemoryScope::Invocation,
QueueFamily = MemoryScope::QueueFamily,
ShaderCallKHR = MemoryScope::ShaderCallKHR,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove "KHR".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed 👍

@csyonghe csyonghe merged commit 9bc0fc0 into shader-slang:main Jul 9, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[CBP] Create formal proposal for adding coherent operations through pointers
3 participants