[libc-commits] [libc] Code Coverage Prototype - Linux Extraction Hook (PR #210062)

Tapiwa Gonga via libc-commits libc-commits at lists.llvm.org
Thu Jul 16 06:41:33 PDT 2026


https://github.com/tapiwagonga created https://github.com/llvm/llvm-project/pull/210062

I put together this draft to test out extracting code coverage metrics from our libc tests without tripping over those circular I/O dependencies. I've scoped it just to Linux for now as a prototype.

In this pass, I refactored LibcTestMain.cpp to use direct Linux syscalls (SYS_mmap, SYS_openat, SYS_write) for writing out the coverage buffers. This keeps the extraction process completely isolated from the compiler-rt standard library I/O that usually deadlocks our freestanding setup.

To keep the C++ hook as lightweight as possible, I intentionally left out environment variable parsing for now. So instead of trying to resolve paths via lit or LLVM_PROFILE_FILE, the test binaries just generate and dump unique .profraw files straight into their immediate execution directories.

I'd love to hear your thoughts on this approach and whether we feel good about this direction for the extraction hook.

>From f4f50f1df5d1b8e8faf74583cacfc13bd3e3bcf1 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Thu, 16 Jul 2026 13:26:20 +0000
Subject: [PATCH] [libc][WIP] Implement freestanding coverage extraction
 prototype using raw syscalls

---
 libc/test/UnitTest/LibcTestMain.cpp | 91 ++++++++++++++++++++++++++++-
 1 file changed, 88 insertions(+), 3 deletions(-)

diff --git a/libc/test/UnitTest/LibcTestMain.cpp b/libc/test/UnitTest/LibcTestMain.cpp
index c348d5ef1aa1b..00f747d1f6f1e 100644
--- a/libc/test/UnitTest/LibcTestMain.cpp
+++ b/libc/test/UnitTest/LibcTestMain.cpp
@@ -43,8 +43,90 @@ 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(__linux__)
+#include "src/__support/OSUtil/syscall.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <time.h>
+
+extern "C" {
+__attribute__((weak)) uint64_t __llvm_profile_get_size_for_buffer(void);
+__attribute__((weak)) int __llvm_profile_write_buffer(char *Buffer);
+__attribute__((weak)) void __llvm_profile_set_filename(const char *FilenamePat);
+
+// Override compiler-rt's weak filename symbol. This redirects the default
+// filename to /dev/null to silence the default dumper by default.
+char __llvm_profile_filename[] = "/dev/null";
+}
+
+namespace {
+void write_raw_profile() {
+  if (!__llvm_profile_get_size_for_buffer || !__llvm_profile_write_buffer)
+    return;
+
+  uint64_t required_size = __llvm_profile_get_size_for_buffer();
+  if (required_size == 0)
+    return;
+
+  long mmap_ret = LIBC_NAMESPACE::syscall_impl<long>(
+      SYS_mmap, nullptr, required_size, PROT_READ | PROT_WRITE,
+      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (mmap_ret < 0 && mmap_ret > -4096)
+    return;
+  char *profile_buffer = reinterpret_cast<char *>(mmap_ret);
+
+  if (__llvm_profile_write_buffer(profile_buffer) != 0) {
+    LIBC_NAMESPACE::syscall_impl<long>(SYS_munmap, profile_buffer, required_size);
+    return;
+  }
+
+  // Create a minimal filename: libc_cov_<pid>.profraw
+  long pid = LIBC_NAMESPACE::syscall_impl<long>(SYS_getpid);
+  if (pid <= 0) pid = 1;
+
+  char filename[64] = "libc_cov_";
+  int idx = 9;
+  
+  long temp = pid;
+  char digits[32];
+  int d_len = 0;
+  while (temp > 0) { digits[d_len++] = static_cast<char>('0' + (temp % 10)); temp /= 10; }
+  if (d_len == 0) digits[d_len++] = '0';
+  for (int i = d_len - 1; i >= 0; --i) filename[idx++] = digits[i];
+  
+  const char *suffix = ".profraw";
+  for (int i = 0; suffix[i] != '\0'; ++i) filename[idx++] = suffix[i];
+  filename[idx] = '\0';
+
+  long fd = LIBC_NAMESPACE::syscall_impl<long>(
+      SYS_openat, AT_FDCWD, filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+      
+  if (fd >= 0) {
+    uint64_t bytes_written = 0;
+    while (bytes_written < required_size) {
+      long ret = LIBC_NAMESPACE::syscall_impl<long>(
+          SYS_write, fd, profile_buffer + bytes_written, required_size - bytes_written);
+      if (ret <= 0 && ret != -EINTR) break;
+      if (ret > 0) bytes_written += ret;
+    }
+    LIBC_NAMESPACE::syscall_impl<long>(SYS_close, fd);
+  }
+
+  LIBC_NAMESPACE::syscall_impl<long>(SYS_munmap, profile_buffer, required_size);
+
+  // Clear the filename pattern to prevent compiler-rt from writing at exit.
+  if (__llvm_profile_set_filename)
+    __llvm_profile_set_filename("/dev/null");
+}
+} // anonymous namespace
+#else
+namespace {
+void write_raw_profile() {}
+} // anonymous namespace
+#endif
+
 #if __STDC_HOSTED__
 #define TEST_MAIN int main
 #else
@@ -56,5 +138,8 @@ 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));
+  write_raw_profile();
+  return result;
 }



More information about the libc-commits mailing list