[llvm] [clang] [clang-tools-extra] [HLSL][Docs] Add documentation for HLSL functions (PR #75397)

Chris B via llvm-commits llvm-commits at lists.llvm.org
Thu Jan 11 10:36:08 PST 2024


================
@@ -0,0 +1,321 @@
+===================
+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 parameters that are both input
+and output, 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 may pass unsized arrays 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 (or clang-cl) V == 2, Clang V == 1
+  }
+
+In the above example the ``Init`` function's behavior depends on the C++
+implementation. C++ does not define the order in which parameters are
+initialized or destroyed. In MSVC and Clang's MSVC compatibility mode, 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
+element-wise conversion of the vector even if the value is unused in the
+function (effectively truncating the floating point values).
+
+
+.. 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 any use of the argument after the call is a use
+of an undefined value which may be illegal in the target (DXIL programs with
+used or potentially used ``undef`` or ``poison`` values fail validation).
+
+Clang Implementation 
----------------
llvm-beanz wrote:

Updated. I'm not sure why the doc build didn't encounter any issues for me locally, but should be fixed now.

https://github.com/llvm/llvm-project/pull/75397


More information about the llvm-commits mailing list