Skip to content

Commit 3fbe3ef

Browse files
committed
Design for resources in structs
1 parent 7c24a29 commit 3fbe3ef

File tree

1 file changed

+247
-0
lines changed

1 file changed

+247
-0
lines changed

proposals/NNNN-resources-in-structs.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,255 @@ We need to support resources in structs in Clang.
415415

416416
## Proposed solution
417417

418+
For each resource or resource array that is a member of a struct declared at
419+
global scope or inside a `cbuffer`, an implicit global variable of the resource
420+
type will be created and associated with the struct instance. All accesses to
421+
the resource member will be redirected to the associated global variable during
422+
Clang CodeGen.
423+
418424
## Detailed design
419425

426+
### Single Resources
427+
428+
For each resource member of a struct declared at global scope or inside a
429+
`cbuffer`, Clang will create an implicit global variable of the same resource
430+
type. The variable name will be derived from the struct instance name and the
431+
member name, following the naming convention used by DXC (see
432+
[Example 1](#example-1)).
433+
434+
For example, given the following struct definitions and instances:
435+
```c++
436+
struct A {
437+
RWBuffer<float> Buf;
438+
};
439+
440+
struct B : A {
441+
A a;
442+
};
443+
444+
A a1;
445+
B b1 : register(u2);
446+
```
447+
448+
For the resource inside `a1`, Clang will create a global variable of type
449+
`RWBuffer<float>` named `a1.Buf`. For the nested resource in `b1`, accessed via
450+
the member `a`, the global variable will be named `b1.a.Buf`.
451+
452+
When a resource is inherited from a base class, the variable name includes the
453+
base class name as a `::` delimited component. For example:
454+
455+
```c++
456+
struct C : A {
457+
};
458+
459+
C c1;
460+
```
461+
462+
The global variable for the inherited resource in `c1` will be named `c1::A.Buf`.
463+
464+
> **Note:** DXC uses `.` as the delimiter for both base classes and fields,
465+
> which can produce ambiguous names when a field and base class share the same
466+
> name. This ambiguity causes DXC to crash:
467+
> https://godbolt.org/z/5EM418s6T.
468+
469+
### Associated Resource Decl Attribute
470+
471+
To enable efficient lookup of the implicit global variables associated with a
472+
struct instance, a new `HLSLAssociatedResourceDeclAttr` attribute will be
473+
introduced. Each attribute instance holds a pointer to one of the global
474+
resource variables created for the struct. The attribute is attached to the
475+
struct instance declaration, with one attribute per embedded resource or
476+
resource array.
477+
478+
### Resources Arrays
479+
480+
Resource array members of a struct are handled similarly: for each resource
481+
array member, Clang will create a global variable with the same array type.
482+
Unlike DXC, which treats each array element as a separate resource, Clang will
483+
represent the entire array as a single global variable. This approach naturally
484+
supports dynamic indexing of the resource array.
485+
486+
For example:
487+
488+
```c++
489+
struct D {
490+
RWBuffer<float> Bufs[10];
491+
};
492+
493+
D d1 : register(u5);
494+
```
495+
496+
Clang creates a global variable named `d1.Bufs` of type `RWBuffer<float>[10]`,
497+
with a binding range of `10`.
498+
499+
```
500+
; Resource Bindings:
501+
;
502+
; Name Type Format Dim ID HLSL Bind Count
503+
; ------------------------------ ---------- ------- ----------- ------- -------------- ------
504+
; d1.Bufs UAV f32 buf U0 u5 10
505+
```
506+
507+
### Resources inside Struct Arrays
508+
509+
When a resource (or resource array) is a member of a struct type used in an
510+
array, Clang will create a separate global variable for each array element. The
511+
variable names will be constructed from the struct array instance name, the
512+
array index, and the resource member name, matching DXC's behavior in [Example
513+
4](#example-4). Since the array index is encoded in the resource name, dynamic
514+
indexing of the struct array will not be supported, consistent with DXC.
515+
516+
For example:
517+
```c++
518+
struct A {
519+
RWBuffer<float> Buf;
520+
};
521+
522+
A array[3];
523+
```
524+
525+
Clang will create three global variables: `array.0.Buf`, `array.1.Buf`, and
526+
`array.2.Buf`.
527+
528+
Indexing the array with a non-constant index produces an error:
529+
`Index for resource array inside cbuffer must be a literal expression.`
530+
531+
### Resource Binding
532+
533+
Each implicit global resource variable will have its own binding attribute
534+
specifying its register binding and whether the binding is explicit or implicit.
535+
536+
When a struct contains multiple resource or resource array members, each one
537+
receives a portion of the binding based on its register class and required
538+
range.
539+
540+
For example:
541+
542+
```c++
543+
struct A {
544+
RWBuffer<float> Buf;
545+
};
546+
547+
struct E {
548+
A a;
549+
RWBuffer<int> array[5];
550+
StructuredBuffer<uint> SB;
551+
};
552+
553+
E e : register(u2) : register (t5);
554+
```
555+
556+
Clang will create the following global resource declarations:
557+
- `e.a.Buf` of type `RWBuffer<float>` with binding `u2` and range `1`
558+
- `e.array` of type `RWBuffer<int>[5]` with binding `u3` and range `5`
559+
- `e.SB` of type `StructuredBuffer<uint>` with binding `t5`
560+
561+
For implicit binding of resources in structs, Clang will apply the same rules as
562+
for resources declared at global scope:
563+
564+
- Implicit bindings are assigned in declaration order of the resources or
565+
resource arrays.
566+
- Resource arrays are assigned a contiguous range of register slots matching the
567+
array size.
568+
569+
This differs from DXC's behavior, which mostly assigns bindings in the order
570+
resources are first used in the shader, though not consistently, making it
571+
unpredictable.
572+
573+
### CodeGen
574+
575+
During Clang CodeGen, any expression that accesses a resource or resource array
576+
member of a global struct instance will be translated to an access of the
577+
corresponding implicit global variable.
578+
579+
#### Single Resource Access
580+
581+
When CodeGen encounters a `MemberExpr` of a resource type, it will traverse the
582+
AST to locate the parent struct declaration, building the expected global
583+
variable name along the way. If the parent is a non-static global struct
584+
instance, CodeGen will search its `HLSLAssociatedResourceDeclAttr` attributes to
585+
find the matching global variable, and then generate IR code to access it.
586+
587+
For example:
588+
```c++
589+
struct A {
590+
RWBuffer<float> Buf;
591+
};
592+
593+
A a1 : register(u5);
594+
595+
[numthreads(4,1,1)]
596+
void main() {
597+
a1.Buf[0] = 13.4;
598+
}
599+
```
600+
601+
The `a1.Buf` expression will be translated as access to `@a1.Buf` global
602+
variable, which has been initialized with resource handle from binding at the
603+
shader entry point.
604+
605+
#### Resource Array Element Access
606+
607+
Similarly to a single resource access, when Clang CodeGen sees an
608+
`ArraySubscriptExpr` of a resource or resource array type that is linked to a
609+
`MemberExpr`, it will walk the AST to find its parent struct declaration and the
610+
associated global resource array varible. Then it will generate IR code to
611+
access the array element (or array subset) the same way global resource arrays
612+
are handled.
613+
614+
For example:
615+
```c++
616+
struct B {
617+
RWBuffer<float> Bufs[4];
618+
};
619+
620+
B b1 : register(u2);
621+
622+
[numthreads(4,4,4)]
623+
void main(uint3 ID : SV_GroupThreadID) {
624+
b1.Bufs[ID.x][ID.y] = 0;
625+
}
626+
```
627+
628+
The expression `b1.Bufs[ID.x]` is translated to a resource handle initialized
629+
from the binding at index `ID.x` within the range of 4 registers starting at
630+
`u2`. The handle is initialized when the array element is accessed, matching
631+
the behavior of global resource arrays.
632+
633+
#### Resource Array Assignment
634+
635+
When an entire resource array is assigned or passed as a function argument,
636+
CodeGen creates a local copy of the array with each element initialized to a
637+
handle from its binding. This matches how global resource array assignments are
638+
handled.
639+
640+
#### Copy of struct with resources
641+
642+
When a struct with resources is assigned to a local variable or passed as a
643+
function parameter, CodeGen creates a local copy. Resource members are
644+
initialized with handle copies from the corresponding global variables, and
645+
resource arrays become local copies with each element initialized to a handle
646+
from its binding.
647+
648+
Note that structs declared at global scope reside in constant address space `2`
649+
and use `cbuffer` struct layout. Copying these structs requires HLSL-specific
650+
handling (see
651+
[llvm/llvm-project#153055](https://github.com/llvm/llvm-project/issues/153055)),
652+
and support for copying embedded resources and resource arrays must be built on
653+
top of that.
654+
655+
#### Binding Range Validation
656+
657+
Clang will detect out-of-range bindings during semantic analysis and report
658+
clear error messages pointing to the resource declaration. This improves upon
659+
DXC's range validation errors, which are often unclear and sometimes missing
660+
entirely.
661+
662+
#### Specifying `space` for resources in structs
663+
664+
Clang will support specifying register `space` for struct instances containing
665+
resources, addressing a limitation in DXC (see [Example 10](#example-10)).
666+
420667
## Alternatives considered (Optional)
421668
422669
## Acknowledgments (Optional)

0 commit comments

Comments
 (0)