Skip to content

Commit 4d3bf14

Browse files
authored
Implicit resource bindings design (#196)
Proposed design for implicit resource bindings in Clang. Closes #177
1 parent b1369ea commit 4d3bf14

File tree

1 file changed

+123
-1
lines changed

1 file changed

+123
-1
lines changed

proposals/0024-implicit-resource-binding.md

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)