[libc-commits] [libc] [libc] Implement vector 'split' and 'concat' routines (PR #157537)

Joseph Huber via libc-commits libc-commits at lists.llvm.org
Wed Sep 10 10:18:48 PDT 2025


https://github.com/jhuber6 updated https://github.com/llvm/llvm-project/pull/157537

>From f5f89201bf303730fa1ba8d88fdcacee2e6dd1cb Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Mon, 8 Sep 2025 15:10:30 -0500
Subject: [PATCH 1/3] [libc] Implement vector 'split' and 'concat' routines

Summary:
This provides some helpers for the split and concatenation routines for
changing the size of an existing vector. This includes a simple tuple
type to do the splitting. The tuple doesn't support structured bindings
yet.

The concat function is more limited than what would be ideal, but the
shufflevector builtin requires things of equivalent sizes and I
didn't think it was worth wrangling with that just yet.
---
 libc/src/__support/CPP/CMakeLists.txt     |  3 +
 libc/src/__support/CPP/simd.h             | 74 +++++++++++++++++++++++
 libc/test/src/__support/CPP/simd_test.cpp | 26 ++++++++
 3 files changed, 103 insertions(+)

diff --git a/libc/src/__support/CPP/CMakeLists.txt b/libc/src/__support/CPP/CMakeLists.txt
index d9b86b4fd2973..a9cb67df0b427 100644
--- a/libc/src/__support/CPP/CMakeLists.txt
+++ b/libc/src/__support/CPP/CMakeLists.txt
@@ -224,4 +224,7 @@ add_header_library(
   simd
   HDRS
     simd.h
+  DEPENDS
+    .utility
+    .tuple
 )
diff --git a/libc/src/__support/CPP/simd.h b/libc/src/__support/CPP/simd.h
index 54fe70a6e9830..70524dff19df2 100644
--- a/libc/src/__support/CPP/simd.h
+++ b/libc/src/__support/CPP/simd.h
@@ -16,7 +16,9 @@
 #include "hdr/stdint_proxy.h"
 #include "src/__support/CPP/algorithm.h"
 #include "src/__support/CPP/limits.h"
+#include "src/__support/CPP/tuple.h"
 #include "src/__support/CPP/type_traits.h"
+#include "src/__support/CPP/utility/integer_sequence.h"
 #include "src/__support/macros/attributes.h"
 #include "src/__support/macros/config.h"
 
@@ -51,6 +53,7 @@ template <typename T> LIBC_INLINE constexpr size_t native_vector_size = 1;
 template <typename T> LIBC_INLINE constexpr T poison() {
   return __builtin_nondeterministic_value(T());
 }
+
 } // namespace internal
 
 // Type aliases.
@@ -273,6 +276,77 @@ LIBC_INLINE constexpr static simd<T, N> select(simd<bool, N> m, simd<T, N> x,
   return m ? x : y;
 }
 
