Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(254)

Unified Diff: base/allocator/allocator_shim.cc

Issue 1675143004: Allocator shim skeleton + Linux impl behind a build flag (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@shim_exp_flag
Patch Set: Make self the 1st arg Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « base/allocator/allocator_shim.h ('k') | base/allocator/allocator_shim_default_dispatch_to_glibc.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: base/allocator/allocator_shim.cc
diff --git a/base/allocator/allocator_shim.cc b/base/allocator/allocator_shim.cc
new file mode 100644
index 0000000000000000000000000000000000000000..cc5097ec543cd589afc7e306b6963481b46be2ba
--- /dev/null
+++ b/base/allocator/allocator_shim.cc
@@ -0,0 +1,246 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/allocator/allocator_shim.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <new>
+
+#include "base/atomicops.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/threading/platform_thread.h"
+#include "build/build_config.h"
+
+// No calls to malloc / new in this file. They would would cause re-entrancy of
+// the shim, which is hard to deal with. Keep this code as simple as possible
+// and don't use any external C++ object here, not even //base ones. Even if
+// they are safe to use today, in future they might be refactored.
+
+namespace {
+
+using namespace base;
+
+subtle::AtomicWord g_chain_head = reinterpret_cast<subtle::AtomicWord>(
+ &allocator::AllocatorDispatch::default_dispatch);
+
+bool g_call_new_handler_on_malloc_failure = false;
+subtle::Atomic32 g_new_handler_lock = 0;
+
+// In theory this should be just base::ThreadChecker. But we can't afford
+// the luxury of a LazyInstance<ThreadChecker> here as it would cause a new().
+bool CalledOnValidThread() {
+ using subtle::Atomic32;
+ const Atomic32 kInvalidTID = static_cast<Atomic32>(kInvalidThreadId);
+ static Atomic32 g_tid = kInvalidTID;
+ Atomic32 cur_tid = static_cast<Atomic32>(PlatformThread::CurrentId());
+ Atomic32 prev_tid =
+ subtle::NoBarrier_CompareAndSwap(&g_tid, kInvalidTID, cur_tid);
+ return prev_tid == kInvalidTID || prev_tid == cur_tid;
+}
+
+inline size_t GetPageSize() {
+ static size_t pagesize = 0;
+ if (!pagesize)
+ pagesize = sysconf(_SC_PAGESIZE);
+ return pagesize;
+}
+
+// Calls the std::new handler thread-safely. Returns true if a new_handler was
+// set and called, false if no new_handler was set.
+bool CallNewHandler() {
+ // TODO(primiano): C++11 has introduced ::get_new_handler() which is supposed
+ // to be thread safe and would avoid the spinlock boilerplate here. However
+ // it doesn't seem to be available yet in the Linux chroot headers yet.
+ std::new_handler nh;
+ {
+ while (subtle::Acquire_CompareAndSwap(&g_new_handler_lock, 0, 1))
+ PlatformThread::YieldCurrentThread();
+ nh = std::set_new_handler(0);
+ ignore_result(std::set_new_handler(nh));
+ subtle::Release_Store(&g_new_handler_lock, 0);
+ }
+ if (!nh)
+ return false;
+ (*nh)();
+ // Assume the new_handler will abort if it fails. Exception are disabled and
+ // we don't support the case of a new_handler throwing std::bad_balloc.
+ return true;
+}
+
+inline const allocator::AllocatorDispatch* GetChainHead() {
+ return reinterpret_cast<const allocator::AllocatorDispatch*>(
+ subtle::NoBarrier_Load(&g_chain_head));
+}
+
+} // namespace
+
+namespace base {
+namespace allocator {
+
+void SetCallNewHandlerOnMallocFailure(bool value) {
+ g_call_new_handler_on_malloc_failure = value;
+}
+
+void* UncheckedAlloc(size_t size) {
+ const allocator::AllocatorDispatch* const chain_head = GetChainHead();
+ return chain_head->alloc_function(chain_head, size);
+}
+
+void InsertAllocatorDispatch(AllocatorDispatch* dispatch) {
+ // Ensure this is always called on the same thread.
+ DCHECK(CalledOnValidThread());
+
+ dispatch->next = GetChainHead();
+
+ // This function does not guarantee to be thread-safe w.r.t. concurrent
+ // insertions, but still has to guarantee that all the threads always
+ // see a consistent chain, hence the MemoryBarrier() below.
+ // InsertAllocatorDispatch() is NOT a fastpath, as opposite to malloc(), so
+ // we don't really want this to be a release-store with a corresponding
+ // acquire-load during malloc().
+ subtle::MemoryBarrier();
+
+ subtle::NoBarrier_Store(&g_chain_head,
+ reinterpret_cast<subtle::AtomicWord>(dispatch));
+}
+
+void RemoveAllocatorDispatchForTesting(AllocatorDispatch* dispatch) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_EQ(GetChainHead(), dispatch);
+ subtle::NoBarrier_Store(&g_chain_head,
+ reinterpret_cast<subtle::AtomicWord>(dispatch->next));
+}
+
+} // namespace allocator
+} // namespace base
+
+// The Shim* functions below are the entry-points into the shim-layer and
+// are supposed to be invoked / aliased by the allocator_shim_override_*
+// headers to route the malloc / new symbols through the shim layer.
+extern "C" {
+
+// The general pattern for allocations is:
+// - Try to allocate, if succeded return the pointer.
+// - If the allocation failed:
+// - Call the std::new_handler if it was a C++ allocation.
+// - Call the std::new_handler if it was a malloc() (or calloc() or similar)
+// AND SetCallNewHandlerOnMallocFailure(true).
+// - If the std::new_handler is NOT set just return nullptr.
+// - If the std::new_handler is set:
+// - Assume it will abort() if it fails (very likely the new_handler will
+// just suicide priting a message).
+// - Assume it did succeed if it returns, in which case reattempt the alloc.
+
+void* ShimCppNew(size_t size) {
+ const allocator::AllocatorDispatch* const chain_head = GetChainHead();
+ void* ptr;
+ do {
+ ptr = chain_head->alloc_function(chain_head, size);
+ } while (!ptr && CallNewHandler());
+ return ptr;
+}
+
+void ShimCppDelete(void* address) {
+ const allocator::AllocatorDispatch* const chain_head = GetChainHead();
+ return chain_head->free_function(chain_head, address);
+}
+
+void* ShimMalloc(size_t size) {
+ const allocator::AllocatorDispatch* const chain_head = GetChainHead();
+ void* ptr;
+ do {
+ ptr = chain_head->alloc_function(chain_head, size);
+ } while (!ptr && g_call_new_handler_on_malloc_failure && CallNewHandler());
+ return ptr;
+}
+
+void* ShimCalloc(size_t n, size_t size) {
+ const allocator::AllocatorDispatch* const chain_head = GetChainHead();
+ void* ptr;
+ do {
+ ptr = chain_head->alloc_zero_initialized_function(chain_head, n, size);
+ } while (!ptr && g_call_new_handler_on_malloc_failure && CallNewHandler());
+ return ptr;
+}
+
+void* ShimRealloc(void* address, size_t size) {
+ // realloc(size == 0) means free() and might return a nullptr. We should
+ // not call the std::new_handler in that case, though.
+ const allocator::AllocatorDispatch* const chain_head = GetChainHead();
+ void* ptr;
+ do {
+ ptr = chain_head->realloc_function(chain_head, address, size);
+ } while (!ptr && size && g_call_new_handler_on_malloc_failure &&
+ CallNewHandler());
+ return ptr;
+}
+
+void* ShimMemalign(size_t alignment, size_t size) {
+ const allocator::AllocatorDispatch* const chain_head = GetChainHead();
+ void* ptr;
+ do {
+ ptr = chain_head->alloc_aligned_function(chain_head, alignment, size);
+ } while (!ptr && g_call_new_handler_on_malloc_failure && CallNewHandler());
+ return ptr;
+}
+
+int ShimPosixMemalign(void** res, size_t alignment, size_t size) {
+ // posix_memalign is supposed to check the arguments. See tc_posix_memalign()
+ // in tc_malloc.cc.
+ if (((alignment % sizeof(void*)) != 0) ||
+ ((alignment & (alignment - 1)) != 0) || (alignment == 0)) {
+ return EINVAL;
+ }
+ void* ptr = ShimMemalign(alignment, size);
+ *res = ptr;
+ return ptr ? 0 : ENOMEM;
+}
+
+void* ShimValloc(size_t size) {
+ return ShimMemalign(GetPageSize(), size);
+}
+
+void* ShimPvalloc(size_t size) {
+ // pvalloc(0) should allocate one page, according to its man page.
+ if (size == 0) {
+ size = GetPageSize();
+ } else {
+ size = (size + GetPageSize() - 1) & ~(GetPageSize() - 1);
+ }
+ return ShimMemalign(GetPageSize(), size);
+}
+
+void ShimFree(void* address) {
+ const allocator::AllocatorDispatch* const chain_head = GetChainHead();
+ return chain_head->free_function(chain_head, address);
+}
+
+} // extern "C"
+
+// Cpp symbols (new / delete) should always be routed through the shim layer.
+#include "base/allocator/allocator_shim_override_cpp_symbols.h"
+
+// Ditto for plain malloc() / calloc() / free() etc. symbols.
+#include "base/allocator/allocator_shim_override_libc_symbols.h"
+
+// In the case of tcmalloc we also want to plumb into the glibc hooks
+// to avoid that allocations made in glibc itself (e.g., strdup()) get
+// accidentally performed on the glibc heap instead of the tcmalloc one.
+#if defined(USE_TCMALLOC)
+#include "base/allocator/allocator_shim_override_glibc_weak_symbols.h"
+#endif
+
+// Cross-checks.
+
+#if defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
+#error The allocator shim should not be compiled when building for memory tools.
+#endif
+
+#if (defined(__GNUC__) && defined(__EXCEPTIONS)) || \
+ (defined(_HAS_EXCEPTIONS) && _HAS_EXCEPTIONS)
+#error This code cannot be used when exceptions are turned on.
+#endif
« no previous file with comments | « base/allocator/allocator_shim.h ('k') | base/allocator/allocator_shim_default_dispatch_to_glibc.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698