[clang] [HLSL] Implement output parameter (PR #101083)
Chris B via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 30 16:40:06 PDT 2024
https://github.com/llvm-beanz updated https://github.com/llvm/llvm-project/pull/101083
>From e8ec3e24e0061714b5dc440b1b92086965809483 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Mon, 29 Jul 2024 16:05:11 -0500
Subject: [PATCH 01/19] [HLSL] Implement output parameter
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 intialized by casting the
argument 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 optional casting
sequence if required.
This change introduces a new HLSLOutArgExpr ast node which represents
the output argument behavior. The OutArgExpr has two defined children:
the base expresion and the writeback expression. The writeback
expression will either be or contain an OpaqueValueExpr child
expression which is used during code generation to represent the
temporary value.
---
clang/include/clang/AST/ASTContext.h | 8 +
clang/include/clang/AST/Attr.h | 38 ++--
clang/include/clang/AST/Expr.h | 61 +++++++
clang/include/clang/AST/RecursiveASTVisitor.h | 3 +
clang/include/clang/AST/TextNodeDumper.h | 1 +
clang/include/clang/Basic/Attr.td | 3 +-
.../clang/Basic/DiagnosticSemaKinds.td | 2 +
clang/include/clang/Basic/Specifiers.h | 6 +
clang/include/clang/Basic/StmtNodes.td | 3 +
clang/include/clang/Sema/SemaHLSL.h | 2 +
.../include/clang/Serialization/ASTBitCodes.h | 3 +
clang/lib/AST/ASTContext.cpp | 14 ++
clang/lib/AST/Expr.cpp | 11 ++
clang/lib/AST/ExprClassification.cpp | 1 +
clang/lib/AST/ExprConstant.cpp | 1 +
clang/lib/AST/ItaniumMangle.cpp | 12 ++
clang/lib/AST/StmtPrinter.cpp | 4 +
clang/lib/AST/StmtProfile.cpp | 4 +
clang/lib/AST/TextNodeDumper.cpp | 4 +
clang/lib/AST/TypePrinter.cpp | 20 ++-
clang/lib/CodeGen/CGCall.cpp | 56 ++++++
clang/lib/CodeGen/CGCall.h | 18 +-
clang/lib/CodeGen/CGExpr.cpp | 25 +++
clang/lib/CodeGen/CodeGenFunction.h | 2 +
clang/lib/Sema/SemaChecking.cpp | 13 ++
clang/lib/Sema/SemaDecl.cpp | 4 +
clang/lib/Sema/SemaExceptionSpec.cpp | 1 +
clang/lib/Sema/SemaExpr.cpp | 19 +-
clang/lib/Sema/SemaHLSL.cpp | 60 ++++++-
clang/lib/Sema/SemaOverload.cpp | 4 +
clang/lib/Sema/SemaSwift.cpp | 3 +
clang/lib/Sema/SemaType.cpp | 2 +
clang/lib/Sema/TreeTransform.h | 29 +++
clang/lib/Serialization/ASTReaderStmt.cpp | 22 ++-
clang/lib/Serialization/ASTWriterStmt.cpp | 12 ++
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 3 +-
clang/test/AST/HLSL/OutArgExpr.hlsl | 65 +++++++
.../BasicFeatures/OutputArguments.hlsl | 128 +++++++++++++
.../SemaHLSL/Language/OutputParameters.hlsl | 34 ++++
.../SemaHLSL/Language/TemplateOutArg.hlsl | 169 ++++++++++++++++++
clang/test/SemaHLSL/parameter_modifiers.hlsl | 8 +-
.../SemaHLSL/parameter_modifiers_ast.hlsl | 20 +--
clang/tools/libclang/CXCursor.cpp | 1 +
43 files changed, 854 insertions(+), 45 deletions(-)
create mode 100644 clang/test/AST/HLSL/OutArgExpr.hlsl
create mode 100644 clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
create mode 100644 clang/test/SemaHLSL/Language/OutputParameters.hlsl
create mode 100644 clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 6d1c8ca8a2f961..e81c7170a022ce 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1377,6 +1377,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);
+
+ /// Determine if two function types are the same, ignoring parameter ABI
+ /// annotations.
+ bool hasSameFunctionTypeIgnoringParamABI(QualType T, QualType U);
+
/// 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 8e9b7ad8b46826..00e3c9d9ab347f 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 5b813bfc2faf90..83eed4827cfe20 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -7061,6 +7061,67 @@ 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.
+class HLSLOutArgExpr : public Expr {
+ friend class ASTStmtReader;
+
+ Expr *Base;
+ Expr *Writeback;
+ OpaqueValueExpr *OpaqueVal;
+ bool IsInOut;
+
+ HLSLOutArgExpr(QualType Ty, Expr *B, Expr *WB, OpaqueValueExpr *OpV,
+ bool IsInOut)
+ : Expr(HLSLOutArgExprClass, Ty, VK_LValue, OK_Ordinary), Base(B),
+ Writeback(WB), OpaqueVal(OpV), IsInOut(IsInOut) {
+ assert(!Ty->isDependentType() && "HLSLOutArgExpr given a dependent type!");
+ }
+
+ explicit HLSLOutArgExpr(EmptyShell Shell)
+ : Expr(HLSLOutArgExprClass, Shell) {}
+
+public:
+ static HLSLOutArgExpr *Create(const ASTContext &C, QualType Ty, Expr *Base,
+ bool IsInOut, Expr *WB, OpaqueValueExpr *OpV);
+ static HLSLOutArgExpr *CreateEmpty(const ASTContext &Ctx);
+
+ const Expr *getBase() const { return Base; }
+ Expr *getBase() { return Base; }
+
+ const Expr *getWriteback() const { return Writeback; }
+ Expr *getWriteback() { return Writeback; }
+
+ const OpaqueValueExpr *getOpaqueValue() const { return OpaqueVal; }
+ OpaqueValueExpr *getOpaqueValue() { return OpaqueVal; }
+
+ bool isInOut() const { return IsInOut; }
+
+ SourceLocation getBeginLoc() const LLVM_READONLY {
+ return Base->getBeginLoc();
+ }
+
+ SourceLocation getEndLoc() const LLVM_READONLY { return Base->getEndLoc(); }
+
+ static bool classof(const Stmt *T) {
+ return T->getStmtClass() == HLSLOutArgExprClass;
+ }
+
+ // Iterators
+ child_range children() {
+ return child_range((Stmt **)&Base, ((Stmt **)&Writeback) + 1);
+ }
+};
+
/// 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 e3c0cb46799f72..27c29099c57cf6 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -4014,6 +4014,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 39dd1f515c9eb3..261853343a0113 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 46d0a66d59c375..6186161e6b182b 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4613,14 +4613,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 581434d33c5c9a..c499ee8ac5906e 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12357,6 +12357,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 fb11e8212f8b68..0ffd9e06cf3e5e 100644
--- a/clang/include/clang/Basic/Specifiers.h
+++ b/clang/include/clang/Basic/Specifiers.h
@@ -382,6 +382,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 9bf23fae50a9e7..a80601b1e4a942 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -306,3 +306,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 2ddbee67c414bb..64b565787f3257 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -61,6 +61,8 @@ class SemaHLSL : public SemaBase {
void handleParamModifierAttr(Decl *D, const ParsedAttr &AL);
bool CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall);
+
+ ExprResult ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg);
};
} // namespace clang
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 5dd0ba33f8a9c2..c19d750d30d56d 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1988,6 +1988,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 a465cdfcf3c89e..750928fc009280 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3590,6 +3590,20 @@ bool ASTContext::hasSameFunctionTypeIgnoringPtrSizes(QualType T, QualType U) {
getFunctionTypeWithoutPtrSizes(U));
}
+QualType ASTContext::getFunctionTypeWithoutParamABIs(QualType T) {
+ 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) {
+ 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 9d5b8167d0ee62..be12e6e93cc453 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;
@@ -5318,3 +5319,13 @@ OMPIteratorExpr *OMPIteratorExpr::CreateEmpty(const ASTContext &Context,
alignof(OMPIteratorExpr));
return new (Mem) OMPIteratorExpr(EmptyShell(), NumIterators);
}
+
+HLSLOutArgExpr *HLSLOutArgExpr::Create(const ASTContext &C, QualType Ty,
+ Expr *Base, bool IsInOut, Expr *WB,
+ OpaqueValueExpr *OpV) {
+ return new (C) HLSLOutArgExpr(Ty, Base, WB, OpV, 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 6482cb6d39acc6..ebbfaa187263fa 100644
--- a/clang/lib/AST/ExprClassification.cpp
+++ b/clang/lib/AST/ExprClassification.cpp
@@ -148,6 +148,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;
// C99 6.5.2.5p5 says that compound literals are lvalues.
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 558e20ed3e4239..c692d47ffd1afa 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -16469,6 +16469,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 d46d621d4c7d41..1a1c316b90c4e6 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -3507,6 +3507,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:
@@ -5703,6 +5709,12 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
Out << "E";
break;
}
+ case Expr::HLSLOutArgExprClass: {
+ const auto *OAE = cast<clang::HLSLOutArgExpr>(E);
+ Out << (OAE->isInOut() ? "_inout_" : "_out_");
+ mangleType(E->getType());
+ break;
+ }
}
if (AsTemplateArg && !IsPrimaryExpr)
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index 69e0b763e8ddcc..0a2ac8c16671ad 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -2799,6 +2799,10 @@ void StmtPrinter::VisitAsTypeExpr(AsTypeExpr *Node) {
OS << ")";
}
+void StmtPrinter::VisitHLSLOutArgExpr(HLSLOutArgExpr *Node) {
+ PrintExpr(Node->getBase());
+}
+
//===----------------------------------------------------------------------===//
// Stmt method implementations
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 89d2a422509d81..37812812ee2b37 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -2631,6 +2631,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 5ba9523504258e..ff88f4aec98a53 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -2874,6 +2874,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 ffec3ef9d22692..b88e9b8f7f4715 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -933,6 +933,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");
}
@@ -955,7 +959,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());
@@ -2023,10 +2037,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 2f3dd5d01fa6c9..fd1788893b7ac3 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2830,6 +2830,9 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
}
switch (FI.getExtParameterInfo(ArgNo).getABI()) {
+ case ParameterABI::HLSLOut:
+ case ParameterABI::HLSLInOut:
+ // FIXME: Do this...
case ParameterABI::Ordinary:
break;
@@ -4148,6 +4151,30 @@ static void emitWriteback(CodeGenFunction &CGF,
assert(!isProvablyNull(srcAddr.getBasePointer()) &&
"shouldn't have writeback for provably null argument");
+ if (CGF.getLangOpts().HLSL) {
+ if (writeback.CastExpr) {
+ RValue TmpVal = CGF.EmitAnyExprToTemp(writeback.CastExpr);
+ if (TmpVal.isScalar())
+ CGF.EmitStoreThroughLValue(TmpVal, srcLV);
+ else
+ CGF.EmitAggregateStore(srcLV.getPointer(CGF),
+ TmpVal.getAggregateAddress(), false);
+ } else {
+ if (srcLV.isSimple())
+ CGF.EmitAggregateStore(srcLV.getPointer(CGF), writeback.Temporary,
+ false);
+ else {
+ llvm::Value *value = CGF.Builder.CreateLoad(writeback.Temporary);
+ RValue TmpVal = RValue::get(value);
+ CGF.EmitStoreThroughLValue(TmpVal, srcLV);
+ }
+ }
+ 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
@@ -4610,6 +4637,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();
}
}
@@ -4689,6 +4719,32 @@ 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)) {
+ LValue LV = EmitLValue(E);
+ llvm::Type *ElTy = ConvertTypeForMem(LV.getType());
+ llvm::Value *Addr, *BaseAddr;
+ if (LV.isExtVectorElt()) {
+ llvm::Constant *VecElts = LV.getExtVectorElts();
+ BaseAddr = LV.getExtVectorAddress().getBasePointer();
+ Addr = Builder.CreateGEP(
+ ElTy, BaseAddr,
+ {Builder.getInt32(0), VecElts->getAggregateElement((unsigned)0)});
+ } else // LV.getAddress() will assert if this is not a simple LValue.
+ Addr = BaseAddr = LV.getAddress().getBasePointer();
+
+ llvm::TypeSize Sz =
+ CGM.getDataLayout().getTypeAllocSize(ConvertTypeForMem(LV.getType()));
+
+ llvm::Value *LifetimeSize = EmitLifetimeStart(Sz, BaseAddr);
+
+ Address TmpAddr(Addr, ElTy, LV.getAlignment());
+ // TODO-HLSLOutArgExp: Fix me!!!
+ args.addWriteback(EmitLValue(OE->getBase()->IgnoreImpCasts()), TmpAddr,
+ nullptr, OE->getWriteback(), LifetimeSize);
+ return args.add(RValue::get(TmpAddr, *this), type);
+ }
+
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..3c3fc8f8a8b6d6 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 representing a cast from the temporary's type to the
+ /// source l-value's type.
+ const Expr *CastExpr;
+
+ // 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 *castExpr = nullptr,
+ llvm::Value *lifetimeSz = nullptr) {
+ Writeback writeback = {srcLV, temporary, toUse, castExpr, 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 5f58a64d8386c3..2c8c31c8bdf529 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1671,6 +1671,8 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
return EmitCoyieldLValue(cast<CoyieldExpr>(E));
case Expr::PackIndexingExprClass:
return EmitLValue(cast<PackIndexingExpr>(E)->getSelectedExpr());
+ case Expr::HLSLOutArgExprClass:
+ return EmitHLSLOutArgExpr(cast<HLSLOutArgExpr>(E));
}
}
@@ -5386,6 +5388,29 @@ LValue CodeGenFunction::EmitOpaqueValueLValue(const OpaqueValueExpr *e) {
return getOrCreateOpaqueLValueMapping(e);
}
+LValue CodeGenFunction::BindHLSLOutArgExpr(const HLSLOutArgExpr *E,
+ Address OutTemp) {
+ LValue Result = MakeAddrLValue(OutTemp, E->getType());
+ OpaqueValueMappingData::bind(*this, E->getOpaqueValue(), Result);
+ return Result;
+}
+
+LValue CodeGenFunction::EmitHLSLOutArgExpr(const HLSLOutArgExpr *E) {
+ if (!E->isInOut())
+ return BindHLSLOutArgExpr(E, CreateIRTemp(E->getType()));
+
+ RValue InVal = EmitAnyExprToTemp(E->getBase());
+ if (!InVal.isScalar())
+ return BindHLSLOutArgExpr(E, InVal.getAggregateAddress());
+
+ Address OutTemp = CreateIRTemp(E->getType());
+ llvm::Value *V = InVal.getScalarVal();
+ if (V->getType()->getScalarType()->isIntegerTy(1))
+ V = Builder.CreateZExt(V, ConvertTypeForMem(E->getType()), "frombool");
+ (void)Builder.CreateStore(V, OutTemp);
+ return BindHLSLOutArgExpr(E, OutTemp);
+}
+
LValue
CodeGenFunction::getOrCreateOpaqueLValueMapping(const OpaqueValueExpr *e) {
assert(OpaqueValueMapping::shouldBindAsLValue(e));
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 89cc819c43bb56..6464fbcc95b444 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4293,6 +4293,8 @@ class CodeGenFunction : public CodeGenTypeCache {
LValue EmitCastLValue(const CastExpr *E);
LValue EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E);
LValue EmitOpaqueValueLValue(const OpaqueValueExpr *e);
+ LValue EmitHLSLOutArgExpr(const HLSLOutArgExpr *E);
+ LValue BindHLSLOutArgExpr(const HLSLOutArgExpr *E, Address OutTemp);
Address EmitExtVectorElementLValue(LValue V);
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index bb30b1e289a1c0..0723d8a4c60985 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -11427,6 +11427,19 @@ static void AnalyzeImplicitConversions(
return;
}
+ if (auto *OutArgE = dyn_cast<HLSLOutArgExpr>(E)) {
+ // 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->getBase(), CC, IsListInit});
+ // In all cases where there is a writeback conversion we should analyze its
+ // conversions.
+ if (OutArgE->getWriteback())
+ WorkList.push_back({OutArgE->getWriteback(), CC, IsListInit});
+ return;
+ }
+
if (BinaryOperator *BO = dyn_cast<BinaryOperator>(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 694a754646f274..fa8e79299a77e0 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4057,6 +4057,10 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
NewQType))
return MergeCompatibleFunctionDecls(New, Old, S, MergeTypeWithOld);
+ if (getLangOpts().HLSL && Context.hasSameFunctionTypeIgnoringParamABI(
+ OldQTypeForComparison, NewQType))
+ return MergeCompatibleFunctionDecls(New, Old, S, MergeTypeWithOld);
+
// If the types are imprecise (due to dependent constructs in friends or
// local extern declarations), it's OK if they differ. We'll check again
// during instantiation.
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index 427ffd9061ef3b..e2368bd9954ed4 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 74c0e017059055..698b6df90baeec 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -52,6 +52,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"
@@ -5915,6 +5916,16 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
ProtoArgType->isBlockPointerType())
if (auto *BE = dyn_cast<BlockExpr>(Arg->IgnoreParenNoopCasts(Context)))
BE->getBlockDecl()->setDoesNotEscape();
+ // During template instantiation it is possible that we already created
+ // the HLSLOutArgExpr if it was produced during tree transformation.
+ if ((Proto->getExtParameterInfo(i).getABI() == ParameterABI::HLSLOut ||
+ Proto->getExtParameterInfo(i).getABI() == ParameterABI::HLSLInOut) &&
+ !isa<HLSLOutArgExpr>(Arg)) {
+ ExprResult ArgExpr = HLSL().ActOnOutParamExpr(Param, Arg);
+ if (ArgExpr.isInvalid())
+ return true;
+ Arg = ArgExpr.getAs<Expr>();
+ }
InitializedEntity Entity =
Param ? InitializedEntity::InitializeParameter(Context, Param,
@@ -17008,10 +17019,10 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result,
if (!isa<ConstantExpr>(E))
E = Result ? ConstantExpr::Create(Context, E, APValue(*Result))
: ConstantExpr::Create(Context, E);
-
+
if (Notes.empty())
return E;
-
+
// If our only note is the usual "invalid subexpression" note, just point
// the caret at its location rather than producing an essentially
// redundant note.
@@ -17020,7 +17031,7 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result,
DiagLoc = Notes[0].first;
Notes.clear();
}
-
+
if (getLangOpts().CPlusPlus) {
if (!Diagnoser.Suppress) {
Diagnoser.diagnoseNotICE(*this, DiagLoc) << E->getSourceRange();
@@ -17033,7 +17044,7 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result,
Diagnoser.diagnoseFold(*this, DiagLoc) << E->getSourceRange();
for (const PartialDiagnosticAt &Note : Notes)
Diag(Note.first, Note.second);
-
+
return E;
}
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 9940bc5b4a606a..3118fe577d7747 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -356,7 +356,7 @@ static bool isLegalTypeForHLSLSV_DispatchThreadID(QualType T) {
return true;
}
-void SemaHLSL::handleSV_DispatchThreadIDAttr(Decl *D, const ParsedAttr &AL) {
+void SemaHLSL::handleSV_DispatchThreadIDAttr(Decl *D, const ParsedAttr &AL) {
auto *VD = cast<ValueDecl>(D);
if (!isLegalTypeForHLSLSV_DispatchThreadID(VD->getType())) {
Diag(AL.getLoc(), diag::err_hlsl_attr_invalid_type)
@@ -1121,3 +1121,61 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
}
return false;
}
+
+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();
+ }
+
+ bool RequiresConversion =
+ Ty.getUnqualifiedType() != Arg->getType().getUnqualifiedType();
+
+ // If the unqualified types mismatch we may have some casting. Since this
+ // results in a copy we can ignore qualifiers.
+ if (RequiresConversion) {
+ ExprResult Res =
+ SemaRef.PerformImplicitConversion(Arg, Ty, Sema::AA_Passing);
+ 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);
+ Res = SemaRef.PerformImplicitConversion(OpV, Arg->getType(),
+ Sema::AA_Passing);
+ if (Res.isInvalid())
+ return ExprError();
+ Expr *Writeback = Res.get();
+ auto *OutExpr =
+ HLSLOutArgExpr::Create(Ctx, Ty, Base, IsInOut, Writeback, OpV);
+
+ return ExprResult(OutExpr);
+ }
+
+ auto *OpV = new (Ctx)
+ OpaqueValueExpr(Param->getBeginLoc(), Ty, VK_LValue, OK_Ordinary, Arg);
+ return ExprResult(HLSLOutArgExpr::Create(Ctx, Ty, Arg, IsInOut, OpV, OpV));
+}
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index c5f56ac62b458c..057ecd323cb956 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -7007,6 +7007,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/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 6fa39cdccef2b9..699154d49cacb4 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -2565,6 +2565,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
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 4d68ebf0cc4524..edc724d26269cb 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -16509,6 +16509,35 @@ TreeTransform<Derived>::TransformCapturedStmt(CapturedStmt *S) {
return getSema().ActOnCapturedRegionEnd(Body.get());
}
+template <typename Derived>
+ExprResult TreeTransform<Derived>::TransformHLSLOutArgExpr(HLSLOutArgExpr *E) {
+ ExprResult Res = getDerived().TransformExpr(E->getBase());
+ if (Res.isInvalid())
+ return ExprError();
+
+ if (!getDerived().AlwaysRebuild() && Res.get() == E->getBase())
+ return E;
+
+ Expr *BaseExpr = Res.get();
+ auto *OpV = new (getSema().Context) OpaqueValueExpr(
+ E->getBeginLoc(), E->getType(), VK_LValue, OK_Ordinary, BaseExpr);
+ if (isa<OpaqueValueExpr>(E->getWriteback()))
+ return HLSLOutArgExpr::Create(getSema().Context, E->getType(), BaseExpr,
+ E->isInOut(), OpV, OpV);
+ // If the writeback isn't directly an opaque value, it means there is a
+ // conversion sequence. Since none of these are actually dependent types, and
+ // we already generated this sequence once successfully in the pattern decl it
+ // should be impossible for this to fail generating a new conversion sequence.
+ Res =
+ getSema().PerformImplicitConversion(OpV, E->getType(), Sema::AA_Passing);
+ if (Res.isInvalid())
+ return ExprError();
+ Expr *Writeback = Res.get();
+
+ return HLSLOutArgExpr::Create(getSema().Context, E->getType(), BaseExpr,
+ E->isInOut(), Writeback, OpV);
+}
+
} // 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 e1cba9e612be3d..e348ba441f08c5 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -2832,6 +2832,22 @@ void ASTStmtReader::VisitOpenACCLoopConstruct(OpenACCLoopConstruct *S) {
VisitOpenACCAssociatedStmtConstruct(S);
}
+//===----------------------------------------------------------------------===//
+// HLSL Constructs/Directives.
+//===----------------------------------------------------------------------===//
+
+void ASTStmtReader::VisitHLSLOutArgExpr(HLSLOutArgExpr *S) {
+ VisitExpr(S);
+ S->Base = Record.readSubExpr();
+ S->Writeback = Record.readSubExpr();
+ S->IsInOut = Record.readBool();
+ Expr::child_iterator Child = S->getWriteback()->child_begin();
+ while (!isa<OpaqueValueExpr>(*Child))
+ Child = Child->child_begin();
+ assert(isa<OpaqueValueExpr>(*Child) && "Writeback must end in an OVE.");
+ S->OpaqueVal = cast<OpaqueValueExpr>(*Child);
+}
+
//===----------------------------------------------------------------------===//
// ASTReader Implementation
//===----------------------------------------------------------------------===//
@@ -4280,13 +4296,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 ec667b58337ff5..e43ced0b59ebdc 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -2903,6 +2903,18 @@ void ASTStmtWriter::VisitOpenACCLoopConstruct(OpenACCLoopConstruct *S) {
Code = serialization::STMT_OPENACC_LOOP_CONSTRUCT;
}
+//===----------------------------------------------------------------------===//
+// HLSL Constructs/Directives.
+//===----------------------------------------------------------------------===//
+
+void ASTStmtWriter::VisitHLSLOutArgExpr(HLSLOutArgExpr *S) {
+ VisitExpr(S);
+ Record.AddStmt(S->getBase());
+ Record.AddStmt(S->getWriteback());
+ 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 62a240ecbc6003..c664b01ece8b7b 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1826,7 +1826,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..8ad77bd77a88b3
--- /dev/null
+++ b/clang/test/AST/HLSL/OutArgExpr.hlsl
@@ -0,0 +1,65 @@
+// 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 &X) {
+
+// AST: FunctionDecl {{.*}} used trunc_Param 'void (inout int)'
+// AST-NEXT: ParmVarDecl {{.*}} X 'int &'
+// AST-NEXT: HLSLParamModifierAttr {{.*}} inout
+
+void trunc_Param(inout int X) {}
+
+// CHECK: void zero(out int &Z) {
+// CHECK-NEXT: Z = 0;
+
+// AST: FunctionDecl {{.*}} zero 'void (out int)'
+// AST-NEXT: ParmVarDecl {{.*}} used Z 'int &'
+// AST-NEXT: HLSLParamModifierAttr {{.*}} out
+void zero(out int Z) { Z = 0; }
+
+// AST: 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: ImplicitCastExpr [[BaseCast:0x[0-9a-fA-F]+]] <{{.*}}> 'int' <FloatingToIntegral>
+// AST-NEXT: ImplicitCastExpr {{.*}} 'float' <LValueToRValue>
+// 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
+// AST-NEXT: ImplicitCastExpr {{.*}} 'uint':'unsigned int' <LValueToRValue>
+// AST-NEXT: DeclRefExpr {{.*}} 'uint':'unsigned int' lvalue ParmVar {{.*}} 'GI' 'uint':'unsigned int'
+// AST-NEXT: ImplicitCastExpr {{.*}} 'float' <IntegralToFloating>
+// AST-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
+// AST-NEXT: OpaqueValueExpr {{.*}} 'int' lvalue
+// AST-NEXT: ImplicitCastExpr [[BaseCast]] <{{.*}}> 'int' <FloatingToIntegral>
+
+// CHECK: void fn(uint GI) {
+// CHECK: trunc_Param(Buf[GI]);
+void fn(uint GI) {
+ trunc_Param(Buf[GI]);
+}
+
+#else
+
+// AST: 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-NEXT: DeclRefExpr {{.*}} 'int' lvalue Var {{.*}} 'I' 'int'
+[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..8ed02bb4bbb76a
--- /dev/null
+++ b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
@@ -0,0 +1,128 @@
+// 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.
+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 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.
+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 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.
+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 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.
+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 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.
+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 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;
+}
diff --git a/clang/test/SemaHLSL/Language/OutputParameters.hlsl b/clang/test/SemaHLSL/Language/OutputParameters.hlsl
new file mode 100644
index 00000000000000..44358a2fbca24b
--- /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 converison 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..0416806b569522
--- /dev/null
+++ b/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
@@ -0,0 +1,169 @@
+// 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: ImplicitCastExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int' lvalue ParmVar {{.*}} 'V' 'int'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} <col:6> 'int' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr {{.*}} <col:6> 'int' lvalue
+// CHECK-NEXT: ImplicitCastExpr [[BaseExpr]] {{.*}} 'int' <LValueToRValue>
+
+// 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: ImplicitCastExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'double'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' <LValueToRValue>
+// CHECK-NEXT: DeclRefExpr {{.*}}'float' lvalue ParmVar {{.*}} 'V' 'float'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' <FloatingCast>
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'double' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr {{.*}} 'double' lvalue
+// CHECK-NEXT: ImplicitCastExpr [[BaseExpr]] {{.*}} 'double' <FloatingCast>
+
+// 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: ImplicitCastExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'double' <LValueToRValue>
+// CHECK-NEXT: DeclRefExpr {{.*}}'double' lvalue ParmVar {{.*}} 'V' 'double'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'double' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr {{.*}} 'double' lvalue
+// CHECK-NEXT: ImplicitCastExpr [[BaseExpr]] {{.*}} 'double' <LValueToRValue>
+
+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 &'
+// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
+
+// CHECK-LABEL: FunctionDecl {{.*}} used fizz 'void (inout float)' implicit_instantiation
+// CHECK: ParmVarDecl {{.*}} used V 'float &'
+// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
+
+// CHECK-LABEL: FunctionDecl {{.*}} used fizz 'void (inout double)' implicit_instantiation
+// CHECK: ParmVarDecl {{.*}} used V 'double &'
+// 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: DeclRefExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// CHECK-NEXT: OpaqueValueExpr {{.*}} 'int' lvalue
+// CHECK-NEXT: DeclRefExpr [[BaseExpr]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+
+// 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: DeclRefExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// CHECK-NEXT: OpaqueValueExpr {{.*}} 'int' lvalue
+// CHECK-NEXT: DeclRefExpr [[BaseExpr]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+
+// 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: DeclRefExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// CHECK-NEXT: OpaqueValueExpr {{.*}} 'int' lvalue
+// CHECK-NEXT: DeclRefExpr [[BaseExpr]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+
+
+// 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: DeclRefExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// CHECK-NEXT: OpaqueValueExpr {{.*}} 'int' lvalue
+// CHECK-NEXT: DeclRefExpr [[BaseExpr]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+
+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..cf739851e4547a 100644
--- a/clang/test/SemaHLSL/parameter_modifiers.hlsl
+++ b/clang/test/SemaHLSL/parameter_modifiers.hlsl
@@ -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.
diff --git a/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl b/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl
index 50b162bdfc26cc..c5246045be259e 100644
--- a/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl
+++ b/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl
@@ -11,25 +11,25 @@ void fn(float f);
// CHECK-NOT: HLSLParamModifierAttr
void fn2(in float f);
-// CHECK: FunctionDecl {{.*}} fn3 'void (float &)'
+// CHECK: FunctionDecl {{.*}} fn3 'void (out float)'
// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} out
// CHECK-NOT: HLSLParamModifierAttr
void fn3(out float f);
-// CHECK: FunctionDecl {{.*}} fn4 'void (float &)'
+// CHECK: FunctionDecl {{.*}} fn4 'void (inout float)'
// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
// CHECK-NOT: HLSLParamModifierAttr
void fn4(inout float f);
-// CHECK: FunctionDecl {{.*}} fn5 'void (float &)'
+// CHECK: FunctionDecl {{.*}} fn5 'void (inout float)'
// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout MergedSpelling
// CHECK-NOT: HLSLParamModifierAttr
void fn5(out in float f);
-// CHECK: FunctionDecl {{.*}} fn6 'void (float &)'
+// CHECK: FunctionDecl {{.*}} fn6 'void (inout float)'
// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout MergedSpelling
// CHECK-NOT: HLSLParamModifierAttr
@@ -37,10 +37,10 @@ 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 &'
@@ -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 782c0c243ef1fb..9e8ebe57b23243 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;
>From ac6799f0b05450b3932692d90ac2f9bc1feff5fc Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Mon, 29 Jul 2024 18:37:48 -0500
Subject: [PATCH 02/19] Fix aggregate copy emission
This fixes up the code generation errors in non-scalar writeback
operations.
---
clang/lib/CodeGen/CGCall.cpp | 20 +++----
.../BasicFeatures/OutputArguments.hlsl | 55 +++++++++++++++++++
2 files changed, 65 insertions(+), 10 deletions(-)
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index fd1788893b7ac3..018938eb53621a 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4152,20 +4152,20 @@ static void emitWriteback(CodeGenFunction &CGF,
"shouldn't have writeback for provably null argument");
if (CGF.getLangOpts().HLSL) {
- if (writeback.CastExpr) {
+ if (!isa<OpaqueValueExpr>(writeback.CastExpr)) {
RValue TmpVal = CGF.EmitAnyExprToTemp(writeback.CastExpr);
if (TmpVal.isScalar())
CGF.EmitStoreThroughLValue(TmpVal, srcLV);
- else
- CGF.EmitAggregateStore(srcLV.getPointer(CGF),
- TmpVal.getAggregateAddress(), false);
- } else {
- if (srcLV.isSimple())
- CGF.EmitAggregateStore(srcLV.getPointer(CGF), writeback.Temporary,
- false);
else {
- llvm::Value *value = CGF.Builder.CreateLoad(writeback.Temporary);
- RValue TmpVal = RValue::get(value);
+ llvm::Value *Val = CGF.Builder.CreateLoad(TmpVal.getAggregateAddress());
+ CGF.EmitAggregateStore(Val, srcLV.getAddress(), false);
+ }
+ } else {
+ llvm::Value *Val = CGF.Builder.CreateLoad(writeback.Temporary);
+ if (srcLV.isSimple()) {
+ CGF.EmitAggregateStore(Val, srcLV.getAddress(), false);
+ } else {
+ RValue TmpVal = RValue::get(Val);
CGF.EmitStoreThroughLValue(TmpVal, srcLV);
}
}
diff --git a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
index 8ed02bb4bbb76a..b64524a13c9231 100644
--- a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
+++ b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
@@ -126,3 +126,58 @@ export int case5() {
increment(I);
return I;
}
+
+// Case 6: Aggregate out parameters.
+struct S {
+ int X;
+ float Y;
+};
+
+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 noundef nonnull align 4 dereferenceable(8) [[Tmp]])
+// CHECK: [[RetVal:%.*]] = load %struct.S, ptr [[Tmp]]
+// CHECK: [[XAddr:%.*]] = getelementptr inbounds %struct.S, ptr [[S]], i32 0, i32 0
+// CHECK: [[XVal:%.*]] = extractvalue %struct.S [[RetVal]], 0
+// CHECK: store i32 [[XVal]], ptr [[XAddr]]
+// CHECK: [[YAddr:%.*]] = getelementptr inbounds %struct.S, ptr [[S]], i32 0, i32 1
+// CHECK: [[YVal:%.*]] = extractvalue %struct.S [[RetVal]], 1
+// CHECK: store float [[YVal]], ptr [[YAddr]]
+
+// OPT: ret i32 7
+export int case6() {
+ S s;
+ init(s);
+ return s.X + s.Y;
+}
+
+// Case 7: Non-scalars with a cast expression.
+void trunc_vec(inout int3 V) {}
+
+// ALL-LABEL: define noundef <3 x float> {{.*}}case7
+
+// 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 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 case7(float3 V) {
+ trunc_vec(V);
+ return V;
+}
>From d865dbf0891810ace90ae6c1e82104958d9c6c8f Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Mon, 29 Jul 2024 20:19:41 -0500
Subject: [PATCH 03/19] Move parameter ABI qualifier mismatch
This change moves the verification for parameter ABI qualifier mismatch
earlier and only merges declarations if the parameter modifiers are
compatible.
There is one case where we do need to merge parameter ABI annotations,
this is in the case for explicit `in` and implicit `in` (no attribute).
All other cases are handled during attribute merging.
This addresses feedback from @efriedma-quic, thank you!
---
clang/include/clang/AST/ASTContext.h | 8 -----
clang/include/clang/Sema/SemaHLSL.h | 1 +
clang/lib/AST/ASTContext.cpp | 14 ---------
clang/lib/Sema/SemaDecl.cpp | 31 +++++--------------
clang/lib/Sema/SemaHLSL.cpp | 32 ++++++++++++++++++++
clang/test/SemaHLSL/parameter_modifiers.hlsl | 2 +-
6 files changed, 41 insertions(+), 47 deletions(-)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index e81c7170a022ce..6d1c8ca8a2f961 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1377,14 +1377,6 @@ 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);
-
- /// Determine if two function types are the same, ignoring parameter ABI
- /// annotations.
- bool hasSameFunctionTypeIgnoringParamABI(QualType T, QualType U);
-
/// 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/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 64b565787f3257..8e1965c252e7d9 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -61,6 +61,7 @@ class SemaHLSL : public SemaBase {
void handleParamModifierAttr(Decl *D, const ParsedAttr &AL);
bool CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall);
+ bool CheckCompatibleParameterABI(FunctionDecl *New, FunctionDecl *Old);
ExprResult ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg);
};
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 750928fc009280..a465cdfcf3c89e 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3590,20 +3590,6 @@ bool ASTContext::hasSameFunctionTypeIgnoringPtrSizes(QualType T, QualType U) {
getFunctionTypeWithoutPtrSizes(U));
}
-QualType ASTContext::getFunctionTypeWithoutParamABIs(QualType T) {
- 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) {
- 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/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index fa8e79299a77e0..315e8bf4eee649 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3244,26 +3244,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<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;
@@ -4049,6 +4029,13 @@ 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;
+ return MergeCompatibleFunctionDecls(New, Old, S, MergeTypeWithOld);
+ }
+
// 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
@@ -4057,10 +4044,6 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
NewQType))
return MergeCompatibleFunctionDecls(New, Old, S, MergeTypeWithOld);
- if (getLangOpts().HLSL && Context.hasSameFunctionTypeIgnoringParamABI(
- OldQTypeForComparison, NewQType))
- return MergeCompatibleFunctionDecls(New, Old, S, MergeTypeWithOld);
-
// If the types are imprecise (due to dependent constructs in friends or
// local extern declarations), it's OK if they differ. We'll check again
// during instantiation.
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 3118fe577d7747..dcc0eca3b09ef2 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -1122,6 +1122,38 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
return false;
}
+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<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");
diff --git a/clang/test/SemaHLSL/parameter_modifiers.hlsl b/clang/test/SemaHLSL/parameter_modifiers.hlsl
index cf739851e4547a..2347c17ce0afdd 100644
--- a/clang/test/SemaHLSL/parameter_modifiers.hlsl
+++ b/clang/test/SemaHLSL/parameter_modifiers.hlsl
@@ -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}}
}
>From 6c8c058d2fad499a907dc357daeca21d5001c9b3 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Mon, 29 Jul 2024 20:21:21 -0500
Subject: [PATCH 04/19] Fix clang-format
---
clang/lib/Sema/SemaDecl.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 315e8bf4eee649..48ad2fb9e89d1d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4031,7 +4031,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
// HLSL check parameters for matching ABI specifications.
if (getLangOpts().HLSL) {
- if(HLSL().CheckCompatibleParameterABI(New, Old))
+ if (HLSL().CheckCompatibleParameterABI(New, Old))
return true;
return MergeCompatibleFunctionDecls(New, Old, S, MergeTypeWithOld);
}
>From 3441e910c1e77c96e152a2eb26a18b2c4f83d30b Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Tue, 30 Jul 2024 08:49:54 -0500
Subject: [PATCH 05/19] Limit when function declarations are merged
This change makes it so that function declarations are only merged when
no parameter ABI mismatches occur and when the functions have the same
type ignoring the paramater ABIs.
This allows handling cases where implict and explicit parameter ABIs
are the same as with the implicit in and explict in for HLSL.
---
clang/include/clang/AST/ASTContext.h | 8 ++++++++
clang/lib/AST/ASTContext.cpp | 15 +++++++++++++++
clang/lib/Sema/SemaDecl.cpp | 11 ++++++++++-
3 files changed, 33 insertions(+), 1 deletion(-)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 6d1c8ca8a2f961..6957afde53d9d9 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1377,6 +1377,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/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index a465cdfcf3c89e..667aa30ef75eb5 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3590,6 +3590,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/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 48ad2fb9e89d1d..74f0b37f991d3c 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -4033,7 +4033,16 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
if (getLangOpts().HLSL) {
if (HLSL().CheckCompatibleParameterABI(New, Old))
return true;
- return MergeCompatibleFunctionDecls(New, Old, S, MergeTypeWithOld);
+
+ // 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
>From 2283f0aedfd9dd038d08f305a1f302554cb020f0 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Wed, 31 Jul 2024 09:23:10 -0500
Subject: [PATCH 06/19] Resolve FIXME/TODO notes
One of these was left behind to properly put in `noalias` call argument
attributes, the other was there while I was working out the writeback
logic and is resolved and verified by the tests.
---
clang/lib/CodeGen/CGCall.cpp | 4 ++--
.../CodeGenHLSL/BasicFeatures/OutputArguments.hlsl | 14 +++++++-------
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 018938eb53621a..7c825047c9d37c 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2832,7 +2832,8 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
switch (FI.getExtParameterInfo(ArgNo).getABI()) {
case ParameterABI::HLSLOut:
case ParameterABI::HLSLInOut:
- // FIXME: Do this...
+ Attrs.addAttribute(llvm::Attribute::NoAlias);
+ break;
case ParameterABI::Ordinary:
break;
@@ -4739,7 +4740,6 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
llvm::Value *LifetimeSize = EmitLifetimeStart(Sz, BaseAddr);
Address TmpAddr(Addr, ElTy, LV.getAlignment());
- // TODO-HLSLOutArgExp: Fix me!!!
args.addWriteback(EmitLValue(OE->getBase()->IgnoreImpCasts()), TmpAddr,
nullptr, OE->getWriteback(), LifetimeSize);
return args.add(RValue::get(TmpAddr, *this), type);
diff --git a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
index b64524a13c9231..be7a17f43e845b 100644
--- a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
+++ b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
@@ -13,7 +13,7 @@ void trunc_Param(inout int X) {}
// CHECK: [[FVal:%.*]] = load float, ptr {{.*}}
// CHECK: [[IVal:%.*]] = fptosi float [[FVal]] to i32
// CHECK: store i32 [[IVal]], ptr [[ArgTmp]]
-// CHECK: call void {{.*}}trunc_Param{{.*}}(ptr noundef nonnull align 4 dereferenceable(4) [[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]]
@@ -35,7 +35,7 @@ void undef(out int Z) { }
// CHECK: [[V:%.*]] = alloca i32
// CHECK: [[ArgTmp:%.*]] = alloca i32
// CHECK-NOT: store {{.*}}, ptr [[ArgTmp]]
-// CHECK: call void {{.*}}unde{{.*}}(ptr noundef nonnull align 4 dereferenceable(4) [[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
@@ -54,7 +54,7 @@ void zero(out int Z) { Z = 0; }
// CHECK: [[V:%.*]] = alloca i32
// CHECK: [[ArgTmp:%.*]] = alloca i32
// CHECK-NOT: store {{.*}}, ptr [[ArgTmp]]
-// CHECK: call void {{.*}}zero{{.*}}(ptr noundef nonnull align 4 dereferenceable(4) [[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
@@ -90,7 +90,7 @@ void funky(inout int3 X) {
// CHECK: store <3 x i32> [[Vyzx]], ptr [[ArgTmp]]
// Call the function with the temporary.
-// CHECK: call void {{.*}}funky{{.*}}(ptr noundef nonnull align 16 dereferenceable(16) [[ArgTmp]])
+// CHECK: call void {{.*}}funky{{.*}}(ptr noalias noundef nonnull align 16 dereferenceable(16) [[ArgTmp]])
// Shuffle it back.
// CHECK: [[RetVal:%.*]] = load <3 x i32>, ptr [[ArgTmp]]
@@ -117,7 +117,7 @@ void increment(inout int I) {
// 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 noundef nonnull align 4 dereferenceable(4) [[ArgTmp]])
+// 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
@@ -142,7 +142,7 @@ void init(out S s) {
// CHECK: [[S:%.*]] = alloca %struct.S
// CHECK: [[Tmp:%.*]] = alloca %struct.S
-// CHECK: call void {{.*}}init{{.*}}(ptr noundef nonnull align 4 dereferenceable(8) [[Tmp]])
+// CHECK: call void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) [[Tmp]])
// CHECK: [[RetVal:%.*]] = load %struct.S, ptr [[Tmp]]
// CHECK: [[XAddr:%.*]] = getelementptr inbounds %struct.S, ptr [[S]], i32 0, i32 0
// CHECK: [[XVal:%.*]] = extractvalue %struct.S [[RetVal]], 0
@@ -168,7 +168,7 @@ void trunc_vec(inout int3 V) {}
// 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 noundef nonnull align 16 dereferenceable(16) [[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]]
>From 6b66117993c8c7a2296820f53240e518237a99b9 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Wed, 31 Jul 2024 13:43:57 -0500
Subject: [PATCH 07/19] One last fix to correctly annotate parameter types
We should be annotating the parameter types as restrict references.
This allows the alias information within a function to correctly
identify non-aliasing parameters.
---
clang/include/clang/Sema/SemaHLSL.h | 2 ++
clang/lib/Sema/SemaHLSL.cpp | 6 ++++++
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp | 3 ++-
clang/lib/Sema/SemaType.cpp | 3 ++-
clang/test/AST/HLSL/OutArgExpr.hlsl | 9 +++++----
.../BasicFeatures/OutputArguments.hlsl | 16 +++++++++++++++-
clang/test/SemaHLSL/Language/TemplateOutArg.hlsl | 6 +++---
clang/test/SemaHLSL/parameter_modifiers_ast.hlsl | 10 +++++-----
8 files changed, 40 insertions(+), 15 deletions(-)
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 8e1965c252e7d9..6ecba4e32c2660 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -64,6 +64,8 @@ class SemaHLSL : public SemaBase {
bool CheckCompatibleParameterABI(FunctionDecl *New, FunctionDecl *Old);
ExprResult ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg);
+
+ QualType getInoutParameterType(QualType Ty);
};
} // namespace clang
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index dcc0eca3b09ef2..6236dc3c1ae23f 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -1211,3 +1211,9 @@ ExprResult SemaHLSL::ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg) {
OpaqueValueExpr(Param->getBeginLoc(), Ty, VK_LValue, OK_Ordinary, Arg);
return ExprResult(HLSLOutArgExpr::Create(Ctx, Ty, Arg, IsInOut, OpV, OpV));
}
+
+QualType SemaHLSL::getInoutParameterType(QualType Ty) {
+ Ty = SemaRef.getASTContext().getLValueReferenceType(Ty);
+ Ty.addRestrict();
+ return Ty;
+}
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index a12d2eff1d2c81..161328c2d17ce7 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -28,6 +28,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"
@@ -696,7 +697,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 699154d49cacb4..8582aba721b150 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -36,6 +36,7 @@
#include "clang/Sema/ScopeInfo.h"
#include "clang/Sema/SemaCUDA.h"
#include "clang/Sema/SemaInternal.h"
+#include "clang/Sema/SemaHLSL.h"
#include "clang/Sema/SemaObjC.h"
#include "clang/Sema/SemaOpenMP.h"
#include "clang/Sema/Template.h"
@@ -8491,7 +8492,7 @@ static void HandleHLSLParamModifierAttr(QualType &CurType,
return;
if (Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_inout ||
Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_out)
- CurType = S.getASTContext().getLValueReferenceType(CurType);
+ CurType = S.HLSL().getInoutParameterType(CurType);
}
static void processTypeAttrs(TypeProcessingState &state, QualType &type,
diff --git a/clang/test/AST/HLSL/OutArgExpr.hlsl b/clang/test/AST/HLSL/OutArgExpr.hlsl
index 8ad77bd77a88b3..4e34de8f85ffbe 100644
--- a/clang/test/AST/HLSL/OutArgExpr.hlsl
+++ b/clang/test/AST/HLSL/OutArgExpr.hlsl
@@ -1,3 +1,4 @@
+// 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
@@ -8,19 +9,19 @@
RWBuffer<float> Buf;
-// CHECK: void trunc_Param(inout int &X) {
+// CHECK: void trunc_Param(inout int &__restrict X) {
// AST: FunctionDecl {{.*}} used trunc_Param 'void (inout int)'
-// AST-NEXT: ParmVarDecl {{.*}} X 'int &'
+// AST-NEXT: ParmVarDecl {{.*}} X 'int &__restrict'
// AST-NEXT: HLSLParamModifierAttr {{.*}} inout
void trunc_Param(inout int X) {}
-// CHECK: void zero(out int &Z) {
+// CHECK: void zero(out int &__restrict Z) {
// CHECK-NEXT: Z = 0;
// AST: FunctionDecl {{.*}} zero 'void (out int)'
-// AST-NEXT: ParmVarDecl {{.*}} used Z 'int &'
+// AST-NEXT: ParmVarDecl {{.*}} used Z 'int &__restrict'
// AST-NEXT: HLSLParamModifierAttr {{.*}} out
void zero(out int Z) { Z = 0; }
diff --git a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
index be7a17f43e845b..25e67aebf0a241 100644
--- a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
+++ b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
@@ -5,6 +5,8 @@
// 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
@@ -29,6 +31,8 @@ export float case1(float F) {
// `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
@@ -47,7 +51,10 @@ export int case2() {
}
// Case 3: Simple initialized `out` parameter.
-// This test should verify that an out parameter value is written to as expected.
+// 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
@@ -68,6 +75,8 @@ export int case3() {
// 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;
@@ -106,6 +115,8 @@ export int3 case4() {
// 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;
}
@@ -133,6 +144,7 @@ struct S {
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;
@@ -159,6 +171,8 @@ export int case6() {
}
// Case 7: 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> {{.*}}case7
diff --git a/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl b/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
index 0416806b569522..bec81f16b40fb2 100644
--- a/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
+++ b/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
@@ -85,15 +85,15 @@ T wrapper(T V) {
// Check the 3 instantiations (int, float, & double).
// CHECK-LABEL: FunctionDecl {{.*}} used fizz 'void (inout int)' implicit_instantiation
-// CHECK: ParmVarDecl {{.*}} used V 'int &'
+// 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 &'
+// 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 &'
+// CHECK: ParmVarDecl {{.*}} used V 'double &__restrict'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
template <typename T>
void fizz(inout T V) {
diff --git a/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl b/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl
index c5246045be259e..bf5ade162a01a1 100644
--- a/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl
+++ b/clang/test/SemaHLSL/parameter_modifiers_ast.hlsl
@@ -12,25 +12,25 @@ void fn(float f);
void fn2(in float f);
// CHECK: FunctionDecl {{.*}} fn3 'void (out float)'
-// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
+// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &__restrict'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} out
// CHECK-NOT: HLSLParamModifierAttr
void fn3(out float f);
// CHECK: FunctionDecl {{.*}} fn4 'void (inout float)'
-// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
+// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &__restrict'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout
// CHECK-NOT: HLSLParamModifierAttr
void fn4(inout float f);
// CHECK: FunctionDecl {{.*}} fn5 'void (inout float)'
-// CHECK-NEXT: ParmVarDecl {{.*}} f '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 (inout float)'
-// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &'
+// CHECK-NEXT: ParmVarDecl {{.*}} f 'float &__restrict'
// CHECK-NEXT: HLSLParamModifierAttr {{.*}} inout MergedSpelling
// CHECK-NOT: HLSLParamModifierAttr
void fn6(in out float f);
@@ -43,7 +43,7 @@ void fn6(in out float f);
// 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>
>From 7acfe268a4ad0fc127a3e4c3b6e0259e8028ab43 Mon Sep 17 00:00:00 2001
From: Chris B <cbieneman at microsoft.com>
Date: Thu, 1 Aug 2024 13:44:02 -0500
Subject: [PATCH 08/19] Update
clang/test/SemaHLSL/Language/OutputParameters.hlsl
Co-authored-by: Damyan Pepper <damyanp at microsoft.com>
---
clang/test/SemaHLSL/Language/OutputParameters.hlsl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/SemaHLSL/Language/OutputParameters.hlsl b/clang/test/SemaHLSL/Language/OutputParameters.hlsl
index 44358a2fbca24b..6d4d59771f8e47 100644
--- a/clang/test/SemaHLSL/Language/OutputParameters.hlsl
+++ b/clang/test/SemaHLSL/Language/OutputParameters.hlsl
@@ -5,7 +5,7 @@ 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 converison is implicit,
+// arguments to vector parameters because scalar->vector conversion is implicit,
// but vector->scalar is not.
void case1() {
float f;
>From c094804524e20dfdce0528bd76543ea3fba9428e Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Mon, 5 Aug 2024 13:23:59 -0500
Subject: [PATCH 09/19] Fix include ordering
I swear I know my A, B, C's...
---
clang/lib/Sema/SemaType.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 8582aba721b150..a1de852aeae63c 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -35,8 +35,8 @@
#include "clang/Sema/ParsedTemplate.h"
#include "clang/Sema/ScopeInfo.h"
#include "clang/Sema/SemaCUDA.h"
-#include "clang/Sema/SemaInternal.h"
#include "clang/Sema/SemaHLSL.h"
+#include "clang/Sema/SemaInternal.h"
#include "clang/Sema/SemaObjC.h"
#include "clang/Sema/SemaOpenMP.h"
#include "clang/Sema/Template.h"
>From adb4d3cd1b6052f2b05050f7739e7c284edb5776 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Mon, 19 Aug 2024 16:47:30 -0500
Subject: [PATCH 10/19] Update for upstream changes
(more changes coming)
---
clang/lib/CodeGen/CGCall.cpp | 16 ++++++++++++++--
.../BasicFeatures/OutputArguments.hlsl | 4 ++--
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 8b6c47a6527639..eb2431c0673e41 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4142,12 +4142,24 @@ static void emitWriteback(CodeGenFunction &CGF,
CGF.EmitStoreThroughLValue(TmpVal, srcLV);
else {
llvm::Value *Val = CGF.Builder.CreateLoad(TmpVal.getAggregateAddress());
- CGF.EmitAggregateStore(Val, srcLV.getAddress(), false);
+ CGF.CreateCoercedStore(
+ Val, srcLV.getAddress(),
+ llvm::TypeSize::getFixed(
+ CGF.getContext()
+ .getTypeSizeInChars(writeback.CastExpr->getType())
+ .getQuantity()),
+ false);
}
} else {
llvm::Value *Val = CGF.Builder.CreateLoad(writeback.Temporary);
if (srcLV.isSimple()) {
- CGF.EmitAggregateStore(Val, srcLV.getAddress(), false);
+ CGF.CreateCoercedStore(
+ Val, srcLV.getAddress(),
+ llvm::TypeSize::getFixed(
+ CGF.getContext()
+ .getTypeSizeInChars(writeback.CastExpr->getType())
+ .getQuantity()),
+ false);
} else {
RValue TmpVal = RValue::get(Val);
CGF.EmitStoreThroughLValue(TmpVal, srcLV);
diff --git a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
index 25e67aebf0a241..671c176d99441d 100644
--- a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
+++ b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
@@ -156,10 +156,10 @@ void init(out S s) {
// CHECK: [[Tmp:%.*]] = alloca %struct.S
// CHECK: call void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) [[Tmp]])
// CHECK: [[RetVal:%.*]] = load %struct.S, ptr [[Tmp]]
-// CHECK: [[XAddr:%.*]] = getelementptr inbounds %struct.S, ptr [[S]], i32 0, i32 0
+// CHECK: [[XAddr:%.*]] = getelementptr inbounds nuw %struct.S, ptr [[S]], i32 0, i32 0
// CHECK: [[XVal:%.*]] = extractvalue %struct.S [[RetVal]], 0
// CHECK: store i32 [[XVal]], ptr [[XAddr]]
-// CHECK: [[YAddr:%.*]] = getelementptr inbounds %struct.S, ptr [[S]], i32 0, i32 1
+// CHECK: [[YAddr:%.*]] = getelementptr inbounds nuw %struct.S, ptr [[S]], i32 0, i32 1
// CHECK: [[YVal:%.*]] = extractvalue %struct.S [[RetVal]], 1
// CHECK: store float [[YVal]], ptr [[YAddr]]
>From a529de0cd86cfc899c7cfe44f1459d5371de8478 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Mon, 26 Aug 2024 18:59:10 -0500
Subject: [PATCH 11/19] Update based on PR feedback
This reworks the PR based on feedback from @rjmcall.
There are a few differences here from his suggestions, but I think this
is the right direction.
The AST node now always creates two opaque values. One that has a
source expression of the original argument lvalue, and one that has a
source expression of the parameter temporary.
The casted temporary is initiailzed via copy initialization, and the
writeback follows the same flow. I had some trouble getting ActOnBinOp
working, and while I haven't given up on it I think this approach works
too.
I've added expanded test coverage of side-effecting lvalues,
order-dependent writebacks, writebacks through bitfields, and more.
---
clang/include/clang/AST/Expr.h | 62 ++++---
clang/lib/AST/Expr.cpp | 6 +-
clang/lib/AST/ItaniumMangle.cpp | 9 +-
clang/lib/AST/StmtPrinter.cpp | 2 +-
clang/lib/CodeGen/CGCall.cpp | 67 +++-----
clang/lib/CodeGen/CGExpr.cpp | 56 +++++--
clang/lib/CodeGen/CodeGenFunction.h | 2 +-
clang/lib/Sema/SemaChecking.cpp | 8 +-
clang/lib/Sema/SemaHLSL.cpp | 53 +++---
clang/lib/Sema/TreeTransform.h | 28 +---
clang/lib/Serialization/ASTReaderStmt.cpp | 10 +-
clang/lib/Serialization/ASTWriterStmt.cpp | 5 +-
clang/test/AST/HLSL/OutArgExpr.hlsl | 32 +++-
.../BasicFeatures/OutputArguments.hlsl | 151 ++++++++++++++++--
.../SemaHLSL/Language/TemplateOutArg.hlsl | 86 ++++++----
15 files changed, 373 insertions(+), 204 deletions(-)
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 83eed4827cfe20..534f64d04dbbbe 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -7075,15 +7075,23 @@ class ArraySectionExpr : public Expr {
class HLSLOutArgExpr : public Expr {
friend class ASTStmtReader;
- Expr *Base;
- Expr *Writeback;
- OpaqueValueExpr *OpaqueVal;
+ enum {
+ BaseLValue,
+ CastedTemporary,
+ WritebackCast,
+ NumSubExprs,
+ };
+
+ Stmt *SubExprs[NumSubExprs];
bool IsInOut;
- HLSLOutArgExpr(QualType Ty, Expr *B, Expr *WB, OpaqueValueExpr *OpV,
- bool IsInOut)
- : Expr(HLSLOutArgExprClass, Ty, VK_LValue, OK_Ordinary), Base(B),
- Writeback(WB), OpaqueVal(OpV), IsInOut(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!");
}
@@ -7091,26 +7099,44 @@ class HLSLOutArgExpr : public Expr {
: Expr(HLSLOutArgExprClass, Shell) {}
public:
- static HLSLOutArgExpr *Create(const ASTContext &C, QualType Ty, Expr *Base,
- bool IsInOut, Expr *WB, OpaqueValueExpr *OpV);
+ static HLSLOutArgExpr *Create(const ASTContext &C, QualType Ty,
+ OpaqueValueExpr *Base, OpaqueValueExpr *OpV,
+ Expr *WB, bool IsInOut);
static HLSLOutArgExpr *CreateEmpty(const ASTContext &Ctx);
- const Expr *getBase() const { return Base; }
- Expr *getBase() { return Base; }
+ const OpaqueValueExpr *getOpaqueArgLValue() const {
+ return cast<OpaqueValueExpr>(SubExprs[BaseLValue]);
+ }
+ OpaqueValueExpr *getOpaqueArgLValue() {
+ return cast<OpaqueValueExpr>(SubExprs[BaseLValue]);
+ }
+
+ const Expr *getArgLValue() const {
+ return getOpaqueArgLValue()->getSourceExpr();
+ }
+ Expr *getArgLValue() { return getOpaqueArgLValue()->getSourceExpr(); }
- const Expr *getWriteback() const { return Writeback; }
- Expr *getWriteback() { return Writeback; }
+ const Expr *getWritebackCast() const {
+ return cast<Expr>(SubExprs[WritebackCast]);
+ }
+ Expr *getWritebackCast() { return cast<Expr>(SubExprs[WritebackCast]); }
- const OpaqueValueExpr *getOpaqueValue() const { return OpaqueVal; }
- OpaqueValueExpr *getOpaqueValue() { return OpaqueVal; }
+ const OpaqueValueExpr *getCastedTemporary() const {
+ return cast<OpaqueValueExpr>(SubExprs[CastedTemporary]);
+ }
+ OpaqueValueExpr *getCastedTemporary() {
+ return cast<OpaqueValueExpr>(SubExprs[CastedTemporary]);
+ }
bool isInOut() const { return IsInOut; }
SourceLocation getBeginLoc() const LLVM_READONLY {
- return Base->getBeginLoc();
+ return SubExprs[BaseLValue]->getBeginLoc();
}
- SourceLocation getEndLoc() const LLVM_READONLY { return Base->getEndLoc(); }
+ SourceLocation getEndLoc() const LLVM_READONLY {
+ return SubExprs[BaseLValue]->getEndLoc();
+ }
static bool classof(const Stmt *T) {
return T->getStmtClass() == HLSLOutArgExprClass;
@@ -7118,7 +7144,7 @@ class HLSLOutArgExpr : public Expr {
// Iterators
child_range children() {
- return child_range((Stmt **)&Base, ((Stmt **)&Writeback) + 1);
+ return child_range(&SubExprs[BaseLValue], &SubExprs[NumSubExprs]);
}
};
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index be12e6e93cc453..1966d94d55f0e6 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -5321,9 +5321,9 @@ OMPIteratorExpr *OMPIteratorExpr::CreateEmpty(const ASTContext &Context,
}
HLSLOutArgExpr *HLSLOutArgExpr::Create(const ASTContext &C, QualType Ty,
- Expr *Base, bool IsInOut, Expr *WB,
- OpaqueValueExpr *OpV) {
- return new (C) HLSLOutArgExpr(Ty, Base, WB, OpV, IsInOut);
+ OpaqueValueExpr *Base, OpaqueValueExpr *OpV,
+ Expr *WB, bool IsInOut) {
+ return new (C) HLSLOutArgExpr(Ty, Base, OpV, WB, IsInOut);
}
HLSLOutArgExpr *HLSLOutArgExpr::CreateEmpty(const ASTContext &C) {
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index fad034725ec405..31a1a7b2b83870 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5739,12 +5739,9 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
Out << "E";
break;
}
- case Expr::HLSLOutArgExprClass: {
- const auto *OAE = cast<clang::HLSLOutArgExpr>(E);
- Out << (OAE->isInOut() ? "_inout_" : "_out_");
- mangleType(E->getType());
- 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 1995f55e49bf61..e1b5bec7a50d0a 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -2805,7 +2805,7 @@ void StmtPrinter::VisitAsTypeExpr(AsTypeExpr *Node) {
}
void StmtPrinter::VisitHLSLOutArgExpr(HLSLOutArgExpr *Node) {
- PrintExpr(Node->getBase());
+ PrintExpr(Node->getArgLValue());
}
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index eb2431c0673e41..9c650f5428648c 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4136,35 +4136,23 @@ static void emitWriteback(CodeGenFunction &CGF,
"shouldn't have writeback for provably null argument");
if (CGF.getLangOpts().HLSL) {
- if (!isa<OpaqueValueExpr>(writeback.CastExpr)) {
+ assert(!srcLV.isMatrixElt() &&
+ "Matrix element expressions not yet supported.");
+ assert((srcLV.isSimple() || srcLV.isBitField() || srcLV.isExtVectorElt()) &&
+ "Argument expressions should only be simple, bitfield, or "
+ "vector/matrix element expressions.");
+ // For vector element or bitfield lvalues, we need to emit the store through
+ // the lvalue, otherwise we won't get the correct component masking. This
+ // will also need to be used for matrices once we support them.
+ if (srcLV.isExtVectorElt() || srcLV.isBitField()) {
RValue TmpVal = CGF.EmitAnyExprToTemp(writeback.CastExpr);
- if (TmpVal.isScalar())
- CGF.EmitStoreThroughLValue(TmpVal, srcLV);
- else {
- llvm::Value *Val = CGF.Builder.CreateLoad(TmpVal.getAggregateAddress());
- CGF.CreateCoercedStore(
- Val, srcLV.getAddress(),
- llvm::TypeSize::getFixed(
- CGF.getContext()
- .getTypeSizeInChars(writeback.CastExpr->getType())
- .getQuantity()),
- false);
- }
- } else {
- llvm::Value *Val = CGF.Builder.CreateLoad(writeback.Temporary);
- if (srcLV.isSimple()) {
- CGF.CreateCoercedStore(
- Val, srcLV.getAddress(),
- llvm::TypeSize::getFixed(
- CGF.getContext()
- .getTypeSizeInChars(writeback.CastExpr->getType())
- .getQuantity()),
- false);
- } else {
- RValue TmpVal = RValue::get(Val);
- CGF.EmitStoreThroughLValue(TmpVal, srcLV);
- }
- }
+ assert(TmpVal.isScalar() &&
+ "a vector lvalue sould only be assigned from a scalar rvalue.");
+ CGF.EmitStoreThroughLValue(TmpVal, srcLV);
+ } else
+ CGF.EmitAnyExprToMem(writeback.CastExpr, srcLV.getAddress(),
+ writeback.CastExpr->getType().getQualifiers(), true);
+
if (writeback.LifetimeSz)
CGF.EmitLifetimeEnd(writeback.LifetimeSz,
writeback.Temporary.getBasePointer());
@@ -4717,27 +4705,8 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
// Add writeback for HLSLOutParamExpr.
if (const HLSLOutArgExpr *OE = dyn_cast<HLSLOutArgExpr>(E)) {
- LValue LV = EmitLValue(E);
- llvm::Type *ElTy = ConvertTypeForMem(LV.getType());
- llvm::Value *Addr, *BaseAddr;
- if (LV.isExtVectorElt()) {
- llvm::Constant *VecElts = LV.getExtVectorElts();
- BaseAddr = LV.getExtVectorAddress().getBasePointer();
- Addr = Builder.CreateGEP(
- ElTy, BaseAddr,
- {Builder.getInt32(0), VecElts->getAggregateElement((unsigned)0)});
- } else // LV.getAddress() will assert if this is not a simple LValue.
- Addr = BaseAddr = LV.getAddress().getBasePointer();
-
- llvm::TypeSize Sz =
- CGM.getDataLayout().getTypeAllocSize(ConvertTypeForMem(LV.getType()));
-
- llvm::Value *LifetimeSize = EmitLifetimeStart(Sz, BaseAddr);
-
- Address TmpAddr(Addr, ElTy, LV.getAlignment());
- args.addWriteback(EmitLValue(OE->getBase()->IgnoreImpCasts()), TmpAddr,
- nullptr, OE->getWriteback(), LifetimeSize);
- return args.add(RValue::get(TmpAddr, *this), type);
+ EmitHLSLOutArgExpr(OE, args, type);
+ return;
}
if (E->isGLValue()) {
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 765d6a848e83e1..792c52ef5f9091 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1673,7 +1673,7 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
case Expr::PackIndexingExprClass:
return EmitLValue(cast<PackIndexingExpr>(E)->getSelectedExpr());
case Expr::HLSLOutArgExprClass:
- return EmitHLSLOutArgExpr(cast<HLSLOutArgExpr>(E));
+ llvm_unreachable("cannot emit a HLSL out argument directly");
}
}
@@ -5426,24 +5426,52 @@ LValue CodeGenFunction::EmitOpaqueValueLValue(const OpaqueValueExpr *e) {
LValue CodeGenFunction::BindHLSLOutArgExpr(const HLSLOutArgExpr *E,
Address OutTemp) {
LValue Result = MakeAddrLValue(OutTemp, E->getType());
- OpaqueValueMappingData::bind(*this, E->getOpaqueValue(), Result);
+ OpaqueValueMappingData::bind(*this, E->getCastedTemporary(), Result);
return Result;
}
-LValue CodeGenFunction::EmitHLSLOutArgExpr(const HLSLOutArgExpr *E) {
- if (!E->isInOut())
- return BindHLSLOutArgExpr(E, CreateIRTemp(E->getType()));
+void CodeGenFunction::EmitHLSLOutArgExpr(const HLSLOutArgExpr *E,
+ CallArgList &Args, QualType Ty) {
- RValue InVal = EmitAnyExprToTemp(E->getBase());
- if (!InVal.isScalar())
- return BindHLSLOutArgExpr(E, InVal.getAggregateAddress());
+ // Emitting the casted temporary through an opaque value.
+ LValue BaseLV = EmitLValue(E->getArgLValue());
+ OpaqueValueMappingData::bind(*this, E->getOpaqueArgLValue(), BaseLV);
- Address OutTemp = CreateIRTemp(E->getType());
- llvm::Value *V = InVal.getScalarVal();
- if (V->getType()->getScalarType()->isIntegerTy(1))
- V = Builder.CreateZExt(V, ConvertTypeForMem(E->getType()), "frombool");
- (void)Builder.CreateStore(V, OutTemp);
- return BindHLSLOutArgExpr(E, OutTemp);
+ LValue TempLV;
+
+ if (E->isInOut()) {
+ RValue InitVal = EmitAnyExprToTemp(E->getCastedTemporary()->getSourceExpr());
+ assert(!InitVal.isComplex() &&
+ "HLSL does not support complex values in output expressions.");
+ if (InitVal.isScalar()) {
+ Address OutTemp = CreateIRTemp(E->getType());
+ llvm::Value *V = InitVal.getScalarVal();
+ if (V->getType()->getScalarType()->isIntegerTy(1))
+ V = Builder.CreateZExt(V, ConvertTypeForMem(E->getType()), "frombool");
+ (void)Builder.CreateStore(V, OutTemp);
+ TempLV = MakeAddrLValue(OutTemp, E->getType());
+ } else {
+ Address OutTemp = InitVal.getAggregateAddress();
+ TempLV = MakeAddrLValue(OutTemp, E->getType());
+ }
+ } else {
+ Address OutTemp = CreateIRTemp(E->getType());
+ TempLV = MakeAddrLValue(OutTemp, E->getType());
+ }
+
+ 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
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index b3ee6e0f95f3ac..8e9e5eb302ca7f 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4293,7 +4293,7 @@ class CodeGenFunction : public CodeGenTypeCache {
LValue EmitCastLValue(const CastExpr *E);
LValue EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E);
LValue EmitOpaqueValueLValue(const OpaqueValueExpr *e);
- LValue EmitHLSLOutArgExpr(const HLSLOutArgExpr *E);
+ void EmitHLSLOutArgExpr(const HLSLOutArgExpr *E, CallArgList &Args, QualType Ty);
LValue BindHLSLOutArgExpr(const HLSLOutArgExpr *E, Address OutTemp);
Address EmitExtVectorElementLValue(LValue V);
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 075f36f67ebf2c..95d9e237f8f613 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -11432,11 +11432,9 @@ static void AnalyzeImplicitConversions(
// arguments to `inout` parameters, so we only traverse down the base
// expression for `inout` cases.
if (OutArgE->isInOut())
- WorkList.push_back({OutArgE->getBase(), CC, IsListInit});
- // In all cases where there is a writeback conversion we should analyze its
- // conversions.
- if (OutArgE->getWriteback())
- WorkList.push_back({OutArgE->getWriteback(), CC, IsListInit});
+ WorkList.push_back(
+ {OutArgE->getCastedTemporary()->getSourceExpr(), CC, IsListInit});
+ WorkList.push_back({OutArgE->getWritebackCast(), CC, IsListInit});
return;
}
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index db82a5454fab8f..07d9151b9bcc32 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -15,6 +15,7 @@
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/LLVM.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"
@@ -1214,35 +1215,33 @@ ExprResult SemaHLSL::ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg) {
return ExprError();
}
- bool RequiresConversion =
- Ty.getUnqualifiedType() != Arg->getType().getUnqualifiedType();
-
- // If the unqualified types mismatch we may have some casting. Since this
- // results in a copy we can ignore qualifiers.
- if (RequiresConversion) {
- ExprResult Res =
- SemaRef.PerformImplicitConversion(Arg, Ty, Sema::AA_Passing);
- 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);
- Res = SemaRef.PerformImplicitConversion(OpV, Arg->getType(),
- Sema::AA_Passing);
- if (Res.isInvalid())
- return ExprError();
- Expr *Writeback = Res.get();
- auto *OutExpr =
- HLSLOutArgExpr::Create(Ctx, Ty, Base, IsInOut, Writeback, OpV);
-
- return ExprResult(OutExpr);
- }
+ auto *ArgOpV = new (Ctx) OpaqueValueExpr(Param->getBeginLoc(), Arg->getType(),
+ VK_LValue, OK_Ordinary, Arg);
+ 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, Arg);
- return ExprResult(HLSLOutArgExpr::Create(Ctx, Ty, Arg, IsInOut, OpV, OpV));
+ OpaqueValueExpr(Param->getBeginLoc(), Ty, VK_LValue, OK_Ordinary, Base);
+
+ // TODO: Revisit this to use ActOnBinOp so that writebacks are assignments.
+ Entity =
+ InitializedEntity::InitializeParameter(Ctx, Arg->getType(), false);
+ Res = SemaRef.PerformCopyInitialization(Entity, Param->getBeginLoc(), 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) {
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 02213c6eb848d9..7222bec668ad17 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -16683,31 +16683,9 @@ TreeTransform<Derived>::TransformCapturedStmt(CapturedStmt *S) {
template <typename Derived>
ExprResult TreeTransform<Derived>::TransformHLSLOutArgExpr(HLSLOutArgExpr *E) {
- ExprResult Res = getDerived().TransformExpr(E->getBase());
- if (Res.isInvalid())
- return ExprError();
-
- if (!getDerived().AlwaysRebuild() && Res.get() == E->getBase())
- return E;
-
- Expr *BaseExpr = Res.get();
- auto *OpV = new (getSema().Context) OpaqueValueExpr(
- E->getBeginLoc(), E->getType(), VK_LValue, OK_Ordinary, BaseExpr);
- if (isa<OpaqueValueExpr>(E->getWriteback()))
- return HLSLOutArgExpr::Create(getSema().Context, E->getType(), BaseExpr,
- E->isInOut(), OpV, OpV);
- // If the writeback isn't directly an opaque value, it means there is a
- // conversion sequence. Since none of these are actually dependent types, and
- // we already generated this sequence once successfully in the pattern decl it
- // should be impossible for this to fail generating a new conversion sequence.
- Res =
- getSema().PerformImplicitConversion(OpV, E->getType(), Sema::AA_Passing);
- if (Res.isInvalid())
- return ExprError();
- Expr *Writeback = Res.get();
-
- return HLSLOutArgExpr::Create(getSema().Context, E->getType(), BaseExpr,
- E->isInOut(), Writeback, OpV);
+ // We can transform the base expression and allow argument resolution to fill
+ // in the rest.
+ return getDerived().TransformExpr(E->getArgLValue());
}
} // end namespace clang
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index af2073dcee431c..f592c15f8cd581 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -2843,14 +2843,10 @@ void ASTStmtReader::VisitOpenACCLoopConstruct(OpenACCLoopConstruct *S) {
void ASTStmtReader::VisitHLSLOutArgExpr(HLSLOutArgExpr *S) {
VisitExpr(S);
- S->Base = Record.readSubExpr();
- S->Writeback = Record.readSubExpr();
+ S->SubExprs[HLSLOutArgExpr::BaseLValue] = Record.readSubExpr();
+ S->SubExprs[HLSLOutArgExpr::CastedTemporary] = Record.readSubExpr();
+ S->SubExprs[HLSLOutArgExpr::WritebackCast] = Record.readSubExpr();
S->IsInOut = Record.readBool();
- Expr::child_iterator Child = S->getWriteback()->child_begin();
- while (!isa<OpaqueValueExpr>(*Child))
- Child = Child->child_begin();
- assert(isa<OpaqueValueExpr>(*Child) && "Writeback must end in an OVE.");
- S->OpaqueVal = cast<OpaqueValueExpr>(*Child);
}
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 62ce0a32c9e5e7..7e17d4c20ca968 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -2915,8 +2915,9 @@ void ASTStmtWriter::VisitOpenACCLoopConstruct(OpenACCLoopConstruct *S) {
void ASTStmtWriter::VisitHLSLOutArgExpr(HLSLOutArgExpr *S) {
VisitExpr(S);
- Record.AddStmt(S->getBase());
- Record.AddStmt(S->getWriteback());
+ Record.AddStmt(S->getOpaqueArgLValue());
+ Record.AddStmt(S->getCastedTemporary());
+ Record.AddStmt(S->getWritebackCast());
Record.writeBool(S->isInOut());
Code = serialization::EXPR_HLSL_OUT_ARG;
}
diff --git a/clang/test/AST/HLSL/OutArgExpr.hlsl b/clang/test/AST/HLSL/OutArgExpr.hlsl
index 4e34de8f85ffbe..114c0c666460e7 100644
--- a/clang/test/AST/HLSL/OutArgExpr.hlsl
+++ b/clang/test/AST/HLSL/OutArgExpr.hlsl
@@ -25,23 +25,27 @@ void trunc_Param(inout int X) {}
// AST-NEXT: HLSLParamModifierAttr {{.*}} out
void zero(out int Z) { Z = 0; }
-// AST: FunctionDecl {{.*}} imported used fn 'void (uint)'
+// 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: ImplicitCastExpr [[BaseCast:0x[0-9a-fA-F]+]] <{{.*}}> 'int' <FloatingToIntegral>
-// AST-NEXT: ImplicitCastExpr {{.*}} 'float' <LValueToRValue>
+// 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
+// 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: ImplicitCastExpr {{.*}} 'float' <IntegralToFloating>
+
+// 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: ImplicitCastExpr {{.*}} 'float' <IntegralToFloating>
// AST-NEXT: ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
-// AST-NEXT: OpaqueValueExpr {{.*}} 'int' lvalue
-// AST-NEXT: ImplicitCastExpr [[BaseCast]] <{{.*}}> 'int' <FloatingToIntegral>
+// AST-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
// CHECK: void fn(uint GI) {
// CHECK: trunc_Param(Buf[GI]);
@@ -51,12 +55,24 @@ void fn(uint GI) {
#else
-// AST: FunctionDecl {{.*}} main 'void (uint)'
+// 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: ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
+// AST-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
+
+
[numthreads(8,1,1)]
void main(uint GI : SV_GroupIndex) {
int I;
diff --git a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
index 671c176d99441d..820c105a8bced4 100644
--- a/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
+++ b/clang/test/CodeGenHLSL/BasicFeatures/OutputArguments.hlsl
@@ -155,13 +155,7 @@ void init(out S s) {
// CHECK: [[S:%.*]] = alloca %struct.S
// CHECK: [[Tmp:%.*]] = alloca %struct.S
// CHECK: call void {{.*}}init{{.*}}(ptr noalias noundef nonnull align 4 dereferenceable(8) [[Tmp]])
-// CHECK: [[RetVal:%.*]] = load %struct.S, ptr [[Tmp]]
-// CHECK: [[XAddr:%.*]] = getelementptr inbounds nuw %struct.S, ptr [[S]], i32 0, i32 0
-// CHECK: [[XVal:%.*]] = extractvalue %struct.S [[RetVal]], 0
-// CHECK: store i32 [[XVal]], ptr [[XAddr]]
-// CHECK: [[YAddr:%.*]] = getelementptr inbounds nuw %struct.S, ptr [[S]], i32 0, i32 1
-// CHECK: [[YVal:%.*]] = extractvalue %struct.S [[RetVal]], 1
-// CHECK: store float [[YVal]], ptr [[YAddr]]
+// 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() {
@@ -170,12 +164,40 @@ export int case6() {
return s.X + s.Y;
}
-// Case 7: Non-scalars with a cast expression.
+// 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> {{.*}}case7
+// ALL-LABEL: define noundef <3 x float> {{.*}}case8
// CHECK: [[V:%.*]] = alloca <3 x float>
// CHECK: [[Tmp:%.*]] = alloca <3 x i32>
@@ -191,7 +213,116 @@ void trunc_vec(inout int3 V) {}
// OPT: [[FVal:%.*]] = sitofp <3 x i32> [[IVal]] to <3 x float>
// OPT: ret <3 x float> [[FVal]]
-export float3 case7(float3 V) {
+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/TemplateOutArg.hlsl b/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
index bec81f16b40fb2..2f6e0541ceddb8 100644
--- a/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
+++ b/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
@@ -31,11 +31,16 @@ void fn(out double F) {
// 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: ImplicitCastExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'int' <LValueToRValue>
-// CHECK-NEXT: DeclRefExpr {{.*}} 'int' lvalue ParmVar {{.*}} 'V' 'int'
-// CHECK-NEXT: ImplicitCastExpr {{.*}} <col:6> 'int' <LValueToRValue>
-// CHECK-NEXT: OpaqueValueExpr {{.*}} <col:6> 'int' lvalue
-// CHECK-NEXT: ImplicitCastExpr [[BaseExpr]] {{.*}} 'int' <LValueToRValue>
+
+// 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: 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.
@@ -45,13 +50,16 @@ void fn(out double F) {
// 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: ImplicitCastExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'double'
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' <LValueToRValue>
-// CHECK-NEXT: DeclRefExpr {{.*}}'float' lvalue ParmVar {{.*}} 'V' 'float'
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' <FloatingCast>
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'double' <LValueToRValue>
-// CHECK-NEXT: OpaqueValueExpr {{.*}} 'double' lvalue
-// CHECK-NEXT: ImplicitCastExpr [[BaseExpr]] {{.*}} 'double' <FloatingCast>
+// 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: ImplicitCastExpr {{.*}} 'float' <FloatingCast>
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'double' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'double' lvalue
+
// Verify that the double instantiation is just an out expression.
@@ -60,11 +68,14 @@ void fn(out double F) {
// 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: ImplicitCastExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'double' <LValueToRValue>
-// CHECK-NEXT: DeclRefExpr {{.*}}'double' lvalue ParmVar {{.*}} 'V' 'double'
+// 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 {{.*}} 'double' lvalue
-// CHECK-NEXT: ImplicitCastExpr [[BaseExpr]] {{.*}} 'double' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[LVOpV]] {{.*}} 'double' lvalue
+
+// CHECK: ImplicitCastExpr {{.*}} 'double' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'double' lvalue
template <typename T>
T wrapper(T V) {
@@ -112,27 +123,42 @@ void fizz(inout T V) {
// 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: DeclRefExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
-// CHECK-NEXT: OpaqueValueExpr {{.*}} 'int' lvalue
-// CHECK-NEXT: DeclRefExpr [[BaseExpr]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// 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: 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: DeclRefExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
-// CHECK-NEXT: OpaqueValueExpr {{.*}} 'int' lvalue
-// CHECK-NEXT: DeclRefExpr [[BaseExpr]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// 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: 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: DeclRefExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
-// CHECK-NEXT: OpaqueValueExpr {{.*}} 'int' lvalue
-// CHECK-NEXT: DeclRefExpr [[BaseExpr]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// 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: ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
// CHECK-LABEL: FunctionDecl {{.*}} used buzz 'double (int, double)' implicit_instantiation
@@ -140,9 +166,13 @@ void fizz(inout T V) {
// 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: DeclRefExpr [[BaseExpr:0x[0-9a-fA-F]+]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
-// CHECK-NEXT: OpaqueValueExpr {{.*}} 'int' lvalue
-// CHECK-NEXT: DeclRefExpr [[BaseExpr]] {{.*}} 'int' lvalue ParmVar {{.*}} 'X' 'int'
+// 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: ImplicitCastExpr {{.*}} 'int' <LValueToRValue>
+// CHECK-NEXT: OpaqueValueExpr [[TmpOpV]] {{.*}} 'int' lvalue
template <typename T>
T buzz(int X, T Y) {
>From 5f7e3514d56e87abb0970a3c53310f32fb4e35ad Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Mon, 26 Aug 2024 21:59:53 -0500
Subject: [PATCH 12/19] Fixed ActOnBinOp
---
clang/lib/CodeGen/CGCall.cpp | 17 +----------------
clang/lib/Sema/SemaHLSL.cpp | 10 ++++++----
clang/test/AST/HLSL/OutArgExpr.hlsl | 5 ++++-
.../test/SemaHLSL/Language/TemplateOutArg.hlsl | 15 +++++++++++++++
4 files changed, 26 insertions(+), 21 deletions(-)
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 9c650f5428648c..b4f011faf20b05 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4136,22 +4136,7 @@ static void emitWriteback(CodeGenFunction &CGF,
"shouldn't have writeback for provably null argument");
if (CGF.getLangOpts().HLSL) {
- assert(!srcLV.isMatrixElt() &&
- "Matrix element expressions not yet supported.");
- assert((srcLV.isSimple() || srcLV.isBitField() || srcLV.isExtVectorElt()) &&
- "Argument expressions should only be simple, bitfield, or "
- "vector/matrix element expressions.");
- // For vector element or bitfield lvalues, we need to emit the store through
- // the lvalue, otherwise we won't get the correct component masking. This
- // will also need to be used for matrices once we support them.
- if (srcLV.isExtVectorElt() || srcLV.isBitField()) {
- RValue TmpVal = CGF.EmitAnyExprToTemp(writeback.CastExpr);
- assert(TmpVal.isScalar() &&
- "a vector lvalue sould only be assigned from a scalar rvalue.");
- CGF.EmitStoreThroughLValue(TmpVal, srcLV);
- } else
- CGF.EmitAnyExprToMem(writeback.CastExpr, srcLV.getAddress(),
- writeback.CastExpr->getType().getQualifiers(), true);
+ CGF.EmitIgnoredExpr(writeback.CastExpr);
if (writeback.LifetimeSz)
CGF.EmitLifetimeEnd(writeback.LifetimeSz,
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 07d9151b9bcc32..0f654066834065 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -1218,6 +1218,8 @@ ExprResult SemaHLSL::ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg) {
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 =
@@ -1230,10 +1232,10 @@ ExprResult SemaHLSL::ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg) {
auto *OpV = new (Ctx)
OpaqueValueExpr(Param->getBeginLoc(), Ty, VK_LValue, OK_Ordinary, Base);
- // TODO: Revisit this to use ActOnBinOp so that writebacks are assignments.
- Entity =
- InitializedEntity::InitializeParameter(Ctx, Arg->getType(), false);
- Res = SemaRef.PerformCopyInitialization(Entity, Param->getBeginLoc(), OpV);
+ // 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();
diff --git a/clang/test/AST/HLSL/OutArgExpr.hlsl b/clang/test/AST/HLSL/OutArgExpr.hlsl
index 114c0c666460e7..b07c2efadbf4aa 100644
--- a/clang/test/AST/HLSL/OutArgExpr.hlsl
+++ b/clang/test/AST/HLSL/OutArgExpr.hlsl
@@ -43,6 +43,8 @@ void zero(out int Z) { Z = 0; }
// 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
@@ -68,7 +70,8 @@ void fn(uint GI) {
// 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
diff --git a/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl b/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
index 2f6e0541ceddb8..2d6252cbb4d2b1 100644
--- a/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
+++ b/clang/test/SemaHLSL/Language/TemplateOutArg.hlsl
@@ -38,6 +38,8 @@ void fn(out double F) {
// 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
@@ -56,6 +58,9 @@ void fn(out double F) {
// 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
@@ -74,6 +79,8 @@ void fn(out double F) {
// 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
@@ -128,6 +135,8 @@ void fizz(inout T V) {
// 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
@@ -143,6 +152,8 @@ void fizz(inout T V) {
// 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
@@ -157,6 +168,8 @@ void fizz(inout T V) {
// 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
@@ -171,6 +184,8 @@ void fizz(inout T V) {
// 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
>From 7ab139520a7fc3a6a67fdaf5c60aa163b85d7c7a Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Tue, 27 Aug 2024 18:54:49 -0500
Subject: [PATCH 13/19] Updates based on latest feedback
Also got a few things I missed in my last round of updates.
---
clang/include/clang/AST/Expr.h | 9 +++++++
clang/lib/CodeGen/CGExpr.cpp | 38 ++++++++++++-----------------
clang/lib/CodeGen/CodeGenFunction.h | 4 +--
3 files changed, 27 insertions(+), 24 deletions(-)
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 4914b0c2741eef..20d384cd2b1986 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -7082,6 +7082,14 @@ class ArraySectionExpr : public Expr {
/// 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 a an implicit conversion
+/// sequence from the source lvalue to the argument type.
+/// - A BinaryOperatorExpr that assigns the first sub-expression with the
+/// value from an implict conversion sequence from the second expression to
+/// the argument expression's type.
class HLSLOutArgExpr : public Expr {
friend class ASTStmtReader;
@@ -7138,6 +7146,7 @@ class HLSLOutArgExpr : public Expr {
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 {
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 67124ebb3fb079..a4fda2630cfde7 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5434,13 +5434,6 @@ LValue CodeGenFunction::EmitOpaqueValueLValue(const OpaqueValueExpr *e) {
return getOrCreateOpaqueLValueMapping(e);
}
-LValue CodeGenFunction::BindHLSLOutArgExpr(const HLSLOutArgExpr *E,
- Address OutTemp) {
- LValue Result = MakeAddrLValue(OutTemp, E->getType());
- OpaqueValueMappingData::bind(*this, E->getCastedTemporary(), Result);
- return Result;
-}
-
void CodeGenFunction::EmitHLSLOutArgExpr(const HLSLOutArgExpr *E,
CallArgList &Args, QualType Ty) {
@@ -5448,26 +5441,27 @@ void CodeGenFunction::EmitHLSLOutArgExpr(const HLSLOutArgExpr *E,
LValue BaseLV = EmitLValue(E->getArgLValue());
OpaqueValueMappingData::bind(*this, E->getOpaqueArgLValue(), BaseLV);
- LValue TempLV;
+ QualType ExprTy = E->getType();
+ Address OutTemp = CreateIRTemp(ExprTy);
+ LValue TempLV = MakeAddrLValue(OutTemp, ExprTy);
if (E->isInOut()) {
- RValue InitVal = EmitAnyExprToTemp(E->getCastedTemporary()->getSourceExpr());
- assert(!InitVal.isComplex() &&
+ Expr *TempExpr = E->getCastedTemporary()->getSourceExpr();
+ auto ExprEvalKind = getEvaluationKind(ExprTy);
+ assert(ExprEvalKind != TEK_Complex &&
"HLSL does not support complex values in output expressions.");
- if (InitVal.isScalar()) {
- Address OutTemp = CreateIRTemp(E->getType());
- llvm::Value *V = InitVal.getScalarVal();
- if (V->getType()->getScalarType()->isIntegerTy(1))
- V = Builder.CreateZExt(V, ConvertTypeForMem(E->getType()), "frombool");
- (void)Builder.CreateStore(V, OutTemp);
- TempLV = MakeAddrLValue(OutTemp, E->getType());
+ if (ExprEvalKind == TEK_Aggregate) {
+ EmitAggExpr(TempExpr,
+ AggValueSlot::forLValue(TempLV, AggValueSlot::IsDestructed,
+ AggValueSlot::DoesNotNeedGCBarriers,
+ AggValueSlot::IsNotAliased,
+ AggValueSlot::MayOverlap));
} else {
- Address OutTemp = InitVal.getAggregateAddress();
- TempLV = MakeAddrLValue(OutTemp, E->getType());
+ if (TempLV.isSimple())
+ EmitScalarInit(TempExpr, /*D=*/nullptr, TempLV, /*Captured=*/false);
+ else
+ EmitStoreThroughLValue(RValue::get(EmitScalarExpr(TempExpr)), TempLV);
}
- } else {
- Address OutTemp = CreateIRTemp(E->getType());
- TempLV = MakeAddrLValue(OutTemp, E->getType());
}
OpaqueValueMappingData::bind(*this, E->getCastedTemporary(), TempLV);
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 5f66d863b2beb8..a7b5683e7b1949 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4294,8 +4294,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);
- LValue BindHLSLOutArgExpr(const HLSLOutArgExpr *E, Address OutTemp);
+ void EmitHLSLOutArgExpr(const HLSLOutArgExpr *E, CallArgList &Args,
+ QualType Ty);
Address EmitExtVectorElementLValue(LValue V);
>From 1bb6cae84005ded9c8176139c8c8d10d2d802803 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Tue, 27 Aug 2024 19:10:57 -0500
Subject: [PATCH 14/19] clang-format
---
clang/include/clang/AST/Expr.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 20d384cd2b1986..f3945c9057f574 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -7083,7 +7083,7 @@ class ArraySectionExpr : public Expr {
/// 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:
+/// 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 a an implicit conversion
/// sequence from the source lvalue to the argument type.
>From 7b21187e6a9bf1da3be24d76e3760b8454ce11e1 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Tue, 27 Aug 2024 19:23:56 -0500
Subject: [PATCH 15/19] clang-format
---
clang/include/clang/Sema/SemaHLSL.h | 4 ++--
clang/lib/AST/Expr.cpp | 5 +++--
clang/lib/Sema/SemaHLSL.cpp | 4 ++--
clang/lib/Sema/SemaTemplateInstantiateDecl.cpp | 2 +-
4 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 04a8aa0e4188bb..e89143ccdaa336 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -61,10 +61,10 @@ class SemaHLSL : public SemaBase {
void handleParamModifierAttr(Decl *D, const ParsedAttr &AL);
bool CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall);
-
+
// HLSL Type trait implementations
bool IsScalarizedLayoutCompatible(QualType T1, QualType T2) const;
-
+
bool CheckCompatibleParameterABI(FunctionDecl *New, FunctionDecl *Old);
ExprResult ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg);
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 5b42a254adcb73..96c6276f3f34c1 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -5391,8 +5391,9 @@ OMPIteratorExpr *OMPIteratorExpr::CreateEmpty(const ASTContext &Context,
}
HLSLOutArgExpr *HLSLOutArgExpr::Create(const ASTContext &C, QualType Ty,
- OpaqueValueExpr *Base, OpaqueValueExpr *OpV,
- Expr *WB, bool IsInOut) {
+ OpaqueValueExpr *Base,
+ OpaqueValueExpr *OpV, Expr *WB,
+ bool IsInOut) {
return new (C) HLSLOutArgExpr(Ty, Base, OpV, WB, IsInOut);
}
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index f219bdc76f1d02..758908c5aa5be7 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -1673,9 +1673,9 @@ ExprResult SemaHLSL::ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg) {
// Parameters are initialized via copy initialization. This allows for
// overload resolution of argument constructors.
InitializedEntity Entity =
- InitializedEntity::InitializeParameter(Ctx, Ty, false);
+ InitializedEntity::InitializeParameter(Ctx, Ty, false);
ExprResult Res =
- SemaRef.PerformCopyInitialization(Entity, Param->getBeginLoc(), ArgOpV);
+ SemaRef.PerformCopyInitialization(Entity, Param->getBeginLoc(), ArgOpV);
if (Res.isInvalid())
return ExprError();
Expr *Base = Res.get();
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index caf04fa45ebce1..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"
>From 821c56580859d5f30c68440941b0f4bd1f4bf9e4 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Wed, 28 Aug 2024 20:01:20 -0500
Subject: [PATCH 16/19] Add an assert in SemaHLSL::getInoutParameterType
---
clang/lib/Sema/SemaHLSL.cpp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 758908c5aa5be7..5d2ae1f696fd48 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -1699,6 +1699,11 @@ ExprResult SemaHLSL::ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg) {
}
QualType SemaHLSL::getInoutParameterType(QualType Ty) {
+ // If HLSL gains support for pointers and/or references, all the cites that
+ // use this will need to be updated with semantic checking to produce errors
+ // for pointers/references.
+ assert(!(Ty.isReferenceType() || Ty.isPointerType()) &&
+ "Pointer and reference types cannot be inout or out parameters");
Ty = SemaRef.getASTContext().getLValueReferenceType(Ty);
Ty.addRestrict();
return Ty;
>From 4c2be5c53b255164a95f56b5ffb214538ca773be Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Thu, 29 Aug 2024 13:40:00 -0500
Subject: [PATCH 17/19] Process the initial lvalue in SemaChecking
This processes just the initial lvalue during sema checking since that
expression is always evaluated.
The updated test case has an implicit conversion in the lvalue
expression, which is evaluated but not used to initialize the argument.
---
clang/lib/Sema/SemaChecking.cpp | 1 +
clang/test/SemaHLSL/parameter_modifiers.hlsl | 10 +++++++++-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 95d9e237f8f613..e60f2dd2c2c35d 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -11428,6 +11428,7 @@ static void AnalyzeImplicitConversions(
}
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.
diff --git a/clang/test/SemaHLSL/parameter_modifiers.hlsl b/clang/test/SemaHLSL/parameter_modifiers.hlsl
index 2347c17ce0afdd..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'}}
@@ -90,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}}
+}
>From b883f6667df88a0c6e0f737a4353f76a91af2714 Mon Sep 17 00:00:00 2001
From: Chris Bieneman <chris.bieneman at me.com>
Date: Thu, 29 Aug 2024 13:41:53 -0500
Subject: [PATCH 18/19] Avoid double mutating parameter modifiers
This updates the code in SemaHLSL::getInoutParameterType to assert on
reference types, and fixes the cooresponding code in SemaType so that
the underlying type isn't mutated more than once.
The double-mutation only occured in error cases where conflicting
attributes were applied (error is checked elsewhere).
---
clang/lib/Sema/SemaHLSL.cpp | 8 ++++----
clang/lib/Sema/SemaType.cpp | 25 +++++++++++++++++++------
2 files changed, 23 insertions(+), 10 deletions(-)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 5d2ae1f696fd48..a85662b60221e5 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -1699,10 +1699,10 @@ ExprResult SemaHLSL::ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg) {
}
QualType SemaHLSL::getInoutParameterType(QualType Ty) {
- // If HLSL gains support for pointers and/or references, all the cites that
- // use this will need to be updated with semantic checking to produce errors
- // for pointers/references.
- assert(!(Ty.isReferenceType() || Ty.isPointerType()) &&
+ // 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();
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index e48bd09ea45f79..3b895b6bb24ccd 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -222,10 +222,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;
@@ -352,6 +357,10 @@ namespace {
bool didParseNoDeref() const { return parsedNoDeref; }
+ void setParsedHLSLParamMod(bool Parsed) { ParsedHLSLParamMod = Parsed; }
+
+ bool didParseHLSLParamMod() const { return ParsedHLSLParamMod; }
+
~TypeProcessingState() {
if (savedAttrs.empty())
return;
@@ -8506,15 +8515,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)
+ Attr.getSemanticSpelling() == HLSLParamModifierAttr::Keyword_out) {
CurType = S.HLSL().getInoutParameterType(CurType);
+ State.setParsedHLSLParamMod(true);
+ }
}
static void processTypeAttrs(TypeProcessingState &state, QualType &type,
@@ -8694,7 +8707,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;
}
>From 55804b1b08e09d4004403c407b112069176a9762 Mon Sep 17 00:00:00 2001
From: Chris B <cbieneman at microsoft.com>
Date: Fri, 30 Aug 2024 18:39:49 -0500
Subject: [PATCH 19/19] Update clang/include/clang/AST/Expr.h
Co-authored-by: John McCall <rjmccall at gmail.com>
---
clang/include/clang/AST/Expr.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index f3945c9057f574..01dec0d4d85f20 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -7085,7 +7085,7 @@ class ArraySectionExpr : public Expr {
///
/// 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 a an implicit conversion
+/// - An OpaqueValueExpr with a source that is an implicit conversion
/// sequence from the source lvalue to the argument type.
/// - A BinaryOperatorExpr that assigns the first sub-expression with the
/// value from an implict conversion sequence from the second expression to
More information about the cfe-commits
mailing list