+namespace internal {
+template <typename T, size_t N, size_t O, size_t... I>
+LIBC_INLINE constexpr static cpp::simd<T, sizeof...(I)>
+extend(cpp::simd<T, N> x, cpp::index_sequence<I...>) {
+  return __builtin_shufflevector(x, x, (I < O ? static_cast<int>(I) : -1)...);
+}
+template <typename T, size_t N, size_t M, size_t O>
+LIBC_INLINE constexpr static auto extend(cpp::simd<T, N> x) {
+  if constexpr (N == M)
+    return x;
+  else if constexpr (M <= 2 * N)
+    return extend<T, N, M>(x, cpp::make_index_sequence<M>{});
+  else
+    return extend<T, 2 * N, M, O>(
+        extend<T, N, 2 * N>(x, cpp::make_index_sequence<2 * N>{}));
+}
+template <typename T, size_t N, size_t M, size_t... I>
+LIBC_INLINE constexpr static cpp::simd<T, N + M>
+concat(cpp::simd<T, N> x, cpp::simd<T, M> y, cpp::index_sequence<I...>) {
+  constexpr size_t L = (N > M ? N : M);
+
+  auto x_ext = extend<T, N, L, N>(x);
+  auto y_ext = extend<T, M, L, M>(y);
+
+  auto remap = [](size_t idx) -> int {
+    if (idx < N)
+      return static_cast<int>(idx);
+    if (idx < N + M)
+      return static_cast<int>((idx - N) + L);
+    return -1;
+  };
+
+  return __builtin_shufflevector(x_ext, y_ext, remap(I)...);
+}
+
+template <typename T, size_t N, size_t Count, size_t Offset, size_t... I>
+LIBC_INLINE constexpr static cpp::simd<T, Count>
+slice(cpp::simd<T, N> x, cpp::index_sequence<I...>) {
+  return __builtin_shufflevector(x, x, (Offset + I)...);
+}
+template <typename T, size_t N, size_t Offset, size_t Head, size_t... Tail>
+LIBC_INLINE constexpr static auto split(cpp::simd<T, N> x) {
+  auto first = cpp::make_tuple(
+      slice<T, N, Head, Offset>(x, cpp::make_index_sequence<Head>{}));
+  if constexpr (sizeof...(Tail) > 0)
+    return cpp::tuple_cat(first, split<T, N, Offset + Head, Tail...>(x));
+  else
+    return first;
+}
+
+} // namespace internal
+
+// Shuffling helpers.
+template <typename T, size_t N, size_t M>
+LIBC_INLINE constexpr static auto concat(cpp::simd<T, N> x, cpp::simd<T, M> y) {
+  return internal::concat(x, y, make_index_sequence<N + M>{});
+}
+template <typename T, size_t N, size_t M, typename... Rest>
+LIBC_INLINE constexpr static auto concat(cpp::simd<T, N> x, cpp::simd<T, M> y,
+                                         Rest... rest) {
+  auto xy = concat(x, y);
+  if constexpr (sizeof...(Rest))
+    return concat(xy, rest...);
+  else
+    return xy;
+}
+template <size_t... Sizes, typename T, size_t N> auto split(cpp::simd<T, N> x) {
+  static_assert((... + Sizes) == N, "split sizes must sum to vector size");
+  return internal::split<T, N, 0, Sizes...>(x);
+}
+
 // TODO: where expressions, scalar overloads, ABI types.
 
 } // namespace cpp
diff --git a/libc/test/src/__support/CPP/simd_test.cpp b/libc/test/src/__support/CPP/simd_test.cpp
index 600bf65057b21..72ec10d12cb5b 100644
--- a/libc/test/src/__support/CPP/simd_test.cpp
+++ b/libc/test/src/__support/CPP/simd_test.cpp
@@ -68,3 +68,29 @@ TEST(LlvmLibcSIMDTest, MaskOperations) {
   EXPECT_EQ(cpp::find_first_set(mask), 0);
   EXPECT_EQ(cpp::find_last_set(mask), 2);
 }
+
+TEST(LlvmLibcSIMDTest, SplitConcat) {
+  cpp::simd<char, 8> v(1);
+  auto [v1, v2, v3, v4] = cpp::split<2, 2, 2, 2>(v);
+  static_assert(cpp::simd_size_v<decltype(v1)> == 2 &&
+                    cpp::simd_size_v<decltype(v2)> == 2 &&
+                    cpp::simd_size_v<decltype(v3)> == 2 &&
+                    cpp::simd_size_v<decltype(v4)> == 2,
+                "invalid size");
+
+  v1 = cpp::simd<char, 2>(1);
+  v2 = cpp::simd<char, 2>(2);
+  v3 = cpp::simd<char, 2>(3);
+  v4 = cpp::simd<char, 2>(4);
+  cpp::simd<char, 8> m = cpp::concat(v1, v2, v3, v4);
+  static_assert(cpp::simd_size_v<decltype(m)> == 8, "invalid size");
+
+  cpp::simd<char, 8> c = {1, 1, 2, 2, 3, 3, 4, 4};
+  for (int i = 0; i < 8; ++i)
+    EXPECT_EQ(c[i], m[i]);
+
+  cpp::simd<char, 1> c1('\0');
+  cpp::simd<char, 8> c2('\0');
+  cpp::simd<char, 9> c3 = cpp::concat(c1, c2);
+  static_assert(cpp::simd_size_v<decltype(c3)> == 9, "invalid size");
+}

>From 0b9c4e7c1001e192242cb43455a093f03467e8e2 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Wed, 10 Sep 2025 12:07:04 -0500
Subject: [PATCH 2/3] cleanup and comments

---
 libc/src/__support/CPP/simd.h | 127 ++++++++++++++++++----------------
 1 file changed, 68 insertions(+), 59 deletions(-)

