| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| //===--- AvoidReturnWithVoidValueCheck.cpp - clang-tidy -------------------===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "AvoidReturnWithVoidValueCheck.h" | ||
| #include "clang/AST/Stmt.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "clang/ASTMatchers/ASTMatchers.h" | ||
|
|
||
| using namespace clang::ast_matchers; | ||
|
|
||
| namespace clang::tidy::readability { | ||
|
|
||
| static constexpr auto IgnoreMacrosName = "IgnoreMacros"; | ||
| static constexpr auto IgnoreMacrosDefault = true; | ||
|
|
||
| static constexpr auto StrictModeName = "StrictMode"; | ||
| static constexpr auto StrictModeDefault = true; | ||
|
|
||
| AvoidReturnWithVoidValueCheck::AvoidReturnWithVoidValueCheck( | ||
| StringRef Name, ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context), | ||
| IgnoreMacros( | ||
| Options.getLocalOrGlobal(IgnoreMacrosName, IgnoreMacrosDefault)), | ||
| StrictMode(Options.getLocalOrGlobal(StrictModeName, StrictModeDefault)) {} | ||
|
|
||
| void AvoidReturnWithVoidValueCheck::registerMatchers(MatchFinder *Finder) { | ||
| Finder->addMatcher( | ||
| returnStmt( | ||
| hasReturnValue(allOf(hasType(voidType()), unless(initListExpr()))), | ||
| optionally(hasParent(compoundStmt().bind("compound_parent")))) | ||
| .bind("void_return"), | ||
| this); | ||
| } | ||
|
|
||
| void AvoidReturnWithVoidValueCheck::check( | ||
| const MatchFinder::MatchResult &Result) { | ||
| const auto *VoidReturn = Result.Nodes.getNodeAs<ReturnStmt>("void_return"); | ||
| if (IgnoreMacros && VoidReturn->getBeginLoc().isMacroID()) | ||
| return; | ||
| if (!StrictMode && !Result.Nodes.getNodeAs<CompoundStmt>("compound_parent")) | ||
| return; | ||
| diag(VoidReturn->getBeginLoc(), "return statement within a void function " | ||
| "should not have a specified return value"); | ||
| } | ||
|
|
||
| void AvoidReturnWithVoidValueCheck::storeOptions( | ||
| ClangTidyOptions::OptionMap &Opts) { | ||
| Options.store(Opts, IgnoreMacrosName, IgnoreMacros); | ||
| Options.store(Opts, StrictModeName, StrictMode); | ||
| } | ||
|
|
||
| } // namespace clang::tidy::readability |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| //===--- AvoidReturnWithVoidValueCheck.h - clang-tidy -----------*- C++ -*-===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOIDRETURNWITHVOIDVALUECHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOIDRETURNWITHVOIDVALUECHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
|
|
||
| namespace clang::tidy::readability { | ||
|
|
||
| /// Finds return statements with `void` values used within functions with `void` | ||
| /// result types. | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/readability/avoid-return-with-void-value.html | ||
| class AvoidReturnWithVoidValueCheck : public ClangTidyCheck { | ||
| public: | ||
| AvoidReturnWithVoidValueCheck(StringRef Name, ClangTidyContext *Context); | ||
|
|
||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
|
|
||
| private: | ||
| std::optional<TraversalKind> getCheckTraversalKind() const override { | ||
| return TK_IgnoreUnlessSpelledInSource; | ||
| } | ||
| bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
| return LangOpts.CPlusPlus; | ||
| } | ||
| void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
|
|
||
| private: | ||
| bool IgnoreMacros; | ||
| bool StrictMode; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::readability | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOIDRETURNWITHVOIDVALUECHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| .. title:: clang-tidy - readability-avoid-return-with-void-value | ||
|
|
||
| readability-avoid-return-with-void-value | ||
| ======================================== | ||
|
|
||
| Finds return statements with ``void`` values used within functions with | ||
| ``void`` result types. | ||
|
|
||
| A function with a ``void`` return type is intended to perform a task without | ||
| producing a return value. Return statements with expressions could lead | ||
| to confusion and may miscommunicate the function's intended behavior. | ||
|
|
||
| Example: | ||
|
|
||
| .. code-block:: | ||
| void g(); | ||
| void f() { | ||
| // ... | ||
| return g(); | ||
| } | ||
| In a long function body, the ``return`` statement suggests that the function | ||
| returns a value. However, ``return g();`` is a combination of two statements | ||
| that should be written as | ||
|
|
||
| .. code-block:: | ||
| g(); | ||
| return; | ||
| to make clear that ``g()`` is called and immediately afterwards the function | ||
| returns (nothing). | ||
|
|
||
| In C, the same issue is detected by the compiler if the ``-Wpedantic`` mode | ||
| is enabled. | ||
|
|
||
| Options | ||
| ------- | ||
|
|
||
| .. option:: IgnoreMacros | ||
|
|
||
| The value `false` specifies that return statements expanded | ||
| from macros are not checked. The default value is `true`. | ||
|
|
||
| .. option:: StrictMode | ||
|
|
||
| The value `false` specifies that a direct return statement shall | ||
| be excluded from the analysis if it is the only statement not | ||
| contained in a block like ``if (cond) return g();``. The default | ||
| value is `true`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // RUN: %check_clang_tidy %s readability-avoid-return-with-void-value %t | ||
| // RUN: %check_clang_tidy -check-suffixes=,INCLUDE-MACROS %s readability-avoid-return-with-void-value %t \ | ||
| // RUN: -- -config="{CheckOptions: [{key: readability-avoid-return-with-void-value.IgnoreMacros, value: false}]}" \ | ||
| // RUN: -- | ||
| // RUN: %check_clang_tidy -check-suffixes=LENIENT %s readability-avoid-return-with-void-value %t \ | ||
| // RUN: -- -config="{CheckOptions: [{key: readability-avoid-return-with-void-value.StrictMode, value: false}]}" \ | ||
| // RUN: -- | ||
|
|
||
| void f1(); | ||
|
|
||
| void f2() { | ||
| return f1(); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| // CHECK-MESSAGES-LENIENT: :[[@LINE-2]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| } | ||
|
|
||
| void f3(bool b) { | ||
| if (b) return f1(); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| return f2(); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| // CHECK-MESSAGES-LENIENT: :[[@LINE-2]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| } | ||
|
|
||
| template<class T> | ||
| T f4() {} | ||
|
|
||
| void f5() { | ||
| return f4<void>(); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| // CHECK-MESSAGES-LENIENT: :[[@LINE-2]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| } | ||
|
|
||
| void f6() { return; } | ||
|
|
||
| int f7() { return 1; } | ||
|
|
||
| int f8() { return f7(); } | ||
|
|
||
| void f9() { | ||
| return (void)f7(); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| // CHECK-MESSAGES-LENIENT: :[[@LINE-2]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| } | ||
|
|
||
| #define RETURN_VOID return (void)1 | ||
|
|
||
| void f10() { | ||
| RETURN_VOID; | ||
| // CHECK-MESSAGES-INCLUDE-MACROS: :[[@LINE-1]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| } | ||
|
|
||
| template <typename A> | ||
| struct C { | ||
| C(A) {} | ||
| }; | ||
|
|
||
| template <class T> | ||
| C<T> f11() { return {}; } | ||
|
|
||
| using VOID = void; | ||
|
|
||
| VOID f12(); | ||
|
|
||
| VOID f13() { | ||
| return f12(); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| // CHECK-MESSAGES-LENIENT: :[[@LINE-2]]:5: warning: return statement within a void function should not have a specified return value [readability-avoid-return-with-void-value] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| # Note that the readability-identifier-naming check is disabled, there are too | ||
| # many violations in the codebase and they create too much noise in clang-tidy | ||
| # results. | ||
| Checks: '-readability-identifier-naming, -misc-include*' | ||
| InheritParentConfig: true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # Plain options configure the first build. | ||
| # BOOTSTRAP_* options configure the second build. | ||
| # BOOTSTRAP_BOOTSTRAP_* options configure the third build. | ||
|
|
||
| set(CMAKE_BUILD_TYPE RELEASE CACHE STRING "") | ||
|
|
||
| # Stage 1 Bootstrap Setup | ||
| set(CLANG_ENABLE_BOOTSTRAP ON CACHE BOOL "") | ||
| set(CLANG_BOOTSTRAP_TARGETS | ||
| clang | ||
| check-all | ||
| check-llvm | ||
| check-clang | ||
| test-suite | ||
| stage3 | ||
| stage3-clang | ||
| stage3-check-all | ||
| stage3-check-llvm | ||
| stage3-check-clang | ||
| stage3-install | ||
| stage3-test-suite CACHE STRING "") | ||
|
|
||
| # Stage 1 Options | ||
| set(LLVM_ENABLE_PROJECTS "clang" CACHE STRING "") | ||
| set(LLVM_TARGETS_TO_BUILD Native CACHE STRING "") | ||
|
|
||
| # Stage 2 Bootstrap Setup | ||
| set(BOOTSTRAP_CLANG_ENABLE_BOOTSTRAP ON CACHE STRING "") | ||
| set(BOOTSTRAP_CLANG_BOOTSTRAP_TARGETS | ||
| clang | ||
| check-all | ||
| check-llvm | ||
| check-clang CACHE STRING "") | ||
|
|
||
| # Stage 2 Options | ||
| set(BOOTSTRAP_LLVM_ENABLE_PROJECTS "clang" CACHE STRING "") | ||
| set(BOOTSTRAP_LLVM_TARGETS_TO_BUILD Native CACHE STRING "") | ||
|
|
||
| # Stage 3 Options | ||
| set(BOOTSTRAP_BOOTSTRAP_LLVM_ENABLE_RUNTIMES "compiler-rt;libcxx;libcxxabi;libunwind" CACHE STRING "") | ||
| set(BOOTSTRAP_BOOTSTRAP_LLVM_ENABLE_PROJECTS "clang;lld;lldb;clang-tools-extra;bolt;polly;mlir;flang" CACHE STRING "") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| ==================== | ||
|
|
||
| .. 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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,3 +14,4 @@ HLSL Design and Implementation | |
| HLSLIRReference | ||
| ResourceTypes | ||
| EntryFunctions | ||
| FunctionCalls | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,6 @@ | ||
| BasedOnStyle: LLVM | ||
| InsertBraces: true | ||
| InsertNewlineAtEOF: true | ||
| LineEnding: LF | ||
| RemoveBracesLLVM: true | ||
| RemoveParentheses: ReturnStatement |