Skip to content

Commit 02654f7

Browse files
authored
[HLSL][Doc] Document multi-argument resolution (llvm#104474)
This updates the expected diffferences document to capture the difference in multi-argument overload resolution between Clang and DXC. Fixes llvm#99530
1 parent d66765d commit 02654f7

File tree

1 file changed

+109
-12
lines changed

1 file changed

+109
-12
lines changed

clang/docs/HLSL/ExpectedDifferences.rst

Lines changed: 109 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ HLSL 202x based on proposal
5454
and
5555
`0008 <https://github.com/microsoft/hlsl-specs/blob/main/proposals/0008-non-member-operator-overloading.md>`_.
5656

57+
The largest difference between Clang and DXC's overload resolution is the
58+
algorithm used for identifying best-match overloads. There are more details
59+
about the algorithmic differences in the :ref:`multi_argument_overloads` section
60+
below. There are three high level differences that should be highlighted:
61+
62+
* **There should be no cases** where DXC and Clang both successfully
63+
resolve an overload where the resolved overload is different between the two.
64+
* There are cases where Clang will successfully resolve an overload that DXC
65+
wouldn't because we've trimmed the overload set in Clang to remove ambiguity.
66+
* There are cases where DXC will successfully resolve an overload that Clang
67+
will not for two reasons: (1) DXC only generates partial overload sets for
68+
builtin functions and (2) DXC resolves cases that probably should be ambiguous.
69+
5770
Clang's implementation extends standard overload resolution rules to HLSL
5871
library functionality. This causes subtle changes in overload resolution
5972
behavior between Clang and DXC. Some examples include:
@@ -71,18 +84,23 @@ behavior between Clang and DXC. Some examples include:
7184
uint U;
7285
int I;
7386
float X, Y, Z;
74-
double3 A, B;
87+
double3 R, G;
7588
}
7689

77-
void twoParams(int, int);
78-
void twoParams(float, float);
90+
void takesSingleDouble(double);
91+
void takesSingleDouble(vector<double, 1>);
92+
93+
void scalarOrVector(double);
94+
void scalarOrVector(vector<double, 2>);
7995

8096
export void call() {
81-
halfOrInt16(U); // DXC: Fails with call ambiguous between int16_t and uint16_t overloads
82-
// Clang: Resolves to halfOrInt16(uint16_t).
83-
halfOrInt16(I); // All: Resolves to halfOrInt16(int16_t).
8497
half H;
98+
halfOrInt16(I); // All: Resolves to halfOrInt16(int16_t).
99+
85100
#ifndef IGNORE_ERRORS
101+
halfOrInt16(U); // All: Fails with call ambiguous between int16_t and uint16_t
102+
// overloads
103+
86104
// asfloat16 is a builtin with overloads for half, int16_t, and uint16_t.
87105
H = asfloat16(I); // DXC: Fails to resolve overload for int.
88106
// Clang: Resolves to asfloat16(int16_t).
@@ -94,21 +112,28 @@ behavior between Clang and DXC. Some examples include:
94112

95113
takesDoubles(X, Y, Z); // Works on all compilers
96114
#ifndef IGNORE_ERRORS
97-
fma(X, Y, Z); // DXC: Fails to resolve no known conversion from float to double.
115+
fma(X, Y, Z); // DXC: Fails to resolve no known conversion from float to
116+
// double.
98117
// Clang: Resolves to fma(double,double,double).
99-
#endif
100118

101-
double D = dot(A, B); // DXC: Resolves to dot(double3, double3), fails DXIL Validation.
119+
double D = dot(R, G); // DXC: Resolves to dot(double3, double3), fails DXIL Validation.
102120
// FXC: Expands to compute double dot product with fmul/fadd
103-
// Clang: Resolves to dot(float3, float3), emits conversion warnings.
121+
// Clang: Fails to resolve as ambiguous against
122+
// dot(half, half) or dot(float, float)
123+
#endif
104124

105125
#ifndef IGNORE_ERRORS
106126
tan(B); // DXC: resolves to tan(float).
107127
// Clang: Fails to resolve, ambiguous between integer types.
108128

109-
twoParams(I, X); // DXC: resolves twoParams(int, int).
110-
// Clang: Fails to resolve ambiguous conversions.
111129
#endif
130+
131+
double D;
132+
takesSingleDouble(D); // All: Fails to resolve ambiguous conversions.
133+
takesSingleDouble(R); // All: Fails to resolve ambiguous conversions.
134+
135+
scalarOrVector(D); // All: Resolves to scalarOrVector(double).
136+
scalarOrVector(R); // All: Fails to resolve ambiguous conversions.
112137
}
113138

