mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-07-31 09:25:41 +00:00
castable.h is bigger than it needs to be, and pretty much every tint .cc file includes castable.h Reduce the amount of code that .cc files that don't use Switch() need to compile. Change-Id: Ibb4e8b0bc7104ad33a7f2f39587c7d9e749fee97 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/123401 Reviewed-by: Dan Sinclair <dsinclair@chromium.org> Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Ben Clayton <bclayton@google.com>
306 lines
13 KiB
C++
306 lines
13 KiB
C++
// Copyright 2023 The Tint Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#ifndef SRC_TINT_SWITCH_H_
|
|
#define SRC_TINT_SWITCH_H_
|
|
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
#include "src/tint/castable.h"
|
|
|
|
namespace tint {
|
|
|
|
/// Default can be used as the default case for a Switch(), when all previous cases failed to match.
|
|
///
|
|
/// Example:
|
|
/// ```
|
|
/// Switch(object,
|
|
/// [&](TypeA*) { /* ... */ },
|
|
/// [&](TypeB*) { /* ... */ },
|
|
/// [&](Default) { /* If not TypeA or TypeB */ });
|
|
/// ```
|
|
struct Default {};
|
|
|
|
} // namespace tint
|
|
|
|
namespace tint::detail {
|
|
|
|
/// Evaluates to the Switch case type being matched by the switch case function `FN`.
|
|
/// @note does not handle the Default case
|
|
/// @see Switch().
|
|
template <typename FN>
|
|
using SwitchCaseType = std::remove_pointer_t<traits::ParameterType<std::remove_reference_t<FN>, 0>>;
|
|
|
|
/// Evaluates to true if the function `FN` has the signature of a Default case in a Switch().
|
|
/// @see Switch().
|
|
template <typename FN>
|
|
inline constexpr bool IsDefaultCase =
|
|
std::is_same_v<traits::ParameterType<std::remove_reference_t<FN>, 0>, Default>;
|
|
|
|
/// Searches the list of Switch cases for a Default case, returning the index of the Default case.
|
|
/// If the a Default case is not found in the tuple, then -1 is returned.
|
|
template <typename TUPLE, std::size_t START_IDX = 0>
|
|
constexpr int IndexOfDefaultCase() {
|
|
if constexpr (START_IDX < std::tuple_size_v<TUPLE>) {
|
|
return IsDefaultCase<std::tuple_element_t<START_IDX, TUPLE>>
|
|
? static_cast<int>(START_IDX)
|
|
: IndexOfDefaultCase<TUPLE, START_IDX + 1>();
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/// The implementation of Switch() for non-Default cases.
|
|
/// Switch splits the cases into two a low and high block of cases, and quickly rules out blocks
|
|
/// that cannot match by comparing the HashCode of the object and the cases in the block. If a block
|
|
/// of cases may match the given object's type, then that block is split into two, and the process
|
|
/// recurses. When NonDefaultCases() is called with a single case, then As<> will be used to
|
|
/// dynamically cast to the case type and if the cast succeeds, then the case handler is called.
|
|
/// @returns true if a case handler was found, otherwise false.
|
|
template <typename T, typename RETURN_TYPE, typename... CASES>
|
|
inline bool NonDefaultCases([[maybe_unused]] T* object,
|
|
const TypeInfo* type,
|
|
[[maybe_unused]] RETURN_TYPE* result,
|
|
std::tuple<CASES...>&& cases) {
|
|
using Cases = std::tuple<CASES...>;
|
|
|
|
static constexpr bool kHasReturnType = !std::is_same_v<RETURN_TYPE, void>;
|
|
static constexpr size_t kNumCases = sizeof...(CASES);
|
|
|
|
if constexpr (kNumCases == 0) {
|
|
// No cases. Nothing to do.
|
|
return false;
|
|
} else if constexpr (kNumCases == 1) { // NOLINT: cpplint doesn't understand
|
|
// `else if constexpr`
|
|
// Single case.
|
|
using CaseFunc = std::tuple_element_t<0, Cases>;
|
|
static_assert(!IsDefaultCase<CaseFunc>, "NonDefaultCases called with a Default case");
|
|
// Attempt to dynamically cast the object to the handler type. If that succeeds, call the
|
|
// case handler with the cast object.
|
|
using CaseType = SwitchCaseType<CaseFunc>;
|
|
if (type->Is<CaseType>()) {
|
|
auto* ptr = static_cast<CaseType*>(object);
|
|
if constexpr (kHasReturnType) {
|
|
new (result) RETURN_TYPE(static_cast<RETURN_TYPE>(std::get<0>(cases)(ptr)));
|
|
} else {
|
|
std::get<0>(cases)(ptr);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
} else {
|
|
// Multiple cases.
|
|
// Check the hashcode bits to see if there's any possibility of a case matching in these
|
|
// cases. If there isn't, we can skip all these cases.
|
|
if (MaybeAnyOf(TypeInfo::CombinedHashCodeOf<SwitchCaseType<CASES>...>(),
|
|
type->full_hashcode)) {
|
|
// Split the cases into two, and recurse.
|
|
constexpr size_t kMid = kNumCases / 2;
|
|
return NonDefaultCases(object, type, result, traits::Slice<0, kMid>(cases)) ||
|
|
NonDefaultCases(object, type, result,
|
|
traits::Slice<kMid, kNumCases - kMid>(cases));
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The implementation of Switch() for all cases.
|
|
/// @see NonDefaultCases
|
|
template <typename T, typename RETURN_TYPE, typename... CASES>
|
|
inline void SwitchCases(T* object, RETURN_TYPE* result, std::tuple<CASES...>&& cases) {
|
|
using Cases = std::tuple<CASES...>;
|
|
|
|
static constexpr int kDefaultIndex = detail::IndexOfDefaultCase<Cases>();
|
|
static constexpr bool kHasDefaultCase = kDefaultIndex >= 0;
|
|
static constexpr bool kHasReturnType = !std::is_same_v<RETURN_TYPE, void>;
|
|
|
|
// Static assertions
|
|
static constexpr bool kDefaultIsOK =
|
|
kDefaultIndex == -1 || kDefaultIndex == static_cast<int>(std::tuple_size_v<Cases> - 1);
|
|
static constexpr bool kReturnIsOK =
|
|
kHasDefaultCase || !kHasReturnType || std::is_constructible_v<RETURN_TYPE>;
|
|
static_assert(kDefaultIsOK, "Default case must be last in Switch()");
|
|
static_assert(kReturnIsOK,
|
|
"Switch() requires either a Default case or a return type that is either void or "
|
|
"default-constructable");
|
|
|
|
// If the static asserts have fired, don't bother spewing more errors below
|
|
static constexpr bool kAllOK = kDefaultIsOK && kReturnIsOK;
|
|
if constexpr (kAllOK) {
|
|
if (object) {
|
|
auto* type = &object->TypeInfo();
|
|
if constexpr (kHasDefaultCase) {
|
|
// Evaluate non-default cases.
|
|
if (!detail::NonDefaultCases<T>(object, type, result,
|
|
traits::Slice<0, kDefaultIndex>(cases))) {
|
|
// Nothing matched. Evaluate default case.
|
|
if constexpr (kHasReturnType) {
|
|
new (result) RETURN_TYPE(
|
|
static_cast<RETURN_TYPE>(std::get<kDefaultIndex>(cases)({})));
|
|
} else {
|
|
std::get<kDefaultIndex>(cases)({});
|
|
}
|
|
}
|
|
} else {
|
|
if (!detail::NonDefaultCases<T>(object, type, result, std::move(cases))) {
|
|
// Nothing matched. No default case.
|
|
if constexpr (kHasReturnType) {
|
|
new (result) RETURN_TYPE();
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Object is nullptr, so no cases can match
|
|
if constexpr (kHasDefaultCase) {
|
|
// Evaluate default case.
|
|
if constexpr (kHasReturnType) {
|
|
new (result)
|
|
RETURN_TYPE(static_cast<RETURN_TYPE>(std::get<kDefaultIndex>(cases)({})));
|
|
} else {
|
|
std::get<kDefaultIndex>(cases)({});
|
|
}
|
|
} else {
|
|
// No default case, no case can match.
|
|
if constexpr (kHasReturnType) {
|
|
new (result) RETURN_TYPE();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Resolves to T if T is not nullptr_t, otherwise resolves to Ignore.
|
|
template <typename T>
|
|
using NullptrToIgnore = std::conditional_t<std::is_same_v<T, std::nullptr_t>, Ignore, T>;
|
|
|
|
/// Resolves to `const TYPE` if any of `CASE_RETURN_TYPES` are const or pointer-to-const, otherwise
|
|
/// resolves to TYPE.
|
|
template <typename TYPE, typename... CASE_RETURN_TYPES>
|
|
using PropagateReturnConst = std::conditional_t<
|
|
// Are any of the pointer-stripped types const?
|
|
(std::is_const_v<std::remove_pointer_t<CASE_RETURN_TYPES>> || ...),
|
|
const TYPE, // Yes: Apply const to TYPE
|
|
TYPE>; // No: Passthrough
|
|
|
|
/// SwitchReturnTypeImpl is the implementation of SwitchReturnType
|
|
template <bool IS_CASTABLE, typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
|
|
struct SwitchReturnTypeImpl;
|
|
|
|
/// SwitchReturnTypeImpl specialization for non-castable case types and an explicitly specified
|
|
/// return type.
|
|
template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
|
|
struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, REQUESTED_TYPE, CASE_RETURN_TYPES...> {
|
|
/// Resolves to `REQUESTED_TYPE`
|
|
using type = REQUESTED_TYPE;
|
|
};
|
|
|
|
/// SwitchReturnTypeImpl specialization for non-castable case types and an inferred return type.
|
|
template <typename... CASE_RETURN_TYPES>
|
|
struct SwitchReturnTypeImpl</*IS_CASTABLE*/ false, Infer, CASE_RETURN_TYPES...> {
|
|
/// Resolves to the common type for all the cases return types.
|
|
using type = std::common_type_t<CASE_RETURN_TYPES...>;
|
|
};
|
|
|
|
/// SwitchReturnTypeImpl specialization for castable case types and an explicitly specified return
|
|
/// type.
|
|
template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
|
|
struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, REQUESTED_TYPE, CASE_RETURN_TYPES...> {
|
|
public:
|
|
/// Resolves to `const REQUESTED_TYPE*` or `REQUESTED_TYPE*`
|
|
using type = PropagateReturnConst<std::remove_pointer_t<REQUESTED_TYPE>, CASE_RETURN_TYPES...>*;
|
|
};
|
|
|
|
/// SwitchReturnTypeImpl specialization for castable case types and an inferred return type.
|
|
template <typename... CASE_RETURN_TYPES>
|
|
struct SwitchReturnTypeImpl</*IS_CASTABLE*/ true, Infer, CASE_RETURN_TYPES...> {
|
|
private:
|
|
using InferredType =
|
|
CastableCommonBase<detail::NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>;
|
|
|
|
public:
|
|
/// `const T*` or `T*`, where T is the common base type for all the castable case types.
|
|
using type = PropagateReturnConst<InferredType, CASE_RETURN_TYPES...>*;
|
|
};
|
|
|
|
/// Resolves to the return type for a Switch() with the requested return type `REQUESTED_TYPE` and
|
|
/// case statement return types. If `REQUESTED_TYPE` is Infer then the return type will be inferred
|
|
/// from the case return types.
|
|
template <typename REQUESTED_TYPE, typename... CASE_RETURN_TYPES>
|
|
using SwitchReturnType = typename SwitchReturnTypeImpl<
|
|
IsCastable<NullptrToIgnore<std::remove_pointer_t<CASE_RETURN_TYPES>>...>,
|
|
REQUESTED_TYPE,
|
|
CASE_RETURN_TYPES...>::type;
|
|
|
|
} // namespace tint::detail
|
|
|
|
namespace tint {
|
|
|
|
/// Switch is used to dispatch one of the provided callback case handler functions based on the type
|
|
/// of `object` and the parameter type of the case handlers. Switch will sequentially check the type
|
|
/// of `object` against each of the switch case handler functions, and will invoke the first case
|
|
/// handler function which has a parameter type that matches the object type. When a case handler is
|
|
/// matched, it will be called with the single argument of `object` cast to the case handler's
|
|
/// parameter type. Switch will invoke at most one case handler. Each of the case functions must
|
|
/// have the signature `R(T*)` or `R(const T*)`, where `T` is the type matched by that case and `R`
|
|
/// is the return type, consistent across all case handlers.
|
|
///
|
|
/// An optional default case function with the signature `R(Default)` can be used as the last case.
|
|
/// This default case will be called if all previous cases failed to match.
|
|
///
|
|
/// If `object` is nullptr and a default case is provided, then the default case will be called. If
|
|
/// `object` is nullptr and no default case is provided, then no cases will be called.
|
|
///
|
|
/// Example:
|
|
/// ```
|
|
/// Switch(object,
|
|
/// [&](TypeA*) { /* ... */ },
|
|
/// [&](TypeB*) { /* ... */ });
|
|
///
|
|
/// Switch(object,
|
|
/// [&](TypeA*) { /* ... */ },
|
|
/// [&](TypeB*) { /* ... */ },
|
|
/// [&](Default) { /* Called if object is not TypeA or TypeB */ });
|
|
/// ```
|
|
///
|
|
/// @param object the object who's type is used to
|
|
/// @param cases the switch cases
|
|
/// @return the value returned by the called case. If no cases matched, then the zero value for the
|
|
/// consistent case type.
|
|
template <typename RETURN_TYPE = detail::Infer, typename T = CastableBase, typename... CASES>
|
|
inline auto Switch(T* object, CASES&&... cases) {
|
|
using ReturnType = detail::SwitchReturnType<RETURN_TYPE, traits::ReturnType<CASES>...>;
|
|
static constexpr bool kHasReturnType = !std::is_same_v<ReturnType, void>;
|
|
|
|
if constexpr (kHasReturnType) {
|
|
// Replacement for std::aligned_storage as this is broken on earlier versions of MSVC.
|
|
struct alignas(alignof(ReturnType)) ReturnStorage {
|
|
uint8_t data[sizeof(ReturnType)];
|
|
};
|
|
ReturnStorage storage;
|
|
auto* res = utils::Bitcast<ReturnType*>(&storage);
|
|
TINT_DEFER(res->~ReturnType());
|
|
detail::SwitchCases(object, res, std::forward_as_tuple(std::forward<CASES>(cases)...));
|
|
return *res;
|
|
} else {
|
|
detail::SwitchCases<T, void>(object, nullptr,
|
|
std::forward_as_tuple(std::forward<CASES>(cases)...));
|
|
}
|
|
}
|
|
|
|
} // namespace tint
|
|
|
|
#endif // SRC_TINT_SWITCH_H_
|