mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-05-15 11:51:22 +00:00
tint: Implement Switch() without recursion
This removes the number of function calls made in non-optimized builds. Reduces the optimized, all-features-enabled 'tint' executable size by about 1%. This change removes the bloom filter optimizations which provided substantial performance gains with the old recursive implementation, however this still appears to be ~1% faster than the optimized version. Change-Id: Ic2bb82e9182459e37907f9e0d0b4771bde218f9f Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/123440 Reviewed-by: Dan Sinclair <dsinclair@chromium.org> Commit-Queue: Ben Clayton <bclayton@chromium.org> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Kokoro: Ben Clayton <bclayton@chromium.org>
This commit is contained in:
parent
23946b3606
commit
4fea9d0fbb
@ -21,9 +21,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "src/tint/traits.h"
|
||||
#include "src/tint/utils/bitcast.h"
|
||||
#include "src/tint/utils/crc32.h"
|
||||
#include "src/tint/utils/defer.h"
|
||||
|
||||
#if defined(__clang__)
|
||||
/// Temporarily disable certain warnings when using Castable API
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include "src/tint/type/multisampled_texture.h"
|
||||
#include "src/tint/type/sampled_texture.h"
|
||||
#include "src/tint/type/texture_dimension.h"
|
||||
#include "src/tint/utils/defer.h"
|
||||
#include "src/tint/utils/reverse.h"
|
||||
#include "src/tint/utils/string.h"
|
||||
#include "src/tint/utils/string_stream.h"
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "src/tint/sem/while_statement.h"
|
||||
#include "src/tint/switch.h"
|
||||
#include "src/tint/utils/block_allocator.h"
|
||||
#include "src/tint/utils/defer.h"
|
||||
#include "src/tint/utils/map.h"
|
||||
#include "src/tint/utils/string_stream.h"
|
||||
#include "src/tint/utils/unique_vector.h"
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include <utility>
|
||||
|
||||
#include "src/tint/castable.h"
|
||||
#include "src/tint/utils/bitcast.h"
|
||||
#include "src/tint/utils/defer.h"
|
||||
|
||||
namespace tint {
|
||||
|
||||
@ -62,126 +64,6 @@ constexpr int IndexOfDefaultCase() {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
@ -282,21 +164,95 @@ namespace tint {
|
||||
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 int kDefaultIndex = detail::IndexOfDefaultCase<std::tuple<CASES...>>();
|
||||
static constexpr bool kHasDefaultCase = kDefaultIndex >= 0;
|
||||
static constexpr bool kHasReturnType = !std::is_same_v<ReturnType, void>;
|
||||
|
||||
// Static assertions
|
||||
static constexpr bool kDefaultIsOK =
|
||||
kDefaultIndex == -1 || kDefaultIndex == static_cast<int>(sizeof...(CASES) - 1);
|
||||
static constexpr bool kReturnIsOK =
|
||||
kHasDefaultCase || !kHasReturnType || std::is_constructible_v<ReturnType>;
|
||||
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 (!object) { // Object is nullptr, so no cases can match
|
||||
if constexpr (kHasDefaultCase) {
|
||||
// Evaluate default case.
|
||||
auto&& default_case =
|
||||
std::get<kDefaultIndex>(std::forward_as_tuple(std::forward<CASES>(cases)...));
|
||||
return static_cast<ReturnType>(default_case(Default{}));
|
||||
} else {
|
||||
// No default case, no case can match.
|
||||
if constexpr (kHasReturnType) {
|
||||
return ReturnType{};
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replacement for std::aligned_storage as this is broken on earlier versions of MSVC.
|
||||
struct alignas(alignof(ReturnType)) ReturnStorage {
|
||||
uint8_t data[sizeof(ReturnType)];
|
||||
using ReturnTypeOrU8 = std::conditional_t<kHasReturnType, ReturnType, uint8_t>;
|
||||
struct alignas(alignof(ReturnTypeOrU8)) ReturnStorage {
|
||||
uint8_t data[sizeof(ReturnTypeOrU8)];
|
||||
};
|
||||
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;
|
||||
auto* result = utils::Bitcast<ReturnTypeOrU8*>(&storage);
|
||||
|
||||
const TypeInfo& type_info = object->TypeInfo();
|
||||
|
||||
// Examines the parameter type of the case function.
|
||||
// If the parameter is a pointer type that `object` is of, or derives from, then that case
|
||||
// function is called with `object` cast to that type, and `try_case` returns true.
|
||||
// If the parameter is of type `Default`, then that case function is called and `try_case`
|
||||
// returns true.
|
||||
// Otherwise `try_case` returns false.
|
||||
// If the case function is called and it returns a value, then this is copy constructed to the
|
||||
// `result` pointer.
|
||||
auto try_case = [&](auto&& case_fn) {
|
||||
using CaseFunc = std::decay_t<decltype(case_fn)>;
|
||||
using CaseType = detail::SwitchCaseType<CaseFunc>;
|
||||
if constexpr (std::is_same_v<CaseType, Default>) {
|
||||
if constexpr (kHasReturnType) {
|
||||
new (result) ReturnType(static_cast<ReturnType>(case_fn(Default{})));
|
||||
} else {
|
||||
detail::SwitchCases<T, void>(object, nullptr,
|
||||
std::forward_as_tuple(std::forward<CASES>(cases)...));
|
||||
case_fn(Default{});
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (type_info.Is<CaseType>()) {
|
||||
auto* v = static_cast<CaseType*>(object);
|
||||
if constexpr (kHasReturnType) {
|
||||
new (result) ReturnType(static_cast<ReturnType>(case_fn(v)));
|
||||
} else {
|
||||
case_fn(v);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Use a logical-or fold expression to try each of the cases in turn, until one matches the
|
||||
// object type or a Default is reached. `handled` is true if a case function was called.
|
||||
bool handled = ((try_case(std::forward<CASES>(cases)) || ...));
|
||||
|
||||
if constexpr (kHasReturnType) {
|
||||
if constexpr (kHasDefaultCase) {
|
||||
// Default case means there must be a returned value.
|
||||
// No need to check handled, no requirement for a zero-initializer of ReturnType.
|
||||
TINT_DEFER(result->~ReturnType());
|
||||
return *result;
|
||||
} else {
|
||||
if (handled) {
|
||||
TINT_DEFER(result->~ReturnType());
|
||||
return *result;
|
||||
}
|
||||
return ReturnType{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "src/tint/castable.h"
|
||||
#include "src/tint/traits.h"
|
||||
#include "src/tint/utils/bitcast.h"
|
||||
|
||||
namespace tint::utils {
|
||||
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include "src/tint/sem/struct.h"
|
||||
#include "src/tint/sem/switch_statement.h"
|
||||
#include "src/tint/switch.h"
|
||||
#include "src/tint/utils/defer.h"
|
||||
#include "src/tint/utils/math.h"
|
||||
#include "src/tint/utils/scoped_assignment.h"
|
||||
#include "src/tint/writer/float_to_string.h"
|
||||
|
Loading…
x
Reference in New Issue
Block a user