diff --git a/CMakeLists.txt b/CMakeLists.txt index 611d730..7191f63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,9 @@ macro(clangformat_targets) else () message(STATUS "Generating clangformat target using ${CLANG_FORMAT}") file(GLOB_RECURSE ALL_SOURCE_FILES *.cc *.h *.hh) + # clangformat does not yet understand concepts well; for the moment, don't + # ask it to format them. See https://reviews.llvm.org/D79773 + list(FILTER ALL_SOURCE_FILES EXCLUDE REGEX "src/pal/pal_concept\.h$") add_custom_target( clangformat COMMAND ${CLANG_FORMAT} diff --git a/src/ds/concept.h b/src/ds/concept.h new file mode 100644 index 0000000..d935417 --- /dev/null +++ b/src/ds/concept.h @@ -0,0 +1,40 @@ +#pragma once + +/** + * C++20 concepts are referenced as if they were types in declarations within + * template parameters (e.g. "template ..."). That is, they + * take the place of the "typename"/"class" keyword on template parameters. + * If the compiler understands concepts, this macro expands as its argument; + * otherwise, it expands to the keyword "typename", so snmalloc templates that + * use concept-qualified parameters should use this to remain compatible across + * C++ versions: "template" + */ +#ifdef __cpp_concepts +# define SNMALLOC_CONCEPT(c) c +#else +# define SNMALLOC_CONCEPT(c) typename +#endif + +#ifdef __cpp_concepts +namespace snmalloc +{ + /** + * C++20 concepts are more than just new syntax; there's a new support + * library specified as well. As C++20 is quite new, however, there are some + * environments, notably Clang, that understand the syntax but do not yet + * offer the library. Fortunately, alternate pronouciations are possible. + */ +# ifdef _cpp_lib_concepts + /** + * ConceptSame is true if T and U are the same type and false otherwise. + * When specifying a concept, use ConceptSame to indicate that an + * expression must evaluate precisely to the type U. + */ + template + concept ConceptSame = std::same_as; +# else + template + concept ConceptSame = std::is_same::value; +# endif +} // namespace snmalloc +#endif diff --git a/src/mem/address_space.h b/src/mem/address_space.h index a727f0b..00dc21b 100644 --- a/src/mem/address_space.h +++ b/src/mem/address_space.h @@ -13,7 +13,7 @@ namespace snmalloc * It cannot unreserve memory, so this does not require the * usual complexity of a buddy allocator. */ - template + template class AddressSpaceManager : public PAL { /** diff --git a/src/mem/largealloc.h b/src/mem/largealloc.h index 2a0c7b3..ff85bb5 100644 --- a/src/mem/largealloc.h +++ b/src/mem/largealloc.h @@ -14,7 +14,7 @@ namespace snmalloc { - template + template class MemoryProviderStateMixin; class Largeslab : public Baseslab @@ -24,7 +24,7 @@ namespace snmalloc private: template friend class MPMCStack; - template + template friend class MemoryProviderStateMixin; std::atomic next; @@ -56,7 +56,7 @@ namespace snmalloc // This represents the state that the large allcoator needs to add to the // global state of the allocator. This is currently stored in the memory // provider, so we add this in. - template + template class MemoryProviderStateMixin : public PalNotificationObject, public PAL { /** diff --git a/src/mem/pool.h b/src/mem/pool.h index c460567..69de2ca 100644 --- a/src/mem/pool.h +++ b/src/mem/pool.h @@ -2,6 +2,7 @@ #include "../ds/flaglock.h" #include "../ds/mpmcstack.h" +#include "../pal/pal_concept.h" #include "pooled.h" namespace snmalloc @@ -21,7 +22,7 @@ namespace snmalloc { private: friend Pooled; - template + template friend class MemoryProviderStateMixin; std::atomic_flag lock = ATOMIC_FLAG_INIT; diff --git a/src/pal/pal.h b/src/pal/pal.h index 47f30e0..9ce3ffc 100644 --- a/src/pal/pal.h +++ b/src/pal/pal.h @@ -1,5 +1,7 @@ #pragma once +#include "../ds/concept.h" +#include "pal_concept.h" #include "pal_consts.h" // If simultating OE, then we need the underlying platform @@ -63,7 +65,7 @@ namespace snmalloc /** * Query whether the PAL supports a specific feature. */ - template + template constexpr static bool pal_supports = (PAL::pal_features & F) == F; // Used to keep Superslab metadata committed. diff --git a/src/pal/pal_concept.h b/src/pal/pal_concept.h new file mode 100644 index 0000000..33f54f4 --- /dev/null +++ b/src/pal/pal_concept.h @@ -0,0 +1,104 @@ +#pragma once + +#ifdef __cpp_concepts +# include "../ds/concept.h" +# include "pal_consts.h" + +# include + +namespace snmalloc +{ + /** + * PALs must advertize the bit vector of their supported features and the + * platform's page size. This concept enforces that these are indeed + * constants that fit in the desired types. (This is subtly different from + * saying that they are the required types; C++ may handle constants without + * much regard for their claimed type.) + */ + template + concept ConceptPAL_static_members = requires() + { + typename std::integral_constant; + typename std::integral_constant; + }; + + /** + * PALs expose an error reporting function which takes a const C string. + */ + template + concept ConceptPAL_error = requires(const char* const str) + { + { PAL::error(str) } -> ConceptSame; + }; + + /** + * PALs expose a basic library of memory operations. + */ + template + concept ConceptPAL_memops = requires(PAL p, void* vp, size_t sz) + { + { p.notify_not_using(vp, sz) } noexcept -> ConceptSame; + + /* For reasons unknown, these seem to tickle some bug in MSVC */ +# if !defined(_MSC_VER) + { p.template notify_using(vp, sz) } noexcept + -> ConceptSame; + { p.template notify_using(vp, sz) } noexcept + -> ConceptSame; +# endif + + { p.template zero(vp, sz) } noexcept -> ConceptSame; + { p.template zero(vp, sz) } noexcept -> ConceptSame; + }; + + /** + * Absent any feature flags, the PAL must support a crude primitive allocator + */ + template + concept ConceptPAL_reserve_at_least = requires(PAL p, void* vp, size_t sz) + { + { p.reserve_at_least(sz) } noexcept + -> ConceptSame>; + }; + + /** + * Some PALs expose a richer allocator which understands aligned allocations + */ + template + concept ConceptPAL_reserve_aligned = requires(PAL p, size_t sz) + { + { p.template reserve_aligned(sz) } noexcept -> ConceptSame; + { p.template reserve_aligned(sz) } noexcept -> ConceptSame; + }; + + /** + * Some PALs can provide memory pressure callbacks. + */ + template + concept ConceptPAL_mem_low_notify = + requires(PAL p, PalNotificationObject* pno) + { + { p.expensive_low_memory_check() } -> ConceptSame; + { p.register_for_low_memory_callback(pno) } -> ConceptSame; + }; + + /** + * PALs ascribe to the conjunction of several concepts. These are broken + * out by the shape of the requires() quantifiers required and by any + * requisite claimed pal_features. PALs not claiming particular features + * are, naturally, not bound by the corresponding concept. + */ + template + concept ConceptPAL = + ConceptPAL_static_members && + ConceptPAL_error && + ConceptPAL_memops && + (!(PAL::pal_features & LowMemoryNotification) || + ConceptPAL_mem_low_notify) && + (!!(PAL::pal_features & AlignedAllocation) || + ConceptPAL_reserve_at_least) && + (!(PAL::pal_features & AlignedAllocation) || + ConceptPAL_reserve_aligned); + +} // namespace snmalloc +#endif