Skip to content

Commit

Permalink
#1311 Fix enum reflection support breaking on clang 20
Browse files Browse the repository at this point in the history
  • Loading branch information
SanderMertens committed Sep 24, 2024
1 parent bfaed68 commit b1bf3a5
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 171 deletions.
178 changes: 92 additions & 86 deletions distr/flecs.h
Original file line number Diff line number Diff line change
Expand Up @@ -662,9 +662,6 @@ extern "C" {
/* Filenames aren't consistent across targets as they can use different casing
* (e.g. WinSock2 vs winsock2). */
#pragma clang diagnostic ignored "-Wnonportable-system-include-path"
/* Enum reflection relies on testing constant values that may not be valid for
* the enumeration. */
#pragma clang diagnostic ignored "-Wenum-constexpr-conversion"
/* Very difficult to workaround this warning in C, especially for an ECS. */
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
/* This warning gets thrown when trying to cast pointer returned from dlproc */
Expand All @@ -678,6 +675,11 @@ extern "C" {
#pragma clang diagnostic ignored "-Wsometimes-uninitialized"
#endif

/* Allows for enum reflection support on legacy compilers */
#if __clang_major__ < 16
#pragma clang diagnostic ignored "-Wenum-constexpr-conversion"
#endif

#elif defined(ECS_TARGET_GNU)
#ifndef __cplusplus
#pragma GCC diagnostic ignored "-Wdeclaration-after-statement"
Expand All @@ -699,6 +701,11 @@ extern "C" {
#pragma GCC diagnostic ignored "-Wrestrict"
#endif

/* Allows for enum reflection support on legacy compilers */
#if defined(__GNUC__) && __GNUC__ <= 10
#pragma GCC diagnostic ignored "-Wconversion"
#endif

/* Standard library dependencies */
#include <assert.h>
#include <stdarg.h>
Expand Down Expand Up @@ -17212,18 +17219,22 @@ struct string_view : string {
#endif
#endif

#if defined(__clang__) && __clang_major__ >= 16
// https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307
#define enum_cast(T, v) __builtin_bit_cast(T, v)
#elif defined(__GNUC__) && __GNUC__ > 10
#define enum_cast(T, v) __builtin_bit_cast(T, v)
#else
#define enum_cast(T, v) static_cast<T>(v)
#endif

namespace flecs {

/** Int to enum */
namespace _ {
template <typename E, underlying_type_t<E> Value>
struct to_constant {
#if defined(__clang__) && __clang_major__ >= 16
// https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307
static constexpr E value = __builtin_bit_cast(E, Value);
#else
static constexpr E value = static_cast<E>(Value);
#endif
static constexpr E value = enum_cast(E, Value);
};

template <typename E, underlying_type_t<E> Value>
Expand Down Expand Up @@ -17333,6 +17344,12 @@ constexpr bool enum_constant_is_valid() {
}
#endif

/* Without this wrapper __builtin_bit_cast doesn't work */
template <typename E, underlying_type_t<E> C>
constexpr bool enum_constant_is_valid_wrap() {
return enum_constant_is_valid<E, enum_cast(E, C)>();
}

template <typename E, E C>
struct enum_is_valid {
static constexpr bool value = enum_constant_is_valid<E, C>();
Expand Down Expand Up @@ -17365,22 +17382,9 @@ struct enum_constant_data {
* @tparam E The enum type.
* @tparam Handler The handler for enum reflection operations.
*/
template <typename E, template<typename> class Handler>
template <typename E, typename Handler>
struct enum_reflection {
template <E Value>
static constexpr underlying_type_t<E> to_int() {
return static_cast<underlying_type_t<E>>(Value);
}

template <underlying_type_t<E> Value>
static constexpr E from_int() {
return to_constant<E, Value>::value;
}

template <E Value>
static constexpr underlying_type_t<E> is_not_0() {
return static_cast<underlying_type_t<E>>(Value != from_int<0>());
}
using U = underlying_type_t<E>;

/**
* @brief Iterates over the range [Low, High] of enum values between Low and High.
Expand All @@ -17391,19 +17395,19 @@ struct enum_reflection {
*
* @tparam Low The lower bound of the search range, inclusive.
* @tparam High The upper bound of the search range, inclusive.
* @tparam Args Additional arguments to be passed through to Handler<E>::handle_constant
* @tparam Args Additional arguments to be passed through to Handler::handle_constant
* @param last_value The last value processed in the iteration.
* @param args Additional arguments to be passed through to Handler<E>::handle_constant
* @return constexpr underlying_type_t<E> The result of the iteration.
*/
template <E Low, E High, typename... Args>
static constexpr underlying_type_t<E> each_enum_range(underlying_type_t<E> last_value, Args... args) {
return to_int<High>() - to_int<Low>() <= 1
? to_int<High>() == to_int<Low>()
? Handler<E>::template handle_constant<Low>(last_value, args...)
: Handler<E>::template handle_constant<High>(Handler<E>::template handle_constant<Low>(last_value, args...), args...)
: each_enum_range<from_int<(to_int<Low>()+to_int<High>()) / 2 + 1>(), High>(
each_enum_range<Low, from_int<(to_int<Low>()+to_int<High>()) / 2>()>(last_value, args...),
* @param args Additional arguments to be passed through to Handler::handle_constant
* @return constexpr U The result of the iteration.
*/
template <U Low, U High, typename... Args>
static constexpr U each_enum_range(U last_value, Args... args) {
return High - Low <= 1
? High == Low
? Handler::template handle_constant<Low>(last_value, args...)
: Handler::template handle_constant<High>(Handler::template handle_constant<Low>(last_value, args...), args...)
: each_enum_range<(Low + High) / 2 + 1, High>(
each_enum_range<Low, (Low + High) / 2>(last_value, args...),
args...
);
}
Expand All @@ -17413,22 +17417,22 @@ struct enum_reflection {
*
* Recursively iterates the search space, looking for enums defined as multiple-of-2
* bitmasks. Each iteration, shifts bit to the right until it hits Low, then calls
* Handler<E>::handle_constant for each bitmask in ascending order.
* Handler::handle_constant for each bitmask in ascending order.
*
* @tparam Low The lower bound of the search range, not inclusive
* @tparam High The upper bound of the search range, inclusive.
* @tparam Args Additional arguments to be passed through to Handler<E>::handle_constant
* @tparam Args Additional arguments to be passed through to Handler::handle_constant
* @param last_value The last value processed in the iteration.
* @param args Additional arguments to be passed through to Handler<E>::handle_constant
* @return constexpr underlying_type_t<E> The result of the iteration.
* @param args Additional arguments to be passed through to Handler::handle_constant
* @return constexpr U The result of the iteration.
*/
template <E Low, E High, typename... Args>
static constexpr underlying_type_t<E> each_mask_range(underlying_type_t<E> last_value, Args... args) {
template <U Low, U High, typename... Args>
static constexpr U each_mask_range(U last_value, Args... args) {
// If Low shares any bits with Current Flag, or if High is less than/equal to Low (and High isn't negative because max-flag signed)
return (to_int<Low>() & to_int<High>()) || (to_int<High>() <= to_int<Low>() && to_int<High>() != high_bit)
return (Low & High) || (High <= Low && High != high_bit)
? last_value
: Handler<E>::template handle_constant<High>(
each_mask_range<Low, from_int<((to_int<High>() >> 1) & ~high_bit)>()>(last_value, args...),
: Handler::template handle_constant<High>(
each_mask_range<Low, ((High >> 1) & ~high_bit)>(last_value, args...),
args...
);
}
Expand All @@ -17441,93 +17445,94 @@ struct enum_reflection {
* (each_mask_range<Value, high_bit>).
*
* @tparam Value The maximum enum value to iterate up to.
* @tparam Args Additional arguments to be passed through to Handler<E>::handle_constant
* @param args Additional arguments to be passed through to Handler<E>::handle_constant
* @return constexpr underlying_type_t<E> The result of the iteration.
* @tparam Args Additional arguments to be passed through to Handler::handle_constant
* @param args Additional arguments to be passed through to Handler::handle_constant
* @return constexpr U The result of the iteration.
*/
template <E Value = FLECS_ENUM_MAX(E), typename... Args>
static constexpr underlying_type_t<E> each_enum(Args... args) {
return each_mask_range<Value, from_int<high_bit>()>(each_enum_range<from_int<0>(), Value>(0, args...), args...);
template <U Value = static_cast<U>(FLECS_ENUM_MAX(E)), typename... Args>
static constexpr U each_enum(Args... args) {
return each_mask_range<Value, high_bit>(each_enum_range<0, Value>(0, args...), args...);
}

static const underlying_type_t<E> high_bit = static_cast<underlying_type_t<E>>(1) << (sizeof(underlying_type_t<E>) * 8 - 1);
static const U high_bit = static_cast<U>(1) << (sizeof(U) * 8 - 1);
};

/** Enumeration type data */
template<typename E>
struct enum_data_impl {
private:
using U = underlying_type_t<E>;

/**
* @brief Handler struct for generating compile-time count of enum constants.
*/
template<typename Enum>
struct reflection_count {
template <Enum Value, flecs::if_not_t< enum_constant_is_valid<Enum, Value>() > = 0>
static constexpr underlying_type_t<Enum> handle_constant(underlying_type_t<E> last_value) {
template <U Value, flecs::if_not_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
static constexpr U handle_constant(U last_value) {
return last_value;
}

template <Enum Value, flecs::if_t< enum_constant_is_valid<Enum, Value>() > = 0>
static constexpr underlying_type_t<Enum> handle_constant(underlying_type_t<E> last_value) {
template <U Value, flecs::if_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
static constexpr U handle_constant(U last_value) {
return 1 + last_value;
}
};

public:
flecs::entity_t id;
int min;
int max;
bool has_contiguous;
// If enum constants start not-sparse, contiguous_until will be the index of the first sparse value, or end of the constants array
underlying_type_t<E> contiguous_until;
U contiguous_until;
// Compile-time generated count of enum constants.
static constexpr unsigned int constants_size = enum_reflection<E, reflection_count>::template each_enum< enum_last<E>::value >();
static constexpr unsigned int constants_size = enum_reflection<E, reflection_count>::template each_enum< static_cast<U>(enum_last<E>::value) >();
// Constants array is sized to the number of found-constants, or 1 (to avoid 0-sized array)
enum_constant_data<underlying_type_t<E>> constants[constants_size? constants_size: 1];
enum_constant_data<U> constants[constants_size? constants_size: 1];
};

/** Class that scans an enum for constants, extracts names & creates entities */
template <typename E>
struct enum_type {
private:
using U = underlying_type_t<E>;

/**
* @brief Helper struct for filling enum_type's static `enum_data_impl<E>` member with reflection data.
*
* Because reflection occurs in-order, we can use current value/last value to determine continuity, and
* use that as a lookup heuristic later on.
*
* @tparam Enum The enum type.
*/
template <typename Enum>
struct reflection_init {
template <Enum Value, flecs::if_not_t< enum_constant_is_valid<Enum, Value>() > = 0>
static underlying_type_t<Enum> handle_constant(underlying_type_t<Enum> last_value, flecs::world_t*) {
template <U Value, flecs::if_not_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
static U handle_constant(U last_value, flecs::world_t*) {
// Search for constant failed. Pass last valid value through.
return last_value;
}

template <Enum Value, flecs::if_t< enum_constant_is_valid<Enum, Value>() > = 0>
static underlying_type_t<Enum> handle_constant(underlying_type_t<Enum> last_value, flecs::world_t *world) {
template <U Value, flecs::if_t< enum_constant_is_valid_wrap<E, Value>() > = 0>
static U handle_constant(U last_value, flecs::world_t *world) {
// Constant is valid, so fill reflection data.
auto v = enum_reflection<E, reflection_init>::template to_int<Value>();
const char *name = enum_constant_to_name<Enum, Value>();
auto v = Value;
const char *name = enum_constant_to_name<E, enum_cast(E, Value)>();

++enum_type<Enum>::data.max; // Increment cursor as we build constants array.
++enum_type<E>::data.max; // Increment cursor as we build constants array.

// If the enum was previously contiguous, and continues to be through the current value...
if (enum_type<Enum>::data.has_contiguous && static_cast<underlying_type_t<Enum>>(enum_type<Enum>::data.max) == v && enum_type<Enum>::data.contiguous_until == v) {
++enum_type<Enum>::data.contiguous_until;
if (enum_type<E>::data.has_contiguous && static_cast<U>(enum_type<E>::data.max) == v && enum_type<E>::data.contiguous_until == v) {
++enum_type<E>::data.contiguous_until;
}
// else, if the enum was never contiguous and hasn't been set as not contiguous...
else if (!enum_type<Enum>::data.contiguous_until && enum_type<Enum>::data.has_contiguous) {
enum_type<Enum>::data.has_contiguous = false;
else if (!enum_type<E>::data.contiguous_until && enum_type<E>::data.has_contiguous) {
enum_type<E>::data.has_contiguous = false;
}

ecs_assert(!(last_value > 0 && v < std::numeric_limits<underlying_type_t<Enum>>::min() + last_value), ECS_UNSUPPORTED,
ecs_assert(!(last_value > 0 && v < std::numeric_limits<U>::min() + last_value), ECS_UNSUPPORTED,
"Signed integer enums causes integer overflow when recording offset from high positive to"
" low negative. Consider using unsigned integers as underlying type.");
enum_type<Enum>::data.constants[enum_type<Enum>::data.max].offset = v - last_value;
enum_type<Enum>::data.constants[enum_type<Enum>::data.max].id = ecs_cpp_enum_constant_register(
world, enum_type<Enum>::data.id, 0, name, static_cast<int32_t>(v));
enum_type<E>::data.constants[enum_type<E>::data.max].offset = v - last_value;
enum_type<E>::data.constants[enum_type<E>::data.max].id = ecs_cpp_enum_constant_register(
world, enum_type<E>::data.id, 0, name, static_cast<int32_t>(v));
return v;
}
};
Expand Down Expand Up @@ -17563,10 +17568,9 @@ struct enum_type {
data.id = id;

// Generate reflection data
enum_reflection<E, reflection_init>::template each_enum< enum_last<E>::value >(world);
enum_reflection<E, reflection_init>::template each_enum< static_cast<U>(enum_last<E>::value) >(world);
ecs_log_pop();
}

};

template <typename E>
Expand All @@ -17585,6 +17589,8 @@ inline static void init_enum(flecs::world_t*, flecs::entity_t) { }
/** Enumeration type data wrapper with world pointer */
template <typename E>
struct enum_data {
using U = underlying_type_t<E>;

enum_data(flecs::world_t *world, _::enum_data_impl<E>& impl)
: world_(world)
, impl_(impl) { }
Expand All @@ -17596,7 +17602,7 @@ struct enum_data {
* @return true If the value is a valid enum value.
* @return false If the value is not a valid enum value.
*/
bool is_valid(underlying_type_t<E> value) {
bool is_valid(U value) {
int index = index_by_value(value);
if (index < 0) {
return false;
Expand All @@ -17612,7 +17618,7 @@ struct enum_data {
* @return false If the value is not valid.
*/
bool is_valid(E value) {
return is_valid(static_cast<underlying_type_t<E>>(value));
return is_valid(static_cast<U>(value));
}

/**
Expand All @@ -17621,15 +17627,15 @@ struct enum_data {
* @param value The enum value.
* @return int The index of the enum value.
*/
int index_by_value(underlying_type_t<E> value) const {
int index_by_value(U value) const {
if (!impl_.max) {
return -1;
}
// Check if value is in contiguous lookup section
if (impl_.has_contiguous && value < impl_.contiguous_until && value >= 0) {
return static_cast<int>(value);
}
underlying_type_t<E> accumulator = impl_.contiguous_until? impl_.contiguous_until - 1: 0;
U accumulator = impl_.contiguous_until? impl_.contiguous_until - 1: 0;
for (int i = static_cast<int>(impl_.contiguous_until); i <= impl_.max; ++i) {
accumulator += impl_.constants[i].offset;
if (accumulator == value) {
Expand All @@ -17646,7 +17652,7 @@ struct enum_data {
* @return int The index of the enum value.
*/
int index_by_value(E value) const {
return index_by_value(static_cast<underlying_type_t<E>>(value));
return index_by_value(static_cast<U>(value));
}

int first() const {
Expand All @@ -17662,7 +17668,7 @@ struct enum_data {
}

flecs::entity entity() const;
flecs::entity entity(underlying_type_t<E> value) const;
flecs::entity entity(U value) const;
flecs::entity entity(E value) const;

flecs::world_t *world_;
Expand Down
3 changes: 3 additions & 0 deletions examples/cpp/hello_world/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@
"flecs"
],
"language": "c++"
},
"lang.cpp": {
"cpp-standard": "c++11"
}
}
Loading

0 comments on commit b1bf3a5

Please sign in to comment.