================ @@ -0,0 +1,316 @@ +=================== +HLSL Function Calls +=================== + +.. contents:: + :local: + +Introduction +============ + +This document describes the design and implementation of HLSL's function call +semantics in Clang. This includes details related to argument conversion and +parameter lifetimes. + +This document does not seek to serve as official documentation for HLSL's +call semantics, but does provide an overview to assist a reader. The +authoritative documentation for HLSL's language semantics is the `draft language +specification <https://microsoft.github.io/hlsl-specs/specs/hlsl.pdf>`_. + +Argument Semantics +================== + +In HLSL, all function arguments are passed by value in and out of functions. +HLSL has 3 keywords which denote the parameter semantics (``in``, ``out`` and +``inout``). In a function declaration a parameter may be annotated any of the +following ways: + +#. <no parameter annotation> - denotes input +#. ``in`` - denotes input +#. ``out`` - denotes output +#. ``in out`` - denotes input and output +#. ``out in`` - denotes input and output +#. ``inout`` - denotes input and output + +Parameters that are exclusively input behave like C/C++ parameters that are +passed by value. + +For parameters that are output (or input and output), a temporary value is +created in the caller. The temporary value is then passed by-address. For +output-only parameters, the temporary is uninitialized when passed (if the +parameter is not explicitly initialized inside the function an undefined value +is stored back to the argument expression). For input and output parameters, the +temporary is initialized from the lvalue argument expression through implicit +or explicit casting from the lvalue argument type to the parameter type. + +On return of the function, the values of any parameter temporaries are written +back to the argument expression through an inverted conversion sequence (if an +``out`` parameter was not initialized in the function, the uninitialized value +may be written back). + +Parameters of constant-sized array type, are also passed with value semantics. +This requires input parameters of arrays to construct temporaries and the +temporaries go through array-to-pointer decay when initializing parameters. + +Implementations are allowed to avoid unnecessary temporaries, and HLSL's strict +no-alias rules can enable some trivial optimizations. + +Array Temporaries +----------------- + +Given the following example: + +.. code-block:: c++ + + void fn(float a[4]) { + a[0] = a[1] + a[2] + a[3]; + } + + float4 main() : SV_Target { + float arr[4] = {1, 1, 1, 1}; + fn(arr); + return float4(arr[0], arr[1], arr[2], arr[3]); + } + +In C or C++, the array parameter decays to a pointer, so after the call to +``fn``, the value of ``arr[0]`` is ``3``. In HLSL, the array is passed by value, +so modifications inside ``fn`` do not propagate out. + +.. note:: + + DXC supports unsized arrays passed directly as decayed pointers, which is an + unfortunate behavior divergence. + +Out Parameter Temporaries +------------------------- + +.. code-block:: c++ + + void Init(inout int X, inout int Y) { + Y = 2; + X = 1; + } + + void main() { + int V; + Init(V, V); // MSVC ABI V == 2, Itanium V == 1 + } + +In the above example the ``Init`` function's behavior depends on the C++ ABI +implementation. In the MSVC C++ ABI (used for the HLSL DXIL target), call +arguments are emitted right-to-left and destroyed left-to-right. This means that +the parameter initialization and destruction occurs in the order: {``Y``, +``X``, ``~X``, ``~Y``}. This causes the write-back of the value of ``Y`` to occur +last, so the resulting value of ``V`` is ``2``. In the Itanium C++ ABI, the +parameter ordering is reversed, so the initialization and destruction occurs in +the order: {``X``, ``Y``, ``~Y``, ``X``}. This causes the write-back of the +value ``X`` to occur last, resulting in the value of ``V`` being set to ``1``. + +.. code-block:: c++ + + void Trunc(inout int3 V) { } + + + void main() { + float3 F = {1.5, 2.6, 3.3}; + Trunc(F); // F == {1.0, 2.0, 3.0} + } + +In the above example, the argument expression ``F`` undergoes element-wise +conversion from a float vector to an integer vector to create a temporary +``int3``. On expiration the temporary undergoes elementwise conversion back to +the floating point vector type ``float3``. This results in an implicit +truncation of the vector even if the value is unused in the function. + + +.. code-block:: c++ + + void UB(out int X) {} + + void main() { + int X = 7; + UB(X); // X is undefined! + } + +In this example an initialized value is passed to an ``out`` parameter. +Parameters marked ``out`` are not initialized by the argument expression or +implicitly by the function. They must be explicitly initialized. In this case +the argument is not initialized in the function so the temporary is still +uninitialized when it is copied back to the argument expression. This is +undefined behavior in HLSL, and may be illegal in generated programs. ---------------- llvm-beanz wrote:
I'll try and wordsmith so that it is more clear. The intent was to capture that we would generate invalid output resulting in a late compiler error or validation failure. https://github.com/llvm/llvm-project/pull/75397 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits