HLSL Specifications

0037 - Refined cbuffer Contexts

StatusAccepted
Authors

Introduction

This proposal aligns cbuffer declarations more closely with syntactic and semantic behaviors that are common in C and C++ to align more closely with user expectation and reduce the need for special case handling in the compiler.

Motivation

The HLSL cbuffer declaration is a source of irregularity within the language semantics, and the interaction between cbuffer declarations and their contained declarations can be different and surprising.

In DXC, declarations inside a cbuffer are always hoisted out to translation unit scope, which effectively ignores the namespace nesting, however the declaration context hierarchy within a cbuffer is preserved except again in the case of a nested cbuffer which pops back out to translation unit scope.

This behavior in DXC is clearly a bug, and FXC behaves more reasonably. As an example given the following cbuffer declarations:

namespace ns {
  cbuffer CB {
    float a;

    namespace ns2 {
      cbuffer CB2 {
        float b;
      } // cbuffer CB2
    } // namespace ns2
  } // cbuffer CB
} // namespace ns

FXC @ ShaderPlayground DXC @ Compiler Explorer

In DXC, a and b are referenced directly with no namespace qualifications, while in FXC they are ns::a and ns::ns2::b respectively (unless the namespace contains other variable declarations, which is clearly a bug: https://godbolt.org/z/Ph57WYexr).

In FXC, cbuffer declarations behave more like a source range declaration that groups global declarations into a single constant buffer rather than a semantic declaration grouping. As such, one could replace the cbuffer syntax in FXC with a preprocessor pragma with push|pop semantics that specified which buffer and binding to group declarations into.

This design does not follow any pattern common in C or C++, and thus runs counter to the principle of least astonishment.

Proposed solution

To simplify HLSL’s language semantics and the compiler implementation a new grammar formation is adopted for cbuffers:

\begin{grammar}
  \define{cbuffer-declaration}\br
  \terminal{cbuffer} name \opt{resource-binding} \terminal{\{}
    \opt{cbuffer-member-seq} \terminal {\}}\br

  \define{cbuffer-member-seq}\br
  cbuffer-member-declaration\br
  cbuffer-member-seq cbuffer-member-declaration\br

  \define{cbuffer-member-declaration}\br
  block-declaration\br
  function-definition\br
  template-declaration\br
  empty-declaration
\end{grammar}

Latex Rendering

This simplified grammar restricts the declarations that are valid within a cbuffer to declarations that are broadly valid inside block scopes as well as function and template declarations. This allows a cbuffer to contain declarations of classes and data types as well as functions and variables.

A cbuffer may only be declared at translation unit or namespace scope. A cbuffer may not contain a namespace declaration and may only contain a subset of valid global declarations.

Detailed Design

The following text will be included in the [Decl.cbuffer] section in the language specification.

A cbuffer declaration is declared with the \texttt{cbuffer} keyword. The name of the cbuffer declaration does not declare a name, and cannot be referenced from within the translation unit, nor is it required to be unique. Each cbuffer declaration refers to a unique constant buffer resource.

Declarations within a cbuffer declaration that declare names, declare their names in the scope containing the cbuffer declaration. The cbuffer declaration itself does not declare a declaration scope. A cbuffer declaration may not contain a namespace-declaration or cbuffer-declaration.

Variable declarations with program storage duration in the cbuffer declaration are called shader constants. Shader constants are implicitly const and cannot be modified in program code.

Long-term Considerations

During our discussion in the language design meeting additional restrictions to cbuffer declarations were considered. The general consensus was that additional restrictions are desirable and will remove possible programmer errors, however there were concerns about HLSL 202x changing too much at once.

In a future language version we may consider further restricting cbuffers to allow only variable declarations of program storage duration.

In implementing cbuffer support in Clang we should consider implementing a diagnostic for all variable declarations that are not of program storage duration to identify potential programmer errors.