[HLSL] Implement output parameter (#101083)

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

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

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

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

Fixes #87526

---------

Co-authored-by: Damyan Pepper <damyanp@microsoft.com>
Co-authored-by: John McCall <rjmccall@gmail.com>
This commit is contained in:
Chris B 2024-08-31 10:59:08 -05:00 committed by GitHub
parent e41579a31f
commit 89fb8490a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
45 changed files with 1220 additions and 96 deletions

View File

@ -1381,6 +1381,14 @@ public:
/// 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;

View File

@ -224,20 +224,7 @@ protected:
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

View File

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

View File

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

View File

@ -407,6 +407,7 @@ public:
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);

View File

@ -4639,14 +4639,13 @@ def HLSLGroupSharedAddressSpace : TypeAttr {
let Documentation = [HLSLGroupSharedAddressSpaceDocs];
}
def HLSLParamModifier : TypeAttr {
def HLSLParamModifier : ParameterABIAttr {
let Spellings = [CustomKeyword<"in">, CustomKeyword<"inout">, CustomKeyword<"out">];
let Accessors = [Accessor<"isIn", [CustomKeyword<"in">]>,
Accessor<"isInOut", [CustomKeyword<"inout">]>,
Accessor<"isOut", [CustomKeyword<"out">]>,
Accessor<"isAnyOut", [CustomKeyword<"out">, CustomKeyword<"inout">]>,
Accessor<"isAnyIn", [CustomKeyword<"in">, CustomKeyword<"inout">]>];
let Subjects = SubjectList<[ParmVar]>;
let Documentation = [HLSLParamQualifierDocs];
let Args = [DefaultBoolArgument<"MergedSpelling", /*default*/0, /*fake*/1>];
}

View File

@ -12380,6 +12380,8 @@ def warn_hlsl_availability : Warning<
def warn_hlsl_availability_unavailable :
Warning<err_unavailable.Summary>,
InGroup<HLSLAvailability>, DefaultError;
def error_hlsl_inout_scalar_extension : Error<"illegal scalar extension cast on argument %0 to %select{|in}1out paramemter">;
def error_hlsl_inout_lvalue : Error<"cannot bind non-lvalue argument %0 to %select{|in}1out paramemter">;
def err_hlsl_export_not_on_function : Error<
"export declaration can only be used on functions">;

View File

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

View File

@ -307,3 +307,6 @@ def OpenACCAssociatedStmtConstruct
: StmtNode<OpenACCConstructStmt, /*abstract=*/1>;
def OpenACCComputeConstruct : StmtNode<OpenACCAssociatedStmtConstruct>;
def OpenACCLoopConstruct : StmtNode<OpenACCAssociatedStmtConstruct>;
// HLSL Constructs.
def HLSLOutArgExpr : StmtNode<Expr>;

View File

@ -71,6 +71,12 @@ public:
// HLSL Type trait implementations
bool IsScalarizedLayoutCompatible(QualType T1, QualType T2) const;
bool CheckCompatibleParameterABI(FunctionDecl *New, FunctionDecl *Old);
ExprResult ActOnOutParamExpr(ParmVarDecl *Param, Expr *Arg);
QualType getInoutParameterType(QualType Ty);
};
} // namespace clang

View File

@ -1995,6 +1995,9 @@ enum StmtCode {
// OpenACC Constructs
STMT_OPENACC_COMPUTE_CONSTRUCT,
STMT_OPENACC_LOOP_CONSTRUCT,
// HLSL Constructs
EXPR_HLSL_OUT_ARG,
};
/// The kinds of designators that can occur in a

View File

