diff --git a/tcmalloc/BUILD b/tcmalloc/BUILD index 87a7fabdd..2af8229b0 100644 --- a/tcmalloc/BUILD +++ b/tcmalloc/BUILD @@ -108,6 +108,26 @@ cc_library( alwayslink = 1, ) +cc_library( + name = "tcmalloc_internal_methods_only", + srcs = [ + "tcmalloc.cc", + "tcmalloc.h", + ], + copts = TCMALLOC_DEFAULT_COPTS + [ + "-DTCMALLOC_INTERNAL_METHODS_ONLY", + ], + linkstatic = 1, + visibility = ["//tcmalloc:__subpackages__"], + deps = tcmalloc_deps + [ + ":common_8k_pages", + "//tcmalloc/internal:allocation_guard", + "//tcmalloc/internal:overflow", + "//tcmalloc/internal:page_size", + ], + alwayslink = 1, +) + # Provides tcmalloc always; use per-thread mode. cc_library( name = "tcmalloc_deprecated_perthread", diff --git a/tcmalloc/tcmalloc.cc b/tcmalloc/tcmalloc.cc index 6cecd0217..c4f93ed0d 100644 --- a/tcmalloc/tcmalloc.cc +++ b/tcmalloc/tcmalloc.cc @@ -1094,7 +1094,12 @@ using tcmalloc::tcmalloc_internal::MallocAlignPolicy; using tcmalloc::tcmalloc_internal::MultiplyOverflow; // depends on TCMALLOC_HAVE_STRUCT_MALLINFO, so needs to come after that. +#ifndef TCMALLOC_INTERNAL_METHODS_ONLY #include "tcmalloc/libc_override.h" +#else +#define TCMALLOC_ALIAS(tc_fn) \ + __attribute__((alias(#tc_fn), visibility("default"))) +#endif // !TCMALLOC_INTERNAL_METHODS_ONLY extern "C" ABSL_CACHELINE_ALIGNED void* TCMallocInternalMalloc( size_t size) noexcept { @@ -1110,6 +1115,7 @@ extern "C" ABSL_CACHELINE_ALIGNED void* TCMallocInternalNewNothrow( return fast_alloc(CppPolicy().Nothrow(), size); } +#ifndef TCMALLOC_INTERNAL_METHODS_ONLY extern "C" ABSL_CACHELINE_ALIGNED ABSL_ATTRIBUTE_SECTION(google_malloc) tcmalloc::sized_ptr_t tcmalloc_size_returning_operator_new(size_t size) { return fast_alloc(CppPolicy().SizeReturning(), size); @@ -1146,6 +1152,7 @@ extern "C" ABSL_CACHELINE_ALIGNED ABSL_ATTRIBUTE_SECTION(google_malloc) .SizeReturning(), size); } +#endif // !TCMALLOC_INTERNAL_METHODS_ONLY extern "C" ABSL_CACHELINE_ALIGNED void* TCMallocInternalMemalign( size_t align, size_t size) noexcept { @@ -1540,6 +1547,7 @@ static TCMallocGuard module_enter_exit_hook; } // namespace tcmalloc GOOGLE_MALLOC_SECTION_END +#ifndef TCMALLOC_INTERNAL_METHODS_ONLY ABSL_CACHELINE_ALIGNED void* operator new( size_t size, tcmalloc::hot_cold_t hot_cold) noexcept(false) { if (hot_cold >= Parameters::min_hot_access_hint()) { @@ -1623,3 +1631,4 @@ ABSL_CACHELINE_ALIGNED void* operator new[]( size); } } +#endif // !TCMALLOC_INTERNAL_METHODS_ONLY diff --git a/tcmalloc/testing/BUILD b/tcmalloc/testing/BUILD index cac898c3f..f5f5d1940 100644 --- a/tcmalloc/testing/BUILD +++ b/tcmalloc/testing/BUILD @@ -903,3 +903,18 @@ create_tcmalloc_testsuite( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "parallel_test", + srcs = ["parallel_test.cc"], + copts = TCMALLOC_DEFAULT_COPTS, + malloc = "//tcmalloc/internal:system_malloc", + tags = ["nomsan"], + deps = [ + "//tcmalloc:tcmalloc_internal_methods_only", # buildcleaner: keep + "@com_google_absl//absl/base:config", + "@com_google_absl//absl/random", + "@com_google_absl//absl/time", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/tcmalloc/testing/parallel_test.cc b/tcmalloc/testing/parallel_test.cc new file mode 100644 index 000000000..d4e3a0a5c --- /dev/null +++ b/tcmalloc/testing/parallel_test.cc @@ -0,0 +1,94 @@ +// Copyright 2023 The TCMalloc Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/random/random.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +extern "C" { + +void* TCMallocInternalNew(size_t); +void TCMallocInternalDelete(void*); +void TCMallocInternalDeleteSized(void*, size_t); + +} // extern "C" + +namespace tcmalloc { +namespace { + +struct Allocator { + Allocator(std::atomic& stop, bool do_sized_delete) + : stop(stop), do_sized_delete(do_sized_delete) {} + + void operator()() { + const int kNumAllocations = 65536; + std::vector v; + v.reserve(kNumAllocations); + + absl::BitGen rng; + + while (!stop.load(std::memory_order_acquire)) { + const size_t size = 1u << absl::LogUniform(rng, 1, 12); + for (int i = 0; i < kNumAllocations; ++i) { + v.push_back(TCMallocInternalNew(size)); + } + + for (void* ptr : v) { + if (do_sized_delete) { + TCMallocInternalDeleteSized(ptr, size); + } else { + TCMallocInternalDelete(ptr); + } + } + v.clear(); + } + } + + std::atomic& stop; + bool do_sized_delete; +}; + +TEST(ParallelTest, Stable) { +#ifdef ABSL_HAVE_MEMORY_SANITIZER + // TODO(b/148986845): Enable this. + GTEST_SKIP() << "Skipping under msan."; +#endif +#ifdef ABSL_HAVE_THREAD_SANITIZER + // TODO(b/274996721): Enable this when Span::nonempty_index_ does not + // conflict with other bitfields. + GTEST_SKIP() << "Skipping under tsan."; +#endif + std::atomic stop{false}; + Allocator a1(stop, /*do_sized_delete=*/true), + a2(stop, /*do_sized_delete=*/true), a3(stop, /*do_sized_delete=*/false); + + std::thread t1(a1), t2(a2), t3(a3); + + absl::SleepFor(absl::Seconds(1)); + + stop.store(true, std::memory_order_release); + t1.join(); + t2.join(); + t3.join(); +} + +} // namespace +} // namespace tcmalloc