From a173605b6043739e69f89d3a559a4f6a68d5fc0a Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Thu, 25 Apr 2024 15:47:22 -0500 Subject: [PATCH 1/2] [HLSL] Shore up floating point conversions This PR fixes bugs in HLSL floating conversions. HLSL always has `half`, `float` and `double` types, which promote in the order: `half`->`float`->`double` and convert in the order: `double`->`float`->`half` As with other conversions in C++, promotions are preferred over conversions. We do have floating conversions documented in the draft language specification (https://microsoft.github.io/hlsl-specs/specs/hlsl.pdf [Conv.rank.float]) although the exact language is still in flux (https://github.com/microsoft/hlsl-specs/pull/206). Resolves #81047 --- clang/lib/Sema/SemaOverload.cpp | 43 +++- .../SemaHLSL/ScalarOverloadResolution.hlsl | 229 ++++++++++++++++++ .../VectorElementOverloadResolution.hlsl | 228 +++++++++++++++++ 3 files changed, 499 insertions(+), 1 deletion(-) create mode 100644 clang/test/SemaHLSL/ScalarOverloadResolution.hlsl create mode 100644 clang/test/SemaHLSL/VectorElementOverloadResolution.hlsl diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 04cd9e78739d20..a416df2e97c439 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -2587,7 +2587,8 @@ bool Sema::IsIntegralPromotion(Expr *From, QualType FromType, QualType ToType) { // In HLSL an rvalue of integral type can be promoted to an rvalue of a larger // integral type. - if (Context.getLangOpts().HLSL) + if (Context.getLangOpts().HLSL && FromType->isIntegerType() && + ToType->isIntegerType()) return Context.getTypeSize(FromType) < Context.getTypeSize(ToType); return false; @@ -2616,6 +2617,13 @@ bool Sema::IsFloatingPointPromotion(QualType FromType, QualType ToType) { ToBuiltin->getKind() == BuiltinType::Ibm128)) return true; + // In HLSL, `half` promotes to `float` or `double`, regardless of whether + // or not native half types are enabled. + if (getLangOpts().HLSL && FromBuiltin->getKind() == BuiltinType::Half && + (ToBuiltin->getKind() == BuiltinType::Float || + ToBuiltin->getKind() == BuiltinType::Double)) + return true; + // Half can be promoted to float. if (!getLangOpts().NativeHalfType && FromBuiltin->getKind() == BuiltinType::Half && @@ -4393,6 +4401,24 @@ getFixedEnumPromtion(Sema &S, const StandardConversionSequence &SCS) { return FixedEnumPromotion::ToPromotedUnderlyingType; } +static ImplicitConversionSequence::CompareKind +HLSLCompareFloatingRank(QualType LHS, QualType RHS) { + assert(LHS->isVectorType() == RHS->isVectorType() && + "Either both elements should be vectors or neither should."); + if (const auto *VT = LHS->getAs()) + LHS = VT->getElementType(); + + if (const auto *VT = RHS->getAs()) + RHS = VT->getElementType(); + + const auto L = LHS->getAs()->getKind(); + const auto R = RHS->getAs()->getKind(); + if (L == R) + return ImplicitConversionSequence::Indistinguishable; + return L < R ? ImplicitConversionSequence::Better + : ImplicitConversionSequence::Worse; +} + /// CompareStandardConversionSequences - Compare two standard /// conversion sequences to determine whether one is better than the /// other or if they are indistinguishable (C++ 13.3.3.2p3). @@ -4634,6 +4660,21 @@ CompareStandardConversionSequences(Sema &S, SourceLocation Loc, : ImplicitConversionSequence::Worse; } + if (S.getLangOpts().HLSL) { + // On a promotion we prefer the lower rank to disambiguate. + if ((SCS1.Second == ICK_Floating_Promotion && + SCS2.Second == ICK_Floating_Promotion) || + (SCS1.Element == ICK_Floating_Promotion && + SCS2.Element == ICK_Floating_Promotion)) + return HLSLCompareFloatingRank(SCS1.getToType(2), SCS2.getToType(2)); + // On a conversion we prefer the higher rank to disambiguate. + if ((SCS1.Second == ICK_Floating_Conversion && + SCS2.Second == ICK_Floating_Conversion) || + (SCS1.Element == ICK_Floating_Conversion && + SCS2.Element == ICK_Floating_Conversion)) + return HLSLCompareFloatingRank(SCS2.getToType(2), SCS1.getToType(2)); + } + return ImplicitConversionSequence::Indistinguishable; } diff --git a/clang/test/SemaHLSL/ScalarOverloadResolution.hlsl b/clang/test/SemaHLSL/ScalarOverloadResolution.hlsl new file mode 100644 index 00000000000000..13758995ec6a1e --- /dev/null +++ b/clang/test/SemaHLSL/ScalarOverloadResolution.hlsl @@ -0,0 +1,229 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -fnative-half-type -finclude-default-header -Wconversion -verify -o - %s +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -fnative-half-type -finclude-default-header -ast-dump %s | FileCheck %s + +// This test verifies floating point type implicit conversion ranks for overload +// resolution. In HLSL the built-in type ranks are half < float < double. This +// applies to both scalar and vector types. + +// HLSL allows implicit truncation fo types, so it differentiates between +// promotions (converting to larger types) and conversions (converting to +// smaller types). Promotions are preferred over conversions. Promotions prefer +// promoting to the next lowest type in the ranking order. Conversions prefer +// converting to the next highest type in the ranking order. + +void HalfFloatDouble(double D); +void HalfFloatDouble(float F); +void HalfFloatDouble(half H); + +// CHECK: FunctionDecl {{.*}} used HalfFloatDouble 'void (double)' +// CHECK: FunctionDecl {{.*}} used HalfFloatDouble 'void (float)' +// CHECK: FunctionDecl {{.*}} used HalfFloatDouble 'void (half)' + +void FloatDouble(double D); +void FloatDouble(float F); + +// CHECK: FunctionDecl {{.*}} used FloatDouble 'void (double)' +// CHECK: FunctionDecl {{.*}} used FloatDouble 'void (float)' + +void HalfDouble(double D); +void HalfDouble(half H); + +// CHECK: FunctionDecl {{.*}} used HalfDouble 'void (double)' +// CHECK: FunctionDecl {{.*}} used HalfDouble 'void (half)' + +void HalfFloat(float F); +void HalfFloat(half H); + +// CHECK: FunctionDecl {{.*}} used HalfFloat 'void (float)' +// CHECK: FunctionDecl {{.*}} used HalfFloat 'void (half)' + +void Double(double D); +void Float(float F); +void Half(half H); + +// CHECK: FunctionDecl {{.*}} used Double 'void (double)' +// CHECK: FunctionDecl {{.*}} used Float 'void (float)' +// CHECK: FunctionDecl {{.*}} used Half 'void (half)' + + +// Case 1: A function declared with overloads for half float and double types. +// (a) When called with half, it will resolve to half because half is an exact +// match. +// (b) When called with float it will resolve to float because float is an +// exact match. +// (c) When called with double it will resolve to double because it is an +// exact match. + +// CHECK: FunctionDecl {{.*}} Case1 'void (half, float, double)' +void Case1(half H, float F, double D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (half)' + HalfFloatDouble(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (float)' + HalfFloatDouble(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (double)' + HalfFloatDouble(D); +} + +// Case 2: A function declared with double and float overlaods. +// (a) When called with half, it will resolve to float because float is lower +// ranked than double. +// (b) When called with float it will resolve to float because float is an +// exact match. +// (c) When called with double it will resolve to double because it is an +// exact match. + +// CHECK: FunctionDecl {{.*}} Case2 'void (half, float, double)' +void Case2(half H, float F, double D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'FloatDouble' 'void (float)' + FloatDouble(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'FloatDouble' 'void (float)' + FloatDouble(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'FloatDouble' 'void (double)' + FloatDouble(D); +} + +// Case 3: A function declared with half and double overloads +// (a) When called with half, it will resolve to half because it is an exact +// match. +// (b) When called with flaot, it will resolve to double because double is a +// valid promotion. +// (c) When called with double, it will resolve to double because it is an +// exact match. + +// CHECK: FunctionDecl {{.*}} Case3 'void (half, float, double)' +void Case3(half H, float F, double D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'HalfDouble' 'void (half)' + HalfDouble(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'HalfDouble' 'void (double)' + HalfDouble(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'HalfDouble' 'void (double)' + HalfDouble(D); +} + +// Case 4: A function declared with half and float overloads. +// (a) When called with half, it will resolve to half because half is an exact +// match. +// (b) When called with float it will resolve to float because float is an +// exact match. +// (c) When called with double it will resolve to float because it is the +// float is higher rank than half. + +// CHECK: FunctionDecl {{.*}} Case4 'void (half, float, double)' +void Case4(half H, float F, double D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'HalfFloat' 'void (half)' + HalfFloat(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'HalfFloat' 'void (float)' + HalfFloat(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'HalfFloat' 'void (float)' + HalfFloat(D); // expected-warning{{implicit conversion loses floating-point precision: 'double' to 'float'}} +} + +// Case 5: A function declared with only a double overload. +// (a) When called with half, it will resolve to double because double is a +// valid promotion. +// (b) When called with float it will resolve to double because double is a +// valid promotion. +// (c) When called with double it will resolve to double because it is an +// exact match. + +// CHECK: FunctionDecl {{.*}} Case5 'void (half, float, double)' +void Case5(half H, float F, double D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'Double' 'void (double)' + Double(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'Double' 'void (double)' + Double(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'Double' 'void (double)' + Double(D); +} + +// Case 6: A function declared with only a float overload. +// (a) When called with half, it will resolve to float because float is a +// valid promotion. +// (b) When called with float it will resolve to float because float is an +// exact match. +// (c) When called with double it will resolve to float because it is a +// valid conversion. + +// CHECK: FunctionDecl {{.*}} Case6 'void (half, float, double)' +void Case6(half H, float F, double D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'Float' 'void (float)' + Float(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'Float' 'void (float)' + Float(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'Float' 'void (float)' + Float(D); // expected-warning{{implicit conversion loses floating-point precision: 'double' to 'float'}} +} + +// Case 7: A function declared with only a half overload. +// (a) When called with half, it will resolve to half because half is an +// exact match +// (b) When called with float it will resolve to half because half is a +// valid conversion. +// (c) When called with double it will resolve to float because it is a +// valid conversion. + +// CHECK: FunctionDecl {{.*}} Case7 'void (half, float, double)' +void Case7(half H, float F, double D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'Half' 'void (half)' + Half(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'Half' 'void (half)' + Half(F); // expected-warning{{implicit conversion loses floating-point precision: 'float' to 'half'}} + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'Half' 'void (half)' + Half(D); // expected-warning{{implicit conversion loses floating-point precision: 'double' to 'half'}} +} diff --git a/clang/test/SemaHLSL/VectorElementOverloadResolution.hlsl b/clang/test/SemaHLSL/VectorElementOverloadResolution.hlsl new file mode 100644 index 00000000000000..78bba54255309b --- /dev/null +++ b/clang/test/SemaHLSL/VectorElementOverloadResolution.hlsl @@ -0,0 +1,228 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -fnative-half-type -finclude-default-header -Wconversion -verify -o - %s +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -fnative-half-type -finclude-default-header -Wno-conversion -ast-dump %s | FileCheck %s + +// This test verifies floating point type implicit conversion ranks for overload +// resolution. In HLSL the built-in type ranks are half < float < double. This +// applies to both scalar and vector types. + +// HLSL allows implicit truncation fo types, so it differentiates between +// promotions (converting to larger types) and conversions (converting to +// smaller types). Promotions are preferred over conversions. Promotions prefer +// promoting to the next lowest type in the ranking order. Conversions prefer +// converting to the next highest type in the ranking order. + +void HalfFloatDouble(double2 D); +void HalfFloatDouble(float2 F); +void HalfFloatDouble(half2 H); + +// CHECK: FunctionDecl {{.*}} used HalfFloatDouble 'void (double2)' +// CHECK: FunctionDecl {{.*}} used HalfFloatDouble 'void (float2)' +// CHECK: FunctionDecl {{.*}} used HalfFloatDouble 'void (half2)' + +void FloatDouble(double2 D); +void FloatDouble(float2 F); + +// CHECK: FunctionDecl {{.*}} used FloatDouble 'void (double2)' +// CHECK: FunctionDecl {{.*}} used FloatDouble 'void (float2)' + +void HalfDouble(double2 D); +void HalfDouble(half2 H); + +// CHECK: FunctionDecl {{.*}} used HalfDouble 'void (double2)' +// CHECK: FunctionDecl {{.*}} used HalfDouble 'void (half2)' + +void HalfFloat(float2 F); +void HalfFloat(half2 H); + +// CHECK: FunctionDecl {{.*}} used HalfFloat 'void (float2)' +// CHECK: FunctionDecl {{.*}} used HalfFloat 'void (half2)' + +void Double(double2 D); +void Float(float2 F); +void Half(half2 H); + +// CHECK: FunctionDecl {{.*}} used Double 'void (double2)' +// CHECK: FunctionDecl {{.*}} used Float 'void (float2)' +// CHECK: FunctionDecl {{.*}} used Half 'void (half2)' + +// Case 1: A function declared with overloads for half float and double types. +// (a) When called with half, it will resolve to half because half is an exact +// match. +// (b) When called with float it will resolve to float because float is an +// exact match. +// (c) When called with double it will resolve to double because it is an +// exact match. + +// CHECK: FunctionDecl {{.*}} Case1 'void (half2, float2, double2)' +void Case1(half2 H, float2 F, double2 D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half2)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (half2)' + HalfFloatDouble(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float2)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (float2)' + HalfFloatDouble(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double2)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (double2)' + HalfFloatDouble(D); +} + +// Case 2: A function declared with double and float overlaods. +// (a) When called with half, it will resolve to float because float is lower +// ranked than double. +// (b) When called with float it will resolve to float because float is an +// exact match. +// (c) When called with double it will resolve to double because it is an +// exact match. + +// CHECK: FunctionDecl {{.*}} Case2 'void (half2, float2, double2)' +void Case2(half2 H, float2 F, double2 D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float2)' lvalue Function {{.*}} 'FloatDouble' 'void (float2)' + FloatDouble(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float2)' lvalue Function {{.*}} 'FloatDouble' 'void (float2)' + FloatDouble(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double2)' lvalue Function {{.*}} 'FloatDouble' 'void (double2)' + FloatDouble(D); +} + +// Case 3: A function declared with half and double overloads +// (a) When called with half, it will resolve to half because it is an exact +// match. +// (b) When called with flaot, it will resolve to double because double is a +// valid promotion. +// (c) When called with double, it will resolve to double because it is an +// exact match. + +// CHECK: FunctionDecl {{.*}} Case3 'void (half2, float2, double2)' +void Case3(half2 H, float2 F, double2 D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half2)' lvalue Function {{.*}} 'HalfDouble' 'void (half2)' + HalfDouble(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double2)' lvalue Function {{.*}} 'HalfDouble' 'void (double2)' + HalfDouble(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double2)' lvalue Function {{.*}} 'HalfDouble' 'void (double2)' + HalfDouble(D); +} + +// Case 4: A function declared with half and float overloads. +// (a) When called with half, it will resolve to half because half is an exact +// match. +// (b) When called with float it will resolve to float because float is an +// exact match. +// (c) When called with double it will resolve to float because it is the +// float is higher rank than half. + +// CHECK: FunctionDecl {{.*}} Case4 'void (half2, float2, double2)' +void Case4(half2 H, float2 F, double2 D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half2)' lvalue Function {{.*}} 'HalfFloat' 'void (half2)' + HalfFloat(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float2)' lvalue Function {{.*}} 'HalfFloat' 'void (float2)' + HalfFloat(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float2)' lvalue Function {{.*}} 'HalfFloat' 'void (float2)' + HalfFloat(D); // expected-warning{{implicit conversion loses floating-point precision: 'double2' (aka 'vector') to 'float2' (aka 'vector')}} +} + +// Case 5: A function declared with only a double overload. +// (a) When called with half, it will resolve to double because double is a +// valid promotion. +// (b) When called with float it will resolve to double because double is a +// valid promotion. +// (c) When called with double it will resolve to double because it is an +// exact match. + +// CHECK: FunctionDecl {{.*}} Case5 'void (half2, float2, double2)' +void Case5(half2 H, float2 F, double2 D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double2)' lvalue Function {{.*}} 'Double' 'void (double2)' + Double(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double2)' lvalue Function {{.*}} 'Double' 'void (double2)' + Double(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (double2)' lvalue Function {{.*}} 'Double' 'void (double2)' + Double(D); +} + +// Case 6: A function declared with only a float overload. +// (a) When called with half, it will resolve to float because float is a +// valid promotion. +// (b) When called with float it will resolve to float because float is an +// exact match. +// (c) When called with double it will resolve to float because it is a +// valid conversion. + +// CHECK: FunctionDecl {{.*}} Case6 'void (half2, float2, double2)' +void Case6(half2 H, float2 F, double2 D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float2)' lvalue Function {{.*}} 'Float' 'void (float2)' + Float(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float2)' lvalue Function {{.*}} 'Float' 'void (float2)' + Float(F); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float2)' lvalue Function {{.*}} 'Float' 'void (float2)' + Float(D); // expected-warning{{implicit conversion loses floating-point precision: 'double2' (aka 'vector') to 'float2' (aka 'vector')}} +} + +// Case 7: A function declared with only a half overload. +// (a) When called with half, it will resolve to half because half is an +// exact match +// (b) When called with float it will resolve to half because half is a +// valid conversion. +// (c) When called with double it will resolve to float because it is a +// valid conversion. + +// CHECK: FunctionDecl {{.*}} Case7 'void (half2, float2, double2)' +void Case7(half2 H, float2 F, double2 D) { + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half2)' lvalue Function {{.*}} 'Half' 'void (half2)' + Half(H); + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half2)' lvalue Function {{.*}} 'Half' 'void (half2)' + Half(F); // expected-warning{{implicit conversion loses floating-point precision: 'float2' (aka 'vector') to 'half2' (aka 'vector')}} + + // CHECK: CallExpr {{.*}} 'void' + // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half2)' + // CHECK-NEXT: DeclRefExpr {{.*}} 'void (half2)' lvalue Function {{.*}} 'Half' 'void (half2)' + Half(D); // expected-warning{{implicit conversion loses floating-point precision: 'double2' (aka 'vector') to 'half2' (aka 'vector')}} +} From 87f56a384908cfadaa0bed5a4b6130b4c2bca95c Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Wed, 1 May 2024 15:01:22 -0500 Subject: [PATCH 2/2] Updating test cases by 270 commits. ../clang/test/SemaHLSL/VectorElementOverloadResolution.hlsl --- .../test/SemaHLSL/OverloadResolutionBugs.hlsl | 26 ++++++------------- .../SemaHLSL/ScalarOverloadResolution.hlsl | 14 +++++----- .../VectorElementOverloadResolution.hlsl | 14 +++++----- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/clang/test/SemaHLSL/OverloadResolutionBugs.hlsl b/clang/test/SemaHLSL/OverloadResolutionBugs.hlsl index c13cb299127aac..30de00063f5427 100644 --- a/clang/test/SemaHLSL/OverloadResolutionBugs.hlsl +++ b/clang/test/SemaHLSL/OverloadResolutionBugs.hlsl @@ -4,15 +4,6 @@ // https://github.com/llvm/llvm-project/issues/81047 // expected-no-diagnostics -void Fn3(double2 D); -void Fn3(float2 F); - -void Call3(half2 H) { Fn3(H); } - -void Fn5(double2 D); - -void Call5(half2 H) { Fn5(H); } - void Fn4(int64_t2 L); void Fn4(int2 I); @@ -61,13 +52,12 @@ float test_frac_int(int p0) { return frac(p0); } float test_frac_bool(bool p0) { return frac(p0); } -// https://github.com/llvm/llvm-project/issues/81049 +// This resolves the wrong overload. In clang this converts down to an int, in +// DXC it extends the scalar to a vector. +void Fn(int) {} +void Fn(vector) {} -// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -triple \ -// RUN: dxil-pc-shadermodel6.2-library %s -emit-llvm -disable-llvm-passes \ -// RUN: -o - | FileCheck %s --check-prefix=NO_HALF - -half sqrt_h(half x) { return sqrt(x); } - -// NO_HALF: define noundef float @"?sqrt_h@@YA$halff@$halff@@Z"( -// NO_HALF: call float @llvm.sqrt.f32(float %0) +void Call() { + int64_t V; + Fn(V); +} diff --git a/clang/test/SemaHLSL/ScalarOverloadResolution.hlsl b/clang/test/SemaHLSL/ScalarOverloadResolution.hlsl index 13758995ec6a1e..41702ef175320a 100644 --- a/clang/test/SemaHLSL/ScalarOverloadResolution.hlsl +++ b/clang/test/SemaHLSL/ScalarOverloadResolution.hlsl @@ -54,7 +54,7 @@ void Half(half H); // (c) When called with double it will resolve to double because it is an // exact match. -// CHECK: FunctionDecl {{.*}} Case1 'void (half, float, double)' +// CHECK-LABEL: FunctionDecl {{.*}} Case1 'void (half, float, double)' void Case1(half H, float F, double D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' @@ -80,7 +80,7 @@ void Case1(half H, float F, double D) { // (c) When called with double it will resolve to double because it is an // exact match. -// CHECK: FunctionDecl {{.*}} Case2 'void (half, float, double)' +// CHECK-LABEL: FunctionDecl {{.*}} Case2 'void (half, float, double)' void Case2(half H, float F, double D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' @@ -106,7 +106,7 @@ void Case2(half H, float F, double D) { // (c) When called with double, it will resolve to double because it is an // exact match. -// CHECK: FunctionDecl {{.*}} Case3 'void (half, float, double)' +// CHECK-LABEL: FunctionDecl {{.*}} Case3 'void (half, float, double)' void Case3(half H, float F, double D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' @@ -132,7 +132,7 @@ void Case3(half H, float F, double D) { // (c) When called with double it will resolve to float because it is the // float is higher rank than half. -// CHECK: FunctionDecl {{.*}} Case4 'void (half, float, double)' +// CHECK-LABEL: FunctionDecl {{.*}} Case4 'void (half, float, double)' void Case4(half H, float F, double D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' @@ -158,7 +158,7 @@ void Case4(half H, float F, double D) { // (c) When called with double it will resolve to double because it is an // exact match. -// CHECK: FunctionDecl {{.*}} Case5 'void (half, float, double)' +// CHECK-LABEL: FunctionDecl {{.*}} Case5 'void (half, float, double)' void Case5(half H, float F, double D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' @@ -184,7 +184,7 @@ void Case5(half H, float F, double D) { // (c) When called with double it will resolve to float because it is a // valid conversion. -// CHECK: FunctionDecl {{.*}} Case6 'void (half, float, double)' +// CHECK-LABEL: FunctionDecl {{.*}} Case6 'void (half, float, double)' void Case6(half H, float F, double D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' @@ -210,7 +210,7 @@ void Case6(half H, float F, double D) { // (c) When called with double it will resolve to float because it is a // valid conversion. -// CHECK: FunctionDecl {{.*}} Case7 'void (half, float, double)' +// CHECK-LABEL: FunctionDecl {{.*}} Case7 'void (half, float, double)' void Case7(half H, float F, double D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' diff --git a/clang/test/SemaHLSL/VectorElementOverloadResolution.hlsl b/clang/test/SemaHLSL/VectorElementOverloadResolution.hlsl index 78bba54255309b..12575084ead2bb 100644 --- a/clang/test/SemaHLSL/VectorElementOverloadResolution.hlsl +++ b/clang/test/SemaHLSL/VectorElementOverloadResolution.hlsl @@ -53,7 +53,7 @@ void Half(half2 H); // (c) When called with double it will resolve to double because it is an // exact match. -// CHECK: FunctionDecl {{.*}} Case1 'void (half2, float2, double2)' +// CHECK-LABEL: FunctionDecl {{.*}} Case1 'void (half2, float2, double2)' void Case1(half2 H, float2 F, double2 D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half2)' @@ -79,7 +79,7 @@ void Case1(half2 H, float2 F, double2 D) { // (c) When called with double it will resolve to double because it is an // exact match. -// CHECK: FunctionDecl {{.*}} Case2 'void (half2, float2, double2)' +// CHECK-LABEL: FunctionDecl {{.*}} Case2 'void (half2, float2, double2)' void Case2(half2 H, float2 F, double2 D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' @@ -105,7 +105,7 @@ void Case2(half2 H, float2 F, double2 D) { // (c) When called with double, it will resolve to double because it is an // exact match. -// CHECK: FunctionDecl {{.*}} Case3 'void (half2, float2, double2)' +// CHECK-LABEL: FunctionDecl {{.*}} Case3 'void (half2, float2, double2)' void Case3(half2 H, float2 F, double2 D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half2)' @@ -131,7 +131,7 @@ void Case3(half2 H, float2 F, double2 D) { // (c) When called with double it will resolve to float because it is the // float is higher rank than half. -// CHECK: FunctionDecl {{.*}} Case4 'void (half2, float2, double2)' +// CHECK-LABEL: FunctionDecl {{.*}} Case4 'void (half2, float2, double2)' void Case4(half2 H, float2 F, double2 D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half2)' @@ -157,7 +157,7 @@ void Case4(half2 H, float2 F, double2 D) { // (c) When called with double it will resolve to double because it is an // exact match. -// CHECK: FunctionDecl {{.*}} Case5 'void (half2, float2, double2)' +// CHECK-LABEL: FunctionDecl {{.*}} Case5 'void (half2, float2, double2)' void Case5(half2 H, float2 F, double2 D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double2)' @@ -183,7 +183,7 @@ void Case5(half2 H, float2 F, double2 D) { // (c) When called with double it will resolve to float because it is a // valid conversion. -// CHECK: FunctionDecl {{.*}} Case6 'void (half2, float2, double2)' +// CHECK-LABEL: FunctionDecl {{.*}} Case6 'void (half2, float2, double2)' void Case6(half2 H, float2 F, double2 D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' @@ -209,7 +209,7 @@ void Case6(half2 H, float2 F, double2 D) { // (c) When called with double it will resolve to float because it is a // valid conversion. -// CHECK: FunctionDecl {{.*}} Case7 'void (half2, float2, double2)' +// CHECK-LABEL: FunctionDecl {{.*}} Case7 'void (half2, float2, double2)' void Case7(half2 H, float2 F, double2 D) { // CHECK: CallExpr {{.*}} 'void' // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half2)'