[clang] 89fb849 - [HLSL] Implement output parameter (#101083)

via cfe-commits cfe-commits at lists.llvm.org
Sat Aug 31 08:59:12 PDT 2024


Author: Chris B
Date: 2024-08-31T10:59:08-05:00
New Revision: 89fb8490a99e612f7a574e8678b21a90f689f5b4

URL: https://github.com/llvm/llvm-project/commit/89fb8490a99e612f7a574e8678b21a90f689f5b4
DIFF: https://github.com/llvm/llvm-project/commit/89fb8490a99e612f7a574e8678b21a90f689f5b4.diff

LOG: [HLSL] Implement output parameter (#101083)

HLSL output parameters are denoted with the `inout` and `out` keywords
in the function declaration. When an argument to an output parameter is
constructed a temporary value is constructed for the argument.

For `inout` pamameters the argument is initialized via copy-initialization
from the argument lvalue expression to the parameter type. For `out`
parameters the argument is not initialized before the call.

In both cases on return of the function the temporary value is written
back to the argument lvalue expression through an implicit assignment
binary operator with casting as required.

This change introduces a new HLSLOutArgExpr ast node which represents
the output argument behavior. The OutArgExpr has three defined children:
- An OpaqueValueExpr of the argument lvalue expression.
- An OpaqueValueExpr of the copy-initialized parameter.
- A BinaryOpExpr assigning the first with the value of the second.

Fixes #87526

---------

Co-authored-by: Damyan Pepper <damyanp at microsoft.com>
Co-authored-by: John McCall <rjmccall at gmail.com>

Added: 
    clang/test/AST/HLSL/OutArgExpr.hlsl
    clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
    clang/test/SemaHLSL/Language/OutputParameters.hlsl
    clang/test/SemaHLSL/Language/TemplateOutArg.hlsl

Modified: 
    clang/include/clang/AST/ASTContext.h
    clang/include/clang/AST/Attr.h
    clang/include/clang/AST/Expr.h
    clang/include/clang/AST/RecursiveASTVisitor.h
    clang/include/clang/AST/TextNodeDumper.h
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Basic/Specifiers.h
    clang/include/clang/Basic/StmtNodes.td
    clang/include/clang/Sema/SemaHLSL.h
    clang/include/clang/Serialization/ASTBitCodes.h
    clang/lib/AST/ASTContext.cpp
    clang/lib/AST/Expr.cpp
    clang/lib/AST/ExprClassification.cpp
    clang/lib/AST/ExprConstant.cpp
    clang/lib/AST/ItaniumMangle.cpp
    clang/lib/AST/StmtPrinter.cpp
    clang/lib/AST/StmtProfile.cpp
    clang/lib/AST/TextNodeDumper.cpp
    clang/lib/AST/TypePrinter.cpp
    clang/lib/CodeGen/CGCall.cpp
    clang/lib/CodeGen/CGCall.h
    clang/lib/CodeGen/CGExpr.cpp
    clang/lib/CodeGen/CGExprAgg.cpp
    clang/lib/CodeGen/CodeGenFunction.h
    clang/lib/Sema/SemaChecking.cpp
    clang/lib/Sema/SemaDecl.cpp
    clang/lib/Sema/SemaExceptionSpec.cpp
    clang/lib/Sema/SemaExpr.cpp
    clang/lib/Sema/SemaHLSL.cpp
    clang/lib/Sema/SemaOverload.cpp
    clang/lib/Sema/SemaSwift.cpp
    clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
    clang/lib/Sema/SemaType.cpp
    clang/lib/Sema/TreeTransform.h
    clang/lib/Serialization/ASTReaderStmt.cpp
    clang/lib/Serialization/ASTWriterStmt.cpp
    clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
    clang/test/SemaHLSL/parameter_modifiers.hlsl
    clang/test/SemaHLSL/parameter_modifiers_ast.hlsl
    clang/tools/libclang/CXCursor.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 8156d283f8fe21..3c43892b6ff719 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1381,6 +1381,14 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// in the return type and parameter types.
   bool hasSameFunctionTypeIgnoringPtrSizes(QualType T, QualType U);
 
+  /// Get or construct a function type that is equivalent to the input type
+  /// except that the parameter ABI annotations are stripped.
+  QualType getFunctionTypeWithoutParamABIs(QualType T) const;
+
+  /// Determine if two function types are the same, ignoring parameter ABI
+  /// annotations.
+  bool hasSameFunctionTypeIgnoringParamABI(QualType T, QualType U) const;
+
   /// Return the uniqued reference to the type for a complex
   /// number with the specified element type.
   QualType getComplexType(QualType T) const;

diff  --git a/clang/include/clang/AST/Attr.h b/clang/include/clang/AST/Attr.h
index bd1851a26ce2e1..ac44e9fdd7c4e9 100644
--- a/clang/include/clang/AST/Attr.h
+++ b/clang/include/clang/AST/Attr.h
@@ -224,20 +224,7 @@ class ParameterABIAttr : public InheritableParamAttr {
                              InheritEvenIfAlreadyPresent) {}
 
 public:
-  ParameterABI getABI() const {
-    switch (getKind()) {
-    case attr::SwiftContext:
-      return ParameterABI::SwiftContext;
-    case attr::SwiftAsyncContext:
-      return ParameterABI::SwiftAsyncContext;
-    case attr::SwiftErrorResult:
-      return ParameterABI::SwiftErrorResult;
-    case attr::SwiftIndirectResult:
-      return ParameterABI::SwiftIndirectResult;
-    default:
-      llvm_unreachable("bad parameter ABI attribute kind");
-    }
-  }
+  ParameterABI getABI() const;
 
   static bool classof(const Attr *A) {
     return A->getKind() >= attr::FirstParameterABIAttr &&
@@ -379,6 +366,29 @@ inline const StreamingDiagnostic &operator<<(const StreamingDiagnostic &DB,
   DB.AddTaggedVal(reinterpret_cast<uint64_t>(At), DiagnosticsEngine::ak_attr);
   return DB;
 }
+
+inline ParameterABI ParameterABIAttr::getABI() const {
+  switch (getKind()) {
+  case attr::SwiftContext:
+    return ParameterABI::SwiftContext;
+  case attr::SwiftAsyncContext:
+    return ParameterABI::SwiftAsyncContext;
+  case attr::SwiftErrorResult:
+    return ParameterABI::SwiftErrorResult;
+  case attr::SwiftIndirectResult:
+    return ParameterABI::SwiftIndirectResult;
+  case attr::HLSLParamModifier: {
+    const auto *A = cast<HLSLParamModifierAttr>(this);
+    if (A->isOut())
+      return ParameterABI::HLSLOut;
+    if (A->isInOut())
+      return ParameterABI::HLSLInOut;
+    return ParameterABI::Ordinary;
+  }
+  default:
+    llvm_unreachable("bad parameter ABI attribute kind");
+  }
+}
 }  // end namespace clang
 
 #endif

diff  --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 7bacf028192c65..65104acda93825 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -7071,6 +7071,103 @@ class ArraySectionExpr : public Expr {
   void setRBracketLoc(SourceLocation L) { RBracketLoc = L; }
 };
 
+/// This class represents temporary values used to represent inout and out
+/// arguments in HLSL. From the callee perspective these parameters are more or
+/// less __restrict__ T&. They are guaranteed to not alias any memory. inout
+/// parameters are initialized by the caller, and out parameters are references
+/// to uninitialized memory.
+///
+/// In the caller, the argument expression creates a temporary in local memory
+/// and the address of the temporary is passed into the callee. There may be
+/// implicit conversion sequences to initialize the temporary, and on expiration
+/// of the temporary an inverse conversion sequence is applied as a write-back
+/// conversion to the source l-value.
+///
+/// This AST node has three sub-expressions:
+///  - An OpaqueValueExpr with a source that is the argument lvalue expression.
+///  - An OpaqueValueExpr with a source that is an implicit conversion
+///    sequence from the source lvalue to the argument type.
+///  - An expression that assigns the second expression into the first,
+///    performing any necessary conversions.
+class HLSLOutArgExpr : public Expr {
+  friend class ASTStmtReader;
+
+  enum {
+    BaseLValue,
+    CastedTemporary,
+    WritebackCast,
+    NumSubExprs,
+  };
+
+  Stmt *SubExprs[NumSubExprs];
+  bool IsInOut;
+
+  HLSLOutArgExpr(QualType Ty, OpaqueValueExpr *B, OpaqueValueExpr *OpV,
+                 Expr *WB, bool IsInOut)
+      : Expr(HLSLOutArgExprClass, Ty, VK_LValue, OK_Ordinary),
+        IsInOut(IsInOut) {
+    SubExprs[BaseLValue] = B;
+    SubExprs[CastedTemporary] = OpV;
+    SubExprs[WritebackCast] = WB;
+    assert(!Ty->isDependentType() && "HLSLOutArgExpr given a dependent type!");
+  }
+
+  explicit HLSLOutArgExpr(EmptyShell Shell)
+      : Expr(HLSLOutArgExprClass, Shell) {}
+
+public:
+  static HLSLOutArgExpr *Create(const ASTContext &C, QualType Ty,
+                                OpaqueValueExpr *Base, OpaqueValueExpr *OpV,
+                                Expr *WB, bool IsInOut);
+  static HLSLOutArgExpr *CreateEmpty(const ASTContext &Ctx);
+
+  const OpaqueValueExpr *getOpaqueArgLValue() const {
+    return cast<OpaqueValueExpr>(SubExprs[BaseLValue]);
+  }
+  OpaqueValueExpr *getOpaqueArgLValue() {
+    return cast<OpaqueValueExpr>(SubExprs[BaseLValue]);
+  }
+
+  /// Return the l-value expression that was written as the argument
+  /// in source.  Everything else here is implicitly generated.
+  const Expr *getArgLValue() const {
+    return getOpaqueArgLValue()->getSourceExpr();
+  }
+  Expr *getArgLValue() { return getOpaqueArgLValue()->getSourceExpr(); }
+
+  const Expr *getWritebackCast() const {
+    return cast<Expr>(SubExprs[WritebackCast]);
+  }
+  Expr *getWritebackCast() { return cast<Expr>(SubExprs[WritebackCast]); }
+
+  const OpaqueValueExpr *getCastedTemporary() const {
+    return cast<OpaqueValueExpr>(SubExprs[CastedTemporary]);
+  }
+  OpaqueValueExpr *getCastedTemporary() {
+    return cast<OpaqueValueExpr>(SubExprs[CastedTemporary]);
+  }
+
+  /// returns true if the parameter is inout and false if the parameter is out.
+  bool isInOut() const { return IsInOut; }
+
+  SourceLocation getBeginLoc() const LLVM_READONLY {
+    return SubExprs[BaseLValue]->getBeginLoc();
+  }
+
+  SourceLocation getEndLoc() const LLVM_READONLY {
+    return SubExprs[BaseLValue]->getEndLoc();
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == HLSLOutArgExprClass;
+  }
+
+  // Iterators
+  child_range children() {
+    return child_range(&SubExprs[BaseLValue], &SubExprs[NumSubExprs]);
+  }
+};
+
 /// Frontend produces RecoveryExprs on semantic errors that prevent creating
 /// other well-formed expressions. E.g. when type-checking of a binary operator
 /// fails, we cannot produce a BinaryOperator expression. Instead, we can choose

diff  --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index e51b04cce39eb9..3389670a2ab9d9 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -4055,6 +4055,9 @@ DEF_TRAVERSE_STMT(OpenACCComputeConstruct,
 DEF_TRAVERSE_STMT(OpenACCLoopConstruct,
                   { TRY_TO(TraverseOpenACCAssociatedStmtConstruct(S)); })
 
+// Traverse HLSL: Out argument expression
+DEF_TRAVERSE_STMT(HLSLOutArgExpr, {})
+
 // FIXME: look at the following tricky-seeming exprs to see if we
 // need to recurse on anything.  These are ones that have methods
 // returning decls or qualtypes or nestednamespecifier -- though I'm

diff  --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 88d5535829910f..57100e7ede171c 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -407,6 +407,7 @@ class TextNodeDumper
   void
   VisitLifetimeExtendedTemporaryDecl(const LifetimeExtendedTemporaryDecl *D);
   void VisitHLSLBufferDecl(const HLSLBufferDecl *D);
+  void VisitHLSLOutArgExpr(const HLSLOutArgExpr *E);
   void VisitOpenACCConstructStmt(const OpenACCConstructStmt *S);
   void VisitOpenACCLoopConstruct(const OpenACCLoopConstruct *S);
   void VisitEmbedExpr(const EmbedExpr *S);

diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 0d4256433365c4..79d77b4d49b8fe 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4639,14 +4639,13 @@ def HLSLGroupSharedAddressSpace : TypeAttr {
   let Documentation = [HLSLGroupSharedAddressSpaceDocs];
 }
 
-def HLSLParamModifier : TypeAttr {
+def HLSLParamModifier : ParameterABIAttr {
   let Spellings = [CustomKeyword<"in">, CustomKeyword<"inout">, CustomKeyword<"out">];
   let Accessors = [Accessor<"isIn", [CustomKeyword<"in">]>,
                    Accessor<"isInOut", [CustomKeyword<"inout">]>,
                    Accessor<"isOut", [CustomKeyword<"out">]>,
                    Accessor<"isAnyOut", [CustomKeyword<"out">, CustomKeyword<"inout">]>,
                    Accessor<"isAnyIn", [CustomKeyword<"in">, CustomKeyword<"inout">]>];
-  let Subjects = SubjectList<[ParmVar]>;
   let Documentation = [HLSLParamQualifierDocs];
   let Args = [DefaultBoolArgument<"MergedSpelling", /*default*/0, /*fake*/1>];
 }

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 2e759b5b67b68d..66f8890da75e5d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12380,6 +12380,8 @@ def warn_hlsl_availability : Warning<
 def warn_hlsl_availability_unavailable :
   Warning<err_unavailable.Summary>,
   InGroup<HLSLAvailability>, DefaultError;
+def error_hlsl_inout_scalar_extension : Error<"illegal scalar extension cast on argument %0 to %select{|in}1out paramemter">;
+def error_hlsl_inout_lvalue : Error<"cannot bind non-lvalue argument %0 to %select{|in}1out paramemter">;
 
 def err_hlsl_export_not_on_function : Error<
   "export declaration can only be used on functions">;

diff  --git a/clang/include/clang/Basic/Specifiers.h b/clang/include/clang/Basic/Specifiers.h
index 7bfa26543578da..9c089908fdc130 100644
--- a/clang/include/clang/Basic/Specifiers.h
+++ b/clang/include/clang/Basic/Specifiers.h
@@ -385,6 +385,12 @@ namespace clang {
     /// Swift asynchronous context-pointer ABI treatment.  There can be at
     /// most one parameter on a given function that uses this treatment.
     SwiftAsyncContext,
+
+    // This parameter is a copy-out HLSL parameter.
+    HLSLOut,
+
+    // This parameter is a copy-in/copy-out HLSL parameter.
+    HLSLInOut,
   };
 
   /// Assigned inheritance model for a class in the MS C++ ABI. Must match order

diff  --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index e4e4343ec6f923..30f2c8f1dbfde8 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -307,3 +307,6 @@ def OpenACCAssociatedStmtConstruct
     : StmtNode<OpenACCConstructStmt, /*abstract=*/1>;
 def OpenACCComputeConstruct : StmtNode<OpenACCAssociatedStmtConstruct>;
 def OpenACCLoopConstruct : StmtNode<OpenACCAssociatedStmtConstruct>;
+
+// HLSL Constructs.
+def HLSLOutArgExpr : StmtNode<Expr>;

diff  --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 210eb1167aa6ef..d79ca9a4fa18d1 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -71,6 +71,12 @@ class SemaHLSL : public SemaBase {
 
   // HLSL Type trait implementations
   bool IsScalarizedLayoutCompatible(QualType T1, QualType T2) const;
+
+  bool CheckCompatibleParameterABI(FunctionDecl *New, FunctionDecl *Old);
+
+  ExprResult ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg);
+
+  QualType getInoutParameterType(QualType Ty);
 };
 
 } // namespace clang

diff  --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 93d28fbef8e456..4410df296d8efc 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1995,6 +1995,9 @@ enum StmtCode {
   // OpenACC Constructs
   STMT_OPENACC_COMPUTE_CONSTRUCT,
   STMT_OPENACC_LOOP_CONSTRUCT,
+
+  // HLSL Constructs
+  EXPR_HLSL_OUT_ARG,
 };
 
 /// The kinds of designators that can occur in a

diff  --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index f108952d7bbf73..7e3713f44801a2 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3612,6 +3612,21 @@ bool ASTContext::hasSameFunctionTypeIgnoringPtrSizes(QualType T, QualType U) {
                      getFunctionTypeWithoutPtrSizes(U));
 }
 
+QualType ASTContext::getFunctionTypeWithoutParamABIs(QualType T) const {
+  if (const auto *Proto = T->getAs<FunctionProtoType>()) {
+    FunctionProtoType::ExtProtoInfo EPI = Proto->getExtProtoInfo();
+    EPI.ExtParameterInfos = nullptr;
+    return getFunctionType(Proto->getReturnType(), Proto->param_types(), EPI);
+  }
+  return T;
+}
+
+bool ASTContext::hasSameFunctionTypeIgnoringParamABI(QualType T,
+                                                     QualType U) const {
+  return hasSameType(T, U) || hasSameType(getFunctionTypeWithoutParamABIs(T),
+                                          getFunctionTypeWithoutParamABIs(U));
+}
+
 void ASTContext::adjustExceptionSpec(
     FunctionDecl *FD, const FunctionProtoType::ExceptionSpecInfo &ESI,
     bool AsWritten) {

diff  --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 3309619850f34a..96c6276f3f34c1 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3631,6 +3631,7 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
   case RequiresExprClass:
   case SYCLUniqueStableNameExprClass:
   case PackIndexingExprClass:
+  case HLSLOutArgExprClass:
     // These never have a side-effect.
     return false;
 
@@ -5388,3 +5389,14 @@ OMPIteratorExpr *OMPIteratorExpr::CreateEmpty(const ASTContext &Context,
       alignof(OMPIteratorExpr));
   return new (Mem) OMPIteratorExpr(EmptyShell(), NumIterators);
 }
+
+HLSLOutArgExpr *HLSLOutArgExpr::Create(const ASTContext &C, QualType Ty,
+                                       OpaqueValueExpr *Base,
+                                       OpaqueValueExpr *OpV, Expr *WB,
+                                       bool IsInOut) {
+  return new (C) HLSLOutArgExpr(Ty, Base, OpV, WB, IsInOut);
+}
+
+HLSLOutArgExpr *HLSLOutArgExpr::CreateEmpty(const ASTContext &C) {
+  return new (C) HLSLOutArgExpr(EmptyShell());
+}

diff  --git a/clang/lib/AST/ExprClassification.cpp b/clang/lib/AST/ExprClassification.cpp
index 1f1b8abc09b9e2..5dde923312698f 100644
--- a/clang/lib/AST/ExprClassification.cpp
+++ b/clang/lib/AST/ExprClassification.cpp
@@ -142,6 +142,7 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) {
   case Expr::ArraySectionExprClass:
   case Expr::OMPArrayShapingExprClass:
   case Expr::OMPIteratorExprClass:
+  case Expr::HLSLOutArgExprClass:
     return Cl::CL_LValue;
 
     // C++ [expr.prim.general]p1: A string literal is an lvalue.

diff  --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index e8a4d1d3c74102..b5dfd4dd32b63c 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -16640,6 +16640,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
   case Expr::CoyieldExprClass:
   case Expr::SYCLUniqueStableNameExprClass:
   case Expr::CXXParenListInitExprClass:
+  case Expr::HLSLOutArgExprClass:
     return ICEDiag(IK_NotICE, E->getBeginLoc());
 
   case Expr::InitListExprClass: {

diff  --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 1ad8cf5a1c911f..1a47caac5a503c 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -3518,6 +3518,12 @@ CXXNameMangler::mangleExtParameterInfo(FunctionProtoType::ExtParameterInfo PI) {
   case ParameterABI::Ordinary:
     break;
 
+  // HLSL parameter mangling.
+  case ParameterABI::HLSLOut:
+  case ParameterABI::HLSLInOut:
+    mangleVendorQualifier(getParameterABISpelling(PI.getABI()));
+    break;
+
   // All of these start with "swift", so they come before "ns_consumed".
   case ParameterABI::SwiftContext:
   case ParameterABI::SwiftAsyncContext:
@@ -5730,6 +5736,9 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
     Out << "E";
     break;
   }
+  case Expr::HLSLOutArgExprClass:
+    llvm_unreachable(
+        "cannot mangle hlsl temporary value; mangling wrong thing?");
   }
 
   if (AsTemplateArg && !IsPrimaryExpr)

diff  --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index 014d02220d2917..e1b5bec7a50d0a 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -2804,6 +2804,10 @@ void StmtPrinter::VisitAsTypeExpr(AsTypeExpr *Node) {
   OS << ")";
 }
 
+void StmtPrinter::VisitHLSLOutArgExpr(HLSLOutArgExpr *Node) {
+  PrintExpr(Node->getArgLValue());
+}
+
 //===----------------------------------------------------------------------===//
 // Stmt method implementations
 //===----------------------------------------------------------------------===//

diff  --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 35d8b0706fe3ce..ad4281986f668e 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -2647,6 +2647,10 @@ void StmtProfiler::VisitOpenACCLoopConstruct(const OpenACCLoopConstruct *S) {
   P.VisitOpenACCClauseList(S->clauses());
 }
 
+void StmtProfiler::VisitHLSLOutArgExpr(const HLSLOutArgExpr *S) {
+  VisitStmt(S);
+}
+
 void Stmt::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context,
                    bool Canonical, bool ProfileLambdaExpr) const {
   StmtProfilerWithPointers Profiler(ID, Context, Canonical, ProfileLambdaExpr);

diff  --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 2c962253c8bea4..c6b1b44206b24d 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -2879,6 +2879,10 @@ void TextNodeDumper::VisitHLSLBufferDecl(const HLSLBufferDecl *D) {
   dumpName(D);
 }
 
+void TextNodeDumper::VisitHLSLOutArgExpr(const HLSLOutArgExpr *E) {
+  OS << (E->isInOut() ? " inout" : " out");
+}
+
 void TextNodeDumper::VisitOpenACCConstructStmt(const OpenACCConstructStmt *S) {
   OS << " " << S->getDirectiveKind();
 }

diff  --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 92812bebeb5b47..b1d9516c96eb7a 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -940,6 +940,10 @@ StringRef clang::getParameterABISpelling(ParameterABI ABI) {
     return "swift_error_result";
   case ParameterABI::SwiftIndirectResult:
     return "swift_indirect_result";
+  case ParameterABI::HLSLOut:
+    return "out";
+  case ParameterABI::HLSLInOut:
+    return "inout";
   }
   llvm_unreachable("bad parameter ABI kind");
 }
