-
Notifications
You must be signed in to change notification settings - Fork 12.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[HLSL] Shore up floating point conversions #90222
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a question: are these There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, the ones at the top are just sanity checking and could be removed. The checks for the
farzonl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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)' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, I wasn't relying on any match variables, which is what I usually use |
||
void Case1(half H, float F, double D) { | ||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (half)' | ||
HalfFloatDouble(H); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'HalfFloatDouble' 'void (float)' | ||
HalfFloatDouble(F); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay> | ||
// 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)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'FloatDouble' 'void (float)' | ||
FloatDouble(H); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'FloatDouble' 'void (float)' | ||
FloatDouble(F); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay> | ||
// 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)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'HalfDouble' 'void (half)' | ||
HalfDouble(H); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'HalfDouble' 'void (double)' | ||
HalfDouble(F); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay> | ||
// 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)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'HalfFloat' 'void (half)' | ||
HalfFloat(H); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'HalfFloat' 'void (float)' | ||
HalfFloat(F); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay> | ||
// 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)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'Double' 'void (double)' | ||
Double(H); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (double)' lvalue Function {{.*}} 'Double' 'void (double)' | ||
Double(F); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(double)' <FunctionToPointerDecay> | ||
// 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)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'Float' 'void (float)' | ||
Float(H); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (float)' lvalue Function {{.*}} 'Float' 'void (float)' | ||
Float(F); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float)' <FunctionToPointerDecay> | ||
// 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)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'Half' 'void (half)' | ||
Half(H); | ||
|
||
// CHECK: CallExpr {{.*}} 'void' | ||
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(half)' <FunctionToPointerDecay> | ||
// 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)' <FunctionToPointerDecay> | ||
// CHECK-NEXT: DeclRefExpr {{.*}} 'void (half)' lvalue Function {{.*}} 'Half' 'void (half)' | ||
Half(D); // expected-warning{{implicit conversion loses floating-point precision: 'double' to 'half'}} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears that
half
is represented as LLVM IR typehalf
with-enable-16bit-types
option specified and is promoted tofloat
without it being specified per this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a difference between the IR and language types.
half
is alwayshalf
in the language, which is always a different type formfloat
. This behavior is consistent in earlier language modes and with 202x:https://godbolt.org/z/3bzoocdrG
When you don't pass
-enable-16bit-types
,half
is 32-bit and conforms to the IEEE single-precision fp32 representation (see: #90694).