[SPIR-V] Incorrect float->int->uint conversion behavior
Description
The compiler generates a float->uint conversion (OpConvertFToU instruction) when trying to convert float->int->uint (which should result in OpConvertFToS followed by OpBitcast) - since OpConvertFToU is as far as I can tell not defined for negative float values (and hardware seems to convert negative floats to 0u), the two operations are not equivalent, and the behavior is different than expected for negative values.
Steps to Reproduce
struct Data {
float4 x;
uint4 y;
};
RWStructuredBuffer<Data> buffer;
[numthreads(1, 1, 1)]
void main()
{
buffer[0].y = uint4(buffer[0].x); // A
buffer[1].y = uint4(int4(buffer[1].x)); // B - Generates same code as A, but shouldn't
buffer[2].y = int4(buffer[2].x); // C - The implicit cast here seems to generate the correct code
}
(https://godbolt.org/z/1sb68E8b1)
Environment All DXC versions currently available via godbolt.org
@llvm-beanz - it might be interesting to see what the language spec says about this.
For the SPIR-V codegen side, seems like there is a slight mismatch between how we handle implicit casts (first is an IntegralCast), vs the explicit cast from float -> int -> uint.
Waiting on MS answer on what the spec says before fixing one side or the other.
I'm not too concerned about the spec in this case. It would be unreasonable that the cast to int could be elided with the case to uint making it undefined. The AST has all of the correct casts, and the spir-v should reflect that.
Looks like this issue is caused by the flatten feature of the InitListHandler code.
And turns out, behavior is different between uint r = uint(int(my_float)) and uint4 r = uint4(int4(my_float4)).
The reason is when we use non-vector types, an actual FloatingToIntegral cast is added at the top of the InitializerList subtree, forcing the float-to-int cast, then doing the implicit int-to-uint cast.
But with the vector type, this explicit cast is not added, meaning we only have 2 subsequence float->int->uint casts through InitListHandlers.
So InitListHandler tries to be clever: it takes a subtree with several subsequent InitListExpr, and directly jumps from type A to type N, without going through intermediate casts.
I need to remove this bit to avoid the issue we see here.
The spec for HLSL aligns with C/C++. See [Conv.iconv]:
If the destination type is unsigned, integer conversion maintains the bit pattern of the source value in the destination type truncating or extending the value to the destination type.
As @s-perron said, the SPIR-V emitter is skipping casts that are present in the AST, and following the spec, this should be a float->int->uint conversion sequence not a float->uint conversion.