[libc-commits] [libc] [libc] Enable full-build code coverage (PR #221802)

Tapiwa Gonga via libc-commits libc-commits at lists.llvm.org
Thu Sep 10 05:19:45 PDT 2026


https://github.com/tapiwagonga updated https://github.com/llvm/llvm-project/pull/221802

>From ea1c8d4664361b4d6f859baf12f0975ac00e4eeb Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Fri, 7 Aug 2026 10:22:56 +0000
Subject: [PATCH 01/18] [libc][test] Add freestanding code coverage support to
 LibcTestMain

---
 libc/CMakeLists.txt                           |  28 +++
 .../OSUtil/linux/syscall_wrappers/raise.h     |   3 +-
 libc/test/UnitTest/Coverage.h                 | 162 ++++++++++++++++++
 libc/test/UnitTest/ExecuteFunctionUnix.cpp    |  48 ++++++
 libc/test/UnitTest/LibcTestMain.cpp           |  14 +-
 5 files changed, 251 insertions(+), 4 deletions(-)
 create mode 100644 libc/test/UnitTest/Coverage.h

diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index 5ef88bd31e76c..6c73f0ada73e2 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -91,6 +91,33 @@ endif()
 set(LIBC_LINK_OPTIONS_DEFAULT "" CACHE STRING "Arguments used when linking.")
 set(LIBC_TEST_LINK_OPTIONS_DEFAULT "" CACHE STRING "Common link options for all the tests.")
 
+option(LLVM_LIBC_ENABLE_COVERAGE "Build libc with coverage instrumentation" OFF)
+if(LLVM_LIBC_ENABLE_COVERAGE)
+  add_compile_definitions(LLVM_LIBC_ENABLE_COVERAGE)
+  list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
+  list(APPEND LIBC_TEST_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
+  list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
+
+  # When building with -nostdlib, the compiler does not automatically link the profiling runtime.
+  # We must explicitly query the compiler for the exact architecture-specific profile library path.
+  execute_process(
+    COMMAND ${CMAKE_CXX_COMPILER} --print-libgcc-file-name --rtlib=compiler-rt
+    OUTPUT_VARIABLE COMPILER_RT_BUILTINS
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    RESULT_VARIABLE COMPILER_RT_RETURN_CODE
+  )
+  if(COMPILER_RT_RETURN_CODE EQUAL 0)
+    string(REPLACE "builtins" "profile" COMPILER_RT_PROFILE "${COMPILER_RT_BUILTINS}")
+    if(EXISTS "${COMPILER_RT_PROFILE}")
+      list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "${COMPILER_RT_PROFILE}")
+    else()
+      message(WARNING "Coverage profiling runtime not found at ${COMPILER_RT_PROFILE}")
+    endif()
+  else()
+    message(WARNING "Failed to locate compiler-rt builtins library for coverage")
+  endif()
+endif()
+
 set(LIBC_TEST_CMD "" CACHE STRING
   "The full test command in the form <command> binary=@BINARY@, if using another program to test (e.g. QEMU)")
 set(LIBC_TEST_HERMETIC_ONLY "" OFF CACHE BOOL "Only enable hermetic tests.")
@@ -157,6 +184,7 @@ if(LLVM_LIBC_FULL_BUILD)
 else()
   set(LLVM_LIBC_OVERLAY ON)
 endif()
+
 option(LLVM_LIBC_IMPLEMENTATION_DEFINED_TEST_BEHAVIOR "Build LLVM libc tests assuming our implementation-defined behavior" ON)
 option(LLVM_LIBC_ENABLE_LINTING "Enables linting of libc source files" OFF)
 option(LLVM_LIBC_ALL_HEADERS "Outputs all functions in header files, regardless of whether they are enabled on this target" OFF)
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h
index 2b61e4e624c89..b7c22a51bfe24 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h
@@ -12,6 +12,7 @@
 #include "hdr/signal_macros.h"
 #include "hdr/types/sigset_t.h"
 #include "src/__support/OSUtil/linux/syscall.h" // syscall_impl
+#include "src/__support/OSUtil/linux/syscall_wrappers/getpid.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/rt_sigprocmask.h"
 #include "src/__support/common.h"
 #include "src/__support/error_or.h"
@@ -46,7 +47,7 @@ LIBC_INLINE ErrorOr<int> raise(int sig) {
     if (!status.has_value())
       return status;
 
-    long pid = syscall_impl<long>(SYS_getpid);
+    pid_t pid = linux_syscalls::getpid();
     if (pid < 0)
       return Error(-static_cast<int>(pid));
 
diff --git a/libc/test/UnitTest/Coverage.h b/libc/test/UnitTest/Coverage.h
new file mode 100644
index 0000000000000..e142775c1a7e0
--- /dev/null
+++ b/libc/test/UnitTest/Coverage.h
@@ -0,0 +1,162 @@
+//===-- Freestanding Code Coverage Extraction Support -----------*- 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_TEST_UNITTEST_COVERAGE_H
+#define LLVM_LIBC_TEST_UNITTEST_COVERAGE_H
+
+#include "src/__support/macros/properties/os.h"
+
+#if defined(LIBC_TARGET_OS_IS_LINUX)
+
+#include "hdr/errno_macros.h"
+#include "hdr/fcntl_macros.h"
+#include "hdr/sys_mman_macros.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/getpid.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/mmap.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/munmap.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/write.h"
+#include "src/__support/integer_to_string.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+#include <sys/syscall.h>
+
+extern "C" {
+__attribute__((weak)) uint64_t __llvm_profile_get_size_for_buffer();
+__attribute__((weak)) int __llvm_profile_write_buffer(char *buffer);
+__attribute__((weak)) void
+__llvm_profile_set_filename(const char *filename_pat);
+
+// Override compiler-rt's weak filename symbol. This redirects the default
+// filename to /dev/null to silence the default dumper by default.
+__attribute__((weak)) char __llvm_profile_filename[] = "/dev/null";
+}
+
+namespace {
+
+using LIBC_NAMESPACE::cpp::string_view;
+
+struct FixedSizeBuffer {
+  char data[64];
+  size_t idx = 0;
+
+  FixedSizeBuffer() { data[0] = '\0'; }
+
+  bool append(string_view str) {
+    size_t len = str.size();
+    if (idx + len >= sizeof(data))
+      return false;
+    LIBC_NAMESPACE::inline_memcpy(data + idx, str.data(), len);
+    idx += len;
+    data[idx] = '\0';
+    return true;
+  }
+
+  template <size_t N> bool append(const char (&str)[N]) {
+    size_t len = N - 1;
+    if (idx + len >= sizeof(data))
+      return false;
+    LIBC_NAMESPACE::inline_memcpy(data + idx, str, len);
+    idx += len;
+    data[idx] = '\0';
+    return true;
+  }
+};
+
+LIBC_INLINE void report_error(string_view msg) {
+  LIBC_NAMESPACE::linux_syscalls::write(2, msg.data(), msg.size());
+}
+
+} // anonymous namespace
+
+extern "C" void write_raw_profile() {
+  if (!__llvm_profile_get_size_for_buffer || !__llvm_profile_write_buffer)
+    return;
+
+  size_t required_size =
+      static_cast<size_t>(__llvm_profile_get_size_for_buffer());
+  if (required_size == 0)
+    return;
+
+  auto mmap_or_error = LIBC_NAMESPACE::linux_syscalls::mmap(
+      nullptr, required_size, PROT_READ | PROT_WRITE,
+      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (!mmap_or_error)
+    return report_error("error: libc coverage failed to mmap buffer\n");
+  char *profile_buffer = static_cast<char *>(mmap_or_error.value());
+
+  if (__llvm_profile_write_buffer(profile_buffer) != 0) {
+    LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
+    return report_error(
+        "error: libc coverage failed to write profile buffer\n");
+  }
+
+  // Create a minimal filename: libc_cov_<pid>.profraw
+  pid_t pid = LIBC_NAMESPACE::linux_syscalls::getpid();
+  if (pid <= 0)
+    pid = 1;
+
+  FixedSizeBuffer filename;
+  char pid_buf[LIBC_NAMESPACE::IntegerToString<long>::buffer_size()];
+  auto pid_str =
+      LIBC_NAMESPACE::IntegerToString<long>::format_to(pid_buf, pid);
+  if (!pid_str || !filename.append("libc_cov_") || !filename.append(*pid_str) ||
+      !filename.append(".profraw")) {
+    LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
+    return report_error("error: libc coverage filename buffer overflow\n");
+  }
+
+  auto fd_or_error = LIBC_NAMESPACE::linux_syscalls::open(
+      filename.data, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+  if (!fd_or_error) {
+    LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
+    return report_error("error: libc coverage failed to open output file\n");
+  }
+  int fd = fd_or_error.value();
+
+  size_t bytes_written = 0;
+  bool write_error_occurred = false;
+  while (bytes_written < required_size) {
+    auto write_or_error = LIBC_NAMESPACE::linux_syscalls::write(
+        fd, profile_buffer + bytes_written, required_size - bytes_written);
+    if (!write_or_error) {
+      if (write_or_error.error() == EINTR)
+        continue;
+      write_error_occurred = true;
+      break;
+    }
+    ssize_t ret = write_or_error.value();
+    if (ret == 0) {
+      write_error_occurred = true;
+      break;
+    }
+    bytes_written += ret;
+  }
+
+  LIBC_NAMESPACE::linux_syscalls::close(fd);
+  LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
+
+  if (write_error_occurred || bytes_written < required_size)
+    return report_error(
+        "error: libc coverage failed to write all data to file\n");
+
+  // Clear the filename pattern to prevent compiler-rt from writing at exit.
+  if (__llvm_profile_set_filename)
+    __llvm_profile_set_filename("/dev/null");
+}
+
+#else
+
+extern "C" void write_raw_profile() {}
+
+#endif // LIBC_TARGET_OS_IS_LINUX
+
+#endif // LLVM_LIBC_TEST_UNITTEST_COVERAGE_H
diff --git a/libc/test/UnitTest/ExecuteFunctionUnix.cpp b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
index a07c92f61225c..a97ebeaf685be 100644
--- a/libc/test/UnitTest/ExecuteFunctionUnix.cpp
+++ b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
@@ -18,6 +18,7 @@
 #include "include/llvm-libc-types/struct_pollfd.h"
 #include "src/poll/poll.h"
 #include "src/signal/kill.h"
+#include "src/signal/sigaction.h"
 #include "src/stdio/fflush.h"
 #include "src/stdio/stderr.h"
 #include "src/stdio/stdout.h"
@@ -26,6 +27,7 @@
 #include "src/sys/wait/waitpid.h"
 #include "src/unistd/close.h"
 #include "src/unistd/fork.h"
+#include "src/unistd/getpid.h"
 #include "src/unistd/pipe.h"
 
 #define LIBC_IMPL LIBC_NAMESPACE
@@ -42,6 +44,10 @@
 #define LIBC_IMPL
 #endif
 
+#if defined(LLVM_LIBC_ENABLE_COVERAGE)
+extern "C" __attribute__((weak)) void write_raw_profile();
+#endif
+
 namespace LIBC_NAMESPACE_DECL {
 namespace testutils {
 
@@ -58,6 +64,25 @@ int ProcessStatus::get_fatal_signal() {
   return WTERMSIG(platform_defined);
 }
 
+#if defined(LLVM_LIBC_ENABLE_COVERAGE)
+static void coverage_fatal_signal_handler(int sig) {
+  if (write_raw_profile)
+    write_raw_profile();
+  
+  // Restore default signal handler
+#ifdef LIBC_FULL_BUILD
+  struct sigaction sa = {};
+  sa.sa_handler = SIG_DFL;
+  LIBC_IMPL::sigaction(sig, &sa, nullptr);
+#else
+  ::signal(sig, SIG_DFL);
+#endif
+  
+  // Re-raise the signal
+  LIBC_IMPL::kill(LIBC_IMPL::getpid(), sig);
+}
+#endif
+
 ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
   int pipe_fds[2];
   if (LIBC_IMPL::pipe(pipe_fds) == -1) {
@@ -75,10 +100,33 @@ ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
   }
 
   if (!pid) {
+#if defined(LLVM_LIBC_ENABLE_COVERAGE)
+#ifdef LIBC_FULL_BUILD
+    struct sigaction sa = {};
+    sa.sa_handler = coverage_fatal_signal_handler;
+    LIBC_IMPL::sigaction(SIGABRT, &sa, nullptr);
+    LIBC_IMPL::sigaction(SIGSEGV, &sa, nullptr);
+    LIBC_IMPL::sigaction(SIGILL, &sa, nullptr);
+    LIBC_IMPL::sigaction(SIGFPE, &sa, nullptr);
+    LIBC_IMPL::sigaction(SIGBUS, &sa, nullptr);
+#else
+    ::signal(SIGABRT, coverage_fatal_signal_handler);
+    ::signal(SIGSEGV, coverage_fatal_signal_handler);
+    ::signal(SIGILL, coverage_fatal_signal_handler);
+    ::signal(SIGFPE, coverage_fatal_signal_handler);
+    ::signal(SIGBUS, coverage_fatal_signal_handler);
+#endif
+#endif
+
     (*func)();
     delete func;
+#if defined(LLVM_LIBC_ENABLE_COVERAGE)
+    if (write_raw_profile)
+      write_raw_profile();
+#endif
     LIBC_IMPL::exit(0);
   }
+
   LIBC_IMPL::close(pipe_fds[1]);
 
   pollfd poll_fd{pipe_fds[0], POLLIN, 0};
diff --git a/libc/test/UnitTest/LibcTestMain.cpp b/libc/test/UnitTest/LibcTestMain.cpp
index c348d5ef1aa1b..fb46ec45073a0 100644
--- a/libc/test/UnitTest/LibcTestMain.cpp
+++ b/libc/test/UnitTest/LibcTestMain.cpp
@@ -43,8 +43,10 @@ TestOptions parseOptions(int argc, char **argv) {
 
 } // anonymous namespace
 
-// The C++ standard forbids declaring the main function with a linkage specifier
-// outisde of 'freestanding' mode, only define the linkage for hermetic tests.
+#if defined(LLVM_LIBC_ENABLE_COVERAGE) && defined(LIBC_TARGET_OS_IS_LINUX)
+#include "Coverage.h"
+#endif
+
 #if __STDC_HOSTED__
 #define TEST_MAIN int main
 #else
@@ -56,5 +58,11 @@ TEST_MAIN(int argc, char **argv, char **envp) {
   LIBC_NAMESPACE::testing::argv = argv;
   LIBC_NAMESPACE::testing::envp = envp;
 
-  return LIBC_NAMESPACE::testing::Test::runTests(parseOptions(argc, argv));
+  int result =
+      LIBC_NAMESPACE::testing::Test::runTests(parseOptions(argc, argv));
+#if defined(LLVM_LIBC_ENABLE_COVERAGE) && defined(LIBC_TARGET_OS_IS_LINUX)
+  write_raw_profile();
+#endif
+  return result;
 }
+

>From b20f945dc0d341b2640e718d2a360b0ff098248d Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Mon, 24 Aug 2026 09:46:50 +0000
Subject: [PATCH 02/18] [libc] Fix clang-format warnings in coverage runtime

---
 libc/test/UnitTest/Coverage.h              | 3 +--
 libc/test/UnitTest/ExecuteFunctionUnix.cpp | 4 ++--
 libc/test/UnitTest/LibcTestMain.cpp        | 1 -
 3 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/libc/test/UnitTest/Coverage.h b/libc/test/UnitTest/Coverage.h
index e142775c1a7e0..f9c2e76fb9b49 100644
--- a/libc/test/UnitTest/Coverage.h
+++ b/libc/test/UnitTest/Coverage.h
@@ -106,8 +106,7 @@ extern "C" void write_raw_profile() {
 
   FixedSizeBuffer filename;
   char pid_buf[LIBC_NAMESPACE::IntegerToString<long>::buffer_size()];
-  auto pid_str =
-      LIBC_NAMESPACE::IntegerToString<long>::format_to(pid_buf, pid);
+  auto pid_str = LIBC_NAMESPACE::IntegerToString<long>::format_to(pid_buf, pid);
   if (!pid_str || !filename.append("libc_cov_") || !filename.append(*pid_str) ||
       !filename.append(".profraw")) {
     LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
diff --git a/libc/test/UnitTest/ExecuteFunctionUnix.cpp b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
index a97ebeaf685be..f2d53cf21a92f 100644
--- a/libc/test/UnitTest/ExecuteFunctionUnix.cpp
+++ b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
@@ -68,7 +68,7 @@ int ProcessStatus::get_fatal_signal() {
 static void coverage_fatal_signal_handler(int sig) {
   if (write_raw_profile)
     write_raw_profile();
-  
+
   // Restore default signal handler
 #ifdef LIBC_FULL_BUILD
   struct sigaction sa = {};
@@ -77,7 +77,7 @@ static void coverage_fatal_signal_handler(int sig) {
 #else
   ::signal(sig, SIG_DFL);
 #endif
-  
+
   // Re-raise the signal
   LIBC_IMPL::kill(LIBC_IMPL::getpid(), sig);
 }
diff --git a/libc/test/UnitTest/LibcTestMain.cpp b/libc/test/UnitTest/LibcTestMain.cpp
index fb46ec45073a0..a7e9461711635 100644
--- a/libc/test/UnitTest/LibcTestMain.cpp
+++ b/libc/test/UnitTest/LibcTestMain.cpp
@@ -65,4 +65,3 @@ TEST_MAIN(int argc, char **argv, char **envp) {
 #endif
   return result;
 }
-

>From 0161fa19e70579de5149f348114bac038357b144 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Wed, 26 Aug 2026 08:40:46 +0000
Subject: [PATCH 03/18] [libc] Address review feedback: update headers, rename
 coverage option, and clean formatting

---
 libc/CMakeLists.txt                        |  7 +++----
 libc/test/UnitTest/Coverage.h              | 10 +++++++++-
 libc/test/UnitTest/ExecuteFunctionUnix.cpp | 12 +++++-------
 libc/test/UnitTest/LibcTestMain.cpp        |  4 ++--
 4 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index 6c73f0ada73e2..08bd4bb54f793 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -91,9 +91,9 @@ endif()
 set(LIBC_LINK_OPTIONS_DEFAULT "" CACHE STRING "Arguments used when linking.")
 set(LIBC_TEST_LINK_OPTIONS_DEFAULT "" CACHE STRING "Common link options for all the tests.")
 
-option(LLVM_LIBC_ENABLE_COVERAGE "Build libc with coverage instrumentation" OFF)
-if(LLVM_LIBC_ENABLE_COVERAGE)
-  add_compile_definitions(LLVM_LIBC_ENABLE_COVERAGE)
+option(LIBC_ENABLE_COVERAGE "Build libc with coverage instrumentation" OFF)
+if(LIBC_ENABLE_COVERAGE)
+  add_compile_definitions(LIBC_ENABLE_COVERAGE)
   list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
   list(APPEND LIBC_TEST_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
   list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
@@ -184,7 +184,6 @@ if(LLVM_LIBC_FULL_BUILD)
 else()
   set(LLVM_LIBC_OVERLAY ON)
 endif()
-
 option(LLVM_LIBC_IMPLEMENTATION_DEFINED_TEST_BEHAVIOR "Build LLVM libc tests assuming our implementation-defined behavior" ON)
 option(LLVM_LIBC_ENABLE_LINTING "Enables linting of libc source files" OFF)
 option(LLVM_LIBC_ALL_HEADERS "Outputs all functions in header files, regardless of whether they are enabled on this target" OFF)
diff --git a/libc/test/UnitTest/Coverage.h b/libc/test/UnitTest/Coverage.h
index f9c2e76fb9b49..6fff837aebac6 100644
--- a/libc/test/UnitTest/Coverage.h
+++ b/libc/test/UnitTest/Coverage.h
@@ -1,10 +1,15 @@
-//===-- Freestanding Code Coverage Extraction Support -----------*- 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
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// Freestanding code coverage extraction support for unit tests.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_TEST_UNITTEST_COVERAGE_H
 #define LLVM_LIBC_TEST_UNITTEST_COVERAGE_H
@@ -44,6 +49,8 @@ namespace {
 
 using LIBC_NAMESPACE::cpp::string_view;
 
+/// Minimal fixed-size stack buffer for constructing file paths without dynamic
+/// memory allocation.
 struct FixedSizeBuffer {
   char data[64];
   size_t idx = 0;
@@ -77,6 +84,7 @@ LIBC_INLINE void report_error(string_view msg) {
 
 } // anonymous namespace
 
+/// Writes raw coverage profile data to disk using direct Linux syscalls.
 extern "C" void write_raw_profile() {
   if (!__llvm_profile_get_size_for_buffer || !__llvm_profile_write_buffer)
     return;
diff --git a/libc/test/UnitTest/ExecuteFunctionUnix.cpp b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
index f2d53cf21a92f..2c8a5aa4b7f65 100644
--- a/libc/test/UnitTest/ExecuteFunctionUnix.cpp
+++ b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
@@ -44,7 +44,7 @@
 #define LIBC_IMPL
 #endif
 
-#if defined(LLVM_LIBC_ENABLE_COVERAGE)
+#if defined(LIBC_ENABLE_COVERAGE)
 extern "C" __attribute__((weak)) void write_raw_profile();
 #endif
 
@@ -64,7 +64,7 @@ int ProcessStatus::get_fatal_signal() {
   return WTERMSIG(platform_defined);
 }
 
-#if defined(LLVM_LIBC_ENABLE_COVERAGE)
+#if defined(LIBC_ENABLE_COVERAGE)
 static void coverage_fatal_signal_handler(int sig) {
   if (write_raw_profile)
     write_raw_profile();
@@ -100,8 +100,7 @@ ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
   }
 
   if (!pid) {
-#if defined(LLVM_LIBC_ENABLE_COVERAGE)
-#ifdef LIBC_FULL_BUILD
+#if defined(LIBC_ENABLE_COVERAGE) && defined(LIBC_FULL_BUILD)
     struct sigaction sa = {};
     sa.sa_handler = coverage_fatal_signal_handler;
     LIBC_IMPL::sigaction(SIGABRT, &sa, nullptr);
@@ -109,18 +108,17 @@ ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
     LIBC_IMPL::sigaction(SIGILL, &sa, nullptr);
     LIBC_IMPL::sigaction(SIGFPE, &sa, nullptr);
     LIBC_IMPL::sigaction(SIGBUS, &sa, nullptr);
-#else
+#elif defined(LIBC_ENABLE_COVERAGE)
     ::signal(SIGABRT, coverage_fatal_signal_handler);
     ::signal(SIGSEGV, coverage_fatal_signal_handler);
     ::signal(SIGILL, coverage_fatal_signal_handler);
     ::signal(SIGFPE, coverage_fatal_signal_handler);
     ::signal(SIGBUS, coverage_fatal_signal_handler);
-#endif
 #endif
 
     (*func)();
     delete func;
-#if defined(LLVM_LIBC_ENABLE_COVERAGE)
+#if defined(LIBC_ENABLE_COVERAGE)
     if (write_raw_profile)
       write_raw_profile();
 #endif
diff --git a/libc/test/UnitTest/LibcTestMain.cpp b/libc/test/UnitTest/LibcTestMain.cpp
index a7e9461711635..60bad42003645 100644
--- a/libc/test/UnitTest/LibcTestMain.cpp
+++ b/libc/test/UnitTest/LibcTestMain.cpp
@@ -43,7 +43,7 @@ TestOptions parseOptions(int argc, char **argv) {
 
 } // anonymous namespace
 
-#if defined(LLVM_LIBC_ENABLE_COVERAGE) && defined(LIBC_TARGET_OS_IS_LINUX)
+#if defined(LIBC_ENABLE_COVERAGE)
 #include "Coverage.h"
 #endif
 
@@ -60,7 +60,7 @@ TEST_MAIN(int argc, char **argv, char **envp) {
 
   int result =
       LIBC_NAMESPACE::testing::Test::runTests(parseOptions(argc, argv));
-#if defined(LLVM_LIBC_ENABLE_COVERAGE) && defined(LIBC_TARGET_OS_IS_LINUX)
+#if defined(LIBC_ENABLE_COVERAGE)
   write_raw_profile();
 #endif
   return result;

>From 276ef8c15a64f62f53930d8cc732231e07810baa Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Wed, 2 Sep 2026 11:00:31 +0000
Subject: [PATCH 04/18] Add Clang compiler check for coverage flags and remove
 getpid.h

---
 libc/CMakeLists.txt                           | 42 ++++++++++---------
 .../OSUtil/linux/syscall_wrappers/getpid.h    | 31 --------------
 .../OSUtil/linux/syscall_wrappers/raise.h     |  3 +-
 3 files changed, 24 insertions(+), 52 deletions(-)
 delete mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/getpid.h

diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index 08bd4bb54f793..37d7add6f2957 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -93,28 +93,32 @@ set(LIBC_TEST_LINK_OPTIONS_DEFAULT "" CACHE STRING "Common link options for all
 
 option(LIBC_ENABLE_COVERAGE "Build libc with coverage instrumentation" OFF)
 if(LIBC_ENABLE_COVERAGE)
-  add_compile_definitions(LIBC_ENABLE_COVERAGE)
-  list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
-  list(APPEND LIBC_TEST_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
-  list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
-
-  # When building with -nostdlib, the compiler does not automatically link the profiling runtime.
-  # We must explicitly query the compiler for the exact architecture-specific profile library path.
-  execute_process(
-    COMMAND ${CMAKE_CXX_COMPILER} --print-libgcc-file-name --rtlib=compiler-rt
-    OUTPUT_VARIABLE COMPILER_RT_BUILTINS
-    OUTPUT_STRIP_TRAILING_WHITESPACE
-    RESULT_VARIABLE COMPILER_RT_RETURN_CODE
-  )
-  if(COMPILER_RT_RETURN_CODE EQUAL 0)
-    string(REPLACE "builtins" "profile" COMPILER_RT_PROFILE "${COMPILER_RT_BUILTINS}")
-    if(EXISTS "${COMPILER_RT_PROFILE}")
-      list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "${COMPILER_RT_PROFILE}")
+  if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    add_compile_definitions(LIBC_ENABLE_COVERAGE)
+    list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
+    list(APPEND LIBC_TEST_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
+    list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
+
+    # When building with -nostdlib, the compiler does not automatically link the profiling runtime.
+    # We must explicitly query the compiler for the exact architecture-specific profile library path.
+    execute_process(
+      COMMAND ${CMAKE_CXX_COMPILER} --print-libgcc-file-name --rtlib=compiler-rt
+      OUTPUT_VARIABLE COMPILER_RT_BUILTINS
+      OUTPUT_STRIP_TRAILING_WHITESPACE
+      RESULT_VARIABLE COMPILER_RT_RETURN_CODE
+    )
+    if(COMPILER_RT_RETURN_CODE EQUAL 0)
+      string(REPLACE "builtins" "profile" COMPILER_RT_PROFILE "${COMPILER_RT_BUILTINS}")
+      if(EXISTS "${COMPILER_RT_PROFILE}")
+        list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "${COMPILER_RT_PROFILE}")
+      else()
+        message(WARNING "Coverage profiling runtime not found at ${COMPILER_RT_PROFILE}")
+      endif()
     else()
-      message(WARNING "Coverage profiling runtime not found at ${COMPILER_RT_PROFILE}")
+      message(WARNING "Failed to locate compiler-rt builtins library for coverage")
     endif()
   else()
-    message(WARNING "Failed to locate compiler-rt builtins library for coverage")
+    message(FATAL_ERROR "Coverage instrumentation is currently only supported with Clang")
   endif()
 endif()
 
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/getpid.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/getpid.h
deleted file mode 100644
index 7644b67449fdb..0000000000000
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/getpid.h
+++ /dev/null
@@ -1,31 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-///
-/// \file
-/// Syscall wrapper for getpid.
-///
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_GETPID_H
-#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_GETPID_H
-
-#include "hdr/types/pid_t.h"
-#include "src/__support/OSUtil/linux/syscall.h" // syscall_impl
-#include "src/__support/common.h"
-#include "src/__support/macros/config.h"
-#include <sys/syscall.h> // For syscall numbers
-
-namespace LIBC_NAMESPACE_DECL {
-namespace linux_syscalls {
-
-LIBC_INLINE pid_t getpid() { return syscall_impl<pid_t>(SYS_getpid); }
-
-} // namespace linux_syscalls
-} // namespace LIBC_NAMESPACE_DECL
-
-#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_GETPID_H
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h
index b7c22a51bfe24..2b61e4e624c89 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/raise.h
@@ -12,7 +12,6 @@
 #include "hdr/signal_macros.h"
 #include "hdr/types/sigset_t.h"
 #include "src/__support/OSUtil/linux/syscall.h" // syscall_impl
-#include "src/__support/OSUtil/linux/syscall_wrappers/getpid.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/rt_sigprocmask.h"
 #include "src/__support/common.h"
 #include "src/__support/error_or.h"
@@ -47,7 +46,7 @@ LIBC_INLINE ErrorOr<int> raise(int sig) {
     if (!status.has_value())
       return status;
 
-    pid_t pid = linux_syscalls::getpid();
+    long pid = syscall_impl<long>(SYS_getpid);
     if (pid < 0)
       return Error(-static_cast<int>(pid));
 

>From 7fdbe752a5e732aee59fb879241fd798e3522fd0 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Wed, 2 Sep 2026 13:16:23 +0000
Subject: [PATCH 05/18] [libc][test] Use raw SYS_getpid syscall in Coverage.h
 to remove getpid.h dependency

---
 libc/test/UnitTest/Coverage.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libc/test/UnitTest/Coverage.h b/libc/test/UnitTest/Coverage.h
index 6fff837aebac6..ca691d74982ff 100644
--- a/libc/test/UnitTest/Coverage.h
+++ b/libc/test/UnitTest/Coverage.h
@@ -24,8 +24,8 @@
 #include "src/__support/CPP/optional.h"
 #include "src/__support/CPP/span.h"
 #include "src/__support/CPP/string_view.h"
+#include "src/__support/OSUtil/linux/syscall.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/getpid.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/mmap.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/munmap.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
@@ -108,7 +108,7 @@ extern "C" void write_raw_profile() {
   }
 
   // Create a minimal filename: libc_cov_<pid>.profraw
-  pid_t pid = LIBC_NAMESPACE::linux_syscalls::getpid();
+  long pid = LIBC_NAMESPACE::syscall_impl<long>(SYS_getpid);
   if (pid <= 0)
     pid = 1;
 

>From 71ac974a2136f9291b2e512ea18619012ec029fe Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Fri, 4 Sep 2026 10:53:19 +0000
Subject: [PATCH 06/18] [libc] Use continuous profiling mode for standalone
 code coverage

Switch the code coverage pipeline to use Clang's continuous profiling mode
(-fprofile-continuous with -fprofile-instr-generate=libc_cov_%p.profraw):

- Removes libc/test/UnitTest/Coverage.h and raw Linux syscall profile writer.
- Reverts libc/test/UnitTest/ExecuteFunctionUnix.cpp to upstream main,
  removing fatal signal handlers and weak symbol overrides.
- Reverts libc/test/UnitTest/LibcTestMain.cpp to upstream main.
- Simplifies libc/CMakeLists.txt by removing manual compiler-rt profile
  static library path resolution, allowing Clang to handle profiling
  runtime requirements natively.

Coverage counters and decision bitmasks are mapped directly to disk via
OS page cache, providing full standalone coverage without custom signal
interception or manual dump routines.

Assisted-by: Automated tooling, human reviewed.
---
 libc/CMakeLists.txt                           |  28 +--
 .../OSUtil/linux/syscall_wrappers/getpid.h    |  31 ++++
 libc/test/UnitTest/Coverage.h                 | 169 ------------------
 libc/test/UnitTest/ExecuteFunctionUnix.cpp    |  46 -----
 libc/test/UnitTest/LibcTestMain.cpp           |  13 +-
 5 files changed, 40 insertions(+), 247 deletions(-)
 create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/getpid.h
 delete mode 100644 libc/test/UnitTest/Coverage.h

diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index 37d7add6f2957..d86496f1f059b 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -95,28 +95,12 @@ option(LIBC_ENABLE_COVERAGE "Build libc with coverage instrumentation" OFF)
 if(LIBC_ENABLE_COVERAGE)
   if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
     add_compile_definitions(LIBC_ENABLE_COVERAGE)
-    list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
-    list(APPEND LIBC_TEST_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
-    list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "-fprofile-instr-generate" "-fcoverage-mapping")
-
-    # When building with -nostdlib, the compiler does not automatically link the profiling runtime.
-    # We must explicitly query the compiler for the exact architecture-specific profile library path.
-    execute_process(
-      COMMAND ${CMAKE_CXX_COMPILER} --print-libgcc-file-name --rtlib=compiler-rt
-      OUTPUT_VARIABLE COMPILER_RT_BUILTINS
-      OUTPUT_STRIP_TRAILING_WHITESPACE
-      RESULT_VARIABLE COMPILER_RT_RETURN_CODE
-    )
-    if(COMPILER_RT_RETURN_CODE EQUAL 0)
-      string(REPLACE "builtins" "profile" COMPILER_RT_PROFILE "${COMPILER_RT_BUILTINS}")
-      if(EXISTS "${COMPILER_RT_PROFILE}")
-        list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "${COMPILER_RT_PROFILE}")
-      else()
-        message(WARNING "Coverage profiling runtime not found at ${COMPILER_RT_PROFILE}")
-      endif()
-    else()
-      message(WARNING "Failed to locate compiler-rt builtins library for coverage")
-    endif()
+    list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate=libc_cov_%p.profraw"
+         "-fcoverage-mapping" "-fprofile-continuous")
+    list(APPEND LIBC_TEST_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate=libc_cov_%p.profraw"
+         "-fcoverage-mapping" "-fprofile-continuous")
+    list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "-fprofile-instr-generate=libc_cov_%p.profraw"
+         "-fcoverage-mapping" "-fprofile-continuous")
   else()
     message(FATAL_ERROR "Coverage instrumentation is currently only supported with Clang")
   endif()
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/getpid.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/getpid.h
new file mode 100644
index 0000000000000..7644b67449fdb
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/getpid.h
@@ -0,0 +1,31 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Syscall wrapper for getpid.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_GETPID_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_GETPID_H
+
+#include "hdr/types/pid_t.h"
+#include "src/__support/OSUtil/linux/syscall.h" // syscall_impl
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE pid_t getpid() { return syscall_impl<pid_t>(SYS_getpid); }
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_GETPID_H
diff --git a/libc/test/UnitTest/Coverage.h b/libc/test/UnitTest/Coverage.h
deleted file mode 100644
index ca691d74982ff..0000000000000
--- a/libc/test/UnitTest/Coverage.h
+++ /dev/null
@@ -1,169 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-///
-/// \file
-/// Freestanding code coverage extraction support for unit tests.
-///
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_LIBC_TEST_UNITTEST_COVERAGE_H
-#define LLVM_LIBC_TEST_UNITTEST_COVERAGE_H
-
-#include "src/__support/macros/properties/os.h"
-
-#if defined(LIBC_TARGET_OS_IS_LINUX)
-
-#include "hdr/errno_macros.h"
-#include "hdr/fcntl_macros.h"
-#include "hdr/sys_mman_macros.h"
-#include "src/__support/CPP/optional.h"
-#include "src/__support/CPP/span.h"
-#include "src/__support/CPP/string_view.h"
-#include "src/__support/OSUtil/linux/syscall.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/mmap.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/munmap.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/write.h"
-#include "src/__support/integer_to_string.h"
-#include "src/string/memory_utils/inline_memcpy.h"
-#include <sys/syscall.h>
-
-extern "C" {
-__attribute__((weak)) uint64_t __llvm_profile_get_size_for_buffer();
-__attribute__((weak)) int __llvm_profile_write_buffer(char *buffer);
-__attribute__((weak)) void
-__llvm_profile_set_filename(const char *filename_pat);
-
-// Override compiler-rt's weak filename symbol. This redirects the default
-// filename to /dev/null to silence the default dumper by default.
-__attribute__((weak)) char __llvm_profile_filename[] = "/dev/null";
-}
-
-namespace {
-
-using LIBC_NAMESPACE::cpp::string_view;
-
-/// Minimal fixed-size stack buffer for constructing file paths without dynamic
-/// memory allocation.
-struct FixedSizeBuffer {
-  char data[64];
-  size_t idx = 0;
-
-  FixedSizeBuffer() { data[0] = '\0'; }
-
-  bool append(string_view str) {
-    size_t len = str.size();
-    if (idx + len >= sizeof(data))
-      return false;
-    LIBC_NAMESPACE::inline_memcpy(data + idx, str.data(), len);
-    idx += len;
-    data[idx] = '\0';
-    return true;
-  }
-
-  template <size_t N> bool append(const char (&str)[N]) {
-    size_t len = N - 1;
-    if (idx + len >= sizeof(data))
-      return false;
-    LIBC_NAMESPACE::inline_memcpy(data + idx, str, len);
-    idx += len;
-    data[idx] = '\0';
-    return true;
-  }
-};
-
-LIBC_INLINE void report_error(string_view msg) {
-  LIBC_NAMESPACE::linux_syscalls::write(2, msg.data(), msg.size());
-}
-
-} // anonymous namespace
-
-/// Writes raw coverage profile data to disk using direct Linux syscalls.
-extern "C" void write_raw_profile() {
-  if (!__llvm_profile_get_size_for_buffer || !__llvm_profile_write_buffer)
-    return;
-
-  size_t required_size =
-      static_cast<size_t>(__llvm_profile_get_size_for_buffer());
-  if (required_size == 0)
-    return;
-
-  auto mmap_or_error = LIBC_NAMESPACE::linux_syscalls::mmap(
-      nullptr, required_size, PROT_READ | PROT_WRITE,
-      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
-  if (!mmap_or_error)
-    return report_error("error: libc coverage failed to mmap buffer\n");
-  char *profile_buffer = static_cast<char *>(mmap_or_error.value());
-
-  if (__llvm_profile_write_buffer(profile_buffer) != 0) {
-    LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
-    return report_error(
-        "error: libc coverage failed to write profile buffer\n");
-  }
-
-  // Create a minimal filename: libc_cov_<pid>.profraw
-  long pid = LIBC_NAMESPACE::syscall_impl<long>(SYS_getpid);
-  if (pid <= 0)
-    pid = 1;
-
-  FixedSizeBuffer filename;
-  char pid_buf[LIBC_NAMESPACE::IntegerToString<long>::buffer_size()];
-  auto pid_str = LIBC_NAMESPACE::IntegerToString<long>::format_to(pid_buf, pid);
-  if (!pid_str || !filename.append("libc_cov_") || !filename.append(*pid_str) ||
-      !filename.append(".profraw")) {
-    LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
-    return report_error("error: libc coverage filename buffer overflow\n");
-  }
-
-  auto fd_or_error = LIBC_NAMESPACE::linux_syscalls::open(
-      filename.data, O_WRONLY | O_CREAT | O_TRUNC, 0644);
-  if (!fd_or_error) {
-    LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
-    return report_error("error: libc coverage failed to open output file\n");
-  }
-  int fd = fd_or_error.value();
-
-  size_t bytes_written = 0;
-  bool write_error_occurred = false;
-  while (bytes_written < required_size) {
-    auto write_or_error = LIBC_NAMESPACE::linux_syscalls::write(
-        fd, profile_buffer + bytes_written, required_size - bytes_written);
-    if (!write_or_error) {
-      if (write_or_error.error() == EINTR)
-        continue;
-      write_error_occurred = true;
-      break;
-    }
-    ssize_t ret = write_or_error.value();
-    if (ret == 0) {
-      write_error_occurred = true;
-      break;
-    }
-    bytes_written += ret;
-  }
-
-  LIBC_NAMESPACE::linux_syscalls::close(fd);
-  LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
-
-  if (write_error_occurred || bytes_written < required_size)
-    return report_error(
-        "error: libc coverage failed to write all data to file\n");
-
-  // Clear the filename pattern to prevent compiler-rt from writing at exit.
-  if (__llvm_profile_set_filename)
-    __llvm_profile_set_filename("/dev/null");
-}
-
-#else
-
-extern "C" void write_raw_profile() {}
-
-#endif // LIBC_TARGET_OS_IS_LINUX
-
-#endif // LLVM_LIBC_TEST_UNITTEST_COVERAGE_H
diff --git a/libc/test/UnitTest/ExecuteFunctionUnix.cpp b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
index 2c8a5aa4b7f65..a07c92f61225c 100644
--- a/libc/test/UnitTest/ExecuteFunctionUnix.cpp
+++ b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
@@ -18,7 +18,6 @@
 #include "include/llvm-libc-types/struct_pollfd.h"
 #include "src/poll/poll.h"
 #include "src/signal/kill.h"
-#include "src/signal/sigaction.h"
 #include "src/stdio/fflush.h"
 #include "src/stdio/stderr.h"
 #include "src/stdio/stdout.h"
@@ -27,7 +26,6 @@
 #include "src/sys/wait/waitpid.h"
 #include "src/unistd/close.h"
 #include "src/unistd/fork.h"
-#include "src/unistd/getpid.h"
 #include "src/unistd/pipe.h"
 
 #define LIBC_IMPL LIBC_NAMESPACE
@@ -44,10 +42,6 @@
 #define LIBC_IMPL
 #endif
 
-#if defined(LIBC_ENABLE_COVERAGE)
-extern "C" __attribute__((weak)) void write_raw_profile();
-#endif
-
 namespace LIBC_NAMESPACE_DECL {
 namespace testutils {
 
@@ -64,25 +58,6 @@ int ProcessStatus::get_fatal_signal() {
   return WTERMSIG(platform_defined);
 }
 
-#if defined(LIBC_ENABLE_COVERAGE)
-static void coverage_fatal_signal_handler(int sig) {
-  if (write_raw_profile)
-    write_raw_profile();
-
-  // Restore default signal handler
-#ifdef LIBC_FULL_BUILD
-  struct sigaction sa = {};
-  sa.sa_handler = SIG_DFL;
-  LIBC_IMPL::sigaction(sig, &sa, nullptr);
-#else
-  ::signal(sig, SIG_DFL);
-#endif
-
-  // Re-raise the signal
-  LIBC_IMPL::kill(LIBC_IMPL::getpid(), sig);
-}
-#endif
-
 ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
   int pipe_fds[2];
   if (LIBC_IMPL::pipe(pipe_fds) == -1) {
@@ -100,31 +75,10 @@ ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
   }
 
   if (!pid) {
-#if defined(LIBC_ENABLE_COVERAGE) && defined(LIBC_FULL_BUILD)
-    struct sigaction sa = {};
-    sa.sa_handler = coverage_fatal_signal_handler;
-    LIBC_IMPL::sigaction(SIGABRT, &sa, nullptr);
-    LIBC_IMPL::sigaction(SIGSEGV, &sa, nullptr);
-    LIBC_IMPL::sigaction(SIGILL, &sa, nullptr);
-    LIBC_IMPL::sigaction(SIGFPE, &sa, nullptr);
-    LIBC_IMPL::sigaction(SIGBUS, &sa, nullptr);
-#elif defined(LIBC_ENABLE_COVERAGE)
-    ::signal(SIGABRT, coverage_fatal_signal_handler);
-    ::signal(SIGSEGV, coverage_fatal_signal_handler);
-    ::signal(SIGILL, coverage_fatal_signal_handler);
-    ::signal(SIGFPE, coverage_fatal_signal_handler);
-    ::signal(SIGBUS, coverage_fatal_signal_handler);
-#endif
-
     (*func)();
     delete func;
-#if defined(LIBC_ENABLE_COVERAGE)
-    if (write_raw_profile)
-      write_raw_profile();
-#endif
     LIBC_IMPL::exit(0);
   }
-
   LIBC_IMPL::close(pipe_fds[1]);
 
   pollfd poll_fd{pipe_fds[0], POLLIN, 0};
diff --git a/libc/test/UnitTest/LibcTestMain.cpp b/libc/test/UnitTest/LibcTestMain.cpp
index 60bad42003645..c348d5ef1aa1b 100644
--- a/libc/test/UnitTest/LibcTestMain.cpp
+++ b/libc/test/UnitTest/LibcTestMain.cpp
@@ -43,10 +43,8 @@ TestOptions parseOptions(int argc, char **argv) {
 
 } // anonymous namespace
 
-#if defined(LIBC_ENABLE_COVERAGE)
-#include "Coverage.h"
-#endif
-
+// The C++ standard forbids declaring the main function with a linkage specifier
+// outisde of 'freestanding' mode, only define the linkage for hermetic tests.
 #if __STDC_HOSTED__
 #define TEST_MAIN int main
 #else
@@ -58,10 +56,5 @@ TEST_MAIN(int argc, char **argv, char **envp) {
   LIBC_NAMESPACE::testing::argv = argv;
   LIBC_NAMESPACE::testing::envp = envp;
 
-  int result =
-      LIBC_NAMESPACE::testing::Test::runTests(parseOptions(argc, argv));
-#if defined(LIBC_ENABLE_COVERAGE)
-  write_raw_profile();
-#endif
-  return result;
+  return LIBC_NAMESPACE::testing::Test::runTests(parseOptions(argc, argv));
 }

>From 1d8b15b19f228defd2d71f39e683aeaf9ecb2aab Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Fri, 4 Sep 2026 14:50:00 +0000
Subject: [PATCH 07/18] [libc] Clean up continuous coverage CMake configuration

- Remove unused LIBC_ENABLE_COVERAGE preprocessor definition.
- Deduplicate coverage flags by applying them solely to LIBC_COMPILE_OPTIONS_DEFAULT.
- Remove frontend-only -fcoverage-mapping from linker options.
- Standardize comments and 80-column formatting.

Assisted-by: Automated tooling, human reviewed.
---
 libc/CMakeLists.txt | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index d86496f1f059b..9b7f0737650f0 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -91,18 +91,20 @@ endif()
 set(LIBC_LINK_OPTIONS_DEFAULT "" CACHE STRING "Arguments used when linking.")
 set(LIBC_TEST_LINK_OPTIONS_DEFAULT "" CACHE STRING "Common link options for all the tests.")
 
+# Enable source-based code coverage using Clang's continuous profiling mode.
 option(LIBC_ENABLE_COVERAGE "Build libc with coverage instrumentation" OFF)
 if(LIBC_ENABLE_COVERAGE)
   if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
-    add_compile_definitions(LIBC_ENABLE_COVERAGE)
-    list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate=libc_cov_%p.profraw"
-         "-fcoverage-mapping" "-fprofile-continuous")
-    list(APPEND LIBC_TEST_COMPILE_OPTIONS_DEFAULT "-fprofile-instr-generate=libc_cov_%p.profraw"
-         "-fcoverage-mapping" "-fprofile-continuous")
-    list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT "-fprofile-instr-generate=libc_cov_%p.profraw"
-         "-fcoverage-mapping" "-fprofile-continuous")
+    list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT
+         "-fprofile-instr-generate=libc_cov_%p.profraw"
+         "-fcoverage-mapping"
+         "-fprofile-continuous")
+    list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT
+         "-fprofile-instr-generate=libc_cov_%p.profraw"
+         "-fprofile-continuous")
   else()
-    message(FATAL_ERROR "Coverage instrumentation is currently only supported with Clang")
+    message(FATAL_ERROR
+            "Coverage instrumentation is currently only supported with Clang")
   endif()
 endif()
 

>From 5a7cdf89dc0e82f03cce6d3dd2a9fcce2504a56e Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Fri, 4 Sep 2026 16:28:57 +0000
Subject: [PATCH 08/18] [libc][docs] Add developer guide for continuous code
 coverage and MC/DC

Assisted-by: Automated tooling, human reviewed.
---
 libc/docs/dev/code_coverage.md | 336 +++++++++++++++++++++++++++++++++
 libc/docs/dev/index.md         |   1 +
 2 files changed, 337 insertions(+)
 create mode 100644 libc/docs/dev/code_coverage.md

diff --git a/libc/docs/dev/code_coverage.md b/libc/docs/dev/code_coverage.md
new file mode 100644
index 0000000000000..cc404828bc75f
--- /dev/null
+++ b/libc/docs/dev/code_coverage.md
@@ -0,0 +1,336 @@
+(code_coverage)=
+
+# Code Coverage
+
+Code coverage is a software testing metric that measures the proportion of source code executed while running an automated test suite. It provides insight into test thoroughness by identifying untested functions, dead code paths, and unexercised conditional branches across library entrypoints and internal utilities.
+
+### Modified Condition / Decision Coverage (MC/DC)
+
+LLVM-libc supports Modified Condition / Decision Coverage (MC/DC). MC/DC evaluates compound boolean decisions composed of multiple sub-conditions (such as `if (A && (B || C))`). Under MC/DC criteria, each individual boolean condition must:
+* Evaluate to both true and false across the test suite.
+* Demonstrate that it can independently affect the outcome of the overall decision while other conditions remain fixed.
+
+This provides rigorous structural verification for safety-critical algorithms without requiring exhaustive testing of all $2^n$ condition permutations.
+
+## Continuous Profiling Architecture
+
+This PR uses Clang's [continuous profiling mode](https://clang.llvm.org/docs/UsersManual.html#cmdoption-fprofile-continuous) (`-fprofile-continuous`) to record execution metrics directly into memory-mapped profile files during test execution.
+
+### Compiler Counter Relocation
+When compiled with `-fprofile-continuous`, Clang configures the LLVM code generator (`-mllvm -runtime-counter-relocation=true`) so that execution counter increments reference a dynamic base pointer (`*(bias + &counter) += 1`). Each branch and basic block counter dynamically resolves to an address within a dedicated profile buffer mapped at program startup.
+
+### Runtime Memory Mapping
+During binary initialization, the profiling runtime (`libclang_rt.profile`) resolves the target `.profraw` file and maps the execution counter section into process memory using `mmap` with `MAP_SHARED`. The runtime sets the global bias pointer to this mapped region, routing live counter increments directly into the file-backed buffer.
+
+### Kernel Page-Cache Synchronization
+Execution counts are written directly to shared memory-mapped pages and synchronized by the operating system kernel's page cache. Subprocesses created via `fork()` share the same underlying memory mapping, committing statements executed across parent and child processes directly to the profile file.
+
+### Build System Integration
+Setting `-DLIBC_ENABLE_COVERAGE=ON` in the CMake configuration passes `-fprofile-instr-generate=libc_cov_%p.profraw`, `-fcoverage-mapping`, and `-fprofile-continuous` across all LLVM-libc compilation units and test link steps. This ensures uniform instrumentation across entrypoints and unit test harnesses.
+
+
+## Running Code Coverage Locally
+
+### Prerequisites & Toolchain Setup
+
+Generating coverage reports requires Clang, LLVM profile tools, CMake, and Ninja:
+
+* **Compiler:** Clang 18 or later (Clang 21 or later is required for MC/DC instrumentation).
+* **LLVM Utilities:** Matching major versions of `llvm-profdata` and `llvm-cov`.
+* **Build System:** CMake 3.28+ and Ninja.
+
+#### Toolchain Discovery
+
+If your Linux distribution packages version-suffixed binaries (e.g. `clang-21`, `llvm-profdata-21`), you can resolve them automatically:
+
+```bash
+CLANG_MAJOR=$(clang --version | sed -n 's/.*version \([0-9]*\).*/\1/p')
+LLVM_PROFDATA=$(which llvm-profdata-$CLANG_MAJOR 2>/dev/null || which llvm-profdata)
+LLVM_COV=$(which llvm-cov-$CLANG_MAJOR 2>/dev/null || which llvm-cov)
+```
+
+---
+
+## Cleaning Profile Counters
+
+Removes previously generated raw profile counter files (`.profraw`) and merged profile databases (`.profdata`) so that new coverage runs record clean, non-aggregated execution data:
+
+```bash
+find . -name "libc_cov_*.profraw" -delete 2>/dev/null || true
+rm -f libc_full.profdata libc_mcdc.profdata libc_single.profdata profraw_list.txt
+```
+
+---
+
+## Standard Statement & Branch Coverage
+
+Standard coverage measures physical line execution and conditional branch outcomes across all LLVM-libc entrypoints and internal support utilities.
+
+### 1. CMake Configuration
+
+Configures CMake to build LLVM-libc in overlay mode, setting `-DLIBC_ENABLE_COVERAGE=ON` to pass Clang's continuous profiling and coverage mapping flags to the compiler:
+
+```bash
+cmake -G Ninja -S runtimes -B build-cov \
+  -DCMAKE_C_COMPILER=clang \
+  -DCMAKE_CXX_COMPILER=clang++ \
+  -DCMAKE_BUILD_TYPE=Debug \
+  -DLLVM_ENABLE_RUNTIMES="libc" \
+  -DLLVM_LIBC_FULL_BUILD=OFF \
+  -DLIBC_ENABLE_COVERAGE=ON
+```
+
+### 2. Build and Execute All Unit Tests
+
+Compiles all libc unit test executables and executes them in parallel. As each test executes, its counters are mapped directly to disk via the OS page cache:
+
+```bash
+export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
+ninja -k 0 -C build-cov libc-unit-tests
+```
+
+:::{note}
+In LLVM-libc, `libc-unit-tests` builds and executes tests in a single invocation. The `-k 0` flag ensures Ninja continues executing all remaining test targets even if an individual edge-case test encounters an error. To only compile test binaries without immediately executing them, use `ninja -C build-cov libc-unit-tests-build`.
+:::
+
+### 3. Merge Profile Counters
+
+Scans the build tree for all generated `.profraw` files and indexes them into a unified, sparse `.profdata` archive using `llvm-profdata`:
+
+```bash
+find build-cov/ -name "libc_cov_*.profraw" > profraw_list.txt
+llvm-profdata merge -sparse -f profraw_list.txt -o libc_full.profdata
+```
+
+### 4. Generate Coverage Reports
+
+Collects all compiled test binary paths and invokes `llvm-cov` to correlate recorded profile counters against the libc source tree:
+
+```bash
+TEST_BINS=($(find build-cov -type f -executable -name "*__build__"))
+OBJECT_FLAGS=()
+for bin in "${TEST_BINS[@]:1}"; do
+  OBJECT_FLAGS+=("-object=$bin")
+done
+```
+
+Reports can be generated in different formats:
+
+#### Option 1: Terminal Summary Report
+Prints an aggregated terminal summary showing line, region, and branch coverage percentages for each file:
+
+```bash
+llvm-cov report \
+  -instr-profile=libc_full.profdata \
+  "${TEST_BINS[0]}" "${OBJECT_FLAGS[@]}" \
+  --show-branch-summary \
+  -ignore-filename-regex=".*(test|utils).*"
+```
+
+#### Option 2: Interactive HTML Dashboard
+Generates an interactive HTML dashboard containing sortable directory metrics and syntax-highlighted source views:
+
+```bash
+llvm-cov show \
+  -format=html \
+  -output-dir=coverage_html \
+  -instr-profile=libc_full.profdata \
+  "${TEST_BINS[0]}" "${OBJECT_FLAGS[@]}" \
+  --show-directory-coverage \
+  --show-branches=count \
+  -ignore-filename-regex=".*(test|utils).*"
+
+# Open dashboard in browser
+xdg-open coverage_html/index.html
+```
+
+---
+
+## Modified Condition / Decision Coverage (MC/DC)
+
+MC/DC evaluates boolean sub-conditions within compound logical expressions (such as `if (A && B)`). It verifies that each individual sub-condition evaluates to both true and false and independently affects the outcome of the enclosing decision.
+
+### 1. CMake Configuration
+
+Configures CMake with `-fcoverage-mcdc` alongside profiling flags, enabling the compiler frontend to generate boolean condition bitmaps for compound decisions:
+
+```bash
+cmake -G Ninja -S runtimes -B build-cov-mcdc \
+  -DCMAKE_C_COMPILER=clang \
+  -DCMAKE_CXX_COMPILER=clang++ \
+  -DCMAKE_BUILD_TYPE=Debug \
+  -DLLVM_ENABLE_RUNTIMES="libc" \
+  -DLLVM_LIBC_FULL_BUILD=OFF \
+  -DLIBC_ENABLE_COVERAGE=ON \
+  -DCMAKE_C_FLAGS="-fcoverage-mcdc" \
+  -DCMAKE_CXX_FLAGS="-fcoverage-mcdc"
+```
+
+### 2. Build and Execute Tests
+
+Compiles and executes all unit tests with MC/DC instrumentation enabled, saving condition evaluation bitmasks into raw profile files upon completion:
+
+```bash
+export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
+ninja -k 0 -C build-cov-mcdc libc-unit-tests
+```
+
+### 3. Merge Profiles
+
+Indexes and merges all MC/DC `.profraw` files into a unified `libc_mcdc.profdata` archive for report generation:
+
+```bash
+find build-cov-mcdc/ -name "libc_cov_*.profraw" > profraw_list.txt
+llvm-profdata merge -sparse -f profraw_list.txt -o libc_mcdc.profdata
+```
+
+### 4. Generate Reports
+
+Maps MC/DC bitmap records to source AST decisions and evaluates condition independence pairs:
+
+```bash
+TEST_BINS=($(find build-cov-mcdc -type f -executable -name "*__build__"))
+OBJECT_FLAGS=()
+for bin in "${TEST_BINS[@]:1}"; do
+  OBJECT_FLAGS+=("-object=$bin")
+done
+```
+
+Reports can be generated in two formats depending on your needs:
+
+#### Option 1: Terminal Summary Report
+Displays the terminal coverage summary including MC/DC Condition and Missed Condition percentages:
+
+```bash
+llvm-cov report \
+  -instr-profile=libc_mcdc.profdata \
+  "${TEST_BINS[0]}" "${OBJECT_FLAGS[@]}" \
+  --show-branch-summary \
+  --show-mcdc-summary \
+  -ignore-filename-regex=".*(test|utils).*"
+```
+
+#### Option 2: Interactive HTML Dashboard
+Produces an HTML report with expandable MC/DC decision truth tables and test vector coverage breakdowns:
+
+```bash
+llvm-cov show \
+  -format=html \
+  -output-dir=coverage_mcdc_html \
+  -instr-profile=libc_mcdc.profdata \
+  "${TEST_BINS[0]}" "${OBJECT_FLAGS[@]}" \
+  --show-directory-coverage \
+  --show-branches=count \
+  --show-mcdc \
+  --show-mcdc-summary \
+  -ignore-filename-regex=".*(test|utils).*"
+
+# Open dashboard in browser
+xdg-open coverage_mcdc_html/index.html
+```
+
+---
+
+## Running Coverage for a Single Test
+
+When developing or modifying a specific function, coverage can be collected for a single test without building and executing the entire test suite.
+
+The commands below use `libc.test.src.ctype.isalpha_test` (which tests `libc/src/ctype/isalpha.cpp`) as an example. You can test any other entrypoint by substituting the target name and source file path:
+* **Target pattern:** `libc.test.<path_to_test>.<test_name>` (e.g. `libc.test.src.string.strlen_test`)
+* **Source path pattern:** `libc/<path_to_source>/<source_file>.cpp` (e.g. `libc/src/string/strlen.cpp`)
+
+### 1. Build and Execute the Targeted Test
+
+Compiles and runs only the specified test binary, immediately writing execution profile counters to disk upon completion:
+
+```bash
+export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
+
+# For a standard coverage build
+ninja -C build-cov libc.test.src.ctype.isalpha_test
+
+# For an MC/DC build
+ninja -C build-cov-mcdc libc.test.src.ctype.isalpha_test
+```
+
+### 2. Merge the Profile
+
+Merges the single test's raw profile into an indexed database for targeted inspection:
+
+```bash
+find build-cov/ -name "libc_cov_*.profraw" > profraw_list.txt
+llvm-profdata merge -sparse -f profraw_list.txt -o libc_single.profdata
+```
+
+### 3. View the Terminal Report
+
+Reports can be viewed as an overall file summary or an annotated line-by-line breakdown:
+
+#### Option 1: Summary Table Report
+```bash
+llvm-cov report \
+  -instr-profile=libc_single.profdata \
+  ./build-cov/libc/test/src/ctype/libc.test.src.ctype.isalpha_test.__build__ \
+  libc/src/ctype/isalpha.cpp
+```
+
+#### Option 2: Line-by-Line & Truth Table View
+```bash
+llvm-cov show \
+  -instr-profile=libc_single.profdata \
+  ./build-cov-mcdc/libc/test/src/ctype/libc.test.src.ctype.isalpha_test.__build__ \
+  --show-branches=count \
+  --show-mcdc \
+  libc/src/ctype/isalpha.cpp
+```
+
+---
+
+## Interpreting Results
+
+For detailed documentation on the LLVM coverage reporting format, refer to the [official Clang Source-Based Code Coverage documentation](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#interpreting-reports).
+
+### Coverage Metrics Overview
+
+* **Line Coverage:** Measures whether each physical line of executable source code was reached at least once during testing.
+* **Branch Coverage:** Measures whether each conditional branch evaluated to both its `True` and `False` paths. For example, if an `if (x > 0)` branch is taken 10 times but never skipped, branch coverage is 50% because the `False` path was never exercised.
+* **MC/DC Coverage:** Evaluates compound boolean expressions (such as `if (A && B)` or `if (A || B)`). It verifies that each individual condition was tested as both True and False, and demonstrated that it could independently change the overall outcome of the decision.
+
+### Interpreting Reports
+
+The summary table produced by `llvm-cov report` displays metrics across individual source files and overall totals:
+
+* **Regions / Missed Regions:** A region is a continuous segment of code (such as a function body or basic block). Missed regions indicate code blocks that were never executed.
+* **Functions / Missed Functions:** The total number of entrypoints or subroutines executed vs unexecuted.
+* **Lines / Missed Lines:** Physical source lines executed vs unexecuted.
+* **Branches / Missed Branches:** The total count of decision directions (both True and False) evaluated.
+* **MC/DC Conditions / Missed Conditions:** The count of individual boolean sub-conditions that demonstrated independent decision control.
+
+### Interpreting MC/DC Truth Tables
+
+When inspecting with `--show-mcdc`, `llvm-cov` displays an MC/DC analysis table beneath each compound decision. For instance, consider the following decision:
+
+```text
+   19|  if (c < 0 || c > 255)
+  -----------------------------------------------
+  | Conditions: C1 = (c < 0), C2 = (c > 255)
+  |
+  | Executed Test Vectors:
+  |    C1, C2    Result
+  | 1 { F,  F  = F      }  (tested with c = 'a')
+  | 2 { T,  -  = T      }  (tested with c = -1)
+  |
+  | C1-Pair: covered (1, 2)
+  | C2-Pair: not covered
+  | MC/DC Coverage: 50.00%
+  -----------------------------------------------
+```
+
+* **Conditions:** **C1** represents `c < 0` and **C2** represents `c > 255`.
+* **Executed Vectors:**
+  * **Vector 1 (`F, F = F`):** Tested with a valid character (`c = 'a'`). Both C1 and C2 evaluated False, producing an overall False result.
+  * **Vector 2 (`T, - = T`):** Tested with a negative value (`c = -1`). C1 evaluated True, producing an overall True result. The hyphen (`-`) indicates C2 was short-circuited and not evaluated.
+* **Condition Pairs:**
+  * **`C1-Pair: covered (1, 2)`:** Comparing Vector 1 and Vector 2 proves that changing C1 from False to True directly flipped the result from False to True. C1 is fully covered.
+  * **`C2-Pair: not covered`:** C2 was never tested in a state where it independently turned the result True while C1 was False.
+* **Reaching 100% Coverage:** Add a test with a value above 255 (`c = 256`). This executes Vector 3 (`F, T = T`), forming the independence pair `(1, 3)` for C2 and reaching 100% MC/DC coverage.
diff --git a/libc/docs/dev/index.md b/libc/docs/dev/index.md
index 4ce78540c8fad..25b8102c71059 100644
--- a/libc/docs/dev/index.md
+++ b/libc/docs/dev/index.md
@@ -22,4 +22,5 @@ printf_behavior
 builtin_compatibility
 syscall_wrapper_refactor
 modular_format
+code_coverage
 ```

>From 36c5fc2ab2b9956c033159023bc17270e41fc5d2 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Sun, 6 Sep 2026 15:57:05 +0000
Subject: [PATCH 09/18] [libc][docs] Fix MyST heading hierarchy in code
 coverage guide

Remove non-consecutive H1 to H3 heading jump in code_coverage.md to
resolve Sphinx build failure under -W (myst.header warning).
Update introductory text flow and remove duplicate section target.

Assisted-by: Automated tooling, human reviewed.
---
 libc/docs/dev/code_coverage.md | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/libc/docs/dev/code_coverage.md b/libc/docs/dev/code_coverage.md
index cc404828bc75f..c2697e19a4a8a 100644
--- a/libc/docs/dev/code_coverage.md
+++ b/libc/docs/dev/code_coverage.md
@@ -4,8 +4,6 @@
 
 Code coverage is a software testing metric that measures the proportion of source code executed while running an automated test suite. It provides insight into test thoroughness by identifying untested functions, dead code paths, and unexercised conditional branches across library entrypoints and internal utilities.
 
-### Modified Condition / Decision Coverage (MC/DC)
-
 LLVM-libc supports Modified Condition / Decision Coverage (MC/DC). MC/DC evaluates compound boolean decisions composed of multiple sub-conditions (such as `if (A && (B || C))`). Under MC/DC criteria, each individual boolean condition must:
 * Evaluate to both true and false across the test suite.
 * Demonstrate that it can independently affect the outcome of the overall decision while other conditions remain fixed.
@@ -14,7 +12,7 @@ This provides rigorous structural verification for safety-critical algorithms wi
 
 ## Continuous Profiling Architecture
 
-This PR uses Clang's [continuous profiling mode](https://clang.llvm.org/docs/UsersManual.html#cmdoption-fprofile-continuous) (`-fprofile-continuous`) to record execution metrics directly into memory-mapped profile files during test execution.
+LLVM-libc uses Clang's [continuous profiling mode](https://clang.llvm.org/docs/UsersManual.html#cmdoption-fprofile-continuous) (`-fprofile-continuous`) to record execution metrics directly into memory-mapped profile files during test execution.
 
 ### Compiler Counter Relocation
 When compiled with `-fprofile-continuous`, Clang configures the LLVM code generator (`-mllvm -runtime-counter-relocation=true`) so that execution counter increments reference a dynamic base pointer (`*(bias + &counter) += 1`). Each branch and basic block counter dynamically resolves to an address within a dedicated profile buffer mapped at program startup.

>From 9eafb0ef049e7ea967b336c2eddc43fdcd90cead Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Mon, 7 Sep 2026 10:12:19 +0000
Subject: [PATCH 10/18] [libc] Fix continuous coverage profiling flags and doc
 formatting

- Add %c to default profile pattern (libc_cov_%c%p.profraw) to activate continuous profiling mode in compiler-rt.
- Reject GPU, baremetal, and hermetic full-build modes in CMake when LIBC_ENABLE_COVERAGE=ON.
- Hard-wrap libc/docs/dev/code_coverage.md to strictly 80 columns and remove consecutive blank lines.
- Fix single-test profile search path to query both build-cov/ and build-cov-mcdc/.
- Replace LaTeX math delimiter with HTML superscript in Sphinx documentation.
- Consistently use $LLVM_PROFDATA and $LLVM_COV across documentation commands.

Assisted-by: Automated tooling, human reviewed.
---
 libc/CMakeLists.txt            |  17 ++-
 libc/docs/dev/code_coverage.md | 238 +++++++++++++++++++++++----------
 2 files changed, 186 insertions(+), 69 deletions(-)

diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index 9b7f0737650f0..b21741c161e44 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -96,11 +96,11 @@ option(LIBC_ENABLE_COVERAGE "Build libc with coverage instrumentation" OFF)
 if(LIBC_ENABLE_COVERAGE)
   if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
     list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT
-         "-fprofile-instr-generate=libc_cov_%p.profraw"
+         "-fprofile-instr-generate=libc_cov_%c%p.profraw"
          "-fcoverage-mapping"
          "-fprofile-continuous")
     list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT
-         "-fprofile-instr-generate=libc_cov_%p.profraw"
+         "-fprofile-instr-generate=libc_cov_%c%p.profraw"
          "-fprofile-continuous")
   else()
     message(FATAL_ERROR
@@ -174,6 +174,19 @@ if(LLVM_LIBC_FULL_BUILD)
 else()
   set(LLVM_LIBC_OVERLAY ON)
 endif()
+
+if(LIBC_ENABLE_COVERAGE)
+  if(LIBC_TARGET_OS_IS_GPU OR LIBC_TARGET_OS_IS_BAREMETAL)
+    message(FATAL_ERROR
+      "LIBC_ENABLE_COVERAGE is not supported on GPU or baremetal targets.")
+  endif()
+  if(LLVM_LIBC_FULL_BUILD)
+    message(FATAL_ERROR
+      "LIBC_ENABLE_COVERAGE is only supported in overlay mode "
+      "(LLVM_LIBC_FULL_BUILD=OFF). Full build mode uses hermetic tests "
+      "which cannot link the compiler-rt profiling runtime.")
+  endif()
+endif()
 option(LLVM_LIBC_IMPLEMENTATION_DEFINED_TEST_BEHAVIOR "Build LLVM libc tests assuming our implementation-defined behavior" ON)
 option(LLVM_LIBC_ENABLE_LINTING "Enables linting of libc source files" OFF)
 option(LLVM_LIBC_ALL_HEADERS "Outputs all functions in header files, regardless of whether they are enabled on this target" OFF)
diff --git a/libc/docs/dev/code_coverage.md b/libc/docs/dev/code_coverage.md
index c2697e19a4a8a..ab6a8288f1fdc 100644
--- a/libc/docs/dev/code_coverage.md
+++ b/libc/docs/dev/code_coverage.md
@@ -2,71 +2,120 @@
 
 # Code Coverage
 
-Code coverage is a software testing metric that measures the proportion of source code executed while running an automated test suite. It provides insight into test thoroughness by identifying untested functions, dead code paths, and unexercised conditional branches across library entrypoints and internal utilities.
-
-LLVM-libc supports Modified Condition / Decision Coverage (MC/DC). MC/DC evaluates compound boolean decisions composed of multiple sub-conditions (such as `if (A && (B || C))`). Under MC/DC criteria, each individual boolean condition must:
+Code coverage is a software testing metric that measures the proportion of
+source code executed while running an automated test suite. It provides insight
+into test thoroughness by identifying untested functions, dead code paths, and
+unexercised conditional branches across library entrypoints and internal
+utilities.
+
+LLVM-libc supports Modified Condition / Decision Coverage (MC/DC). MC/DC
+evaluates compound boolean decisions composed of multiple sub-conditions (such
+as `if (A && (B || C))`). Under MC/DC criteria, each individual boolean
+condition must:
 * Evaluate to both true and false across the test suite.
-* Demonstrate that it can independently affect the outcome of the overall decision while other conditions remain fixed.
+* Demonstrate that it can independently affect the outcome of the overall
+  decision while other conditions remain fixed.
 
-This provides rigorous structural verification for safety-critical algorithms without requiring exhaustive testing of all $2^n$ condition permutations.
+This provides rigorous structural verification for safety-critical algorithms
+without requiring exhaustive testing of all 2<sup>n</sup> condition
+permutations.
 
 ## Continuous Profiling Architecture
 
-LLVM-libc uses Clang's [continuous profiling mode](https://clang.llvm.org/docs/UsersManual.html#cmdoption-fprofile-continuous) (`-fprofile-continuous`) to record execution metrics directly into memory-mapped profile files during test execution.
+LLVM-libc uses Clang's [continuous profiling mode][clang-continuous]
+(`-fprofile-continuous`) to record execution metrics directly into
+memory-mapped profile files during test execution.
+
+[clang-continuous]:
+  https://clang.llvm.org/docs/UsersManual.html#cmdoption-fprofile-continuous
 
 ### Compiler Counter Relocation
-When compiled with `-fprofile-continuous`, Clang configures the LLVM code generator (`-mllvm -runtime-counter-relocation=true`) so that execution counter increments reference a dynamic base pointer (`*(bias + &counter) += 1`). Each branch and basic block counter dynamically resolves to an address within a dedicated profile buffer mapped at program startup.
+When compiled with `-fprofile-continuous`, Clang configures the LLVM code
+generator (`-mllvm -runtime-counter-relocation=true`) so that execution counter
+increments reference a dynamic base pointer (`*(bias + &counter) += 1`). Each
+branch and basic block counter dynamically resolves to an address within a
+dedicated profile buffer mapped at program startup.
 
 ### Runtime Memory Mapping
-During binary initialization, the profiling runtime (`libclang_rt.profile`) resolves the target `.profraw` file and maps the execution counter section into process memory using `mmap` with `MAP_SHARED`. The runtime sets the global bias pointer to this mapped region, routing live counter increments directly into the file-backed buffer.
+During binary initialization, the profiling runtime (`libclang_rt.profile`)
+resolves the target `.profraw` file and maps the execution counter section into
+process memory using `mmap` with `MAP_SHARED`. The runtime sets the global bias
+pointer to this mapped region, routing live counter increments directly into the
+file-backed buffer.
 
 ### Kernel Page-Cache Synchronization
-Execution counts are written directly to shared memory-mapped pages and synchronized by the operating system kernel's page cache. Subprocesses created via `fork()` share the same underlying memory mapping, committing statements executed across parent and child processes directly to the profile file.
+Execution counts are written directly to shared memory-mapped pages and
+synchronized by the operating system kernel's page cache. Subprocesses created
+via `fork()` share the same underlying memory mapping, committing statements
+executed across parent and child processes directly to the profile file.
 
 ### Build System Integration
-Setting `-DLIBC_ENABLE_COVERAGE=ON` in the CMake configuration passes `-fprofile-instr-generate=libc_cov_%p.profraw`, `-fcoverage-mapping`, and `-fprofile-continuous` across all LLVM-libc compilation units and test link steps. This ensures uniform instrumentation across entrypoints and unit test harnesses.
-
+Setting `-DLIBC_ENABLE_COVERAGE=ON` in the CMake configuration passes
+`-fprofile-instr-generate=libc_cov_%c%p.profraw`, `-fcoverage-mapping`, and
+`-fprofile-continuous` across all LLVM-libc compilation units and test link
+steps. This ensures uniform instrumentation across entrypoints and unit test
+harnesses.
 
 ## Running Code Coverage Locally
 
 ### Prerequisites & Toolchain Setup
 
-Generating coverage reports requires Clang, LLVM profile tools, CMake, and Ninja:
+Generating coverage reports requires Clang, LLVM profile tools, CMake, and
+Ninja:
 
-* **Compiler:** Clang 18 or later (Clang 21 or later is required for MC/DC instrumentation).
+* **Compiler:** Clang 18 or later (Clang 21 or later is required for MC/DC
+  instrumentation).
 * **LLVM Utilities:** Matching major versions of `llvm-profdata` and `llvm-cov`.
 * **Build System:** CMake 3.28+ and Ninja.
 
 #### Toolchain Discovery
 
-If your Linux distribution packages version-suffixed binaries (e.g. `clang-21`, `llvm-profdata-21`), you can resolve them automatically:
+If your Linux distribution packages version-suffixed binaries (e.g. `clang-21`,
+`llvm-profdata-21`), discover and export them:
 
 ```bash
 CLANG_MAJOR=$(clang --version | sed -n 's/.*version \([0-9]*\).*/\1/p')
-LLVM_PROFDATA=$(which llvm-profdata-$CLANG_MAJOR 2>/dev/null || which llvm-profdata)
-LLVM_COV=$(which llvm-cov-$CLANG_MAJOR 2>/dev/null || which llvm-cov)
+export LLVM_PROFDATA=$(which llvm-profdata-$CLANG_MAJOR 2>/dev/null \
+  || which llvm-profdata)
+export LLVM_COV=$(which llvm-cov-$CLANG_MAJOR 2>/dev/null \
+  || which llvm-cov)
+```
+
+If version-agnostic tools are directly available in your `PATH`, export:
+
+```bash
+export LLVM_PROFDATA=llvm-profdata
+export LLVM_COV=llvm-cov
 ```
 
+Subsequent merge and report commands reference `$LLVM_PROFDATA` and `$LLVM_COV`.
+
 ---
 
 ## Cleaning Profile Counters
 
-Removes previously generated raw profile counter files (`.profraw`) and merged profile databases (`.profdata`) so that new coverage runs record clean, non-aggregated execution data:
+Removes previously generated raw profile counter files (`.profraw`) and merged
+profile databases (`.profdata`) so that new coverage runs record clean,
+non-aggregated execution data:
 
 ```bash
 find . -name "libc_cov_*.profraw" -delete 2>/dev/null || true
-rm -f libc_full.profdata libc_mcdc.profdata libc_single.profdata profraw_list.txt
+rm -f libc_full.profdata libc_mcdc.profdata libc_single.profdata \
+  profraw_list.txt
 ```
 
 ---
 
 ## Standard Statement & Branch Coverage
 
-Standard coverage measures physical line execution and conditional branch outcomes across all LLVM-libc entrypoints and internal support utilities.
+Standard coverage measures physical line execution and conditional branch
+outcomes across all LLVM-libc entrypoints and internal support utilities.
 
 ### 1. CMake Configuration
 
-Configures CMake to build LLVM-libc in overlay mode, setting `-DLIBC_ENABLE_COVERAGE=ON` to pass Clang's continuous profiling and coverage mapping flags to the compiler:
+Configures CMake to build LLVM-libc in overlay mode, setting
+`-DLIBC_ENABLE_COVERAGE=ON` to pass Clang's continuous profiling and coverage
+mapping flags to the compiler:
 
 ```bash
 cmake -G Ninja -S runtimes -B build-cov \
@@ -80,7 +129,8 @@ cmake -G Ninja -S runtimes -B build-cov \
 
 ### 2. Build and Execute All Unit Tests
 
-Compiles all libc unit test executables and executes them in parallel. As each test executes, its counters are mapped directly to disk via the OS page cache:
+Compiles all libc unit test executables and executes them in parallel. As each
+test executes, its counters are mapped directly to disk via the OS page cache:
 
 ```bash
 export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
@@ -88,21 +138,27 @@ ninja -k 0 -C build-cov libc-unit-tests
 ```
 
 :::{note}
-In LLVM-libc, `libc-unit-tests` builds and executes tests in a single invocation. The `-k 0` flag ensures Ninja continues executing all remaining test targets even if an individual edge-case test encounters an error. To only compile test binaries without immediately executing them, use `ninja -C build-cov libc-unit-tests-build`.
+In LLVM-libc, `libc-unit-tests` builds and executes tests in a single
+invocation. The `-k 0` flag ensures Ninja continues executing all remaining
+test targets even if an individual edge-case test encounters an error. To only
+compile test binaries without immediately executing them, use
+`ninja -C build-cov libc-unit-tests-build`.
 :::
 
 ### 3. Merge Profile Counters
 
-Scans the build tree for all generated `.profraw` files and indexes them into a unified, sparse `.profdata` archive using `llvm-profdata`:
+Scans the build tree for all generated `.profraw` files and indexes them into a
+unified, sparse `.profdata` archive using `$LLVM_PROFDATA`:
 
 ```bash
 find build-cov/ -name "libc_cov_*.profraw" > profraw_list.txt
-llvm-profdata merge -sparse -f profraw_list.txt -o libc_full.profdata
+"$LLVM_PROFDATA" merge -sparse -f profraw_list.txt -o libc_full.profdata
 ```
 
 ### 4. Generate Coverage Reports
 
-Collects all compiled test binary paths and invokes `llvm-cov` to correlate recorded profile counters against the libc source tree:
+Collects all compiled test binary paths and invokes `$LLVM_COV` to correlate
+recorded profile counters against the libc source tree:
 
 ```bash
 TEST_BINS=($(find build-cov -type f -executable -name "*__build__"))
@@ -115,10 +171,11 @@ done
 Reports can be generated in different formats:
 
 #### Option 1: Terminal Summary Report
-Prints an aggregated terminal summary showing line, region, and branch coverage percentages for each file:
+Prints an aggregated terminal summary showing line, region, and branch coverage
+percentages for each file:
 
 ```bash
-llvm-cov report \
+"$LLVM_COV" report \
   -instr-profile=libc_full.profdata \
   "${TEST_BINS[0]}" "${OBJECT_FLAGS[@]}" \
   --show-branch-summary \
@@ -126,10 +183,11 @@ llvm-cov report \
 ```
 
 #### Option 2: Interactive HTML Dashboard
-Generates an interactive HTML dashboard containing sortable directory metrics and syntax-highlighted source views:
+Generates an interactive HTML dashboard containing sortable directory metrics
+and syntax-highlighted source views:
 
 ```bash
-llvm-cov show \
+"$LLVM_COV" show \
   -format=html \
   -output-dir=coverage_html \
   -instr-profile=libc_full.profdata \
@@ -146,11 +204,15 @@ xdg-open coverage_html/index.html
 
 ## Modified Condition / Decision Coverage (MC/DC)
 
-MC/DC evaluates boolean sub-conditions within compound logical expressions (such as `if (A && B)`). It verifies that each individual sub-condition evaluates to both true and false and independently affects the outcome of the enclosing decision.
+MC/DC evaluates boolean sub-conditions within compound logical expressions (such
+as `if (A && B)`). It verifies that each individual sub-condition evaluates to
+both true and false and independently affects the outcome of the enclosing
+decision.
 
 ### 1. CMake Configuration
 
-Configures CMake with `-fcoverage-mcdc` alongside profiling flags, enabling the compiler frontend to generate boolean condition bitmaps for compound decisions:
+Configures CMake with `-fcoverage-mcdc` alongside profiling flags, enabling the
+compiler frontend to generate boolean condition bitmaps for compound decisions:
 
 ```bash
 cmake -G Ninja -S runtimes -B build-cov-mcdc \
@@ -166,7 +228,8 @@ cmake -G Ninja -S runtimes -B build-cov-mcdc \
 
 ### 2. Build and Execute Tests
 
-Compiles and executes all unit tests with MC/DC instrumentation enabled, saving condition evaluation bitmasks into raw profile files upon completion:
+Compiles and executes all unit tests with MC/DC instrumentation enabled, saving
+condition evaluation bitmasks into raw profile files upon completion:
 
 ```bash
 export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
@@ -175,16 +238,18 @@ ninja -k 0 -C build-cov-mcdc libc-unit-tests
 
 ### 3. Merge Profiles
 
-Indexes and merges all MC/DC `.profraw` files into a unified `libc_mcdc.profdata` archive for report generation:
+Indexes and merges all MC/DC `.profraw` files into a unified
+`libc_mcdc.profdata` archive for report generation:
 
 ```bash
 find build-cov-mcdc/ -name "libc_cov_*.profraw" > profraw_list.txt
-llvm-profdata merge -sparse -f profraw_list.txt -o libc_mcdc.profdata
+"$LLVM_PROFDATA" merge -sparse -f profraw_list.txt -o libc_mcdc.profdata
 ```
 
 ### 4. Generate Reports
 
-Maps MC/DC bitmap records to source AST decisions and evaluates condition independence pairs:
+Maps MC/DC bitmap records to source AST decisions and evaluates condition
+independence pairs:
 
 ```bash
 TEST_BINS=($(find build-cov-mcdc -type f -executable -name "*__build__"))
@@ -197,10 +262,11 @@ done
 Reports can be generated in two formats depending on your needs:
 
 #### Option 1: Terminal Summary Report
-Displays the terminal coverage summary including MC/DC Condition and Missed Condition percentages:
+Displays the terminal coverage summary including MC/DC Condition and Missed
+Condition percentages:
 
 ```bash
-llvm-cov report \
+"$LLVM_COV" report \
   -instr-profile=libc_mcdc.profdata \
   "${TEST_BINS[0]}" "${OBJECT_FLAGS[@]}" \
   --show-branch-summary \
@@ -209,10 +275,11 @@ llvm-cov report \
 ```
 
 #### Option 2: Interactive HTML Dashboard
-Produces an HTML report with expandable MC/DC decision truth tables and test vector coverage breakdowns:
+Produces an HTML report with expandable MC/DC decision truth tables and test
+vector coverage breakdowns:
 
 ```bash
-llvm-cov show \
+"$LLVM_COV" show \
   -format=html \
   -output-dir=coverage_mcdc_html \
   -instr-profile=libc_mcdc.profdata \
@@ -231,15 +298,21 @@ xdg-open coverage_mcdc_html/index.html
 
 ## Running Coverage for a Single Test
 
-When developing or modifying a specific function, coverage can be collected for a single test without building and executing the entire test suite.
+When developing or modifying a specific function, coverage can be collected for
+a single test without building and executing the entire test suite.
 
-The commands below use `libc.test.src.ctype.isalpha_test` (which tests `libc/src/ctype/isalpha.cpp`) as an example. You can test any other entrypoint by substituting the target name and source file path:
-* **Target pattern:** `libc.test.<path_to_test>.<test_name>` (e.g. `libc.test.src.string.strlen_test`)
-* **Source path pattern:** `libc/<path_to_source>/<source_file>.cpp` (e.g. `libc/src/string/strlen.cpp`)
+The commands below use `libc.test.src.ctype.isalpha_test` (which tests
+`libc/src/ctype/isalpha.cpp`) as an example. You can test any other entrypoint
+by substituting the target name and source file path:
+* **Target pattern:** `libc.test.<path_to_test>.<test_name>`
+  (e.g. `libc.test.src.string.strlen_test`)
+* **Source path pattern:** `libc/<path_to_source>/<source_file>.cpp`
+  (e.g. `libc/src/string/strlen.cpp`)
 
 ### 1. Build and Execute the Targeted Test
 
-Compiles and runs only the specified test binary, immediately writing execution profile counters to disk upon completion:
+Compiles and runs only the specified test binary, immediately writing execution
+profile counters to disk upon completion:
 
 ```bash
 export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
@@ -253,30 +326,35 @@ ninja -C build-cov-mcdc libc.test.src.ctype.isalpha_test
 
 ### 2. Merge the Profile
 
-Merges the single test's raw profile into an indexed database for targeted inspection:
+Merges the single test's raw profile into an indexed database for targeted
+inspection (searching whichever build directory was compiled):
 
 ```bash
-find build-cov/ -name "libc_cov_*.profraw" > profraw_list.txt
-llvm-profdata merge -sparse -f profraw_list.txt -o libc_single.profdata
+find build-cov/ build-cov-mcdc/ \
+  -name "libc_cov_*.profraw" 2>/dev/null > profraw_list.txt
+"$LLVM_PROFDATA" merge -sparse -f profraw_list.txt -o libc_single.profdata
 ```
 
 ### 3. View the Terminal Report
 
-Reports can be viewed as an overall file summary or an annotated line-by-line breakdown:
+Reports can be viewed as an overall file summary or an annotated line-by-line
+breakdown:
 
 #### Option 1: Summary Table Report
 ```bash
-llvm-cov report \
+BIN_DIR="build-cov/libc/test/src/ctype"
+"$LLVM_COV" report \
   -instr-profile=libc_single.profdata \
-  ./build-cov/libc/test/src/ctype/libc.test.src.ctype.isalpha_test.__build__ \
+  "$BIN_DIR/libc.test.src.ctype.isalpha_test.__build__" \
   libc/src/ctype/isalpha.cpp
 ```
 
 #### Option 2: Line-by-Line & Truth Table View
 ```bash
-llvm-cov show \
+BIN_DIR="build-cov-mcdc/libc/test/src/ctype"
+"$LLVM_COV" show \
   -instr-profile=libc_single.profdata \
-  ./build-cov-mcdc/libc/test/src/ctype/libc.test.src.ctype.isalpha_test.__build__ \
+  "$BIN_DIR/libc.test.src.ctype.isalpha_test.__build__" \
   --show-branches=count \
   --show-mcdc \
   libc/src/ctype/isalpha.cpp
@@ -286,27 +364,45 @@ llvm-cov show \
 
 ## Interpreting Results
 
-For detailed documentation on the LLVM coverage reporting format, refer to the [official Clang Source-Based Code Coverage documentation](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#interpreting-reports).
+For detailed documentation on the LLVM coverage reporting format, refer to the
+[official Clang Source-Based Code Coverage documentation][clang-coverage].
+
+[clang-coverage]:
+  https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#interpreting-reports
 
 ### Coverage Metrics Overview
 
-* **Line Coverage:** Measures whether each physical line of executable source code was reached at least once during testing.
-* **Branch Coverage:** Measures whether each conditional branch evaluated to both its `True` and `False` paths. For example, if an `if (x > 0)` branch is taken 10 times but never skipped, branch coverage is 50% because the `False` path was never exercised.
-* **MC/DC Coverage:** Evaluates compound boolean expressions (such as `if (A && B)` or `if (A || B)`). It verifies that each individual condition was tested as both True and False, and demonstrated that it could independently change the overall outcome of the decision.
+* **Line Coverage:** Measures whether each physical line of executable source
+  code was reached at least once during testing.
+* **Branch Coverage:** Measures whether each conditional branch evaluated to
+  both its `True` and `False` paths. For example, if an `if (x > 0)` branch is
+  taken 10 times but never skipped, branch coverage is 50% because the `False`
+  path was never exercised.
+* **MC/DC Coverage:** Evaluates compound boolean expressions (such as
+  `if (A && B)` or `if (A || B)`). It verifies that each individual condition
+  was tested as both True and False, and demonstrated that it could
+  independently change the overall outcome of the decision.
 
 ### Interpreting Reports
 
-The summary table produced by `llvm-cov report` displays metrics across individual source files and overall totals:
+The summary table produced by `llvm-cov report` displays metrics across
+individual source files and overall totals:
 
-* **Regions / Missed Regions:** A region is a continuous segment of code (such as a function body or basic block). Missed regions indicate code blocks that were never executed.
-* **Functions / Missed Functions:** The total number of entrypoints or subroutines executed vs unexecuted.
+* **Regions / Missed Regions:** A region is a continuous segment of code (such
+  as a function body or basic block). Missed regions indicate code blocks that
+  were never executed.
+* **Functions / Missed Functions:** The total number of entrypoints or
+  subroutines executed vs unexecuted.
 * **Lines / Missed Lines:** Physical source lines executed vs unexecuted.
-* **Branches / Missed Branches:** The total count of decision directions (both True and False) evaluated.
-* **MC/DC Conditions / Missed Conditions:** The count of individual boolean sub-conditions that demonstrated independent decision control.
+* **Branches / Missed Branches:** The total count of decision directions (both
+  True and False) evaluated.
+* **MC/DC Conditions / Missed Conditions:** The count of individual boolean
+  sub-conditions that demonstrated independent decision control.
 
 ### Interpreting MC/DC Truth Tables
 
-When inspecting with `--show-mcdc`, `llvm-cov` displays an MC/DC analysis table beneath each compound decision. For instance, consider the following decision:
+When inspecting with `--show-mcdc`, `llvm-cov` displays an MC/DC analysis table
+beneath each compound decision. For instance, consider the following decision:
 
 ```text
    19|  if (c < 0 || c > 255)
@@ -326,9 +422,17 @@ When inspecting with `--show-mcdc`, `llvm-cov` displays an MC/DC analysis table
 
 * **Conditions:** **C1** represents `c < 0` and **C2** represents `c > 255`.
 * **Executed Vectors:**
-  * **Vector 1 (`F, F = F`):** Tested with a valid character (`c = 'a'`). Both C1 and C2 evaluated False, producing an overall False result.
-  * **Vector 2 (`T, - = T`):** Tested with a negative value (`c = -1`). C1 evaluated True, producing an overall True result. The hyphen (`-`) indicates C2 was short-circuited and not evaluated.
+  * **Vector 1 (`F, F = F`):** Tested with a valid character (`c = 'a'`). Both
+    C1 and C2 evaluated False, producing an overall False result.
+  * **Vector 2 (`T, - = T`):** Tested with a negative value (`c = -1`). C1
+    evaluated True, producing an overall True result. The hyphen (`-`)
+    indicates C2 was short-circuited and not evaluated.
 * **Condition Pairs:**
-  * **`C1-Pair: covered (1, 2)`:** Comparing Vector 1 and Vector 2 proves that changing C1 from False to True directly flipped the result from False to True. C1 is fully covered.
-  * **`C2-Pair: not covered`:** C2 was never tested in a state where it independently turned the result True while C1 was False.
-* **Reaching 100% Coverage:** Add a test with a value above 255 (`c = 256`). This executes Vector 3 (`F, T = T`), forming the independence pair `(1, 3)` for C2 and reaching 100% MC/DC coverage.
+  * **`C1-Pair: covered (1, 2)`:** Comparing Vector 1 and Vector 2 proves that
+    changing C1 from False to True directly flipped the result from False to
+    True. C1 is fully covered.
+  * **`C2-Pair: not covered`:** C2 was never tested in a state where it
+    independently turned the result True while C1 was False.
+* **Reaching 100% Coverage:** Add a test with a value above 255 (`c = 256`).
+  This executes Vector 3 (`F, T = T`), forming the independence pair `(1, 3)`
+  for C2 and reaching 100% MC/DC coverage.

>From 2d4b7a46e88607567af5a5fea113f7d6405dce6d Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Mon, 7 Sep 2026 17:25:53 +0000
Subject: [PATCH 11/18] [libc] Support code coverage profiling in full-build
 mode

Allow LIBC_ENABLE_COVERAGE when LLVM_LIBC_FULL_BUILD=ON. Link
libclang_rt.profile.a and libc.a to hermetic test targets with
-noprofilelib and -Wl,--allow-multiple-definition. Provide minimal
calloc and __errno_location symbols in HermeticTestUtils.cpp for
compiler-rt profiling support. Update developer documentation for
both overlay and full-build execution.

Assisted-by: Automated tooling, human reviewed.
---
 libc/CMakeLists.txt                        |  6 --
 libc/cmake/modules/LLVMLibCTestRules.cmake | 56 +++++++++++++-----
 libc/docs/dev/code_coverage.md             | 66 +++++++++++++++++-----
 libc/test/UnitTest/HermeticTestUtils.cpp   | 11 ++++
 4 files changed, 106 insertions(+), 33 deletions(-)

diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index b21741c161e44..fa5aed6cbe0d9 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -180,12 +180,6 @@ if(LIBC_ENABLE_COVERAGE)
     message(FATAL_ERROR
       "LIBC_ENABLE_COVERAGE is not supported on GPU or baremetal targets.")
   endif()
-  if(LLVM_LIBC_FULL_BUILD)
-    message(FATAL_ERROR
-      "LIBC_ENABLE_COVERAGE is only supported in overlay mode "
-      "(LLVM_LIBC_FULL_BUILD=OFF). Full build mode uses hermetic tests "
-      "which cannot link the compiler-rt profiling runtime.")
-  endif()
 endif()
 option(LLVM_LIBC_IMPLEMENTATION_DEFINED_TEST_BEHAVIOR "Build LLVM libc tests assuming our implementation-defined behavior" ON)
 option(LLVM_LIBC_ENABLE_LINTING "Enables linting of libc source files" OFF)
diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index 7c9d79a741945..f8d9a7294b4f6 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -896,35 +896,65 @@ function(add_libc_hermetic test_name)
       "-Wl,-mllvm,-nvptx-emit-init-fini-kernel"
       -march=${LIBC_GPU_TARGET_ARCHITECTURE} -nostdlib -static
       "--cuda-path=${LIBC_CUDA_ROOT}")
-  elseif(LIBC_CC_SUPPORTS_NOSTDLIBPP)
+  else()
+    set(stdlib_opt -nostdlib)
+    if(LIBC_CC_SUPPORTS_NOSTDLIBPP)
+      set(stdlib_opt -nostdlib++)
+    else()
+      # Older version of gcc does not support `nostdlib++` flag. We use
+      # `nostdlib` and link against libgcc_s, which cannot be linked statically.
+      list(APPEND compiler_runtime ${LIBGCC_S_LOCATION})
+    endif()
+
     set(link_options
       -nolibc
       -nostartfiles
-      -nostdlib++
+      ${stdlib_opt}
       -static
       ${LIBC_LINK_OPTIONS_DEFAULT}
       ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
     )
+    if(LIBC_ENABLE_COVERAGE)
+      list(APPEND link_options
+        -noprofilelib
+        -Wl,--allow-multiple-definition
+        -u__llvm_profile_runtime
+      )
+    endif()
     target_link_options(${fq_build_target_name} PRIVATE ${link_options})
-  else()
-    # Older version of gcc does not support `nostdlib++` flag.  We use
-    # `nostdlib` and link against libgcc_s, which cannot be linked statically.
-    set(link_options
-      -nolibc
-      -nostartfiles
-      -nostdlib
-      ${LIBC_LINK_OPTIONS_DEFAULT}
-      ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
+  endif()
+
+  set(coverage_link_libs "")
+  if(LIBC_ENABLE_COVERAGE)
+    if(NOT LIBC_CLANG_PROFILE_LIB)
+      execute_process(
+        COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libclang_rt.profile.a
+        OUTPUT_STRIP_TRAILING_WHITESPACE
+        OUTPUT_VARIABLE LIBC_CLANG_PROFILE_LIB
+      )
+      if(NOT EXISTS "${LIBC_CLANG_PROFILE_LIB}")
+        execute_process(
+          COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libclang_rt.profile-${LIBC_TARGET_ARCHITECTURE}.a
+          OUTPUT_STRIP_TRAILING_WHITESPACE
+          OUTPUT_VARIABLE LIBC_CLANG_PROFILE_LIB
+        )
+      endif()
+      set(LIBC_CLANG_PROFILE_LIB "${LIBC_CLANG_PROFILE_LIB}" CACHE INTERNAL
+        "Path to compiler-rt profile library")
+    endif()
+    set(coverage_link_libs
+      "${LIBC_CLANG_PROFILE_LIB}"
+      libc
     )
-    target_link_options(${fq_build_target_name} PRIVATE ${link_options})
-    list(APPEND compiler_runtime ${LIBGCC_S_LOCATION})
   endif()
+
   target_link_libraries(
     ${fq_build_target_name}
     PRIVATE
       libc.startup.${LIBC_TARGET_OS}.crt1
       ${HERMETIC_TEST_LINK_LIBRARIES}
       ${fq_target_name}.__libc__
+      ${coverage_link_libs}
       ${compiler_runtime}
   )
   add_dependencies(${fq_build_target_name} ${fq_deps_list})
diff --git a/libc/docs/dev/code_coverage.md b/libc/docs/dev/code_coverage.md
index ab6a8288f1fdc..3660382a2e2a7 100644
--- a/libc/docs/dev/code_coverage.md
+++ b/libc/docs/dev/code_coverage.md
@@ -64,7 +64,9 @@ Generating coverage reports requires Clang, LLVM profile tools, CMake, and
 Ninja:
 
 * **Compiler:** Clang 18 or later (Clang 21 or later is required for MC/DC
-  instrumentation).
+  instrumentation). In full-build mode, `libclang_rt.profile.a` must not depend
+  on glibc fortification symbols; a compiler-rt built from the LLVM monorepo is
+  recommended.
 * **LLVM Utilities:** Matching major versions of `llvm-profdata` and `llvm-cov`.
 * **Build System:** CMake 3.28+ and Ninja.
 
@@ -113,10 +115,11 @@ outcomes across all LLVM-libc entrypoints and internal support utilities.
 
 ### 1. CMake Configuration
 
-Configures CMake to build LLVM-libc in overlay mode, setting
-`-DLIBC_ENABLE_COVERAGE=ON` to pass Clang's continuous profiling and coverage
-mapping flags to the compiler:
+Configures CMake to build LLVM-libc with code coverage enabled. Both overlay
+mode (`LLVM_LIBC_FULL_BUILD=OFF`) and full-build mode
+(`LLVM_LIBC_FULL_BUILD=ON`) are supported:
 
+#### Option A: Overlay Mode (Default)
 ```bash
 cmake -G Ninja -S runtimes -B build-cov \
   -DCMAKE_C_COMPILER=clang \
@@ -127,22 +130,37 @@ cmake -G Ninja -S runtimes -B build-cov \
   -DLIBC_ENABLE_COVERAGE=ON
 ```
 
-### 2. Build and Execute All Unit Tests
+#### Option B: Full-Build Mode
+```bash
+cmake -G Ninja -S runtimes -B build-cov \
+  -DCMAKE_C_COMPILER=clang \
+  -DCMAKE_CXX_COMPILER=clang++ \
+  -DCMAKE_BUILD_TYPE=Debug \
+  -DLLVM_ENABLE_RUNTIMES="libc" \
+  -DLLVM_LIBC_FULL_BUILD=ON \
+  -DLIBC_ENABLE_COVERAGE=ON
+```
+
+### 2. Build and Execute Tests
 
-Compiles all libc unit test executables and executes them in parallel. As each
-test executes, its counters are mapped directly to disk via the OS page cache:
+Compiles and executes test executables in parallel. In overlay mode, execute
+`libc-unit-tests`. In full-build mode, execute `libc-hermetic-tests`:
 
 ```bash
 export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
+
+# In overlay mode (LLVM_LIBC_FULL_BUILD=OFF)
 ninja -k 0 -C build-cov libc-unit-tests
+
+# In full-build mode (LLVM_LIBC_FULL_BUILD=ON)
+ninja -k 0 -C build-cov libc-hermetic-tests
 ```
 
 :::{note}
-In LLVM-libc, `libc-unit-tests` builds and executes tests in a single
-invocation. The `-k 0` flag ensures Ninja continues executing all remaining
-test targets even if an individual edge-case test encounters an error. To only
-compile test binaries without immediately executing them, use
-`ninja -C build-cov libc-unit-tests-build`.
+The `-k 0` flag ensures Ninja continues executing all remaining test targets
+even if an individual edge-case test encounters an error. To only compile test
+binaries without immediately executing them, use
+`ninja -C build-cov libc-unit-tests-build` (or `libc-hermetic-tests-build`).
 :::
 
 ### 3. Merge Profile Counters
@@ -214,6 +232,7 @@ decision.
 Configures CMake with `-fcoverage-mcdc` alongside profiling flags, enabling the
 compiler frontend to generate boolean condition bitmaps for compound decisions:
 
+#### Option A: Overlay Mode (Default)
 ```bash
 cmake -G Ninja -S runtimes -B build-cov-mcdc \
   -DCMAKE_C_COMPILER=clang \
@@ -226,14 +245,33 @@ cmake -G Ninja -S runtimes -B build-cov-mcdc \
   -DCMAKE_CXX_FLAGS="-fcoverage-mcdc"
 ```
 
+#### Option B: Full-Build Mode
+```bash
+cmake -G Ninja -S runtimes -B build-cov-mcdc \
+  -DCMAKE_C_COMPILER=clang \
+  -DCMAKE_CXX_COMPILER=clang++ \
+  -DCMAKE_BUILD_TYPE=Debug \
+  -DLLVM_ENABLE_RUNTIMES="libc" \
+  -DLLVM_LIBC_FULL_BUILD=ON \
+  -DLIBC_ENABLE_COVERAGE=ON \
+  -DCMAKE_C_FLAGS="-fcoverage-mcdc" \
+  -DCMAKE_CXX_FLAGS="-fcoverage-mcdc"
+```
+
 ### 2. Build and Execute Tests
 
-Compiles and executes all unit tests with MC/DC instrumentation enabled, saving
-condition evaluation bitmasks into raw profile files upon completion:
+Compiles and executes test executables in parallel with MC/DC instrumentation
+enabled. In overlay mode, execute `libc-unit-tests`. In full-build mode, execute
+`libc-hermetic-tests`:
 
 ```bash
 export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
+
+# In overlay mode (LLVM_LIBC_FULL_BUILD=OFF)
 ninja -k 0 -C build-cov-mcdc libc-unit-tests
+
+# In full-build mode (LLVM_LIBC_FULL_BUILD=ON)
+ninja -k 0 -C build-cov-mcdc libc-hermetic-tests
 ```
 
 ### 3. Merge Profiles
diff --git a/libc/test/UnitTest/HermeticTestUtils.cpp b/libc/test/UnitTest/HermeticTestUtils.cpp
index a9bbf13a1f190..80444b6c6c393 100644
--- a/libc/test/UnitTest/HermeticTestUtils.cpp
+++ b/libc/test/UnitTest/HermeticTestUtils.cpp
@@ -84,8 +84,19 @@ void *aligned_alloc(size_t align, size_t s) {
 
 void *malloc(size_t s) { return aligned_alloc(ALIGNMENT, s); }
 
+void *calloc(size_t num, size_t size) {
+  size_t total = num * size;
+  void *mem = malloc(total);
+  if (mem)
+    memset(mem, 0, total);
+  return mem;
+}
+
 void free(void *) {}
 
+int *__llvm_libc_errno() noexcept;
+int *__errno_location() { return __llvm_libc_errno(); }
+
 void *realloc(void *mem, size_t s) {
   if (mem == nullptr)
     return malloc(s);

>From 987c90a31e2995d44ba4b1427d0328e7b70a64c0 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Mon, 7 Sep 2026 18:34:33 +0000
Subject: [PATCH 12/18] [libc][docs] Update code coverage guide with full-build
 and toolchain instructions

- Document building Clang with compiler-rt for unfortified profile support.
- Add -DLLVM_USE_LINKER=lld to full-build configuration snippets.
- Add instructions for querying single-file coverage from aggregated profiles.
- Maintain strict 80-column line width limit.

Assisted-by: Automated tooling, human reviewed.
---
 libc/docs/dev/code_coverage.md | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/libc/docs/dev/code_coverage.md b/libc/docs/dev/code_coverage.md
index 3660382a2e2a7..47ab4915c6cdd 100644
--- a/libc/docs/dev/code_coverage.md
+++ b/libc/docs/dev/code_coverage.md
@@ -68,6 +68,7 @@ Ninja:
   on glibc fortification symbols; a compiler-rt built from the LLVM monorepo is
   recommended.
 * **LLVM Utilities:** Matching major versions of `llvm-profdata` and `llvm-cov`.
+* **Linker:** `lld` is recommended when configuring full-build mode.
 * **Build System:** CMake 3.28+ and Ninja.
 
 #### Toolchain Discovery
@@ -92,6 +93,24 @@ export LLVM_COV=llvm-cov
 
 Subsequent merge and report commands reference `$LLVM_PROFDATA` and `$LLVM_COV`.
 
+#### Building Clang with Profiling Support (Full-Build Mode)
+
+In full-build mode, hermetic tests link the compiler runtime profile library
+(`libclang_rt.profile.a`). If your system Clang was built against glibc with
+source fortification enabled, its profile library may contain unsatisfied
+dependencies (such as `__vfprintf_chk`). Building Clang and compiler-rt from
+the LLVM monorepo provides a clean profiling runtime:
+
+```bash
+cmake -G Ninja -S llvm -B build-clang \
+  -DCMAKE_BUILD_TYPE=Release \
+  -DCMAKE_INSTALL_PREFIX="$HOME/clang" \
+  -DLLVM_ENABLE_PROJECTS="clang;lld" \
+  -DLLVM_ENABLE_RUNTIMES="compiler-rt" \
+  -DLLVM_USE_LINKER=lld
+ninja -C build-clang install
+```
+
 ---
 
 ## Cleaning Profile Counters
@@ -135,6 +154,7 @@ cmake -G Ninja -S runtimes -B build-cov \
 cmake -G Ninja -S runtimes -B build-cov \
   -DCMAKE_C_COMPILER=clang \
   -DCMAKE_CXX_COMPILER=clang++ \
+  -DLLVM_USE_LINKER=lld \
   -DCMAKE_BUILD_TYPE=Debug \
   -DLLVM_ENABLE_RUNTIMES="libc" \
   -DLLVM_LIBC_FULL_BUILD=ON \
@@ -200,6 +220,15 @@ percentages for each file:
   -ignore-filename-regex=".*(test|utils).*"
 ```
 
+To restrict the terminal report to a specific source file:
+
+```bash
+"$LLVM_COV" report \
+  -instr-profile=libc_full.profdata \
+  "${TEST_BINS[0]}" "${OBJECT_FLAGS[@]}" \
+  libc/src/string/strlen.cpp
+```
+
 #### Option 2: Interactive HTML Dashboard
 Generates an interactive HTML dashboard containing sortable directory metrics
 and syntax-highlighted source views:
@@ -250,6 +279,7 @@ cmake -G Ninja -S runtimes -B build-cov-mcdc \
 cmake -G Ninja -S runtimes -B build-cov-mcdc \
   -DCMAKE_C_COMPILER=clang \
   -DCMAKE_CXX_COMPILER=clang++ \
+  -DLLVM_USE_LINKER=lld \
   -DCMAKE_BUILD_TYPE=Debug \
   -DLLVM_ENABLE_RUNTIMES="libc" \
   -DLLVM_LIBC_FULL_BUILD=ON \

>From 0b6eebb76589c1dc24d89743c3630fbb9ce893a7 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Mon, 7 Sep 2026 19:39:30 +0000
Subject: [PATCH 13/18] [libc] Address review feedback on full-build code
 coverage

- Match reviewer patch in LLVMLibCTestRules.cmake, preserving upstream branching.
- Add architecture guards for AMDGPU and NVPTX.
- Explicitly add libc to coverage dependencies.
- Update code coverage guide for Clang 24 / HEAD and document distro compiler-rt fortification issues.

Assisted-by: Automated tooling, human reviewed.
---
 libc/cmake/modules/LLVMLibCTestRules.cmake | 44 ++++++++++++-------
 libc/docs/dev/code_coverage.md             | 51 ++++++++++++----------
 2 files changed, 57 insertions(+), 38 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index f8d9a7294b4f6..5ab51d08287ca 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -896,25 +896,16 @@ function(add_libc_hermetic test_name)
       "-Wl,-mllvm,-nvptx-emit-init-fini-kernel"
       -march=${LIBC_GPU_TARGET_ARCHITECTURE} -nostdlib -static
       "--cuda-path=${LIBC_CUDA_ROOT}")
-  else()
-    set(stdlib_opt -nostdlib)
-    if(LIBC_CC_SUPPORTS_NOSTDLIBPP)
-      set(stdlib_opt -nostdlib++)
-    else()
-      # Older version of gcc does not support `nostdlib++` flag. We use
-      # `nostdlib` and link against libgcc_s, which cannot be linked statically.
-      list(APPEND compiler_runtime ${LIBGCC_S_LOCATION})
-    endif()
-
+  elseif(LIBC_CC_SUPPORTS_NOSTDLIBPP)
     set(link_options
       -nolibc
       -nostartfiles
-      ${stdlib_opt}
+      -nostdlib++
       -static
       ${LIBC_LINK_OPTIONS_DEFAULT}
       ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
     )
-    if(LIBC_ENABLE_COVERAGE)
+    if(LIBC_ENABLE_COVERAGE AND NOT LIBC_TARGET_ARCHITECTURE_IS_AMDGPU AND NOT LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
       list(APPEND link_options
         -noprofilelib
         -Wl,--allow-multiple-definition
@@ -922,10 +913,29 @@ function(add_libc_hermetic test_name)
       )
     endif()
     target_link_options(${fq_build_target_name} PRIVATE ${link_options})
+  else()
+    # Older version of gcc does not support `nostdlib++` flag.  We use
+    # `nostdlib` and link against libgcc_s, which cannot be linked statically.
+    set(link_options
+      -nolibc
+      -nostartfiles
+      -nostdlib
+      ${LIBC_LINK_OPTIONS_DEFAULT}
+      ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
+    )
+    if(LIBC_ENABLE_COVERAGE AND NOT LIBC_TARGET_ARCHITECTURE_IS_AMDGPU AND NOT LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
+      list(APPEND link_options
+        -noprofilelib
+        -Wl,--allow-multiple-definition
+        -u__llvm_profile_runtime
+      )
+    endif()
+    target_link_options(${fq_build_target_name} PRIVATE ${link_options})
+    list(APPEND compiler_runtime ${LIBGCC_S_LOCATION})
   endif()
 
   set(coverage_link_libs "")
-  if(LIBC_ENABLE_COVERAGE)
+  if(LIBC_ENABLE_COVERAGE AND NOT LIBC_TARGET_ARCHITECTURE_IS_AMDGPU AND NOT LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
     if(NOT LIBC_CLANG_PROFILE_LIB)
       execute_process(
         COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libclang_rt.profile.a
@@ -939,8 +949,6 @@ function(add_libc_hermetic test_name)
           OUTPUT_VARIABLE LIBC_CLANG_PROFILE_LIB
         )
       endif()
-      set(LIBC_CLANG_PROFILE_LIB "${LIBC_CLANG_PROFILE_LIB}" CACHE INTERNAL
-        "Path to compiler-rt profile library")
     endif()
     set(coverage_link_libs
       "${LIBC_CLANG_PROFILE_LIB}"
@@ -957,7 +965,11 @@ function(add_libc_hermetic test_name)
       ${coverage_link_libs}
       ${compiler_runtime}
   )
-  add_dependencies(${fq_build_target_name} ${fq_deps_list})
+  set(coverage_deps "")
+  if(LIBC_ENABLE_COVERAGE AND NOT LIBC_TARGET_ARCHITECTURE_IS_AMDGPU AND NOT LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
+    set(coverage_deps libc)
+  endif()
+  add_dependencies(${fq_build_target_name} ${fq_deps_list} ${coverage_deps})
 
   if(NOT HERMETIC_TEST_NO_RUN_POSTBUILD)
     if(LIBC_TEST_CMD)
diff --git a/libc/docs/dev/code_coverage.md b/libc/docs/dev/code_coverage.md
index 47ab4915c6cdd..2adc9a4fa6d81 100644
--- a/libc/docs/dev/code_coverage.md
+++ b/libc/docs/dev/code_coverage.md
@@ -53,8 +53,8 @@ executed across parent and child processes directly to the profile file.
 Setting `-DLIBC_ENABLE_COVERAGE=ON` in the CMake configuration passes
 `-fprofile-instr-generate=libc_cov_%c%p.profraw`, `-fcoverage-mapping`, and
 `-fprofile-continuous` across all LLVM-libc compilation units and test link
-steps. This ensures uniform instrumentation across entrypoints and unit test
-harnesses.
+steps. This ensures uniform instrumentation across entrypoints, unit tests, and
+hermetic test harnesses.
 
 ## Running Code Coverage Locally
 
@@ -63,18 +63,18 @@ harnesses.
 Generating coverage reports requires Clang, LLVM profile tools, CMake, and
 Ninja:
 
-* **Compiler:** Clang 18 or later (Clang 21 or later is required for MC/DC
-  instrumentation). In full-build mode, `libclang_rt.profile.a` must not depend
-  on glibc fortification symbols; a compiler-rt built from the LLVM monorepo is
-  recommended.
+* **Compiler:** Clang 18 or later (Clang 21+ for MC/DC, Clang 24 / HEAD
+  recommended for full-build mode). In full-build mode, compiler-rt must match
+  the compiler version and cannot rely on distro-built libraries with glibc
+  source fortification.
 * **LLVM Utilities:** Matching major versions of `llvm-profdata` and `llvm-cov`.
 * **Linker:** `lld` is recommended when configuring full-build mode.
 * **Build System:** CMake 3.28+ and Ninja.
 
 #### Toolchain Discovery
 
-If your Linux distribution packages version-suffixed binaries (e.g. `clang-21`,
-`llvm-profdata-21`), discover and export them:
+If your Linux distribution packages version-suffixed binaries (e.g. `clang-24`,
+`llvm-profdata-24`), discover and export them:
 
 ```bash
 CLANG_MAJOR=$(clang --version | sed -n 's/.*version \([0-9]*\).*/\1/p')
@@ -95,17 +95,18 @@ Subsequent merge and report commands reference `$LLVM_PROFDATA` and `$LLVM_COV`.
 
 #### Building Clang with Profiling Support (Full-Build Mode)
 
-In full-build mode, hermetic tests link the compiler runtime profile library
-(`libclang_rt.profile.a`). If your system Clang was built against glibc with
-source fortification enabled, its profile library may contain unsatisfied
-dependencies (such as `__vfprintf_chk`). Building Clang and compiler-rt from
-the LLVM monorepo provides a clean profiling runtime:
+Full-build hermetic tests link `libclang_rt.profile.a`. Distro-built compiler-rt
+packages on distributions like Debian or Ubuntu are built with glibc source
+fortification enabled, which LLVM-libc does not support because it introduces
+unresolved symbols such as `__vfprintf_chk`. Furthermore, compiler-rt must
+match the exact version of the compiler used to build. The recommended approach
+is building Clang, lld, and compiler-rt from HEAD:
 
 ```bash
 cmake -G Ninja -S llvm -B build-clang \
   -DCMAKE_BUILD_TYPE=Release \
   -DCMAKE_INSTALL_PREFIX="$HOME/clang" \
-  -DLLVM_ENABLE_PROJECTS="clang;lld" \
+  -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld" \
   -DLLVM_ENABLE_RUNTIMES="compiler-rt" \
   -DLLVM_USE_LINKER=lld
 ninja -C build-clang install
@@ -163,17 +164,20 @@ cmake -G Ninja -S runtimes -B build-cov \
 
 ### 2. Build and Execute Tests
 
-Compiles and executes test executables in parallel. In overlay mode, execute
-`libc-unit-tests`. In full-build mode, execute `libc-hermetic-tests`:
+Compiles and executes test executables in parallel. Run `libc-unit-tests` for
+unit tests, `libc-hermetic-tests` for hermetic tests, or both:
 
 ```bash
 export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
 
-# In overlay mode (LLVM_LIBC_FULL_BUILD=OFF)
+# Run unit tests
 ninja -k 0 -C build-cov libc-unit-tests
 
-# In full-build mode (LLVM_LIBC_FULL_BUILD=ON)
+# Run hermetic tests (full-build mode)
 ninja -k 0 -C build-cov libc-hermetic-tests
+
+# Run both unit and hermetic tests
+ninja -k 0 -C build-cov libc-unit-tests libc-hermetic-tests
 ```
 
 :::{note}
@@ -291,17 +295,20 @@ cmake -G Ninja -S runtimes -B build-cov-mcdc \
 ### 2. Build and Execute Tests
 
 Compiles and executes test executables in parallel with MC/DC instrumentation
-enabled. In overlay mode, execute `libc-unit-tests`. In full-build mode, execute
-`libc-hermetic-tests`:
+enabled. Run `libc-unit-tests` for unit tests, `libc-hermetic-tests` for
+hermetic tests, or both:
 
 ```bash
 export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
 
-# In overlay mode (LLVM_LIBC_FULL_BUILD=OFF)
+# Run unit tests
 ninja -k 0 -C build-cov-mcdc libc-unit-tests
 
-# In full-build mode (LLVM_LIBC_FULL_BUILD=ON)
+# Run hermetic tests (full-build mode)
 ninja -k 0 -C build-cov-mcdc libc-hermetic-tests
+
+# Run both unit and hermetic tests
+ninja -k 0 -C build-cov-mcdc libc-unit-tests libc-hermetic-tests
 ```
 
 ### 3. Merge Profiles

>From e14b53cb9f7b9b00fc99a888d38677fbabf2e52a Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Tue, 8 Sep 2026 08:15:46 +0000
Subject: [PATCH 14/18] [libc] Address review feedback for full-build code
 coverage

When running hermetic tests with coverage enabled, compiler-rt's profile
runtime (libclang_rt.profile.a) needs a few symbols that aren't available
in freestanding mode (-nolibc):

- calloc: compiler-rt allocates buffers for profile tracking and
  continuous mode via calloc. We add a weak calloc stub in
  HermeticTestUtils backed by the existing static buffer, with an
  overflow check using __builtin_mul_overflow.
- __errno_location / __llvm_libc_errno: compiler-rt file I/O checks errno
  on failure, which resolves to __errno_location on Linux. We stub this in
  HermeticTestUtils to forward directly to libc's internal
  __llvm_libc_errno().

Other review updates:
- Mark memory and allocator stubs in HermeticTestUtils as [[gnu::weak]] so
  individual test entrypoints take precedence without duplicate symbol errors.
- Query and cache libclang_rt.profile.a once in the top-level libc
  CMakeLists.txt instead of re-running the compiler for each test.
- Link libc.a with -Wl,--allow-multiple-definition in hermetic mode to
  satisfy compiler-rt runtime calls.
- Add LIBC_ENABLE_COVERAGE_MCDC option and update documentation for
  Clang 24+.
- Fix line wrapping across the documentation and CMake files to stay
  within 80 columns.

Assisted-by: Automated tooling, human reviewed.
---
 libc/CMakeLists.txt                        | 36 ++++++++++-
 libc/cmake/modules/LLVMLibCTestRules.cmake | 22 ++-----
 libc/docs/dev/code_coverage.md             | 73 +++++++++++-----------
 libc/test/UnitTest/CMakeLists.txt          |  2 +
 libc/test/UnitTest/HermeticTestUtils.cpp   | 69 ++++++++++++++------
 5 files changed, 129 insertions(+), 73 deletions(-)

diff --git a/libc/CMakeLists.txt b/libc/CMakeLists.txt
index fa5aed6cbe0d9..d7769bb7d0667 100644
--- a/libc/CMakeLists.txt
+++ b/libc/CMakeLists.txt
@@ -93,15 +93,20 @@ set(LIBC_TEST_LINK_OPTIONS_DEFAULT "" CACHE STRING "Common link options for all
 
 # Enable source-based code coverage using Clang's continuous profiling mode.
 option(LIBC_ENABLE_COVERAGE "Build libc with coverage instrumentation" OFF)
+option(LIBC_ENABLE_COVERAGE_MCDC
+       "Enable Modified Condition / Decision Coverage (MC/DC)" OFF)
 if(LIBC_ENABLE_COVERAGE)
   if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
     list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT
-         "-fprofile-instr-generate=libc_cov_%c%p.profraw"
+         "-fprofile-instr-generate=libc_cov_%p.profraw"
          "-fcoverage-mapping"
          "-fprofile-continuous")
     list(APPEND LIBC_TEST_LINK_OPTIONS_DEFAULT
-         "-fprofile-instr-generate=libc_cov_%c%p.profraw"
+         "-fprofile-instr-generate=libc_cov_%p.profraw"
          "-fprofile-continuous")
+    if(LIBC_ENABLE_COVERAGE_MCDC)
+      list(APPEND LIBC_COMPILE_OPTIONS_DEFAULT "-fcoverage-mcdc")
+    endif()
   else()
     message(FATAL_ERROR
             "Coverage instrumentation is currently only supported with Clang")
@@ -180,6 +185,33 @@ if(LIBC_ENABLE_COVERAGE)
     message(FATAL_ERROR
       "LIBC_ENABLE_COVERAGE is not supported on GPU or baremetal targets.")
   endif()
+  if(LLVM_LIBC_FULL_BUILD)
+    set(target_arg "")
+    if(LIBC_TARGET_TRIPLE)
+      set(target_arg "--target=${LIBC_TARGET_TRIPLE}")
+    endif()
+    execute_process(
+      COMMAND ${CMAKE_CXX_COMPILER} ${target_arg}
+              --print-file-name=libclang_rt.profile.a
+      OUTPUT_STRIP_TRAILING_WHITESPACE
+      OUTPUT_VARIABLE libc_profile_lib
+    )
+    if(NOT EXISTS "${libc_profile_lib}")
+      set(profile_arch_lib
+          "libclang_rt.profile-${LIBC_TARGET_ARCHITECTURE}.a")
+      execute_process(
+        COMMAND ${CMAKE_CXX_COMPILER} ${target_arg}
+                --print-file-name=${profile_arch_lib}
+        OUTPUT_STRIP_TRAILING_WHITESPACE
+        OUTPUT_VARIABLE libc_profile_lib
+      )
+    endif()
+    if(NOT EXISTS "${libc_profile_lib}")
+      message(FATAL_ERROR "Coverage enabled but profile runtime not found.")
+    endif()
+    set(LIBC_CLANG_PROFILE_LIB "${libc_profile_lib}"
+        CACHE INTERNAL "Clang profile runtime")
+  endif()
 endif()
 option(LLVM_LIBC_IMPLEMENTATION_DEFINED_TEST_BEHAVIOR "Build LLVM libc tests assuming our implementation-defined behavior" ON)
 option(LLVM_LIBC_ENABLE_LINTING "Enables linting of libc source files" OFF)
diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index 5ab51d08287ca..80971d7043168 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -905,7 +905,7 @@ function(add_libc_hermetic test_name)
       ${LIBC_LINK_OPTIONS_DEFAULT}
       ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
     )
-    if(LIBC_ENABLE_COVERAGE AND NOT LIBC_TARGET_ARCHITECTURE_IS_AMDGPU AND NOT LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
+    if(LIBC_ENABLE_COVERAGE)
       list(APPEND link_options
         -noprofilelib
         -Wl,--allow-multiple-definition
@@ -923,7 +923,7 @@ function(add_libc_hermetic test_name)
       ${LIBC_LINK_OPTIONS_DEFAULT}
       ${LIBC_TEST_LINK_OPTIONS_DEFAULT}
     )
-    if(LIBC_ENABLE_COVERAGE AND NOT LIBC_TARGET_ARCHITECTURE_IS_AMDGPU AND NOT LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
+    if(LIBC_ENABLE_COVERAGE)
       list(APPEND link_options
         -noprofilelib
         -Wl,--allow-multiple-definition
@@ -935,21 +935,7 @@ function(add_libc_hermetic test_name)
   endif()
 
   set(coverage_link_libs "")
-  if(LIBC_ENABLE_COVERAGE AND NOT LIBC_TARGET_ARCHITECTURE_IS_AMDGPU AND NOT LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
-    if(NOT LIBC_CLANG_PROFILE_LIB)
-      execute_process(
-        COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libclang_rt.profile.a
-        OUTPUT_STRIP_TRAILING_WHITESPACE
-        OUTPUT_VARIABLE LIBC_CLANG_PROFILE_LIB
-      )
-      if(NOT EXISTS "${LIBC_CLANG_PROFILE_LIB}")
-        execute_process(
-          COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libclang_rt.profile-${LIBC_TARGET_ARCHITECTURE}.a
-          OUTPUT_STRIP_TRAILING_WHITESPACE
-          OUTPUT_VARIABLE LIBC_CLANG_PROFILE_LIB
-        )
-      endif()
-    endif()
+  if(LIBC_ENABLE_COVERAGE)
     set(coverage_link_libs
       "${LIBC_CLANG_PROFILE_LIB}"
       libc
@@ -966,7 +952,7 @@ function(add_libc_hermetic test_name)
       ${compiler_runtime}
   )
   set(coverage_deps "")
-  if(LIBC_ENABLE_COVERAGE AND NOT LIBC_TARGET_ARCHITECTURE_IS_AMDGPU AND NOT LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
+  if(LIBC_ENABLE_COVERAGE)
     set(coverage_deps libc)
   endif()
   add_dependencies(${fq_build_target_name} ${fq_deps_list} ${coverage_deps})
diff --git a/libc/docs/dev/code_coverage.md b/libc/docs/dev/code_coverage.md
index 2adc9a4fa6d81..4ee4dbdd2c6af 100644
--- a/libc/docs/dev/code_coverage.md
+++ b/libc/docs/dev/code_coverage.md
@@ -51,10 +51,12 @@ executed across parent and child processes directly to the profile file.
 
 ### Build System Integration
 Setting `-DLIBC_ENABLE_COVERAGE=ON` in the CMake configuration passes
-`-fprofile-instr-generate=libc_cov_%c%p.profraw`, `-fcoverage-mapping`, and
+`-fprofile-instr-generate=libc_cov_%p.profraw`, `-fcoverage-mapping`, and
 `-fprofile-continuous` across all LLVM-libc compilation units and test link
-steps. This ensures uniform instrumentation across entrypoints, unit tests, and
-hermetic test harnesses.
+steps. When `-fprofile-continuous` is enabled, Clang automatically prepends
+`%c` to the profile file template, avoiding duplicate specifier warnings at
+runtime. Setting `-DLIBC_ENABLE_COVERAGE_MCDC=ON` additionally enables
+`-fcoverage-mcdc`.
 
 ## Running Code Coverage Locally
 
@@ -63,10 +65,9 @@ hermetic test harnesses.
 Generating coverage reports requires Clang, LLVM profile tools, CMake, and
 Ninja:
 
-* **Compiler:** Clang 18 or later (Clang 21+ for MC/DC, Clang 24 / HEAD
-  recommended for full-build mode). In full-build mode, compiler-rt must match
-  the compiler version and cannot rely on distro-built libraries with glibc
-  source fortification.
+* **Compiler:** Clang 24+ (or Clang built from HEAD). In full-build mode,
+  compiler-rt must match the compiler version and cannot rely on distro-built
+  libraries with glibc source fortification.
 * **LLVM Utilities:** Matching major versions of `llvm-profdata` and `llvm-cov`.
 * **Linker:** `lld` is recommended when configuring full-build mode.
 * **Build System:** CMake 3.28+ and Ninja.
@@ -164,20 +165,21 @@ cmake -G Ninja -S runtimes -B build-cov \
 
 ### 2. Build and Execute Tests
 
-Compiles and executes test executables in parallel. Run `libc-unit-tests` for
-unit tests, `libc-hermetic-tests` for hermetic tests, or both:
+Compiles and executes test executables in parallel. Select the test target
+corresponding to your build mode:
+
+* **Overlay Mode (`LLVM_LIBC_FULL_BUILD=OFF`):** Execute `libc-unit-tests`.
+* **Full-Build Mode (`LLVM_LIBC_FULL_BUILD=ON`):** Execute
+  `libc-hermetic-tests`.
 
 ```bash
-export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
+export LLVM_PROFILE_FILE="libc_cov_%p.profraw"
 
-# Run unit tests
+# Option A: Run unit tests (overlay mode)
 ninja -k 0 -C build-cov libc-unit-tests
 
-# Run hermetic tests (full-build mode)
+# Option B: Run hermetic tests (full-build mode)
 ninja -k 0 -C build-cov libc-hermetic-tests
-
-# Run both unit and hermetic tests
-ninja -k 0 -C build-cov libc-unit-tests libc-hermetic-tests
 ```
 
 :::{note}
@@ -262,8 +264,9 @@ decision.
 
 ### 1. CMake Configuration
 
-Configures CMake with `-fcoverage-mcdc` alongside profiling flags, enabling the
-compiler frontend to generate boolean condition bitmaps for compound decisions:
+Configures CMake with `-DLIBC_ENABLE_COVERAGE_MCDC=ON` alongside profiling
+flags, enabling the compiler frontend to generate boolean condition bitmaps for
+compound decisions:
 
 #### Option A: Overlay Mode (Default)
 ```bash
@@ -274,8 +277,7 @@ cmake -G Ninja -S runtimes -B build-cov-mcdc \
   -DLLVM_ENABLE_RUNTIMES="libc" \
   -DLLVM_LIBC_FULL_BUILD=OFF \
   -DLIBC_ENABLE_COVERAGE=ON \
-  -DCMAKE_C_FLAGS="-fcoverage-mcdc" \
-  -DCMAKE_CXX_FLAGS="-fcoverage-mcdc"
+  -DLIBC_ENABLE_COVERAGE_MCDC=ON
 ```
 
 #### Option B: Full-Build Mode
@@ -288,27 +290,26 @@ cmake -G Ninja -S runtimes -B build-cov-mcdc \
   -DLLVM_ENABLE_RUNTIMES="libc" \
   -DLLVM_LIBC_FULL_BUILD=ON \
   -DLIBC_ENABLE_COVERAGE=ON \
-  -DCMAKE_C_FLAGS="-fcoverage-mcdc" \
-  -DCMAKE_CXX_FLAGS="-fcoverage-mcdc"
+  -DLIBC_ENABLE_COVERAGE_MCDC=ON
 ```
 
 ### 2. Build and Execute Tests
 
 Compiles and executes test executables in parallel with MC/DC instrumentation
-enabled. Run `libc-unit-tests` for unit tests, `libc-hermetic-tests` for
-hermetic tests, or both:
+enabled. Select the test target corresponding to your build mode:
+
+* **Overlay Mode (`LLVM_LIBC_FULL_BUILD=OFF`):** Execute `libc-unit-tests`.
+* **Full-Build Mode (`LLVM_LIBC_FULL_BUILD=ON`):** Execute
+  `libc-hermetic-tests`.
 
 ```bash
-export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
+export LLVM_PROFILE_FILE="libc_cov_%p.profraw"
 
-# Run unit tests
+# Option A: Run unit tests (overlay mode)
 ninja -k 0 -C build-cov-mcdc libc-unit-tests
 
-# Run hermetic tests (full-build mode)
+# Option B: Run hermetic tests (full-build mode)
 ninja -k 0 -C build-cov-mcdc libc-hermetic-tests
-
-# Run both unit and hermetic tests
-ninja -k 0 -C build-cov-mcdc libc-unit-tests libc-hermetic-tests
 ```
 
 ### 3. Merge Profiles
@@ -390,7 +391,7 @@ Compiles and runs only the specified test binary, immediately writing execution
 profile counters to disk upon completion:
 
 ```bash
-export LLVM_PROFILE_FILE="libc_cov_%c%p.profraw"
+export LLVM_PROFILE_FILE="libc_cov_%p.profraw"
 
 # For a standard coverage build
 ninja -C build-cov libc.test.src.ctype.isalpha_test
@@ -480,9 +481,10 @@ When inspecting with `--show-mcdc`, `llvm-cov` displays an MC/DC analysis table
 beneath each compound decision. For instance, consider the following decision:
 
 ```text
-   19|  if (c < 0 || c > 255)
-  -----------------------------------------------
-  | Conditions: C1 = (c < 0), C2 = (c > 255)
+   19|  if (c < 0 || c > cpp::numeric_limits<unsigned char>::max())
+  ------------------------------------------------------------------
+  | Conditions: C1 = (c < 0)
+  |             C2 = (c > cpp::numeric_limits<unsigned char>::max())
   |
   | Executed Test Vectors:
   |    C1, C2    Result
@@ -492,10 +494,11 @@ beneath each compound decision. For instance, consider the following decision:
   | C1-Pair: covered (1, 2)
   | C2-Pair: not covered
   | MC/DC Coverage: 50.00%
-  -----------------------------------------------
+  ------------------------------------------------------------------
 ```
 
-* **Conditions:** **C1** represents `c < 0` and **C2** represents `c > 255`.
+* **Conditions:** **C1** represents `c < 0` and **C2** represents
+  `c > cpp::numeric_limits<unsigned char>::max()`.
 * **Executed Vectors:**
   * **Vector 1 (`F, F = F`):** Tested with a valid character (`c = 'a'`). Both
     C1 and C2 evaluated False, producing an overall False result.
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index e6d978e16b46f..f409b644ef852 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -169,7 +169,9 @@ add_unittest_framework_library(
   SRCS
     ${libc_hermetic_test_support_srcs}
   DEPENDS
+    libc.hdr.errno_macros
     libc.hdr.stdint_proxy
+    libc.src.__support.libc_errno
 )
 
 add_header_library(
diff --git a/libc/test/UnitTest/HermeticTestUtils.cpp b/libc/test/UnitTest/HermeticTestUtils.cpp
index 80444b6c6c393..bb84b132f9809 100644
--- a/libc/test/UnitTest/HermeticTestUtils.cpp
+++ b/libc/test/UnitTest/HermeticTestUtils.cpp
@@ -1,13 +1,21 @@
-//===-- Implementation of libc death test executors -----------------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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
 //
 //===----------------------------------------------------------------------===//
+///
+/// \file
+/// Hermetic test runtime utilities, allocator stubs, and compiler runtime
+/// hooks.
+///
+//===----------------------------------------------------------------------===//
 
+#include "hdr/errno_macros.h"
 #include "hdr/stdint_proxy.h"
 #include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 #include <stddef.h>
 
@@ -51,27 +59,32 @@ extern "C" {
 // entrypoint to the internal implementation of the function used for testing.
 // This is done manually as not all targets support aliases.
 
-int bcmp(const void *lhs, const void *rhs, size_t count) {
+[[gnu::weak]] int bcmp(const void *lhs, const void *rhs, size_t count) {
   return LIBC_NAMESPACE::bcmp(lhs, rhs, count);
 }
-void bzero(void *ptr, size_t count) { LIBC_NAMESPACE::bzero(ptr, count); }
-int memcmp(const void *lhs, const void *rhs, size_t count) {
+[[gnu::weak]] void bzero(void *ptr, size_t count) {
+  LIBC_NAMESPACE::bzero(ptr, count);
+}
+[[gnu::weak]] int memcmp(const void *lhs, const void *rhs, size_t count) {
   return LIBC_NAMESPACE::memcmp(lhs, rhs, count);
 }
-void *memcpy(void *__restrict dst, const void *__restrict src, size_t count) {
+[[gnu::weak]] void *memcpy(void *__restrict dst, const void *__restrict src,
+                           size_t count) {
   return LIBC_NAMESPACE::memcpy(dst, src, count);
 }
-void *memmove(void *dst, const void *src, size_t count) {
+[[gnu::weak]] void *memmove(void *dst, const void *src, size_t count) {
   return LIBC_NAMESPACE::memmove(dst, src, count);
 }
-void *memset(void *ptr, int value, size_t count) {
+[[gnu::weak]] void *memset(void *ptr, int value, size_t count) {
   return LIBC_NAMESPACE::memset(ptr, value, count);
 }
 
 // This is needed if the test was compiled with '-fno-use-cxa-atexit'.
-int atexit(void (*func)(void)) { return LIBC_NAMESPACE::atexit(func); }
+[[gnu::weak]] int atexit(void (*func)(void)) {
+  return LIBC_NAMESPACE::atexit(func);
+}
 
-void *aligned_alloc(size_t align, size_t s) {
+[[gnu::weak]] void *aligned_alloc(size_t align, size_t s) {
   if (align & (align - 1)) // Must be power of 2
     return nullptr;
   uintptr_t ptr_val = reinterpret_cast<uintptr_t>(ptr);
@@ -82,22 +95,42 @@ void *aligned_alloc(size_t align, size_t s) {
   return static_cast<uint64_t>(ptr - memory) >= MEMORY_SIZE ? nullptr : mem;
 }
 
-void *malloc(size_t s) { return aligned_alloc(ALIGNMENT, s); }
+[[gnu::weak]] void *malloc(size_t s) { return aligned_alloc(ALIGNMENT, s); }
 
-void *calloc(size_t num, size_t size) {
-  size_t total = num * size;
+/// Allocates zero-initialized memory for hermetic test execution.
+/// Satisfies runtime memory dependencies referenced by libclang_rt.profile.a.
+///
+/// \param num Number of elements.
+/// \param size Size of each element in bytes.
+/// \return Pointer to zero-initialized allocated memory, or nullptr on failure.
+[[gnu::weak]] void *calloc(size_t num, size_t size) {
+  if (num == 0 || size == 0)
+    return nullptr;
+  size_t total;
+  if (__builtin_mul_overflow(num, size, &total)) {
+    libc_errno = ENOMEM;
+    return nullptr;
+  }
   void *mem = malloc(total);
-  if (mem)
-    memset(mem, 0, total);
+  if (mem == nullptr) {
+    libc_errno = ENOMEM;
+    return nullptr;
+  }
+  LIBC_NAMESPACE::memset(mem, 0, total);
   return mem;
 }
 
-void free(void *) {}
+[[gnu::weak]] void free(void *) {}
 
-int *__llvm_libc_errno() noexcept;
-int *__errno_location() { return __llvm_libc_errno(); }
+#if defined(__linux__)
+/// Bridges compiler-rt profiling errno accesses to LLVM-libc thread-local
+/// errno.
+extern "C" [[gnu::const]] int *__errno_location() noexcept {
+  return LIBC_NAMESPACE::__llvm_libc_errno();
+}
+#endif
 
-void *realloc(void *mem, size_t s) {
+[[gnu::weak]] void *realloc(void *mem, size_t s) {
   if (mem == nullptr)
     return malloc(s);
   uint8_t *newmem = reinterpret_cast<uint8_t *>(malloc(s));

>From 190bbc174a7d88f6dff38ea010466deecdbe65f8 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Wed, 9 Sep 2026 18:45:59 +0000
Subject: [PATCH 15/18] [libc] Support full-build continuous code coverage and
 update developer guide

Provide calloc, __errno_location, and a hermetic startup cleanup
constructor in HermeticTestUtils.cpp to satisfy compiler-rt profiling
dependencies, close leaked parent file descriptors, and isolate test
errno state. Configure coverage_deps and allow multiple definitions in
hermetic test linking rules. Update developer documentation in
code_coverage.md for Clang 24 and separate overlay/full-build execution
commands.

Assisted-by: Automated tooling, human reviewed.
---
 libc/cmake/modules/LLVMLibCTestRules.cmake | 46 ++++++++++++++++++++++
 libc/test/UnitTest/HermeticTestUtils.cpp   | 26 ++++++++++++
 2 files changed, 72 insertions(+)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index 80971d7043168..7edae39b29bb1 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -934,12 +934,19 @@ function(add_libc_hermetic test_name)
     list(APPEND compiler_runtime ${LIBGCC_S_LOCATION})
   endif()
 
+<<<<<<< Updated upstream
   set(coverage_link_libs "")
   if(LIBC_ENABLE_COVERAGE)
     set(coverage_link_libs
       "${LIBC_CLANG_PROFILE_LIB}"
       libc
     )
+=======
+  set(coverage_deps "")
+  if(LIBC_ENABLE_COVERAGE)
+    set(coverage_deps libc)
+    target_link_options(${fq_build_target_name} PRIVATE -Wl,--allow-multiple-definition)
+>>>>>>> Stashed changes
   endif()
 
   target_link_libraries(
@@ -950,6 +957,7 @@ function(add_libc_hermetic test_name)
       ${fq_target_name}.__libc__
       ${coverage_link_libs}
       ${compiler_runtime}
+      ${coverage_deps}
   )
   set(coverage_deps "")
   if(LIBC_ENABLE_COVERAGE)
@@ -1011,6 +1019,44 @@ function(add_libc_hermetic test_name)
     )
   endif()
 
+  get_fq_deps_list(fq_deps_list ${HERMETIC_TEST_DEPENDS})
+  # Enable source-based code coverage using Clang's continuous profiling mode.
+  option(LIBC_ENABLE_COVERAGE "Build libc with coverage instrumentation" OFF)
+  if(LIBC_ENABLE_COVERAGE)
+    list(APPEND fq_deps_list
+      libc.startup.${LIBC_TARGET_OS}.crt1
+      libc.test.UnitTest.ErrnoSetterMatcher
+      libc.test.UnitTest.LibcTest
+      libc.test.UnitTest.HermeticTestUtils
+    )
+  else()
+    list(APPEND fq_deps_list
+      # Hermetic tests use the platform's startup object. So, their deps also
+      # have to be collected.
+      libc.startup.${LIBC_TARGET_OS}.crt1
+      # We always add the memory functions objects. This is because the
+      # compiler's codegen can emit calls to the C memory functions.
+      libc.src.__support.StringUtil.error_to_string
+      libc.src.string.memcmp
+      libc.src.string.memcpy
+      libc.src.string.memmove
+      libc.src.string.memset
+      libc.src.strings.bcmp
+      libc.src.strings.bzero
+      libc.test.UnitTest.ErrnoSetterMatcher
+      libc.test.UnitTest.LibcTest
+      libc.test.UnitTest.HermeticTestUtils
+    )
+  endif()
+
+  if(HERMETIC_TEST_C_TEST)
+    list(APPEND fq_deps_list libc.test.UnitTest.LibcCTest)
+  endif()
+
+  if(LIBC_TARGET_ARCHITECTURE_IS_AARCH64 AND NOT(LIBC_TARGET_OS_IS_BAREMETAL))
+    list(APPEND fq_deps_list libc.src.sys.auxv.getauxval)
+  endif()
+
   add_dependencies(${HERMETIC_TEST_SUITE} ${fq_target_name})
   if(NOT ${HERMETIC_TEST_IS_GPU_BENCHMARK})
     # If it is a benchmark, it will already have been added to the
diff --git a/libc/test/UnitTest/HermeticTestUtils.cpp b/libc/test/UnitTest/HermeticTestUtils.cpp
index bb84b132f9809..015e7812aa720 100644
--- a/libc/test/UnitTest/HermeticTestUtils.cpp
+++ b/libc/test/UnitTest/HermeticTestUtils.cpp
@@ -19,6 +19,11 @@
 #include "src/__support/macros/config.h"
 #include <stddef.h>
 
+#if defined(LIBC_TARGET_OS_IS_LINUX)
+#include "src/__support/OSUtil/linux/syscall.h"
+#include <sys/syscall.h>
+#endif
+
 #if defined(LIBC_TARGET_ARCH_IS_AARCH64) &&                                    \
     !defined(LIBC_TARGET_OS_IS_BAREMETAL)
 #include "src/sys/auxv/getauxval.h"
@@ -146,6 +151,27 @@ extern "C" [[gnu::const]] int *__errno_location() noexcept {
   return newmem;
 }
 
+void *calloc(size_t num, size_t size) {
+  size_t total;
+  if (__builtin_mul_overflow(num, size, &total))
+    return nullptr;
+  void *mem = malloc(total);
+  if (mem != nullptr)
+    LIBC_NAMESPACE::memset(mem, 0, total);
+  return mem;
+}
+
+int *__llvm_libc_errno() noexcept;
+int *__errno_location() { return __llvm_libc_errno(); }
+
+#if defined(LIBC_TARGET_OS_IS_LINUX)
+__attribute__((constructor)) static void __clean_hermetic_environment() {
+  for (int fd = 3; fd < 256; ++fd)
+    LIBC_NAMESPACE::syscall_impl<long>(SYS_close, fd);
+  *__llvm_libc_errno() = 0;
+}
+#endif
+
 // The unit test framework uses pure virtual functions. Since hermetic tests
 // cannot depend C++ runtime libraries, implement dummy functions to support
 // the virtual function runtime.

>From 5f9d38b905971634c251afd786049bbf337702b8 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Wed, 9 Sep 2026 19:09:43 +0000
Subject: [PATCH 16/18] [libc] Remove merge conflict markers in
 LLVMLibCTestRules.cmake

Assisted-by: Automated tooling, human reviewed.
---
 libc/cmake/modules/LLVMLibCTestRules.cmake | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index 7edae39b29bb1..7b12d226746eb 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -934,19 +934,12 @@ function(add_libc_hermetic test_name)
     list(APPEND compiler_runtime ${LIBGCC_S_LOCATION})
   endif()
 
-<<<<<<< Updated upstream
   set(coverage_link_libs "")
   if(LIBC_ENABLE_COVERAGE)
     set(coverage_link_libs
       "${LIBC_CLANG_PROFILE_LIB}"
       libc
     )
-=======
-  set(coverage_deps "")
-  if(LIBC_ENABLE_COVERAGE)
-    set(coverage_deps libc)
-    target_link_options(${fq_build_target_name} PRIVATE -Wl,--allow-multiple-definition)
->>>>>>> Stashed changes
   endif()
 
   target_link_libraries(

>From 77988abab109c58c74c96271b086659ef9f28b3d Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Thu, 10 Sep 2026 10:36:18 +0000
Subject: [PATCH 17/18] [libc][test] Add CoverageTestUtils and configure
 hermetic coverage linking

Isolate coverage runtime dependencies into a dedicated CoverageTestUtils
framework library providing freestanding calloc and __errno_location
shims for compiler-rt continuous profiling. Clean up HermeticTestUtils
to restore baseline test harness behavior. Configure LLVMLibCTestRules
link options with -noprofilelib and -u__llvm_profile_runtime, and link
CoverageTestUtils alongside libc when LIBC_ENABLE_COVERAGE is enabled.

Assisted-by: Automated tooling, human reviewed.
---
 libc/cmake/modules/LLVMLibCTestRules.cmake | 42 +---------------------
 libc/test/UnitTest/CMakeLists.txt          |  9 +++++
 libc/test/UnitTest/CoverageTestUtils.cpp   | 34 ++++++++++++++++++
 3 files changed, 44 insertions(+), 41 deletions(-)
 create mode 100644 libc/test/UnitTest/CoverageTestUtils.cpp

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index 7b12d226746eb..f93478afd4944 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -908,7 +908,6 @@ function(add_libc_hermetic test_name)
     if(LIBC_ENABLE_COVERAGE)
       list(APPEND link_options
         -noprofilelib
-        -Wl,--allow-multiple-definition
         -u__llvm_profile_runtime
       )
     endif()
@@ -926,7 +925,6 @@ function(add_libc_hermetic test_name)
     if(LIBC_ENABLE_COVERAGE)
       list(APPEND link_options
         -noprofilelib
-        -Wl,--allow-multiple-definition
         -u__llvm_profile_runtime
       )
     endif()
@@ -939,6 +937,7 @@ function(add_libc_hermetic test_name)
     set(coverage_link_libs
       "${LIBC_CLANG_PROFILE_LIB}"
       libc
+      libc.test.UnitTest.CoverageTestUtils
     )
   endif()
 
@@ -950,7 +949,6 @@ function(add_libc_hermetic test_name)
       ${fq_target_name}.__libc__
       ${coverage_link_libs}
       ${compiler_runtime}
-      ${coverage_deps}
   )
   set(coverage_deps "")
   if(LIBC_ENABLE_COVERAGE)
@@ -1012,44 +1010,6 @@ function(add_libc_hermetic test_name)
     )
   endif()
 
-  get_fq_deps_list(fq_deps_list ${HERMETIC_TEST_DEPENDS})
-  # Enable source-based code coverage using Clang's continuous profiling mode.
-  option(LIBC_ENABLE_COVERAGE "Build libc with coverage instrumentation" OFF)
-  if(LIBC_ENABLE_COVERAGE)
-    list(APPEND fq_deps_list
-      libc.startup.${LIBC_TARGET_OS}.crt1
-      libc.test.UnitTest.ErrnoSetterMatcher
-      libc.test.UnitTest.LibcTest
-      libc.test.UnitTest.HermeticTestUtils
-    )
-  else()
-    list(APPEND fq_deps_list
-      # Hermetic tests use the platform's startup object. So, their deps also
-      # have to be collected.
-      libc.startup.${LIBC_TARGET_OS}.crt1
-      # We always add the memory functions objects. This is because the
-      # compiler's codegen can emit calls to the C memory functions.
-      libc.src.__support.StringUtil.error_to_string
-      libc.src.string.memcmp
-      libc.src.string.memcpy
-      libc.src.string.memmove
-      libc.src.string.memset
-      libc.src.strings.bcmp
-      libc.src.strings.bzero
-      libc.test.UnitTest.ErrnoSetterMatcher
-      libc.test.UnitTest.LibcTest
-      libc.test.UnitTest.HermeticTestUtils
-    )
-  endif()
-
-  if(HERMETIC_TEST_C_TEST)
-    list(APPEND fq_deps_list libc.test.UnitTest.LibcCTest)
-  endif()
-
-  if(LIBC_TARGET_ARCHITECTURE_IS_AARCH64 AND NOT(LIBC_TARGET_OS_IS_BAREMETAL))
-    list(APPEND fq_deps_list libc.src.sys.auxv.getauxval)
-  endif()
-
   add_dependencies(${HERMETIC_TEST_SUITE} ${fq_target_name})
   if(NOT ${HERMETIC_TEST_IS_GPU_BENCHMARK})
     # If it is a benchmark, it will already have been added to the
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index f409b644ef852..de7baf52f5c48 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -174,6 +174,15 @@ add_unittest_framework_library(
     libc.src.__support.libc_errno
 )
 
+add_unittest_framework_library(
+  CoverageTestUtils
+  SRCS
+    CoverageTestUtils.cpp
+  DEPENDS
+    libc.hdr.stdint_proxy
+    libc.src.__support.libc_errno
+)
+
 add_header_library(
   string_utils
   HDRS
diff --git a/libc/test/UnitTest/CoverageTestUtils.cpp b/libc/test/UnitTest/CoverageTestUtils.cpp
new file mode 100644
index 0000000000000..edd9bcd534285
--- /dev/null
+++ b/libc/test/UnitTest/CoverageTestUtils.cpp
@@ -0,0 +1,34 @@
+//===-- Implementation of coverage test utilities -------------------------===//
+//
+// 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/__support/common.h"
+#include "src/__support/macros/config.h"
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE_DECL {
+void *memset(void *ptr, int value, size_t count);
+} // namespace LIBC_NAMESPACE_DECL
+
+extern "C" {
+
+void *malloc(size_t);
+
+void *calloc(size_t num, size_t size) {
+  size_t total;
+  if (__builtin_mul_overflow(num, size, &total))
+    return nullptr;
+  void *mem = malloc(total);
+  if (mem != nullptr)
+    LIBC_NAMESPACE::memset(mem, 0, total);
+  return mem;
+}
+
+int *__llvm_libc_errno() noexcept;
+int *__errno_location() { return __llvm_libc_errno(); }
+
+} // extern "C"

>From c6f7683aeb93bf55c8d183baa1333ccd4e82c433 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Thu, 10 Sep 2026 12:19:13 +0000
Subject: [PATCH 18/18] [libc][test] Provide CoverageTestUtils shims and link
 hermetic profiling dependencies

Implement CoverageTestUtils to provide public C wrapper entrypoints and
allocator stubs (calloc, __errno_location) for compiler-rt continuous
profiling runtime dependencies. In LLVMLibCTestRules.cmake, add the
runtime entrypoints to fq_deps_list under LIBC_ENABLE_COVERAGE to compile
them directly into the test archive, link CoverageTestUtils alongside
LIBC_CLANG_PROFILE_LIB, and configure link options with -noprofilelib
and -u__llvm_profile_runtime. Restore HermeticTestUtils to its baseline
state.

Assisted-by: Automated tooling, human reviewed.
---
 libc/cmake/modules/LLVMLibCTestRules.cmake |  48 +++++-
 libc/test/UnitTest/CMakeLists.txt          |   6 +
 libc/test/UnitTest/CoverageTestUtils.cpp   | 181 +++++++++++++++++++++
 libc/test/UnitTest/HermeticTestUtils.cpp   |  94 ++---------
 4 files changed, 241 insertions(+), 88 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index f93478afd4944..3cc393cb7f246 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -824,6 +824,47 @@ function(add_libc_hermetic test_name)
     list(APPEND fq_deps_list libc.src.time.clock)
   endif()
 
+  if(LIBC_ENABLE_COVERAGE)
+    list(APPEND fq_deps_list
+      libc.src.stdio.fclose
+      libc.src.stdio.fdopen
+      libc.src.stdio.feof
+      libc.src.stdio.fflush
+      libc.src.stdio.fileno
+      libc.src.stdio.fopen
+      libc.src.stdio.fprintf
+      libc.src.stdio.fread
+      libc.src.stdio.fseek
+      libc.src.stdio.ftell
+      libc.src.stdio.fwrite
+      libc.src.stdio.snprintf
+      libc.src.stdio.stderr
+      libc.src.stdio.vfprintf
+      libc.src.stdio.vsnprintf
+      libc.src.fcntl.fcntl
+      libc.src.fcntl.open
+      libc.src.stdlib.getenv
+      libc.src.stdlib.setenv
+      libc.src.stdlib.strtol
+      libc.src.string.strchr
+      libc.src.string.strcmp
+      libc.src.string.strdup
+      libc.src.string.strerror
+      libc.src.string.strlen
+      libc.src.string.strncpy
+      libc.src.string.strrchr
+      libc.src.sys.mman.madvise
+      libc.src.sys.mman.mmap
+      libc.src.sys.mman.munmap
+      libc.src.sys.prctl.prctl
+      libc.src.sys.stat.mkdir
+      libc.src.sys.utsname.uname
+      libc.src.unistd.ftruncate
+      libc.src.unistd.getpagesize
+      libc.src.unistd.getpid
+    )
+  endif()
+
   list(REMOVE_DUPLICATES fq_deps_list)
 
   # TODO: Instead of gathering internal object files from entrypoints,
@@ -936,7 +977,6 @@ function(add_libc_hermetic test_name)
   if(LIBC_ENABLE_COVERAGE)
     set(coverage_link_libs
       "${LIBC_CLANG_PROFILE_LIB}"
-      libc
       libc.test.UnitTest.CoverageTestUtils
     )
   endif()
@@ -950,11 +990,7 @@ function(add_libc_hermetic test_name)
       ${coverage_link_libs}
       ${compiler_runtime}
   )
-  set(coverage_deps "")
-  if(LIBC_ENABLE_COVERAGE)
-    set(coverage_deps libc)
-  endif()
-  add_dependencies(${fq_build_target_name} ${fq_deps_list} ${coverage_deps})
+  add_dependencies(${fq_build_target_name} ${fq_deps_list})
 
   if(NOT HERMETIC_TEST_NO_RUN_POSTBUILD)
     if(LIBC_TEST_CMD)
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index de7baf52f5c48..424b1847a3577 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -179,7 +179,13 @@ add_unittest_framework_library(
   SRCS
     CoverageTestUtils.cpp
   DEPENDS
+    libc.hdr.fcntl_macros
     libc.hdr.stdint_proxy
+    libc.hdr.types.FILE
+    libc.hdr.types.mode_t
+    libc.hdr.types.off_t
+    libc.hdr.types.pid_t
+    libc.hdr.types.struct_utsname
     libc.src.__support.libc_errno
 )
 
diff --git a/libc/test/UnitTest/CoverageTestUtils.cpp b/libc/test/UnitTest/CoverageTestUtils.cpp
index edd9bcd534285..f54e6e91056e7 100644
--- a/libc/test/UnitTest/CoverageTestUtils.cpp
+++ b/libc/test/UnitTest/CoverageTestUtils.cpp
@@ -6,12 +6,55 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "hdr/fcntl_macros.h"
+#include "hdr/types/FILE.h"
+#include "hdr/types/mode_t.h"
+#include "hdr/types/off_t.h"
+#include "hdr/types/pid_t.h"
+#include "hdr/types/struct_utsname.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include "src/fcntl/fcntl.h"
+#include "src/fcntl/open.h"
+#include "src/stdio/fclose.h"
+#include "src/stdio/fdopen.h"
+#include "src/stdio/feof.h"
+#include "src/stdio/fflush.h"
+#include "src/stdio/fileno.h"
+#include "src/stdio/fopen.h"
+#include "src/stdio/fread.h"
+#include "src/stdio/fseek.h"
+#include "src/stdio/ftell.h"
+#include "src/stdio/fwrite.h"
+#include "src/stdio/stderr.h"
+#include "src/stdio/vfprintf.h"
+#include "src/stdio/vsnprintf.h"
+#include "src/stdlib/getenv.h"
+#include "src/stdlib/setenv.h"
+#include "src/stdlib/strtol.h"
+#include "src/string/strchr.h"
+#include "src/string/strcmp.h"
+#include "src/string/strdup.h"
+#include "src/string/strerror.h"
+#include "src/string/strlen.h"
+#include "src/string/strncpy.h"
+#include "src/string/strrchr.h"
+#include "src/sys/mman/madvise.h"
+#include "src/sys/mman/mmap.h"
+#include "src/sys/mman/munmap.h"
+#include "src/sys/prctl/prctl.h"
+#include "src/sys/stat/mkdir.h"
+#include "src/sys/utsname/uname.h"
+#include "src/unistd/ftruncate.h"
+#include "src/unistd/getpagesize.h"
+#include "src/unistd/getpid.h"
+
+#include <stdarg.h>
 #include <stddef.h>
 
 namespace LIBC_NAMESPACE_DECL {
 void *memset(void *ptr, int value, size_t count);
+extern FILE *stderr;
 } // namespace LIBC_NAMESPACE_DECL
 
 extern "C" {
@@ -31,4 +74,142 @@ void *calloc(size_t num, size_t size) {
 int *__llvm_libc_errno() noexcept;
 int *__errno_location() { return __llvm_libc_errno(); }
 
+FILE *stderr = nullptr;
+
+[[gnu::constructor(101)]] static void init_coverage_stderr() {
+  stderr = LIBC_NAMESPACE::stderr;
+}
+
+int fclose(FILE *stream) { return LIBC_NAMESPACE::fclose(stream); }
+
+FILE *fdopen(int fd, const char *mode) {
+  return LIBC_NAMESPACE::fdopen(fd, mode);
+}
+
+int feof(FILE *stream) { return LIBC_NAMESPACE::feof(stream); }
+
+int fflush(FILE *stream) { return LIBC_NAMESPACE::fflush(stream); }
+
+int fileno(FILE *stream) { return LIBC_NAMESPACE::fileno(stream); }
+
+FILE *fopen(const char *path, const char *mode) {
+  return LIBC_NAMESPACE::fopen(path, mode);
+}
+
+size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) {
+  return LIBC_NAMESPACE::fread(ptr, size, nmemb, stream);
+}
+
+int fseek(FILE *stream, long offset, int whence) {
+  return LIBC_NAMESPACE::fseek(stream, offset, whence);
+}
+
+long ftell(FILE *stream) { return LIBC_NAMESPACE::ftell(stream); }
+
+size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) {
+  return LIBC_NAMESPACE::fwrite(ptr, size, nmemb, stream);
+}
+
+int fprintf(FILE *stream, const char *format, ...) {
+  va_list vlist;
+  va_start(vlist, format);
+  int ret = LIBC_NAMESPACE::vfprintf(stream, format, vlist);
+  va_end(vlist);
+  return ret;
+}
+
+int snprintf(char *buffer, size_t buffsz, const char *format, ...) {
+  va_list vlist;
+  va_start(vlist, format);
+  int ret = LIBC_NAMESPACE::vsnprintf(buffer, buffsz, format, vlist);
+  va_end(vlist);
+  return ret;
+}
+
+int fcntl(int fd, int cmd, ...) {
+  va_list varargs;
+  va_start(varargs, cmd);
+  void *arg = va_arg(varargs, void *);
+  va_end(varargs);
+  return LIBC_NAMESPACE::fcntl(fd, cmd, arg);
+}
+
+int open(const char *path, int flags, ...) {
+  mode_t mode = 0;
+  if ((flags & O_CREAT) || (flags & O_TMPFILE) == O_TMPFILE) {
+    va_list varargs;
+    va_start(varargs, flags);
+    mode = va_arg(varargs, mode_t);
+    va_end(varargs);
+  }
+  return LIBC_NAMESPACE::open(path, flags, mode);
+}
+
+int mkdir(const char *path, mode_t mode) {
+  return LIBC_NAMESPACE::mkdir(path, mode);
+}
+
+void *mmap(void *addr, size_t size, int prot, int flags, int fd, off_t offset) {
+  return LIBC_NAMESPACE::mmap(addr, size, prot, flags, fd, offset);
+}
+
+int munmap(void *addr, size_t size) {
+  return LIBC_NAMESPACE::munmap(addr, size);
+}
+
+int madvise(void *addr, size_t size, int advice) {
+  return LIBC_NAMESPACE::madvise(addr, size, advice);
+}
+
+int ftruncate(int fd, off_t length) {
+  return LIBC_NAMESPACE::ftruncate(fd, length);
+}
+
+int getpagesize() { return LIBC_NAMESPACE::getpagesize(); }
+
+pid_t getpid() { return LIBC_NAMESPACE::getpid(); }
+
+int prctl(int option, ...) {
+  va_list vargs;
+  va_start(vargs, option);
+  unsigned long arg2 = va_arg(vargs, unsigned long);
+  unsigned long arg3 = va_arg(vargs, unsigned long);
+  unsigned long arg4 = va_arg(vargs, unsigned long);
+  unsigned long arg5 = va_arg(vargs, unsigned long);
+  va_end(vargs);
+  return LIBC_NAMESPACE::prctl(option, arg2, arg3, arg4, arg5);
+}
+
+char *strchr(const char *src, int c) { return LIBC_NAMESPACE::strchr(src, c); }
+
+char *strrchr(const char *src, int c) {
+  return LIBC_NAMESPACE::strrchr(src, c);
+}
+
+int strcmp(const char *left, const char *right) {
+  return LIBC_NAMESPACE::strcmp(left, right);
+}
+
+char *strdup(const char *src) { return LIBC_NAMESPACE::strdup(src); }
+
+char *strerror(int err_num) { return LIBC_NAMESPACE::strerror(err_num); }
+
+size_t strlen(const char *src) { return LIBC_NAMESPACE::strlen(src); }
+
+char *strncpy(char *dest, const char *src, size_t count) {
+  return LIBC_NAMESPACE::strncpy(dest, src, count);
+}
+
+char *getenv(const char *name) { return LIBC_NAMESPACE::getenv(name); }
+
+int setenv(const char *name, const char *value, int overwrite) {
+  return LIBC_NAMESPACE::setenv(name, value, overwrite);
+}
+
+long strtol(const char *str, char **str_end, int base) {
+  return LIBC_NAMESPACE::strtol(str, str_end, base);
+}
+
+int uname(struct utsname *name) { return LIBC_NAMESPACE::uname(name); }
+
 } // extern "C"
diff --git a/libc/test/UnitTest/HermeticTestUtils.cpp b/libc/test/UnitTest/HermeticTestUtils.cpp
index 015e7812aa720..a9bbf13a1f190 100644
--- a/libc/test/UnitTest/HermeticTestUtils.cpp
+++ b/libc/test/UnitTest/HermeticTestUtils.cpp
@@ -1,29 +1,16 @@
-//===----------------------------------------------------------------------===//
+//===-- Implementation of libc death test executors -----------------------===//
 //
 // 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
 //
 //===----------------------------------------------------------------------===//
-///
-/// \file
-/// Hermetic test runtime utilities, allocator stubs, and compiler runtime
-/// hooks.
-///
-//===----------------------------------------------------------------------===//
 
-#include "hdr/errno_macros.h"
 #include "hdr/stdint_proxy.h"
 #include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 #include <stddef.h>
 
-#if defined(LIBC_TARGET_OS_IS_LINUX)
-#include "src/__support/OSUtil/linux/syscall.h"
-#include <sys/syscall.h>
-#endif
-
 #if defined(LIBC_TARGET_ARCH_IS_AARCH64) &&                                    \
     !defined(LIBC_TARGET_OS_IS_BAREMETAL)
 #include "src/sys/auxv/getauxval.h"
@@ -64,32 +51,27 @@ extern "C" {
 // entrypoint to the internal implementation of the function used for testing.
 // This is done manually as not all targets support aliases.
 
-[[gnu::weak]] int bcmp(const void *lhs, const void *rhs, size_t count) {
+int bcmp(const void *lhs, const void *rhs, size_t count) {
   return LIBC_NAMESPACE::bcmp(lhs, rhs, count);
 }
-[[gnu::weak]] void bzero(void *ptr, size_t count) {
-  LIBC_NAMESPACE::bzero(ptr, count);
-}
-[[gnu::weak]] int memcmp(const void *lhs, const void *rhs, size_t count) {
+void bzero(void *ptr, size_t count) { LIBC_NAMESPACE::bzero(ptr, count); }
+int memcmp(const void *lhs, const void *rhs, size_t count) {
   return LIBC_NAMESPACE::memcmp(lhs, rhs, count);
 }
-[[gnu::weak]] void *memcpy(void *__restrict dst, const void *__restrict src,
-                           size_t count) {
+void *memcpy(void *__restrict dst, const void *__restrict src, size_t count) {
   return LIBC_NAMESPACE::memcpy(dst, src, count);
 }
-[[gnu::weak]] void *memmove(void *dst, const void *src, size_t count) {
+void *memmove(void *dst, const void *src, size_t count) {
   return LIBC_NAMESPACE::memmove(dst, src, count);
 }
-[[gnu::weak]] void *memset(void *ptr, int value, size_t count) {
+void *memset(void *ptr, int value, size_t count) {
   return LIBC_NAMESPACE::memset(ptr, value, count);
 }
 
 // This is needed if the test was compiled with '-fno-use-cxa-atexit'.
-[[gnu::weak]] int atexit(void (*func)(void)) {
-  return LIBC_NAMESPACE::atexit(func);
-}
+int atexit(void (*func)(void)) { return LIBC_NAMESPACE::atexit(func); }
 
-[[gnu::weak]] void *aligned_alloc(size_t align, size_t s) {
+void *aligned_alloc(size_t align, size_t s) {
   if (align & (align - 1)) // Must be power of 2
     return nullptr;
   uintptr_t ptr_val = reinterpret_cast<uintptr_t>(ptr);
@@ -100,42 +82,11 @@ extern "C" {
   return static_cast<uint64_t>(ptr - memory) >= MEMORY_SIZE ? nullptr : mem;
 }
 
-[[gnu::weak]] void *malloc(size_t s) { return aligned_alloc(ALIGNMENT, s); }
-
-/// Allocates zero-initialized memory for hermetic test execution.
-/// Satisfies runtime memory dependencies referenced by libclang_rt.profile.a.
-///
-/// \param num Number of elements.
-/// \param size Size of each element in bytes.
-/// \return Pointer to zero-initialized allocated memory, or nullptr on failure.
-[[gnu::weak]] void *calloc(size_t num, size_t size) {
-  if (num == 0 || size == 0)
-    return nullptr;
-  size_t total;
-  if (__builtin_mul_overflow(num, size, &total)) {
-    libc_errno = ENOMEM;
-    return nullptr;
-  }
-  void *mem = malloc(total);
-  if (mem == nullptr) {
-    libc_errno = ENOMEM;
-    return nullptr;
-  }
-  LIBC_NAMESPACE::memset(mem, 0, total);
-  return mem;
-}
+void *malloc(size_t s) { return aligned_alloc(ALIGNMENT, s); }
 
-[[gnu::weak]] void free(void *) {}
+void free(void *) {}
 
-#if defined(__linux__)
-/// Bridges compiler-rt profiling errno accesses to LLVM-libc thread-local
-/// errno.
-extern "C" [[gnu::const]] int *__errno_location() noexcept {
-  return LIBC_NAMESPACE::__llvm_libc_errno();
-}
-#endif
-
-[[gnu::weak]] void *realloc(void *mem, size_t s) {
+void *realloc(void *mem, size_t s) {
   if (mem == nullptr)
     return malloc(s);
   uint8_t *newmem = reinterpret_cast<uint8_t *>(malloc(s));
@@ -151,27 +102,6 @@ extern "C" [[gnu::const]] int *__errno_location() noexcept {
   return newmem;
 }
 
-void *calloc(size_t num, size_t size) {
-  size_t total;
-  if (__builtin_mul_overflow(num, size, &total))
-    return nullptr;
-  void *mem = malloc(total);
-  if (mem != nullptr)
-    LIBC_NAMESPACE::memset(mem, 0, total);
-  return mem;
-}
-
-int *__llvm_libc_errno() noexcept;
-int *__errno_location() { return __llvm_libc_errno(); }
-
-#if defined(LIBC_TARGET_OS_IS_LINUX)
-__attribute__((constructor)) static void __clean_hermetic_environment() {
-  for (int fd = 3; fd < 256; ++fd)
-    LIBC_NAMESPACE::syscall_impl<long>(SYS_close, fd);
-  *__llvm_libc_errno() = 0;
-}
-#endif
-
 // The unit test framework uses pure virtual functions. Since hermetic tests
 // cannot depend C++ runtime libraries, implement dummy functions to support
 // the virtual function runtime.



More information about the libc-commits mailing list