From 9e3fadf6f11380031486be704d2ff46ca143664f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Sat, 15 Aug 2026 05:36:08 +0000 Subject: [PATCH] Fixed floatFlip, SIMD variable-shift masks and %g precision, and added tests. (#417) --- include/bx/inline/math.inl | 3 +- include/bx/inline/simd_ni.inl | 72 +++++++++----------------- scripts/genie.lua | 1 + src/string.cpp | 10 ++-- tests/math_test.cpp | 37 +++++++++++++ tests/settings_test.cpp | 56 ++++++++++---------- tests/simd_test.cpp | 97 +++++++++++++++++++++++++++++++++++ 7 files changed, 196 insertions(+), 80 deletions(-) diff --git a/include/bx/inline/math.inl b/include/bx/inline/math.inl index 4c913a7..7ab877f 100644 --- a/include/bx/inline/math.inl +++ b/include/bx/inline/math.inl @@ -67,8 +67,7 @@ namespace bx const simd32_t signMask = simd32_splat(kFloatSignMask); const simd32_t value = simd32_splat(_value); const simd32_t tmp0 = simd32_x32_sra(value, 31); - const simd32_t tmp1 = simd32_i32_neg(tmp0); - const simd32_t mask = simd32_or(tmp1, signMask); + const simd32_t mask = simd32_or(tmp0, signMask); const simd32_t result = simd32_xor(value, mask); return result.u32; diff --git a/include/bx/inline/simd_ni.inl b/include/bx/inline/simd_ni.inl index 651173e..e22fec9 100644 --- a/include/bx/inline/simd_ni.inl +++ b/include/bx/inline/simd_ni.inl @@ -801,37 +801,35 @@ namespace bx return result; } + template + BX_SIMD_INLINE Ty simd_x32_bit_mask_ni(Ty _count) + { + const Ty shifted = simd_x32_sll(_count, 31-BitT); + const Ty result = simd_x32_sra(shifted, 31); + + return result; + } + template BX_SIMD_INLINE Ty simd_x32_srl_ni(Ty _a, Ty _count) { - const Ty bit0 = simd_splat(uint32_t(1) ); - const Ty bit1 = simd_splat(uint32_t(2) ); - const Ty bit2 = simd_splat(uint32_t(4) ); - const Ty bit3 = simd_splat(uint32_t(8) ); - const Ty bit4 = simd_splat(uint32_t(16) ); - - const Ty b0 = simd_and(_count, bit0); - const Ty mask0 = simd_i32_neg(b0); + const Ty mask0 = simd_x32_bit_mask_ni<0, Ty>(_count); const Ty shr0 = simd_x32_srl(_a, 1); const Ty r0 = simd_selb(mask0, shr0, _a); - const Ty b1 = simd_and(_count, bit1); - const Ty mask1 = simd_i32_neg(b1); + const Ty mask1 = simd_x32_bit_mask_ni<1, Ty>(_count); const Ty shr1 = simd_x32_srl(r0, 2); const Ty r1 = simd_selb(mask1, shr1, r0); - const Ty b2 = simd_and(_count, bit2); - const Ty mask2 = simd_i32_neg(b2); + const Ty mask2 = simd_x32_bit_mask_ni<2, Ty>(_count); const Ty shr2 = simd_x32_srl(r1, 4); const Ty r2 = simd_selb(mask2, shr2, r1); - const Ty b3 = simd_and(_count, bit3); - const Ty mask3 = simd_i32_neg(b3); + const Ty mask3 = simd_x32_bit_mask_ni<3, Ty>(_count); const Ty shr3 = simd_x32_srl(r2, 8); const Ty r3 = simd_selb(mask3, shr3, r2); - const Ty b4 = simd_and(_count, bit4); - const Ty mask4 = simd_i32_neg(b4); + const Ty mask4 = simd_x32_bit_mask_ni<4, Ty>(_count); const Ty shr4 = simd_x32_srl(r3, 16); const Ty result = simd_selb(mask4, shr4, r3); @@ -841,34 +839,23 @@ namespace bx template BX_SIMD_INLINE Ty simd_x32_sll_ni(Ty _a, Ty _count) { - const Ty bit0 = simd_splat(uint32_t(1) ); - const Ty bit1 = simd_splat(uint32_t(2) ); - const Ty bit2 = simd_splat(uint32_t(4) ); - const Ty bit3 = simd_splat(uint32_t(8) ); - const Ty bit4 = simd_splat(uint32_t(16) ); - - const Ty b0 = simd_and(_count, bit0); - const Ty mask0 = simd_i32_neg(b0); + const Ty mask0 = simd_x32_bit_mask_ni<0, Ty>(_count); const Ty sll0 = simd_x32_sll(_a, 1); const Ty r0 = simd_selb(mask0, sll0, _a); - const Ty b1 = simd_and(_count, bit1); - const Ty mask1 = simd_i32_neg(b1); + const Ty mask1 = simd_x32_bit_mask_ni<1, Ty>(_count); const Ty sll1 = simd_x32_sll(r0, 2); const Ty r1 = simd_selb(mask1, sll1, r0); - const Ty b2 = simd_and(_count, bit2); - const Ty mask2 = simd_i32_neg(b2); + const Ty mask2 = simd_x32_bit_mask_ni<2, Ty>(_count); const Ty sll2 = simd_x32_sll(r1, 4); const Ty r2 = simd_selb(mask2, sll2, r1); - const Ty b3 = simd_and(_count, bit3); - const Ty mask3 = simd_i32_neg(b3); + const Ty mask3 = simd_x32_bit_mask_ni<3, Ty>(_count); const Ty sll3 = simd_x32_sll(r2, 8); const Ty r3 = simd_selb(mask3, sll3, r2); - const Ty b4 = simd_and(_count, bit4); - const Ty mask4 = simd_i32_neg(b4); + const Ty mask4 = simd_x32_bit_mask_ni<4, Ty>(_count); const Ty sll4 = simd_x32_sll(r3, 16); const Ty result = simd_selb(mask4, sll4, r3); @@ -878,34 +865,23 @@ namespace bx template BX_SIMD_INLINE Ty simd_x32_sra_ni(Ty _a, Ty _count) { - const Ty bit0 = simd_splat(uint32_t(1) ); - const Ty bit1 = simd_splat(uint32_t(2) ); - const Ty bit2 = simd_splat(uint32_t(4) ); - const Ty bit3 = simd_splat(uint32_t(8) ); - const Ty bit4 = simd_splat(uint32_t(16) ); - - const Ty b0 = simd_and(_count, bit0); - const Ty mask0 = simd_i32_neg(b0); + const Ty mask0 = simd_x32_bit_mask_ni<0, Ty>(_count); const Ty sra0 = simd_x32_sra(_a, 1); const Ty r0 = simd_selb(mask0, sra0, _a); - const Ty b1 = simd_and(_count, bit1); - const Ty mask1 = simd_i32_neg(b1); + const Ty mask1 = simd_x32_bit_mask_ni<1, Ty>(_count); const Ty sra1 = simd_x32_sra(r0, 2); const Ty r1 = simd_selb(mask1, sra1, r0); - const Ty b2 = simd_and(_count, bit2); - const Ty mask2 = simd_i32_neg(b2); + const Ty mask2 = simd_x32_bit_mask_ni<2, Ty>(_count); const Ty sra2 = simd_x32_sra(r1, 4); const Ty r2 = simd_selb(mask2, sra2, r1); - const Ty b3 = simd_and(_count, bit3); - const Ty mask3 = simd_i32_neg(b3); + const Ty mask3 = simd_x32_bit_mask_ni<3, Ty>(_count); const Ty sra3 = simd_x32_sra(r2, 8); const Ty r3 = simd_selb(mask3, sra3, r2); - const Ty b4 = simd_and(_count, bit4); - const Ty mask4 = simd_i32_neg(b4); + const Ty mask4 = simd_x32_bit_mask_ni<4, Ty>(_count); const Ty sra4 = simd_x32_sra(r3, 16); const Ty result = simd_selb(mask4, sra4, r3); diff --git a/scripts/genie.lua b/scripts/genie.lua index 5ac999a..3e11df5 100644 --- a/scripts/genie.lua +++ b/scripts/genie.lua @@ -60,6 +60,7 @@ project "bx.test" files { path.join(BX_DIR, "3rdparty/catch/catch_amalgamated.cpp"), path.join(BX_DIR, "tests/*_test.cpp"), + path.join(BX_DIR, "tests/*-test.cpp"), path.join(BX_DIR, "tests/*.h"), path.join(BX_DIR, "tests/dbg.*"), } diff --git a/src/string.cpp b/src/string.cpp index 4380735..a4a290c 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -936,11 +936,13 @@ namespace bx char* fracBegin = &str[dot - str + min(prec + _param.spec, 1)]; const int32_t curPrec = int32_t(fracEnd - fracBegin); + const int32_t outPrec = 'g' == _param.fmt ? min(prec, curPrec) : prec; + // Move exponent to its final location after trimming or adding extra 0s. if (fracEnd != strEnd) { const int32_t exponentLen = int32_t(strEnd - fracEnd); - char* finalExponentPtr = &fracBegin[prec]; + char* finalExponentPtr = &fracBegin[outPrec]; memMove(finalExponentPtr, fracEnd, exponentLen); finalExponentPtr[exponentLen] = '\0'; @@ -948,12 +950,12 @@ namespace bx } else { - len = (int32_t)(fracBegin + prec - str); + len = (int32_t)(fracBegin + outPrec - str); } - if (curPrec < prec) + if (curPrec < outPrec) { - for (int32_t ii = curPrec; ii < prec; ++ii) + for (int32_t ii = curPrec; ii < outPrec; ++ii) { fracBegin[ii] = '0'; } diff --git a/tests/math_test.cpp b/tests/math_test.cpp index 8154b91..bc8c449 100644 --- a/tests/math_test.cpp +++ b/tests/math_test.cpp @@ -861,6 +861,43 @@ TEST_CASE("bitsToFloat, floatToBits, bitsToDouble, doubleToBits", "[math]") STATIC_REQUIRE(0x123456789abcdef0ull == bx::doubleToBits(bx::bitsToDouble(0x123456789abcdef0ull) ) ); } +TEST_CASE("floatFlip", "[math]") +{ + const float values[] = + { + bx::LimitsT::min, + -1000.0f, + -30.0f, + -10.0f, + -1.0f, + -0.5f, + -0.0f, + 0.0f, + 0.5f, + 1.0f, + 10.0f, + 30.0f, + 1000.0f, + bx::LimitsT::max, + }; + + for (uint32_t ii = 1; ii < BX_COUNTOF(values); ++ii) + { + const uint32_t prev = bx::floatFlip(bx::floatToBits(values[ii-1]) ); + const uint32_t curr = bx::floatFlip(bx::floatToBits(values[ii ]) ); + + INFO("values[" << ii-1 << "] = " << values[ii-1] << " -> " << prev); + INFO("values[" << ii << "] = " << values[ii ] << " -> " << curr); + + REQUIRE(prev <= curr); + } + + REQUIRE(bx::floatFlip(bx::floatToBits(-0.0f) ) < bx::floatFlip(bx::floatToBits(0.0f) ) ); + + REQUIRE(bx::floatFlip(bx::floatToBits( 1.0f) ) != bx::floatFlip(bx::floatToBits(bx::bitsToFloat(bx::floatToBits( 1.0f)+1) ) ) ); + REQUIRE(bx::floatFlip(bx::floatToBits(-1.0f) ) != bx::floatFlip(bx::floatToBits(bx::bitsToFloat(bx::floatToBits(-1.0f)+1) ) ) ); +} + TEST_CASE("lerp", "[math]") { STATIC_REQUIRE(1389.0f == bx::lerp(1389.0f, 1453.0f, 0.0f) ); diff --git a/tests/settings_test.cpp b/tests/settings_test.cpp index be964e6..f7775c4 100644 --- a/tests/settings_test.cpp +++ b/tests/settings_test.cpp @@ -7,39 +7,43 @@ #include #include -class CountingAllocator : public bx::AllocatorI +namespace { -public: - CountingAllocator() - : m_live(0) + class CountingAllocator : public bx::AllocatorI { - } - - virtual ~CountingAllocator() - { - } - - virtual void* realloc(void* _ptr, size_t _size, size_t _align, const char* _filePath, uint32_t _line) override - { - if (NULL == _ptr - && 0 != _size) + public: + CountingAllocator() + : m_live(0) { - ++m_live; - } - else if (NULL != _ptr - && 0 == _size) - { - --m_live; } - return m_allocator.realloc(_ptr, _size, _align, _filePath, _line); - } + virtual ~CountingAllocator() + { + } - int32_t m_live; + virtual void* realloc(void* _ptr, size_t _size, size_t _align, const char* _filePath, uint32_t _line) override + { + if (NULL == _ptr + && 0 != _size) + { + ++m_live; + } + else if (NULL != _ptr + && 0 == _size) + { + --m_live; + } -private: - bx::DefaultAllocator m_allocator; -}; + return m_allocator.realloc(_ptr, _size, _align, _filePath, _line); + } + + int32_t m_live; + + private: + bx::DefaultAllocator m_allocator; + }; + +} // namespace TEST_CASE("Settings", "") { diff --git a/tests/simd_test.cpp b/tests/simd_test.cpp index 930c2e1..3bdcebd 100644 --- a/tests/simd_test.cpp +++ b/tests/simd_test.cpp @@ -773,6 +773,103 @@ TEST_CASE("simd128_x32_sra", "[simd]") check_i32("sra", simd128_x32_sra(a, 2), -2, -4, 8, 16); } +TEST_CASE("simd_x32_bit_mask_ni", "[simd]") +{ + static constexpr uint32_t ones = UINT32_MAX; + + SECTION("each bit is tested independently") + { + const simd128_t bit0 = simd128_ld( 0u, 1u, 2u, 3u); + check_u32("bit0", simd_x32_bit_mask_ni<0, simd128_t>(bit0), 0, ones, 0, ones); + + const simd128_t bit1 = simd128_ld( 0u, 2u, 1u, 3u); + check_u32("bit1", simd_x32_bit_mask_ni<1, simd128_t>(bit1), 0, ones, 0, ones); + + const simd128_t bit2 = simd128_ld( 0u, 4u, 3u, 7u); + check_u32("bit2", simd_x32_bit_mask_ni<2, simd128_t>(bit2), 0, ones, 0, ones); + + const simd128_t bit3 = simd128_ld( 0u, 8u, 7u, 15u); + check_u32("bit3", simd_x32_bit_mask_ni<3, simd128_t>(bit3), 0, ones, 0, ones); + + const simd128_t bit4 = simd128_ld( 0u, 16u, 15u, 31u); + check_u32("bit4", simd_x32_bit_mask_ni<4, simd128_t>(bit4), 0, ones, 0, ones); + } + + SECTION("bits above the one asked for are ignored") + { + const simd128_t high = simd128_splat(~uint32_t(31) ); + + check_u32("bit0", simd_x32_bit_mask_ni<0, simd128_t>(high), 0, 0, 0, 0); + check_u32("bit1", simd_x32_bit_mask_ni<1, simd128_t>(high), 0, 0, 0, 0); + check_u32("bit2", simd_x32_bit_mask_ni<2, simd128_t>(high), 0, 0, 0, 0); + check_u32("bit3", simd_x32_bit_mask_ni<3, simd128_t>(high), 0, 0, 0, 0); + check_u32("bit4", simd_x32_bit_mask_ni<4, simd128_t>(high), 0, 0, 0, 0); + } + + SECTION("a count of 31 sets every mask") + { + const simd128_t all = simd128_splat(uint32_t(31) ); + + check_u32("bit0", simd_x32_bit_mask_ni<0, simd128_t>(all), ones, ones, ones, ones); + check_u32("bit1", simd_x32_bit_mask_ni<1, simd128_t>(all), ones, ones, ones, ones); + check_u32("bit2", simd_x32_bit_mask_ni<2, simd128_t>(all), ones, ones, ones, ones); + check_u32("bit3", simd_x32_bit_mask_ni<3, simd128_t>(all), ones, ones, ones, ones); + check_u32("bit4", simd_x32_bit_mask_ni<4, simd128_t>(all), ones, ones, ones, ones); + } +} + +TEST_CASE("simd_x32_shift_ni", "[simd]") +{ + SECTION("srl") + { + const simd128_t a = simd128_ld(0x80000000u, 0xffffffffu, 0xffffffffu, 0xffffffffu); + const simd128_t count = simd128_ld( 0u, 1u, 16u, 31u); + + check_u32("srl", simd_x32_srl_ni(a, count), 0x80000000, 0x7fffffff, 0x0000ffff, 0x00000001); + } + + SECTION("sll") + { + const simd128_t a = simd128_ld(1u, 1u, 1u, 1u); + const simd128_t count = simd128_ld(0u, 1u, 16u, 31u); + + check_u32("sll", simd_x32_sll_ni(a, count), 1, 2, 0x00010000, 0x80000000); + } + + SECTION("sra keeps the sign") + { + const simd128_t a = simd128_ld(uint32_t(-256), uint32_t(-256), uint32_t(-256), 256u); + const simd128_t count = simd128_ld( 0u, 4u, 31u, 4u); + + check_i32("sra", simd_x32_sra_ni(a, count), -256, -16, -1, 16); + } + + SECTION("every shift distance, one lane at a time") + { + for (uint32_t ii = 0; ii < 32; ii += 4) + { + const simd128_t a = simd128_splat(UINT32_MAX); + const simd128_t count = simd128_ld(ii, ii+1, ii+2, ii+3); + + check_u32("srl", simd_x32_srl_ni(a, count) + , UINT32_MAX >> ii + , UINT32_MAX >> (ii+1) + , UINT32_MAX >> (ii+2) + , UINT32_MAX >> (ii+3) + ); + + const simd128_t one = simd128_splat(uint32_t(1) ); + + check_u32("sll", simd_x32_sll_ni(one, count) + , uint32_t(1) << ii + , uint32_t(1) << (ii+1) + , uint32_t(1) << (ii+2) + , uint32_t(1) << (ii+3) + ); + } + } +} + TEST_CASE("simd128_selb", "[simd]") { const simd128_t a = simd128_ld(1.0f, 2.0f, 3.0f, 4.0f);