138 changes: 138 additions & 0 deletions compiler-rt/lib/memprof/memprof_thread.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//===-- memprof_thread.h ---------------------------------------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file is a part of MemProfiler, a memory profiler.
//
// MemProf-private header for memprof_thread.cpp.
//===----------------------------------------------------------------------===//

#ifndef MEMPROF_THREAD_H
#define MEMPROF_THREAD_H

#include "memprof_allocator.h"
#include "memprof_internal.h"
#include "memprof_stats.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_libc.h"
#include "sanitizer_common/sanitizer_thread_registry.h"

namespace __sanitizer {
struct DTLS;
} // namespace __sanitizer

namespace __memprof {

const u32 kInvalidTid = 0xffffff; // Must fit into 24 bits.
const u32 kMaxNumberOfThreads = (1 << 22); // 4M

class MemprofThread;

// These objects are created for every thread and are never deleted,
// so we can find them by tid even if the thread is long dead.
struct MemprofThreadContext : public ThreadContextBase {
explicit MemprofThreadContext(int tid)
: ThreadContextBase(tid), announced(false),
destructor_iterations(GetPthreadDestructorIterations()), stack_id(0),
thread(nullptr) {}
bool announced;
u8 destructor_iterations;
u32 stack_id;
MemprofThread *thread;

void OnCreated(void *arg) override;
void OnFinished() override;

struct CreateThreadContextArgs {
MemprofThread *thread;
StackTrace *stack;
};
};

// MemprofThreadContext objects are never freed, so we need many of them.
COMPILER_CHECK(sizeof(MemprofThreadContext) <= 256);

// MemprofThread are stored in TSD and destroyed when the thread dies.
class MemprofThread {
public:
static MemprofThread *Create(thread_callback_t start_routine, void *arg,
u32 parent_tid, StackTrace *stack,
bool detached);
static void TSDDtor(void *tsd);
void Destroy();

struct InitOptions;
void Init(const InitOptions *options = nullptr);

thread_return_t ThreadStart(tid_t os_id,
atomic_uintptr_t *signal_thread_is_registered);

uptr stack_top();
uptr stack_bottom();
uptr stack_size();
uptr tls_begin() { return tls_begin_; }
uptr tls_end() { return tls_end_; }
DTLS *dtls() { return dtls_; }
u32 tid() { return context_->tid; }
MemprofThreadContext *context() { return context_; }
void set_context(MemprofThreadContext *context) { context_ = context; }

bool AddrIsInStack(uptr addr);

// True is this thread is currently unwinding stack (i.e. collecting a stack
// trace). Used to prevent deadlocks on platforms where libc unwinder calls
// malloc internally. See PR17116 for more details.
bool isUnwinding() const { return unwinding_; }
void setUnwinding(bool b) { unwinding_ = b; }

MemprofThreadLocalMallocStorage &malloc_storage() { return malloc_storage_; }
MemprofStats &stats() { return stats_; }

private:
// NOTE: There is no MemprofThread constructor. It is allocated
// via mmap() and *must* be valid in zero-initialized state.

void SetThreadStackAndTls(const InitOptions *options);

struct StackBounds {
uptr bottom;
uptr top;
};
StackBounds GetStackBounds() const;

MemprofThreadContext *context_;
thread_callback_t start_routine_;
void *arg_;

uptr stack_top_;
uptr stack_bottom_;

uptr tls_begin_;
uptr tls_end_;
DTLS *dtls_;

MemprofThreadLocalMallocStorage malloc_storage_;
MemprofStats stats_;
bool unwinding_;
};

// Returns a single instance of registry.
ThreadRegistry &memprofThreadRegistry();

// Must be called under ThreadRegistryLock.
MemprofThreadContext *GetThreadContextByTidLocked(u32 tid);

// Get the current thread. May return 0.
MemprofThread *GetCurrentThread();
void SetCurrentThread(MemprofThread *t);
u32 GetCurrentTidOrInvalid();

// Used to handle fork().
void EnsureMainThreadIDIsCorrect();
} // namespace __memprof

#endif // MEMPROF_THREAD_H
1 change: 1 addition & 0 deletions compiler-rt/lib/memprof/weak_symbols.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
___memprof_default_options
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,12 @@ void NORETURN ReportOutOfMemory(uptr requested_size, const StackTrace *stack) {
Die();
}

void NORETURN ReportRssLimitExceeded(const StackTrace *stack) {
{
ScopedAllocatorErrorReport report("rss-limit-exceeded", stack);
Report("ERROR: %s: allocator exceeded the RSS limit\n", SanitizerToolName);
}
Die();
}

} // namespace __sanitizer
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ void NORETURN ReportInvalidPosixMemalignAlignment(uptr alignment,
void NORETURN ReportAllocationSizeTooBig(uptr user_size, uptr max_size,
const StackTrace *stack);
void NORETURN ReportOutOfMemory(uptr requested_size, const StackTrace *stack);
void NORETURN ReportRssLimitExceeded(const StackTrace *stack);

} // namespace __sanitizer

