pw_allocator: replacing malloc()

Add pw_malloc module to replace libc malloc with self defined methods.

Add pw_malloc_freelist backend to provide wrapper functions for freelist
malloc.

Change-Id: I7b645206655068d6c08063d7a520aca62d955052
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/12601
Commit-Queue: Chenghan Zhou <chenghanzh@google.com>
Reviewed-by: Chenghan Zhou <chenghanzh@google.com>
Reviewed-by: Alexei Frolov <frolv@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 30ba175..f4c55a9 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -172,6 +172,7 @@
       "$dir_pw_fuzzer:tests",
       "$dir_pw_log:tests",
       "$dir_pw_log_tokenized:tests",
+      "$dir_pw_malloc_freelist:tests",
       "$dir_pw_polyfill:tests",
       "$dir_pw_preprocessor:tests",
       "$dir_pw_protobuf:tests",
diff --git a/modules.gni b/modules.gni
index 41e0731..378ef6d 100644
--- a/modules.gni
+++ b/modules.gni
@@ -38,6 +38,8 @@
   dir_pw_log = get_path_info("pw_log", "abspath")
   dir_pw_log_basic = get_path_info("pw_log_basic", "abspath")
   dir_pw_log_tokenized = get_path_info("pw_log_tokenized", "abspath")
+  dir_pw_malloc = get_path_info("pw_malloc", "abspath")
+  dir_pw_malloc_freelist = get_path_info("pw_malloc_freelist", "abspath")
   dir_pw_minimal_cpp_stdlib = get_path_info("pw_minimal_cpp_stdlib", "abspath")
   dir_pw_module = get_path_info("pw_module", "abspath")
   dir_pw_fuzzer = get_path_info("pw_fuzzer", "abspath")
