brintos

brintos / llvm-project-archived public Read only

0
0
Text · 12.9 KiB · ea6dc2a Raw
348 lines · plain
1===================2HLSL Function Calls3===================4 5.. contents::6   :local:7 8Introduction9============10 11This document describes the design and implementation of HLSL's function call12semantics in Clang. This includes details related to argument conversion and13parameter lifetimes.14 15This document does not seek to serve as official documentation for HLSL's16call semantics, but does provide an overview to assist a reader. The17authoritative documentation for HLSL's language semantics is the `draft language18specification <https://microsoft.github.io/hlsl-specs/specs/hlsl.pdf>`_.19 20Argument Semantics21==================22 23In HLSL, all function arguments are passed by value in and out of functions.24HLSL has 3 keywords which denote the parameter semantics (``in``, ``out`` and25``inout``). In a function declaration a parameter may be annotated any of the26following ways:27 28#. <no parameter annotation> - denotes input29#. ``in`` - denotes input30#. ``out`` - denotes output31#. ``in out`` - denotes input and output32#. ``out in`` - denotes input and output33#. ``inout`` - denotes input and output34 35Parameters that are exclusively input behave like C/C++ parameters that are36passed by value.37 38For parameters that are output (or input and output), a temporary value is39created in the caller. The temporary value is then passed by-address. For40output-only parameters, the temporary is uninitialized when passed (if the41parameter is not explicitly initialized inside the function an undefined value42is stored back to the argument expression). For parameters that are both input43and output, the temporary is initialized from the lvalue argument expression44through implicit  or explicit casting from the lvalue argument type to the45parameter type.46 47On return of the function, the values of any parameter temporaries are written48back to the argument expression through an inverted conversion sequence (if an49``out`` parameter was not initialized in the function, the uninitialized value50may be written back).51 52Parameters of constant-sized array type are also passed with value semantics.53This requires input parameters of arrays to construct temporaries and the54temporaries go through array-to-pointer decay when initializing parameters.55 56Implementations are allowed to avoid unnecessary temporaries, and HLSL's strict57no-alias rules can enable some trivial optimizations.58 59Array Temporaries60-----------------61 62Given the following example:63 64.. code-block:: c++65 66  void fn(float a[4]) {67    a[0] = a[1] + a[2] + a[3];68  }69 70  float4 main() : SV_Target {71    float arr[4] = {1, 1, 1, 1};72    fn(arr);73    return float4(arr[0], arr[1], arr[2], arr[3]);74  }75 76In C or C++, the array parameter decays to a pointer, so after the call to77``fn``, the value of ``arr[0]`` is ``3``. In HLSL, the array is passed by value,78so modifications inside ``fn`` do not propagate out.79 80.. note::81 82  DXC may pass unsized arrays directly as decayed pointers, which is an83  unfortunate behavior divergence.84 85Out Parameter Temporaries86-------------------------87 88.. code-block:: c++89 90  void Init(inout int X, inout int Y) {91    Y = 2;92    X = 1;93  }94 95  void main() {96    int V;97    Init(V, V); // MSVC (or clang-cl) V == 2, Clang V == 198  }99 100In the above example the ``Init`` function's behavior depends on the C++101implementation. C++ does not define the order in which parameters are102initialized or destroyed. In MSVC and Clang's MSVC compatibility mode, arguments103are emitted right-to-left and destroyed left-to-right. This means that  the104parameter 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,106so the resulting value of ``V`` is ``2``. In the Itanium C++ ABI, the  parameter107ordering 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`` to109occur last, resulting in the value of ``V`` being set to ``1``.110 111.. code-block:: c++112 113  void Trunc(inout int3 V) { }114 115 116  void main() {117    float3 F = {1.5, 2.6, 3.3};118    Trunc(F); // F == {1.0, 2.0, 3.0}119  }120 121In the above example, the argument expression ``F`` undergoes element-wise122conversion from a float vector to an integer vector to create a temporary123``int3``. On expiration the temporary undergoes elementwise conversion back to124the floating point vector type ``float3``. This results in an implicit125element-wise conversion of the vector even if the value is unused in the126function (effectively truncating the floating point values).127 128 129.. code-block:: c++130 131  void UB(out int X) {}132 133  void main() {134    int X = 7;135    UB(X); // X is undefined!136  }137 138In this example an initialized value is passed to an ``out`` parameter.139Parameters marked ``out`` are not initialized by the argument expression or140implicitly by the function. They must be explicitly initialized. In this case141the argument is not initialized in the function so the temporary is still142uninitialized when it is copied back to the argument expression. This is143undefined behavior in HLSL, and any use of the argument after the call is a use144of an undefined value which may be illegal in the target (DXIL programs with145used or potentially used ``undef`` or ``poison`` values fail validation).146 147Clang Implementation148====================149 150.. note::151 152  The implementation described here is a proposal. It has not yet been fully153  implemented, so the current state of Clang's sources may not reflect this154  design. A prototype implementation was built on DXC which is Clang-3.7 based.155  The prototype can be found156  `here <https://github.com/microsoft/DirectXShaderCompiler/pull/5249>`_. A lot157  of the changes in the prototype implementation are restoring Clang-3.7 code158  that was previously modified to its original state.159 160The implementation in clang adds a new non-decaying array type, a new AST node161to represent output parameters, and minor extensions to Clang's existing support162for Objective-C write-back arguments. The goal of this design is to capture the163semantic details of HLSL function calls in the AST, and minimize the amount of164magic that needs to occur during IR generation.165 166Array Temporaries167-----------------168 169The new ``ArrayParameterType`` is a sub-class of ``ConstantArrayType``170inheriting all the behaviors and methods of the parent except that it does not171decay to a pointer during overload resolution or template type deduction.172 173An argument of ``ConstantArrayType`` can be implicitly converted to an174equivalent non-decayed ``ArrayParameterType`` if the underlying canonical175``ConstantArrayType`` is the same. This occurs during overload resolution176instead of array to pointer decay.177 178.. code-block:: c++179 180  void SizedArray(float a[4]);181  void UnsizedArray(float a[]);182 183  void main() {184    float arr[4] = {1, 1, 1, 1};185    SizedArray(arr);186    UnsizedArray(arr);187  }188 189In the example above, the following AST is generated for the call to190``SizedArray``:191 192.. code-block:: text193 194  CallExpr 'void'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]'199 200In the example above, the following AST is generated for the call to201``UnsizedArray``:202 203.. code-block:: text204 205  CallExpr 'void'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]'210 211In both of these cases the argument expression is of known array size so we can212initialize an appropriately sized temporary.213 214It is illegal in HLSL to convert an unsized array to a sized array:215 216.. code-block:: c++217 218  void SizedArray(float a[4]);219  void UnsizedArray(float a[]) {220    SizedArray(a); // Cannot convert float[] to float[4]221  }222 223When converting a sized array to an unsized array, an array temporary can also224be inserted. Given the following code:225 226.. code-block:: c++227 228  void UnsizedArray(float a[]);229  void SizedArray(float a[4]) {230    UnsizedArray(a);231  }232 233An expected AST should be something like:234 235.. code-block:: text236 237  CallExpr 'void'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]'242 243Out Parameter Temporaries244-------------------------245 246Output parameters are defined in HLSL as *casting expiring values* (cx-values),247which is a term made up for HLSL. A cx-value is a temporary value which may be248the result of a cast, and stores its value back to an lvalue when the value249expires.250 251To represent this concept in Clang we introduce a new ``HLSLOutArgExpr``. An252``HLSLOutArgExpr`` has three sub-expressions:253 254* An OpaqueValueExpr of the argument lvalue expression.255* An OpaqueValueExpr of the copy-initialized parameter temporary.256* A BinaryOpExpr assigning the first with the value of the second.257 258Given this example:259 260.. code-block:: c++261 262  void Init(inout int X) {263    X = 1;264  }265 266  void main() {267    int V;268    Init(V);269  }270 271The expected AST formulation for this code would be something like the example272below. Due to the nature of OpaqueValueExpr nodes, the nodes repeat in the AST273dump. The fake addresses ``0xSOURCE`` and ``0xTEMPORARY`` denote the source274lvalue and argument temporary lvalue expressions.275 276.. code-block:: text277 278  CallExpr 'void'279  |-ImplicitCastExpr 'void (*)(int &)' <FunctionToPointerDecay>280  | `-DeclRefExpr 'void (int &)' lvalue Function  'Init' 'void (int &)'281  `-HLSLOutArgExpr <col:10> 'int' lvalue inout282    |-OpaqueValueExpr 0xSOURCE <col:10> 'int' lvalue283    | `-DeclRefExpr <col:10> 'int' lvalue Var 'V' 'int'284    |-OpaqueValueExpr 0xTEMPORARY <col:10> 'int' lvalue285    | `-ImplicitCastExpr <col:10> 'int' <LValueToRValue>286    |   `-OpaqueValueExpr 0xSOURCE <col:10> 'int' lvalue287    |     `-DeclRefExpr <col:10> 'int' lvalue Var 'V' 'int'288    `-BinaryOperator <col:10> 'int' lvalue '='289      |-OpaqueValueExpr 0xSOURCE <col:10> 'int' lvalue290      | `-DeclRefExpr <col:10> 'int' lvalue Var 'V' 'int'291      `-ImplicitCastExpr <col:10> 'int' <LValueToRValue>292        `-OpaqueValueExpr 0xTEMPORARY <col:10> 'int' lvalue293          `-ImplicitCastExpr <col:10> 'int' <LValueToRValue>294            `-OpaqueValueExpr 0xSOURCE <col:10> 'int' lvalue295              `-DeclRefExpr <col:10> 'int' lvalue Var 'V' 'int'296 297The ``HLSLOutArgExpr`` captures that the value is ``inout`` vs ``out`` to298denote whether or not the temporary is initialized from the sub-expression.299 300The example below demonstrates argument casting:301 302.. code-block:: c++303 304  void Trunc(inout int3 V) { }305 306 307  void main() {308    float3 F = {1.5, 2.6, 3.3};309    Trunc(F);310  }311 312For this case the ``HLSLOutArgExpr`` will have sub-expressions to record both313casting expression sequences for the initialization and write back:314 315.. code-block:: text316 317  -CallExpr 'void'318    |-ImplicitCastExpr 'void (*)(int3 &)' <FunctionToPointerDecay>319    | `-DeclRefExpr 'void (int3 &)' lvalue Function 'inc_i32' 'void (int3 &)'320    `-HLSLOutArgExpr <col:11> 'int3':'vector<int, 3>' lvalue inout321      |-OpaqueValueExpr 0xSOURCE <col:11> 'float3':'vector<float, 3>' lvalue322      | `-DeclRefExpr <col:11> 'float3':'vector<float, 3>' lvalue Var 'F' 'float3':'vector<float, 3>'323      |-OpaqueValueExpr 0xTEMPORARY <col:11> 'int3':'vector<int, 3>' lvalue324      | `-ImplicitCastExpr <col:11> 'vector<int, 3>' <FloatingToIntegral>325      |   `-ImplicitCastExpr <col:11> 'float3':'vector<float, 3>' <LValueToRValue>326      |     `-OpaqueValueExpr 0xSOURCE <col:11> 'float3':'vector<float, 3>' lvalue327      |       `-DeclRefExpr <col:11> 'float3':'vector<float, 3>' lvalue Var 'F' 'float3':'vector<float, 3>'328      `-BinaryOperator <col:11> 'float3':'vector<float, 3>' lvalue '='329        |-OpaqueValueExpr 0xSOURCE <col:11> 'float3':'vector<float, 3>' lvalue330        | `-DeclRefExpr <col:11> 'float3':'vector<float, 3>' lvalue Var 'F' 'float3':'vector<float, 3>'331        `-ImplicitCastExpr <col:11> 'vector<float, 3>' <IntegralToFloating>332          `-ImplicitCastExpr <col:11> 'int3':'vector<int, 3>' <LValueToRValue>333            `-OpaqueValueExpr 0xTEMPORARY <col:11> 'int3':'vector<int, 3>' lvalue334              `-ImplicitCastExpr <col:11> 'vector<int, 3>' <FloatingToIntegral>335                `-ImplicitCastExpr <col:11> 'float3':'vector<float, 3>' <LValueToRValue>336                  `-OpaqueValueExpr 0xSOURCE <col:11> 'float3':'vector<float, 3>' lvalue337                    `-DeclRefExpr <col:11> 'float3':'vector<float, 3>' lvalue Var 'F' 'float3':'vector<float, 3>'338 339The AST representation is the same whether casting is required or not, which340simplifies the code generation. IR generation does the following:341 342* Emit the argument lvalue expression.343* Initialize the argument:344  * For ``inout`` arguments, emit the copy-initialization expression.345  * For ``out`` arguments, emit an uninitialized temporary.346* Emit the call347* Emit the write-back BinaryOperator expression.348