https://github.com/PiJoules created https://github.com/llvm/llvm-project/pull/94270
None >From 75447192671f66610f4bc2d50587d78e9cc34f4c Mon Sep 17 00:00:00 2001 From: Leonard Chan <leonardc...@google.com> Date: Fri, 31 May 2024 14:27:16 -0700 Subject: [PATCH] [WIP][libc] Add freelist malloc --- clang/cmake/caches/Fuchsia-stage2.cmake | 2 +- libc/config/baremetal/riscv/entrypoints.txt | 1 + libc/src/__support/CPP/algorithm.h | 14 + libc/src/__support/CPP/cstddef.h | 2 + libc/src/__support/CPP/type_traits.h | 1 + .../CPP/type_traits/aligned_storage.h | 27 + libc/src/__support/fixedvector.h | 20 + libc/src/stdlib/CMakeLists.txt | 13 +- libc/src/stdlib/block.h | 530 ++++++++++++++++++ libc/src/stdlib/calloc.h | 20 + libc/src/stdlib/free.h | 2 +- libc/src/stdlib/freelist.cpp | 125 +++++ libc/src/stdlib/freelist.h | 226 ++++++++ libc/src/stdlib/freelist_heap.h | 237 ++++++++ libc/src/stdlib/freelist_malloc.cpp | 50 ++ libc/src/stdlib/placement_new.h | 17 + libc/src/stdlib/status.h | 370 ++++++++++++ libc/src/sys/stat/linux/kernel_statx.h | 2 +- libc/test/src/CMakeLists.txt | 2 +- libc/test/src/stdlib/CMakeLists.txt | 27 +- libc/test/src/stdlib/freelist_malloc_test.cpp | 56 ++ libc/test/src/stdlib/malloc_test.cpp | 4 + 22 files changed, 1728 insertions(+), 20 deletions(-) create mode 100644 libc/src/__support/CPP/type_traits/aligned_storage.h create mode 100644 libc/src/stdlib/block.h create mode 100644 libc/src/stdlib/calloc.h create mode 100644 libc/src/stdlib/freelist.cpp create mode 100644 libc/src/stdlib/freelist.h create mode 100644 libc/src/stdlib/freelist_heap.h create mode 100644 libc/src/stdlib/freelist_malloc.cpp create mode 100644 libc/src/stdlib/placement_new.h create mode 100644 libc/src/stdlib/status.h create mode 100644 libc/test/src/stdlib/freelist_malloc_test.cpp diff --git a/clang/cmake/caches/Fuchsia-stage2.cmake b/clang/cmake/caches/Fuchsia-stage2.cmake index d5546e20873b3..d628406649214 100644 --- a/clang/cmake/caches/Fuchsia-stage2.cmake +++ b/clang/cmake/caches/Fuchsia-stage2.cmake @@ -189,7 +189,7 @@ foreach(target aarch64-unknown-linux-gnu;armv7-unknown-linux-gnueabihf;i386-unkn set(RUNTIMES_${target}_SANITIZER_TEST_CXX "libc++" CACHE STRING "") set(RUNTIMES_${target}_SANITIZER_TEST_CXX_INTREE ON CACHE BOOL "") set(RUNTIMES_${target}_LLVM_TOOLS_DIR "${CMAKE_BINARY_DIR}/bin" CACHE BOOL "") - set(RUNTIMES_${target}_LLVM_ENABLE_RUNTIMES "compiler-rt;libcxx;libcxxabi;libunwind" CACHE STRING "") + set(RUNTIMES_${target}_LLVM_ENABLE_RUNTIMES "compiler-rt;libcxx;libcxxabi;libunwind;libc" CACHE STRING "") # Use .build-id link. list(APPEND RUNTIME_BUILD_ID_LINK "${target}") diff --git a/libc/config/baremetal/riscv/entrypoints.txt b/libc/config/baremetal/riscv/entrypoints.txt index b769b43f03a2c..363a762909c3a 100644 --- a/libc/config/baremetal/riscv/entrypoints.txt +++ b/libc/config/baremetal/riscv/entrypoints.txt @@ -170,6 +170,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.stdlib.ldiv libc.src.stdlib.llabs libc.src.stdlib.lldiv + libc.src.stdlib.malloc libc.src.stdlib.qsort libc.src.stdlib.rand libc.src.stdlib.srand diff --git a/libc/src/__support/CPP/algorithm.h b/libc/src/__support/CPP/algorithm.h index fef3c18dc50bc..d930c260bac1c 100644 --- a/libc/src/__support/CPP/algorithm.h +++ b/libc/src/__support/CPP/algorithm.h @@ -25,6 +25,20 @@ template <class T> LIBC_INLINE constexpr const T &min(const T &a, const T &b) { return (a < b) ? a : b; } +template <class InputIt, class UnaryPred> +constexpr InputIt find_if_not(InputIt first, InputIt last, UnaryPred q) { + for (; first != last; ++first) + if (!q(*first)) + return first; + + return last; +} + +template <class InputIt, class UnaryPred> +constexpr bool all_of(InputIt first, InputIt last, UnaryPred p) { + return find_if_not(first, last, p) == last; +} + } // namespace cpp } // namespace LIBC_NAMESPACE diff --git a/libc/src/__support/CPP/cstddef.h b/libc/src/__support/CPP/cstddef.h index 1da51fd253fb5..f2c2948178212 100644 --- a/libc/src/__support/CPP/cstddef.h +++ b/libc/src/__support/CPP/cstddef.h @@ -16,6 +16,8 @@ namespace LIBC_NAMESPACE::cpp { enum class byte : unsigned char {}; +using ::max_align_t __attribute__((__using_if_exists__)); + template <class IntegerType> LIBC_INLINE constexpr enable_if_t<is_integral_v<IntegerType>, byte> operator>>(byte b, IntegerType shift) noexcept { diff --git a/libc/src/__support/CPP/type_traits.h b/libc/src/__support/CPP/type_traits.h index 1494aeb905e09..d50b6612656db 100644 --- a/libc/src/__support/CPP/type_traits.h +++ b/libc/src/__support/CPP/type_traits.h @@ -12,6 +12,7 @@ #include "src/__support/CPP/type_traits/add_lvalue_reference.h" #include "src/__support/CPP/type_traits/add_pointer.h" #include "src/__support/CPP/type_traits/add_rvalue_reference.h" +#include "src/__support/CPP/type_traits/aligned_storage.h" #include "src/__support/CPP/type_traits/bool_constant.h" #include "src/__support/CPP/type_traits/conditional.h" #include "src/__support/CPP/type_traits/decay.h" diff --git a/libc/src/__support/CPP/type_traits/aligned_storage.h b/libc/src/__support/CPP/type_traits/aligned_storage.h new file mode 100644 index 0000000000000..574b1146f6b2a --- /dev/null +++ b/libc/src/__support/CPP/type_traits/aligned_storage.h @@ -0,0 +1,27 @@ +//===-- aligned_storage type_traits --------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC___SUPPORT_CPP_TYPE_TRAITS_ALIGNED_STORAGE_H +#define LLVM_LIBC_SRC___SUPPORT_CPP_TYPE_TRAITS_ALIGNED_STORAGE_H + +#include <stddef.h> // size_t + +namespace LIBC_NAMESPACE::cpp { + +template <size_t Len, size_t Align> struct aligned_storage { + struct type { + alignas(Align) unsigned char data[Len]; + }; +}; + +template <size_t Len, size_t Align> +using aligned_storage_t = typename aligned_storage<Len, Align>::type; + +} // namespace LIBC_NAMESPACE::cpp + +#endif // LLVM_LIBC_SRC___SUPPORT_CPP_TYPE_TRAITS_ALIGNED_STORAGE_H diff --git a/libc/src/__support/fixedvector.h b/libc/src/__support/fixedvector.h index 81747ee10067c..e7b7304f05a20 100644 --- a/libc/src/__support/fixedvector.h +++ b/libc/src/__support/fixedvector.h @@ -24,6 +24,18 @@ template <typename T, size_t CAPACITY> class FixedVector { public: constexpr FixedVector() = default; + template <typename It> FixedVector(It begin, It end) { + for (; begin != end; ++begin) { + push_back(*begin); + } + } + + FixedVector(size_t count, const T &value) { + for (size_t i = 0; i < count; ++i) { + push_back(value); + } + } + bool push_back(const T &obj) { if (item_count == CAPACITY) return false; @@ -36,6 +48,9 @@ template <typename T, size_t CAPACITY> class FixedVector { T &back() { return store[item_count - 1]; } + T &operator[](size_t idx) { return store[idx]; } + const T &operator[](size_t idx) const { return store[idx]; } + bool pop_back() { if (item_count == 0) return false; @@ -44,6 +59,7 @@ template <typename T, size_t CAPACITY> class FixedVector { } bool empty() const { return item_count == 0; } + size_t size() const { return item_count; } // Empties the store for all practical purposes. void reset() { item_count = 0; } @@ -63,6 +79,10 @@ template <typename T, size_t CAPACITY> class FixedVector { return reverse_iterator{&store[item_count]}; } LIBC_INLINE constexpr reverse_iterator rend() { return store.rend(); } + + using iterator = typename cpp::array<T, CAPACITY>::iterator; + LIBC_INLINE constexpr iterator begin() { return store.begin(); } + LIBC_INLINE constexpr iterator end() { return iterator{&store[item_count]}; } }; } // namespace LIBC_NAMESPACE diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt index 9b76a6a0f8575..d50bb1c448a6c 100644 --- a/libc/src/stdlib/CMakeLists.txt +++ b/libc/src/stdlib/CMakeLists.txt @@ -369,8 +369,19 @@ elseif(LIBC_TARGET_OS_IS_GPU) aligned_alloc ) else() - add_entrypoint_external( + add_entrypoint_object( malloc + SRCS + freelist_malloc.cpp + HDRS + malloc.h + DEPENDS + libc.src.__support.CPP.optional + libc.src.__support.CPP.span + libc.src.__support.CPP.type_traits + libc.src.__support.fixedvector + libc.src.string.memcpy + libc.src.string.memset ) add_entrypoint_external( free diff --git a/libc/src/stdlib/block.h b/libc/src/stdlib/block.h new file mode 100644 index 0000000000000..bde1274028d48 --- /dev/null +++ b/libc/src/stdlib/block.h @@ -0,0 +1,530 @@ +//===-- Implementation header for a block of memory -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDLIB_BLOCK_H +#define LLVM_LIBC_SRC_STDLIB_BLOCK_H + +#include "src/__support/CPP/algorithm.h" +#include "src/__support/CPP/cstddef.h" +#include "src/__support/CPP/limits.h" +#include "src/__support/CPP/optional.h" +#include "src/__support/CPP/span.h" +#include "src/__support/CPP/type_traits.h" + +#include <stdint.h> + +#include "placement_new.h" +#include "status.h" + +namespace LIBC_NAMESPACE { + +namespace internal { +// Types of corrupted blocks, and functions to crash with an error message +// corresponding to each type. +enum BlockStatus { + kValid, + kMisaligned, + kPrevMismatched, + kNextMismatched, +}; +} // namespace internal + +/// Returns the value rounded down to the nearest multiple of alignment. +constexpr size_t AlignDown(size_t value, size_t alignment) { + // PW_ASSERT(!PW_MUL_OVERFLOW((value / alignment), alignment, &value)); + __builtin_mul_overflow(value / alignment, alignment, &value); + return value; +} + +/// Returns the value rounded down to the nearest multiple of alignment. +template <typename T> constexpr T *AlignDown(T *value, size_t alignment) { + return reinterpret_cast<T *>( + AlignDown(reinterpret_cast<size_t>(value), alignment)); +} + +/// Returns the value rounded up to the nearest multiple of alignment. +constexpr size_t AlignUp(size_t value, size_t alignment) { + // PW_ASSERT(!PW_ADD_OVERFLOW(value, alignment - 1, &value)); + __builtin_add_overflow(value, alignment - 1, &value); + return AlignDown(value, alignment); +} + +/// Returns the value rounded up to the nearest multiple of alignment. +template <typename T> constexpr T *AlignUp(T *value, size_t alignment) { + return reinterpret_cast<T *>( + AlignUp(reinterpret_cast<size_t>(value), alignment)); +} + +using ByteSpan = cpp::span<LIBC_NAMESPACE::cpp::byte>; +using cpp::optional; + +/// Memory region with links to adjacent blocks. +/// +/// The blocks do not encode their size directly. Instead, they encode offsets +/// to the next and previous blocks using the type given by the `OffsetType` +/// template parameter. The encoded offsets are simply the offsets divded by the +/// minimum block alignment, `kAlignment`. +/// +/// The `kAlignment` constant provided by the derived block is typically the +/// minimum value of `alignof(OffsetType)`. Since the addressable range of a +/// block is given by `std::numeric_limits<OffsetType>::max() * +/// kAlignment`, it may be advantageous to set a higher alignment if it allows +/// using a smaller offset type, even if this wastes some bytes in order to +/// align block headers. +/// +/// Blocks will always be aligned to a `kAlignment` boundary. Block sizes will +/// always be rounded up to a multiple of `kAlignment`. +/// +/// As an example, the diagram below represents two contiguous +/// `Block<uint32_t, true, 8>`s. The indices indicate byte offsets: +/// +/// @code{.unparsed} +/// Block 1: +/// +---------------------+------+--------------+ +/// | Header | Info | Usable space | +/// +----------+----------+------+--------------+ +/// | Prev | Next | | | +/// | 0......3 | 4......7 | 8..9 | 10.......280 | +/// | 00000000 | 00000046 | 8008 | <app data> | +/// +----------+----------+------+--------------+ +/// Block 2: +/// +---------------------+------+--------------+ +/// | Header | Info | Usable space | +/// +----------+----------+------+--------------+ +/// | Prev | Next | | | +/// | 0......3 | 4......7 | 8..9 | 10......1056 | +/// | 00000046 | 00000106 | 6008 | f7f7....f7f7 | +/// +----------+----------+------+--------------+ +/// @endcode +/// +/// The overall size of the block (e.g. 280 bytes) is given by its next offset +/// multiplied by the alignment (e.g. 0x106 * 4). Also, the next offset of a +/// block matches the previous offset of its next block. The first block in a +/// list is denoted by having a previous offset of `0`. +/// +/// @tparam OffsetType Unsigned integral type used to encode offsets. Larger +/// types can address more memory, but consume greater +/// overhead. +/// @tparam kAlign Sets the overall alignment for blocks. Minimum is +/// `alignof(OffsetType)` (the default). Larger values can +/// address more memory, but consume greater overhead. +template <typename OffsetType = uintptr_t, size_t kAlign = alignof(OffsetType)> +class Block { +public: + using offset_type = OffsetType; + static_assert(cpp::is_unsigned_v<offset_type>, + "offset type must be unsigned"); + + static constexpr size_t kAlignment = cpp::max(kAlign, alignof(offset_type)); + static constexpr size_t kBlockOverhead = AlignUp(sizeof(Block), kAlignment); + + // No copy or move. + Block(const Block &other) = delete; + Block &operator=(const Block &other) = delete; + + /// @brief Creates the first block for a given memory region. + /// + /// @returns @rst + /// + /// .. pw-status-codes:: + /// + /// OK: Returns a block representing the region. + /// + /// INVALID_ARGUMENT: The region is null. + /// + /// RESOURCE_EXHAUSTED: The region is too small for a block. + /// + /// OUT_OF_RANGE: The region is too big to be addressed using + /// ``OffsetType``. + /// + /// @endrst + static optional<Block *> Init(ByteSpan region); + + /// @returns A pointer to a `Block`, given a pointer to the start of the + /// usable space inside the block. + /// + /// This is the inverse of `UsableSpace()`. + /// + /// @warning This method does not do any checking; passing a random + /// pointer will return a non-null pointer. + static Block *FromUsableSpace(void *usable_space) { + auto *bytes = reinterpret_cast<cpp::byte *>(usable_space); + return reinterpret_cast<Block *>(bytes - kBlockOverhead); + } + + /// @returns The total size of the block in bytes, including the header. + size_t OuterSize() const { return next_ * kAlignment; } + + /// @returns The number of usable bytes inside the block. + size_t InnerSize() const { return OuterSize() - kBlockOverhead; } + + /// @returns The number of bytes requested using AllocFirst, AllocLast, or + /// Resize. + size_t RequestedSize() const { return InnerSize() - padding_; } + + /// @returns A pointer to the usable space inside this block. + cpp::byte *UsableSpace() { + return reinterpret_cast<cpp::byte *>(this) + kBlockOverhead; + } + + /// Marks the block as free and merges it with any free neighbors. + /// + /// This method is static in order to consume and replace the given block + /// pointer. If neither member is free, the returned pointer will point to the + /// original block. Otherwise, it will point to the new, larger block created + /// by merging adjacent free blocks together. + static void Free(Block *&block); + + /// Attempts to split this block. + /// + /// If successful, the block will have an inner size of `new_inner_size`, + /// rounded up to a `kAlignment` boundary. The remaining space will be + /// returned as a new block. + /// + /// This method may fail if the remaining space is too small to hold a new + /// block. If this method fails for any reason, the original block is + /// unmodified. + /// + /// This method is static in order to consume and replace the given block + /// pointer with a pointer to the new, smaller block. + /// + /// TODO(b/326509341): Remove from the public API when FreeList is no longer + /// in use. + /// + /// @pre The block must not be in use. + /// + /// @returns @rst + /// + /// .. pw-status-codes:: + /// + /// OK: The split completed successfully. + /// + /// FAILED_PRECONDITION: This block is in use and cannot be split. + /// + /// OUT_OF_RANGE: The requested size for this block is greater + /// than the current ``inner_size``. + /// + /// RESOURCE_EXHAUSTED: The remaining space is too small to hold a + /// new block. + /// + /// @endrst + static optional<Block *> Split(Block *&block, size_t new_inner_size); + + /// Merges this block with the one that comes after it. + /// + /// This method is static in order to consume and replace the given block + /// pointer with a pointer to the new, larger block. + /// + /// TODO(b/326509341): Remove from the public API when FreeList is no longer + /// in use. + /// + /// @pre The blocks must not be in use. + /// + /// @returns @rst + /// + /// .. pw-status-codes:: + /// + /// OK: The merge was successful. + /// + /// OUT_OF_RANGE: The given block is the last block. + /// + /// FAILED_PRECONDITION: One or more of the blocks is in use. + /// + /// @endrst + static Status MergeNext(Block *&block); + + /// Fetches the block immediately after this one. + /// + /// For performance, this always returns a block pointer, even if the returned + /// pointer is invalid. The pointer is valid if and only if `Last()` is false. + /// + /// Typically, after calling `Init` callers may save a pointer past the end of + /// the list using `Next()`. This makes it easy to subsequently iterate over + /// the list: + /// @code{.cpp} + /// auto result = Block<>::Init(byte_span); + /// Block<>* begin = *result; + /// Block<>* end = begin->Next(); + /// ... + /// for (auto* block = begin; block != end; block = block->Next()) { + /// // Do something which each block. + /// } + /// @endcode + Block *Next() const; + + /// @copydoc `Next`. + static Block *NextBlock(const Block *block) { + return block == nullptr ? nullptr : block->Next(); + } + + /// @returns The block immediately before this one, or a null pointer if this + /// is the first block. + Block *Prev() const; + + /// @copydoc `Prev`. + static Block *PrevBlock(const Block *block) { + return block == nullptr ? nullptr : block->Prev(); + } + + /// Returns the current alignment of a block. + size_t Alignment() const { return Used() ? info_.alignment : 1; } + + /// Indicates whether the block is in use. + /// + /// @returns `true` if the block is in use or `false` if not. + bool Used() const { return info_.used; } + + /// Indicates whether this block is the last block or not (i.e. whether + /// `Next()` points to a valid block or not). This is needed because + /// `Next()` points to the end of this block, whether there is a valid + /// block there or not. + /// + /// @returns `true` is this is the last block or `false` if not. + bool Last() const { return info_.last; } + + /// Marks this block as in use. + void MarkUsed() { info_.used = 1; } + + /// Marks this block as free. + void MarkFree() { info_.used = 0; } + + /// Marks this block as the last one in the chain. + void MarkLast() { info_.last = 1; } + + /// Clears the last bit from this block. + void ClearLast() { info_.last = 1; } + + /// @brief Checks if a block is valid. + /// + /// @returns `true` if and only if the following conditions are met: + /// * The block is aligned. + /// * The prev/next fields match with the previous and next blocks. + /// * The poisoned bytes are not damaged (if poisoning is enabled). + bool IsValid() const { return CheckStatus() == internal::kValid; } + +private: + /// Consumes the block and returns as a span of bytes. + static ByteSpan AsBytes(Block *&&block); + + /// Consumes the span of bytes and uses it to construct and return a block. + static Block *AsBlock(size_t prev_outer_size, ByteSpan bytes); + + Block(size_t prev_outer_size, size_t outer_size); + + /// Returns a `BlockStatus` that is either kValid or indicates the reason why + /// the block is invalid. + /// + /// If the block is invalid at multiple points, this function will only return + /// one of the reasons. + internal::BlockStatus CheckStatus() const; + + /// Like `Split`, but assumes the caller has already checked to parameters to + /// ensure the split will succeed. + static Block *SplitImpl(Block *&block, size_t new_inner_size); + + /// Offset (in increments of the minimum alignment) from this block to the + /// previous block. 0 if this is the first block. + offset_type prev_ = 0; + + /// Offset (in increments of the minimum alignment) from this block to the + /// next block. Valid even if this is the last block, since it equals the + /// size of the block. + offset_type next_ = 0; + + /// Information about the current state of the block: + /// * If the `used` flag is set, the block's usable memory has been allocated + /// and is being used. + /// * If the `poisoned` flag is set and the `used` flag is clear, the block's + /// usable memory contains a poison pattern that will be checked when the + /// block is allocated. + /// * If the `last` flag is set, the block does not have a next block. + /// * If the `used` flag is set, the alignment represents the requested value + /// when the memory was allocated, which may be less strict than the actual + /// alignment. + struct { + uint16_t used : 1; + uint16_t poisoned : 1; + uint16_t last : 1; + uint16_t alignment : 13; + } info_; + + /// Number of bytes allocated beyond what was requested. This will be at most + /// the minimum alignment, i.e. `alignof(offset_type).` + uint16_t padding_ = 0; +} __attribute__((packed, aligned(kAlign))); + +// Public template method implementations. + +inline ByteSpan GetAlignedSubspan(ByteSpan bytes, size_t alignment) { + if (bytes.data() == nullptr) { + return ByteSpan(); + } + auto unaligned_start = reinterpret_cast<uintptr_t>(bytes.data()); + auto aligned_start = AlignUp(unaligned_start, alignment); + auto unaligned_end = unaligned_start + bytes.size(); + auto aligned_end = AlignDown(unaligned_end, alignment); + if (aligned_end <= aligned_start) { + return ByteSpan(); + } + return bytes.subspan(aligned_start - unaligned_start, + aligned_end - aligned_start); +} + +template <typename OffsetType, size_t kAlign> +optional<Block<OffsetType, kAlign> *> +Block<OffsetType, kAlign>::Init(ByteSpan region) { + optional<ByteSpan> result = GetAlignedSubspan(region, kAlignment); + if (!result) { + return {}; + } + region = result.value(); + if (region.size() < kBlockOverhead) { + return {}; + } + if (cpp::numeric_limits<OffsetType>::max() < region.size() / kAlignment) { + return {}; + } + Block *block = AsBlock(0, region); + block->MarkLast(); + return block; +} + +template <typename OffsetType, size_t kAlign> +void Block<OffsetType, kAlign>::Free(Block *&block) { + if (block == nullptr) { + return; + } + block->MarkFree(); + Block *prev = block->Prev(); + if (MergeNext(prev).ok()) { + block = prev; + } + MergeNext(block).IgnoreError(); +} + +template <typename OffsetType, size_t kAlign> +optional<Block<OffsetType, kAlign> *> +Block<OffsetType, kAlign>::Split(Block *&block, size_t new_inner_size) { + if (block == nullptr) { + return {}; + } + if (block->Used()) { + return {}; + } + size_t old_inner_size = block->InnerSize(); + new_inner_size = AlignUp(new_inner_size, kAlignment); + if (old_inner_size < new_inner_size) { + return {}; + } + if (old_inner_size - new_inner_size < kBlockOverhead) { + return {}; + } + return SplitImpl(block, new_inner_size); +} + +template <typename OffsetType, size_t kAlign> +Block<OffsetType, kAlign> * +Block<OffsetType, kAlign>::SplitImpl(Block *&block, size_t new_inner_size) { + size_t prev_outer_size = block->prev_ * kAlignment; + size_t outer_size1 = new_inner_size + kBlockOverhead; + bool is_last = block->Last(); + ByteSpan bytes = AsBytes(cpp::move(block)); + Block *block1 = AsBlock(prev_outer_size, bytes.subspan(0, outer_size1)); + Block *block2 = AsBlock(outer_size1, bytes.subspan(outer_size1)); + if (is_last) { + block2->MarkLast(); + } else { + block2->Next()->prev_ = block2->next_; + } + block = cpp::move(block1); + return block2; +} + +template <typename OffsetType, size_t kAlign> +Status Block<OffsetType, kAlign>::MergeNext(Block *&block) { + if (block == nullptr) { + return Status::InvalidArgument(); + } + if (block->Last()) { + return Status::OutOfRange(); + } + Block *next = block->Next(); + if (block->Used() || next->Used()) { + return Status::FailedPrecondition(); + } + size_t prev_outer_size = block->prev_ * kAlignment; + bool is_last = next->Last(); + ByteSpan prev_bytes = AsBytes(cpp::move(block)); + ByteSpan next_bytes = AsBytes(cpp::move(next)); + size_t outer_size = prev_bytes.size() + next_bytes.size(); + cpp::byte *merged = ::new (prev_bytes.data()) cpp::byte[outer_size]; + block = AsBlock(prev_outer_size, ByteSpan(merged, outer_size)); + if (is_last) { + block->MarkLast(); + } else { + block->Next()->prev_ = block->next_; + } + return OkStatus(); +} + +template <typename OffsetType, size_t kAlign> +Block<OffsetType, kAlign> *Block<OffsetType, kAlign>::Next() const { + uintptr_t addr = Last() ? 0 : reinterpret_cast<uintptr_t>(this) + OuterSize(); + return reinterpret_cast<Block *>(addr); +} + +template <typename OffsetType, size_t kAlign> +Block<OffsetType, kAlign> *Block<OffsetType, kAlign>::Prev() const { + uintptr_t addr = + (prev_ == 0) ? 0 + : reinterpret_cast<uintptr_t>(this) - (prev_ * kAlignment); + return reinterpret_cast<Block *>(addr); +} + +// Private template method implementations. + +template <typename OffsetType, size_t kAlign> +Block<OffsetType, kAlign>::Block(size_t prev_outer_size, size_t outer_size) { + prev_ = prev_outer_size / kAlignment; + next_ = outer_size / kAlignment; + info_.used = 0; + info_.poisoned = 0; + info_.last = 0; + info_.alignment = kAlignment; +} + +template <typename OffsetType, size_t kAlign> +ByteSpan Block<OffsetType, kAlign>::AsBytes(Block *&&block) { + size_t block_size = block->OuterSize(); + cpp::byte *bytes = new (cpp::move(block)) cpp::byte[block_size]; + return {bytes, block_size}; +} + +template <typename OffsetType, size_t kAlign> +Block<OffsetType, kAlign> * +Block<OffsetType, kAlign>::AsBlock(size_t prev_outer_size, ByteSpan bytes) { + return ::new (bytes.data()) Block(prev_outer_size, bytes.size()); +} + +template <typename OffsetType, size_t kAlign> +internal::BlockStatus Block<OffsetType, kAlign>::CheckStatus() const { + if (reinterpret_cast<uintptr_t>(this) % kAlignment != 0) { + return internal::kMisaligned; + } + if (!Last() && (this >= Next() || this != Next()->Prev())) { + return internal::kNextMismatched; + } + if (Prev() && (this <= Prev() || this != Prev()->Next())) { + return internal::kPrevMismatched; + } + return internal::kValid; +} + +} // namespace LIBC_NAMESPACE + +#endif // LLVM_LIBC_SRC_STDLIB_BLOCK_H diff --git a/libc/src/stdlib/calloc.h b/libc/src/stdlib/calloc.h new file mode 100644 index 0000000000000..06c9430a2e266 --- /dev/null +++ b/libc/src/stdlib/calloc.h @@ -0,0 +1,20 @@ +//===-- Implementation header for calloc ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include <stdlib.h> + +#ifndef LLVM_LIBC_SRC_STDLIB_CALLOC_H +#define LLVM_LIBC_SRC_STDLIB_CALLOC_H + +namespace LIBC_NAMESPACE { + +void *calloc(size_t num, size_t size); + +} // namespace LIBC_NAMESPACE + +#endif // LLVM_LIBC_SRC_STDLIB_CALLOC_H diff --git a/libc/src/stdlib/free.h b/libc/src/stdlib/free.h index f802f1d192d81..b3970fd967740 100644 --- a/libc/src/stdlib/free.h +++ b/libc/src/stdlib/free.h @@ -17,4 +17,4 @@ void free(void *ptr); } // namespace LIBC_NAMESPACE -#endif // LLVM_LIBC_SRC_STDLIB_LDIV_H +#endif // LLVM_LIBC_SRC_STDLIB_FREE_H diff --git a/libc/src/stdlib/freelist.cpp b/libc/src/stdlib/freelist.cpp new file mode 100644 index 0000000000000..c65164c24a2e6 --- /dev/null +++ b/libc/src/stdlib/freelist.cpp @@ -0,0 +1,125 @@ +#include "freelist.h" +#include "src/__support/CPP/span.h" +#include <stddef.h> + +using LIBC_NAMESPACE::cpp::span; + +namespace pw::allocator { + +template <size_t kNumBuckets> +Status FreeList<kNumBuckets>::AddChunk(span<LIBC_NAMESPACE::cpp::byte> chunk) { + // Check that the size is enough to actually store what we need + if (chunk.size() < sizeof(FreeListNode)) { + return Status::OutOfRange(); + } + + union { + FreeListNode *node; + LIBC_NAMESPACE::cpp::byte *bytes; + } aliased; + + aliased.bytes = chunk.data(); + + unsigned short chunk_ptr = FindChunkPtrForSize(chunk.size(), false); + + // Add it to the correct list. + aliased.node->size = chunk.size(); + aliased.node->next = chunks_[chunk_ptr]; + chunks_[chunk_ptr] = aliased.node; + + return OkStatus(); +} + +template <size_t kNumBuckets> +span<LIBC_NAMESPACE::cpp::byte> +FreeList<kNumBuckets>::FindChunk(size_t size) const { + if (size == 0) { + return span<LIBC_NAMESPACE::cpp::byte>(); + } + + unsigned short chunk_ptr = FindChunkPtrForSize(size, true); + + // Check that there's data. This catches the case where we run off the + // end of the array + if (chunks_[chunk_ptr] == nullptr) { + return span<LIBC_NAMESPACE::cpp::byte>(); + } + + // Now iterate up the buckets, walking each list to find a good candidate + for (size_t i = chunk_ptr; i < chunks_.size(); i++) { + union { + FreeListNode *node; + LIBC_NAMESPACE::cpp::byte *data; + } aliased; + aliased.node = chunks_[static_cast<unsigned short>(i)]; + + while (aliased.node != nullptr) { + if (aliased.node->size >= size) { + return span<LIBC_NAMESPACE::cpp::byte>(aliased.data, + aliased.node->size); + } + + aliased.node = aliased.node->next; + } + } + + // If we get here, we've checked every block in every bucket. There's + // nothing that can support this allocation. + return span<LIBC_NAMESPACE::cpp::byte>(); +} + +template <size_t kNumBuckets> +Status +FreeList<kNumBuckets>::RemoveChunk(span<LIBC_NAMESPACE::cpp::byte> chunk) { + unsigned short chunk_ptr = FindChunkPtrForSize(chunk.size(), true); + + // Walk that list, finding the chunk. + union { + FreeListNode *node; + LIBC_NAMESPACE::cpp::byte *data; + } aliased, aliased_next; + + // Check head first. + if (chunks_[chunk_ptr] == nullptr) { + return Status::NotFound(); + } + + aliased.node = chunks_[chunk_ptr]; + if (aliased.data == chunk.data()) { + chunks_[chunk_ptr] = aliased.node->next; + + return OkStatus(); + } + + // No? Walk the nodes. + aliased.node = chunks_[chunk_ptr]; + + while (aliased.node->next != nullptr) { + aliased_next.node = aliased.node->next; + if (aliased_next.data == chunk.data()) { + // Found it, remove this node out of the chain + aliased.node->next = aliased_next.node->next; + return OkStatus(); + } + + aliased.node = aliased.node->next; + } + + return Status::NotFound(); +} + +template <size_t kNumBuckets> +unsigned short FreeList<kNumBuckets>::FindChunkPtrForSize(size_t size, + bool non_null) const { + unsigned short chunk_ptr = 0; + for (chunk_ptr = 0u; chunk_ptr < sizes_.size(); chunk_ptr++) { + if (sizes_[chunk_ptr] >= size && + (!non_null || chunks_[chunk_ptr] != nullptr)) { + break; + } + } + + return chunk_ptr; +} + +} // namespace pw::allocator diff --git a/libc/src/stdlib/freelist.h b/libc/src/stdlib/freelist.h new file mode 100644 index 0000000000000..7c9a181b7d6b4 --- /dev/null +++ b/libc/src/stdlib/freelist.h @@ -0,0 +1,226 @@ +//===-- Interface for freelist_malloc -------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDLIB_FREELIST_H +#define LLVM_LIBC_SRC_STDLIB_FREELIST_H + +#include "src/__support/CPP/array.h" +#include "src/__support/CPP/cstddef.h" +#include "src/__support/CPP/span.h" +#include "src/__support/fixedvector.h" +#include "status.h" + +namespace LIBC_NAMESPACE { + +using cpp::span; + +/// Basic [freelist](https://en.wikipedia.org/wiki/Free_list) implementation +/// for an allocator. This implementation buckets by chunk size, with a list +/// of user-provided buckets. Each bucket is a linked list of storage chunks. +/// Because this freelist uses the added chunks themselves as list nodes, there +/// is a lower bound of `sizeof(FreeList.FreeListNode)` bytes for chunks which +/// can be added to this freelist. There is also an implicit bucket for +/// "everything else", for chunks which do not fit into a bucket. +/// +/// Each added chunk will be added to the smallest bucket under which it fits. +/// If it does not fit into any user-provided bucket, it will be added to the +/// default bucket. +/// +/// As an example, assume that the `FreeList` is configured with buckets of +/// sizes {64, 128, 256, and 512} bytes. The internal state may look like the +/// following: +/// +/// @code{.unparsed} +/// bucket[0] (64B) --> chunk[12B] --> chunk[42B] --> chunk[64B] --> NULL +/// bucket[1] (128B) --> chunk[65B] --> chunk[72B] --> NULL +/// bucket[2] (256B) --> NULL +/// bucket[3] (512B) --> chunk[312B] --> chunk[512B] --> chunk[416B] --> NULL +/// bucket[4] (implicit) --> chunk[1024B] --> chunk[513B] --> NULL +/// @endcode +/// +/// Note that added chunks should be aligned to a 4-byte boundary. +template <size_t kNumBuckets = 6> class FreeList { +public: + // Remove copy/move ctors + FreeList(const FreeList &other) = delete; + FreeList(FreeList &&other) = delete; + FreeList &operator=(const FreeList &other) = delete; + FreeList &operator=(FreeList &&other) = delete; + + /// Adds a chunk to this freelist. + /// + /// @returns @rst + /// + /// .. pw-status-codes:: + /// + /// OK: The chunk was added successfully. + /// + /// OUT_OF_RANGE: The chunk could not be added for size reasons (e.g. the + /// chunk is too small to store the ``FreeListNode``). + /// + /// @endrst + Status AddChunk(cpp::span<cpp::byte> chunk); + + /// Finds an eligible chunk for an allocation of size `size`. + /// + /// @note This returns the first allocation possible within a given bucket; + /// It does not currently optimize for finding the smallest chunk. + /// + /// @returns + /// * On success - A span representing the chunk. + /// * On failure (e.g. there were no chunks available for that allocation) - + /// A span with a size of 0. + cpp::span<cpp::byte> FindChunk(size_t size) const; + + /// Removes a chunk from this freelist. + /// + /// @returns @rst + /// + /// .. pw-status-codes:: + /// + /// OK: The chunk was removed successfully. + /// + /// NOT_FOUND: The chunk could not be found in this freelist. + /// + /// @endrst + Status RemoveChunk(cpp::span<cpp::byte> chunk); + +private: + // For a given size, find which index into chunks_ the node should be written + // to. + unsigned short FindChunkPtrForSize(size_t size, bool non_null) const; + + struct FreeListNode { + FreeListNode *next; + size_t size; + }; + +public: + explicit FreeList(cpp::array<size_t, kNumBuckets> sizes) + : chunks_(kNumBuckets + 1, 0), sizes_(sizes.begin(), sizes.end()) {} + + FixedVector<FreeList::FreeListNode *, kNumBuckets + 1> chunks_; + FixedVector<size_t, kNumBuckets> sizes_; +}; + +template <size_t kNumBuckets> +Status FreeList<kNumBuckets>::AddChunk(span<cpp::byte> chunk) { + // Check that the size is enough to actually store what we need + if (chunk.size() < sizeof(FreeListNode)) { + return Status::OutOfRange(); + } + + union { + FreeListNode *node; + cpp::byte *bytes; + } aliased; + + aliased.bytes = chunk.data(); + + unsigned short chunk_ptr = FindChunkPtrForSize(chunk.size(), false); + + // Add it to the correct list. + aliased.node->size = chunk.size(); + aliased.node->next = chunks_[chunk_ptr]; + chunks_[chunk_ptr] = aliased.node; + + return OkStatus(); +} + +template <size_t kNumBuckets> +span<cpp::byte> FreeList<kNumBuckets>::FindChunk(size_t size) const { + if (size == 0) { + return span<cpp::byte>(); + } + + unsigned short chunk_ptr = FindChunkPtrForSize(size, true); + + // Check that there's data. This catches the case where we run off the + // end of the array + if (chunks_[chunk_ptr] == nullptr) { + return span<cpp::byte>(); + } + + // Now iterate up the buckets, walking each list to find a good candidate + for (size_t i = chunk_ptr; i < chunks_.size(); i++) { + union { + FreeListNode *node; + cpp::byte *data; + } aliased; + aliased.node = chunks_[static_cast<unsigned short>(i)]; + + while (aliased.node != nullptr) { + if (aliased.node->size >= size) { + return span<cpp::byte>(aliased.data, aliased.node->size); + } + + aliased.node = aliased.node->next; + } + } + + // If we get here, we've checked every block in every bucket. There's + // nothing that can support this allocation. + return span<cpp::byte>(); +} + +template <size_t kNumBuckets> +Status FreeList<kNumBuckets>::RemoveChunk(span<cpp::byte> chunk) { + unsigned short chunk_ptr = FindChunkPtrForSize(chunk.size(), true); + + // Walk that list, finding the chunk. + union { + FreeListNode *node; + cpp::byte *data; + } aliased, aliased_next; + + // Check head first. + if (chunks_[chunk_ptr] == nullptr) { + return Status::NotFound(); + } + + aliased.node = chunks_[chunk_ptr]; + if (aliased.data == chunk.data()) { + chunks_[chunk_ptr] = aliased.node->next; + + return OkStatus(); + } + + // No? Walk the nodes. + aliased.node = chunks_[chunk_ptr]; + + while (aliased.node->next != nullptr) { + aliased_next.node = aliased.node->next; + if (aliased_next.data == chunk.data()) { + // Found it, remove this node out of the chain + aliased.node->next = aliased_next.node->next; + return OkStatus(); + } + + aliased.node = aliased.node->next; + } + + return Status::NotFound(); +} + +template <size_t kNumBuckets> +unsigned short FreeList<kNumBuckets>::FindChunkPtrForSize(size_t size, + bool non_null) const { + unsigned short chunk_ptr = 0; + for (chunk_ptr = 0u; chunk_ptr < sizes_.size(); chunk_ptr++) { + if (sizes_[chunk_ptr] >= size && + (!non_null || chunks_[chunk_ptr] != nullptr)) { + break; + } + } + + return chunk_ptr; +} + +} // namespace LIBC_NAMESPACE + +#endif // LLVM_LIBC_SRC_STDLIB_FREELIST_H diff --git a/libc/src/stdlib/freelist_heap.h b/libc/src/stdlib/freelist_heap.h new file mode 100644 index 0000000000000..ad69d53d3cc29 --- /dev/null +++ b/libc/src/stdlib/freelist_heap.h @@ -0,0 +1,237 @@ +//===-- Interface for freelist_heap ---------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H +#define LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H + +#include <stddef.h> + +#include "block.h" +#include "freelist.h" +#include "src/__support/CPP/optional.h" +#include "src/__support/CPP/span.h" +#include "src/string/memcpy.h" +#include "src/string/memset.h" + +namespace LIBC_NAMESPACE { + +void MallocInit(uint8_t *heap_low_addr, uint8_t *heap_high_addr); + +using cpp::optional; +using cpp::span; + +template <size_t kNumBuckets> class FreeListHeap { +public: + using BlockType = Block<>; + + template <size_t> friend class FreeListHeapBuffer; + + struct HeapStats { + size_t total_bytes; + size_t bytes_allocated; + size_t cumulative_allocated; + size_t cumulative_freed; + size_t total_allocate_calls; + size_t total_free_calls; + }; + FreeListHeap(span<cpp::byte> region, FreeList<kNumBuckets> &freelist); + + void *Allocate(size_t size); + void Free(void *ptr); + void *Realloc(void *ptr, size_t size); + void *Calloc(size_t num, size_t size); + + void LogHeapStats(); + +private: + span<cpp::byte> BlockToSpan(BlockType *block) { + return span<cpp::byte>(block->UsableSpace(), block->InnerSize()); + } + + void InvalidFreeCrash() { __builtin_trap(); } + + span<cpp::byte> region_; + FreeList<kNumBuckets> &freelist_; + HeapStats heap_stats_; +}; + +static constexpr cpp::array<size_t, 6> defaultBuckets{16, 32, 64, + 128, 256, 512}; + +template <size_t kNumBuckets = defaultBuckets.size()> class FreeListHeapBuffer { +public: + FreeListHeapBuffer(span<cpp::byte> region) + : freelist_(defaultBuckets), heap_(region, freelist_) {} + + void *Allocate(size_t size) { return heap_.Allocate(size); } + void Free(void *ptr) { heap_.Free(ptr); } + void *Realloc(void *ptr, size_t size) { return heap_.Realloc(ptr, size); } + void *Calloc(size_t num, size_t size) { return heap_.Calloc(num, size); } + + const typename FreeListHeap<kNumBuckets>::HeapStats &heap_stats() const { + return heap_.heap_stats_; + } + + void LogHeapStats() { heap_.LogHeapStats(); } + +private: + FreeList<kNumBuckets> freelist_; + FreeListHeap<kNumBuckets> heap_; +}; + +template <size_t kNumBuckets> +FreeListHeap<kNumBuckets>::FreeListHeap(span<cpp::byte> region, + FreeList<kNumBuckets> &freelist) + : freelist_(freelist), heap_stats_() { + auto result = BlockType::Init(region); + BlockType *block = *result; + + freelist_.AddChunk(BlockToSpan(block)) + .IgnoreError(); // TODO: b/242598609 - Handle Status properly + + region_ = region; + heap_stats_.total_bytes = region.size(); +} + +template <size_t kNumBuckets> +void *FreeListHeap<kNumBuckets>::Allocate(size_t size) { + // Find a chunk in the freelist. Split it if needed, then return + + auto chunk = freelist_.FindChunk(size); + + if (chunk.data() == nullptr) { + return nullptr; + } + freelist_.RemoveChunk(chunk) + .IgnoreError(); // TODO: b/242598609 - Handle Status properly + + BlockType *chunk_block = BlockType::FromUsableSpace(chunk.data()); + + // Split that chunk. If there's a leftover chunk, add it to the freelist + optional<BlockType *> result = BlockType::Split(chunk_block, size); + if (result) { + freelist_.AddChunk(BlockToSpan(*result)) + .IgnoreError(); // TODO: b/242598609 - Handle Status properly + } + + chunk_block->MarkUsed(); + + heap_stats_.bytes_allocated += size; + heap_stats_.cumulative_allocated += size; + heap_stats_.total_allocate_calls += 1; + + return chunk_block->UsableSpace(); +} + +template <size_t kNumBuckets> void FreeListHeap<kNumBuckets>::Free(void *ptr) { + cpp::byte *bytes = static_cast<cpp::byte *>(ptr); + + if (bytes < region_.data() || bytes >= region_.data() + region_.size()) { + InvalidFreeCrash(); + return; + } + + BlockType *chunk_block = BlockType::FromUsableSpace(bytes); + + size_t size_freed = chunk_block->InnerSize(); + // Ensure that the block is in-use + if (!chunk_block->Used()) { + InvalidFreeCrash(); + return; + } + chunk_block->MarkFree(); + // Can we combine with the left or right blocks? + BlockType *prev = chunk_block->Prev(); + BlockType *next = nullptr; + + if (!chunk_block->Last()) { + next = chunk_block->Next(); + } + + if (prev != nullptr && !prev->Used()) { + // Remove from freelist and merge + freelist_.RemoveChunk(BlockToSpan(prev)) + .IgnoreError(); // TODO: b/242598609 - Handle Status properly + chunk_block = chunk_block->Prev(); + BlockType::MergeNext(chunk_block) + .IgnoreError(); // TODO: b/242598609 - Handle Status properly + } + + if (next != nullptr && !next->Used()) { + freelist_.RemoveChunk(BlockToSpan(next)) + .IgnoreError(); // TODO: b/242598609 - Handle Status properly + BlockType::MergeNext(chunk_block) + .IgnoreError(); // TODO: b/242598609 - Handle Status properly + } + // Add back to the freelist + freelist_.AddChunk(BlockToSpan(chunk_block)) + .IgnoreError(); // TODO: b/242598609 - Handle Status properly + + heap_stats_.bytes_allocated -= size_freed; + heap_stats_.cumulative_freed += size_freed; + heap_stats_.total_free_calls += 1; +} + +// Follows constract of the C standard realloc() function +// If ptr is free'd, will return nullptr. +template <size_t kNumBuckets> +void *FreeListHeap<kNumBuckets>::Realloc(void *ptr, size_t size) { + if (size == 0) { + Free(ptr); + return nullptr; + } + + // If the pointer is nullptr, allocate a new memory. + if (ptr == nullptr) { + return Allocate(size); + } + + cpp::byte *bytes = static_cast<cpp::byte *>(ptr); + + // TODO(chenghanzh): Enhance with debug information for out-of-range and more. + if (bytes < region_.data() || bytes >= region_.data() + region_.size()) { + return nullptr; + } + + BlockType *chunk_block = BlockType::FromUsableSpace(bytes); + if (!chunk_block->Used()) { + return nullptr; + } + size_t old_size = chunk_block->InnerSize(); + + // Do nothing and return ptr if the required memory size is smaller than + // the current size. + if (old_size >= size) { + return ptr; + } + + void *new_ptr = Allocate(size); + // Don't invalidate ptr if Allocate(size) fails to initilize the memory. + if (new_ptr == nullptr) { + return nullptr; + } + memcpy(new_ptr, ptr, old_size); + + Free(ptr); + return new_ptr; +} + +template <size_t kNumBuckets> +void *FreeListHeap<kNumBuckets>::Calloc(size_t num, size_t size) { + void *ptr = Allocate(num * size); + if (ptr != nullptr) { + memset(ptr, 0, num * size); + } + return ptr; +} + +extern FreeListHeapBuffer<> *freelist_heap; + +} // namespace LIBC_NAMESPACE + +#endif // LLVM_LIBC_SRC_STDLIB_FREELIST_HEAP_H diff --git a/libc/src/stdlib/freelist_malloc.cpp b/libc/src/stdlib/freelist_malloc.cpp new file mode 100644 index 0000000000000..b07e0b569f31e --- /dev/null +++ b/libc/src/stdlib/freelist_malloc.cpp @@ -0,0 +1,50 @@ +//===-- Implementation for freelist_malloc --------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "freelist_heap.h" +#include "placement_new.h" +#include "src/__support/CPP/span.h" +#include "src/__support/CPP/type_traits.h" +#include "src/string/memcpy.h" +#include "src/string/memset.h" + +#include <stddef.h> + +namespace LIBC_NAMESPACE { + +namespace { +cpp::aligned_storage_t<sizeof(FreeListHeapBuffer<>), + alignof(FreeListHeapBuffer<>)> + buf; +} // namespace + +FreeListHeapBuffer<> *freelist_heap; + +// Define the global heap variables. +void MallocInit(uint8_t *heap_low_addr, uint8_t *heap_high_addr) { + cpp::span<LIBC_NAMESPACE::cpp::byte> allocator_freelist_raw_heap = + cpp::span<cpp::byte>(reinterpret_cast<cpp::byte *>(heap_low_addr), + heap_high_addr - heap_low_addr); + freelist_heap = new (&buf) FreeListHeapBuffer<>(allocator_freelist_raw_heap); +} + +void *malloc(size_t size) { return freelist_heap->Allocate(size); } + +void free(void *ptr) { freelist_heap->Free(ptr); } + +void *calloc(size_t num, size_t size) { + return freelist_heap->Calloc(num, size); +} + +} // namespace LIBC_NAMESPACE + +extern "C" { +using LIBC_NAMESPACE::calloc; +using LIBC_NAMESPACE::free; +using LIBC_NAMESPACE::malloc; +} // extern "C" diff --git a/libc/src/stdlib/placement_new.h b/libc/src/stdlib/placement_new.h new file mode 100644 index 0000000000000..1a07eee13545a --- /dev/null +++ b/libc/src/stdlib/placement_new.h @@ -0,0 +1,17 @@ +//===-- Interface for placement new ---------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDLIB_PLACEMENT_NEW_H +#define LLVM_LIBC_SRC_STDLIB_PLACEMENT_NEW_H + +// FIXME: These should go inside new.h, but we can't use that header internally +// because it depends on defining aligned_alloc. +inline void *operator new(size_t, void *__p) { return __p; } +inline void *operator new[](size_t, void *__p) { return __p; } + +#endif // LLVM_LIBC_SRC_STDLIB_PLACEMENT_NEW_H diff --git a/libc/src/stdlib/status.h b/libc/src/stdlib/status.h new file mode 100644 index 0000000000000..2b738c5ddae10 --- /dev/null +++ b/libc/src/stdlib/status.h @@ -0,0 +1,370 @@ +//===-- Interface for malloc status 00-------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_STDLIB_STATUS_H +#define LLVM_LIBC_SRC_STDLIB_STATUS_H + +#include "src/__support/CPP/type_traits.h" + +// This is the Status enum. Status is used to return the status from an +// operation. +// +// In C++, use the Status class instead of the Status enum. Status and +// Status implicitly convert to one another and can be passed cleanly between C +// and C++ APIs. +// +// Status uses the canonical Google error codes. The following enum was based +// on Abseil's status/status.h. +typedef enum { + STATUS_OK = 0, // Use OkStatus() in C++ + STATUS_CANCELLED = 1, // Use Status::Cancelled() in C++ + STATUS_UNKNOWN = 2, // Use Status::Unknown() in C++ + STATUS_INVALID_ARGUMENT = 3, // Use Status::InvalidArgument() in C++ + STATUS_DEADLINE_EXCEEDED = 4, // Use Status::DeadlineExceeded() in C++ + STATUS_NOT_FOUND = 5, // Use Status::NotFound() in C++ + STATUS_ALREADY_EXISTS = 6, // Use Status::AlreadyExists() in C++ + STATUS_PERMISSION_DENIED = 7, // Use Status::PermissionDenied() in C++ + STATUS_RESOURCE_EXHAUSTED = 8, // Use Status::ResourceExhausted() in C++ + STATUS_FAILED_PRECONDITION = 9, // Use Status::FailedPrecondition() in C++ + STATUS_ABORTED = 10, // Use Status::Aborted() in C++ + STATUS_OUT_OF_RANGE = 11, // Use Status::OutOfRange() in C++ + STATUS_UNIMPLEMENTED = 12, // Use Status::Unimplemented() in C++ + STATUS_INTERNAL = 13, // Use Status::Internal() in C++ + STATUS_UNAVAILABLE = 14, // Use Status::Unavailable() in C++ + STATUS_DATA_LOSS = 15, // Use Status::DataLoss() in C++ + STATUS_UNAUTHENTICATED = 16, // Use Status::Unauthenticated() in C++ + + // NOTE: this error code entry should not be used and you should not rely on + // its value, which may change. + // + // The purpose of this enumerated value is to force people who handle status + // codes with `switch()` statements to *not* simply enumerate all possible + // values, but instead provide a "default:" case. Providing such a default + // case ensures that code will compile when new codes are added. + STATUS_DO_NOT_USE_RESERVED_FOR_FUTURE_EXPANSION_USE_DEFAULT_IN_SWITCH_INSTEAD_, +} StatusKind; // Use Status in C++ + +/// `Status` is a thin, zero-cost abstraction around the `Status` enum. It +/// initializes to @status{OK} by default and adds `ok()` and `str()` +/// methods. Implicit conversions are permitted between `Status` and +/// `Status`. +/// +/// An @status{OK} `Status` is created by the @cpp_func{OkStatus} +/// function or by the default `Status` constructor. Non-OK `Status` is created +/// with a static member function that corresponds with the status code. +class Status { +public: + using Code = StatusKind; + + // Functions that create a Status with the specified code. + [[nodiscard]] static constexpr Status Cancelled() { return STATUS_CANCELLED; } + [[nodiscard]] static constexpr Status Unknown() { return STATUS_UNKNOWN; } + [[nodiscard]] static constexpr Status InvalidArgument() { + return STATUS_INVALID_ARGUMENT; + } + [[nodiscard]] static constexpr Status DeadlineExceeded() { + return STATUS_DEADLINE_EXCEEDED; + } + [[nodiscard]] static constexpr Status NotFound() { return STATUS_NOT_FOUND; } + [[nodiscard]] static constexpr Status AlreadyExists() { + return STATUS_ALREADY_EXISTS; + } + [[nodiscard]] static constexpr Status PermissionDenied() { + return STATUS_PERMISSION_DENIED; + } + [[nodiscard]] static constexpr Status ResourceExhausted() { + return STATUS_RESOURCE_EXHAUSTED; + } + [[nodiscard]] static constexpr Status FailedPrecondition() { + return STATUS_FAILED_PRECONDITION; + } + [[nodiscard]] static constexpr Status Aborted() { return STATUS_ABORTED; } + [[nodiscard]] static constexpr Status OutOfRange() { + return STATUS_OUT_OF_RANGE; + } + [[nodiscard]] static constexpr Status Unimplemented() { + return STATUS_UNIMPLEMENTED; + } + [[nodiscard]] static constexpr Status Internal() { return STATUS_INTERNAL; } + [[nodiscard]] static constexpr Status Unavailable() { + return STATUS_UNAVAILABLE; + } + [[nodiscard]] static constexpr Status DataLoss() { return STATUS_DATA_LOSS; } + [[nodiscard]] static constexpr Status Unauthenticated() { + return STATUS_UNAUTHENTICATED; + } + // clang-format on + + // Statuses are created with a Status::Code. + constexpr Status(Code code = STATUS_OK) : code_(code) {} + + constexpr Status(const Status &) = default; + constexpr Status &operator=(const Status &) = default; + + /// Returns the `Status::Code` (`Status`) for this `Status`. + constexpr Code code() const { return code_; } + + /// True if the status is @status{OK}. + /// + /// This function is provided in place of an `IsOk()` function. + [[nodiscard]] constexpr bool ok() const { return code_ == STATUS_OK; } + + // Functions for checking which status this is. + [[nodiscard]] constexpr bool IsCancelled() const { + return code_ == STATUS_CANCELLED; + } + [[nodiscard]] constexpr bool IsUnknown() const { + return code_ == STATUS_UNKNOWN; + } + [[nodiscard]] constexpr bool IsInvalidArgument() const { + return code_ == STATUS_INVALID_ARGUMENT; + } + [[nodiscard]] constexpr bool IsDeadlineExceeded() const { + return code_ == STATUS_DEADLINE_EXCEEDED; + } + [[nodiscard]] constexpr bool IsNotFound() const { + return code_ == STATUS_NOT_FOUND; + } + [[nodiscard]] constexpr bool IsAlreadyExists() const { + return code_ == STATUS_ALREADY_EXISTS; + } + [[nodiscard]] constexpr bool IsPermissionDenied() const { + return code_ == STATUS_PERMISSION_DENIED; + } + [[nodiscard]] constexpr bool IsResourceExhausted() const { + return code_ == STATUS_RESOURCE_EXHAUSTED; + } + [[nodiscard]] constexpr bool IsFailedPrecondition() const { + return code_ == STATUS_FAILED_PRECONDITION; + } + [[nodiscard]] constexpr bool IsAborted() const { + return code_ == STATUS_ABORTED; + } + [[nodiscard]] constexpr bool IsOutOfRange() const { + return code_ == STATUS_OUT_OF_RANGE; + } + [[nodiscard]] constexpr bool IsUnimplemented() const { + return code_ == STATUS_UNIMPLEMENTED; + } + [[nodiscard]] constexpr bool IsInternal() const { + return code_ == STATUS_INTERNAL; + } + [[nodiscard]] constexpr bool IsUnavailable() const { + return code_ == STATUS_UNAVAILABLE; + } + [[nodiscard]] constexpr bool IsDataLoss() const { + return code_ == STATUS_DATA_LOSS; + } + [[nodiscard]] constexpr bool IsUnauthenticated() const { + return code_ == STATUS_UNAUTHENTICATED; + } + + /// Updates this `Status` to the provided `Status` IF this status is + /// @status{OK}. This is useful for tracking the first encountered error, + /// as calls to this helper will not change one error status to another error + /// status. + constexpr void Update(Status other) { + if (ok()) { + code_ = other.code(); + } + } + + /// Ignores any errors. This method does nothing except potentially suppress + /// complaints from any tools that are checking that errors are not dropped on + /// the floor. + constexpr void IgnoreError() const {} + +private: + Code code_; +}; + +class StatusWithSize { +public: + static constexpr StatusWithSize Cancelled(size_t size = 0) { + return StatusWithSize(Status::Cancelled(), size); + } + static constexpr StatusWithSize Unknown(size_t size = 0) { + return StatusWithSize(Status::Unknown(), size); + } + static constexpr StatusWithSize InvalidArgument(size_t size = 0) { + return StatusWithSize(Status::InvalidArgument(), size); + } + static constexpr StatusWithSize DeadlineExceeded(size_t size = 0) { + return StatusWithSize(Status::DeadlineExceeded(), size); + } + static constexpr StatusWithSize NotFound(size_t size = 0) { + return StatusWithSize(Status::NotFound(), size); + } + static constexpr StatusWithSize AlreadyExists(size_t size = 0) { + return StatusWithSize(Status::AlreadyExists(), size); + } + static constexpr StatusWithSize PermissionDenied(size_t size = 0) { + return StatusWithSize(Status::PermissionDenied(), size); + } + static constexpr StatusWithSize Unauthenticated(size_t size = 0) { + return StatusWithSize(Status::Unauthenticated(), size); + } + static constexpr StatusWithSize ResourceExhausted(size_t size = 0) { + return StatusWithSize(Status::ResourceExhausted(), size); + } + static constexpr StatusWithSize FailedPrecondition(size_t size = 0) { + return StatusWithSize(Status::FailedPrecondition(), size); + } + static constexpr StatusWithSize Aborted(size_t size = 0) { + return StatusWithSize(Status::Aborted(), size); + } + static constexpr StatusWithSize OutOfRange(size_t size = 0) { + return StatusWithSize(Status::OutOfRange(), size); + } + static constexpr StatusWithSize Unimplemented(size_t size = 0) { + return StatusWithSize(Status::Unimplemented(), size); + } + static constexpr StatusWithSize Internal(size_t size = 0) { + return StatusWithSize(Status::Internal(), size); + } + static constexpr StatusWithSize Unavailable(size_t size = 0) { + return StatusWithSize(Status::Unavailable(), size); + } + static constexpr StatusWithSize DataLoss(size_t size = 0) { + return StatusWithSize(Status::DataLoss(), size); + } + + // Creates a StatusWithSize with OkStatus() and a size of 0. + explicit constexpr StatusWithSize() : size_(0) {} + + // Creates a StatusWithSize with status OK and the provided size. + // std::enable_if is used to prevent enum types (e.g. Status) from being used. + // TODO(hepler): Add debug-only assert that size <= max_size(). + // template <typename T, typename = + // LIBC_NAMESPACE::cpp::enable_if_t<std::is_integral<T>::value>> explicit + // constexpr StatusWithSize(T size) + // : size_(static_cast<size_t>(size)) {} + explicit constexpr StatusWithSize(size_t size) + : size_(static_cast<size_t>(size)) {} + + // Creates a StatusWithSize with the provided status and size. + explicit constexpr StatusWithSize(Status status, size_t size) + : StatusWithSize((static_cast<size_t>(status.code()) << kStatusShift) | + size) {} + + constexpr StatusWithSize(const StatusWithSize &) = default; + constexpr StatusWithSize &operator=(const StatusWithSize &) = default; + + /// ``Update`` s this status and adds to ``size``. + /// + /// The resulting ``StatusWithSize`` will have a size of + /// ``this->size() + new_status_with_size.size()`` + /// + /// The resulting status will be Ok if both statuses are ``Ok``, + /// otherwise it will take on the earliest non-``Ok`` status. + constexpr void UpdateAndAdd(StatusWithSize new_status_with_size) { + Status new_status; + if (ok()) { + new_status = new_status_with_size.status(); + } else { + new_status = status(); + } + size_t new_size = size() + new_status_with_size.size(); + *this = StatusWithSize(new_status, new_size); + } + + /// Zeros this size if the status is not ``Ok``. + constexpr void ZeroIfNotOk() { + if (!ok()) { + *this = StatusWithSize(status(), 0); + } + } + + // Returns the size. The size is always present, even if status() is an error. + [[nodiscard]] constexpr size_t size() const { return size_ & kSizeMask; } + + // Returns the size if the status() == OkStatus(), or the given default value + // if status() is an error. + [[nodiscard]] constexpr size_t size_or(size_t default_value) { + return ok() ? size() : default_value; + } + + // The maximum valid value for size. + [[nodiscard]] static constexpr size_t max_size() { return kSizeMask; } + + // True if status() == OkStatus(). + [[nodiscard]] constexpr bool ok() const { + return (size_ & kStatusMask) == 0u; + } + + // Ignores any errors. This method does nothing except potentially suppress + // complaints from any tools that are checking that errors are not dropped on + // the floor. + constexpr void IgnoreError() const {} + + [[nodiscard]] constexpr Status status() const { + return static_cast<Status::Code>((size_ & kStatusMask) >> kStatusShift); + } + + // Functions for checking which status the StatusWithSize contains. + [[nodiscard]] constexpr bool IsCancelled() const { + return status().IsCancelled(); + } + [[nodiscard]] constexpr bool IsUnknown() const { + return status().IsUnknown(); + } + [[nodiscard]] constexpr bool IsInvalidArgument() const { + return status().IsInvalidArgument(); + } + [[nodiscard]] constexpr bool IsDeadlineExceeded() const { + return status().IsDeadlineExceeded(); + } + [[nodiscard]] constexpr bool IsNotFound() const { + return status().IsNotFound(); + } + [[nodiscard]] constexpr bool IsAlreadyExists() const { + return status().IsAlreadyExists(); + } + [[nodiscard]] constexpr bool IsPermissionDenied() const { + return status().IsPermissionDenied(); + } + [[nodiscard]] constexpr bool IsResourceExhausted() const { + return status().IsResourceExhausted(); + } + [[nodiscard]] constexpr bool IsFailedPrecondition() const { + return status().IsFailedPrecondition(); + } + [[nodiscard]] constexpr bool IsAborted() const { + return status().IsAborted(); + } + [[nodiscard]] constexpr bool IsOutOfRange() const { + return status().IsOutOfRange(); + } + [[nodiscard]] constexpr bool IsUnimplemented() const { + return status().IsUnimplemented(); + } + [[nodiscard]] constexpr bool IsInternal() const { + return status().IsInternal(); + } + [[nodiscard]] constexpr bool IsUnavailable() const { + return status().IsUnavailable(); + } + [[nodiscard]] constexpr bool IsDataLoss() const { + return status().IsDataLoss(); + } + [[nodiscard]] constexpr bool IsUnauthenticated() const { + return status().IsUnauthenticated(); + } + +private: + static constexpr size_t kStatusBits = 5; + static constexpr size_t kSizeMask = ~static_cast<size_t>(0) >> kStatusBits; + static constexpr size_t kStatusMask = ~kSizeMask; + static constexpr size_t kStatusShift = sizeof(size_t) * 8 - kStatusBits; + + size_t size_; +}; + +[[nodiscard]] constexpr Status OkStatus() { return Status(); } + +#endif // LLVM_LIBC_SRC_STDLIB_STATUS_H diff --git a/libc/src/sys/stat/linux/kernel_statx.h b/libc/src/sys/stat/linux/kernel_statx.h index 60969160b9ba4..5b4b014487c92 100644 --- a/libc/src/sys/stat/linux/kernel_statx.h +++ b/libc/src/sys/stat/linux/kernel_statx.h @@ -73,7 +73,7 @@ LIBC_INLINE int statx(int dirfd, const char *__restrict path, int flags, struct stat *__restrict statbuf) { // We make a statx syscall and copy out the result into the |statbuf|. ::statx_buf xbuf; - int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_statx, dirfd, path, flags, + int ret = LIBC_NAMESPACE::syscall_impl<int>(0, dirfd, path, flags, ::STATX_BASIC_STATS_MASK, &xbuf); if (ret < 0) return -ret; diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt index a5e7a2a4dee72..935feb59ecdf6 100644 --- a/libc/test/src/CMakeLists.txt +++ b/libc/test/src/CMakeLists.txt @@ -61,7 +61,7 @@ add_subdirectory(inttypes) if(${LIBC_TARGET_OS} STREQUAL "linux") add_subdirectory(fcntl) add_subdirectory(sched) - add_subdirectory(sys) + #add_subdirectory(sys) add_subdirectory(termios) add_subdirectory(unistd) endif() diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt index 28c5b566cc477..e7fec98298df5 100644 --- a/libc/test/src/stdlib/CMakeLists.txt +++ b/libc/test/src/stdlib/CMakeLists.txt @@ -373,19 +373,16 @@ if(LLVM_LIBC_FULL_BUILD) libc.src.signal.raise ) - # Only the GPU has an in-tree 'malloc' implementation. - if(LIBC_TARGET_OS_IS_GPU) - add_libc_test( - malloc_test - HERMETIC_TEST_ONLY - SUITE - libc-stdlib-tests - SRCS - malloc_test.cpp - DEPENDS - libc.include.stdlib - libc.src.stdlib.malloc - libc.src.stdlib.free - ) - endif() + add_libc_test( + malloc_test + SUITE + libc-stdlib-tests + SRCS + malloc_test.cpp + freelist_malloc_test.cpp + DEPENDS + libc.include.stdlib + libc.src.stdlib.malloc + libc.src.stdlib.free + ) endif() diff --git a/libc/test/src/stdlib/freelist_malloc_test.cpp b/libc/test/src/stdlib/freelist_malloc_test.cpp new file mode 100644 index 0000000000000..4844ed0127a32 --- /dev/null +++ b/libc/test/src/stdlib/freelist_malloc_test.cpp @@ -0,0 +1,56 @@ +//===-- Unittests for freelist_malloc -------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/stdlib/calloc.h" +#include "src/stdlib/free.h" +#include "src/stdlib/freelist_heap.h" +#include "src/stdlib/malloc.h" +#include "test/UnitTest/Test.h" + +using LIBC_NAMESPACE::freelist_heap; + +TEST(LlvmLibcFreeListMalloc, ReplacingMalloc) { + constexpr size_t kAllocSize = 256; + constexpr size_t kCallocNum = 4; + constexpr size_t kCallocSize = 64; + + uint8_t kBuff[4096]; + LIBC_NAMESPACE::MallocInit(kBuff, kBuff + sizeof(kBuff)); + + void *ptr1 = LIBC_NAMESPACE::malloc(kAllocSize); + + const auto &freelist_heap_stats = freelist_heap->heap_stats(); + + ASSERT_NE(ptr1, static_cast<void *>(nullptr)); + EXPECT_EQ(freelist_heap_stats.bytes_allocated, kAllocSize); + EXPECT_EQ(freelist_heap_stats.cumulative_allocated, kAllocSize); + EXPECT_EQ(freelist_heap_stats.cumulative_freed, size_t(0)); + + LIBC_NAMESPACE::free(ptr1); + EXPECT_EQ(freelist_heap_stats.bytes_allocated, size_t(0)); + EXPECT_EQ(freelist_heap_stats.cumulative_allocated, kAllocSize); + EXPECT_EQ(freelist_heap_stats.cumulative_freed, kAllocSize); + + void *ptr2 = LIBC_NAMESPACE::calloc(kCallocNum, kCallocSize); + ASSERT_NE(ptr2, static_cast<void *>(nullptr)); + EXPECT_EQ(freelist_heap_stats.bytes_allocated, kCallocNum * kCallocSize); + EXPECT_EQ(freelist_heap_stats.cumulative_allocated, + kAllocSize + kCallocNum * kCallocSize); + EXPECT_EQ(freelist_heap_stats.cumulative_freed, kAllocSize); + + for (size_t i = 0; i < kCallocNum * kCallocSize; ++i) { + EXPECT_EQ(reinterpret_cast<uint8_t *>(ptr2)[i], uint8_t(0)); + } + + LIBC_NAMESPACE::free(ptr2); + EXPECT_EQ(freelist_heap_stats.bytes_allocated, size_t(0)); + EXPECT_EQ(freelist_heap_stats.cumulative_allocated, + kAllocSize + kCallocNum * kCallocSize); + EXPECT_EQ(freelist_heap_stats.cumulative_freed, + kAllocSize + kCallocNum * kCallocSize); +} diff --git a/libc/test/src/stdlib/malloc_test.cpp b/libc/test/src/stdlib/malloc_test.cpp index d9023cf56d9fe..6fb07bec9f336 100644 --- a/libc/test/src/stdlib/malloc_test.cpp +++ b/libc/test/src/stdlib/malloc_test.cpp @@ -7,10 +7,14 @@ //===----------------------------------------------------------------------===// #include "src/stdlib/free.h" +#include "src/stdlib/freelist_heap.h" #include "src/stdlib/malloc.h" #include "test/UnitTest/Test.h" TEST(LlvmLibcMallocTest, Allocate) { + uint8_t kBuff[1024]; + LIBC_NAMESPACE::MallocInit(kBuff, kBuff + sizeof(kBuff)); + int *ptr = reinterpret_cast<int *>(LIBC_NAMESPACE::malloc(sizeof(int))); EXPECT_NE(reinterpret_cast<void *>(ptr), static_cast<void *>(nullptr)); *ptr = 1; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits