From 6de30f1fdf75a7a8ff11f0097e1d4de5db1e34d9 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Fri, 30 Aug 2024 18:40:43 -0500 Subject: [PATCH] [HLSL] Vector Usual Arithmetic Conversions HLSL has a different set of usual arithmetic conversions for vector types to resolve a common type for binary operator expressions. This PR implements the current spec proposal from: https://github.com/microsoft/hlsl-specs/pull/311 There is one case that may need additional handling for implicitly truncating `vector` to `T` early to allow other transformations. --- .../clang/Basic/DiagnosticSemaKinds.td | 2 + clang/include/clang/Driver/Options.td | 2 +- clang/include/clang/Sema/SemaHLSL.h | 4 + clang/lib/AST/ExprConstant.cpp | 2 +- clang/lib/Sema/SemaExpr.cpp | 4 + clang/lib/Sema/SemaHLSL.cpp | 169 +++++++++++ .../Language/UsualArithmeticConversions.hlsl | 284 ++++++++++++++++++ 7 files changed, 465 insertions(+), 2 deletions(-) create mode 100644 clang/test/SemaHLSL/Language/UsualArithmeticConversions.hlsl diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index dcb49d8a67604a3..c36079a2dbe3297 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -12370,6 +12370,8 @@ def err_hlsl_operator_unsupported : Error< def err_hlsl_param_qualifier_mismatch : Error<"conflicting parameter qualifier %0 on parameter %1">; +def err_hlsl_vector_compound_assignment_truncation : + Error<"left hand operand of type %0 to compound assignment cannot be truncated when used with right hand operand of type %1">; def warn_hlsl_impcast_vector_truncation : Warning< "implicit conversion truncates vector: %0 to %1">, InGroup; diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 1142416e227fc8b..e8fc591ce8110b4 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -2956,7 +2956,7 @@ def flax_vector_conversions_EQ : Joined<["-"], "flax-vector-conversions=">, Grou "LangOptions::LaxVectorConversionKind::Integer", "LangOptions::LaxVectorConversionKind::All"]>, MarshallingInfoEnum, - open_cl.KeyPath # + !strconcat("(", open_cl.KeyPath, " || ", hlsl.KeyPath, ")") # " ? LangOptions::LaxVectorConversionKind::None" # " : LangOptions::LaxVectorConversionKind::All">; def flax_vector_conversions : Flag<["-"], "flax-vector-conversions">, Group, diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h index 285e4e5f3c765a7..814fb54c48f4e03 100644 --- a/clang/include/clang/Sema/SemaHLSL.h +++ b/clang/include/clang/Sema/SemaHLSL.h @@ -54,6 +54,10 @@ class SemaHLSL : public SemaBase { std::initializer_list AllowedStages); void DiagnoseAvailabilityViolations(TranslationUnitDecl *TU); + QualType handleVectorBinOpConversion(ExprResult &LHS, ExprResult &RHS, + QualType LHSType, QualType RHSType, + bool IsCompAssign); + void handleNumThreadsAttr(Decl *D, const ParsedAttr &AL); void handleWaveSizeAttr(Decl *D, const ParsedAttr &AL); void handleSV_DispatchThreadIDAttr(Decl *D, const ParsedAttr &AL); diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 3dc13c14c00343f..55b400d81dbc7ab 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -14466,7 +14466,6 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) { case CK_FixedPointCast: case CK_IntegralToFixedPoint: case CK_MatrixCast: - case CK_HLSLVectorTruncation: llvm_unreachable("invalid cast kind for integral value"); case CK_BitCast: @@ -14485,6 +14484,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) { case CK_NoOp: case CK_LValueToRValueBitCast: case CK_HLSLArrayRValue: + case CK_HLSLVectorTruncation: return ExprEvaluatorBaseTy::VisitCastExpr(E); case CK_MemberPointerToBoolean: diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index e291ef6c97eefcb..86ac3d700c838d9 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -10112,6 +10112,10 @@ QualType Sema::CheckVectorOperands(ExprResult &LHS, ExprResult &RHS, const VectorType *RHSVecType = RHSType->getAs(); assert(LHSVecType || RHSVecType); + if (getLangOpts().HLSL) + return HLSL().handleVectorBinOpConversion(LHS, RHS, LHSType, RHSType, + IsCompAssign); + // AltiVec-style "vector bool op vector bool" combinations are allowed // for some operators but not others. if (!AllowBothBool && LHSVecType && diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 65aeda4b7b613ee..c82f8cb49a9703d 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -351,6 +351,175 @@ void SemaHLSL::DiagnoseAttrStageMismatch( << (AllowedStages.size() != 1) << join(StageStrings, ", "); } +template +static void castVector(Sema &S, ExprResult &E, QualType &Ty, unsigned Sz) { + if (const auto *VTy = Ty->getAs()) + Ty = VTy->getElementType(); + Ty = S.getASTContext().getExtVectorType(Ty, Sz); + E = S.ImpCastExprToType(E.get(), Ty, Kind); +} + +template +static QualType castElement(Sema &S, ExprResult &E, QualType Ty) { + E = S.ImpCastExprToType(E.get(), Ty, Kind); + return Ty; +} + +static QualType handleFloatVectorBinOpConversion( + Sema &SemaRef, ExprResult &LHS, ExprResult &RHS, QualType LHSType, + QualType RHSType, QualType LElTy, QualType RElTy, bool IsCompAssign) { + bool LHSFloat = LElTy->isRealFloatingType(); + bool RHSFloat = RElTy->isRealFloatingType(); + + if (LHSFloat && RHSFloat) { + if (IsCompAssign || + SemaRef.getASTContext().getFloatingTypeOrder(LElTy, RElTy) > 0) + return castElement(SemaRef, RHS, LHSType); + + return castElement(SemaRef, LHS, RHSType); + } + + if (LHSFloat) + return castElement(SemaRef, RHS, LHSType); + + assert(RHSFloat); + if (IsCompAssign) + return castElement(SemaRef, RHS, LHSType); + + return castElement(SemaRef, LHS, RHSType); +} + +static QualType handleIntegerVectorBinOpConversion( + Sema &SemaRef, ExprResult &LHS, ExprResult &RHS, QualType LHSType, + QualType RHSType, QualType LElTy, QualType RElTy, bool IsCompAssign) { + + int IntOrder = SemaRef.Context.getIntegerTypeOrder(LElTy, RElTy); + bool LHSSigned = LElTy->hasSignedIntegerRepresentation(); + bool RHSSigned = RElTy->hasSignedIntegerRepresentation(); + auto &Ctx = SemaRef.getASTContext(); + + // If both types have the same signedness, use the higher ranked type. + if (LHSSigned == RHSSigned) { + if (IsCompAssign || IntOrder >= 0) + return castElement(SemaRef, RHS, LHSType); + + return castElement(SemaRef, LHS, RHSType); + } + + // If the unsigned type has greater than or equal rank of the signed type, use + // the unsigned type. + if (IntOrder != (LHSSigned ? 1 : -1)) { + if (IsCompAssign || RHSSigned) + return castElement(SemaRef, RHS, LHSType); + return castElement(SemaRef, LHS, RHSType); + } + + // At this point the signed type has higher rank than the unsigned type, which + // means it will be the same size or bigger. If the signed type is bigger, it + // can represent all the values of the unsigned type, so select it. + if (Ctx.getIntWidth(LElTy) != Ctx.getIntWidth(RElTy)) { + if (IsCompAssign || LHSSigned) + return castElement(SemaRef, RHS, LHSType); + return castElement(SemaRef, LHS, RHSType); + } + + // This is a bit of an odd duck case in HLSL. It shouldn't happen, but can due + // to C/C++ leaking through. The place this happens today is long vs long + // long. When arguments are vector and vector, + // the long long has higher rank than long even though they are the same size. + + // If this is a compound assignment cast the right hand side to the left hand + // side's type. + if (IsCompAssign) + return castElement(SemaRef, RHS, LHSType); + + // If this isn't a compound assignment we convert to unsigned long long. + QualType ElTy = Ctx.getCorrespondingUnsignedType(LHSSigned ? LElTy : RElTy); + QualType NewTy = Ctx.getExtVectorType( + ElTy, RHSType->castAs()->getNumElements()); + (void)castElement(SemaRef, RHS, NewTy); + + return castElement(SemaRef, LHS, NewTy); +} + +static CastKind getScalarCastKind(ASTContext &Ctx, QualType DestTy, QualType SrcTy) { + if (DestTy->isRealFloatingType() && SrcTy->isRealFloatingType()) + return CK_FloatingCast; + if (DestTy->isIntegralType(Ctx) && SrcTy->isIntegralType(Ctx)) + return CK_IntegralCast; + if (DestTy->isRealFloatingType()) + return CK_IntegralToFloating; + assert(SrcTy->isRealFloatingType() && DestTy->isIntegralType(Ctx)); + return CK_FloatingToIntegral; +} + +QualType SemaHLSL::handleVectorBinOpConversion(ExprResult &LHS, ExprResult &RHS, + QualType LHSType, + QualType RHSType, + bool IsCompAssign) { + const auto *LVecTy = LHSType->getAs(); + const auto *RVecTy = RHSType->getAs(); + auto &Ctx = getASTContext(); + + // If the LHS is not a vector and this is a compound assignment, we truncate + // the argument to a scalar then convert it to the LHS's type. + if (!LVecTy && IsCompAssign) { + QualType RElTy = RHSType->castAs()->getElementType(); + RHS = SemaRef.ImpCastExprToType(RHS.get(), RElTy, CK_HLSLVectorTruncation); + RHSType = RHS.get()->getType(); + if (Ctx.hasSameUnqualifiedType(LHSType, RHSType)) + return LHSType; + RHS = SemaRef.ImpCastExprToType(RHS.get(), LHSType, + getScalarCastKind(Ctx, LHSType, RHSType)); + return LHSType; + } + + unsigned EndSz = std::numeric_limits::max(); + unsigned LSz = 0; + if (LVecTy) + LSz = EndSz = LVecTy->getNumElements(); + if (RVecTy) + EndSz = std::min(RVecTy->getNumElements(), EndSz); + assert(EndSz != std::numeric_limits::max() && + "one of the above should have had a value"); + + // In a compound assignment, the left operand does not change type, the right + // operand is converted to the type of the left operand. + if (IsCompAssign && LSz != EndSz) { + Diag(LHS.get()->getBeginLoc(), + diag::err_hlsl_vector_compound_assignment_truncation) + << LHSType << RHSType; + return QualType(); + } + + if (RVecTy && RVecTy->getNumElements() > EndSz) + castVector(SemaRef, RHS, RHSType, EndSz); + if (!IsCompAssign && LVecTy && LVecTy->getNumElements() > EndSz) + castVector(SemaRef, LHS, LHSType, EndSz); + + if (!RVecTy) + castVector(SemaRef, RHS, RHSType, EndSz); + if (!IsCompAssign && !LVecTy) + castVector(SemaRef, LHS, LHSType, EndSz); + + // If we're at the same type after resizing we can stop here. + if (Ctx.hasSameUnqualifiedType(LHSType, RHSType)) + return Ctx.getCommonSugaredType(LHSType, RHSType); + + QualType LElTy = LHSType->castAs()->getElementType(); + QualType RElTy = RHSType->castAs()->getElementType(); + + // Handle conversion for floating point vectors. + if (LElTy->isRealFloatingType() || RElTy->isRealFloatingType()) + return handleFloatVectorBinOpConversion(SemaRef, LHS, RHS, LHSType, RHSType, + LElTy, RElTy, IsCompAssign); + + assert(LElTy->isIntegralType(Ctx) && RElTy->isIntegralType(Ctx) && + "HLSL Vectors can only contain integer or floating point types"); + return handleIntegerVectorBinOpConversion(SemaRef, LHS, RHS, LHSType, RHSType, + LElTy, RElTy, IsCompAssign); +} + void SemaHLSL::handleNumThreadsAttr(Decl *D, const ParsedAttr &AL) { llvm::VersionTuple SMVersion = getASTContext().getTargetInfo().getTriple().getOSVersion(); diff --git a/clang/test/SemaHLSL/Language/UsualArithmeticConversions.hlsl b/clang/test/SemaHLSL/Language/UsualArithmeticConversions.hlsl new file mode 100644 index 000000000000000..921d472bb057174 --- /dev/null +++ b/clang/test/SemaHLSL/Language/UsualArithmeticConversions.hlsl @@ -0,0 +1,284 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -finclude-default-header -fnative-half-type %s -DERRORS -Wconversion -Wdouble-promotion -verify +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -finclude-default-header -fnative-half-type %s -ast-dump | FileCheck %s + +//----------------------------------------------------------------------------// +// Case 1: float4 * int4 and inverse. +// +// In both cases here the int is converted to a float and the computation +// produces a float value. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used f4f4i4 'float4 (float4, int4)' +// CHECK: BinaryOperator {{.*}} 'float4':'vector' '*' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float4':'vector' lvalue ParmVar {{.*}} 'A' 'float4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int4':'vector' lvalue ParmVar {{.*}} 'B' 'int4':'vector' +export float4 f4f4i4(float4 A, int4 B) { + return A * B; // expected-warning{{implicit conversion from 'int4' (aka 'vector') to 'float4' (aka 'vector') may lose precision}} +} + +// CHECK-LABEL: FunctionDecl {{.*}} used f4i4f4 'float4 (float4, int4)' +// CHECK: BinaryOperator {{.*}} 'float4':'vector' '*' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int4':'vector' lvalue ParmVar {{.*}} 'B' 'int4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float4':'vector' lvalue ParmVar {{.*}} 'A' 'float4':'vector' +export float4 f4i4f4(float4 A, int4 B) { + return B * A; // expected-warning{{implicit conversion from 'int4' (aka 'vector') to 'float4' (aka 'vector') may lose precision}} +} + +//----------------------------------------------------------------------------// +// Case 2: float4 * int2 and inverse. +// +// In both cases the float vector is trunctated to a float2 and the integer +// vector is converted to a float2. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used f2f4i2 'float2 (float4, int2)' +// CHECK: BinaryOperator {{.*}} 'vector' '*' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}}'float4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float4':'vector' lvalue ParmVar {{.*}} 'A' 'float4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int2':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int2':'vector' lvalue ParmVar {{.*}} 'B' 'int2':'vector' +export float2 f2f4i2(float4 A, int2 B) { + // expected-warning@#f2f4i2 {{implicit conversion from 'int2' (aka 'vector') to 'vector' (vector of 2 'float' values) may lose precision}} + // expected-warning@#f2f4i2 {{implicit conversion truncates vector: 'float4' (aka 'vector') to 'vector' (vector of 2 'float' values)}} + return A * B; // #f2f4i2 +} + +// CHECK-LABEL: FunctionDecl {{.*}} used f2i2f4 'float2 (float4, int2)' +// CHECK: BinaryOperator {{.*}} 'vector' '*' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int2':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int2':'vector' lvalue ParmVar {{.*}} 'B' 'int2':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}}'float4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float4':'vector' lvalue ParmVar {{.*}} 'A' 'float4':'vector' +export float2 f2i2f4(float4 A, int2 B) { + // expected-warning@#f2i2f4 {{implicit conversion from 'int2' (aka 'vector') to 'vector' (vector of 2 'float' values) may lose precision}} + // expected-warning@#f2i2f4 {{implicit conversion truncates vector: 'float4' (aka 'vector') to 'vector' (vector of 2 'float' values)}} + return B * A; // #f2i2f4 +} + +//----------------------------------------------------------------------------// +// Case 3: Integers of mismatched sign, equivalent size, but the unsigned type +// has lower conversion rank. +// +// This is the odd-ball case for HLSL that isn't really in spec, but we should +// handle gracefully. The lower-ranked unsigned type is converted to the +// equivalent unsigned type of higher rank, and the signed type is also +// converted to that unsigned type (meaning `unsigned long` becomes `unsinged +// long long`, and `long long` becomes `unsigned long long`). +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used wierdo 'int4 (vector, vector)' +// CHECK: BinaryOperator {{.*}} 'vector' '*' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: DeclRefExpr{{.*}} 'vector' lvalue ParmVar {{.*}} 'A' 'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: ImplicitCastExpr{{.*}}> 'vector' +// CHECK-NEXT: DeclRefExpr {{.*}}'vector' lvalue ParmVar {{.*}} 'B' 'vector' +export int4 wierdo(vector A, vector B) { + // expected-warning@#wierdo {{implicit conversion loses integer precision: 'vector' (vector of 4 'unsigned long long' values) to 'vector' (vector of 4 'int' values)}} + // expected-warning@#wierdo {{implicit conversion changes signedness: 'vector' (vector of 4 'long long' values) to 'vector' (vector of 4 'unsigned long long' values)}} + return A * B; // #wierdo +} + +//----------------------------------------------------------------------------// +// Case 4: Compound assignment of float4 with an int4. +// +// In compound assignment the RHS is converted to match the LHS. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used f4f4i4compound 'float4 (float4, int4)' +// CHECK: CompoundAssignOperator {{.*}} 'float4':'vector' lvalue '+=' ComputeLHSTy='float4':'vector' ComputeResultTy='float4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float4':'vector' lvalue ParmVar {{.*}} 'A' 'float4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int4':'vector' lvalue ParmVar {{.*}} 'B' 'int4':'vector' +export float4 f4f4i4compound(float4 A, int4 B) { + A += B; // expected-warning{{implicit conversion from 'int4' (aka 'vector') to 'float4' (aka 'vector') may lose precision}} + return A; +} + + +//----------------------------------------------------------------------------// +// Case 5: Compound assignment of float2 with an int4. +// +// In compound assignment the RHS is converted to match the LHS. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used f4f2i4compound 'float4 (float2, int4)' +// CHECK: CompoundAssignOperator {{.*}} 'float2':'vector' lvalue '+=' ComputeLHSTy='float2':'vector' ComputeResultTy='float2':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float2':'vector' lvalue ParmVar {{.*}} 'A' 'float2':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float2':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int4':'vector' lvalue ParmVar {{.*}} 'B' 'int4':'vector' +export float4 f4f2i4compound(float2 A, int4 B) { + // expected-warning@#f4f2i4compound{{implicit conversion truncates vector: 'int4' (aka 'vector') to 'float2' (aka 'vector')}} + // expected-warning@#f4f2i4compound{{implicit conversion from 'int4' (aka 'vector') to 'float2' (aka 'vector') may lose precision}} + A += B; // #f4f2i4compound + return A.xyxy; +} + +//----------------------------------------------------------------------------// +// Case 6: float2 * int4 +// +// The int4 vector is trunctated to int2 then converted to float2. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used f4f2i4 'float2 (float2, int4)' +// CHECK: BinaryOperator {{.*}} 'float2':'vector' '*' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float2':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float2':'vector' lvalue ParmVar {{.*}} 'A' 'float2':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float2':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int4':'vector' lvalue ParmVar {{.*}} 'B' 'int4':'vector' +export float2 f4f2i4(float2 A, int4 B) { + // expected-warning@#f4f2i4{{implicit conversion truncates vector: 'int4' (aka 'vector') to 'float2' (aka 'vector')}} + // expected-warning@#f4f2i4{{implicit conversion from 'int4' (aka 'vector') to 'float2' (aka 'vector') may lose precision}} + return A * B; // #f4f2i4 +} + +//----------------------------------------------------------------------------// +// Case 7: Compound assignment of half4 with float4, and inverse. +// +// In compound assignment the RHS is converted to match the LHS. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used f4h4f4compound 'float4 (half4, float4)' +// CHECK: CompoundAssignOperator {{.*}} 'half4':'vector' lvalue '+=' ComputeLHSTy='half4':'vector' ComputeResultTy='half4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'half4':'vector' lvalue ParmVar {{.*}} 'A' 'half4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'half4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float4':'vector' lvalue ParmVar {{.*}} 'B' 'float4':'vector' +export float4 f4h4f4compound(half4 A, float4 B) { + A += B; // expected-warning{{implicit conversion loses floating-point precision: 'float4' (aka 'vector') to 'half4' (aka 'vector')}} + return B; +} + +// CHECK-LABEL: FunctionDecl {{.*}} used f4f4h4compound 'float4 (float4, half4)' +// CHECK: CompoundAssignOperator {{.*}} 'float4':'vector' lvalue '+=' ComputeLHSTy='float4':'vector' ComputeResultTy='float4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float4':'vector' lvalue ParmVar {{.*}} 'A' 'float4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'half4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'half4':'vector' lvalue ParmVar {{.*}} 'B' 'half4':'vector' +export float4 f4f4h4compound(float4 A, half4 B) { + A += B; // expected-warning{{implicit conversion increases floating-point precision: 'half4' (aka 'vector') to 'float4' (aka 'vector')}} + return A; +} + +//----------------------------------------------------------------------------// +// Case 8: int64_t4 * uint4 +// +// The unsigned argument is promoted to the higher ranked signed type since it +// can express all values of the unsgined argument. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used l4l4i4 'int64_t4 (int64_t4, uint4)' +// CHECK: BinaryOperator {{.*}} 'int64_t4':'vector' '*' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int64_t4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int64_t4':'vector' lvalue ParmVar {{.*}} 'A' 'int64_t4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int64_t4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'uint4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'uint4':'vector' lvalue ParmVar {{.*}} 'B' 'uint4':'vector' +export int64_t4 l4l4i4(int64_t4 A, uint4 B) { + return A * B; +} + +//----------------------------------------------------------------------------// +// Case 9: Compound assignment of int4 from int64_t4 +// +// In compound assignment the RHS is converted to match the LHS. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used i4i4l4compound 'int4 (int4, int64_t4)' +// CHECK: CompoundAssignOperator {{.*}} 'int4':'vector' lvalue '+=' ComputeLHSTy='int4':'vector' ComputeResultTy='int4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int4':'vector' lvalue ParmVar {{.*}} 'A' 'int4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int4':'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int64_t4':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int64_t4':'vector' lvalue ParmVar {{.*}} 'B' 'int64_t4':'vector' +export int4 i4i4l4compound(int4 A, int64_t4 B) { + A += B; // expected-warning{{implicit conversion loses integer precision: 'int64_t4' (aka 'vector') to 'int4' (aka 'vector')}} + return A; +} + +//----------------------------------------------------------------------------// +// Case 10: Compound assignment of vector with argument of +// vector +// +// In compound assignment the RHS is converted to match the LHS. This one is +// also the weird case because it is out of spec, but we should handle it +// gracefully. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used wierdocompound 'vector (vector, vector)' +// CHECK: CompoundAssignOperator {{.*}} 'vector' lvalue '+=' ComputeLHSTy='vector' ComputeResultTy='vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'vector' lvalue ParmVar {{.*}} 'A' 'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'vector' lvalue ParmVar {{.*}} 'B' 'vector' +export vector wierdocompound(vector A, vector B) { + // expected-warning@#wierdocompound{{implicit conversion changes signedness: 'vector' (vector of 4 'long long' values) to 'vector' (vector of 4 'unsigned long' values)}} + A += B; // #wierdocompound + return A; +} + +//----------------------------------------------------------------------------// +// Case 11: Compound assignment of scalar with vector argument. +// +// Because the LHS of a compound assignment cannot change type, the RHS must be +// implicitly convertable to the LHS type. +//----------------------------------------------------------------------------// + +// CHECK-LABEL: FunctionDecl {{.*}} used ffi2compound 'float (float, int2)' +// CHECK: CompoundAssignOperator {{.*}} 'float' lvalue '+=' ComputeLHSTy='float' ComputeResultTy='float' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float' lvalue ParmVar {{.*}} 'A' 'float' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int2':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int2':'vector' lvalue ParmVar {{.*}} 'B' 'int2':'vector' +export float ffi2compound(float A, int2 B) { + A += B; // expected-warning {{implicit conversion turns vector to scalar: 'int2' (aka 'vector') to 'float'}} + return A; +} + +// CHECK-LABEL: FunctionDecl {{.*}} used iif2compound 'int (int, float2)' +// CHECK: CompoundAssignOperator {{.*}} 'int' lvalue '+=' ComputeLHSTy='int' ComputeResultTy='int' +// CHECK-NEXT: DeclRefExpr {{.*}} 'int' lvalue ParmVar {{.*}} 'A' 'int' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int' +// CHECK-NEXT: mplicitCastExpr {{.*}} 'float' +// CHECK-NEXT: ImplicitCastExpr{{.*}} 'float2':'vector' +// CHECK-NEXT: DeclRefExpr {{.*}} 'float2':'vector' lvalue ParmVar {{.*}} 'B' 'float2':'vector' +export int iif2compound(int A, float2 B) { + A += B; // expected-warning{{implicit conversion turns vector to scalar: 'float2' (aka 'vector') to 'int'}} + return A; +} + + +#ifdef ERRORS +// The only cases that are really illegal here are when the RHS is a vector that +// is larger than the LHS or when the LHS is a scalar. + +//----------------------------------------------------------------------------// +// Case 12: Compound assignment of vector of larger size than the argument. +// +// Because the LHS of a compound assignment cannot change type, the RHS must be +// implicitly convertable to the LHS type. This fails since the RHS type can't +// be vector-extended implicitly. +//----------------------------------------------------------------------------// + +export float2 f2f4i2compound(float4 A, int2 B) { + A += B; // expected-error{{left hand operand of type 'float4' (aka 'vector') to compound assignment cannot be truncated when used with right hand operand of type 'int2' (aka 'vector')}} + return A.xy; +} + +#endif