diff --git a/src/snmalloc/global/libc.h b/src/snmalloc/global/libc.h index 73e1783..d6ffa58 100644 --- a/src/snmalloc/global/libc.h +++ b/src/snmalloc/global/libc.h @@ -52,6 +52,25 @@ namespace snmalloc::libc SNMALLOC_FAST_PATH_INLINE void* realloc(void* ptr, size_t size) { + // Glibc treats + // realloc(p, 0) as free(p) + // realloc(nullptr, s) as malloc(s) + // and for the overlap + // realloc(nullptr, 0) as malloc(1) + // Without the first two we don't pass the glibc tests. + // The last one is required by various gnu utilities such as grep, sed. + if (SNMALLOC_UNLIKELY(!is_small_sizeclass(size))) + { + if (SNMALLOC_UNLIKELY(size == 0)) + { + if (SNMALLOC_UNLIKELY(ptr == nullptr)) + return malloc(1); + + dealloc(ptr); + return nullptr; + } + } + size_t sz = alloc_size(ptr); // Keep the current allocation if the given size is in the same sizeclass. if (sz == round_size(size)) @@ -72,13 +91,10 @@ namespace snmalloc::libc } dealloc(ptr); } - else if (SNMALLOC_LIKELY(size == 0)) - { - dealloc(ptr); - } else { - return set_error(); + // Error should be set by alloc on this path already. + SNMALLOC_ASSERT(errno == ENOMEM); } return p; } diff --git a/src/snmalloc/mem/sizeclasstable.h b/src/snmalloc/mem/sizeclasstable.h index c9f70be..18c849a 100644 --- a/src/snmalloc/mem/sizeclasstable.h +++ b/src/snmalloc/mem/sizeclasstable.h @@ -457,6 +457,17 @@ namespace snmalloc constexpr SizeClassLookup sizeclass_lookup = SizeClassLookup(); + /** + * @brief Returns true if the size is a small sizeclass. Note that + * 0 is not considered a small sizeclass. + */ + constexpr bool is_small_sizeclass(size_t size) + { + // Perform the - 1 on size, so that zero wraps around and ends up on + // slow path. + return (size - 1) < sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1); + } + constexpr smallsizeclass_t size_to_sizeclass(size_t size) { auto index = sizeclass_lookup_index(size); @@ -482,7 +493,7 @@ namespace snmalloc */ static inline sizeclass_t size_to_sizeclass_full(size_t size) { - if ((size - 1) < sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1)) + if (is_small_sizeclass(size)) { return sizeclass_t::from_small_class(size_to_sizeclass(size)); } @@ -493,26 +504,28 @@ namespace snmalloc inline SNMALLOC_FAST_PATH static size_t round_size(size_t size) { - if (size > sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1)) + if (is_small_sizeclass(size)) { - if (size > bits::one_at_bit(bits::BITS - 1)) - { - // This size is too large, no rounding should occur as will result in a - // failed allocation later. - return size; - } - return bits::next_pow2(size); + return sizeclass_to_size(size_to_sizeclass(size)); } - // If realloc(ptr, 0) returns nullptr, some consumers treat this as a - // reallocation failure and abort. To avoid this, we round up the size of - // requested allocations to the smallest size class. This can be changed - // on any platform that's happy to return nullptr from realloc(ptr,0) and - // should eventually become a configuration option. + if (size == 0) { + // If realloc(ptr, 0) returns nullptr, some consumers treat this as a + // reallocation failure and abort. To avoid this, we round up the size of + // requested allocations to the smallest size class. This can be changed + // on any platform that's happy to return nullptr from realloc(ptr,0) and + // should eventually become a configuration option. return sizeclass_to_size(size_to_sizeclass(1)); } - return sizeclass_to_size(size_to_sizeclass(size)); + + if (size > bits::one_at_bit(bits::BITS - 1)) + { + // This size is too large, no rounding should occur as will result in a + // failed allocation later. + return size; + } + return bits::next_pow2(size); } /// Returns the alignment that this size naturally has, that is @@ -549,9 +562,7 @@ namespace snmalloc // sizeclass calculation. We use the same fast path constant to // move the case where result==0 to the slow path, and then check for which // case we are in. - constexpr size_t SmallSizeClassUpperBound = - sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1) - 1; - if (SNMALLOC_LIKELY((result - 1) < SmallSizeClassUpperBound)) + if (is_small_sizeclass(result)) return result; // We are in the slow path, so we need to check for overflow. diff --git a/src/test/func/malloc/malloc.cc b/src/test/func/malloc/malloc.cc index 630d723..b3ab963 100644 --- a/src/test/func/malloc/malloc.cc +++ b/src/test/func/malloc/malloc.cc @@ -265,6 +265,9 @@ int main(int argc, char** argv) test_calloc(0, size, SUCCESS, false); } + // Check realloc(nullptr,0) behaves like malloc(1) + test_realloc(nullptr, 0, SUCCESS, false); + for (smallsizeclass_t sc = 0; sc < NUM_SMALL_SIZECLASSES; sc++) { const size_t size = sizeclass_to_size(sc); @@ -277,6 +280,8 @@ int main(int argc, char** argv) test_realloc(our_malloc(size), size2, SUCCESS, false); test_realloc(our_malloc(size + 1), size2, SUCCESS, false); } + // Check realloc(p,0), behaves like free(p), if p != nullptr + test_realloc(our_malloc(size), 0, SUCCESS, true); } for (smallsizeclass_t sc = 0; sc < (MAX_SMALL_SIZECLASS_BITS + 4); sc++)