Expand Down
5 changes: 3 additions & 2 deletions compiler-rt/lib/sanitizer_common/sanitizer_flags.inc
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ COMMON_FLAG(bool, print_summary, true,
"If false, disable printing error summaries in addition to error "
"reports.")
COMMON_FLAG(int, print_module_map, 0,
"OS X only (0 - don't print, 1 - print only once before process "
"exits, 2 - print after each report).")
"Print the process module map where supported (0 - don't print, "
"1 - print only once before process exits, 2 - print after each "
"report).")
COMMON_FLAG(bool, check_printf, true, "Check printf arguments.")
#define COMMON_FLAG_HANDLE_SIGNAL_HELP(signal) \
"Controls custom tool's " #signal " handler (0 - do not registers the " \
Expand Down
3 changes: 3 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -448,5 +448,8 @@ using namespace __sanitizer;
namespace __hwasan {
using namespace __sanitizer;
}
namespace __memprof {
using namespace __sanitizer;
}

#endif // SANITIZER_DEFS_H
3 changes: 3 additions & 0 deletions compiler-rt/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS)
if(COMPILER_RT_BUILD_PROFILE AND COMPILER_RT_HAS_PROFILE)
compiler_rt_test_runtime(profile)
endif()
if(COMPILER_RT_BUILD_MEMPROF)
compiler_rt_test_runtime(memprof)
endif()
if(COMPILER_RT_BUILD_XRAY)
compiler_rt_test_runtime(xray)
endif()
Expand Down
7 changes: 7 additions & 0 deletions compiler-rt/test/lit.common.cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
# If needed, add cflag for shadow scale.
if config.asan_shadow_scale != '':
config.target_cflags += " -mllvm -asan-mapping-scale=" + config.asan_shadow_scale
if config.memprof_shadow_scale != '':
config.target_cflags += " -mllvm -memprof-mapping-scale=" + config.memprof_shadow_scale

# BFD linker in 64-bit android toolchains fails to find libc++_shared.so, which
# is a transitive shared library dependency (via asan runtime).
Expand Down Expand Up @@ -554,6 +556,11 @@ def is_windows_lto_supported():
else:
config.available_features.add("shadow-scale-3")

if config.memprof_shadow_scale:
config.available_features.add("memprof-shadow-scale-%s" % config.memprof_shadow_scale)
else:
config.available_features.add("memprof-shadow-scale-3")

if config.expensive_checks:
config.available_features.add("expensive_checks")

Expand Down
1 change: 1 addition & 0 deletions compiler-rt/test/lit.common.configured.in
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set_default("compiler_rt_intercept_libdispatch", @COMPILER_RT_INTERCEPT_LIBDISPA
set_default("compiler_rt_libdir", "@COMPILER_RT_RESOLVED_LIBRARY_OUTPUT_DIR@")
set_default("emulator", "@COMPILER_RT_EMULATOR@")
set_default("asan_shadow_scale", "@COMPILER_RT_ASAN_SHADOW_SCALE@")
set_default("memprof_shadow_scale", "@COMPILER_RT_MEMPROF_SHADOW_SCALE@")
set_default("apple_platform", "osx")
set_default("apple_platform_min_deployment_target_flag", "-mmacosx-version-min")
set_default("sanitizer_can_use_cxxabi", @SANITIZER_CAN_USE_CXXABI_PYBOOL@)
Expand Down
60 changes: 60 additions & 0 deletions compiler-rt/test/memprof/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
set(MEMPROF_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})

set(MEMPROF_TESTSUITES)
set(MEMPROF_DYNAMIC_TESTSUITES)

macro(get_bits_for_arch arch bits)
if (${arch} MATCHES "x86_64")
set(${bits} 64)
else()
message(FATAL_ERROR "Unexpected target architecture: ${arch}")
endif()
endmacro()

set(MEMPROF_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
if(NOT COMPILER_RT_STANDALONE_BUILD)
list(APPEND MEMPROF_TEST_DEPS memprof)
if(COMPILER_RT_HAS_LLD AND TARGET lld)
list(APPEND MEMPROF_TEST_DEPS lld)
endif()
endif()
set(MEMPROF_DYNAMIC_TEST_DEPS ${MEMPROF_TEST_DEPS})

set(MEMPROF_TEST_ARCH ${MEMPROF_SUPPORTED_ARCH})

foreach(arch ${MEMPROF_TEST_ARCH})
set(MEMPROF_TEST_TARGET_ARCH ${arch})
string(TOLOWER "-${arch}-${OS_NAME}" MEMPROF_TEST_CONFIG_SUFFIX)
get_bits_for_arch(${arch} MEMPROF_TEST_BITS)
get_test_cc_for_arch(${arch} MEMPROF_TEST_TARGET_CC MEMPROF_TEST_TARGET_CFLAGS)
set(MEMPROF_TEST_DYNAMIC False)
string(TOUPPER ${arch} ARCH_UPPER_CASE)
set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}Config)
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py
)
list(APPEND MEMPROF_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})

string(TOLOWER "-${arch}-${OS_NAME}-dynamic" MEMPROF_TEST_CONFIG_SUFFIX)
set(MEMPROF_TEST_DYNAMIC True)
set(CONFIG_NAME ${ARCH_UPPER_CASE}${OS_NAME}DynamicConfig)
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py)
list(APPEND MEMPROF_DYNAMIC_TESTSUITES
${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
endforeach()

add_lit_testsuite(check-memprof "Running the MemProfiler tests"
${MEMPROF_TESTSUITES}
DEPENDS ${MEMPROF_TEST_DEPS})
set_target_properties(check-memprof PROPERTIES FOLDER "Compiler-RT Misc")

add_lit_testsuite(check-memprof-dynamic
"Running the MemProfiler tests with dynamic runtime"
${MEMPROF_DYNAMIC_TESTSUITES}
${exclude_from_check_all.g}
DEPENDS ${MEMPROF_DYNAMIC_TEST_DEPS})
set_target_properties(check-memprof-dynamic
PROPERTIES FOLDER "Compiler-RT Misc")
20 changes: 20 additions & 0 deletions compiler-rt/test/memprof/TestCases/atexit_stats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Check atexit option.

// RUN: %clangxx_memprof -O0 %s -o %t
// RUN: %env_memprof_opts=atexit=1 %run %t 2>&1 | FileCheck %s
// RUN: %env_memprof_opts=atexit=0 %run %t 2>&1 | FileCheck %s --check-prefix=NOATEXIT

// CHECK: MemProfiler exit stats:
// CHECK: Stats: {{[0-9]+}}M malloced ({{[0-9]+}}M for overhead) by {{[0-9]+}} calls
// CHECK: Stats: {{[0-9]+}}M realloced by {{[0-9]+}} calls
// CHECK: Stats: {{[0-9]+}}M freed by {{[0-9]+}} calls
// CHECK: Stats: {{[0-9]+}}M really freed by {{[0-9]+}} calls
// CHECK: Stats: {{[0-9]+}}M ({{[0-9]+}}M-{{[0-9]+}}M) mmaped; {{[0-9]+}} maps, {{[0-9]+}} unmaps
// CHECK: mallocs by size class:
// CHECK: Stats: malloc large: {{[0-9]+}}

// NOATEXIT-NOT: MemProfiler exit stats

int main() {
return 0;
}
12 changes: 12 additions & 0 deletions compiler-rt/test/memprof/TestCases/default_options.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// RUN: %clangxx_memprof -O2 %s -o %t && %run %t 2>&1 | FileCheck %s

const char *kMemProfDefaultOptions = "verbosity=1 help=1";

extern "C" const char *__memprof_default_options() {
// CHECK: Available flags for MemProfiler:
return kMemProfDefaultOptions;
}

int main() {
return 0;
}
14 changes: 14 additions & 0 deletions compiler-rt/test/memprof/TestCases/dump_process_map.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Check print_module_map option.

// RUN: %clangxx_memprof -O0 %s -o %t
// RUN: %env_memprof_opts=print_module_map=1 %run %t 2>&1 | FileCheck %s
// RUN: %env_memprof_opts=print_module_map=0 %run %t 2>&1 | FileCheck %s --check-prefix=NOMAP

// CHECK: Process memory map follows:
// CHECK: dump_process_map.cpp.tmp
// CHECK: End of process memory map.
// NOMAP-NOT: memory map

int main() {
return 0;
}
33 changes: 33 additions & 0 deletions compiler-rt/test/memprof/TestCases/free_hook_realloc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Check that free hook doesn't conflict with Realloc.
// RUN: %clangxx_memprof -O2 %s -o %t && %run %t 2>&1 | FileCheck %s

#include <sanitizer/allocator_interface.h>
#include <stdlib.h>
#include <unistd.h>

static void *glob_ptr;

extern "C" {
void __sanitizer_free_hook(const volatile void *ptr) {
if (ptr == glob_ptr) {
*(int *)ptr = 0;
write(1, "FreeHook\n", sizeof("FreeHook\n"));
}
}
}

int main() {
int *x = (int *)malloc(100);
x[0] = 42;
glob_ptr = x;
int *y = (int *)realloc(x, 200);
// Verify that free hook was called and didn't spoil the memory.
if (y[0] != 42) {
_exit(1);
}
write(1, "Passed\n", sizeof("Passed\n"));
free(y);
// CHECK: FreeHook
// CHECK: Passed
return 0;
}
25 changes: 25 additions & 0 deletions compiler-rt/test/memprof/TestCases/interface_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Check that user may include MemProf interface header.
// Also check that interfaces declared in the sanitizer's allocator_interface
// are defined for MemProf.
// RUN: %clang_memprof %s -o %t -DMEMPROF && %run %t
// RUN: %clang_memprof -x c %s -o %t -DMEMPROF && %run %t
// RUN: %clang %s -pie -o %t && %run %t
// RUN: %clang -x c %s -pie -o %t && %run %t
#include <sanitizer/allocator_interface.h>
#include <sanitizer/memprof_interface.h>
#include <stdlib.h>

int main() {
int *p = (int *)malloc(10 * sizeof(int));
#ifdef MEMPROF
__sanitizer_get_estimated_allocated_size(8);
__sanitizer_get_ownership(p);
__sanitizer_get_allocated_size(p);
__sanitizer_get_current_allocated_bytes();
__sanitizer_get_heap_size();
__sanitizer_get_free_bytes();
__sanitizer_get_unmapped_bytes();
// malloc and free hooks are tested by the malloc_hook.cpp test.
#endif
return 0;
}
34 changes: 34 additions & 0 deletions compiler-rt/test/memprof/TestCases/log_path_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// The for loop in the backticks below requires bash.
// REQUIRES: shell
//
// RUN: %clangxx_memprof %s -o %t

// Regular run.
// RUN: %run %t 2> %t.out
// RUN: FileCheck %s --check-prefix=CHECK-GOOD < %t.out

// Good log_path.
// RUN: rm -f %t.log.*
// RUN: %env_memprof_opts=log_path=%t.log %run %t 2> %t.out
// RUN: FileCheck %s --check-prefix=CHECK-GOOD < %t.log.*

// Invalid log_path.
// RUN: %env_memprof_opts=log_path=/dev/null/INVALID not %run %t 2> %t.out
// RUN: FileCheck %s --check-prefix=CHECK-INVALID < %t.out

// Too long log_path.
// RUN: %env_memprof_opts=log_path=`for((i=0;i<10000;i++)); do echo -n $i; done` \
// RUN: not %run %t 2> %t.out
// RUN: FileCheck %s --check-prefix=CHECK-LONG < %t.out

#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
char *x = (char *)malloc(10);
memset(x, 0, 10);
free(x);
return 0;
}
// CHECK-GOOD: Memory allocation stack id
// CHECK-INVALID: ERROR: Can't open file: /dev/null/INVALID
// CHECK-LONG: ERROR: Path is too long: 01234
23 changes: 23 additions & 0 deletions compiler-rt/test/memprof/TestCases/malloc-size-too-big.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: %clangxx_memprof -O0 %s -o %t
// RUN: %env_memprof_opts=allocator_may_return_null=0 not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-SUMMARY
// RUN: %env_memprof_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NULL
// Test print_summary
// RUN: %env_memprof_opts=allocator_may_return_null=0:print_summary=0 not %run %t 2>&1 | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-NOSUMMARY

#include <stdio.h>
#include <stdlib.h>

static const size_t kMaxAllowedMallocSizePlusOne = (1ULL << 40) + 1;
int main() {
void *p = malloc(kMaxAllowedMallocSizePlusOne);
// CHECK: {{ERROR: MemProfiler: requested allocation size .* exceeds maximum supported size}}
// CHECK: {{#0 0x.* in .*malloc}}
// CHECK: {{#1 0x.* in main .*malloc-size-too-big.cpp:}}[[@LINE-3]]
// CHECK-SUMMARY: SUMMARY: MemProfiler: allocation-size-too-big
// CHECK-NOSUMMARY-NOT: SUMMARY:

printf("malloc returned: %zu\n", (size_t)p);
// CHECK-NULL: malloc returned: 0

return 0;
}
58 changes: 58 additions & 0 deletions compiler-rt/test/memprof/TestCases/malloc_hook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Check that MemProf correctly handles malloc and free hooks.
// RUN: %clangxx_memprof -O2 %s -o %t && %run %t 2>&1 | FileCheck %s

#include <sanitizer/allocator_interface.h>
#include <stdlib.h>
#include <unistd.h>

extern "C" {
const volatile void *global_ptr;

#define WRITE(s) write(1, s, sizeof(s))

// Note: avoid calling functions that allocate memory in malloc/free
// to avoid infinite recursion.
void __sanitizer_malloc_hook(const volatile void *ptr, size_t sz) {
if (__sanitizer_get_ownership(ptr) && sz == 4) {
WRITE("MallocHook\n");
global_ptr = ptr;
}
}
void __sanitizer_free_hook(const volatile void *ptr) {
if (__sanitizer_get_ownership(ptr) && ptr == global_ptr)
WRITE("FreeHook\n");
}
} // extern "C"

volatile int *x;

void MallocHook1(const volatile void *ptr, size_t sz) { WRITE("MH1\n"); }
void MallocHook2(const volatile void *ptr, size_t sz) { WRITE("MH2\n"); }
void FreeHook1(const volatile void *ptr) { WRITE("FH1\n"); }
void FreeHook2(const volatile void *ptr) { WRITE("FH2\n"); }
// Call this function with uninitialized arguments to poison
// TLS shadow for function parameters before calling operator
// new and, eventually, user-provided hook.
__attribute__((noinline)) void allocate(int *unused1, int *unused2) {
x = new int;
}

int main() {
__sanitizer_install_malloc_and_free_hooks(MallocHook1, FreeHook1);
__sanitizer_install_malloc_and_free_hooks(MallocHook2, FreeHook2);
int *undef1, *undef2;
allocate(undef1, undef2);
// CHECK: MallocHook
// CHECK: MH1
// CHECK: MH2
// Check that malloc hook was called with correct argument.
if (global_ptr != (void *)x) {
_exit(1);
}
*x = 0;
delete x;
// CHECK: FreeHook
// CHECK: FH1
// CHECK: FH2
return 0;
}
10 changes: 10 additions & 0 deletions compiler-rt/test/memprof/TestCases/mem_info_cache_entries.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Check mem_info_cache_entries option.

// RUN: %clangxx_memprof -O0 %s -o %t && %env_memprof_opts=mem_info_cache_entries=15:print_mem_info_cache_miss_rate=1:print_mem_info_cache_miss_rate_details=1 %run %t 2>&1 | FileCheck %s

// CHECK: Set 14 miss rate: 0 / {{.*}} = 0.00%
// CHECK-NOT: Set

int main() {
return 0;
}
7 changes: 7 additions & 0 deletions compiler-rt/test/memprof/TestCases/memprof_options-help.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// RUN: %clangxx_memprof -O0 %s -o %t && %env_memprof_opts=help=1 %run %t 2>&1 | FileCheck %s

int main() {
}

// CHECK: Available flags for MemProfiler:
// CHECK-DAG: atexit
14 changes: 14 additions & 0 deletions compiler-rt/test/memprof/TestCases/print_miss_rate.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Check print_mem_info_cache_miss_rate and
// print_mem_info_cache_miss_rate_details options.

// RUN: %clangxx_memprof -O0 %s -o %t
// RUN: %env_memprof_opts=print_mem_info_cache_miss_rate=1 %run %t 2>&1 | FileCheck %s
// RUN: %env_memprof_opts=print_mem_info_cache_miss_rate=1:print_mem_info_cache_miss_rate_details=1 %run %t 2>&1 | FileCheck %s --check-prefix=DETAILS

// CHECK: Overall miss rate: 0 / {{.*}} = 0.00%
// DETAILS: Set 0 miss rate: 0 / {{.*}} = 0.00%
// DETAILS: Set 16380 miss rate: 0 / {{.*}} = 0.00%

int main() {
return 0;
}
21 changes: 21 additions & 0 deletions compiler-rt/test/memprof/TestCases/realloc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %clangxx_memprof -O0 %s -o %t
// Default is true (free on realloc to 0 size)
// RUN: %run %t 2>&1 | FileCheck %s
// RUN: %env_memprof_opts=allocator_frees_and_returns_null_on_realloc_zero=true %run %t 2>&1 | FileCheck %s
// RUN: %env_memprof_opts=allocator_frees_and_returns_null_on_realloc_zero=false %run %t 2>&1 | FileCheck %s --check-prefix=NO-FREE

#include <stdio.h>
#include <stdlib.h>

int main() {
void *p = malloc(42);
p = realloc(p, 0);
if (p) {
// NO-FREE: Allocated something on realloc(p, 0)
fprintf(stderr, "Allocated something on realloc(p, 0)\n");
} else {
// CHECK: realloc(p, 0) returned nullptr
fprintf(stderr, "realloc(p, 0) returned nullptr\n");
}
free(p);
}
112 changes: 112 additions & 0 deletions compiler-rt/test/memprof/TestCases/stress_dtls.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// REQUIRES: memprof-64-bits
// Stress test dynamic TLS + dlopen + threads.
//
// RUN: %clangxx_memprof -x c -DSO_NAME=f0 %s -shared -o %t-f0.so -fPIC
// RUN: %clangxx_memprof -x c -DSO_NAME=f1 %s -shared -o %t-f1.so -fPIC
// RUN: %clangxx_memprof -x c -DSO_NAME=f2 %s -shared -o %t-f2.so -fPIC
// RUN: %clangxx_memprof %s -ldl -pthread -o %t
// RUN: %run %t 0 3
// RUN: %run %t 2 3
// RUN: %env_memprof_opts=verbosity=2 %run %t 10 2 2>&1 | FileCheck %s
// RUN: %env_memprof_opts=verbosity=2:intercept_tls_get_addr=1 %run %t 10 2 2>&1 | FileCheck %s
// RUN: %env_memprof_opts=verbosity=2:intercept_tls_get_addr=0 %run %t 10 2 2>&1 | FileCheck %s --check-prefix=CHECK0
// CHECK: __tls_get_addr
// CHECK: Creating thread 0
// CHECK: __tls_get_addr
// CHECK: Creating thread 1
// CHECK: __tls_get_addr
// CHECK: Creating thread 2
// CHECK: __tls_get_addr
// CHECK: Creating thread 3
// CHECK: __tls_get_addr
// Make sure that TLS slots don't leak
// CHECK-NOT: num_live_dtls 5
//
// CHECK0-NOT: __tls_get_addr
/*
cc=your-compiler
$cc stress_dtls.c -pthread -ldl
for((i=0;i<100;i++)); do
$cc -fPIC -shared -DSO_NAME=f$i -o a.out-f$i.so stress_dtls.c;
done
./a.out 2 4 # <<<<<< 2 threads, 4 libs
./a.out 3 50 # <<<<<< 3 threads, 50 libs
*/
#ifndef SO_NAME
#define _GNU_SOURCE
#include <assert.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

typedef void **(*f_t)();

__thread int my_tls;

#define MAX_N_FUNCTIONS 1000
f_t Functions[MAX_N_FUNCTIONS];

void *PrintStuff(void *unused) {
uintptr_t stack;
// fprintf(stderr, "STACK: %p TLS: %p SELF: %p\n", &stack, &my_tls,
// (void *)pthread_self());
int i;
for (i = 0; i < MAX_N_FUNCTIONS; i++) {
if (!Functions[i])
break;
uintptr_t dtls = (uintptr_t)Functions[i]();
fprintf(stderr, " dtls[%03d]: %lx\n", i, dtls);
*(long *)dtls = 42; // check that this is writable.
}
return NULL;
}

int main(int argc, char *argv[]) {
int num_threads = 1;
int num_libs = 1;
if (argc >= 2)
num_threads = atoi(argv[1]);
if (argc >= 3)
num_libs = atoi(argv[2]);
assert(num_libs <= MAX_N_FUNCTIONS);

int lib;
for (lib = 0; lib < num_libs; lib++) {
char buf[4096];
snprintf(buf, sizeof(buf), "%s-f%d.so", argv[0], lib);
void *handle = dlopen(buf, RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
snprintf(buf, sizeof(buf), "f%d", lib);
Functions[lib] = (f_t)dlsym(handle, buf);
if (!Functions[lib]) {
fprintf(stderr, "%s\n", dlerror());
exit(1);
}
fprintf(stderr, "LIB[%03d] %s: %p\n", lib, buf, Functions[lib]);
PrintStuff(0);

int i;
for (i = 0; i < num_threads; i++) {
pthread_t t;
fprintf(stderr, "Creating thread %d\n", i);
pthread_create(&t, 0, PrintStuff, 0);
pthread_join(t, 0);
}
}
return 0;
}
#else // SO_NAME
#ifndef DTLS_SIZE
#define DTLS_SIZE (1 << 17)
#endif
__thread void *huge_thread_local_array[DTLS_SIZE];
void **SO_NAME() {
return &huge_thread_local_array[0];
}
#endif
38 changes: 38 additions & 0 deletions compiler-rt/test/memprof/TestCases/test_malloc_load_store.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Check profile with a single malloc call and set of loads and stores. Ensures
// we get the same profile regardless of whether the memory is deallocated
// before exit.

// RUN: %clangxx_memprof -O0 %s -o %t
// RUN: %env_memprof_opts= %run %t 2>&1 | FileCheck %s

// RUN: %clangxx_memprof -DFREE -O0 %s -o %t
// RUN: %env_memprof_opts= %run %t 2>&1 | FileCheck %s

// This is actually:
// Memory allocation stack id = STACKID
// alloc_count 1, size (ave/min/max) 40.00 / 40 / 40
// but we need to look for them in the same CHECK to get the correct STACKID.
// CHECK: Memory allocation stack id = [[STACKID:[0-9]+]]{{[[:space:]].*}}alloc_count 1, size (ave/min/max) 40.00 / 40 / 40
// CHECK-NEXT: access_count (ave/min/max): 20.00 / 20 / 20
// CHECK-NEXT: lifetime (ave/min/max): [[AVELIFETIME:[0-9]+]].00 / [[AVELIFETIME]] / [[AVELIFETIME]]
// CHECK-NEXT: num migrated: 0, num lifetime overlaps: 0, num same alloc cpu: 0, num same dealloc_cpu: 0
// CHECK: Stack for id [[STACKID]]:
// CHECK-NEXT: #0 {{.*}} in malloc
// CHECK-NEXT: #1 {{.*}} in main {{.*}}:[[@LINE+6]]

#include <stdio.h>
#include <stdlib.h>

int main() {
int *p = (int *)malloc(10 * sizeof(int));
for (int i = 0; i < 10; i++)
p[i] = i;
int j = 0;
for (int i = 0; i < 10; i++)
j += p[i];
#ifdef FREE
free(p);
#endif

return 0;
}
48 changes: 48 additions & 0 deletions compiler-rt/test/memprof/TestCases/test_memintrin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Check profile with calls to memory intrinsics.

// RUN: %clangxx_memprof -O0 %s -o %t && %run %t 2>&1 | FileCheck %s

// This is actually:
// Memory allocation stack id = STACKIDP
// alloc_count 1, size (ave/min/max) 40.00 / 40 / 40
// access_count (ave/min/max): 3.00 / 3 / 3
// but we need to look for them in the same CHECK to get the correct STACKIDP.
// CHECK-DAG: Memory allocation stack id = [[STACKIDP:[0-9]+]]{{[[:space:]].*}} alloc_count 1, size (ave/min/max) 40.00 / 40 / 40{{[[:space:]].*}} access_count (ave/min/max): 3.00 / 3 / 3
//
// This is actually:
// Memory allocation stack id = STACKIDQ
// alloc_count 1, size (ave/min/max) 20.00 / 20 / 20
// access_count (ave/min/max): 2.00 / 2 / 2
// but we need to look for them in the same CHECK to get the correct STACKIDQ.
// CHECK-DAG: Memory allocation stack id = [[STACKIDQ:[0-9]+]]{{[[:space:]].*}} alloc_count 1, size (ave/min/max) 20.00 / 20 / 20{{[[:space:]].*}} access_count (ave/min/max): 2.00 / 2 / 2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
// This is actually:
// Stack for id STACKIDP:
// #0 {{.*}} in operator new
// #1 {{.*}} in main {{.*}}:@LINE+1
// but we need to look for them in the same CHECK-DAG.
// CHECK-DAG: Stack for id [[STACKIDP]]:{{[[:space:]].*}} #0 {{.*}} in operator new{{.*[[:space:]].*}} #1 {{.*}} in main {{.*}}:[[@LINE+1]]
int *p = new int[10];

// This is actually:
// Stack for id STACKIDQ:
// #0 {{.*}} in operator new
// #1 {{.*}} in main {{.*}}:@LINE+1
// but we need to look for them in the same CHECK-DAG.
// CHECK-DAG: Stack for id [[STACKIDQ]]:{{[[:space:]].*}} #0 {{.*}} in operator new{{.*[[:space:]].*}} #1 {{.*}} in main {{.*}}:[[@LINE+1]]
int *q = new int[5];

memset(p, 1, 10);
memcpy(q, p, 5);
int x = memcmp(p, q, 5);

delete p;
delete q;

return x;
}
42 changes: 42 additions & 0 deletions compiler-rt/test/memprof/TestCases/test_new_load_store.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Check profile with a single new call and set of loads and stores. Ensures
// we get the same profile regardless of whether the memory is deallocated
// before exit.

// RUN: %clangxx_memprof -O0 %s -o %t
// RUN: %env_memprof_opts= %run %t 2>&1 | FileCheck %s

// RUN: %clangxx_memprof -DFREE -O0 %s -o %t
// RUN: %env_memprof_opts= %run %t 2>&1 | FileCheck %s

// Try again with callbacks instead of inline sequences
// RUN: %clangxx_memprof -mllvm -memprof-use-callbacks -O0 %s -o %t
// RUN: %env_memprof_opts= %run %t 2>&1 | FileCheck %s

// This is actually:
// Memory allocation stack id = STACKID
// alloc_count 1, size (ave/min/max) 40.00 / 40 / 40
// but we need to look for them in the same CHECK to get the correct STACKID.
// CHECK: Memory allocation stack id = [[STACKID:[0-9]+]]{{[[:space:]].*}}alloc_count 1, size (ave/min/max) 40.00 / 40 / 40
// CHECK-NEXT: access_count (ave/min/max): 20.00 / 20 / 20
// CHECK-NEXT: lifetime (ave/min/max): [[AVELIFETIME:[0-9]+]].00 / [[AVELIFETIME]] / [[AVELIFETIME]]
// CHECK-NEXT: num migrated: 0, num lifetime overlaps: 0, num same alloc cpu: 0, num same dealloc_cpu: 0
// CHECK: Stack for id [[STACKID]]:
// CHECK-NEXT: #0 {{.*}} in operator new
// CHECK-NEXT: #1 {{.*}} in main {{.*}}:[[@LINE+6]]

#include <stdio.h>
#include <stdlib.h>

int main() {
int *p = new int[10];
for (int i = 0; i < 10; i++)
p[i] = i;
int j = 0;
for (int i = 0; i < 10; i++)
j += p[i];
#ifdef FREE
delete p;
#endif

return 0;
}
31 changes: 31 additions & 0 deletions compiler-rt/test/memprof/TestCases/test_terse.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Check terse format profile with a single malloc call and set of loads and
// stores. Ensures we get the same profile regardless of whether the memory is
// deallocated before exit.

// RUN: %clangxx_memprof -O0 %s -o %t
// RUN: %env_memprof_opts=print_terse=1 %run %t 2>&1 | FileCheck %s

// RUN: %clangxx_memprof -DFREE -O0 %s -o %t
// RUN: %env_memprof_opts=print_terse=1 %run %t 2>&1 | FileCheck %s

// CHECK: MIB:[[STACKID:[0-9]+]]/1/40.00/40/40/20.00/20/20/[[AVELIFETIME:[0-9]+]].00/[[AVELIFETIME]]/[[AVELIFETIME]]/0/0/0/0
// CHECK: Stack for id [[STACKID]]:
// CHECK-NEXT: #0 {{.*}} in operator new
// CHECK-NEXT: #1 {{.*}} in main {{.*}}:[[@LINE+6]]

#include <stdio.h>
#include <stdlib.h>

int main() {
int *p = new int[10];
for (int i = 0; i < 10; i++)
p[i] = i;
int j = 0;
for (int i = 0; i < 10; i++)
j += p[i];
#ifdef FREE
delete p;
#endif

return 0;
}
30 changes: 30 additions & 0 deletions compiler-rt/test/memprof/TestCases/unaligned_loads_and_stores.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// RUN: %clangxx_memprof -O0 %s -o %t && %run %t 2>&1 | FileCheck %s

// This is actually:
// Memory allocation stack id = STACKID
// alloc_count 1, size (ave/min/max) 128.00 / 128 / 128
// but we need to look for them in the same CHECK to get the correct STACKID.
// CHECK: Memory allocation stack id = [[STACKID:[0-9]+]]{{[[:space:]].*}}alloc_count 1, size (ave/min/max) 128.00 / 128 / 128
// CHECK-NEXT: access_count (ave/min/max): 7.00 / 7 / 7

#include <sanitizer/memprof_interface.h>

#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
// CHECK: Stack for id [[STACKID]]:
// CHECK-NEXT: #0 {{.*}} in operator new[](unsigned long)
// CHECK-NEXT: #1 {{.*}} in main {{.*}}:[[@LINE+1]]
char *x = new char[128];
memset(x, 0xab, 128);
__sanitizer_unaligned_load16(x + 15);
__sanitizer_unaligned_load32(x + 15);
__sanitizer_unaligned_load64(x + 15);

__sanitizer_unaligned_store16(x + 15, 0);
__sanitizer_unaligned_store32(x + 15, 0);
__sanitizer_unaligned_store64(x + 15, 0);

delete[] x;
return 0;
}
103 changes: 103 additions & 0 deletions compiler-rt/test/memprof/lit.cfg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# -*- Python -*-