diff --git a/pw_allocator/BUILD.gn b/pw_allocator/BUILD.gn
index 93b274e..529a6a5 100644
--- a/pw_allocator/BUILD.gn
+++ b/pw_allocator/BUILD.gn
@@ -23,7 +23,7 @@
 }
 
 group("pw_allocator") {
-  deps = [
+  public_deps = [
     ":block",
     ":freelist",
     ":freelist_heap",
@@ -44,7 +44,7 @@
   public_configs = [ ":default_config" ]
   public = [ "public/pw_allocator/freelist.h" ]
   public_deps = [
-    "$dir_pw_containers",
+    "$dir_pw_containers:vector",
     "$dir_pw_span",
     "$dir_pw_status",
   ]
diff --git a/pw_allocator/freelist_heap.cc b/pw_allocator/freelist_heap.cc
index dfaba56..14f850d 100644
--- a/pw_allocator/freelist_heap.cc
+++ b/pw_allocator/freelist_heap.cc
@@ -19,7 +19,7 @@
 namespace pw::allocator {
 
 FreeListHeap::FreeListHeap(std::span<std::byte> region, FreeList& freelist)
-    : freelist_(freelist) {
+    : freelist_(freelist), heap_stats_() {
   Block* block;
   Block::Init(region, &block);
 
@@ -30,6 +30,7 @@
 
 void* FreeListHeap::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) {
@@ -47,7 +48,8 @@
   }
 
   chunk_block->MarkUsed();
-
+  heap_stats_.bytes_allocated += size;
+  heap_stats_.cumulative_allocated += size;
   return chunk_block->UsableSpace();
 }
 
@@ -59,6 +61,7 @@
   }
 
   Block* chunk_block = Block::FromUsableSpace(bytes);
+  size_t size_freed = chunk_block->InnerSize();
   // Ensure that the block is in-use
   if (!chunk_block->Used()) {
     return;
@@ -87,6 +90,8 @@
   }
   // Add back to the freelist
   freelist_.AddChunk(BlockToSpan(chunk_block));
+  heap_stats_.bytes_allocated -= size_freed;
+  heap_stats_.cumulative_freed += size_freed;
 }
 
 // Follows constract of the C standard realloc() function
diff --git a/pw_allocator/public/pw_allocator/freelist_heap.h b/pw_allocator/public/pw_allocator/freelist_heap.h
index cf44ffd..73ff9d0 100644
--- a/pw_allocator/public/pw_allocator/freelist_heap.h
+++ b/pw_allocator/public/pw_allocator/freelist_heap.h
@@ -24,6 +24,13 @@
 
 class FreeListHeap {
  public:
+  template <size_t N>
+  friend class FreeListHeapBuffer;
+  struct HeapStats {
+    size_t bytes_allocated;
+    size_t cumulative_allocated;
+    size_t cumulative_freed;
+  };
   FreeListHeap(std::span<std::byte> region, FreeList& freelist);
 
   void* Allocate(size_t size);
@@ -38,6 +45,7 @@
 
   std::span<std::byte> region_;
   FreeList& freelist_;
+  HeapStats heap_stats_;
 };
 
 template <size_t N = 6>
@@ -54,6 +62,10 @@
   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 FreeListHeap::HeapStats& heap_stats() const {
+    return heap_.heap_stats_;
+  };
+
  private:
   FreeListBuffer<N> freelist_;
   FreeListHeap heap_;
diff --git a/pw_malloc/BUILD b/pw_malloc/BUILD
new file mode 100644
index 0000000..f3ab4f3
--- /dev/null
+++ b/pw_malloc/BUILD
@@ -0,0 +1,50 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+# TODO(pwbug/101): Need to add support for facades/backends to Bazel.
+PW_MALLOC_BACKEND = "//pw_malloc_freelist"
+
+pw_cc_library(
+    name = "facade",
+    hdrs = [
+        "public/pw_malloc/malloc.h",
+    ],
+    includes = ["public"],
+    deps = [
+        PW_MALLOC_BACKEND + ":headers",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_malloc",
+    deps = [
+        ":facade",
+        PW_MALLOC_BACKEND + ":headers",
+    ],
+)
+
+pw_cc_library(
+    name = "backend",
+    deps = [
+        PW_MALLOC_BACKEND,
+    ],
+)
diff --git a/pw_malloc/BUILD.gn b/pw_malloc/BUILD.gn
new file mode 100644
index 0000000..f56a0fe
--- /dev/null
+++ b/pw_malloc/BUILD.gn
@@ -0,0 +1,54 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+# gn-format disable
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/facade.gni")
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_malloc/backend.gni")
+import("$dir_pw_unit_test/test.gni")
+config("default_config") {
+  include_dirs = [ "public" ]
+}
+
+config("pw_malloc_wrapper_config") {
+  # Link options that provides replace dynamic memory operations in standard
+  # library with the pigweed malloc.
+  ldflags = [
+    # memory allocation -- these must be re-entrant and do locking
+    "-Wl,--wrap=malloc",
+    "-Wl,--wrap=free",
+    "-Wl,--wrap=realloc",
+    "-Wl,--wrap=calloc",
+
+    # Wrap these in case internal newlib call them (e.g. strdup will)
+    # directly call _malloc_r)
+    "-Wl,--wrap=_malloc_r",
+    "-Wl,--wrap=_realloc_r",
+    "-Wl,--wrap=_free_r",
+    "-Wl,--wrap=_calloc_r",
+  ]
+}
+
+pw_facade("pw_malloc") {
+  public_configs = [ ":default_config" ]
+  public = [ "public/pw_malloc/malloc.h" ]
+  backend = pw_malloc_BACKEND
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_malloc/backend.gni b/pw_malloc/backend.gni
new file mode 100644
index 0000000..e0d62a0
--- /dev/null
+++ b/pw_malloc/backend.gni
@@ -0,0 +1,18 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+declare_args() {
+  # Backends for the pw_malloc facade.
+  pw_malloc_BACKEND = ""
+}
diff --git a/pw_malloc/docs.rst b/pw_malloc/docs.rst
new file mode 100644
index 0000000..7d55265
--- /dev/null
+++ b/pw_malloc/docs.rst
@@ -0,0 +1,31 @@
+.. _chapter-pw-malloc:
+
+.. default-domain:: cpp
+
+---------
+pw_malloc
+---------
+
+This module defines an interface for replacing the standard libc dynamic memory
+operations.
+
+This facade doesn't implement any heap structure or dynamic memory methods. It
+only requires that backends implements a ``void pw_MallocInit();`` method.
+This function is called before static intialization, and is responsible for
+initializing global data structures required by the malloc implementation.
+
+The intent of this module is to provide an interface for user-provided dynamic
+memory operations that is compatible with different implementations.
+
+Setup
+=====
+This module requires the following setup:
+
+  1. Chose a ``pw_malloc`` backend, or write one yourself.
+  2. If using GN build, Specify the ``pw_sys_io_BACKEND`` GN build arg to point
+     the library that provides a ``pw_malloc`` backend.
+
+Module usage
+============
+See backend docs for how to interact with the underlying dynamic memory
+operations implementation.
diff --git a/pw_malloc/public/pw_malloc/malloc.h b/pw_malloc/public/pw_malloc/malloc.h
new file mode 100644
index 0000000..c5e1f42
--- /dev/null
+++ b/pw_malloc/public/pw_malloc/malloc.h
@@ -0,0 +1,22 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#if __cplusplus
+extern "C" {
+#endif
+void pw_MallocInit();
+#if __cplusplus
+}
+#endif
diff --git a/pw_malloc_freelist/BUILD b/pw_malloc_freelist/BUILD
new file mode 100644
index 0000000..1efbb30
--- /dev/null
+++ b/pw_malloc_freelist/BUILD
@@ -0,0 +1,60 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+load(
+    "//pw_build:pigweed.bzl",
+    "pw_cc_library",
+    "pw_cc_test",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+pw_cc_library(
+    name = "headers",
+    hdrs = [
+        "public/pw_malloc_freelist/freelist_malloc.h",
+    ],
+    includes = [
+        "public",
+    ],
+)
+
+pw_cc_library(
+    name = "pw_malloc_freelist",
+    srcs = [
+        "freelist_malloc.cc",
+    ],
+    deps = [
+        ":headers",
+        "//dir_pw_allocator:block",
+        "//dir_pw_allocator:freelist_heap",
+        "//dir_pw_boot_armv7m",
+        "//dir_pw_malloc:facade",
+        "//dir_pw_preprocessor",
+        "//dir_pw_span",
+    ],
+)
+
+pw_cc_test(
+    name = "freelist_malloctest",
+    srcs = [
+        "freelist_malloc_test.cc",
+    ],
+    deps = [
+        ":headers",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_malloc_freelist/BUILD.gn b/pw_malloc_freelist/BUILD.gn
new file mode 100644
index 0000000..2479f8b
--- /dev/null
+++ b/pw_malloc_freelist/BUILD.gn
@@ -0,0 +1,55 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+# gn-format disable
+import("//build_overrides/pigweed.gni")
+import("$dir_pw_malloc/backend.gni")
+
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
+config("default_config") {
+  include_dirs = [ "public" ]
+}
+
+pw_source_set("pw_malloc_freelist") {
+  public_configs = [ ":default_config" ]
+  public = [ "public/pw_malloc_freelist/freelist_malloc.h" ]
+  deps = [
+    "$dir_pw_allocator:block",
+    "$dir_pw_allocator:freelist_heap",
+    "$dir_pw_boot_armv7m",
+    "$dir_pw_malloc:facade",
+    "$dir_pw_preprocessor",
+    "$dir_pw_span",
+  ]
+  sources = [ "freelist_malloc.cc" ]
+}
+
+pw_test_group("tests") {
+  enable_if = pw_malloc_BACKEND == dir_pw_malloc_freelist
+  tests = [ ":freelist_malloc_test" ]
+}
+
+pw_test("freelist_malloc_test") {
+  deps = [
+    "$dir_pw_allocator",
+    "$dir_pw_malloc",
+  ]
+  sources = [ "freelist_malloc_test.cc" ]
+}
+
+pw_doc_group("docs") {
+  sources = [ "docs.rst" ]
+}
diff --git a/pw_malloc_freelist/docs.rst b/pw_malloc_freelist/docs.rst
new file mode 100644
index 0000000..928ac5b
--- /dev/null
+++ b/pw_malloc_freelist/docs.rst
@@ -0,0 +1,21 @@
+.. _chapter-pw-malloc-freelist:
+
+.. default-domain:: cpp
+
+------------------
+pw_malloc_freelist
+------------------
+
+``pw_malloc_freelist`` implements the ``pw_malloc`` facade using a freelist
+heap.
+
+``pw_malloc_freelist`` initializes a global ``FreeListHeapBuffer`` object to
+organize heap usage. Implementation details are in the ``pw_allocator`` module.
+
+``pw_malloc_freelist`` provides wrapper functions for ``malloc``, ``free``,
+``realloc`` and ``calloc`` that uses the freelist implementation of heap in
+``pw_allocator``. In the GN build file, ``pw_malloc_freelist`` provides linker
+options needed in ``public_configs``, which will be forwarded to the facade. In
+the case of freelist, we specify the wrapper functions ``malloc, free, realloc,
+calloc, _malloc_r, _free_r, _realloc_r, _calloc_r`` to replace the original libc
+functions at linker time.
diff --git a/pw_malloc_freelist/freelist_malloc.cc b/pw_malloc_freelist/freelist_malloc.cc
new file mode 100644
index 0000000..b2285e5
--- /dev/null
+++ b/pw_malloc_freelist/freelist_malloc.cc
@@ -0,0 +1,82 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <span>
+
+#include "pw_allocator/freelist_heap.h"
+#include "pw_boot_armv7m/boot.h"
+#include "pw_malloc/malloc.h"
+#include "pw_preprocessor/util.h"
+
+namespace {
+std::aligned_storage_t<sizeof(pw::allocator::FreeListHeapBuffer<>),
+                       alignof(pw::allocator::FreeListHeapBuffer<>)>
+    buf;
+std::span<std::byte> pw_allocator_freelist_raw_heap;
+}  // namespace
+pw::allocator::FreeListHeapBuffer<>* pw_freelist_heap;
+
+#if __cplusplus
+extern "C" {
+#endif
+// Define the global heap variables.
+void pw_MallocInit() {
+  // pw_heap_low_addr and pw_heap_high_addr specifies the heap region from
+  // the linker script in "pw_boot_armv7m".
+  std::span<std::byte> pw_allocator_freelist_raw_heap =
+      std::span(reinterpret_cast<std::byte*>(&pw_heap_low_addr),
+                &pw_heap_high_addr - &pw_heap_low_addr);
+  pw_freelist_heap = new (&buf)
+      pw::allocator::FreeListHeapBuffer(pw_allocator_freelist_raw_heap);
+}
+
+// Wrapper functions for malloc, free, realloc and calloc.
+// With linker options "-Wl --wrap=<function name>", linker will link
+// "__wrap_<function name>" with "<function_name>", and calling
+// "<function name>" will call "__wrap_<function name>" instead
+// Linker options are set in a config in "pw_malloc:pw_malloc_config".
+void* __wrap_malloc(size_t size) { return pw_freelist_heap->Allocate(size); }
+
+void __wrap_free(void* ptr) { pw_freelist_heap->Free(ptr); }
+
+void* __wrap_realloc(void* ptr, size_t size) {
+  return pw_freelist_heap->Realloc(ptr, size);
+}
+
+void* __wrap_calloc(size_t num, size_t size) {
+  return pw_freelist_heap->Calloc(num, size);
+}
+
+void* __wrap__malloc_r(struct _reent* r, size_t size) {
+  PW_UNUSED(r);
+  return pw_freelist_heap->Allocate(size);
+}
+
+void __wrap__free_r(struct _reent* r, void* ptr) {
+  PW_UNUSED(r);
+  pw_freelist_heap->Free(ptr);
+}
+
+void* __wrap__realloc_r(struct _reent* r, void* ptr, size_t size) {
+  PW_UNUSED(r);
+  return pw_freelist_heap->Realloc(ptr, size);
+}
+
+void* __wrap__calloc_r(struct _reent* r, size_t num, size_t size) {
+  PW_UNUSED(r);
+  return pw_freelist_heap->Calloc(num, size);
+}
+#if __cplusplus
+}
+#endif
diff --git a/pw_malloc_freelist/freelist_malloc_test.cc b/pw_malloc_freelist/freelist_malloc_test.cc
new file mode 100644
index 0000000..1194230
--- /dev/null
+++ b/pw_malloc_freelist/freelist_malloc_test.cc
@@ -0,0 +1,64 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_malloc_freelist/freelist_malloc.h"
+
+#include <span>
+
+#include "gtest/gtest.h"
+#include "pw_allocator/freelist_heap.h"
+
+namespace pw::allocator {
+
+TEST(FreeListMalloc, ReplacingMalloc) {
+  constexpr size_t kAllocSize = 256;
+  constexpr size_t kReallocSize = 512;
+  constexpr size_t kCallocNum = 4;
+  constexpr size_t kCallocSize = 64;
+  constexpr size_t zero = 0;
+
+  void* ptr1 = malloc(kAllocSize);
+  const FreeListHeap::HeapStats& freelist_heap_stats =
+      pw_freelist_heap->heap_stats();
+  ASSERT_NE(ptr1, nullptr);
+  EXPECT_EQ(freelist_heap_stats.bytes_allocated, kAllocSize);
+  EXPECT_EQ(freelist_heap_stats.cumulative_allocated, kAllocSize);
+  EXPECT_EQ(freelist_heap_stats.cumulative_freed, zero);
+  void* ptr2 = realloc(ptr1, kReallocSize);
+  ASSERT_NE(ptr2, nullptr);
+  EXPECT_EQ(freelist_heap_stats.bytes_allocated, kReallocSize);
+  EXPECT_EQ(freelist_heap_stats.cumulative_allocated,
+            kAllocSize + kReallocSize);
+  EXPECT_EQ(freelist_heap_stats.cumulative_freed, kAllocSize);
+  void* ptr3 = calloc(kCallocNum, kCallocSize);
+  ASSERT_NE(ptr3, nullptr);
+  EXPECT_EQ(freelist_heap_stats.bytes_allocated,
+            kReallocSize + kCallocNum * kCallocSize);
+  EXPECT_EQ(freelist_heap_stats.cumulative_allocated,
+            kAllocSize + kReallocSize + kCallocNum * kCallocSize);
+  EXPECT_EQ(freelist_heap_stats.cumulative_freed, kAllocSize);
+  free(ptr2);
+  EXPECT_EQ(freelist_heap_stats.bytes_allocated, kCallocNum * kCallocSize);
+  EXPECT_EQ(freelist_heap_stats.cumulative_allocated,
+            kAllocSize + kReallocSize + kCallocNum * kCallocSize);
+  EXPECT_EQ(freelist_heap_stats.cumulative_freed, kAllocSize + kReallocSize);
+  free(ptr3);
+  EXPECT_EQ(freelist_heap_stats.bytes_allocated, zero);
+  EXPECT_EQ(freelist_heap_stats.cumulative_allocated,
+            kAllocSize + kReallocSize + kCallocNum * kCallocSize);
+  EXPECT_EQ(freelist_heap_stats.cumulative_freed,
+            kAllocSize + kReallocSize + kCallocNum * kCallocSize);
+}
+
+}  // namespace pw::allocator
diff --git a/pw_malloc_freelist/public/pw_malloc_freelist/freelist_malloc.h b/pw_malloc_freelist/public/pw_malloc_freelist/freelist_malloc.h
new file mode 100644
index 0000000..93599e9
--- /dev/null
+++ b/pw_malloc_freelist/public/pw_malloc_freelist/freelist_malloc.h
@@ -0,0 +1,20 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "pw_allocator/freelist_heap.h"
+#include "pw_span/span.h"
+
+// Global variables to initialize a freelist heap.
+extern pw::allocator::FreeListHeapBuffer<>* pw_freelist_heap;
diff --git a/pw_sys_io_baremetal_stm32f429/BUILD.gn b/pw_sys_io_baremetal_stm32f429/BUILD.gn
index cc232fc..f4a652e 100644
--- a/pw_sys_io_baremetal_stm32f429/BUILD.gn
+++ b/pw_sys_io_baremetal_stm32f429/BUILD.gn
@@ -17,9 +17,18 @@
 
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_malloc/backend.gni")
+config("pw_malloc_active") {
+  if (pw_malloc_BACKEND != "") {
+    defines = [ "PW_MALLOC_ACTIVE=1" ]
+  }
+}
+
 pw_source_set("pw_sys_io_baremetal_stm32f429") {
+  configs = [ ":pw_malloc_active" ]
   public_deps = [ "$dir_pw_boot_armv7m" ]
   deps = [
+    "$dir_pw_malloc",
     "$dir_pw_preprocessor",
     "$dir_pw_sys_io:default_putget_bytes",
     "$dir_pw_sys_io:facade",
diff --git a/pw_sys_io_baremetal_stm32f429/early_boot.c b/pw_sys_io_baremetal_stm32f429/early_boot.c
index afee255..e10a361 100644
--- a/pw_sys_io_baremetal_stm32f429/early_boot.c
+++ b/pw_sys_io_baremetal_stm32f429/early_boot.c
@@ -15,6 +15,7 @@
 #include <inttypes.h>
 
 #include "pw_boot_armv7m/boot.h"
+#include "pw_malloc/malloc.h"
 
 void pw_PreStaticConstructorInit() {
   // TODO(pwbug/17): Optionally enable Replace when Pigweed config system is
@@ -29,4 +30,8 @@
 
   *arm_v7m_cpacr |= kFpuEnableMask;
 #endif  // PW_ARMV7M_ENABLE_FPU
-}
\ No newline at end of file
+
+#if PW_MALLOC_ACTIVE
+  pw_MallocInit();
+#endif  // PW_MALLOC_ACTIVE
+}
diff --git a/targets/stm32f429i-disc1/stm32f429i_executable.gni b/targets/stm32f429i-disc1/stm32f429i_executable.gni
index 1600761..5f336e2 100644
--- a/targets/stm32f429i-disc1/stm32f429i_executable.gni
+++ b/targets/stm32f429i-disc1/stm32f429i_executable.gni
@@ -14,6 +14,7 @@
 
 # gn-format disable
 import("//build_overrides/pigweed.gni")
+import("$dir_pw_malloc/backend.gni")
 
 # Executable wrapper that includes some baremetal startup code.
 template("stm32f429i_executable") {
@@ -23,5 +24,11 @@
       deps = []
     }
     deps += [ dir_pw_sys_io_baremetal_stm32f429 ]
+    if (pw_malloc_BACKEND != "") {
+      if (!defined(configs)) {
+        configs = []
+      }
+      configs += [ "$dir_pw_malloc:pw_malloc_wrapper_config" ]
+    }
   }
 }
diff --git a/targets/stm32f429i-disc1/target_toolchains.gni b/targets/stm32f429i-disc1/target_toolchains.gni
index 70b1f6c..748be5f 100644
--- a/targets/stm32f429i-disc1/target_toolchains.gni
+++ b/targets/stm32f429i-disc1/target_toolchains.gni
@@ -48,11 +48,16 @@
   pw_cpu_exception_SUPPORT_BACKEND = "$dir_pw_cpu_exception_armv7m:support"
   pw_log_BACKEND = dir_pw_log_basic
   pw_sys_io_BACKEND = dir_pw_sys_io_baremetal_stm32f429
+  pw_malloc_BACKEND = dir_pw_malloc_freelist
 
   pw_boot_armv7m_LINK_CONFIG_DEFINES = [
     "PW_BOOT_FLASH_BEGIN=0x08000200",
     "PW_BOOT_FLASH_SIZE=512K",
-    "PW_BOOT_HEAP_SIZE=0",
+
+    # TODO(pwbug/219): Currently "pw_tokenizer/detokenize_test" requires at
+    # least 6K bytes in heap when using pw_malloc_freelist. The heap size
+    # required for tests should be investigated.
+    "PW_BOOT_HEAP_SIZE=6K",
     "PW_BOOT_MIN_STACK_SIZE=1K",
     "PW_BOOT_RAM_BEGIN=0x20000000",
     "PW_BOOT_RAM_SIZE=192K",