Skip to content

Commit

Permalink
rework simd traits to be c++17 friendly
Browse files Browse the repository at this point in the history
Summary:
previous version of simd contains was rolled back because of no c++17 support.

This should hopefully rectify that.

Differential Revision: D63635013
  • Loading branch information
Denis Yaroshevskiy authored and facebook-github-bot committed Sep 30, 2024
1 parent c79d267 commit 982404a
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 118 deletions.
3 changes: 2 additions & 1 deletion folly/algorithm/simd/FindFixed.h
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ constexpr std::optional<std::size_t> findFixed(std::span<const T, N> where, U x)
return find_fixed_detail::findFixedConstexpr(std::span<const T>(where), x);
} else {
return find_fixed_detail::findFixedDispatch(
detail::asSimdFriendlyUint(where), detail::asSimdFriendlyUint(x));
simd::detail::asSimdFriendlyUint(where),
simd::detail::asSimdFriendlyUint(x));
}
}

Expand Down
1 change: 1 addition & 0 deletions folly/algorithm/simd/detail/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ cpp_library(
name = "traits",
headers = ["Traits.h"],
exported_deps = [
"//folly:c_portability",
"//folly:memory",
"//folly:traits",
"//folly/container:span",
Expand Down
107 changes: 59 additions & 48 deletions folly/algorithm/simd/detail/Traits.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@

#pragma once

#include <folly/CPortability.h>
#include <folly/Memory.h>
#include <folly/Traits.h>
#include <folly/container/span.h>

#include <concepts>
#include <type_traits>

namespace folly::detail {
namespace folly::simd::detail {

template <typename T>
auto findSimdFriendlyEquivalent() {
Expand All @@ -36,65 +37,75 @@ auto findSimdFriendlyEquivalent() {
return double{};
}
} else if constexpr (std::is_signed_v<T>) {
if constexpr (sizeof(T) == 1) {
return std::int8_t{};
} else if constexpr (sizeof(T) == 2) {
return std::int16_t{};
} else if constexpr (sizeof(T) == 4) {
return std::int32_t{};
} else if constexpr (sizeof(T) == 8) {
return std::int64_t{};
}
return int_bits_t<sizeof(T) * 8>{};
} else if constexpr (std::is_unsigned_v<T>) {
if constexpr (sizeof(T) == 1) {
return std::uint8_t{};
} else if constexpr (sizeof(T) == 2) {
return std::uint16_t{};
} else if constexpr (sizeof(T) == 4) {
return std::uint32_t{};
} else if constexpr (sizeof(T) == 8) {
return std::uint64_t{};
}
return uint_bits_t<sizeof(T) * 8>{};
}
}

template <typename T>
concept has_simd_friendly_equivalent =
constexpr bool has_simd_friendly_equivalent_scalar =
!std::is_same_v<void, decltype(findSimdFriendlyEquivalent<T>())>;

template <has_simd_friendly_equivalent T>
using simd_friendly_equivalent_t = folly::like_t< //
T,
decltype(findSimdFriendlyEquivalent<std::remove_const_t<T>>())>;
template <typename T>
using simd_friendly_equivalent_scalar_t = std::enable_if_t<
has_simd_friendly_equivalent_scalar<T>,
like_t<T, decltype(findSimdFriendlyEquivalent<std::remove_const_t<T>>())>>;

template <typename T>
concept has_integral_simd_friendly_equivalent =
has_simd_friendly_equivalent<T> && // have to explicitly specify this for
// subsumption to work
std::integral<simd_friendly_equivalent_t<T>>;
constexpr bool has_integral_simd_friendly_equivalent_scalar =
std::is_integral_v< // void will return false
decltype(findSimdFriendlyEquivalent<std::remove_const_t<T>>())>;

template <has_integral_simd_friendly_equivalent T>
using integral_simd_friendly_equivalent = simd_friendly_equivalent_t<T>;
template <typename T>
using unsigned_simd_friendly_equivalent_scalar_t = std::enable_if_t<
has_integral_simd_friendly_equivalent_scalar<T>,
like_t<T, uint_bits_t<sizeof(T) * 8>>>;

template <has_simd_friendly_equivalent T, std::size_t Extend>
auto asSimdFriendly(folly::span<T, Extend> s) {
return folly::reinterpret_span_cast<simd_friendly_equivalent_t<T>>(s);
}
template <typename R>
using span_for = decltype(folly::span(std::declval<const R&>()));

template <has_simd_friendly_equivalent T>
constexpr auto asSimdFriendly(T x) {
return static_cast<simd_friendly_equivalent_t<T>>(x);
}
struct AsSimdFriendlyFn {
template <typename T, std::size_t extent>
FOLLY_ERASE auto operator()(folly::span<T, extent> s) const
-> folly::span<simd_friendly_equivalent_scalar_t<T>, extent> {
return reinterpret_span_cast<simd_friendly_equivalent_scalar_t<T>>(s);
}

template <has_simd_friendly_equivalent T, std::size_t Extend>
auto asSimdFriendlyUint(folly::span<T, Extend> s) {
return folly::reinterpret_span_cast<
folly::like_t<T, uint_bits_t<sizeof(T) * 8>>>(s);
}
template <typename R>
FOLLY_ERASE auto operator()(R&& r) const
-> decltype(operator()(span_for<R>(r))) {
return operator()(folly::span(r));
}

template <has_simd_friendly_equivalent T>
constexpr auto asSimdFriendlyUint(T x) {
return static_cast<uint_bits_t<sizeof(T) * 8>>(x);
}
template <typename T>
FOLLY_ERASE constexpr auto operator()(T x) const
-> simd_friendly_equivalent_scalar_t<T> {
return static_cast<simd_friendly_equivalent_scalar_t<T>>(x);
}
};
inline constexpr AsSimdFriendlyFn asSimdFriendly;

struct AsSimdFriendlyUintFn {
template <typename T, std::size_t extent>
FOLLY_ERASE auto operator()(folly::span<T, extent> s) const
-> folly::span<unsigned_simd_friendly_equivalent_scalar_t<T>, extent> {
return reinterpret_span_cast<unsigned_simd_friendly_equivalent_scalar_t<T>>(
s);
}

template <typename R>
FOLLY_ERASE auto operator()(R&& r) const
-> decltype(operator()(span_for<R>(r))) {
return operator()(folly::span(r));
}

template <typename T>
FOLLY_ERASE constexpr auto operator()(T x) const
-> unsigned_simd_friendly_equivalent_scalar_t<T> {
return static_cast<unsigned_simd_friendly_equivalent_scalar_t<T>>(x);
}
};
inline constexpr AsSimdFriendlyUintFn asSimdFriendlyUint;

} // namespace folly::detail
} // namespace folly::simd::detail
1 change: 1 addition & 0 deletions folly/algorithm/simd/detail/test/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ cpp_unittest(
cpp_unittest(
name = "traits_test",
srcs = ["TraitsTest.cpp"],
compiler_flags = ["--std=c++17"],
deps = [
"//folly/algorithm/simd/detail:traits",
"//folly/portability:gmock",
Expand Down
181 changes: 113 additions & 68 deletions folly/algorithm/simd/detail/test/TraitsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,84 +19,147 @@
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>

namespace folly::detail {
#include <set>
#include <vector>

namespace folly::simd::detail {

struct FollySimdTraitsTest : testing::Test {};

namespace simd_friendly_equivalent_test {
namespace simd_friendly_equivalent_scalar_test {

// ints
static_assert(
std::is_same_v<std::int8_t, simd_friendly_equivalent_t<signed char>>);
static_assert(
std::is_same_v<std::uint8_t, simd_friendly_equivalent_t<unsigned char>>);
static_assert(std::is_same_v<
std::int8_t,
simd_friendly_equivalent_scalar_t<signed char>>);
static_assert(std::is_same_v<
std::uint8_t,
simd_friendly_equivalent_scalar_t<unsigned char>>);

static_assert(std::is_same_v<std::int16_t, simd_friendly_equivalent_t<short>>);
static_assert(
std::is_same_v<std::uint16_t, simd_friendly_equivalent_t<unsigned short>>);
std::is_same_v<std::int16_t, simd_friendly_equivalent_scalar_t<short>>);
static_assert(std::is_same_v<
std::uint16_t,
simd_friendly_equivalent_scalar_t<unsigned short>>);

static_assert(std::is_same_v<std::int32_t, simd_friendly_equivalent_t<int>>);
static_assert(
std::is_same_v<std::uint32_t, simd_friendly_equivalent_t<unsigned int>>);

static_assert(
std::is_same_v<std::int64_t, simd_friendly_equivalent_t<std::int64_t>>);
static_assert(
std::is_same_v<std::uint64_t, simd_friendly_equivalent_t<std::uint64_t>>);
std::is_same_v<std::int32_t, simd_friendly_equivalent_scalar_t<int>>);
static_assert(std::is_same_v<
std::uint32_t,
simd_friendly_equivalent_scalar_t<unsigned int>>);

static_assert(std::is_same_v<
std::int64_t,
simd_friendly_equivalent_scalar_t<std::int64_t>>);
static_assert(std::is_same_v<
std::uint64_t,
simd_friendly_equivalent_scalar_t<std::uint64_t>>);

// floats
static_assert(std::is_same_v<float, simd_friendly_equivalent_t<float>>);
static_assert(std::is_same_v<double, simd_friendly_equivalent_t<double>>);
static_assert(std::is_same_v<float, simd_friendly_equivalent_scalar_t<float>>);
static_assert(
std::is_same_v<double, simd_friendly_equivalent_scalar_t<double>>);

// enum
enum SomeInt {};
enum class SomeIntClass : std::int32_t {};

static_assert(
std::is_same_v<std::uint32_t, simd_friendly_equivalent_t<SomeInt>>);
static_assert(
std::is_same_v<std::int32_t, simd_friendly_equivalent_t<SomeIntClass>>);
std::is_same_v<std::uint32_t, simd_friendly_equivalent_scalar_t<SomeInt>>);
static_assert(std::is_same_v<
std::int32_t,
simd_friendly_equivalent_scalar_t<SomeIntClass>>);

// const

static_assert(
std::is_same_v<const std::int32_t, simd_friendly_equivalent_t<const int>>);
static_assert(std::is_same_v<
const std::int32_t,
simd_friendly_equivalent_scalar_t<const int>>);

// sfinae
constexpr auto sfinae_call =
[]<typename T>(T) -> simd_friendly_equivalent_t<T> { return {}; };

static_assert(std::invocable<decltype(sfinae_call), int>);
struct sfinae_call {
template <typename T>
simd_friendly_equivalent_scalar_t<T> operator()(T) const {
return {};
}
};

static_assert(std::is_invocable_v<sfinae_call, int>);

struct NotSimdFriendly {};
static_assert(!std::invocable<decltype(sfinae_call), NotSimdFriendly>);
static_assert(!std::is_invocable_v<sfinae_call, NotSimdFriendly>);

} // namespace simd_friendly_equivalent_test
} // namespace simd_friendly_equivalent_scalar_test

namespace integral_simd_friendly_equivalent_test {
namespace as_simd_friendly_type_test {

static_assert(std::is_same_v< //
std::int8_t,
integral_simd_friendly_equivalent<signed char>>);
template <typename T>
using asSimdFriendlyResult = std::invoke_result_t<AsSimdFriendlyFn, T>;

struct Overloading {
constexpr int operator()(auto) { return 0; }
constexpr int operator()(has_simd_friendly_equivalent auto) { return 1; }
constexpr int operator()(has_integral_simd_friendly_equivalent auto) {
return 2;
}
};
static_assert(std::is_same_v<
folly::span<std::int32_t>,
asSimdFriendlyResult<folly::span<std::int32_t>>>);

// Subsumption tests
struct NotSimdFriendly {};
enum class SomeInt {};
static_assert(std::is_same_v<
folly::span<std::int32_t>,
asSimdFriendlyResult<folly::span<int>>>);

static_assert(std::is_same_v<
folly::span<std::int32_t>,
asSimdFriendlyResult<std::vector<int>&>>);

static_assert(std::is_same_v<
folly::span<const std::int32_t>,
asSimdFriendlyResult<const std::vector<int>&>>);

static_assert(std::is_same_v<
folly::span<const double>,
asSimdFriendlyResult<const std::vector<double>&>>);

static_assert(std::is_same_v<double, asSimdFriendlyResult<double>>);

static_assert(Overloading{}(NotSimdFriendly{}) == 0);
static_assert(Overloading{}(float{}) == 1);
static_assert(Overloading{}(int{}) == 2);
static_assert(Overloading{}(SomeInt{}) == 2);
static_assert(!std::is_invocable_v<AsSimdFriendlyFn, std::set<int>>);

} // namespace integral_simd_friendly_equivalent_test
} // namespace as_simd_friendly_type_test

namespace as_simd_friendly_uint_type_test {

template <typename T>
using asSimdFriendlyUintResult = std::invoke_result_t<AsSimdFriendlyUintFn, T>;

static_assert(std::is_same_v<
folly::span<std::uint32_t>,
asSimdFriendlyUintResult<folly::span<std::uint32_t>>>);

static_assert(std::is_same_v<
folly::span<std::uint32_t>,
asSimdFriendlyUintResult<folly::span<std::int32_t>>>);

static_assert(std::is_same_v<
folly::span<std::uint32_t>,
asSimdFriendlyUintResult<folly::span<int>>>);

static_assert(std::is_same_v<
folly::span<std::uint32_t>,
asSimdFriendlyUintResult<std::vector<int>&>>);

static_assert(std::is_same_v<
folly::span<const std::uint32_t>,
asSimdFriendlyUintResult<const std::vector<std::uint32_t>&>>);

static_assert(
std::is_same_v<std::uint32_t, asSimdFriendlyUintResult<std::int32_t>>);

static_assert(
!std::is_invocable_v<AsSimdFriendlyUintFn, const std::vector<double>&>);

static_assert(
!std::is_invocable_v<AsSimdFriendlyUintFn, const std::vector<double>&>);

static_assert(!std::is_invocable_v<AsSimdFriendlyUintFn, std::set<int>>);

} // namespace as_simd_friendly_uint_type_test

TEST_F(FollySimdTraitsTest, AsSimdFriendly) {
enum SomeEnum : int { Foo = 1, Bar, Baz };
Expand All @@ -108,32 +171,14 @@ TEST_F(FollySimdTraitsTest, AsSimdFriendly) {
ASSERT_THAT(castSpan, testing::ElementsAre(1, 2, 3));
}

template <typename T, typename U>
void isSameTest(const T&, const U&) = delete;

template <typename T>
void isSameTest(const T&, const T&) {}

template <typename From, typename To>
void asSimdFriendlyUintTypeTest() {
isSameTest(asSimdFriendlyUint(From{}), To{});
isSameTest(asSimdFriendlyUint(std::span<From>{}), std::span<To>{});
isSameTest(
asSimdFriendlyUint(std::span<const From>{}), std::span<const To>{});
}

TEST_F(FollySimdTraitsTest, AsSimdFriendlyUint) {
enum SomeEnum : int { Foo = 1, Bar, Baz };

static_assert(asSimdFriendlyUint(SomeEnum::Foo) == 1U);

asSimdFriendlyUintTypeTest<char, std::uint8_t>();
asSimdFriendlyUintTypeTest<short, std::uint16_t>();
asSimdFriendlyUintTypeTest<int, std::uint32_t>();
asSimdFriendlyUintTypeTest<unsigned, std::uint32_t>();
asSimdFriendlyUintTypeTest<float, std::uint32_t>();
asSimdFriendlyUintTypeTest<int64_t, std::uint64_t>();
asSimdFriendlyUintTypeTest<double, std::uint64_t>();
std::array arr{SomeEnum::Foo, SomeEnum::Bar, SomeEnum::Baz};
folly::span<std::uint32_t, 3> castSpan = asSimdFriendlyUint(folly::span(arr));
ASSERT_THAT(castSpan, testing::ElementsAre(1, 2, 3));
}

} // namespace folly::detail
} // namespace folly::simd::detail
Loading

0 comments on commit 982404a

Please sign in to comment.