11 This document describes the design and implementation of HLSL's function call
12 semantics in Clang. This includes details related to argument conversion and
15 This document does not seek to serve as official documentation for HLSL's
16 call semantics, but does provide an overview to assist a reader. The
17 authoritative documentation for HLSL's language semantics is the `draft language
18 specification <https://microsoft.github.io/hlsl-specs/specs/hlsl.pdf>`_.
23 In HLSL, all function arguments are passed by value in and out of functions.
24 HLSL has 3 keywords which denote the parameter semantics (``in``, ``out`` and
25 ``inout``). In a function declaration a parameter may be annotated any of the
28 #. <no parameter annotation> - denotes input
29 #. ``in`` - denotes input
30 #. ``out`` - denotes output
31 #. ``in out`` - denotes input and output
32 #. ``out in`` - denotes input and output
33 #. ``inout`` - denotes input and output
35 Parameters that are exclusively input behave like C/C++ parameters that are
38 For parameters that are output (or input and output), a temporary value is
39 created in the caller. The temporary value is then passed by-address. For
40 output-only parameters, the temporary is uninitialized when passed (if the
41 parameter is not explicitly initialized inside the function an undefined value
42 is stored back to the argument expression). For parameters that are both input
43 and output, the temporary is initialized from the lvalue argument expression
44 through implicit or explicit casting from the lvalue argument type to the
47 On return of the function, the values of any parameter temporaries are written
48 back to the argument expression through an inverted conversion sequence (if an
49 ``out`` parameter was not initialized in the function, the uninitialized value
52 Parameters of constant-sized array type are also passed with value semantics.
53 This requires input parameters of arrays to construct temporaries and the
54 temporaries go through array-to-pointer decay when initializing parameters.
56 Implementations are allowed to avoid unnecessary temporaries, and HLSL's strict
57 no-alias rules can enable some trivial optimizations.
62 Given the following example:
67 a[0] = a[1] + a[2] + a[3];
70 float4 main() : SV_Target {
71 float arr[4] = {1, 1, 1, 1};
73 return float4(arr[0], arr[1], arr[2], arr[3]);
76 In C or C++, the array parameter decays to a pointer, so after the call to
77 ``fn``, the value of ``arr[0]`` is ``3``. In HLSL, the array is passed by value,
78 so modifications inside ``fn`` do not propagate out.
82 DXC may pass unsized arrays directly as decayed pointers, which is an
83 unfortunate behavior divergence.
85 Out Parameter Temporaries
86 -------------------------
90 void Init(inout int X, inout int Y) {
97 Init(V, V); // MSVC (or clang-cl) V == 2, Clang V == 1
100 In the above example the ``Init`` function's behavior depends on the C++
101 implementation. C++ does not define the order in which parameters are
102 initialized or destroyed. In MSVC and Clang's MSVC compatibility mode, arguments
103 are emitted right-to-left and destroyed left-to-right. This means that the
104 parameter initialization and destruction occurs in the order: {``Y``, ``X``,
105 ``~X``, ``~Y``}. This causes the write-back of the value of ``Y`` to occur last,
106 so the resulting value of ``V`` is ``2``. In the Itanium C++ ABI, the parameter
107 ordering is reversed, so the initialization and destruction occurs in the order:
108 {``X``, ``Y``, ``~Y``, ``X``}. This causes the write-back of the value ``X`` to
109 occur last, resulting in the value of ``V`` being set to ``1``.
113 void Trunc(inout int3 V) { }
117 float3 F = {1.5, 2.6, 3.3};
118 Trunc(F); // F == {1.0, 2.0, 3.0}
121 In the above example, the argument expression ``F`` undergoes element-wise
122 conversion from a float vector to an integer vector to create a temporary
123 ``int3``. On expiration the temporary undergoes elementwise conversion back to
124 the floating point vector type ``float3``. This results in an implicit
125 element-wise conversion of the vector even if the value is unused in the
126 function (effectively truncating the floating point values).
131 void UB(out int X) {}
135 UB(X); // X is undefined!
138 In this example an initialized value is passed to an ``out`` parameter.
139 Parameters marked ``out`` are not initialized by the argument expression or
140 implicitly by the function. They must be explicitly initialized. In this case
141 the argument is not initialized in the function so the temporary is still
142 uninitialized when it is copied back to the argument expression. This is
143 undefined behavior in HLSL, and any use of the argument after the call is a use
144 of an undefined value which may be illegal in the target (DXIL programs with
145 used or potentially used ``undef`` or ``poison`` values fail validation).
152 The implementation described here is a proposal. It has not yet been fully
153 implemented, so the current state of Clang's sources may not reflect this
154 design. A prototype implementation was built on DXC which is Clang-3.7 based.
155 The prototype can be found
156 `here <https://github.com/microsoft/DirectXShaderCompiler/pull/5249>`_. A lot
157 of the changes in the prototype implementation are restoring Clang-3.7 code
158 that was previously modified to its original state.
160 The implementation in clang adds a new non-decaying array type, a new AST node
161 to represent output parameters, and minor extensions to Clang's existing support
162 for Objective-C write-back arguments. The goal of this design is to capture the
163 semantic details of HLSL function calls in the AST, and minimize the amount of
164 magic that needs to occur during IR generation.
169 The new ``ArrayParameterType`` is a sub-class of ``ConstantArrayType``
170 inheriting all the behaviors and methods of the parent except that it does not
171 decay to a pointer during overload resolution or template type deduction.
173 An argument of ``ConstantArrayType`` can be implicitly converted to an
174 equivalent non-decayed ``ArrayParameterType`` if the underlying canonical
175 ``ConstantArrayType`` is the same. This occurs during overload resolution
176 instead of array to pointer decay.
180 void SizedArray(float a[4]);
181 void UnsizedArray(float a[]);
184 float arr[4] = {1, 1, 1, 1};
189 In the example above, the following AST is generated for the call to
195 |-ImplicitCastExpr 'void (*)(float [4])' <FunctionToPointerDecay>
196 | `-DeclRefExpr 'void (float [4])' lvalue Function 'SizedArray' 'void (float [4])'
197 `-ImplicitCastExpr 'float [4]' <HLSLArrayRValue>
198 `-DeclRefExpr 'float [4]' lvalue Var 'arr' 'float [4]'
200 In the example above, the following AST is generated for the call to
206 |-ImplicitCastExpr 'void (*)(float [])' <FunctionToPointerDecay>
207 | `-DeclRefExpr 'void (float [])' lvalue Function 'UnsizedArray' 'void (float [])'
208 `-ImplicitCastExpr 'float [4]' <HLSLArrayRValue>
209 `-DeclRefExpr 'float [4]' lvalue Var 'arr' 'float [4]'
211 In both of these cases the argument expression is of known array size so we can
212 initialize an appropriately sized temporary.
214 It is illegal in HLSL to convert an unsized array to a sized array:
218 void SizedArray(float a[4]);
219 void UnsizedArray(float a[]) {
220 SizedArray(a); // Cannot convert float[] to float[4]
223 When converting a sized array to an unsized array, an array temporary can also
224 be inserted. Given the following code:
228 void UnsizedArray(float a[]);
229 void SizedArray(float a[4]) {
233 An expected AST should be something like:
238 |-ImplicitCastExpr 'void (*)(float [])' <FunctionToPointerDecay>
239 | `-DeclRefExpr 'void (float [])' lvalue Function 'UnsizedArray' 'void (float [])'
240 `-ImplicitCastExpr 'float [4]' <HLSLArrayRValue>
241 `-DeclRefExpr 'float [4]' lvalue Var 'arr' 'float [4]'
243 Out Parameter Temporaries
244 -------------------------
246 Output parameters are defined in HLSL as *casting expiring values* (cx-values),
247 which is a term made up for HLSL. A cx-value is a temporary value which may be
248 the result of a cast, and stores its value back to an lvalue when the value
251 To represent this concept in Clang we introduce a new ``HLSLOutParamExpr``. An
252 ``HLSLOutParamExpr`` has two forms, one with a single sub-expression and one
253 with two sub-expressions.
255 The single sub-expression form is used when the argument expression and the
256 function parameter are the same type, so no cast is required. As in this
261 void Init(inout int X) {
270 The expected AST formulation for this code would be something like:
275 |-ImplicitCastExpr 'void (*)(int &)' <FunctionToPointerDecay>
276 | `-DeclRefExpr 'void (int &)' lvalue Function 'Init' 'void (int &)'
277 |-HLSLOutParamExpr 'int' lvalue inout
278 `-DeclRefExpr 'int' lvalue Var 'V' 'int'
280 The ``HLSLOutParamExpr`` captures that the value is ``inout`` vs ``out`` to
281 denote whether or not the temporary is initialized from the sub-expression. If
282 no casting is required the sub-expression denotes the lvalue expression that the
283 cx-value will be copied to when the value expires.
285 The two sub-expression form of the AST node is required when the argument type
286 is not the same as the parameter type. Given this example:
290 void Trunc(inout int3 V) { }
294 float3 F = {1.5, 2.6, 3.3};
298 For this case the ``HLSLOutParamExpr`` will have sub-expressions to record both
299 casting expression sequences for the initialization and write back:
304 |-ImplicitCastExpr 'void (*)(int3 &)' <FunctionToPointerDecay>
305 | `-DeclRefExpr 'void (int3 &)' lvalue Function 'inc_i32' 'void (int3 &)'
306 `-HLSLOutParamExpr 'int3' lvalue inout
307 |-ImplicitCastExpr 'float3' <IntegralToFloating>
308 | `-ImplicitCastExpr 'int3' <LValueToRValue>
309 | `-OpaqueValueExpr 'int3' lvalue
310 `-ImplicitCastExpr 'int3' <FloatingToIntegral>
311 `-ImplicitCastExpr 'float3' <LValueToRValue>
312 `-DeclRefExpr 'float3' lvalue 'F' 'float3'
314 In this formation the write-back casts are captured as the first sub-expression
315 and they cast from an ``OpaqueValueExpr``. In IR generation we can use the
316 ``OpaqueValueExpr`` as a placeholder for the ``HLSLOutParamExpr``'s temporary
317 value on function return.
319 In code generation this can be implemented with some targeted extensions to the
320 Objective-C write-back support. Specifically extending CGCall.cpp's
321 ``EmitWriteback`` function to support casting expressions and emission of