IntersectionBoard cleanups

Add some simplifications and better comments to IntersectionBoard.

Update the IntersectionBoard benchmark to test actual sets of rectangles from real rendering scenes.

Also clean up some math functions and improve tests.

Diffs=
fafdef56c IntersectionBoard cleanups (#6474)

Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head
index b2dba0e..0637e69 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-584a1c28ed6ec51aecde5a9983f366a26ba77280
+fafdef56c74ebb2bfe129b242ebf353b89d6ecf7
diff --git a/include/rive/math/math_types.hpp b/include/rive/math/math_types.hpp
index f79c6fd..481c9b5 100644
--- a/include/rive/math/math_types.hpp
+++ b/include/rive/math/math_types.hpp
@@ -61,6 +61,29 @@
     return dst;
 }
 
+// Attempt to generate a "clz" assembly instruction.
+RIVE_ALWAYS_INLINE static int clz32(uint32_t x)
+{
+    assert(x != 0);
+#if __has_builtin(__builtin_clz)
+    return __builtin_clz(x);
+#else
+    uint64_t doubleBits = bit_cast<uint64_t>(static_cast<double>(x));
+    return 1054 - (doubleBits >> 52);
+#endif
+}
+
+RIVE_ALWAYS_INLINE static int clz64(uint64_t x)
+{
+    assert(x != 0);
+#if __has_builtin(__builtin_clzll)
+    return __builtin_clzll(x);
+#else
+    uint32_t hi32 = x >> 32;
+    return hi32 != 0 ? clz32(hi32) : 32 + clz32(x & 0xffffffff);
+#endif
+}
+
 // Returns the 1-based index of the most significat bit in x.
 //
 //   0    -> 0
@@ -69,23 +92,18 @@
 //   4..7 -> 3
 //   ...
 //
-RIVE_ALWAYS_INLINE static uint32_t msb(uint32_t x)
+RIVE_ALWAYS_INLINE static uint32_t msb(uint32_t x) { return x != 0 ? 32 - clz32(x) : 0; }
+
+// Attempt to generate a "rotl" (rotate-left) assembly instruction.
+RIVE_ALWAYS_INLINE static uint32_t rotateleft32(uint32_t x, int y)
 {
-    if (x == 0)
-    {
-        return 0; // __builtin_clz is undefined for x=0, and the double method doesn't work either.
-    }
-#if defined(__clang__) || defined(__GNUC__)
-    return 32 - __builtin_clz(x);
+#if __has_builtin(__builtin_rotateleft32)
+    return __builtin_rotateleft32(x, y);
 #else
-    uint64_t doubleBits = bit_cast<uint64_t>(static_cast<double>(x));
-    return (doubleBits >> 52) - 1022;
+    return (x << y) | (x >> (32 - y));
 #endif
 }
 
-// Attempt to generate a "rotate-left" (rotl) assembly instruction.
-constexpr static uint32_t rotl(uint32_t x, int shift) { return (x << shift) | (x >> (32 - shift)); }
-
 // Returns x rounded up to the next multiple of N.
 // If x is already a multiple of N, returns x.
 template <size_t N> RIVE_ALWAYS_INLINE constexpr size_t round_up_to_multiple_of(size_t x)
diff --git a/test/math_test.cpp b/test/math_test.cpp
index 4bf0006..31e4a81 100644
--- a/test/math_test.cpp
+++ b/test/math_test.cpp
@@ -61,6 +61,33 @@
     CHECK(std::isnan(math::bit_cast<float>(0x7fc00000)));
 }
 
+// Check math::clz*
+TEST_CASE("clz", "[math]")
+{
+    CHECK(math::clz32(1) == 31);
+    CHECK(math::clz32(-1) == 0);
+    for (int i = 0; i < 32; ++i)
+    {
+        CHECK(math::clz32(1 << i) == 31 - i);
+        CHECK(math::clz32((1 << i) | (rand() & ((1 << i) - 1))) == 31 - i);
+    }
+
+    CHECK(math::clz64(1) == 63);
+    CHECK(math::clz64(-1) == 0);
+    for (int i = 0; i < 64; ++i)
+    {
+        CHECK(math::clz64(1ll << i) == 63 - i);
+        CHECK(math::clz64((1ll << i) | (rand() & ((1ll << i) - 1))) == 63 - i);
+    }
+}
+
+// Check math::rotateleft32
+TEST_CASE("rotateleft32", "[math]")
+{
+    CHECK(math::rotateleft32(0xabcdef01, 24) == 0x01abcdef);
+    CHECK(math::rotateleft32(0xffff0000, 16) == 0x0000ffff);
+}
+
 // Check math::msb.
 TEST_CASE("msb", "[math]")
 {