diff --git a/libc/src/__support/CPP/simd.h b/libc/src/__support/CPP/simd.h
index 70524dff19df2..d69df2bb92fe5 100644
--- a/libc/src/__support/CPP/simd.h
+++ b/libc/src/__support/CPP/simd.h
@@ -34,9 +34,6 @@ namespace cpp {
 
 namespace internal {
 
-template <typename T>
-using get_as_integer_type_t = unsigned _BitInt(sizeof(T) * CHAR_BIT);
-
 #if defined(LIBC_TARGET_CPU_HAS_AVX512F)
 template <typename T>
 LIBC_INLINE_VAR constexpr size_t native_vector_size = 64 / sizeof(T);
@@ -50,10 +47,6 @@ LIBC_INLINE_VAR constexpr size_t native_vector_size = 16 / sizeof(T);
 template <typename T> LIBC_INLINE constexpr size_t native_vector_size = 1;
 #endif
 
-template <typename T> LIBC_INLINE constexpr T poison() {
-  return __builtin_nondeterministic_value(T());
-}
-
 } // namespace internal
 
 // Type aliases.
@@ -64,6 +57,74 @@ using simd = T [[clang::ext_vector_type(N)]];
 template <typename T>
 using simd_mask = simd<bool, internal::native_vector_size<T>>;
 
+namespace internal {
+
+template <typename T>
+using get_as_integer_type_t = unsigned _BitInt(sizeof(T) * CHAR_BIT);
+
+template <typename T> LIBC_INLINE constexpr T poison() {
+  return __builtin_nondeterministic_value(T());
+}
+
+template <typename T, size_t N, size_t OriginalSize, size_t... Indices>
+LIBC_INLINE constexpr static cpp::simd<T, sizeof...(Indices)>
+extend(cpp::simd<T, N> x, cpp::index_sequence<Indices...>) {
+  return __builtin_shufflevector(
+      x, x, (Indices < OriginalSize ? static_cast<int>(Indices) : -1)...);
+}
+
+template <typename T, size_t N, size_t TargetSize, size_t OriginalSize>
+LIBC_INLINE constexpr static auto extend(cpp::simd<T, N> x) {
+  // Recursively resize an input vector to the target size, increasing its size
+  // by at most double the input size each step.
+  if constexpr (N == TargetSize)
+    return x;
+  else if constexpr (TargetSize <= 2 * N)
+    return extend<T, N, TargetSize>(x, cpp::make_index_sequence<TargetSize>{});
+  else
+    return extend<T, 2 * N, TargetSize, OriginalSize>(
+        extend<T, N, 2 * N>(x, cpp::make_index_sequence<2 * N>{}));
+}
+
+template <typename T, size_t N, size_t M, size_t... Indices>
+LIBC_INLINE constexpr static cpp::simd<T, N + M>
+concat(cpp::simd<T, N> x, cpp::simd<T, M> y, cpp::index_sequence<Indices...>) {
+  constexpr size_t Length = (N > M ? N : M);
+  auto remap = [](size_t idx) -> int {
+    if (idx < N)
+      return static_cast<int>(idx);
+    if (idx < N + M)
+      return static_cast<int>((idx - N) + Length);
+    return -1;
+  };
+
+  // Extend the input vectors until they are the same size, then use the indices
+  // to shuffle in only the indices that correspond to the original values.
+  auto x_ext = extend<T, N, Length, N>(x);
+  auto y_ext = extend<T, M, Length, M>(y);
+  return __builtin_shufflevector(x_ext, y_ext, remap(Indices)...);
+}
+
+template <typename T, size_t N, size_t Count, size_t Offset, size_t... Indices>
+LIBC_INLINE constexpr static cpp::simd<T, Count>
+slice(cpp::simd<T, N> x, cpp::index_sequence<Indices...>) {
+  return __builtin_shufflevector(x, x, (Offset + Indices)...);
+}
+
+template <typename T, size_t N, size_t Offset, size_t Head, size_t... Tail>
+LIBC_INLINE constexpr static auto split(cpp::simd<T, N> x) {
+  // Recursively splits the input vector by walking the variadic template list,
+  // increasing our current head each call.
+  auto first = cpp::make_tuple(
+      slice<T, N, Head, Offset>(x, cpp::make_index_sequence<Head>{}));
+  if constexpr (sizeof...(Tail) > 0)
+    return cpp::tuple_cat(first, split<T, N, Offset + Head, Tail...>(x));
+  else
+    return first;
+}
+
+} // namespace internal
+
 // Type trait helpers.
 template <typename T>
 struct simd_size : cpp::integral_constant<size_t, __builtin_vectorelements(T)> {
@@ -276,58 +337,6 @@ LIBC_INLINE constexpr static simd<T, N> select(simd<bool, N> m, simd<T, N> x,
   return m ? x : y;
 }
 
-namespace internal {
-template <typename T, size_t N, size_t O, size_t... I>
-LIBC_INLINE constexpr static cpp::simd<T, sizeof...(I)>
-extend(cpp::simd<T, N> x, cpp::index_sequence<I...>) {
-  return __builtin_shufflevector(x, x, (I < O ? static_cast<int>(I) : -1)...);
-}
-template <typename T, size_t N, size_t M, size_t O>
-LIBC_INLINE constexpr static auto extend(cpp::simd<T, N> x) {
-  if constexpr (N == M)
-    return x;
-  else if constexpr (M <= 2 * N)
-    return extend<T, N, M>(x, cpp::make_index_sequence<M>{});
-  else
-    return extend<T, 2 * N, M, O>(
-        extend<T, N, 2 * N>(x, cpp::make_index_sequence<2 * N>{}));
-}
-template <typename T, size_t N, size_t M, size_t... I>
-LIBC_INLINE constexpr static cpp::simd<T, N + M>
-concat(cpp::simd<T, N> x, cpp::simd<T, M> y, cpp::index_sequence<I...>) {
-  constexpr size_t L = (N > M ? N : M);
-
-  auto x_ext = extend<T, N, L, N>(x);
-  auto y_ext = extend<T, M, L, M>(y);
-
-  auto remap = [](size_t idx) -> int {
-    if (idx < N)
-      return static_cast<int>(idx);
-    if (idx < N + M)
-      return static_cast<int>((idx - N) + L);
-    return -1;
-  };
-
-  return __builtin_shufflevector(x_ext, y_ext, remap(I)...);
-}
-
-template <typename T, size_t N, size_t Count, size_t Offset, size_t... I>
-LIBC_INLINE constexpr static cpp::simd<T, Count>
-slice(cpp::simd<T, N> x, cpp::index_sequence<I...>) {
-  return __builtin_shufflevector(x, x, (Offset + I)...);
-}
-template <typename T, size_t N, size_t Offset, size_t Head, size_t... Tail>
-LIBC_INLINE constexpr static auto split(cpp::simd<T, N> x) {
-  auto first = cpp::make_tuple(
-      slice<T, N, Head, Offset>(x, cpp::make_index_sequence<Head>{}));
-  if constexpr (sizeof...(Tail) > 0)
-    return cpp::tuple_cat(first, split<T, N, Offset + Head, Tail...>(x));
-  else
-    return first;
-}
-
-} // namespace internal
-
 // Shuffling helpers.
 template <typename T, size_t N, size_t M>
 LIBC_INLINE constexpr static auto concat(cpp::simd<T, N> x, cpp::simd<T, M> y) {

>From 1d3c8fd6e4ddf8d7b1e3044d8fc41a6fd878cc5b Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Wed, 10 Sep 2025 12:18:38 -0500
Subject: [PATCH 3/3] Use max

---
 libc/src/__support/CPP/simd.h | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/libc/src/__support/CPP/simd.h b/libc/src/__support/CPP/simd.h
index d69df2bb92fe5..1c3a3f0ee8d6c 100644
--- a/libc/src/__support/CPP/simd.h
+++ b/libc/src/__support/CPP/simd.h
@@ -89,19 +89,19 @@ LIBC_INLINE constexpr static auto extend(cpp::simd<T, N> x) {
 template <typename T, size_t N, size_t M, size_t... Indices>
 LIBC_INLINE constexpr static cpp::simd<T, N + M>
 concat(cpp::simd<T, N> x, cpp::simd<T, M> y, cpp::index_sequence<Indices...>) {
-  constexpr size_t Length = (N > M ? N : M);
+  constexpr size_t Size = cpp::max(N, M);
   auto remap = [](size_t idx) -> int {
     if (idx < N)
       return static_cast<int>(idx);
     if (idx < N + M)
-      return static_cast<int>((idx - N) + Length);
+      return static_cast<int>((idx - N) + Size);
     return -1;
   };
 
   // Extend the input vectors until they are the same size, then use the indices
   // to shuffle in only the indices that correspond to the original values.
-  auto x_ext = extend<T, N, Length, N>(x);
-  auto y_ext = extend<T, M, Length, M>(y);
+  auto x_ext = extend<T, N, Size, N>(x);
+  auto y_ext = extend<T, M, Size, M>(y);
   return __builtin_shufflevector(x_ext, y_ext, remap(Indices)...);
 }
 



More information about the libc-commits mailing list