[clang] [HLSL][Docs] Add documentation for HLSL functions (PR #75397)

Chris B via cfe-commits cfe-commits at lists.llvm.org
Thu Dec 14 07:22:25 PST 2023


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

>From 4ebc0c58dc751f422de85b0909636cb1a87f8ce4 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Wed, 13 Dec 2023 16:44:09 -0600
Subject: [PATCH 1/4] [HLSL][Docs] Add documentation for HLSL functions

This adds a new document that covers the HLSL approach to function
calls and parameter semantics. At time of writing this document is a
proposal for the implementation.
---
 clang/docs/HLSL/FunctionCalls.rst | 316 ++++++++++++++++++++++++++++++
 clang/docs/HLSL/HLSLDocs.rst      |   1 +
 2 files changed, 317 insertions(+)
 create mode 100644 clang/docs/HLSL/FunctionCalls.rst

diff --git a/clang/docs/HLSL/FunctionCalls.rst b/clang/docs/HLSL/FunctionCalls.rst
new file mode 100644
index 00000000000000..e62b249e011a94
--- /dev/null
+++ b/clang/docs/HLSL/FunctionCalls.rst
@@ -0,0 +1,316 @@
+===================
+HLSL Function Calls
+===================
+
+.. contents::
+   :local:
+
+Introduction
+============
+
+This document descries 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 (it is
+undefined behavior to not explicitly initialize an ``out`` parameter inside a
+function). 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(a[0], a[1], a[2], a[3]);
+  }
+
+In C or C++, the array parameter decays to a pointer, so after the call to
+``fn``, the value of ``a[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.
+
+Clang Implementation 
+====================
+
+.. note::
+
+  The implementation described here is a proposal. It has not yet been fully
+  implemented, so the current state of Clang's sources may not reflect this
+  design. A prototype implementation was built on DXC which is Clang-3.7 based.
+  The prototype can be found
+  `here <https://github.com/microsoft/DirectXShaderCompiler/pull/5249>`_. A lot
+  of the changes in the prototype implementation are restoring Clang-3.7 code
+  that was previously modified to its original state.
+
+The implementation in clang depends on two new AST nodes and minor extensions to
+Clang's existing support for Objective-C write-back arguments. The goal of this
+design is to capture the semantic details of HLSL function calls in the AST, and
+minimize the amount of magic that needs to occur during IR generation.
+
+The two new AST nodes are ``HLSLArrayTemporaryExpr`` and ``HLSLOutParamExpr``,
+which respectively represent the temporaries used for passing arrays by value
+and the temporaries created for function outputs.
+
+Array Temporaries
+-----------------
+
+The ``HLSLArrayTemporaryExpr`` represents temporary values for input
+constant-sized array arguments. This applies for all constant-sized array
+arguments regardless of whether or not the parameter is constant-sized or
+unsized.
+
+.. code-block:: c++
+
+  void SizedArray(float a[4]);
+  void UnsizedArray(float a[]);
+
+  void main() {
+    float arr[4] = {1, 1, 1, 1};
+    SizedArray(arr);
+    UnsizedArray(arr);
+  }
+
+In the example above, the following AST is generated for the call to
+``SizedArray``:
+
+.. code-block:: text
+
+  CallExpr 'void'
+  |-ImplicitCastExpr 'void (*)(float [4])' <FunctionToPointerDecay>
+  | `-DeclRefExpr 'void (float [4])' lvalue Function 'SizedArray' 'void (float [4])'
+  `-HLSLArrayTemporaryExpr 'float [4]'
+    `-DeclRefExpr 'float [4]' lvalue Var 'arr' 'float [4]'
+
+In the example above, the following AST is generated for the call to
+``UnsizedArray``:
+
+.. code-block:: text
+
+  CallExpr 'void'
+  |-ImplicitCastExpr 'void (*)(float [])' <FunctionToPointerDecay>
+  | `-DeclRefExpr 'void (float [])' lvalue Function 'UnsizedArray' 'void (float [])'
+  `-HLSLArrayTemporaryExpr 'float [4]'
+    `-DeclRefExpr 'float [4]' lvalue Var 'arr' 'float [4]'
+
+In both of these cases the argument expression is of known array size so we can
+initialize an appropriately sized temporary.
+
+It is illegal in HLSL to convert an unsized array to a sized array:
+
+.. code-block:: c++
+
+  void SizedArray(float a[4]);
+  void UnsizedArray(float a[]) {
+    SizedArray(a); // Cannot convert float[] to float[4]
+  }
+
+When converting a sized array to an unsized array, an array temporary can also
+be inserted. Given the following code:
+
+.. code-block:: c++
+
+  void UnsizedArray(float a[]);
+  void SizedArray(float a[4]) {
+    UnsizedArray(a);
+  }
+
+An expected AST should be something like:
+
+.. code-block:: text
+
+  CallExpr 'void'
+  |-ImplicitCastExpr 'void (*)(float [])' <FunctionToPointerDecay>
+  | `-DeclRefExpr 'void (float [])' lvalue Function 'UnsizedArray' 'void (float [])'
+  `-HLSLArrayTemporaryExpr 'float [4]'
+    `-DeclRefExpr 'float [4]' lvalue Var 'arr' 'float [4]'
+
+Out Parameter Temporaries
+-------------------------
+
+Output parameters are defined in HLSL as *casting expiring values* (cx-values),
+which is a term made up for HLSL. A cx-value is a temporary value which may be
+the result of a cast, and stores its value back to an lvalue when the value
+expires.
+
+To represent this concept in Clang we introduce a new ``HLSLOutParamExpr``. An
+``HLSLOutParamExpr`` has two forms, one with a single sub-expression and one
+with two sub-expressions.
+
+The single sub-expression form is used when the argument expression and the
+function parameter are the same type, so no cast is required. As in this
+example:
+
+.. code-block:: c++
+
+  void Init(inout int X) {
+    X = 1;
+  }
+
+  void main() {
+    int V;
+    Init(V);
+  }
+
+The expected AST formulation for this code would be something like:
+
+.. code-block:: text
+
+  CallExpr 'void'
+  |-ImplicitCastExpr 'void (*)(int &)' <FunctionToPointerDecay>
+  | `-DeclRefExpr 'void (int &)' lvalue Function  'Init' 'void (int &)'
+  |-HLSLOutParamExpr 'int' lvalue inout
+    `-DeclRefExpr 'int' lvalue Var 'V' 'int'
+
+The ``HLSLOutParamExpr`` captures that the value is ``inout`` vs ``out`` to
+denote whether or not the temporary is initialized from the sub-expression. If
+no casting is required the sub-expression denotes the lvalue expression that the
+cx-value will be copied to when the value expires.
+
+The two sub-expression form of the AST node is required when the argument type
+is not the same as the parameter type. Given this example:
+
+.. code-block:: c++
+
+  void Trunc(inout int3 V) { }
+
+
+  void main() {
+    float3 F = {1.5, 2.6, 3.3};
+    Trunc(F);
+  }
+
+For this case the ``HLSLOutParamExpr`` will have sub-expressions to record both
+casting expression sequences for the initialization and write back:
+
+.. code-block:: text
+
+  -CallExpr 'void'
+    |-ImplicitCastExpr 'void (*)(int3 &)' <FunctionToPointerDecay>
+    | `-DeclRefExpr 'void (int3 &)' lvalue Function 'inc_i32' 'void (int3 &)'
+    `-HLSLOutParamExpr 'int3' lvalue inout
+      |-ImplicitCastExpr 'float3' <IntegralToFloating>
+      | `-ImplicitCastExpr 'int3' <LValueToRValue>
+      |   `-OpaqueValueExpr 'int3' lvalue
+      `-ImplicitCastExpr 'int3' <FloatingToIntegral>
+        `-ImplicitCastExpr 'float3' <LValueToRValue>
+          `-DeclRefExpr 'float3' lvalue 'F' 'float3'
+
+In this formation the write-back casts are captured as the first sub-expression
+and they cast from an ``OpaqueValueExpr``. In IR generation we can use the
+``OpaqueValueExpr`` as a placeholder for the ``HLSLOutParamExpr``'s temporary
+value on function return.
+
+In code generation this can be implemented with some targeted extensions to the
+Objective-C write-back support. Specifically extending CGCall.cpp's
+``EmitWriteback`` function to support casting expressions and emission of
+aggregate lvalues.
diff --git a/clang/docs/HLSL/HLSLDocs.rst b/clang/docs/HLSL/HLSLDocs.rst
index a02dd2e8a96261..1f232129548d0b 100644
--- a/clang/docs/HLSL/HLSLDocs.rst
+++ b/clang/docs/HLSL/HLSLDocs.rst
@@ -14,3 +14,4 @@ HLSL Design and Implementation
    HLSLIRReference
    ResourceTypes
    EntryFunctions
+   FunctionCalls

>From 7b41f43402e585c3da45db434aec1e9efa3786bb Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Wed, 13 Dec 2023 17:15:34 -0600
Subject: [PATCH 2/4] Fix spelling

---
 clang/docs/HLSL/FunctionCalls.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/docs/HLSL/FunctionCalls.rst b/clang/docs/HLSL/FunctionCalls.rst
index e62b249e011a94..a01eecbf08c67c 100644
--- a/clang/docs/HLSL/FunctionCalls.rst
+++ b/clang/docs/HLSL/FunctionCalls.rst
@@ -8,7 +8,7 @@ HLSL Function Calls
 Introduction
 ============
 
-This document descries the design and implementation of HLSL's function call
+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.
 

>From 89004649b2176c518d11d3cc22cb2ee65dd1c7dd Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Wed, 13 Dec 2023 18:06:00 -0600
Subject: [PATCH 3/4] Fixes based on @tex3d's review. Thank you!

---
 clang/docs/HLSL/FunctionCalls.rst | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/clang/docs/HLSL/FunctionCalls.rst b/clang/docs/HLSL/FunctionCalls.rst
index a01eecbf08c67c..c0ff1b126b50b6 100644
--- a/clang/docs/HLSL/FunctionCalls.rst
+++ b/clang/docs/HLSL/FunctionCalls.rst
@@ -37,11 +37,11 @@ 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 (it is
-undefined behavior to not explicitly initialize an ``out`` parameter inside a
-function). 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.
+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
@@ -69,11 +69,11 @@ Given the following example:
   float4 main() : SV_Target {
     float arr[4] = {1, 1, 1, 1};
     fn(arr);
-    return float4(a[0], a[1], a[2], a[3]);
+    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 ``a[0]`` is ``3``. In HLSL, the array is passed by value,
+``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::

>From e7406c2a622184140711cf6e5ec7d097a9d39a93 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Thu, 14 Dec 2023 09:21:57 -0600
Subject: [PATCH 4/4] More PR review feedback from @tex3d!

---
 clang/docs/HLSL/FunctionCalls.rst | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/clang/docs/HLSL/FunctionCalls.rst b/clang/docs/HLSL/FunctionCalls.rst
index c0ff1b126b50b6..609eb995e0e055 100644
--- a/clang/docs/HLSL/FunctionCalls.rst
+++ b/clang/docs/HLSL/FunctionCalls.rst
@@ -120,7 +120,8 @@ 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.
+element-wise conversion of the vector even if the value is unused in the
+function (effectively truncating the floating point values).
 
 
 .. code-block:: c++
@@ -137,7 +138,9 @@ 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.
+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 
 ====================



More information about the cfe-commits mailing list