diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 8156d283f8fe2..3c43892b6ff71 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -1381,6 +1381,14 @@ class ASTContext : public RefCountedBase { /// 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 bd1851a26ce2e..ac44e9fdd7c4e 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(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(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 7bacf028192c6..65104acda9382 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(SubExprs[BaseLValue]); + } + OpaqueValueExpr *getOpaqueArgLValue() { + return cast(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(SubExprs[WritebackCast]); + } + Expr *getWritebackCast() { return cast(SubExprs[WritebackCast]); } + + const OpaqueValueExpr *getCastedTemporary() const { + return cast(SubExprs[CastedTemporary]); + } + OpaqueValueExpr *getCastedTemporary() { + return cast(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 e51b04cce39eb..3389670a2ab9d 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 88d5535829910..57100e7ede171 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 a83e908899c83..60e4144a60c62 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 edf22b909c4d5..83d1890af0c79 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, InGroup, 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 7bfa26543578d..9c089908fdc13 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 e4e4343ec6f92..30f2c8f1dbfde 100644 --- a/clang/include/clang/Basic/StmtNodes.td +++ b/clang/include/clang/Basic/StmtNodes.td @@ -307,3 +307,6 @@ def OpenACCAssociatedStmtConstruct : StmtNode; def OpenACCComputeConstruct : StmtNode; def OpenACCLoopConstruct : StmtNode; + +// HLSL Constructs. +def HLSLOutArgExpr : StmtNode; diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 363a3ee6b4c1f..e1ebf0e30af3d 100644 --- a/clang/include/clang/Sema/SemaHLSL.h +++ b/clang/include/clang/Sema/SemaHLSL.h @@ -67,6 +67,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 93d28fbef8e45..4410df296d8ef 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 f108952d7bbf7..7e3713f44801a 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::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 3309619850f34..96c6276f3f34c 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 1f1b8abc09b9e..5dde923312698 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 e8a4d1d3c7410..b5dfd4dd32b63 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 1ad8cf5a1c911..1a47caac5a503 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 014d02220d291..e1b5bec7a50d0 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 35d8b0706fe3c..ad4281986f668 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 2c962253c8bea..c6b1b44206b24 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 92812bebeb5b4..b1d9516c96eb7 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 ca2c79b51ac96..997510d71c3ef 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(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 412b44a8c753a..6fa65e1916183 100644 --- a/clang/lib/CodeGen/CGCall.h +++ b/clang/lib/CodeGen/CGCall.h @@ -285,6 +285,13 @@ class CallArgList : public SmallVector { /// 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 { 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 { /// memory. bool isUsingInAlloca() const { return StackBase; } + // Support reversing writebacks for MSVC ABI. + void reverseWritebacks() { + std::reverse(Writebacks.begin(), Writebacks.end()); + } + private: SmallVector Writebacks; diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 426fccb721db8..99cd61b9e7895 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(E)); case Expr::PackIndexingExprClass: return EmitLValue(cast(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 d9f44f4be617e..bbfc6672ecc25 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 05f85f8b95bfa..368fc112187ff 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 b021e27209cf1..b01765b6833a1 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(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(E)) { // Do a somewhat different check with comparison operators. if (BO->isComparisonOp()) diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 6327ae9b99aa4..f6017b79db974 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -3245,26 +3245,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 different calling behavior, so you cannot overload a method based on a - // difference between inout and out annotations. - if (S.getLangOpts().HLSL) { - const auto *NDAttr = newDecl->getAttr(); - const auto *ODAttr = oldDecl->getAttr(); - // 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; @@ -4050,6 +4030,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 c4481250f345a..8aedbfcf878a1 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 092d174a811c6..94bb938b53b44 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(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(); + } InitializedEntity Entity = Param ? InitializedEntity::InitializeParameter(Context, Param, diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 65972987458d7..fd9621d217210 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" @@ -1628,3 +1629,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 different calling behavior, so you cannot overload a + // method based on a difference between inout and out annotations. + const auto *NDAttr = NewParam->getAttr(); + unsigned NSpellingIdx = (NDAttr ? NDAttr->getSpellingListIndex() : 0); + const auto *ODAttr = OldParam->getAttr(); + 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() && + "We should not get here without a parameter modifier expression"); + const auto *Attr = Param->getAttr(); + 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 a3c13e21c709c..95551173df91a 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 bf56ae8ac76d5..2eebce74b5e2f 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 0e064be239183..51109b092d756 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(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 56c4031ada0d1..7df8f663da26a 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 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 a4d5d71bd1127..66e3f27fed9de 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -16699,6 +16699,13 @@ TreeTransform::TransformCapturedStmt(CapturedStmt *S) { return getSema().ActOnCapturedRegionEnd(Body.get()); } +template +ExprResult TreeTransform::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 8ae07907a04ab..84743a52d4c8b 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 c292d0a789c7c..837136600181c 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 40eb6463b217f..dfb7111b51255 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 0000000000000..b07c2efadbf4a --- /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 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)' +// 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)' +// AST-NEXT: DeclRefExpr {{.*}} 'float &(unsigned int)' lvalue CXXMethod {{.*}} 'operator[]' 'float &(unsigned int)' +// AST-NEXT: DeclRefExpr {{.*}} 'RWBuffer':'hlsl::RWBuffer' lvalue Var {{.*}} 'Buf' 'RWBuffer':'hlsl::RWBuffer' +// AST-NEXT: ImplicitCastExpr {{.*}} 'uint':'unsigned int' +// 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' +// AST-NEXT: ImplicitCastExpr {{.*}} 'float' +// AST-NEXT: OpaqueValueExpr [[LVOpV]] 'float' lvalue + +// AST: BinaryOperator {{.*}} 'float' lvalue '=' +// AST-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'float' lvalue +// AST: ImplicitCastExpr {{.*}} 'float' +// AST-NEXT: ImplicitCastExpr {{.*}} 'int' +// 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)' +// 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' +// AST-NEXT: OpaqueValueExpr [[LVOpV]] 'int' lvalue + +// AST: BinaryOperator {{.*}} 'int' lvalue '=' +// AST-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue +// AST: ImplicitCastExpr {{.*}} 'int' +// 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 0000000000000..820c105a8bced --- /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> +// 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> +// CHECK: store <3 x i32> [[Vxyz]], ptr [[V]] + +// OPT: ret <3 x i32> +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 0000000000000..6d4d59771f8e4 --- /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 0000000000000..2d6252cbb4d2b --- /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 {{.*}} '' +// CHECK: UnresolvedLookupExpr {{.*}} '' 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)' +// 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' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue + +// CHECK: BinaryOperator {{.*}} 'int' lvalue '=' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue +// CHECK: ImplicitCastExpr {{.*}} 'int' +// 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)' +// 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' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'float' lvalue + +// CHECK: BinaryOperator {{.*}} 'float' lvalue '=' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'float' lvalue +// CHECK: ImplicitCastExpr {{.*}} 'float' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'double' +// 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)' +// 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' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'double' lvalue + +// CHECK: BinaryOperator {{.*}} 'double' lvalue '=' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'double' lvalue +// CHECK: ImplicitCastExpr {{.*}} 'double' +// CHECK-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'double' lvalue + +template +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 +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)' +// 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' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue +// CHECK: BinaryOperator {{.*}} 'int' lvalue '=' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue +// CHECK: ImplicitCastExpr {{.*}} 'int' +// 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)' +// 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' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue +// CHECK: BinaryOperator {{.*}} 'int' lvalue '=' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue +// CHECK: ImplicitCastExpr {{.*}} 'int' +// 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)' +// 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' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue +// CHECK: BinaryOperator {{.*}} 'int' lvalue '=' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue +// CHECK: ImplicitCastExpr {{.*}} 'int' +// 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)' +// 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' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue +// CHECK: BinaryOperator {{.*}} 'int' lvalue '=' +// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'int' lvalue +// CHECK: ImplicitCastExpr {{.*}} 'int' +// CHECK-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue + +template +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 c728a41b650ee..5c4a1e4ec2926 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(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 50b162bdfc26c..bf5ade162a01a 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 @@ -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 &)' -// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float &)' lvalue -// CHECK-SAME: Function [[Instantiation]] 'fn7' 'void (float &)' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(inout float)' +// 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(f); diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp index d87eb95761ed7..4e068f272a153 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;