tint: implement updated spec rules for shift left of concrete values
Rules for shift left of concrete values are now split between signed and unsigned. Shifting unsigned values no longer fails with "sign change" errors. Furthermore, shifting unsigned values must only shift out 0s. See https://github.com/gpuweb/gpuweb/pull/3539. Bug: tint:1701 Bug: tint:1717 Change-Id: Iba2799f4b02cdc77cc58a6c7c104aaa408f0f0f9 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/106381 Reviewed-by: Dan Sinclair <dsinclair@chromium.org> Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Antonio Maiorano <amaiorano@google.com>
This commit is contained in:
parent
3fa6887679
commit
1c94938726
|
@ -1491,14 +1491,26 @@ ConstEval::Result ConstEval::OpShiftLeft(const sem::Type* ty,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The e2 + 1 most significant bits of e1 must have the same bit value, otherwise
|
if constexpr (std::is_signed_v<T>) {
|
||||||
// sign change (overflow) would occur.
|
// If T is a signed integer type, and the e2+1 most significant bits of e1 do
|
||||||
|
// not have the same bit value, then error.
|
||||||
size_t must_match_msb = e2u + 1;
|
size_t must_match_msb = e2u + 1;
|
||||||
UT mask = ~UT{0} << (bit_width - must_match_msb);
|
UT mask = ~UT{0} << (bit_width - must_match_msb);
|
||||||
if ((e1u & mask) != 0 && (e1u & mask) != mask) {
|
if ((e1u & mask) != 0 && (e1u & mask) != mask) {
|
||||||
AddError("shift left operation results in sign change", source);
|
AddError("shift left operation results in sign change", source);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If T is an unsigned integer type, and any of the e2 most significant bits of
|
||||||
|
// e1 are 1, then error.
|
||||||
|
if (e2u > 0) {
|
||||||
|
size_t must_be_zero_msb = e2u;
|
||||||
|
UT mask = ~UT{0} << (bit_width - must_be_zero_msb);
|
||||||
|
if ((e1u & mask) != 0) {
|
||||||
|
AddError(OverflowErrorMessage(e1, "<<", e2), source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid UB by left shifting as unsigned value
|
// Avoid UB by left shifting as unsigned value
|
||||||
|
|
|
@ -621,9 +621,6 @@ std::vector<Case> ShiftLeftCases() {
|
||||||
C(T{0b1010}, ST{7}, T{0b0101'0000'0000}), //
|
C(T{0b1010}, ST{7}, T{0b0101'0000'0000}), //
|
||||||
C(T{0b1010}, ST{8}, T{0b1010'0000'0000}), //
|
C(T{0b1010}, ST{8}, T{0b1010'0000'0000}), //
|
||||||
C(B::LeftMost, ST{0}, B::LeftMost), //
|
C(B::LeftMost, ST{0}, B::LeftMost), //
|
||||||
C(B::TwoLeftMost, ST{1}, B::LeftMost), // No overflow
|
|
||||||
C(B::All, ST{1}, B::AllButRightMost), // No overflow
|
|
||||||
C(B::All, ST{B::NumBits - 1}, B::LeftMost), // No overflow
|
|
||||||
|
|
||||||
C(Vec(T{0b1010}, T{0b1010}), //
|
C(Vec(T{0b1010}, T{0b1010}), //
|
||||||
Vec(ST{0}, ST{1}), //
|
Vec(ST{0}, ST{1}), //
|
||||||
|
@ -641,7 +638,7 @@ std::vector<Case> ShiftLeftCases() {
|
||||||
|
|
||||||
// Only abstract 0 can be shifted left as much as we like. For concrete 0 (and any number), it
|
// Only abstract 0 can be shifted left as much as we like. For concrete 0 (and any number), it
|
||||||
// cannot be shifted equal or more than the number of bits of the lhs (see
|
// cannot be shifted equal or more than the number of bits of the lhs (see
|
||||||
// ResolverConstEvalShiftLeftConcreteGeqBitWidthError)
|
// ResolverConstEvalShiftLeftConcreteGeqBitWidthError for negative tests)
|
||||||
ConcatIntoIf<IsAbstract<T>>( //
|
ConcatIntoIf<IsAbstract<T>>( //
|
||||||
r, std::vector<Case>{
|
r, std::vector<Case>{
|
||||||
C(T{0}, ST{64}, T{0}),
|
C(T{0}, ST{64}, T{0}),
|
||||||
|
@ -656,6 +653,31 @@ std::vector<Case> ShiftLeftCases() {
|
||||||
C(Negate(T{0}), T::Highest(), Negate(T{0})),
|
C(Negate(T{0}), T::Highest(), Negate(T{0})),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cases that are fine for signed values (no sign change), but would overflow unsigned values.
|
||||||
|
// See ResolverConstEvalBinaryOpTest_Overflow for negative tests.
|
||||||
|
ConcatIntoIf<IsSignedIntegral<T>>( //
|
||||||
|
r, std::vector<Case>{
|
||||||
|
C(B::TwoLeftMost, ST{1}, B::LeftMost), //
|
||||||
|
C(B::All, ST{1}, B::AllButRightMost), //
|
||||||
|
C(B::All, ST{B::NumBits - 1}, B::LeftMost) //
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cases that are fine for unsigned values, but would overflow (sign change) signed
|
||||||
|
// values. See ShiftLeftSignChangeErrorCases() for negative tests.
|
||||||
|
ConcatIntoIf<IsUnsignedIntegral<T>>( //
|
||||||
|
r, std::vector<Case>{
|
||||||
|
C(T{0b0001}, ST{B::NumBits - 1}, B::Lsh(0b0001, B::NumBits - 1)),
|
||||||
|
C(T{0b0010}, ST{B::NumBits - 2}, B::Lsh(0b0010, B::NumBits - 2)),
|
||||||
|
C(T{0b0100}, ST{B::NumBits - 3}, B::Lsh(0b0100, B::NumBits - 3)),
|
||||||
|
C(T{0b1000}, ST{B::NumBits - 4}, B::Lsh(0b1000, B::NumBits - 4)),
|
||||||
|
|
||||||
|
C(T{0b0011}, ST{B::NumBits - 2}, B::Lsh(0b0011, B::NumBits - 2)),
|
||||||
|
C(T{0b0110}, ST{B::NumBits - 3}, B::Lsh(0b0110, B::NumBits - 3)),
|
||||||
|
C(T{0b1100}, ST{B::NumBits - 4}, B::Lsh(0b1100, B::NumBits - 4)),
|
||||||
|
|
||||||
|
C(B::AllButLeftMost, ST{1}, B::AllButRightMost),
|
||||||
|
});
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
INSTANTIATE_TEST_SUITE_P(ShiftLeft,
|
INSTANTIATE_TEST_SUITE_P(ShiftLeft,
|
||||||
|
@ -795,7 +817,21 @@ INSTANTIATE_TEST_SUITE_P(
|
||||||
Val(AInt{BitValues<AInt>::NumBits + 1})}, //
|
Val(AInt{BitValues<AInt>::NumBits + 1})}, //
|
||||||
OverflowCase{ast::BinaryOp::kShiftLeft, //
|
OverflowCase{ast::BinaryOp::kShiftLeft, //
|
||||||
Val(AInt{BitValues<AInt>::AllButLeftMost}), //
|
Val(AInt{BitValues<AInt>::AllButLeftMost}), //
|
||||||
Val(AInt{BitValues<AInt>::NumBits + 1000})}
|
Val(AInt{BitValues<AInt>::NumBits + 1000})},
|
||||||
|
|
||||||
|
// ShiftLeft of u32s that overflow (non-zero bits get shifted out)
|
||||||
|
OverflowCase{ast::BinaryOp::kShiftLeft, Val(0b00010_u), Val(31_u)},
|
||||||
|
OverflowCase{ast::BinaryOp::kShiftLeft, Val(0b00100_u), Val(30_u)},
|
||||||
|
OverflowCase{ast::BinaryOp::kShiftLeft, Val(0b01000_u), Val(29_u)},
|
||||||
|
OverflowCase{ast::BinaryOp::kShiftLeft, Val(0b10000_u), Val(28_u)},
|
||||||
|
// ...
|
||||||
|
OverflowCase{ast::BinaryOp::kShiftLeft, Val(u32(1u << 28)), Val(4_u)},
|
||||||
|
OverflowCase{ast::BinaryOp::kShiftLeft, Val(u32(1u << 29)), Val(3_u)},
|
||||||
|
OverflowCase{ast::BinaryOp::kShiftLeft, Val(u32(1u << 30)), Val(2_u)},
|
||||||
|
OverflowCase{ast::BinaryOp::kShiftLeft, Val(u32(1u << 31)), Val(1_u)},
|
||||||
|
// And some more
|
||||||
|
OverflowCase{ast::BinaryOp::kShiftLeft, Val(BitValues<u32>::All), Val(1_u)},
|
||||||
|
OverflowCase{ast::BinaryOp::kShiftLeft, Val(BitValues<u32>::AllButLeftMost), Val(2_u)}
|
||||||
|
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -945,8 +981,7 @@ INSTANTIATE_TEST_SUITE_P(Test,
|
||||||
ResolverConstEvalShiftLeftSignChangeError,
|
ResolverConstEvalShiftLeftSignChangeError,
|
||||||
testing::ValuesIn(Concat( //
|
testing::ValuesIn(Concat( //
|
||||||
ShiftLeftSignChangeErrorCases<AInt>(),
|
ShiftLeftSignChangeErrorCases<AInt>(),
|
||||||
ShiftLeftSignChangeErrorCases<i32>(),
|
ShiftLeftSignChangeErrorCases<i32>())));
|
||||||
ShiftLeftSignChangeErrorCases<u32>())));
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace tint::resolver
|
} // namespace tint::resolver
|
||||||
|
|
|
@ -281,7 +281,7 @@ struct BitValues {
|
||||||
/// @returns the shifted value
|
/// @returns the shifted value
|
||||||
template <typename U, typename V>
|
template <typename U, typename V>
|
||||||
static constexpr NumberT Lsh(U val, V shiftBy) {
|
static constexpr NumberT Lsh(U val, V shiftBy) {
|
||||||
return NumberT{T{val} << T{shiftBy}};
|
return NumberT{static_cast<T>(val) << static_cast<T>(shiftBy)};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn f() -> u32 {
|
||||||
|
return 0x1u << 31u;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
[numthreads(1, 1, 1)]
|
||||||
|
void unused_entry_point() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint f() {
|
||||||
|
return 2147483648u;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
[numthreads(1, 1, 1)]
|
||||||
|
void unused_entry_point() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint f() {
|
||||||
|
return 2147483648u;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
#version 310 es
|
||||||
|
|
||||||
|
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
|
||||||
|
void unused_entry_point() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint f() {
|
||||||
|
return 2147483648u;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
#include <metal_stdlib>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
|
uint f() {
|
||||||
|
return 2147483648u;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
; SPIR-V
|
||||||
|
; Version: 1.3
|
||||||
|
; Generator: Google Tint Compiler; 0
|
||||||
|
; Bound: 10
|
||||||
|
; Schema: 0
|
||||||
|
OpCapability Shader
|
||||||
|
OpMemoryModel Logical GLSL450
|
||||||
|
OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
|
||||||
|
OpExecutionMode %unused_entry_point LocalSize 1 1 1
|
||||||
|
OpName %unused_entry_point "unused_entry_point"
|
||||||
|
OpName %f "f"
|
||||||
|
%void = OpTypeVoid
|
||||||
|
%1 = OpTypeFunction %void
|
||||||
|
%uint = OpTypeInt 32 0
|
||||||
|
%5 = OpTypeFunction %uint
|
||||||
|
%uint_2147483648 = OpConstant %uint 2147483648
|
||||||
|
%unused_entry_point = OpFunction %void None %1
|
||||||
|
%4 = OpLabel
|
||||||
|
OpReturn
|
||||||
|
OpFunctionEnd
|
||||||
|
%f = OpFunction %uint None %5
|
||||||
|
%8 = OpLabel
|
||||||
|
OpReturnValue %uint_2147483648
|
||||||
|
OpFunctionEnd
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn f() -> u32 {
|
||||||
|
return (1u << 31u);
|
||||||
|
}
|
Loading…
Reference in New Issue