@ -3612,6 +3612,21 @@ bool ASTContext::hasSameFunctionTypeIgnoringPtrSizes(QualType T, QualType U) {
getFunctionTypeWithoutPtrSizes(U));
}
QualType ASTContext::getFunctionTypeWithoutParamABIs(QualType T) const {
if (const auto *Proto = T->getAs<FunctionProtoType>()) {
FunctionProtoType::ExtProtoInfo EPI = Proto->getExtProtoInfo();
EPI.ExtParameterInfos = nullptr;
return getFunctionType(Proto->getReturnType(), Proto->param_types(), EPI);
}
return T;
}
bool ASTContext::hasSameFunctionTypeIgnoringParamABI(QualType T,
QualType U) const {
return hasSameType(T, U) || hasSameType(getFunctionTypeWithoutParamABIs(T),
getFunctionTypeWithoutParamABIs(U));
}
void ASTContext::adjustExceptionSpec(
FunctionDecl *FD, const FunctionProtoType::ExceptionSpecInfo &ESI,
bool AsWritten) {

View File

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

View File

@ -142,6 +142,7 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) {
case Expr::ArraySectionExprClass:
case Expr::OMPArrayShapingExprClass:
case Expr::OMPIteratorExprClass:
case Expr::HLSLOutArgExprClass:
return Cl::CL_LValue;
// C++ [expr.prim.general]p1: A string literal is an lvalue.

View File

@ -16640,6 +16640,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
case Expr::CoyieldExprClass:
case Expr::SYCLUniqueStableNameExprClass:
case Expr::CXXParenListInitExprClass:
case Expr::HLSLOutArgExprClass:
return ICEDiag(IK_NotICE, E->getBeginLoc());
case Expr::InitListExprClass: {

View File

@ -3518,6 +3518,12 @@ CXXNameMangler::mangleExtParameterInfo(FunctionProtoType::ExtParameterInfo PI) {
case ParameterABI::Ordinary:
break;
// HLSL parameter mangling.
case ParameterABI::HLSLOut:
case ParameterABI::HLSLInOut:
mangleVendorQualifier(getParameterABISpelling(PI.getABI()));
break;
// All of these start with "swift", so they come before "ns_consumed".
case ParameterABI::SwiftContext:
case ParameterABI::SwiftAsyncContext:
@ -5730,6 +5736,9 @@ recurse:
Out << "E";
break;
}
case Expr::HLSLOutArgExprClass:
llvm_unreachable(
"cannot mangle hlsl temporary value; mangling wrong thing?");
}
if (AsTemplateArg && !IsPrimaryExpr)

View File

@ -2804,6 +2804,10 @@ void StmtPrinter::VisitAsTypeExpr(AsTypeExpr *Node) {
OS << ")";
}
void StmtPrinter::VisitHLSLOutArgExpr(HLSLOutArgExpr *Node) {
PrintExpr(Node->getArgLValue());
}
//===----------------------------------------------------------------------===//
// Stmt method implementations
//===----------------------------------------------------------------------===//

View File

@ -2647,6 +2647,10 @@ void StmtProfiler::VisitOpenACCLoopConstruct(const OpenACCLoopConstruct *S) {
P.VisitOpenACCClauseList(S->clauses());
}
void StmtProfiler::VisitHLSLOutArgExpr(const HLSLOutArgExpr *S) {
VisitStmt(S);
}
void Stmt::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context,
bool Canonical, bool ProfileLambdaExpr) const {
StmtProfilerWithPointers Profiler(ID, Context, Canonical, ProfileLambdaExpr);

View File

@ -2879,6 +2879,10 @@ void TextNodeDumper::VisitHLSLBufferDecl(const HLSLBufferDecl *D) {
dumpName(D);
}
void TextNodeDumper::VisitHLSLOutArgExpr(const HLSLOutArgExpr *E) {
OS << (E->isInOut() ? " inout" : " out");
}
void TextNodeDumper::VisitOpenACCConstructStmt(const OpenACCConstructStmt *S) {
OS << " " << S->getDirectiveKind();
}

View File

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

View File

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

View File

@ -285,6 +285,13 @@ public:
/// A value to "use" after the writeback, or null.
llvm::Value *ToUse;
/// An Expression (optional) that performs the writeback with any required
/// casting.
const Expr *WritebackExpr;
// Size for optional lifetime end on the temporary.
llvm::Value *LifetimeSz;
};
struct CallArgCleanup {
@ -316,8 +323,10 @@ public:
StackBase = other.StackBase;
}
void addWriteback(LValue srcLV, Address temporary, llvm::Value *toUse) {
Writeback writeback = {srcLV, temporary, toUse};
void addWriteback(LValue srcLV, Address temporary, llvm::Value *toUse,
const Expr *writebackExpr = nullptr,
llvm::Value *lifetimeSz = nullptr) {
Writeback writeback = {srcLV, temporary, toUse, writebackExpr, lifetimeSz};
Writebacks.push_back(writeback);
}
@ -350,6 +359,11 @@ public:
/// 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;

View File

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

View File

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

View File

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

View File

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

View File

@ -3249,26 +3249,6 @@ static void mergeParamDeclAttributes(ParmVarDecl *newDecl,
diag::note_carries_dependency_missing_first_decl) << 1/*Param*/;
}
// HLSL parameter declarations for inout and out must match between
// declarations. In HLSL inout and out are ambiguous at the call site, but
// have 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;
@ -4054,6 +4034,22 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
}
}
// HLSL check parameters for matching ABI specifications.
if (getLangOpts().HLSL) {
if (HLSL().CheckCompatibleParameterABI(New, Old))
return true;
// If no errors are generated when checking parameter ABIs we can check if
// the two declarations have the same type ignoring the ABIs and if so,
// the declarations can be merged. This case for merging is only valid in
// HLSL because there are no valid cases of merging mismatched parameter
// ABIs except the HLSL implicit in and explicit in.
if (Context.hasSameFunctionTypeIgnoringParamABI(OldQTypeForComparison,
NewQType))
return MergeCompatibleFunctionDecls(New, Old, S, MergeTypeWithOld);
// Fall through for conflicting redeclarations and redefinitions.
}
// If the function types are compatible, merge the declarations. Ignore the
// exception specifier because it was already checked above in
// CheckEquivalentExceptionSpec, and we don't want follow-on diagnostics

View File

@ -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;

View File

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

View File

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

View File

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

View File

@ -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?");

View File

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

View File

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

View File

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

View File

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

View File

@ -2910,6 +2910,19 @@ void ASTStmtWriter::VisitOpenACCLoopConstruct(OpenACCLoopConstruct *S) {
Code = serialization::STMT_OPENACC_LOOP_CONSTRUCT;
}
//===----------------------------------------------------------------------===//
// HLSL Constructs/Directives.
//===----------------------------------------------------------------------===//
void ASTStmtWriter::VisitHLSLOutArgExpr(HLSLOutArgExpr *S) {
VisitExpr(S);
Record.AddStmt(S->getOpaqueArgLValue());
Record.AddStmt(S->getCastedTemporary());
Record.AddStmt(S->getWritebackCast());
Record.writeBool(S->isInOut());
Code = serialization::EXPR_HLSL_OUT_ARG;
}
//===----------------------------------------------------------------------===//
// ASTWriter Implementation
//===----------------------------------------------------------------------===//

View File

@ -1830,7 +1830,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::OpenACCComputeConstructClass:
case Stmt::OpenACCLoopConstructClass:
case Stmt::OMPUnrollDirectiveClass:
case Stmt::OMPMetaDirectiveClass: {
case Stmt::OMPMetaDirectiveClass:
case Stmt::HLSLOutArgExprClass: {
const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState());
Engine.addAbortedBlock(node, currBldrCtx->getBlock());
break;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;