[compiler-rt] [compiler-rt][memprof] clear histogram tail granule on allocation. (PR #208911)

David CARLIER via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 16 11:48:48 PDT 2026


https://github.com/devnexen updated https://github.com/llvm/llvm-project/pull/208911

>From 35a450f0eb31ad97693a8e81fd77dbf78dc8acfa Mon Sep 17 00:00:00 2001
From: David Carlier <devnexen at gmail.com>
Date: Sat, 11 Jul 2026 14:11:45 +0100
Subject: [PATCH 1/3] [compiler-rt][memprof] clear histogram tail granule on
 allocation.

ClearShadow rounded the size down, leaving the partial tail counter
(ceil(size/8)-th) uncleared, so a recycled chunk leaked a stale count
into the last histogram bucket. Round up instead.
---
 compiler-rt/lib/memprof/memprof_allocator.cpp |  5 +--
 .../memprof_histogram_tail_clear.cpp          | 44 +++++++++++++++++++
 2 files changed, 45 insertions(+), 4 deletions(-)
 create mode 100644 compiler-rt/test/memprof/TestCases/memprof_histogram_tail_clear.cpp

diff --git a/compiler-rt/lib/memprof/memprof_allocator.cpp b/compiler-rt/lib/memprof/memprof_allocator.cpp
index d11a909a02c5e..4ddf8dfcf06ab 100644
--- a/compiler-rt/lib/memprof/memprof_allocator.cpp
+++ b/compiler-rt/lib/memprof/memprof_allocator.cpp
@@ -457,10 +457,7 @@ struct Allocator {
     m->timestamp_ms = GetTimestamp();
     m->alloc_context_id = StackDepotPut(*stack);
 
-    uptr size_rounded_down_to_granularity =
-        RoundDownTo(size, SHADOW_GRANULARITY);
-    if (size_rounded_down_to_granularity)
-      ClearShadow(user_beg, size_rounded_down_to_granularity);
+    ClearShadow(user_beg, RoundUpTo(size, SHADOW_GRANULARITY));
 
     MemprofStats &thread_stats = GetCurrentThreadStats();
     thread_stats.mallocs++;
diff --git a/compiler-rt/test/memprof/TestCases/memprof_histogram_tail_clear.cpp b/compiler-rt/test/memprof/TestCases/memprof_histogram_tail_clear.cpp
new file mode 100644
index 0000000000000..119be97b76839
--- /dev/null
+++ b/compiler-rt/test/memprof/TestCases/memprof_histogram_tail_clear.cpp
@@ -0,0 +1,44 @@
+// A non-granule-multiple allocation owns a partial tail histogram counter that
+// must be cleared on allocation; otherwise a recycled chunk leaks the previous
+// allocation's count into the last bucket.
+
+// RUN: %clangxx_memprof -O0 -mllvm -memprof-histogram -mllvm -memprof-use-callbacks=true %s -o %t
+// RUN: %env_memprof_opts=print_text=1:histogram=1:log_path=stdout %run %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// Distinct call sites keep the two allocations as separate (unmerged) MIBs.
+__attribute__((noinline)) static char *alloc_first() {
+  return (char *)malloc(20);
+}
+__attribute__((noinline)) static char *alloc_second() {
+  return (char *)malloc(20);
+}
+
+int main() {
+  // Leave a count of 42 in the tail granule (byte 16), then free.
+  char *a = alloc_first();
+  if (!a)
+    return 1;
+  for (int i = 0; i < 42; ++i)
+    a[16] = 'A';
+  free(a);
+
+  // Reuse the chunk without touching the tail; its bucket must read 0.
+  char *b = alloc_second();
+  if (!b)
+    return 1;
+  for (int i = 0; i < 5; ++i)
+    b[0] = 'B';
+  for (int i = 0; i < 7; ++i)
+    b[8] = 'C';
+  free(b);
+
+  printf("Test completed successfully\n");
+  return 0;
+}
+
+// CHECK: AccessCountHistogram[3]: 5 7 0
+// CHECK-NOT: AccessCountHistogram[3]: 5 7 42
+// CHECK: Test completed successfully

>From cc02d991c375c26c678039e63c5c89958eece002 Mon Sep 17 00:00:00 2001
From: David Carlier <devnexen at gmail.com>
Date: Tue, 14 Jul 2026 19:33:24 +0100
Subject: [PATCH 2/3] update test

---
 .../memprof/TestCases/memprof_histogram_tail_clear.cpp    | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/compiler-rt/test/memprof/TestCases/memprof_histogram_tail_clear.cpp b/compiler-rt/test/memprof/TestCases/memprof_histogram_tail_clear.cpp
index 119be97b76839..9bb7226b39d78 100644
--- a/compiler-rt/test/memprof/TestCases/memprof_histogram_tail_clear.cpp
+++ b/compiler-rt/test/memprof/TestCases/memprof_histogram_tail_clear.cpp
@@ -25,9 +25,12 @@ int main() {
     a[16] = 'A';
   free(a);
 
-  // Reuse the chunk without touching the tail; its bucket must read 0.
+  // The stale-tail bug only manifests on a recycled chunk (a fresh chunk's
+  // shadow is already zero). memprof has no quarantine, so with no intervening
+  // same-size-class allocation the allocator returns the just-freed chunk;
+  // require the reuse so the test can't pass vacuously. Its bucket must read 0.
   char *b = alloc_second();
-  if (!b)
+  if (b != a)
     return 1;
   for (int i = 0; i < 5; ++i)
     b[0] = 'B';
@@ -40,5 +43,4 @@ int main() {
 }
 
 // CHECK: AccessCountHistogram[3]: 5 7 0
-// CHECK-NOT: AccessCountHistogram[3]: 5 7 42
 // CHECK: Test completed successfully

>From 3a969bedd090d36f99f3cd24053f57879c0d16a0 Mon Sep 17 00:00:00 2001
From: David Carlier <devnexen at gmail.com>
Date: Thu, 16 Jul 2026 19:48:01 +0100
Subject: [PATCH 3/3] [compiler-rt][memprof] add test for the non-histogram
 case.

---
 .../memprof_nonhistogram_tail_clear.cpp       | 40 +++++++++++++++++++
 1 file changed, 40 insertions(+)
 create mode 100644 compiler-rt/test/memprof/TestCases/memprof_nonhistogram_tail_clear.cpp

diff --git a/compiler-rt/test/memprof/TestCases/memprof_nonhistogram_tail_clear.cpp b/compiler-rt/test/memprof/TestCases/memprof_nonhistogram_tail_clear.cpp
new file mode 100644
index 0000000000000..95d1128748f05
--- /dev/null
+++ b/compiler-rt/test/memprof/TestCases/memprof_nonhistogram_tail_clear.cpp
@@ -0,0 +1,40 @@
+// The non-histogram shadow keeps one counter per MEM_GRANULARITY block. An
+// allocation smaller than SHADOW_GRANULARITY used to round its size down to 0,
+// which skipped ClearShadow entirely, so a recycled chunk reported the previous
+// allocation's access count.
+
+// RUN: %clangxx_memprof -O0 -mllvm -memprof-use-callbacks=true %s -o %t
+// RUN: %env_memprof_opts=print_text=1:log_path=stdout %run %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+  char *first = nullptr;
+  // A single call site, so both allocations merge into one MIB.
+  for (int i = 0; i < 2; ++i) {
+    char *p = (char *)malloc(4);
+    if (!p)
+      return 1;
+    if (i == 0) {
+      first = p;
+      for (int j = 0; j < 42; ++j)
+        p[0] = 'A';
+    } else if (p != first) {
+      // The stale count only manifests on a recycled chunk. memprof has no
+      // quarantine, so with no intervening allocation the just-freed chunk
+      // comes back; require it so the test cannot pass vacuously.
+      return 1;
+    }
+    free(p);
+  }
+
+  printf("Test completed successfully\n");
+  return 0;
+}
+
+// The second allocation never touches the chunk, so it must contribute 0:
+// 42 accesses over 2 allocations gives an average of 21.
+// CHECK: access_count (ave/min/max): 21.00 / 0 / 42
+// CHECK-NOT: access_count (ave/min/max): 42.00 / 42 / 42
+// CHECK: Test completed successfully



More information about the llvm-commits mailing list