@@ -358,10 +358,132 @@ part of the HLSL language.
358358
359359## Proposed solution
360360
361- TBD
361+ While there are many shaders that explicitly specify bindings for their
362+ resources, a shader without an explicit binding is not at all uncommon. We
363+ should try to match DXC implicit binding behavior in the most common cases
364+ and where it makes sense because debugging resource binding mismatches is not
365+ always straightforward.
366+
367+ ### Resources declared directly at global scope
368+ For resources and resource arrays that are declared directly at the global scope
369+ Clang should assign the bindings exactly as DXC does. This is the most common
370+ case and departing from it would be rather impactful breaking change.
371+
372+ That means assigning bindings in `space0` for resources that are identified as
373+ used (i.e. not optimized away) in the order the resources were declared. Arrays
374+ of resources will get the full range of descriptors reserved for them
375+ irrespective of which array elements are accessed, as long as at least of the
376+ array elements it used. Same rules apply if ther resource has a space-only
377+ resource binding, except the binding will be assigned from the specified space
378+ instead of the default `space0`.
379+
380+ ### Resources inside user-defined structures and classes
381+
382+ We should consider departure from DXC behavior when a resource or resource array
383+ is declared inside user-defined structure or class. DXC assigns these bindings
384+ based on the order in which the resource is used in the code, which is
385+ impractical and arguably unusable. Any change in the code that reorders the
386+ resource accesses can result in a different binding assignment, and that just
387+ seems wrong (see [Example 2.1](#example-21)).
388+
389+ Additionally, resource arrays inside struct should be treated as arrays and not
390+ as individual resources. The `createHandleFromBinding` calls associated with
391+ resource arrays should reflect the range of the array/descriptors, and the full
392+ range of descriptiors should be reserved, the same was it is reserved for
393+ resource arrays declared at global scope. DXC currently treats these as
394+ individual resources (see [Example 2.2](#example-22)).
395+
396+ ### In which part of the compiler should the implicit binding happen
397+
398+ Since the implicing binding assignments need to happen after unused resources
399+ are identified and removed from the code, it needs to happen later in the
400+ compiler after codegen and code optimizations.
401+
402+ If we were to attempt doing it in Sema, we would need to an extensive analysis
403+ to identify all of the resources that are not used, which includes those that
404+ are in unreachable code or internal functions not included in the final shader.
405+ It seems like such analysis would be duplicating the work the existing LLVM
406+ passes are already doing. And it is very likely we would not be able to identify
407+ all of the cases where a resource can be optimized away by LLVM optimization
408+ passes.
409+
410+ Having the implicit bindings assignments done later in the compiler is probably
411+ also going to be useful once we get to designing DXIL libraries. Unbound
412+ resources exist in library shaders and their bindings get assigned once a
413+ particular entry point is linked to a final shader.
362414
363415## Detailed design
364416
417+ ### Clang Frontend
418+
419+ Unbound resources will be initialized by a resource class constructor that takes
420+ 3 arguments - the `space`, `range` and `index`. This constructor will be
421+ declared in `HLSLExternalSemaSource` as part of resource class setup.
422+
423+ The arguments specify the virtual register space (defaulting to 0),
424+ required descriptor range and an index of the resource in the range.
425+
426+ The constructor will call Clang builtin function
427+ `__builtin_hlsl_create_handle_from_implicit_binding` and will pass along the
428+ `space`, `range` and `index` values, and it will also include the uninitialized
429+ resource handle. The type of the handle argument will be used to infer the
430+ specific resource handle type returned by the buildin function. This will happen
431+ in `SemaHLSL::CheckBuiltinFunctionCall` the same way we infer return types for
432+ HLSL intrinsic builtins based on the builtin arguments.
433+
434+ ```c++
435+ template <typename T> class RWBuffer<T> {
436+ RWBuffer(unsigned space, int range, unsigned index) {
437+ __handle = __builtin_hlsl_create_handle_from_implicit_binding(__handle, space, range, index);
438+ }
439+ }
440+ ```
441+
442+ ### Clang Codegen
443+
444+ The builtin function ` __builtin_hlsl_create_handle_from_implicit_binding ` will be
445+ translated to LLVM intrinsic ` llvm.{dx|spv}.resource.handlefromimplicitbinding ` .
446+
447+ The signature of the ` @llvm.dx.resource.handlefromimplicitbinding ` intrinsic will be:
448+
449+ | Argument | Type | Description |
450+ | -| -| -|
451+ | return value | ` target() ` type| A handle which can be operated on |
452+ | %order_id | i32 | Number uniquely identifying the unbound resource and declaration order |
453+ | %space | i32 | Virtual register space |
454+ | %range | i32 | Range size of the binding |
455+ | %index | i32 | Index from the beginning of the binding |
456+
457+ _ Note: We might need to add a uniformity bit here, unless we can derive it from uniformity analysis._
458+
459+ The ` order_id ` number will be generated by Clang Codegen in ` GCHLSLRuntime ` . It
460+ will be used to uniquely identify the unbound resource, and it will also reflect
461+ the order in which the resource has been declared. Implicit binding assignments
462+ depend on the order the resources were declared, so this will help the compiler
463+ to preserve the order. Additionally, it will also help the compiler distinquish
464+ between individual resources with the same type and binding range.
465+
466+ ### LLVM Binding Assignment Pass
467+
468+ The implicit binding assignment for DirectX will happen in an LLVM pass
469+ ` DXILResourceImplicitBindings ` . The pass will scan the module for all instances of
470+ ` @llvm.dx.resource.handlefrombinding ` and create a map of available register
471+ slots. Then it will gather all instances of
472+ ` @llvm.dx.resource.handlefromimplicitbinding ` calls and sort them by
473+ ` %order_id ` . Then for each group of calls operating on the same unique resource
474+ it will:
475+ - find an available space to bind the resource based on the resource class,
476+ required range and space
477+ - replace the ` @llvm.dx.resource.handlefromimplicitbinding ` calls and all of their
478+ uses with ` @llvm.dx.resource.handlefrombinding ` using the new binding
479+
480+ The ` DXILResourceImplicitBindings ` pass needs to run after all IR optimizations
481+ passes but before any pass or analysis that relies on
482+ ` @llvm.dx.resource.handlefrombinding ` calls to exist for all non-dynamically
483+ bound resources used by the shader. For example, it needs to run before any pass
484+ that requires ` DXILResourceAnalysis ` . The pass invalidate any existing
485+ ` DXILResourceAnalysis ` if it assigns any new bindings.
486+
365487## Alternatives considered (Optional)
366488
367489## Acknowledgments (Optional)
0 commit comments