import os
import platform
import re

import lit.formats

# Get shlex.quote if available (added in 3.3), and fall back to pipes.quote if
# it's not available.
try:
import shlex
sh_quote = shlex.quote
except:
import pipes
sh_quote = pipes.quote

def get_required_attr(config, attr_name):
attr_value = getattr(config, attr_name, None)
if attr_value == None:
lit_config.fatal(
"No attribute %r in test configuration! You may need to run "
"tests from your build directory or add this attribute "
"to lit.site.cfg.py " % attr_name)
return attr_value

# Setup config name.
config.name = 'MemProfiler' + config.name_suffix

# Platform-specific default MEMPROF_OPTIONS for lit tests.
default_memprof_opts = list(config.default_sanitizer_opts)

default_memprof_opts_str = ':'.join(default_memprof_opts)
if default_memprof_opts_str:
config.environment['MEMPROF_OPTIONS'] = default_memprof_opts_str
config.substitutions.append(('%env_memprof_opts=',
'env MEMPROF_OPTIONS=' + default_memprof_opts_str))

# Setup source root.
config.test_source_root = os.path.dirname(__file__)

libdl_flag = '-ldl'

# Setup default compiler flags used with -fmemory-profile option.
# FIXME: Review the set of required flags and check if it can be reduced.
target_cflags = [get_required_attr(config, 'target_cflags')]
target_cxxflags = config.cxx_mode_flags + target_cflags
clang_memprof_static_cflags = (['-fmemory-profile',
'-mno-omit-leaf-frame-pointer',
'-fno-omit-frame-pointer',
'-fno-optimize-sibling-calls'] +
config.debug_info_flags + target_cflags)
clang_memprof_static_cxxflags = config.cxx_mode_flags + clang_memprof_static_cflags

