[clang] [libc] [WIP][libc] Add freelist malloc (PR #94270)

via cfe-commits cfe-commits at lists.llvm.org
Mon Jun 3 11:44:58 PDT 2024


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 <leonardchan at 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;



More information about the cfe-commits mailing list