Skip to content

Commit a173605

Browse files
committed
[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 (microsoft/hlsl-specs#206). Resolves llvm#81047
1 parent 1728a56 commit a173605

File tree

3 files changed

+499
-1
lines changed

3 files changed

+499
-1
lines changed

clang/lib/Sema/SemaOverload.cpp

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2587,7 +2587,8 @@ bool Sema::IsIntegralPromotion(Expr *From, QualType FromType, QualType ToType) {
25872587

25882588
// In HLSL an rvalue of integral type can be promoted to an rvalue of a larger
25892589
// integral type.
2590-
if (Context.getLangOpts().HLSL)
2590+
if (Context.getLangOpts().HLSL && FromType->isIntegerType() &&
2591+
ToType->isIntegerType())
25912592
return Context.getTypeSize(FromType) < Context.getTypeSize(ToType);
25922593

25932594
return false;
@@ -2616,6 +2617,13 @@ bool Sema::IsFloatingPointPromotion(QualType FromType, QualType ToType) {
26162617
ToBuiltin->getKind() == BuiltinType::Ibm128))
26172618
return true;
26182619

2620+
// In HLSL, `half` promotes to `float` or `double`, regardless of whether
2621+
// or not native half types are enabled.
2622+
if (getLangOpts().HLSL && FromBuiltin->getKind() == BuiltinType::Half &&
2623+
(ToBuiltin->getKind() == BuiltinType::Float ||
2624+
ToBuiltin->getKind() == BuiltinType::Double))
2625+
return true;
2626+
26192627
// Half can be promoted to float.
26202628
if (!getLangOpts().NativeHalfType &&
26212629
FromBuiltin->getKind() == BuiltinType::Half &&
@@ -4393,6 +4401,24 @@ getFixedEnumPromtion(Sema &S, const StandardConversionSequence &SCS) {
43934401
return FixedEnumPromotion::ToPromotedUnderlyingType;
43944402
}
43954403

4404+
static ImplicitConversionSequence::CompareKind
4405+
HLSLCompareFloatingRank(QualType LHS, QualType RHS) {
4406+
assert(LHS->isVectorType() == RHS->isVectorType() &&
4407+
"Either both elements should be vectors or neither should.");
4408+
if (const auto *VT = LHS->getAs<VectorType>())
4409+
LHS = VT->getElementType();
4410+
4411+
if (const auto *VT = RHS->getAs<VectorType>())
4412+
RHS = VT->getElementType();
4413+
4414+
const auto L = LHS->getAs<BuiltinType>()->getKind();
4415+
const auto R = RHS->getAs<BuiltinType>()->getKind();
4416+
if (L == R)
4417+
return ImplicitConversionSequence::Indistinguishable;
4418+
return L < R ? ImplicitConversionSequence::Better
4419+
: ImplicitConversionSequence::Worse;
4420+
}
4421+
43964422
/// CompareStandardConversionSequences - Compare two standard
43974423
/// conversion sequences to determine whether one is better than the
43984424
/// other or if they are indistinguishable (C++ 13.3.3.2p3).
@@ -4634,6 +4660,21 @@ CompareStandardConversionSequences(Sema &S, SourceLocation Loc,
46344660
: ImplicitConversionSequence::Worse;
46354661
}
46364662

4663+
if (S.getLangOpts().HLSL) {
4664+
// On a promotion we prefer the lower rank to disambiguate.
4665+
if ((SCS1.Second == ICK_Floating_Promotion &&
4666+
SCS2.Second == ICK_Floating_Promotion) ||
4667+
(SCS1.Element == ICK_Floating_Promotion &&
4668+
SCS2.Element == ICK_Floating_Promotion))
4669+
return HLSLCompareFloatingRank(SCS1.getToType(2), SCS2.getToType(2));
4670+
// On a conversion we prefer the higher rank to disambiguate.
4671+
if ((SCS1.Second == ICK_Floating_Conversion &&
4672+
SCS2.Second == ICK_Floating_Conversion) ||
4673+
(SCS1.Element == ICK_Floating_Conversion &&
4674+
SCS2.Element == ICK_Floating_Conversion))
4675+
return HLSLCompareFloatingRank(SCS2.getToType(2), SCS1.getToType(2));
4676+
}
4677+
46374678
return ImplicitConversionSequence::Indistinguishable;
46384679
}
46394680

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -fnative-half-type -finclude-default-header -Wconversion -verify -o - %s
2+
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -fnative-half-type -finclude-default-header -ast-dump %s | FileCheck %s
3+
4+
// This test verifies floating point type implicit conversion ranks for overload
5+
// resolution. In HLSL the built-in type ranks are half < float < double. This
6+
// applies to both scalar and vector types.
7+
8+
// HLSL allows implicit truncation fo types, so it differentiates between
9+
// promotions (converting to larger types) and conversions (converting to
10+
// smaller types). Promotions are preferred over conversions. Promotions prefer
11+
// promoting to the next lowest type in the ranking order. Conversions prefer
12+
// converting to the next highest type in the ranking order.
13+
14+
void HalfFloatDouble(double D);
15+
void HalfFloatDouble(float F);
16+
void HalfFloatDouble(half H);
17+
18+
// CHECK: FunctionDecl {{.*}} used HalfFloatDouble 'void (double)'
19+
// CHECK: FunctionDecl {{.*}} used HalfFloatDouble 'void (float)'
20+
// CHECK: FunctionDecl {{.*}} used HalfFloatDouble 'void (half)'
21+
22+
void FloatDouble(double D);
23+
void FloatDouble(float F);
24+
25+
// CHECK: FunctionDecl {{.*}} used FloatDouble 'void (double)'
26+
// CHECK: FunctionDecl {{.*}} used FloatDouble 'void (float)'
27+
28+
void HalfDouble(double D);
29+
void HalfDouble(half H);
30+
31+
// CHECK: FunctionDecl {{.*}} used HalfDouble 'void (double)'
32+
// CHECK: FunctionDecl {{.*}} used HalfDouble 'void (half)'
33+
34+
void HalfFloat(float F);
35+
void HalfFloat(half H);
36+
37+
// CHECK: FunctionDecl {{.*}} used HalfFloat 'void (float)'
38+
// CHECK: FunctionDecl {{.*}} used HalfFloat 'void (half)'
39+
40+
void Double(double D);
41+
void Float(float F);
42+
void Half(half H);
43+
44+
// CHECK: FunctionDecl {{.*}} used Double 'void (double)'
45+
// CHECK: FunctionDecl {{.*}} used Float 'void (float)'
46+
// CHECK: FunctionDecl {{.*}} used Half 'void (half)'
47+
48+
49+
// Case 1: A function declared with overloads for half float and double types.
50+
// (a) When called with half, it will resolve to half because half is an exact
51+
// match.
52+
// (b) When called with float it will resolve to float because float is an
53+
// exact match.
54+
// (c) When called with double it will resolve to double because it is an
55+
// exact match.
56+
57+
// CHECK: FunctionDecl {{.*}} Case1 'void (half, float, double)'
58+
void Case1(half H, float F, double D) {
59+
// CHECK: CallExpr {{.*}} 'void'
60+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' <FunctionToPointerDecay>
61+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (half)'
62+
HalfFloatDouble(H);
63+
64+
// CHECK: CallExpr {{.*}} 'void'
65+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay>
66+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (float)'
67+
HalfFloatDouble(F);
68+
69+
// CHECK: CallExpr {{.*}} 'void'
70+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay>
71+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (double)'
72+
HalfFloatDouble(D);
73+
}
74+
75+
// Case 2: A function declared with double and float overlaods.
76+
// (a) When called with half, it will resolve to float because float is lower
77+
// ranked than double.
78+
// (b) When called with float it will resolve to float because float is an
79+
// exact match.
80+
// (c) When called with double it will resolve to double because it is an
81+
// exact match.
82+
83+
// CHECK: FunctionDecl {{.*}} Case2 'void (half, float, double)'
84+
void Case2(half H, float F, double D) {
85+
// CHECK: CallExpr {{.*}} 'void'
86+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay>
87+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'FloatDouble' 'void (float)'
88+
FloatDouble(H);
89+
90+
// CHECK: CallExpr {{.*}} 'void'
91+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay>
92+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'FloatDouble' 'void (float)'
93+
FloatDouble(F);
94+
95+
// CHECK: CallExpr {{.*}} 'void'
96+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay>
97+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'FloatDouble' 'void (double)'
98+
FloatDouble(D);
99+
}
100+
101+
// Case 3: A function declared with half and double overloads
102+
// (a) When called with half, it will resolve to half because it is an exact
103+
// match.
104+
// (b) When called with flaot, it will resolve to double because double is a
105+
// valid promotion.
106+
// (c) When called with double, it will resolve to double because it is an
107+
// exact match.
108+
109+
// CHECK: FunctionDecl {{.*}} Case3 'void (half, float, double)'
110+
void Case3(half H, float F, double D) {
111+
// CHECK: CallExpr {{.*}} 'void'
112+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' <FunctionToPointerDecay>
113+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'HalfDouble' 'void (half)'
114+
HalfDouble(H);
115+
116+
// CHECK: CallExpr {{.*}} 'void'
117+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay>
118+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'HalfDouble' 'void (double)'
119+
HalfDouble(F);
120+
121+
// CHECK: CallExpr {{.*}} 'void'
122+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay>
123+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'HalfDouble' 'void (double)'
124+
HalfDouble(D);
125+
}
126+
127+
// Case 4: A function declared with half and float overloads.
128+
// (a) When called with half, it will resolve to half because half is an exact
129+
// match.
130+
// (b) When called with float it will resolve to float because float is an
131+
// exact match.
132+
// (c) When called with double it will resolve to float because it is the
133+
// float is higher rank than half.
134+
135+
// CHECK: FunctionDecl {{.*}} Case4 'void (half, float, double)'
136+
void Case4(half H, float F, double D) {
137+
// CHECK: CallExpr {{.*}} 'void'
138+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' <FunctionToPointerDecay>
139+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'HalfFloat' 'void (half)'
140+
HalfFloat(H);
141+
142+
// CHECK: CallExpr {{.*}} 'void'
143+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay>
144+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'HalfFloat' 'void (float)'
145+
HalfFloat(F);
146+
147+
// CHECK: CallExpr {{.*}} 'void'
148+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay>
149+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'HalfFloat' 'void (float)'
150+
HalfFloat(D); // expected-warning{{implicit conversion loses floating-point precision: 'double' to 'float'}}
151+
}
152+
153+
// Case 5: A function declared with only a double overload.
154+
// (a) When called with half, it will resolve to double because double is a
155+
// valid promotion.
156+
// (b) When called with float it will resolve to double because double is a
157+
// valid promotion.
158+
// (c) When called with double it will resolve to double because it is an
159+
// exact match.
160+
161+
// CHECK: FunctionDecl {{.*}} Case5 'void (half, float, double)'
162+
void Case5(half H, float F, double D) {
163+
// CHECK: CallExpr {{.*}} 'void'
164+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay>
165+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'Double' 'void (double)'
166+
Double(H);
167+
168+
// CHECK: CallExpr {{.*}} 'void'
169+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay>
170+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'Double' 'void (double)'
171+
Double(F);
172+
173+
// CHECK: CallExpr {{.*}} 'void'
174+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay>
175+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'Double' 'void (double)'
176+
Double(D);
177+
}
178+
179+
// Case 6: A function declared with only a float overload.
180+
// (a) When called with half, it will resolve to float because float is a
181+
// valid promotion.
182+
// (b) When called with float it will resolve to float because float is an
183+
// exact match.
184+
// (c) When called with double it will resolve to float because it is a
185+
// valid conversion.
186+
187+
// CHECK: FunctionDecl {{.*}} Case6 'void (half, float, double)'
188+
void Case6(half H, float F, double D) {
189+
// CHECK: CallExpr {{.*}} 'void'
190+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay>
191+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'Float' 'void (float)'
192+
Float(H);
193+
194+
// CHECK: CallExpr {{.*}} 'void'
195+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay>
196+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'Float' 'void (float)'
197+
Float(F);
198+
199+
// CHECK: CallExpr {{.*}} 'void'
200+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay>
201+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'Float' 'void (float)'
202+
Float(D); // expected-warning{{implicit conversion loses floating-point precision: 'double' to 'float'}}
203+
}
204+
205+
// Case 7: A function declared with only a half overload.
206+
// (a) When called with half, it will resolve to half because half is an
207+
// exact match
208+
// (b) When called with float it will resolve to half because half is a
209+
// valid conversion.
210+
// (c) When called with double it will resolve to float because it is a
211+
// valid conversion.
212+
213+
// CHECK: FunctionDecl {{.*}} Case7 'void (half, float, double)'
214+
void Case7(half H, float F, double D) {
215+
// CHECK: CallExpr {{.*}} 'void'
216+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' <FunctionToPointerDecay>
217+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'Half' 'void (half)'
218+
Half(H);
219+
220+
// CHECK: CallExpr {{.*}} 'void'
221+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' <FunctionToPointerDecay>
222+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'Half' 'void (half)'
223+
Half(F); // expected-warning{{implicit conversion loses floating-point precision: 'float' to 'half'}}
224+
225+
// CHECK: CallExpr {{.*}} 'void'
226+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' <FunctionToPointerDecay>
227+
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'Half' 'void (half)'
228+
Half(D); // expected-warning{{implicit conversion loses floating-point precision: 'double' to 'half'}}
229+
}

0 commit comments

Comments
 (0)