Skip to content

Commit b9b92a0

Browse files
authored
Design proposal for resources in user-defined structs (#370)
Design proposal on how to implement resource and resources arrays embedded in user-defined structs and classes in Clang. Closes #212
1 parent f64ca8a commit b9b92a0

File tree

1 file changed

+247
-0
lines changed

1 file changed

+247
-0
lines changed

proposals/0038-resources-in-structs.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,8 +531,255 @@ binding order, to make the feature more robust and user-friendly.
531531
532532
## Proposed solution
533533
534+
For each resource or resource array that is a member of a struct declared at
535+
global scope or inside a `cbuffer`, an implicit global variable of the resource
536+
type will be created and associated with the struct instance. All accesses to
537+
the resource member will be redirected to the associated global variable during
538+
Clang CodeGen.
539+
534540
## Detailed design
535541
542+
### Single Resources
543+
544+
For each resource member of a struct declared at global scope or inside a
545+
`cbuffer`, Clang will create an implicit global variable of the same resource
546+
type. The variable name will be derived from the struct instance name and the
547+
member name, following the naming convention used by DXC (see
548+
[Example 1](#example-1)).
549+
550+
For example, given the following struct definitions and instances:
551+
```c++
552+
struct A {
553+
RWBuffer<float> Buf;
554+
};
555+
556+
struct B {
557+
A a;
558+
};
559+
560+
A a1;
561+
B b1 : register(u2);
562+
```
563+
564+
For the resource inside `a1`, Clang will create a global variable of type
565+
`RWBuffer<float>` named `a1.Buf`. For the nested resource in `b1`, accessed via
566+
the member `a`, the global variable will be named `b1.a.Buf`.
567+
568+
When a resource is inherited from a base class, the variable name includes the
569+
base class name as a `::` delimited component. For example:
570+
571+
```c++
572+
struct C : A {
573+
};
574+
575+
C c1;
576+
```
577+
578+
The global variable for the inherited resource in `c1` will be named `c1.A::Buf`.
579+
580+
> **Note:** DXC uses `.` as the delimiter for both base classes and fields,
581+
> which can produce ambiguous names when a field and base class share the same
582+
> name. This ambiguity causes DXC to crash:
583+
> https://godbolt.org/z/5EM418s6T.
584+
585+
### Associated Resource Decl Attribute
586+
587+
To enable efficient lookup of the implicit global variables associated with a
588+
struct instance, a new `HLSLAssociatedResourceDeclAttr` attribute will be
589+
introduced. Each attribute instance holds a pointer to one of the global
590+
resource variables created for the struct. The attribute is attached to the
591+
struct instance declaration, with one attribute per embedded resource or
592+
resource array.
593+
594+
### Resources Arrays
595+
596+
Resource array members of a struct are handled similarly: for each resource
597+
array member, Clang will create a global variable with the same array type.
598+
Unlike DXC, which treats each array element as a separate resource, Clang will
599+
represent the entire array as a single global variable. This approach naturally
600+
supports dynamic indexing of the resource array.
601+
602+
For example:
603+
604+
```c++
605+
struct D {
606+
RWBuffer<float> Bufs[10];
607+
};
608+
609+
D d1 : register(u5);
610+
```
611+
612+
Clang creates a global variable named `d1.Bufs` of type `RWBuffer<float>[10]`,
613+
with a binding range of `10`.
614+
615+
```
616+
; Resource Bindings:
617+
;
618+
; Name Type Format Dim ID HLSL Bind Count
619+
; ------------------------------ ---------- ------- ----------- ------- -------------- ------
620+
; d1.Bufs UAV f32 buf U0 u5 10
621+
```
622+
623+
### Resources inside Struct Arrays
624+
625+
When a resource (or resource array) is a member of a struct type used in an
626+
array, Clang will create a separate global variable for each array element. The
627+
variable names will be constructed from the struct array instance name, the
628+
array index, and the resource member name, matching DXC's behavior in [Example
629+
4](#example-4). Since the array index is encoded in the resource name, dynamic
630+
indexing of the struct array will not be supported, consistent with DXC.
631+
632+
For example:
633+
```c++
634+
struct A {
635+
RWBuffer<float> Buf;
636+
};
637+
638+
A array[3];
639+
```
640+
641+
Clang will create three global variables: `array.0.Buf`, `array.1.Buf`, and
642+
`array.2.Buf`.
643+
644+
Indexing the array with a non-constant index produces an error:
645+
`Index for resource array inside cbuffer must be a literal expression.`
646+
647+
### Resource Binding
648+
649+
Each implicit global resource variable will have its own binding attribute
650+
specifying its register binding and whether the binding is explicit or implicit.
651+
652+
When a struct contains multiple resource or resource array members, each one
653+
receives a portion of the binding based on its register class and required
654+
range.
655+
656+
For example:
657+
658+
```c++
659+
struct A {
660+
RWBuffer<float> Buf;
661+
};
662+
663+
struct E {
664+
A a;
665+
RWBuffer<int> array[5];
666+
StructuredBuffer<uint> SB;
667+
};
668+
669+
E e : register(u2) : register (t5);
670+
```
671+
672+
Clang will create the following global resource declarations:
673+
- `e.a.Buf` of type `RWBuffer<float>` with binding `u2` and range `1`
674+
- `e.array` of type `RWBuffer<int>[5]` with binding `u3` and range `5`
675+
- `e.SB` of type `StructuredBuffer<uint>` with binding `t5`
676+
677+
For implicit binding of resources in structs, Clang will apply the same rules as
678+
for resources declared at global scope:
679+
680+
- Implicit bindings are assigned in declaration order of the resources or
681+
resource arrays.
682+
- Resource arrays are assigned a contiguous range of register slots matching the
683+
array size.
684+
685+
This differs from DXC's behavior, which mostly assigns bindings in the order
686+
resources are first used in the shader, though not consistently, making it
687+
unpredictable.
688+
689+
### CodeGen
690+
691+
During Clang CodeGen, any expression that accesses a resource or resource array
692+
member of a global struct instance will be translated to an access of the
693+
corresponding implicit global variable.
694+
695+
#### Single Resource Access
696+
697+
When CodeGen encounters a `MemberExpr` of a resource type, it will traverse the
698+
AST to locate the parent struct declaration, building the expected global
699+
variable name along the way. If the parent is a non-static global struct
700+
instance, CodeGen will search its `HLSLAssociatedResourceDeclAttr` attributes to
701+
find the matching global variable, and then generate IR code to access it.
702+
703+
For example:
704+
```c++
705+
struct A {
706+
RWBuffer<float> Buf;
707+
};
708+
709+
A a1 : register(u5);
710+
711+
[numthreads(4,1,1)]
712+
void main() {
713+
a1.Buf[0] = 13.4;
714+
}
715+
```
716+
717+
The `a1.Buf` expression will be translated as access to `@a1.Buf` global
718+
variable, which has been initialized with resource handle from binding at the
719+
shader entry point.
720+
721+
#### Resource Array Element Access
722+
723+
Similarly to a single resource access, when Clang CodeGen sees an
724+
`ArraySubscriptExpr` of a resource or resource array type that is linked to a
725+
`MemberExpr`, it will walk the AST to find its parent struct declaration and the
726+
associated global resource array varible. Then it will generate IR code to
727+
access the array element (or array subset) the same way global resource arrays
728+
are handled.
729+
730+
For example:
731+
```c++
732+
struct B {
733+
RWBuffer<float> Bufs[4];
734+
};
735+
736+
B b1 : register(u2);
737+
738+
[numthreads(4,4,4)]
739+
void main(uint3 ID : SV_GroupThreadID) {
740+
b1.Bufs[ID.x][ID.y] = 0;
741+
}
742+
```
743+
744+
The expression `b1.Bufs[ID.x]` is translated to a resource handle initialized
745+
from the binding at index `ID.x` within the range of 4 registers starting at
746+
`u2`. The handle is initialized when the array element is accessed, matching
747+
the behavior of global resource arrays.
748+
749+
#### Resource Array Assignment
750+
751+
When an entire resource array is assigned or passed as a function argument,
752+
CodeGen creates a local copy of the array with each element initialized to a
753+
handle from its binding. This matches how global resource array assignments are
754+
handled.
755+
756+
#### Copy of struct with resources
757+
758+
When a struct with resources is assigned to a local variable or passed as a
759+
function parameter, CodeGen creates a local copy. Resource members are
760+
initialized with handle copies from the corresponding global variables, and
761+
resource arrays become local copies with each element initialized to a handle
762+
from its binding.
763+
764+
Note that structs declared at global scope reside in constant address space `2`
765+
and use `cbuffer` struct layout. Copying these structs requires HLSL-specific
766+
handling (see
767+
[llvm/llvm-project#153055](https://github.com/llvm/llvm-project/issues/153055)),
768+
and support for copying embedded resources and resource arrays must be built on
769+
top of that.
770+
771+
#### Binding Range Validation
772+
773+
Clang will detect out-of-range bindings during semantic analysis and report
774+
clear error messages pointing to the resource declaration. This improves upon
775+
DXC's range validation errors, which are often unclear and sometimes missing
776+
entirely.
777+
778+
#### Specifying `space` for resources in structs
779+
780+
Clang will support specifying register `space` for struct instances containing
781+
resources, addressing a limitation in DXC (see [Example 10](#example-10)).
782+
536783
## Alternatives considered (Optional)
537784
538785
## Acknowledgments (Optional)

0 commit comments

Comments
 (0)