@@ -962,7 +966,17 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
       if (EPI.isNoEscape())
         OS << "__attribute__((noescape)) ";
       auto ABI = EPI.getABI();
-      if (ABI != ParameterABI::Ordinary)
+      if (ABI == ParameterABI::HLSLInOut || ABI == ParameterABI::HLSLOut) {
+        OS << getParameterABISpelling(ABI) << " ";
+        if (Policy.UseHLSLTypes) {
+          // This is a bit of a hack because we _do_ use reference types in the
+          // AST for representing inout and out parameters so that code
+          // generation is sane, but when re-printing these for HLSL we need to
+          // skip the reference.
+          print(T->getParamType(i).getNonReferenceType(), OS, StringRef());
+          continue;
+        }
+      } else if (ABI != ParameterABI::Ordinary)
         OS << "__attribute__((" << getParameterABISpelling(ABI) << ")) ";
 
       print(T->getParamType(i), OS, StringRef());
@@ -2030,10 +2044,6 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::ArmMveStrictPolymorphism:
     OS << "__clang_arm_mve_strict_polymorphism";
     break;
-
-  // Nothing to print for this attribute.
-  case attr::HLSLParamModifier:
-    break;
   }
   OS << "))";
 }

diff  --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index ca2c79b51ac96b..997510d71c3efb 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2809,6 +2809,10 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
     }
 
     switch (FI.getExtParameterInfo(ArgNo).getABI()) {
+    case ParameterABI::HLSLOut:
+    case ParameterABI::HLSLInOut:
+      Attrs.addAttribute(llvm::Attribute::NoAlias);
+      break;
     case ParameterABI::Ordinary:
       break;
 
@@ -4132,6 +4136,15 @@ static void emitWriteback(CodeGenFunction &CGF,
   assert(!isProvablyNull(srcAddr.getBasePointer()) &&
          "shouldn't have writeback for provably null argument");
 
+  if (writeback.WritebackExpr) {
+    CGF.EmitIgnoredExpr(writeback.WritebackExpr);
+
+    if (writeback.LifetimeSz)
+      CGF.EmitLifetimeEnd(writeback.LifetimeSz,
+                          writeback.Temporary.getBasePointer());
+    return;
+  }
+
   llvm::BasicBlock *contBB = nullptr;
 
   // If the argument wasn't provably non-null, we need to null check
@@ -4594,6 +4607,9 @@ void CodeGenFunction::EmitCallArgs(
     // Un-reverse the arguments we just evaluated so they match up with the LLVM
     // IR function.
     std::reverse(Args.begin() + CallArgsStart, Args.end());
+
+    // Reverse the writebacks to match the MSVC ABI.
+    Args.reverseWritebacks();
   }
 }
 
@@ -4673,6 +4689,12 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
   assert(type->isReferenceType() == E->isGLValue() &&
          "reference binding to unmaterialized r-value!");
 
+  // Add writeback for HLSLOutParamExpr.
+  if (const HLSLOutArgExpr *OE = dyn_cast<HLSLOutArgExpr>(E)) {
+    EmitHLSLOutArgExpr(OE, args, type);
+    return;
+  }
+
   if (E->isGLValue()) {
     assert(E->getObjectKind() == OK_Ordinary);
     return args.add(EmitReferenceBindingToExpr(E), type);

diff  --git a/clang/lib/CodeGen/CGCall.h b/clang/lib/CodeGen/CGCall.h
index 412b44a8c753aa..6fa65e1916183a 100644
--- a/clang/lib/CodeGen/CGCall.h
+++ b/clang/lib/CodeGen/CGCall.h
@@ -285,6 +285,13 @@ class CallArgList : public SmallVector<CallArg, 8> {
 
     /// A value to "use" after the writeback, or null.
     llvm::Value *ToUse;
+
+    /// An Expression (optional) that performs the writeback with any required
+    /// casting.
+    const Expr *WritebackExpr;
+
+    // Size for optional lifetime end on the temporary.
+    llvm::Value *LifetimeSz;
   };
 
   struct CallArgCleanup {
@@ -316,8 +323,10 @@ class CallArgList : public SmallVector<CallArg, 8> {
       StackBase = other.StackBase;
   }
 
-  void addWriteback(LValue srcLV, Address temporary, llvm::Value *toUse) {
-    Writeback writeback = {srcLV, temporary, toUse};
+  void addWriteback(LValue srcLV, Address temporary, llvm::Value *toUse,
+                    const Expr *writebackExpr = nullptr,
+                    llvm::Value *lifetimeSz = nullptr) {
+    Writeback writeback = {srcLV, temporary, toUse, writebackExpr, lifetimeSz};
     Writebacks.push_back(writeback);
   }
 
@@ -350,6 +359,11 @@ class CallArgList : public SmallVector<CallArg, 8> {
   /// memory.
   bool isUsingInAlloca() const { return StackBase; }
 
+  // Support reversing writebacks for MSVC ABI.
+  void reverseWritebacks() {
+    std::reverse(Writebacks.begin(), Writebacks.end());
+  }
+
 private:
   SmallVector<Writeback, 1> Writebacks;
 

diff  --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 426fccb721db84..99cd61b9e78953 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -299,6 +299,29 @@ void CodeGenFunction::EmitAnyExprToMem(const Expr *E,
   llvm_unreachable("bad evaluation kind");
 }
 
+void CodeGenFunction::EmitInitializationToLValue(
+    const Expr *E, LValue LV, AggValueSlot::IsZeroed_t IsZeroed) {
+  QualType Type = LV.getType();
+  switch (getEvaluationKind(Type)) {
+  case TEK_Complex:
+    EmitComplexExprIntoLValue(E, LV, /*isInit*/ true);
+    return;
+  case TEK_Aggregate:
+    EmitAggExpr(E, AggValueSlot::forLValue(LV, AggValueSlot::IsDestructed,
+                                           AggValueSlot::DoesNotNeedGCBarriers,
+                                           AggValueSlot::IsNotAliased,
+                                           AggValueSlot::MayOverlap, IsZeroed));
+    return;
+  case TEK_Scalar:
+    if (LV.isSimple())
+      EmitScalarInit(E, /*D=*/nullptr, LV, /*Captured=*/false);
+    else
+      EmitStoreThroughLValue(RValue::get(EmitScalarExpr(E)), LV);
+    return;
+  }
+  llvm_unreachable("bad evaluation kind");
+}
+
 static void
 pushTemporaryCleanup(CodeGenFunction &CGF, const MaterializeTemporaryExpr *M,
                      const Expr *E, Address ReferenceTemporary) {
@@ -1672,6 +1695,8 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
     return EmitCoyieldLValue(cast<CoyieldExpr>(E));
   case Expr::PackIndexingExprClass:
     return EmitLValue(cast<PackIndexingExpr>(E)->getSelectedExpr());
+  case Expr::HLSLOutArgExprClass:
+    llvm_unreachable("cannot emit a HLSL out argument directly");
   }
 }
 
@@ -5432,6 +5457,36 @@ LValue CodeGenFunction::EmitOpaqueValueLValue(const OpaqueValueExpr *e) {
   return getOrCreateOpaqueLValueMapping(e);
 }
 
+void CodeGenFunction::EmitHLSLOutArgExpr(const HLSLOutArgExpr *E,
+                                         CallArgList &Args, QualType Ty) {
+
+  // Emitting the casted temporary through an opaque value.
+  LValue BaseLV = EmitLValue(E->getArgLValue());
+  OpaqueValueMappingData::bind(*this, E->getOpaqueArgLValue(), BaseLV);
+
+  QualType ExprTy = E->getType();
+  Address OutTemp = CreateIRTemp(ExprTy);
+  LValue TempLV = MakeAddrLValue(OutTemp, ExprTy);
+
+  if (E->isInOut())
+    EmitInitializationToLValue(E->getCastedTemporary()->getSourceExpr(),
+                               TempLV);
+
+  OpaqueValueMappingData::bind(*this, E->getCastedTemporary(), TempLV);
+
+  llvm::Value *Addr = TempLV.getAddress().getBasePointer();
+  llvm::Type *ElTy = ConvertTypeForMem(TempLV.getType());
+
+  llvm::TypeSize Sz = CGM.getDataLayout().getTypeAllocSize(ElTy);
+
+  llvm::Value *LifetimeSize = EmitLifetimeStart(Sz, Addr);
+
+  Address TmpAddr(Addr, ElTy, TempLV.getAlignment());
+  Args.addWriteback(BaseLV, TmpAddr, nullptr, E->getWritebackCast(),
+                    LifetimeSize);
+  Args.add(RValue::get(TmpAddr, *this), Ty);
+}
+
 LValue
 CodeGenFunction::getOrCreateOpaqueLValueMapping(const OpaqueValueExpr *e) {
   assert(OpaqueValueMapping::shouldBindAsLValue(e));

diff  --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index d9f44f4be617e5..bbfc6672ecc25a 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -1567,26 +1567,7 @@ AggExprEmitter::EmitInitializationToLValue(Expr *E, LValue LV) {
     return CGF.EmitStoreThroughLValue(RV, LV);
   }
 
-  switch (CGF.getEvaluationKind(type)) {
-  case TEK_Complex:
-    CGF.EmitComplexExprIntoLValue(E, LV, /*isInit*/ true);
-    return;
-  case TEK_Aggregate:
-    CGF.EmitAggExpr(
-        E, AggValueSlot::forLValue(LV, AggValueSlot::IsDestructed,
-                                   AggValueSlot::DoesNotNeedGCBarriers,
-                                   AggValueSlot::IsNotAliased,
-                                   AggValueSlot::MayOverlap, Dest.isZeroed()));
-    return;
-  case TEK_Scalar:
-    if (LV.isSimple()) {
-      CGF.EmitScalarInit(E, /*D=*/nullptr, LV, /*Captured=*/false);
-    } else {
-      CGF.EmitStoreThroughLValue(RValue::get(CGF.EmitScalarExpr(E)), LV);
-    }
-    return;
-  }
-  llvm_unreachable("bad evaluation kind");
+  CGF.EmitInitializationToLValue(E, LV, Dest.isZeroed());
 }
 
 void AggExprEmitter::EmitNullInitializationToLValue(LValue lv) {

diff  --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 05f85f8b95bfa2..368fc112187ffc 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -2927,6 +2927,11 @@ class CodeGenFunction : public CodeGenTypeCache {
 
   void EmitAnyExprToExn(const Expr *E, Address Addr);
 
+  /// EmitInitializationToLValue - Emit an initializer to an LValue.
+  void EmitInitializationToLValue(
+      const Expr *E, LValue LV,
+      AggValueSlot::IsZeroed_t IsZeroed = AggValueSlot::IsNotZeroed);
+
   /// EmitExprAsInit - Emits the code necessary to initialize a
   /// location in memory with the given initializer.
   void EmitExprAsInit(const Expr *init, const ValueDecl *D, LValue lvalue,
@@ -4294,6 +4299,8 @@ class CodeGenFunction : public CodeGenTypeCache {
   LValue EmitCastLValue(const CastExpr *E);
   LValue EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E);
   LValue EmitOpaqueValueLValue(const OpaqueValueExpr *e);
+  void EmitHLSLOutArgExpr(const HLSLOutArgExpr *E, CallArgList &Args,
+                          QualType Ty);
 
   Address EmitExtVectorElementLValue(LValue V);
 

diff  --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index b021e27209cf1b..b01765b6833a17 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -11428,6 +11428,18 @@ static void AnalyzeImplicitConversions(
     return;
   }
 
+  if (auto *OutArgE = dyn_cast<HLSLOutArgExpr>(E)) {
+    WorkList.push_back({OutArgE->getArgLValue(), CC, IsListInit});
+    // The base expression is only used to initialize the parameter for
+    // arguments to `inout` parameters, so we only traverse down the base
+    // expression for `inout` cases.
+    if (OutArgE->isInOut())
+      WorkList.push_back(
+          {OutArgE->getCastedTemporary()->getSourceExpr(), CC, IsListInit});
+    WorkList.push_back({OutArgE->getWritebackCast(), CC, IsListInit});
+    return;
+  }
+
   if (BinaryOperator *BO = dyn_cast<BinaryOperator>(E)) {
     // Do a somewhat 
diff erent check with comparison operators.
     if (BO->isComparisonOp())

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 69b793b987e42c..8ae1baa1b521bc 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3249,26 +3249,6 @@ static void mergeParamDeclAttributes(ParmVarDecl *newDecl,
            diag::note_carries_dependency_missing_first_decl) << 1/*Param*/;
   }
 
-  // HLSL parameter declarations for inout and out must match between
-  // declarations. In HLSL inout and out are ambiguous at the call site, but
-  // have 
diff erent calling behavior, so you cannot overload a method based on a
-  // 
diff erence between inout and out annotations.
-  if (S.getLangOpts().HLSL) {
-    const auto *NDAttr = newDecl->getAttr<HLSLParamModifierAttr>();
-    const auto *ODAttr = oldDecl->getAttr<HLSLParamModifierAttr>();
-    // We don't need to cover the case where one declaration doesn't have an
-    // attribute. The only possible case there is if one declaration has an `in`
-    // attribute and the other declaration has no attribute. This case is
-    // allowed since parameters are `in` by default.
-    if (NDAttr && ODAttr &&
-        NDAttr->getSpellingListIndex() != ODAttr->getSpellingListIndex()) {
-      S.Diag(newDecl->getLocation(), diag::err_hlsl_param_qualifier_mismatch)
-          << NDAttr << newDecl;
-      S.Diag(oldDecl->getLocation(), diag::note_previous_declaration_as)
-          << ODAttr;
-    }
-  }
-
   if (!oldDecl->hasAttrs())
     return;
 
@@ -4054,6 +4034,22 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
       }
     }
 
+    // HLSL check parameters for matching ABI specifications.
+    if (getLangOpts().HLSL) {
+      if (HLSL().CheckCompatibleParameterABI(New, Old))
+        return true;
+
+      // If no errors are generated when checking parameter ABIs we can check if
+      // the two declarations have the same type ignoring the ABIs and if so,
+      // the declarations can be merged. This case for merging is only valid in
+      // HLSL because there are no valid cases of merging mismatched parameter
+      // ABIs except the HLSL implicit in and explicit in.
+      if (Context.hasSameFunctionTypeIgnoringParamABI(OldQTypeForComparison,
+                                                      NewQType))
+        return MergeCompatibleFunctionDecls(New, Old, S, MergeTypeWithOld);
+      // Fall through for conflicting redeclarations and redefinitions.
+    }
+
     // If the function types are compatible, merge the declarations. Ignore the
     // exception specifier because it was already checked above in
     // CheckEquivalentExceptionSpec, and we don't want follow-on diagnostics

diff  --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index c4481250f345ab..8aedbfcf878a11 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -1395,6 +1395,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
   case Expr::EmbedExprClass:
   case Expr::ConceptSpecializationExprClass:
   case Expr::RequiresExprClass:
+  case Expr::HLSLOutArgExprClass:
     // These expressions can never throw.
     return CT_Cannot;
 

diff  --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 092d174a811c60..94bb938b53b441 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -53,6 +53,7 @@
 #include "clang/Sema/ScopeInfo.h"
 #include "clang/Sema/SemaCUDA.h"
 #include "clang/Sema/SemaFixItUtils.h"
+#include "clang/Sema/SemaHLSL.h"
 #include "clang/Sema/SemaInternal.h"
 #include "clang/Sema/SemaObjC.h"
 #include "clang/Sema/SemaOpenMP.h"
@@ -5927,6 +5928,13 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
           ProtoArgType->isBlockPointerType())
         if (auto *BE = dyn_cast<BlockExpr>(Arg->IgnoreParenNoopCasts(Context)))
           BE->getBlockDecl()->setDoesNotEscape();
+      if ((Proto->getExtParameterInfo(i).getABI() == ParameterABI::HLSLOut ||
+           Proto->getExtParameterInfo(i).getABI() == ParameterABI::HLSLInOut)) {
+        ExprResult ArgExpr = HLSL().ActOnOutParamExpr(Param, Arg);
+        if (ArgExpr.isInvalid())
+          return true;
+        Arg = ArgExpr.getAs<Expr>();
+      }
 
       InitializedEntity Entity =
           Param ? InitializedEntity::InitializeParameter(Context, Param,

diff  --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 1373c2ea034bf5..83e612c6751644 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -16,6 +16,7 @@
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/TargetInfo.h"
+#include "clang/Sema/Initialization.h"
 #include "clang/Sema/ParsedAttr.h"
 #include "clang/Sema/Sema.h"
 #include "llvm/ADT/STLExtras.h"
@@ -1737,3 +1738,104 @@ bool SemaHLSL::IsScalarizedLayoutCompatible(QualType T1, QualType T2) const {
                        return SemaRef.IsLayoutCompatible(LHS, RHS);
                      });
 }
+
+bool SemaHLSL::CheckCompatibleParameterABI(FunctionDecl *New,
+                                           FunctionDecl *Old) {
+  if (New->getNumParams() != Old->getNumParams())
+    return true;
+
+  bool HadError = false;
+
+  for (unsigned i = 0, e = New->getNumParams(); i != e; ++i) {
+    ParmVarDecl *NewParam = New->getParamDecl(i);
+    ParmVarDecl *OldParam = Old->getParamDecl(i);
+
+    // HLSL parameter declarations for inout and out must match between
+    // declarations. In HLSL inout and out are ambiguous at the call site,
+    // but have 
diff erent calling behavior, so you cannot overload a
+    // method based on a 
diff erence between inout and out annotations.
+    const auto *NDAttr = NewParam->getAttr<HLSLParamModifierAttr>();
+    unsigned NSpellingIdx = (NDAttr ? NDAttr->getSpellingListIndex() : 0);
+    const auto *ODAttr = OldParam->getAttr<HLSLParamModifierAttr>();
+    unsigned OSpellingIdx = (ODAttr ? ODAttr->getSpellingListIndex() : 0);
+
+    if (NSpellingIdx != OSpellingIdx) {
+      SemaRef.Diag(NewParam->getLocation(),
+                   diag::err_hlsl_param_qualifier_mismatch)
+          << NDAttr << NewParam;
+      SemaRef.Diag(OldParam->getLocation(), diag::note_previous_declaration_as)
+          << ODAttr;
+      HadError = true;
+    }
+  }
+  return HadError;
+}
+
+ExprResult SemaHLSL::ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg) {
+  assert(Param->hasAttr<HLSLParamModifierAttr>() &&
+         "We should not get here without a parameter modifier expression");
+  const auto *Attr = Param->getAttr<HLSLParamModifierAttr>();
+  if (Attr->getABI() == ParameterABI::Ordinary)
+    return ExprResult(Arg);
+
+  bool IsInOut = Attr->getABI() == ParameterABI::HLSLInOut;
+  if (!Arg->isLValue()) {
+    SemaRef.Diag(Arg->getBeginLoc(), diag::error_hlsl_inout_lvalue)
+        << Arg << (IsInOut ? 1 : 0);
+    return ExprError();
+  }
+
+  ASTContext &Ctx = SemaRef.getASTContext();
+
+  QualType Ty = Param->getType().getNonLValueExprType(Ctx);
+
+  // HLSL allows implicit conversions from scalars to vectors, but not the
+  // inverse, so we need to disallow `inout` with scalar->vector or
+  // scalar->matrix conversions.
+  if (Arg->getType()->isScalarType() != Ty->isScalarType()) {
+    SemaRef.Diag(Arg->getBeginLoc(), diag::error_hlsl_inout_scalar_extension)
+        << Arg << (IsInOut ? 1 : 0);
+    return ExprError();
+  }
+
+  auto *ArgOpV = new (Ctx) OpaqueValueExpr(Param->getBeginLoc(), Arg->getType(),
+                                           VK_LValue, OK_Ordinary, Arg);
+
+  // Parameters are initialized via copy initialization. This allows for
+  // overload resolution of argument constructors.
+  InitializedEntity Entity =
+      InitializedEntity::InitializeParameter(Ctx, Ty, false);
+  ExprResult Res =
+      SemaRef.PerformCopyInitialization(Entity, Param->getBeginLoc(), ArgOpV);
+  if (Res.isInvalid())
+    return ExprError();
+  Expr *Base = Res.get();
+  // After the cast, drop the reference type when creating the exprs.
+  Ty = Ty.getNonLValueExprType(Ctx);
+  auto *OpV = new (Ctx)
+      OpaqueValueExpr(Param->getBeginLoc(), Ty, VK_LValue, OK_Ordinary, Base);
+
+  // Writebacks are performed with `=` binary operator, which allows for
+  // overload resolution on writeback result expressions.
+  Res = SemaRef.ActOnBinOp(SemaRef.getCurScope(), Param->getBeginLoc(),
+                           tok::equal, ArgOpV, OpV);
+
+  if (Res.isInvalid())
+    return ExprError();
+  Expr *Writeback = Res.get();
+  auto *OutExpr =
+      HLSLOutArgExpr::Create(Ctx, Ty, ArgOpV, OpV, Writeback, IsInOut);
+
+  return ExprResult(OutExpr);
+}
+
+QualType SemaHLSL::getInoutParameterType(QualType Ty) {
+  // If HLSL gains support for references, all the cites that use this will need
+  // to be updated with semantic checking to produce errors for
+  // pointers/references.
+  assert(!Ty->isReferenceType() &&
+         "Pointer and reference types cannot be inout or out parameters");
+  Ty = SemaRef.getASTContext().getLValueReferenceType(Ty);
+  Ty.addRestrict();
+  return Ty;
+}

diff  --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index a3c13e21c709cb..95551173df91a5 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -7059,6 +7059,10 @@ void Sema::AddOverloadCandidate(
       // (13.3.3.1) that converts that argument to the corresponding
       // parameter of F.
       QualType ParamType = Proto->getParamType(ArgIdx);
+      auto ParamABI = Proto->getExtParameterInfo(ArgIdx).getABI();
+      if (ParamABI == ParameterABI::HLSLOut ||
+          ParamABI == ParameterABI::HLSLInOut)
+        ParamType = ParamType.getNonReferenceType();
       Candidate.Conversions[ConvIdx] = TryCopyInitialization(
           *this, Args[ArgIdx], ParamType, SuppressUserConversions,
           /*InOverloadResolution=*/true,

diff  --git a/clang/lib/Sema/SemaSwift.cpp b/clang/lib/Sema/SemaSwift.cpp
index bf56ae8ac76d57..2eebce74b5e2f8 100644
--- a/clang/lib/Sema/SemaSwift.cpp
+++ b/clang/lib/Sema/SemaSwift.cpp
@@ -724,6 +724,9 @@ void SemaSwift::AddParameterABIAttr(Decl *D, const AttributeCommonInfo &CI,
   }
 
   switch (abi) {
+  case ParameterABI::HLSLOut:
+  case ParameterABI::HLSLInOut:
+    llvm_unreachable("explicit attribute for non-swift parameter ABI?");
   case ParameterABI::Ordinary:
     llvm_unreachable("explicit attribute for ordinary parameter ABI?");
 

diff  --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 0e064be2391838..51109b092d7568 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -19,8 +19,8 @@
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/PrettyDeclStackTrace.h"
-#include "clang/AST/TypeOrdering.h"
 #include "clang/AST/TypeLoc.h"
+#include "clang/AST/TypeOrdering.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Basic/TargetInfo.h"
 #include "clang/Sema/EnterExpressionEvaluationContext.h"
@@ -29,6 +29,7 @@
 #include "clang/Sema/ScopeInfo.h"
 #include "clang/Sema/SemaAMDGPU.h"
 #include "clang/Sema/SemaCUDA.h"
+#include "clang/Sema/SemaHLSL.h"
 #include "clang/Sema/SemaInternal.h"
 #include "clang/Sema/SemaObjC.h"
 #include "clang/Sema/SemaOpenMP.h"
@@ -697,7 +698,7 @@ static void instantiateDependentHLSLParamModifierAttr(
     const HLSLParamModifierAttr *Attr, Decl *New) {
   ParmVarDecl *P = cast<ParmVarDecl>(New);
   P->addAttr(Attr->clone(S.getASTContext()));
-  P->setType(S.getASTContext().getLValueReferenceType(P->getType()));
+  P->setType(S.HLSL().getInoutParameterType(P->getType()));
 }
 
 void Sema::InstantiateAttrsForDecl(

diff  --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 56c4031ada0d1f..7df8f663da26a5 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -223,10 +223,15 @@ namespace {
     /// validating that noderef was used on a pointer or array.
     bool parsedNoDeref;
 
+    // Flag to indicate that we already parsed a HLSL parameter modifier
+    // attribute. This prevents double-mutating the type.
+    bool ParsedHLSLParamMod;
+
   public:
     TypeProcessingState(Sema &sema, Declarator &declarator)
         : sema(sema), declarator(declarator),
-          chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false) {}
+          chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false),
+          ParsedHLSLParamMod(false) {}
 
     Sema &getSema() const {
       return sema;
@@ -353,6 +358,10 @@ namespace {
 
     bool didParseNoDeref() const { return parsedNoDeref; }
 
+    void setParsedHLSLParamMod(bool Parsed) { ParsedHLSLParamMod = Parsed; }
+
+    bool didParseHLSLParamMod() const { return ParsedHLSLParamMod; }
+
     ~TypeProcessingState() {
       if (savedAttrs.empty())
         return;
@@ -2578,6 +2587,8 @@ static void checkExtParameterInfos(Sema &S, ArrayRef<QualType> paramTypes,
     switch (EPI.ExtParameterInfos[paramIndex].getABI()) {
     // Nothing interesting to check for orindary-ABI parameters.
     case ParameterABI::Ordinary:
+    case ParameterABI::HLSLOut:
+    case ParameterABI::HLSLInOut:
       continue;
 
     // swift_indirect_result parameters must be a prefix of the function
@@ -8518,15 +8529,19 @@ static void HandleLifetimeBoundAttr(TypeProcessingState &State,
   }
 }
 
-static void HandleHLSLParamModifierAttr(QualType &CurType,
+static void HandleHLSLParamModifierAttr(TypeProcessingState &State,
+                                        QualType &CurType,
                                         const ParsedAttr &Attr, Sema &S) {
   // Don't apply this attribute to template dependent types. It is applied on
-  // substitution during template instantiation.
-  if (CurType->isDependentType())
+  // substitution during template instantiation. Also skip parsing this if we've
+  // already modified the type based on an earlier attribute.
+  if (CurType->isDependentType() || State.didParseHLSLParamMod())
     return;
   if (Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_inout ||
-      Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_out)
-    CurType = S.getASTContext().getLValueReferenceType(CurType);
+      Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_out) {
+    CurType = S.HLSL().getInoutParameterType(CurType);
+    State.setParsedHLSLParamMod(true);
+  }
 }
 
 static void processTypeAttrs(TypeProcessingState &state, QualType &type,
@@ -8706,7 +8721,7 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
     }
 
     case ParsedAttr::AT_HLSLParamModifier: {
-      HandleHLSLParamModifierAttr(type, attr, state.getSema());
+      HandleHLSLParamModifierAttr(state, type, attr, state.getSema());
       attr.setUsedAsTypeAttr();
       break;
     }

diff  --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index a4d5d71bd11274..66e3f27fed9de0 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -16699,6 +16699,13 @@ TreeTransform<Derived>::TransformCapturedStmt(CapturedStmt *S) {
   return getSema().ActOnCapturedRegionEnd(Body.get());
 }
 
+template <typename Derived>
+ExprResult TreeTransform<Derived>::TransformHLSLOutArgExpr(HLSLOutArgExpr *E) {
+  // We can transform the base expression and allow argument resolution to fill
+  // in the rest.
+  return getDerived().TransformExpr(E->getArgLValue());
+}
+
 } // end namespace clang
 
 #endif // LLVM_CLANG_LIB_SEMA_TREETRANSFORM_H

diff  --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 8ae07907a04aba..84743a52d4c8b8 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -2838,6 +2838,18 @@ void ASTStmtReader::VisitOpenACCLoopConstruct(OpenACCLoopConstruct *S) {
   VisitOpenACCAssociatedStmtConstruct(S);
 }
 
+//===----------------------------------------------------------------------===//
+// HLSL Constructs/Directives.
+//===----------------------------------------------------------------------===//
+
+void ASTStmtReader::VisitHLSLOutArgExpr(HLSLOutArgExpr *S) {
+  VisitExpr(S);
+  S->SubExprs[HLSLOutArgExpr::BaseLValue] = Record.readSubExpr();
+  S->SubExprs[HLSLOutArgExpr::CastedTemporary] = Record.readSubExpr();
+  S->SubExprs[HLSLOutArgExpr::WritebackCast] = Record.readSubExpr();
+  S->IsInOut = Record.readBool();
+}
+
 //===----------------------------------------------------------------------===//
 // ASTReader Implementation
 //===----------------------------------------------------------------------===//
@@ -4292,13 +4304,17 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
       S = OpenACCLoopConstruct::CreateEmpty(Context, NumClauses);
       break;
     }
-    case EXPR_REQUIRES:
+    case EXPR_REQUIRES: {
       unsigned numLocalParameters = Record[ASTStmtReader::NumExprFields];
       unsigned numRequirement = Record[ASTStmtReader::NumExprFields + 1];
       S = RequiresExpr::Create(Context, Empty, numLocalParameters,
                                numRequirement);
       break;
     }
+    case EXPR_HLSL_OUT_ARG:
+      S = HLSLOutArgExpr::CreateEmpty(Context);
+      break;
+    }
 
     // We hit a STMT_STOP, so we're done with this expression.
     if (Finished)

diff  --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index c292d0a789c7cd..837136600181c1 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -2910,6 +2910,19 @@ void ASTStmtWriter::VisitOpenACCLoopConstruct(OpenACCLoopConstruct *S) {
   Code = serialization::STMT_OPENACC_LOOP_CONSTRUCT;
 }
 
+//===----------------------------------------------------------------------===//
+// HLSL Constructs/Directives.
+//===----------------------------------------------------------------------===//
+
+void ASTStmtWriter::VisitHLSLOutArgExpr(HLSLOutArgExpr *S) {
+  VisitExpr(S);
+  Record.AddStmt(S->getOpaqueArgLValue());
+  Record.AddStmt(S->getCastedTemporary());
+  Record.AddStmt(S->getWritebackCast());
+  Record.writeBool(S->isInOut());
+  Code = serialization::EXPR_HLSL_OUT_ARG;
+}
+
 //===----------------------------------------------------------------------===//
 // ASTWriter Implementation
 //===----------------------------------------------------------------------===//

diff  --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 40eb6463b217fa..dfb7111b512552 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1830,7 +1830,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
     case Stmt::OpenACCComputeConstructClass:
     case Stmt::OpenACCLoopConstructClass:
     case Stmt::OMPUnrollDirectiveClass:
-    case Stmt::OMPMetaDirectiveClass: {
+    case Stmt::OMPMetaDirectiveClass:
+    case Stmt::HLSLOutArgExprClass: {
       const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState());
       Engine.addAbortedBlock(node, currBldrCtx->getBlock());
       break;

diff  --git a/clang/test/AST/HLSL/OutArgExpr.hlsl b/clang/test/AST/HLSL/OutArgExpr.hlsl
new file mode 100644
index 00000000000000..b07c2efadbf4aa
--- /dev/null
+++ b/clang/test/AST/HLSL/OutArgExpr.hlsl
@@ -0,0 +1,85 @@
+// RUN: rm -f %t.pch
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -emit-pch -finclude-default-header -o %t.pch %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -finclude-default-header -include-pch %t.pch %s -ast-dump | FileCheck --check-prefix=AST %s
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -finclude-default-header -include-pch %t.pch %s -ast-print | FileCheck %s
+
+
+#ifndef TEST_HLSL
+#define TEST_HLSL
+
+RWBuffer<float> Buf;
+
+// CHECK: void trunc_Param(inout int &__restrict X) {
+
+// AST: FunctionDecl {{.*}} used trunc_Param 'void (inout int)'
+// AST-NEXT: ParmVarDecl {{.*}} X 'int &__restrict'
+// AST-NEXT: HLSLParamModifierAttr {{.*}} inout
+
+void trunc_Param(inout int X) {}
+
+// CHECK: void zero(out int &__restrict Z) {
+// CHECK-NEXT: Z = 0;
+
+// AST: FunctionDecl {{.*}} zero 'void (out int)'
+// AST-NEXT: ParmVarDecl {{.*}} used Z 'int &__restrict'
+// AST-NEXT: HLSLParamModifierAttr {{.*}} out
+void zero(out int Z) { Z = 0; }
+
+// AST-LABEL: FunctionDecl {{.*}} imported used fn 'void (uint)'
+// AST: CallExpr {{.*}} 'void'
+// AST-NEXT: ImplicitCastExpr {{.*}} 'void (*)(inout int)' <FunctionToPointerDecay>
+// AST-NEXT: DeclRefExpr {{.*}} 'void (inout int)' lvalue Function
+// AST-NEXT: HLSLOutArgExpr {{.*}} 'int' lvalue inout
+// AST-NEXT: OpaqueValueExpr [[LVOpV:0x[0-9a-fA-F]+]] {{.*}} 'float' lvalue
+// AST-NEXT: CXXOperatorCallExpr {{.*}} 'float' lvalue '[]'
+// AST-NEXT: ImplicitCastExpr {{.*}} 'float &(*)(unsigned int)' <FunctionToPointerDecay>
+// AST-NEXT: DeclRefExpr {{.*}} 'float &(unsigned int)' lvalue CXXMethod {{.*}} 'operator[]' 'float &(unsigned int)'
+// AST-NEXT: DeclRefExpr {{.*}} 'RWBuffer<float>':'hlsl::RWBuffer<float>' lvalue Var {{.*}} 'Buf' 'RWBuffer<float>':'hlsl::RWBuffer<float>'
+// AST-NEXT: ImplicitCastExpr {{.*}} 'uint':'unsigned int' <LValueToRValue>
+// AST-NEXT: DeclRefExpr {{.*}} 'uint':'unsigned int' lvalue ParmVar {{.*}} 'GI' 'uint':'unsigned int'
+
+// AST-NEXT: OpaqueValueExpr [[TmpOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// AST-NEXT: ImplicitCastExpr {{.*}} 'int' <FloatingToIntegral>
+// AST-NEXT: ImplicitCastExpr {{.*}} 'float' <LValueToRValue>
+// AST-NEXT: OpaqueValueExpr [[LVOpV]] <col:15, col:21> 'float' lvalue
+
+// AST: BinaryOperator {{.*}} 'float' lvalue '='
+// AST-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'float' lvalue
+// AST: ImplicitCastExpr {{.*}} 'float' <IntegralToFloating>
+// AST-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
+// AST-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
+
+// CHECK: void fn(uint GI) {
+// CHECK:     trunc_Param(Buf[GI]);
+void fn(uint GI) {
+  trunc_Param(Buf[GI]);
+}
+
+#else
+
+// AST-LABEL: FunctionDecl {{.*}} main 'void (uint)'
+// AST: CallExpr {{.*}} 'void'
+// AST-NEXT: ImplicitCastExpr {{.*}} 'void (*)(out int)' <FunctionToPointerDecay>
+// AST-NEXT: DeclRefExpr {{.*}} 'void (out int)' lvalue Function {{.*}} 'zero' 'void (out int)'
+// AST-NEXT: HLSLOutArgExpr {{.*}} 'int' lvalue out
+
+// AST: OpaqueValueExpr [[LVOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// AST-NEXT: DeclRefExpr {{.*}} 'int' lvalue Var {{.*}} 'I' 'int'
+
+// AST-NEXT: OpaqueValueExpr [[TmpOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// AST-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
+// AST-NEXT: OpaqueValueExpr [[LVOpV]] <col:8> 'int' lvalue
+
+// AST: BinaryOperator {{.*}} 'int' lvalue '='
+// AST-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+// AST: ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
+// AST-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
+
+
+[numthreads(8,1,1)]
+void main(uint GI : SV_GroupIndex) {
+  int I;
+  zero(I);
+  fn(GI);
+}
+#endif // TEST_HLSL

diff  --git a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
new file mode 100644
index 00000000000000..820c105a8bced4
--- /dev/null
+++ b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
@@ -0,0 +1,328 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -disable-llvm-passes -emit-llvm -finclude-default-header -o - %s | FileCheck %s --check-prefixes=CHECK,ALL
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -O3 -emit-llvm -finclude-default-header -o - %s | FileCheck %s --check-prefixes=OPT,ALL
+
+// Case 1: Simple floating integral conversion.
+// In this test case a float value is passed to an inout parameter taking an
+// integer. It is converted to an integer on call and converted back after the
+// function.
+
+// CHECK: define void {{.*}}trunc_Param{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) {{%.*}})
+void trunc_Param(inout int X) {}
+
+// ALL-LABEL: define noundef float {{.*}}case1
+// CHECK: [[F:%.*]] = alloca float
+// CHECK: [[ArgTmp:%.*]] = alloca i32
+// CHECK: [[FVal:%.*]] = load float, ptr {{.*}}
+// CHECK: [[IVal:%.*]] = fptosi float [[FVal]] to i32
+// CHECK: store i32 [[IVal]], ptr [[ArgTmp]]
+// CHECK: call void {{.*}}trunc_Param{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[ArgTmp]])
+// CHECK: [[IRet:%.*]] = load i32, ptr [[ArgTmp]]
+// CHECK: [[FRet:%.*]] = sitofp i32 [[IRet]] to float
+// CHECK: store float [[FRet]], ptr [[F]]
+// OPT: [[IVal:%.*]] = fptosi float {{.*}} to i32
+// OPT: [[FVal:%.*]] = sitofp i32 [[IVal]] to float
+// OPT: ret float [[FVal]]
+export float case1(float F) {
+  trunc_Param(F);
+  return F;
+}
+
+// Case 2: Uninitialized `out` parameters.
+// `out` parameters are not pre-initialized by the caller, so they are
+// uninitialized in the function. If they are not initialized before the
+// function returns the value is undefined.
+
+// CHECK: define void {{.*}}undef{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) {{%.*}})
+void undef(out int Z) { }
+
+// ALL-LABEL: define noundef i32 {{.*}}case2
+// CHECK: [[V:%.*]] = alloca i32
+// CHECK: [[ArgTmp:%.*]] = alloca i32
+// CHECK-NOT: store {{.*}}, ptr [[ArgTmp]]
+// CHECK: call void {{.*}}unde{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[ArgTmp]])
+// CHECK-NOT: store {{.*}}, ptr [[ArgTmp]]
+// CHECK: [[Res:%.*]] = load i32, ptr [[ArgTmp]]
+// CHECK: store i32 [[Res]], ptr [[V]], align 4
+// OPT: ret i32 undef
+export int case2() {
+  int V;
+  undef(V);
+  return V;
+}
+
+// Case 3: Simple initialized `out` parameter.
+// This test should verify that an out parameter value is written to as
+// expected.
+
+// CHECK: define void {{.*}}zero{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) {{%.*}})
+void zero(out int Z) { Z = 0; }
+
+// ALL-LABEL: define noundef i32 {{.*}}case3
+// CHECK: [[V:%.*]] = alloca i32
+// CHECK: [[ArgTmp:%.*]] = alloca i32
+// CHECK-NOT: store {{.*}}, ptr [[ArgTmp]]
+// CHECK: call void {{.*}}zero{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[ArgTmp]])
+// CHECK-NOT: store {{.*}}, ptr [[ArgTmp]]
+// CHECK: [[Res:%.*]] = load i32, ptr [[ArgTmp]]
+// CHECK: store i32 [[Res]], ptr [[V]], align 4
+// OPT: ret i32 0
+export int case3() {
+  int V;
+  zero(V);
+  return V;
+}
+
+// Case 4: Vector swizzle arguments.
+// Vector swizzles in HLSL produce lvalues, so they can be used as arguments to
+// inout parameters and the swizzle is reversed on writeback.
+
+// CHECK: define void {{.*}}funky{{.*}}(ptr noalias noundef nonnull align 16 dereferenceable(16) {{%.*}})
+void funky(inout int3 X) {
+  X.x += 1;
+  X.y += 2;
+  X.z += 3;
+}
+
+// ALL-LABEL: define noundef <3 x i32> {{.*}}case4
+
+// This block initializes V = 0.xxx.
+// CHECK:  [[V:%.*]] = alloca <3 x i32>
+// CHECK:  [[ArgTmp:%.*]] = alloca <3 x i32>
+// CHECK:  store <1 x i32> zeroinitializer, ptr [[ZeroPtr:%.*]]
+// CHECK:  [[ZeroV1:%.*]] = load <1 x i32>, ptr [[ZeroPtr]]
+// CHECK:  [[ZeroV3:%.*]] = shufflevector <1 x i32> [[ZeroV1]], <1 x i32> poison, <3 x i32> zeroinitializer
+// CHECK:  store <3 x i32> [[ZeroV3]], ptr [[V]]
+
+// Shuffle the vector to the temporary.
+// CHECK:  [[VVal:%.*]] = load <3 x i32>, ptr [[V]]
+// CHECK:  [[Vyzx:%.*]] = shufflevector <3 x i32> [[VVal]], <3 x i32> poison, <3 x i32> <i32 1, i32 2, i32 0>
+// CHECK:  store <3 x i32> [[Vyzx]], ptr [[ArgTmp]]
+
+// Call the function with the temporary.
+// CHECK: call void {{.*}}funky{{.*}}(ptr noalias noundef nonnull align 16 dereferenceable(16) [[ArgTmp]])
+
+// Shuffle it back.
+// CHECK:  [[RetVal:%.*]] = load <3 x i32>, ptr [[ArgTmp]]
+// CHECK:  [[Vxyz:%.*]] = shufflevector <3 x i32> [[RetVal]], <3 x i32> poison, <3 x i32> <i32 2, i32 0, i32 1>
+// CHECK:  store <3 x i32> [[Vxyz]], ptr [[V]]
+
+// OPT: ret <3 x i32> <i32 3, i32 1, i32 2>
+export int3 case4() {
+  int3 V = 0.xxx;
+  funky(V.yzx);
+  return V;
+}
+
+
+// Case 5: Straightforward inout of a scalar value.
+
+// CHECK: define void {{.*}}increment{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) {{%.*}})
+void increment(inout int I) {
+  I += 1;
+}
+
+// ALL-LABEL: define noundef i32 {{.*}}case5
+
+// CHECK: [[I:%.*]] = alloca i32
+// CHECK: [[ArgTmp:%.*]] = alloca i32
+// CHECK: store i32 4, ptr [[I]]
+// CHECK: [[IInit:%.*]] = load i32, ptr [[I]]
+// CHECK: store i32 [[IInit:%.*]], ptr [[ArgTmp]], align 4
+// CHECK: call void {{.*}}increment{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[ArgTmp]])
+// CHECK: [[RetVal:%.*]] = load i32, ptr [[ArgTmp]]
+// CHECK: store i32 [[RetVal]], ptr [[I]], align 4
+// OPT: ret i32 5
+export int case5() {
+  int I = 4;
+  increment(I);
+  return I;
+}
+
+// Case 6: Aggregate out parameters.
+struct S {
+  int X;
+  float Y;
+};
+
+// CHECK: define void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) {{%.*}})
+void init(out S s) {
+  s.X = 3;
+  s.Y = 4;
+}
+
+// ALL-LABEL: define noundef i32 {{.*}}case6
+
+// CHECK: [[S:%.*]] = alloca %struct.S
+// CHECK: [[Tmp:%.*]] = alloca %struct.S
+// CHECK: call void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) [[Tmp]])
+// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[S]], ptr align 4 [[Tmp]], i32 8, i1 false)
+
+// OPT: ret i32 7
+export int case6() {
+  S s;
+  init(s);
+  return s.X + s.Y;
+}
+
+// Case 7: Aggregate inout parameters.
+struct R {
+  int X;
+  float Y;
+};
+
+// CHECK: define void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) {{%.*}})
+void init(inout R s) {
+  s.X = 3;
+  s.Y = 4;
+}
+
+// ALL-LABEL: define noundef i32 {{.*}}case7
+
+// CHECK: [[S:%.*]] = alloca %struct.R
+// CHECK: [[Tmp:%.*]] = alloca %struct.R
+// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[Tmp]], ptr align 4 [[S]], i32 8, i1 false)
+// CHECK: call void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) [[Tmp]])
+// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[S]], ptr align 4 [[Tmp]], i32 8, i1 false)
+
+// OPT: ret i32 7
+export int case7() {
+  R s;
+  init(s);
+  return s.X + s.Y;
+}
+
+
+// Case 8: Non-scalars with a cast expression.
+
+// CHECK: define void {{.*}}trunc_vec{{.*}}(ptr noalias noundef nonnull align 16 dereferenceable(16) {{%.*}})
+void trunc_vec(inout int3 V) {}
+
+// ALL-LABEL: define noundef <3 x float> {{.*}}case8
+
+// CHECK: [[V:%.*]] = alloca <3 x float>
+// CHECK: [[Tmp:%.*]] = alloca <3 x i32>
+// CHECK: [[FVal:%.*]] = load <3 x float>, ptr [[V]]
+// CHECK: [[IVal:%.*]] = fptosi <3 x float> [[FVal]] to <3 x i32>
+// CHECK: store <3 x i32> [[IVal]], ptr [[Tmp]]
+// CHECK: call void {{.*}}trunc_vec{{.*}}(ptr noalias noundef nonnull align 16 dereferenceable(16) [[Tmp]])
+// CHECK: [[IRet:%.*]] = load <3 x i32>, ptr [[Tmp]]
+// CHECK: [[FRet:%.*]] = sitofp <3 x i32> [[IRet]] to <3 x float>
+// CHECK: store <3 x float> [[FRet]], ptr [[V]]
+
+// OPT: [[IVal:%.*]] = fptosi <3 x float> {{.*}} to <3 x i32>
+// OPT: [[FVal:%.*]] = sitofp <3 x i32> [[IVal]] to <3 x float>
+// OPT: ret <3 x float> [[FVal]]
+
+export float3 case8(float3 V) {
+  trunc_vec(V);
+  return V;
+}
+
+// Case 9: Side-effecting lvalue argument expression!
+
+void do_nothing(inout int V) {}
+
+// ALL-LABEL: define noundef i32 {{.*}}case9
+// CHECK: [[V:%.*]] = alloca i32
+// CHECK: [[Tmp:%.*]] = alloca i32
+// CHECK: store i32 0, ptr [[V]]
+// CHECK: [[VVal:%.*]] = load i32, ptr [[V]]
+// CHECK: [[VInc:%.*]] = add nsw i32 [[VVal]], 1
+// CHECK: store i32 [[VInc]], ptr [[V]]
+// CHECK: [[VArg:%.*]] = load i32, ptr [[V]]
+// CHECK-NOT: add
+// CHECK: store i32 [[VArg]], ptr [[Tmp]]
+// CHECK: call void {{.*}}do_nothing{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[Tmp]])
+// CHECK: [[RetVal:%.*]] = load i32, ptr [[Tmp]]
+// CHECK: store i32 [[RetVal]], ptr [[V]]
+
+// OPT: ret i32 1
+export int case9() {
+  int V = 0;
+  do_nothing(++V);
+  return V;
+}
+
+// Case 10: Verify argument writeback ordering for aliasing arguments.
+
+void order_matters(inout int X, inout int Y) {
+  Y = 2;
+  X = 1;
+}
+
+// ALL-LABEL: define noundef i32 {{.*}}case10
+
+// CHECK: [[V:%.*]] = alloca i32
+// CHECK: [[Tmp0:%.*]] = alloca i32
+// CHECK: [[Tmp1:%.*]] = alloca i32
+// CHECK: store i32 0, ptr [[V]]
+// CHECK: [[VVal:%.*]] = load i32, ptr [[V]]
+// CHECK: store i32 [[VVal]], ptr [[Tmp0]]
+// CHECK: [[VVal:%.*]] = load i32, ptr [[V]]
+// CHECK: store i32 [[VVal]], ptr [[Tmp1]]
+// CHECK: call void {{.*}}order_matters{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[Tmp1]], ptr noalias noundef nonnull align 4 dereferenceable(4) [[Tmp0]])
+// CHECK: [[Arg1Val:%.*]] = load i32, ptr [[Tmp1]]
+// CHECK: store i32 [[Arg1Val]], ptr [[V]]
+// CHECK: [[Arg2Val:%.*]] = load i32, ptr [[Tmp0]]
+// CHECK: store i32 [[Arg2Val]], ptr [[V]]
+
+// OPT: ret i32 2
+export int case10() {
+  int V = 0;
+  order_matters(V, V);
+  return V;
+}
+
+// Case 11: Verify inout on bitfield lvalues
+
+struct B {
+  int X : 8;
+  int Y : 8;
+};
+
+void setFour(inout int I) {
+  I = 4;
+}
+
+// ALL-LABEL: define {{.*}} i32 {{.*}}case11
+
+// CHECK: [[B:%.*]] = alloca %struct.B
+// CHECK: [[Tmp:%.*]] = alloca i32
+
+// CHECK: [[BFLoad:%.*]] = load i32, ptr [[B]]
+// CHECK: [[BFshl:%.*]] = shl i32 [[BFLoad]], 24
+// CHECK: [[BFashr:%.*]] = ashr i32 [[BFshl]], 24
+// CHECK: store i32 [[BFashr]], ptr [[Tmp]]
+// CHECK: call void {{.*}}setFour{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[Tmp]])
+// CHECK: [[RetVal:%.*]] = load i32, ptr [[Tmp]]
+// CHECK: [[BFLoad:%.*]] = load i32, ptr [[B]]
+// CHECK: [[BFValue:%.*]] = and i32 [[RetVal]], 255
+// CHECK: [[ZerodField:%.*]] = and i32 [[BFLoad]], -256
+// CHECK: [[BFSet:%.*]] = or i32 [[ZerodField]], [[BFValue]]
+// CHECK: store i32 [[BFSet]], ptr [[B]]
+
+// OPT: ret i32 8
+export int case11() {
+  B b = {1 , 2};
+  setFour(b.X);
+  return b.X * b.Y;
+}
+
+// Case 12: Uninitialized out parameters are undefined
+
+void oops(out int X) {}
+// ALL-LABEL: define {{.*}} i32 {{.*}}case12
+
+// CHECK: [[V:%.*]] = alloca i32
+// CHECK: [[Tmp:%.*]] = alloca i32
+// CHECK-NOT: store {{.*}}, ptr [[Tmp]]
+// CHECK: call void {{.*}}oops{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(4) [[Tmp]])
+// CHECK: [[ArgVal:%.*]] = load i32, ptr [[Tmp]]
+// CHECK: store i32 [[ArgVal]], ptr [[V]]
+
+// OPT:  ret i32 undef
+export int case12() {
+  int V = 0;
+  oops(V);
+  return V;
+}

diff  --git a/clang/test/SemaHLSL/Language/OutputParameters.hlsl b/clang/test/SemaHLSL/Language/OutputParameters.hlsl
new file mode 100644
index 00000000000000..6d4d59771f8e47
--- /dev/null
+++ b/clang/test/SemaHLSL/Language/OutputParameters.hlsl
@@ -0,0 +1,34 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -finclude-default-header -verify -Wdouble-promotion -Wconversion %s
+
+void OutVecFn(out float3) {}
+void InOutVecFn(inout float3) {}
+
+// Case 1: Calling out and inout parameters with types that cannot be
+// back-converted. In HLSL 2021 and earlier this only occurs when passing scalar
+// arguments to vector parameters because scalar->vector conversion is implicit,
+// but vector->scalar is not.
+void case1() {
+  float f;
+  int i;
+  OutVecFn(f); // expected-error{{illegal scalar extension cast on argument f to out paramemter}}
+  InOutVecFn(f); // expected-error{{illegal scalar extension cast on argument f to inout paramemter}}
+
+  OutVecFn(i); // expected-error{{illegal scalar extension cast on argument i to out paramemter}}
+  InOutVecFn(i); // expected-error{{illegal scalar extension cast on argument i to inout paramemter}}
+}
+
+// Case 2: Conversion warnings on argument initialization. Clang generates
+// implicit conversion warnings only on the writeback conversion for `out`
+// parameters since the parameter is not initialized from the argument. Clang
+// generates implicit conversion warnings on both the parameter initialization
+// and the writeback for `inout` parameters since the parameter is both copied
+// in and out of the function.
+
+void OutFloat(out float) {}
+void InOutFloat(inout float) {}
+
+void case2() {
+  double f;
+  OutFloat(f); // expected-warning{{implicit conversion increases floating-point precision: 'float' to 'double'}}
+  InOutFloat(f); // expected-warning{{implicit conversion increases floating-point precision: 'float' to 'double'}} expected-warning{{implicit conversion loses floating-point precision: 'double' to 'float'}}
+}

diff  --git a/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl b/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
new file mode 100644
index 00000000000000..2d6252cbb4d2b1
--- /dev/null
+++ b/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
@@ -0,0 +1,214 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -finclude-default-header %s -ast-dump | FileCheck %s
+
+// Case 1: Template declaration with a call to an inout or out argument that is
+// resolved based on the template parameter. For this case the template decl
+// should have an UnresolvedLookupExpr for the call, and the HLSLOutArgExpr is
+// built during call resolution.
+
+// CHECK: FunctionDecl {{.*}} used fn 'void (inout int)'
+void fn(inout int I) {
+  I += 1;
+}
+
+// CHECK: FunctionDecl {{.*}} used fn 'void (out double)'
+void fn(out double F) {
+  F = 1.5;
+}
+
+// CHECK-LABEL: FunctionTemplateDecl {{.*}} wrapper
+// CHECK-NEXT: TemplateTypeParmDecl {{.*}} referenced typename depth 0 index 0 T
+
+// Verify that the template has an unresolved call.
+// CHECK-NEXT: FunctionDecl {{.*}} wrapper 'T (T)'
+// CHECK-NEXT: ParmVarDecl {{.*}} referenced V 'T'
+// CHECK: CallExpr {{.*}} '<dependent type>'
+// CHECK: UnresolvedLookupExpr {{.*}} '<overloaded function type>' lvalue (ADL) = 'fn'
+
+// Verify that the int instantiation resolves an inout argument expression.
+
+// CHECK-LABEL: FunctionDecl {{.*}} used wrapper 'int (int)' implicit_instantiation
+// CHECK: CallExpr {{.*}} 'void'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(inout int)' <FunctionToPointerDecay>
+// CHECK-NEXT:   DeclRefExpr {{.*}} 'void (inout int)' lvalue Function {{.*}} 'fn' 'void (inout int)'
+// CHECK-NEXT: HLSLOutArgExpr {{.*}} 'int' lvalue inout
+
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int' lvalue ParmVar {{.*}} 'V' 'int'
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+
+// CHECK: BinaryOperator {{.*}} 'int' lvalue '='
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+// CHECK: ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
+
+
+// Verify that the float instantiation has an out argument expression
+// containing casts to and from double.
+
+// CHECK-LABEL: FunctionDecl {{.*}} used wrapper 'float (float)' implicit_instantiation
+// CHECK: CallExpr {{.*}} 'void'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(out double)' <FunctionToPointerDecay>
+// CHECK-NEXT:   DeclRefExpr {{.*}}'void (out double)' lvalue Function {{.*}} 'fn' 'void (out double)'
+// CHECK-NEXT: HLSLOutArgExpr {{.*}} 'double' lvalue out
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV:0x[0-9a-fA-F]+]] {{.*}} 'float' lvalue
+// CHECK-NEXT: DeclRefExpr {{.*}} 'float' lvalue ParmVar {{.*}} 'V' 'float'
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV:0x[0-9a-fA-F]+]] {{.*}} 'double' lvalue
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'double' <FloatingCast>
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'float' lvalue
+
+// CHECK: BinaryOperator {{.*}} 'float' lvalue '='
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'float' lvalue
+// CHECK: ImplicitCastExpr {{.*}} 'float' <FloatingCast>
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'double' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'double' lvalue
+
+
+// Verify that the double instantiation is just an out expression.
+
+// CHECK-LABEL: FunctionDecl {{.*}} used wrapper 'double (double)' implicit_instantiation
+// CHECK: CallExpr {{.*}} 'void'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(out double)' <FunctionToPointerDecay>
+// CHECK-NEXT:   DeclRefExpr {{.*}}'void (out double)' lvalue Function {{.*}} 'fn' 'void (out double)'
+// CHECK-NEXT: HLSLOutArgExpr {{.*}} 'double' lvalue out
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV:0x[0-9a-fA-F]+]] {{.*}} 'double' lvalue
+// CHECK-NEXT: DeclRefExpr {{.*}} 'double' lvalue ParmVar {{.*}} 'V' 'double'
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV:0x[0-9a-fA-F]+]] {{.*}} 'double' lvalue
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'double' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'double' lvalue
+
+// CHECK: BinaryOperator {{.*}} 'double' lvalue '='
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'double' lvalue
+// CHECK: ImplicitCastExpr {{.*}} 'double' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'double' lvalue
+
+template <typename T>
+T wrapper(T V) {
+  fn(V);
+  return V;
+}
+
+// Case 2: Verify that the parameter modifier attribute is instantiated with the
+// template (this one is a gimme).
+
+// CHECK-LABEL: FunctionTemplateDecl {{.*}} fizz
+
+// Check the pattern decl.
+// CHECK: FunctionDecl {{.*}} fizz 'void (inout T)'
+// CHECK-NEXT: ParmVarDecl {{.*}} referenced V 'T'
+// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
+
+// Check the 3 instantiations (int, float, & double).
+
+// CHECK-LABEL: FunctionDecl {{.*}} used fizz 'void (inout int)' implicit_instantiation
+// CHECK: ParmVarDecl {{.*}} used V 'int &__restrict'
+// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
+
+// CHECK-LABEL: FunctionDecl {{.*}} used fizz 'void (inout float)' implicit_instantiation
+// CHECK: ParmVarDecl {{.*}} used V 'float &__restrict'
+// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
+
+// CHECK-LABEL: FunctionDecl {{.*}} used fizz 'void (inout double)' implicit_instantiation
+// CHECK: ParmVarDecl {{.*}} used V 'double &__restrict'
+// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
+template <typename T>
+void fizz(inout T V) {
+  V += 2;
+}
+
+// Case 3: Verify that HLSLOutArgExpr nodes which are present in the template
+// are correctly instantiated into the instantation.
+
+// First we check that the AST node is in the template.
+
+// CHECK-LABEL: FunctionTemplateDecl {{.*}} buzz
+
+// CHECK: FunctionDecl {{.*}} buzz 'T (int, T)'
+// CHECK: CallExpr {{.*}} 'void'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(inout int)' <FunctionToPointerDecay>
+// CHECK-NEXT:   DeclRefExpr {{.*}} 'void (inout int)' lvalue Function {{.*}} 'fn' 'void (inout int)'
+// CHECK-NEXT: HLSLOutArgExpr {{.*}} 'int' lvalue inout
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// CHECK-NEXT:   DeclRefExpr  {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// CHECK-NEXT:   ImplicitCastExpr  {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT:     OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+// CHECK:      BinaryOperator {{.*}} 'int' lvalue '='
+// CHECK-NEXT:   OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+// CHECK:      ImplicitCastExpr  {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT:   OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
+
+
+
+// CHECK-LABEL: FunctionDecl {{.*}} used buzz 'int (int, int)' implicit_instantiation
+// CHECK: CallExpr {{.*}} 'void'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(inout int)' <FunctionToPointerDecay>
+// CHECK-NEXT:   DeclRefExpr {{.*}} 'void (inout int)' lvalue Function {{.*}} 'fn' 'void (inout int)'
+// CHECK-NEXT: HLSLOutArgExpr {{.*}} 'int' lvalue inout
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// CHECK-NEXT:   DeclRefExpr  {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// CHECK-NEXT:   ImplicitCastExpr  {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT:     OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+// CHECK:      BinaryOperator {{.*}} 'int' lvalue '='
+// CHECK-NEXT:   OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+// CHECK:      ImplicitCastExpr  {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT:   OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
+
+
+// CHECK-LABEL: FunctionDecl {{.*}} used buzz 'float (int, float)' implicit_instantiation
+// CHECK: CallExpr {{.*}} 'void'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(inout int)' <FunctionToPointerDecay>
+// CHECK-NEXT:   DeclRefExpr {{.*}} 'void (inout int)' lvalue Function {{.*}} 'fn' 'void (inout int)'
+// CHECK-NEXT: HLSLOutArgExpr {{.*}} 'int' lvalue inout
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// CHECK-NEXT:   DeclRefExpr  {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// CHECK-NEXT:   ImplicitCastExpr  {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT:     OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+// CHECK:      BinaryOperator {{.*}} 'int' lvalue '='
+// CHECK-NEXT:   OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+// CHECK:      ImplicitCastExpr  {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT:   OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
+
+
+// CHECK-LABEL: FunctionDecl {{.*}} used buzz 'double (int, double)' implicit_instantiation
+// CHECK: CallExpr {{.*}} 'void'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(inout int)' <FunctionToPointerDecay>
+// CHECK-NEXT:   DeclRefExpr {{.*}} 'void (inout int)' lvalue Function {{.*}} 'fn' 'void (inout int)'
+// CHECK-NEXT: HLSLOutArgExpr {{.*}} 'int' lvalue inout
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// CHECK-NEXT:   DeclRefExpr  {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue
+// CHECK-NEXT:   ImplicitCastExpr  {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT:     OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+// CHECK:      BinaryOperator {{.*}} 'int' lvalue '='
+// CHECK-NEXT:   OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue
+// CHECK:      ImplicitCastExpr  {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT:   OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
+
+template <typename T>
+T buzz(int X, T Y) {
+  fn(X);
+  return X + Y;
+}
+
+export void caller() {
+  int X = 2;
+  float Y = 3.3;
+  double Z = 2.2;
+
+  X = wrapper(X);
+  Y = wrapper(Y);
+  Z = wrapper(Z);
+
+  fizz(X);
+  fizz(Y);
+  fizz(Z);
+
+  X = buzz(X, X);
+  Y = buzz(X, Y);
+  Z = buzz(X, Z);
+}

