|
| 1 | +* Proposal: [0011](0011-resource-element-type-validation.md) |
| 2 | +* Author(s): [Joshua Batista](https://github.com/bob80905) |
| 3 | +* Sponsor: Joshua Batista |
| 4 | +* Status: **Under Consideration** |
| 5 | +* Impacted Project(s): (LLVM) |
| 6 | +* Issues: [#75676](https://github.com/llvm/llvm-project/issues/75676) |
| 7 | + |
| 8 | +## Introduction |
| 9 | +Resources are often used in HLSL, with various resource element types. |
| 10 | + |
| 11 | +For example: |
| 12 | +``` |
| 13 | +RWBuffer<float> rwbuf: register(u0); |
| 14 | +``` |
| 15 | +In this code, the element type is `float`, and the resource type is `RWBuffer`. |
| 16 | +`RWBuffer`, along with some other buffers and textures, fall under the "typed buffer" |
| 17 | +category. Other buffer resources are categorized as "raw buffers". |
| 18 | +Below is a description of resources that are considered "typed buffers" vs "raw buffers". |
| 19 | +* typed buffers |
| 20 | + * [RW|RasterizerOrdered]Buffer |
| 21 | + * [Feedback]Texture* |
| 22 | +* raw buffers |
| 23 | + * [Append|Consume|RW|RasterizerOrdered]StructuredBuffer |
| 24 | + * [RW]ByteAddressBuffer |
| 25 | + |
| 26 | +There is a distinct set of rules that define valid element types for typed buffer resources |
| 27 | +and valid element types for `*StructuredBuffer` resources and `*ByteAddressBuffer` Load/Store operations. |
| 28 | + |
| 29 | +Element types for typed buffer resources: |
| 30 | +* Are not intangible (e.g., isn't a resource type) |
| 31 | +* Must be vectors or scalars of arithmetic types, not bools nor enums nor arrays |
| 32 | +* Should be a scalar or homogenous vector of a floating-point or integer type, with a maximum of 4 components after translating 64-bit components into pairs of uint32_t components |
| 33 | +Element types for raw buffer resources: |
| 34 | +* Are not intangible (e.g., isn't a resource type) |
| 35 | + |
| 36 | +If someone writes `RWBuffer<MyCustomType>` and MyCustomType is not a valid element type, |
| 37 | +there should be infrastructure to reject this element type and emit a message explaining |
| 38 | +why it was rejected as an element type. |
| 39 | + |
| 40 | +## Motivation |
| 41 | +Currently, there is an allow list of valid element types where element types not on the list |
| 42 | +are rejected. It must be modified with respect to this spec. The allow list isn't |
| 43 | +broad enough, because user-defined types aren't allowed for raw buffer resources. |
| 44 | +Ideally, a user should be able to determine exactly how any user-defined type is invalid |
| 45 | +as an element type. Some system should be in place to more completely enforce the rules for |
| 46 | +valid and invalid element types, as well as provide useful information on why they are invalid. |
| 47 | + |
| 48 | +For example, `RWBuffer<double4> b : register(u4);` will emit an error in DXC, but will not in |
| 49 | +clang-dxc, despite the fact that `double4` is an invalid element type for typed buffers. |
| 50 | + |
| 51 | +## Proposed solution |
| 52 | + |
| 53 | +The proposed solution is to modify the declaration of each resource declared in |
| 54 | +`clang\lib\Sema\HLSLExternalSemaSource.cpp` and insert into each representative |
| 55 | +AST node a concept. The AST node will be created as if the C++20 `concept` keyword |
| 56 | +was parsed and applied to the declaration. The concept will be used to validate the |
| 57 | +given element type, and will emit errors when the given element type is invalid. |
| 58 | +Although concepts are not currently supported in HLSL, we expect support to be |
| 59 | +added at some point in the future. Meanwhile, because LLVM does support concepts, |
| 60 | +we can make use of them when constructing the AST in Sema. |
| 61 | + |
| 62 | +Two builtins will be used to validate typed buffer element types. Any resource |
| 63 | +element type may not be intangible, so the negation of `__builtin_hlsl_is_intangible` |
| 64 | +will be used for both typed and raw buffer resources. |
| 65 | +A new built-in, `__builtin_hlsl_typed_resource_element_compatible`, will be added in order |
| 66 | +to fully express the typed buffer constraint. This builtin will be placed within a |
| 67 | +concept constraint expression that is added to each AST node representing a typed |
| 68 | +buffer resource. The builtin is described below. Standard clang diagnostics for |
| 69 | +unsatisfied constraints will be used to report any invalid element types. Until |
| 70 | +concepts are formally supported by HLSL, the concepts and constraints will be |
| 71 | +expressed only in the AST via the HLSL external sema source. |
| 72 | + |
| 73 | +## Detailed design |
| 74 | + |
| 75 | +In `clang\lib\Sema\HLSLExternalSemaSource.cpp`, `RWBuffer` is defined, along with |
| 76 | +`RasterizerOrderedBuffer` and `StructuredBuffer`. It is at this point that the |
| 77 | +concept would be incorporated into these resource declarations. A concept representing |
| 78 | +the relevant constraints will be applied to each resource declaration. If a concept |
| 79 | +is not true for the given element type, a corresponding error message will be emitted. |
| 80 | + |
| 81 | +The list of builtins to be used as type traits that will be available for |
| 82 | +concept definition are described below: |
| 83 | +| type trait | Description| |
| 84 | +|-|-| |
| 85 | +| `!__builtin_hlsl_is_intangible ` | An element type should be an arithmetic type, bool, enum, or a vector or matrix or UDT containing such types. This is equivalent to validating that the element type is not intangible. This will error when given an incomplete type. | |
| 86 | +| `__builtin_hlsl_typed_resource_element_compatible ` | A typed buffer element type should never have two different subelement types. Compatible typed buffer element types require at most 4 elements, and a total size of at most 16 bytes. The builtin will also disallow the element type if any of its constituent types are enums or bools. | |
| 87 | + |
| 88 | +For typed buffers, `__builtin_hlsl_typed_resource_element_compatible` and |
| 89 | +`!__builtin_hlsl_is_intangible` needs to be true, while `!__builtin_hlsl_is_intangible` is all |
| 90 | +that's needed to validate element types for raw buffers. |
| 91 | + |
| 92 | +### Examples of Element Type validation results: |
| 93 | +``` |
| 94 | +struct oneInt { |
| 95 | + int i; |
| 96 | +}; |
| 97 | +
|
| 98 | +struct twoInt { |
| 99 | + int aa; |
| 100 | + int ab; |
| 101 | +}; |
| 102 | +
|
| 103 | +struct threeInts { |
| 104 | + oneInt o; |
| 105 | + twoInt t; |
| 106 | +}; |
| 107 | +
|
| 108 | +struct oneFloat { |
| 109 | + float f; |
| 110 | +}; |
| 111 | +struct notComplete; |
| 112 | +struct depthDiff { |
| 113 | + int i; |
| 114 | + oneInt o; |
| 115 | + oneFloat f; |
| 116 | +}; |
| 117 | +
|
| 118 | +struct notHomogenous{ |
| 119 | + int i; |
| 120 | + float f; |
| 121 | +}; |
| 122 | +
|
| 123 | +struct EightElements { |
| 124 | + twoInt x[2]; |
| 125 | + twoInt y[2]; |
| 126 | +}; |
| 127 | +
|
| 128 | +struct EightHalves { |
| 129 | +half x[8]; |
| 130 | +}; |
| 131 | +
|
| 132 | +struct intVec { |
| 133 | + int2 i; |
| 134 | +}; |
| 135 | +
|
| 136 | +struct oneIntWithVec { |
| 137 | + int i; |
| 138 | + oneInt i2; |
| 139 | + int2 i3; |
| 140 | +}; |
| 141 | +
|
| 142 | +struct weirdStruct { |
| 143 | + int i; |
| 144 | + intVec iv; |
| 145 | +}; |
| 146 | +
|
| 147 | +RWBuffer<double2> r0; // valid - element type fits in 4 32-bit quantities |
| 148 | +RWBuffer<int> r1; // valid |
| 149 | +RWBuffer<float> r2; // valid |
| 150 | +RWBuffer<float4> r3; // valid |
| 151 | +RWBuffer<notComplete> r4; // invalid - the element type isn't complete, the definition is missing. |
| 152 | +// the type trait that would catch this is the negation of `__builtin_hlsl_is_intangible` |
| 153 | +RWBuffer<oneInt> r5; // valid - all leaf types are valid primitive types, and homogenous |
| 154 | +RWBuffer<oneFloat> r6; // valid |
| 155 | +RWBuffer<twoInt> r7; // valid |
| 156 | +RWBuffer<threeInts> r8; // valid |
| 157 | +RWBuffer<notHomogenous> r9; // invalid, all template type components must have the same type, DXC fails |
| 158 | +StructuredBuffer<notHomogenous> r9Structured; // valid |
| 159 | +RWBuffer<depthDiff> r10; // invalid, all template type components must have the same type, DXC fails |
| 160 | +RWBuffer<EightElements> r11; // invalid, > 4 elements and > 16 bytes, DXC fails |
| 161 | +// This would be caught by __builtin_hlsl_typed_resource_element_compatible |
| 162 | +StructuredBuffer<EightElements> r9Structured; // valid |
| 163 | +RWBuffer<EightHalves> r12; // invalid, > 4 elements, DXC fails |
| 164 | +// This would be caught by __builtin_hlsl_typed_resource_element_compatible |
| 165 | +StructuredBuffer<EightHalves> r12Structured; // valid |
| 166 | +RWBuffer<oneIntWithVec> r13; // valid |
| 167 | +RWBuffer<weirdStruct> r14; // valid |
| 168 | +RWBuffer<RWBuffer<int> > r15; // invalid - the element type has a handle with unknown size, |
| 169 | +// thus it is an intangible element type. The type trait that would catch this is the negation of `__builtin_hlsl_is_intangible` |
| 170 | +``` |
| 171 | + |
| 172 | +Below is a sample C++ implementation of the `RWBuffer` resource type. |
| 173 | +This code would exist within an hlsl header, but concepts are not implemented in HLSL. Instead, the AST node |
| 174 | +associated with RWBuffers is constructed as if this code was read and parsed by the compiler. |
| 175 | +``` |
| 176 | +#include <type_traits> |
| 177 | +
|
| 178 | +namespace hlsl { |
| 179 | +
|
| 180 | +template<typename T> |
| 181 | +concept is_typed_resource_element_compatible = |
| 182 | + __builtin_hlsl_typed_resource_element_compatible(T); |
| 183 | +
|
| 184 | +template<typename element_type> requires !__builtin_hlsl_is_intangible(element_type) && is_typed_resource_element_compatible<element_type> |
| 185 | +struct RWBuffer { |
| 186 | + element_type Val; |
| 187 | +}; |
| 188 | +
|
| 189 | +// doesn't need __builtin_hlsl_typed_resource_element_compatible, because this is a raw buffer |
| 190 | +// also, raw buffers allow bools and enums as constituent types |
| 191 | +template<typename T> requires !__builtin_hlsl_is_intangible(T) |
| 192 | +struct StructuredBuffer { |
| 193 | + T Val; |
| 194 | +}; |
| 195 | +} |
| 196 | +
|
| 197 | +``` |
| 198 | + |
| 199 | +## Alternatives considered (Optional) |
| 200 | +We could instead implement a diagnostic function that checks each of these conceptual constraints in |
| 201 | +one place, either in Sema or CodeGen, but this would prevent us from defining a single header where |
| 202 | +all resource information is localized. |
| 203 | + |
| 204 | +Another alternative considered was creating a builtin called `__builtin_hlsl_is_valid_resource_element_type`, to |
| 205 | +check all possible valid resource element types, rather than just checking that the element type is not intangible. |
| 206 | +This is unneeded because all primitive non-intangible types are valid element types. |
| 207 | + |
| 208 | +## Acknowledgments (Optional) |
| 209 | +* Damyan Pepper |
| 210 | +* Chris Bieneman |
| 211 | +* Greg Roth |
| 212 | +* Sarah Spall |
| 213 | +* Tex Riddell |
| 214 | +* Justin Bogner |
| 215 | +<!-- {% endraw %} --> |
0 commit comments