Replies: 2 comments 3 replies
-
If it's any help, the "inout" behavior in DXC causes a lot of troubles with intersection and anyhit programs. If I call the intrinsic to accept the hit and terminate traversal, I've ran into the issue where I've passed ray payload registers into a function as inout, expecting their values to be written as reference. If you only copy the values back to inout parameters at the end of the function, then intrinsics like "accept hit and terminate traversal" will effectively terminate execution in the middle of certain function calls, and the inout parameters act as if they were only "in". This has caused some bugs with devs working with HLSL and RT hardware for quite a while now, and so HLSL is now somewhat moving towards a true "by reference" syntax. The current proposal for HLSL "by reference" syntax is here: https://github.com/microsoft/hlsl-specs/blob/main/proposals/0006-reference-types.md The DXC devs are aware of this, here's the issue on their GH, microsoft/DirectXShaderCompiler#5158, but closed it without a fix, just assuming that this proposal would go through (and it's been several years since then...) Here's the reported bug caused by conflicts between inout and the built in RT intrinsics for reference: https://forums.developer.nvidia.com/t/anyhit-payload-lost-when-calling-accepthitandendsearch-or-ignorehit/242880 |
Beta Was this translation helpful? Give feedback.
-
Hi everyone, I'm encountering an issue while using the following code with a CPU compilation target:
According to the documentation:
My intent was to use the
To resolve the warning, I modified the function as follows:
I understand that constref was introduced as an input-only ref. Is this the correct approach to eliminate the warning and ensure the function behaves as intended? Or is there a better way to handle this situation? Thanks in advance for your help! |
Beta Was this translation helpful? Give feedback.
-
(Original slack thread by Tess)
The basic HLSL/Cg/GLSL setup is to have:
in (the default)
out
in out / inout
They historically had copy-in/copy-out behavior, but then HLSL is kind of getting slopping and giving inout by-reference behavior in some cases.
There's a few ways where this set of directions and those semantics are insufficient:
They can't easily handle non-copyable types (which require things akin to Rust-like borrows)
The mandatory copying and by-value passing is inefficient for many targets (seemingly not just C++/CPU targets, but also SPIR-V?)
Some operations have (or require) genuine by-reference passing (e.g., atomics)
We added ref (well, __ref) as a direction modifier to handle the last case, where ref is currently a by-reference analog to inout.
And we also made our semantics for inout more subtle to address the second case, by saying that it is "either by-reference or by-value, at the discretion of the compiler" and that any case where the user wrote code that would depend on the difference, they are in error.
(And then we subsequently added constref direction, with the implication that it is like an input-only ref, but with the actual semantics being closer to our modified inout semantics of "maybe by-reference and maybe not")
It's relevant for all of this stuff to think of the l-values we pass as arguments to these parameters as "abstract storage locations" with examples including:
A global or local variable
this
A field of an abstract storage location of struct type
A property of an abstract storage location with properties
An element of an abstract storage location with array elements
Matrix rows, vector elements, etc.
A swizzle of a vector-type abstract storage location, so long as no element appears more than once in the swizzle (basically just a property... except not always)
The property case is kind of the most complicated (and therefore interesting) because it might be implemented with distinct get and set accessors, so that any read-modify-write necessarily involves copy-in and copy-out.
(There are details about how to support properties together with non-copyable types, but that's even more to lump in here)
The key point is that when an abstract storage location is used as part of an expression, we can think of that as an "access" operation that has a defined beginning and ending point in the execution flow of a single thread.
(Multithreading is also out of scope of what I'm trying to say here...)
An access can be a read, a write, or a read-modify-write, depending on how the l-value is used.
In the case of an abstract storage location backed by a property:
A read access starts when we begin invoking the get accessor, and ends when that accessor returns
A write access starts when we begin invoking the set accessor, and ends when that accessor returns
A read-modify-write access starts when we begin invoking the get accessor, and ends when the set accessor returns.
If two accesses to the same abstract storage location overlap in time, then either:
The two accesses are both reads, and everything is cool
The program is invalid, and UB may result
(edited)
The above rule is what allows us to say that inout can either be by-value or by-reference. Any program that could observe the difference is invalid by our rules.
(And its easy to argue that no useful program needs to break the given rule)
Okay, with the background out of the way, I propose the following as the (hopefully) complete set of parameter directions we want to support.
Guaranteed by-reference semantics:
in ref - a guaranteed by-reference immutable borrow. A Slang in ref T would translate to a C T const*.
inout ref / in out ref / ref a guaranteed by-reference mutable borrow. A Slang inout ref T would translate to a C T*
Guaranteed by-value semantics:
in / default - may be implemented as pass-by-reference or pass-by-value at ABI level, but the compiler (not the programmer) takes responsibility for ruling out overlapping accesses and inserting copies as needed
By-value or by-reference at compiler's discretion:
borrow - semantically an immutable borrow
inout / in out - semantically a mutable borrow, but compiler may insert copy-in-copy-out in cases where there is a property/swizzle/etc.
The by-reference/-value distinction doesn't really matter:
out - effectively just sugar for additional function results
consume - for non-copyable types this is is the equivalent of a Rust function parameter that has been moved into. For copyable types this is semantically equivalent to in.
I should ammend my earlier comment about accesses and their durations to note that when a value is being passed as an argument to a parameter that uses an immutable borrow (in ref, or borrow) the duration of the access to that abstract storage location lasts until the callee function returns (to account for the fact that the callee might have received a pointer to the storage location).
Beta Was this translation helpful? Give feedback.
All reactions