@@ -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