diff  --git a/clang/test/SemaHLSL/parameter_modifiers.hlsl b/clang/test/SemaHLSL/parameter_modifiers.hlsl
index c728a41b650eed..5c4a1e4ec2926a 100644
--- a/clang/test/SemaHLSL/parameter_modifiers.hlsl
+++ b/clang/test/SemaHLSL/parameter_modifiers.hlsl
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library %s -verify
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.6-library %s -verify -Wconversion
 void fn(in out float f); // #fn
 
 // expected-error@#fn2{{duplicate parameter modifier 'in'}}
@@ -35,7 +35,7 @@ void fn(in float f); // #fn-in
 void failOverloadResolution() {
   float f = 1.0;
   fn(f); // expected-error{{call to 'fn' is ambiguous}}
-  // expected-note@#fn-def{{candidate function}}
+  // expected-note@#fn{{candidate function}}
   // expected-note@#fn-in{{candidate function}}
 }
 
@@ -48,11 +48,9 @@ void callFns() {
   // Call with literal arguments.
   implicitFn(1); // Ok.
   inFn(1); // Ok.
-  inoutFn(1); // expected-error{{no matching function for call to 'inoutFn'}}
-  // expected-note@#inoutFn{{candidate function not viable: no known conversion from 'int' to 'float &' for 1st argument}}
-  outFn(1); // expected-error{{no matching function for call to 'outFn}}
-  // expected-note@#outFn{{candidate function not viable: no known conversion from 'int' to 'float &' for 1st argument}}
-  
+  inoutFn(1); // expected-error{{cannot bind non-lvalue argument 1 to inout paramemter}}
+  outFn(1); // expected-error{{cannot bind non-lvalue argument 1 to out paramemter}}
+
   // Call with variables.
   float f;
   implicitFn(f); // Ok.
@@ -92,3 +90,11 @@ void fn13() {
   float f;
   fn12<float>(f);
 }
+
+void fn14(out float f);
+
+void fn15() {
+  float f;
+  int x = 5;
+  fn14(f += x); // expected-warning{{implicit conversion from 'int' to 'float' may lose precision}}
+}

diff  --git a/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl b/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl
index 50b162bdfc26cc..bf5ade162a01a1 100644
--- a/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl
+++ b/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl
@@ -11,39 +11,39 @@ void fn(float f);
 // CHECK-NOT: HLSLParamModifierAttr
 void fn2(in float f);
 
-// CHECK: FunctionDecl {{.*}} fn3 'void (float &)'
-// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
+// CHECK: FunctionDecl {{.*}} fn3 'void (out float)'
+// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &__restrict'
 // CHECK-NEXT: HLSLParamModifierAttr {{.*}} out
 // CHECK-NOT: HLSLParamModifierAttr
 void fn3(out float f);
 
-// CHECK: FunctionDecl {{.*}} fn4 'void (float &)'
-// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
+// CHECK: FunctionDecl {{.*}} fn4 'void (inout float)'
+// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &__restrict'
 // CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
 // CHECK-NOT: HLSLParamModifierAttr
 void fn4(inout float f);
 
-// CHECK: FunctionDecl {{.*}} fn5 'void (float &)'
-// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
+// CHECK: FunctionDecl {{.*}} fn5 'void (inout float)'
+// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &__restrict'
 // CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout MergedSpelling
 // CHECK-NOT: HLSLParamModifierAttr
 void fn5(out in float f);
 
-// CHECK: FunctionDecl {{.*}} fn6 'void (float &)'
-// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
+// CHECK: FunctionDecl {{.*}} fn6 'void (inout float)'
+// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &__restrict'
 // CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout MergedSpelling
 // CHECK-NOT: HLSLParamModifierAttr
 void fn6(in out float f);
 
 // CHECK-NEXT: FunctionTemplateDecl [[Template:0x[0-9a-fA-F]+]] {{.*}} fn7
 // CHECK-NEXT: TemplateTypeParmDecl {{.*}} referenced typename depth 0 index 0 T
-// CHECK-NEXT: FunctionDecl {{.*}} fn7 'void (T)'
+// CHECK-NEXT: FunctionDecl {{.*}} fn7 'void (inout T)'
 // CHECK-NEXT: ParmVarDecl {{.*}} f 'T'
 // CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
-// CHECK-NEXT: FunctionDecl [[Instantiation:0x[0-9a-fA-F]+]] {{.*}} used fn7 'void (float &)' implicit_instantiation
+// CHECK-NEXT: FunctionDecl [[Instantiation:0x[0-9a-fA-F]+]] {{.*}} used fn7 'void (inout float)' implicit_instantiation
 // CHECK-NEXT: TemplateArgument type 'float'
 // CHECK-NEXT:  BuiltinType {{.*}} 'float'
-// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
+// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &__restrict'
 // CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
 
 template <typename T>
@@ -54,11 +54,11 @@ void fn7(inout T f);
 // CHECK-NEXT: DeclStmt
 // CHECK-NEXT: VarDecl {{.*}} used f 'float'
 // CHECK-NEXT: CallExpr {{.*}} 'void'
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float &)' <FunctionToPointerDecay>
-// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float &)' lvalue
-// CHECK-SAME: Function [[Instantiation]] 'fn7' 'void (float &)'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(inout float)' <FunctionToPointerDecay>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'void (inout float)' lvalue
+// CHECK-SAME: Function [[Instantiation]] 'fn7' 'void (inout float)'
 // CHECK-SAME: (FunctionTemplate [[Template]] 'fn7')
-// CHECK-NEXT: DeclRefExpr {{.*}} 'float' lvalue Var {{.*}} 'f' 'float'
+// CHECK-NEXT: HLSLOutArgExpr {{.*}}'float' lvalue
 void fn8() {
   float f;
   fn7<float>(f);

diff  --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index d87eb95761ed78..4e068f272a153f 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -336,6 +336,7 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
   case Stmt::RecoveryExprClass:
   case Stmt::SYCLUniqueStableNameExprClass:
   case Stmt::EmbedExprClass:
+  case Stmt::HLSLOutArgExprClass:
     K = CXCursor_UnexposedExpr;
     break;
 


        


More information about the cfe-commits mailing list