memprof_dynamic_flags = []
if config.memprof_dynamic:
memprof_dynamic_flags = ['-shared-libsan']
config.available_features.add('memprof-dynamic-runtime')
else:
config.available_features.add('memprof-static-runtime')
clang_memprof_cflags = clang_memprof_static_cflags + memprof_dynamic_flags
clang_memprof_cxxflags = clang_memprof_static_cxxflags + memprof_dynamic_flags

def build_invocation(compile_flags):
return ' ' + ' '.join([config.clang] + compile_flags) + ' '

config.substitutions.append( ("%clang ", build_invocation(target_cflags)) )
config.substitutions.append( ("%clangxx ", build_invocation(target_cxxflags)) )
config.substitutions.append( ("%clang_memprof ", build_invocation(clang_memprof_cflags)) )
config.substitutions.append( ("%clangxx_memprof ", build_invocation(clang_memprof_cxxflags)) )
if config.memprof_dynamic:
shared_libmemprof_path = os.path.join(config.compiler_rt_libdir, 'libclang_rt.memprof{}.so'.format(config.target_suffix))
config.substitutions.append( ("%shared_libmemprof", shared_libmemprof_path) )
config.substitutions.append( ("%clang_memprof_static ", build_invocation(clang_memprof_static_cflags)) )
config.substitutions.append( ("%clangxx_memprof_static ", build_invocation(clang_memprof_static_cxxflags)) )

