[OPT] Zero-extend unsigned 16-bit integers when bitcasting (#5714)
The folding rule `BitCastScalarOrVector` was incorrectly handling
bitcasting to unsigned integers smaller than 32-bits. It was simply
copying the entire 32-bit word containing the integer. This conflicts with the
requirement in section 2.2.1 of the SPIR-V spec which states that
unsigned numeric types with a bit width less than 32-bits must have the
high-order bits set to 0.
This change include a refactor of the bit extension code to be able to
test it better, and to use it in multiple files.
Fixes https://github.com/microsoft/DirectXShaderCompiler/issues/6319.
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index 17900af..a5d4cbe 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -21,59 +21,6 @@
namespace {
constexpr uint32_t kExtractCompositeIdInIdx = 0;
-// Returns the value obtained by extracting the |number_of_bits| least
-// significant bits from |value|, and sign-extending it to 64-bits.
-uint64_t SignExtendValue(uint64_t value, uint32_t number_of_bits) {
- if (number_of_bits == 64) return value;
-
- uint64_t mask_for_sign_bit = 1ull << (number_of_bits - 1);
- uint64_t mask_for_significant_bits = (mask_for_sign_bit << 1) - 1ull;
- if (value & mask_for_sign_bit) {
- // Set upper bits to 1
- value |= ~mask_for_significant_bits;
- } else {
- // Clear the upper bits
- value &= mask_for_significant_bits;
- }
- return value;
-}
-
-// Returns the value obtained by extracting the |number_of_bits| least
-// significant bits from |value|, and zero-extending it to 64-bits.
-uint64_t ZeroExtendValue(uint64_t value, uint32_t number_of_bits) {
- if (number_of_bits == 64) return value;
-
- uint64_t mask_for_first_bit_to_clear = 1ull << (number_of_bits);
- uint64_t mask_for_bits_to_keep = mask_for_first_bit_to_clear - 1;
- value &= mask_for_bits_to_keep;
- return value;
-}
-
-// Returns a constant whose value is `value` and type is `type`. This constant
-// will be generated by `const_mgr`. The type must be a scalar integer type.
-const analysis::Constant* GenerateIntegerConstant(
- const analysis::Integer* integer_type, uint64_t result,
- analysis::ConstantManager* const_mgr) {
- assert(integer_type != nullptr);
-
- std::vector<uint32_t> words;
- if (integer_type->width() == 64) {
- // In the 64-bit case, two words are needed to represent the value.
- words = {static_cast<uint32_t>(result),
- static_cast<uint32_t>(result >> 32)};
- } else {
- // In all other cases, only a single word is needed.
- assert(integer_type->width() <= 32);
- if (integer_type->IsSigned()) {
- result = SignExtendValue(result, integer_type->width());
- } else {
- result = ZeroExtendValue(result, integer_type->width());
- }
- words = {static_cast<uint32_t>(result)};
- }
- return const_mgr->GetConstant(integer_type, words);
-}
-
// Returns a constants with the value NaN of the given type. Only works for
// 32-bit and 64-bit float point types. Returns |nullptr| if an error occurs.
const analysis::Constant* GetNan(const analysis::Type* type,
@@ -1730,7 +1677,7 @@
uint64_t result = op(ia, ib);
const analysis::Constant* result_constant =
- GenerateIntegerConstant(integer_type, result, const_mgr);
+ const_mgr->GenerateIntegerConstant(integer_type, result);
return result_constant;
};
}
@@ -1745,7 +1692,7 @@
const analysis::Integer* integer_type = result_type->AsInteger();
assert(integer_type && "The result type of an SConvert");
int64_t value = a->GetSignExtendedValue();
- return GenerateIntegerConstant(integer_type, value, const_mgr);
+ return const_mgr->GenerateIntegerConstant(integer_type, value);
}
// A scalar folding rule that folds OpUConvert.
@@ -1762,8 +1709,8 @@
// If the operand was an unsigned value with less than 32-bit, it would have
// been sign extended earlier, and we need to clear those bits.
auto* operand_type = a->type()->AsInteger();
- value = ZeroExtendValue(value, operand_type->width());
- return GenerateIntegerConstant(integer_type, value, const_mgr);
+ value = utils::ClearHighBits(value, 64 - operand_type->width());
+ return const_mgr->GenerateIntegerConstant(integer_type, value);
}
} // namespace
diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp
index 6eebbb5..7dc02de 100644
--- a/source/opt/constants.cpp
+++ b/source/opt/constants.cpp
@@ -525,6 +525,28 @@
return GetDefiningInstruction(c)->result_id();
}
+const Constant* ConstantManager::GenerateIntegerConstant(
+ const analysis::Integer* integer_type, uint64_t result) {
+ assert(integer_type != nullptr);
+
+ std::vector<uint32_t> words;
+ if (integer_type->width() == 64) {
+ // In the 64-bit case, two words are needed to represent the value.
+ words = {static_cast<uint32_t>(result),
+ static_cast<uint32_t>(result >> 32)};
+ } else {
+ // In all other cases, only a single word is needed.
+ assert(integer_type->width() <= 32);
+ if (integer_type->IsSigned()) {
+ result = utils::SignExtendValue(result, integer_type->width());
+ } else {
+ result = utils::ZeroExtendValue(result, integer_type->width());
+ }
+ words = {static_cast<uint32_t>(result)};
+ }
+ return GetConstant(integer_type, words);
+}
+
std::vector<const analysis::Constant*> Constant::GetVectorComponents(
analysis::ConstantManager* const_mgr) const {
std::vector<const analysis::Constant*> components;
diff --git a/source/opt/constants.h b/source/opt/constants.h
index ae8dc62..534afa6 100644
--- a/source/opt/constants.h
+++ b/source/opt/constants.h
@@ -671,6 +671,11 @@
// Returns the id of a OpConstantNull with type of |type|.
uint32_t GetNullConstId(const Type* type);
+ // Returns a constant whose value is `value` and type is `type`. This constant
+ // will be generated by `const_mgr`. The type must be a scalar integer type.
+ const Constant* GenerateIntegerConstant(const analysis::Integer* integer_type,
+ uint64_t result);
+
private:
// Creates a Constant instance with the given type and a vector of constant
// defining words. Returns a unique pointer to the created Constant instance
diff --git a/source/opt/fold_spec_constant_op_and_composite_pass.cpp b/source/opt/fold_spec_constant_op_and_composite_pass.cpp
index c568027..ddfe59f 100644
--- a/source/opt/fold_spec_constant_op_and_composite_pass.cpp
+++ b/source/opt/fold_spec_constant_op_and_composite_pass.cpp
@@ -247,18 +247,7 @@
// Truncate first_word if the |type| has width less than uint32.
if (bit_width < bits_per_word) {
- const uint32_t num_high_bits_to_mask = bits_per_word - bit_width;
- const bool is_negative_after_truncation =
- result_type_signed &&
- utils::IsBitAtPositionSet(first_word, bit_width - 1);
-
- if (is_negative_after_truncation) {
- // Truncate and sign-extend |first_word|. No padding words will be
- // added and |pad_value| can be left as-is.
- first_word = utils::SetHighBits(first_word, num_high_bits_to_mask);
- } else {
- first_word = utils::ClearHighBits(first_word, num_high_bits_to_mask);
- }
+ first_word = utils::SignExtendValue(first_word, bit_width);
}
utils::SmallVector<uint32_t, 2> words = {first_word};
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index 2497967..6def9c4 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -180,8 +180,14 @@
const analysis::Constant* ConvertWordsToNumericScalarOrVectorConstant(
analysis::ConstantManager* const_mgr, const std::vector<uint32_t>& words,
const analysis::Type* type) {
- if (type->AsInteger() || type->AsFloat())
- return const_mgr->GetConstant(type, words);
+ const spvtools::opt::analysis::Integer* int_type = type->AsInteger();
+
+ if (int_type && int_type->width() <= 32) {
+ assert(words.size() == 1);
+ return const_mgr->GenerateIntegerConstant(int_type, words[0]);
+ }
+
+ if (int_type || type->AsFloat()) return const_mgr->GetConstant(type, words);
if (const auto* vec_type = type->AsVector())
return const_mgr->GetNumericVectorConstantWithWords(vec_type, words);
return nullptr;
diff --git a/source/util/bitutils.h b/source/util/bitutils.h
index 9ced2f9..a121dc3 100644
--- a/source/util/bitutils.h
+++ b/source/util/bitutils.h
@@ -181,6 +181,31 @@
false);
}
+// Returns the value obtained by extracting the |number_of_bits| least
+// significant bits from |value|, and sign-extending it to 64-bits.
+template <typename T>
+T SignExtendValue(T value, uint32_t number_of_bits) {
+ const uint32_t bit_width = sizeof(value) * 8;
+ if (number_of_bits == bit_width) return value;
+
+ bool is_negative = utils::IsBitAtPositionSet(value, number_of_bits - 1);
+ if (is_negative) {
+ value = utils::SetHighBits(value, bit_width - number_of_bits);
+ } else {
+ value = utils::ClearHighBits(value, bit_width - number_of_bits);
+ }
+ return value;
+}
+
+// Returns the value obtained by extracting the |number_of_bits| least
+// significant bits from |value|, and zero-extending it to 64-bits.
+template <typename T>
+T ZeroExtendValue(T value, uint32_t number_of_bits) {
+ const uint32_t bit_width = sizeof(value) * 8;
+ if (number_of_bits == bit_width) return value;
+ return utils::ClearHighBits(value, bit_width - number_of_bits);
+}
+
} // namespace utils
} // namespace spvtools
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index 35828ab..cb14b94 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -924,7 +924,7 @@
"%2 = OpBitcast %ushort %short_0xBC00\n" +
"OpReturn\n" +
"OpFunctionEnd",
- 2, 0xFFFFBC00),
+ 2, 0xBC00),
// Test case 53: Bit-cast half 1 to ushort
InstructionFoldingCase<uint32_t>(
Header() + "%main = OpFunction %void None %void_func\n" +
@@ -940,7 +940,7 @@
"%2 = OpBitcast %short %ushort_0xBC00\n" +
"OpReturn\n" +
"OpFunctionEnd",
- 2, 0xBC00),
+ 2, 0xFFFFBC00),
// Test case 55: Bit-cast short 0xBC00 to short
InstructionFoldingCase<uint32_t>(
Header() + "%main = OpFunction %void None %void_func\n" +
@@ -996,7 +996,7 @@
"%2 = OpBitcast %ubyte %byte_n1\n" +
"OpReturn\n" +
"OpFunctionEnd",
- 2, 0xFFFFFFFF),
+ 2, 0xFF),
// Test case 62: Negate 2.
InstructionFoldingCase<uint32_t>(
Header() + "%main = OpFunction %void None %void_func\n" +
diff --git a/test/util/bitutils_test.cpp b/test/util/bitutils_test.cpp
index 3be7ed2..aea7897 100644
--- a/test/util/bitutils_test.cpp
+++ b/test/util/bitutils_test.cpp
@@ -188,6 +188,46 @@
EXPECT_TRUE(IsBitAtPositionSet(max_u64, i));
}
}
+
+struct ExtendedValueTestCase {
+ uint32_t input;
+ uint32_t bit_width;
+ uint32_t expected_result;
+};
+
+using SignExtendedValueTest = ::testing::TestWithParam<ExtendedValueTestCase>;
+
+TEST_P(SignExtendedValueTest, SignExtendValue) {
+ const auto& tc = GetParam();
+ auto result = SignExtendValue(tc.input, tc.bit_width);
+ EXPECT_EQ(result, tc.expected_result);
+}
+INSTANTIATE_TEST_SUITE_P(
+ SignExtendValue, SignExtendedValueTest,
+ ::testing::Values(ExtendedValueTestCase{1, 1, 0xFFFFFFFF},
+ ExtendedValueTestCase{1, 2, 0x1},
+ ExtendedValueTestCase{2, 1, 0x0},
+ ExtendedValueTestCase{0x8, 4, 0xFFFFFFF8},
+ ExtendedValueTestCase{0x8765, 16, 0xFFFF8765},
+ ExtendedValueTestCase{0x7765, 16, 0x7765},
+ ExtendedValueTestCase{0xDEADBEEF, 32, 0xDEADBEEF}));
+
+using ZeroExtendedValueTest = ::testing::TestWithParam<ExtendedValueTestCase>;
+
+TEST_P(ZeroExtendedValueTest, ZeroExtendValue) {
+ const auto& tc = GetParam();
+ auto result = ZeroExtendValue(tc.input, tc.bit_width);
+ EXPECT_EQ(result, tc.expected_result);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ZeroExtendValue, ZeroExtendedValueTest,
+ ::testing::Values(ExtendedValueTestCase{1, 1, 0x1},
+ ExtendedValueTestCase{1, 2, 0x1},
+ ExtendedValueTestCase{2, 1, 0x0},
+ ExtendedValueTestCase{0x8, 4, 0x8},
+ ExtendedValueTestCase{0xFF8765, 16, 0x8765},
+ ExtendedValueTestCase{0xDEADBEEF, 32, 0xDEADBEEF}));
} // namespace
} // namespace utils
} // namespace spvtools