114139
.. note::
@@ -119,3 +144,75 @@ behavior between Clang and DXC. Some examples include:
119144
diagnostic notifying the user of the conversion rather than silently altering
120145
precision relative to the other overloads (as FXC does) or generating code
121146
that will fail validation (as DXC does).
147+
148+
.. _multi_argument_overloads:
149+
150+
Multi-Argument Overloads
151+
------------------------
152+
153+
In addition to the differences in single-element conversions, Clang and DXC
154+
differ dramatically in multi-argument overload resolution. C++ multi-argument
155+
overload resolution behavior (or something very similar) is required to
156+
implement
157+
`non-member operator overloading <https://github.com/microsoft/hlsl-specs/blob/main/proposals/0008-non-member-operator-overloading.md>`_.
158+
159+
Clang adopts the C++ inspired language from the
160+
`draft HLSL specification <https://microsoft.github.io/hlsl-specs/specs/hlsl.pdf>`_,
161+
where an overload ``f1`` is a better candidate than ``f2`` if for all arguments the
162+
conversion sequences is not worse than the corresponding conversion sequence and
163+
for at least one argument it is better.
164+
165+
.. code-block:: c++
166+
167+
cbuffer CB {
168+
int I;
169+
float X;
170+
float4 V;
171+
}
172+
173+
void twoParams(int, int);
174+
void twoParams(float, float);
175+
void threeParams(float, float, float);
176+
void threeParams(float4, float4, float4);
177+
178+
export void call() {
179+
twoParams(I, X); // DXC: resolves twoParams(int, int).
180+
// Clang: Fails to resolve ambiguous conversions.
181+
182+
threeParams(X, V, V); // DXC: resolves threeParams(float4, float4, float4).
183+
// Clang: Fails to resolve ambiguous conversions.
184+
}
185+
186+
For the examples above since ``twoParams`` called with mixed parameters produces
187+
implicit conversion sequences that are { ExactMatch, FloatingIntegral } and {
188+
FloatingIntegral, ExactMatch }. In both cases an argument has a worse conversion
189+
in the other sequence, so the overload is ambiguous.
190+
191+
In the ``threeParams`` example the sequences are { ExactMatch, VectorTruncation,
192+
VectorTruncation } or { VectorSplat, ExactMatch, ExactMatch }, again in both
193+
cases at least one parameter has a worse conversion in the other sequence, so
194+
the overload is ambiguous.
195+
196+
.. note::
197+
198+
The behavior of DXC documented below is undocumented so this is gleaned from
199+
observation and a bit of reading the source.
200+
201+
DXC's approach for determining the best overload produces an integer score value
202+
for each implicit conversion sequence for each argument expression. Scores for
203+
casts are based on a bitmask construction that is complicated to reverse
204+
engineer. It seems that:
205+
206+
* Exact match is 0
207+
* Dimension increase is 1
208+
* Promotion is 2
209+
* Integral -> Float conversion is 4
210+
* Float -> Integral conversion is 8
211+
* Cast is 16
212+
213+
The masks are or'd against each other to produce a score for the cast.
214+
215+
The scores of each conversion sequence are then summed to generate a score for
216+
the overload candidate. The overload candidate with the lowest score is the best
217+
candidate. If more than one overload are matched for the lowest score the call
218+
is ambiguous.

0 commit comments

Comments
 (0)