# Some tests uses C++11 features such as lambdas and need to pass -std=c++11.
config.substitutions.append(("%stdcxx11 ", '-std=c++11 '))

config.substitutions.append( ("%libdl", libdl_flag) )

config.available_features.add('memprof-' + config.bits + '-bits')

config.available_features.add('fast-unwinder-works')

# Set LD_LIBRARY_PATH to pick dynamic runtime up properly.
new_ld_library_path = os.path.pathsep.join(
(config.compiler_rt_libdir, config.environment.get('LD_LIBRARY_PATH', '')))
config.environment['LD_LIBRARY_PATH'] = new_ld_library_path

# Default test suffixes.
config.suffixes = ['.c', '.cpp']

config.substitutions.append(('%fPIC', '-fPIC'))
config.substitutions.append(('%fPIE', '-fPIE'))
config.substitutions.append(('%pie', '-pie'))

# Only run the tests on supported OSs.
if config.host_os not in ['Linux']:
config.unsupported = True

if not config.parallelism_group:
config.parallelism_group = 'shadow-memory'
15 changes: 15 additions & 0 deletions compiler-rt/test/memprof/lit.site.cfg.py.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@LIT_SITE_CFG_IN_HEADER@

# Tool-specific config options.
config.name_suffix = "@MEMPROF_TEST_CONFIG_SUFFIX@"
config.target_cflags = "@MEMPROF_TEST_TARGET_CFLAGS@"
config.clang = "@MEMPROF_TEST_TARGET_CC@"
config.bits = "@MEMPROF_TEST_BITS@"
config.memprof_dynamic = @MEMPROF_TEST_DYNAMIC@
config.target_arch = "@MEMPROF_TEST_TARGET_ARCH@"

# Load common config for all compiler-rt lit tests.
lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured")

# Load tool-specific config that would do the real work.
lit_config.load_config(config, "@MEMPROF_LIT_SOURCE_DIR@/lit.cfg.py")