-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable a seconary allocator support (e.g. GWP-Asan) #737
Changes from 7 commits
c2eaee2
573c221
9a98bc1
6d0b985
f6709db
d7acd32
121102c
dbaae40
7bc335c
3492df6
0081383
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
#pragma once | ||
|
||
#include "snmalloc/aal/address.h" | ||
#include "snmalloc/mem/secondary.h" | ||
#if defined(_MSC_VER) | ||
# define ALLOCATOR __declspec(allocator) __declspec(restrict) | ||
#elif __has_attribute(malloc) | ||
|
@@ -186,6 +187,10 @@ namespace snmalloc | |
return small_alloc<NoZero>(1); | ||
} | ||
|
||
void* result = SecondaryAllocator::allocate(size); | ||
if (result != nullptr) | ||
return capptr::Alloc<void>::unsafe_from(result); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should probably move this inside the |
||
return check_init([&](CoreAlloc* core_alloc) { | ||
if (size > bits::one_at_bit(bits::BITS - 1)) | ||
{ | ||
|
@@ -697,13 +702,7 @@ namespace snmalloc | |
return; | ||
} | ||
|
||
// If p_tame is not null, then dealloc has been call on something | ||
// it shouldn't be called on. | ||
// TODO: Should this be tested even in the !CHECK_CLIENT case? | ||
snmalloc_check_client( | ||
mitigations(sanity_checks), | ||
p_tame == nullptr, | ||
"Not allocated by snmalloc."); | ||
SecondaryAllocator::deallocate(p_tame.unsafe_ptr()); | ||
|
||
# ifdef SNMALLOC_TRACING | ||
message<1024>("nullptr deallocation"); | ||
|
@@ -719,6 +718,8 @@ namespace snmalloc | |
#else | ||
if constexpr (mitigations(sanity_checks)) | ||
{ | ||
if (SecondaryAllocator::has_secondary_ownership(p)) | ||
return; | ||
size = size == 0 ? 1 : size; | ||
auto sc = size_to_sizeclass_full(size); | ||
auto pm_sc = | ||
|
@@ -767,6 +768,9 @@ namespace snmalloc | |
#ifdef SNMALLOC_PASS_THROUGH | ||
return external_alloc::malloc_usable_size(const_cast<void*>(p_raw)); | ||
#else | ||
|
||
if (SecondaryAllocator::has_secondary_ownership(p_raw)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer this to check that the allocation is not owned by snmalloc, rather than rely on the secondary allocator having this. For GWP Asan, it is fine. But I would also been keen to use this to build an approach on Windows to replace the allocator, and pass any original frees back to the Windows Heap. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Could you illustrate a better way to do this? If do it the way above, we will always have the overhead for domestication test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. const PagemapEntry& entry =
Config::Backend::get_metaentry(address_cast(p_raw));
// Check is managed by snmalloc
if (SNMALLOC_LIKELY(entry.get_remote() != nullptr)
return sizeclass_full_to_size(entry.get_sizeclass());
// If not managed by snmalloc, pass to secondary allocator.
return SecondaryAllocator::alloc_size(p_raw); |
||
return SecondaryAllocator::alloc_size(p_raw); | ||
// TODO What's the domestication policy here? At the moment we just | ||
// probe the pagemap with the raw address, without checks. There could | ||
// be implicit domestication through the `Config::Pagemap` or | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#pragma once | ||
#ifdef SNMALLOC_ENABLE_GWP_ASAN_INTEGRATION | ||
# include "snmalloc/mem/secondary/gwp_asan.h" | ||
|
||
namespace snmalloc | ||
{ | ||
using SecondaryAllocator = GwpAsanSecondaryAllocator; | ||
} // namespace snmalloc | ||
#else | ||
# include "snmalloc/mem/secondary/default.h" | ||
|
||
namespace snmalloc | ||
{ | ||
using SecondaryAllocator = DefaultSecondaryAllocator; | ||
} // namespace snmalloc | ||
#endif |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,49 @@ | ||||||
#pragma once | ||||||
|
||||||
#include "snmalloc/ds_core/defines.h" | ||||||
#include "snmalloc/ds_core/mitigations.h" | ||||||
|
||||||
#include <stddef.h> | ||||||
|
||||||
namespace snmalloc | ||||||
{ | ||||||
class DefaultSecondaryAllocator | ||||||
{ | ||||||
public: | ||||||
SNMALLOC_FAST_PATH | ||||||
static void initialize() {} | ||||||
|
||||||
SNMALLOC_FAST_PATH | ||||||
static void* allocate([[maybe_unused]] size_t size) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{ | ||||||
return nullptr; | ||||||
} | ||||||
|
||||||
SNMALLOC_FAST_PATH | ||||||
static void deallocate(void* pointer) | ||||||
{ | ||||||
// If pointer is not null, then dealloc has been call on something | ||||||
// it shouldn't be called on. | ||||||
// TODO: Should this be tested even in the !CHECK_CLIENT case? | ||||||
snmalloc_check_client( | ||||||
mitigations(sanity_checks), | ||||||
pointer == nullptr, | ||||||
"Not allocated by snmalloc."); | ||||||
} | ||||||
|
||||||
SNMALLOC_FAST_PATH | ||||||
static bool has_secondary_ownership([[maybe_unused]] const void* pointer) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{ | ||||||
return false; | ||||||
} | ||||||
|
||||||
SNMALLOC_FAST_PATH | ||||||
static size_t alloc_size([[maybe_unused]] const void* pointer) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{ | ||||||
SNMALLOC_ASSERT( | ||||||
false && | ||||||
"secondary alloc_size should never be invoked with default setup"); | ||||||
return 0; | ||||||
} | ||||||
}; | ||||||
} // namespace snmalloc |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
#pragma once | ||
|
||
#include "gwp_asan/guarded_pool_allocator.h" | ||
#include "snmalloc/ds_core/defines.h" | ||
#include "snmalloc/mem/sizeclasstable.h" | ||
#if defined(SNMALLOC_BACKTRACE_HEADER) | ||
# include SNMALLOC_BACKTRACE_HEADER | ||
#endif | ||
|
||
namespace snmalloc | ||
{ | ||
class GwpAsanSecondaryAllocator | ||
{ | ||
static inline gwp_asan::GuardedPoolAllocator singleton; | ||
static inline size_t max_allocation_size; | ||
|
||
public: | ||
static void initialize() noexcept | ||
{ | ||
// for now, we use default options | ||
gwp_asan::options::Options opt; | ||
opt.setDefaults(); | ||
#ifdef SNMALLOC_BACKTRACE_HEADER | ||
opt.Backtrace = [](uintptr_t* buf, size_t length) { | ||
return static_cast<size_t>( | ||
::backtrace(reinterpret_cast<void**>(buf), static_cast<int>(length))); | ||
}; | ||
#endif | ||
singleton.init(opt); | ||
max_allocation_size = | ||
singleton.getAllocatorState()->maximumAllocationSize(); | ||
} | ||
|
||
SNMALLOC_FAST_PATH static void* allocate(size_t size) | ||
{ | ||
if (SNMALLOC_UNLIKELY(singleton.shouldSample())) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As this is only getting called when a free list is exhausted should we be doing something else here. I.e. should we be passing how many requests of this size have been handled since we last called this allocator? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
// NextSampleCounter == 0 means we "should regenerate the counter".
// == 1 means we "should sample this allocation".
// AdjustedSampleRatePlusOne is designed to intentionally underflow. This
// class must be valid when zero-initialised, and we wish to sample as
// infrequently as possible when this is the case, hence we underflow to
// UINT32_MAX.
if (GWP_ASAN_UNLIKELY(getThreadLocals()->NextSampleCounter == 0))
getThreadLocals()->NextSampleCounter =
((getRandomUnsigned32() % (AdjustedSampleRatePlusOne - 1)) + 1) &
ThreadLocalPackedVariables::NextSampleCounterMask;
return GWP_ASAN_UNLIKELY(--getThreadLocals()->NextSampleCounter == 0); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I meant. Normally usage of GWP Asan, every call to I think this is probably the same information that would be required to unbias profiling, so should probably be left to a subsequent PR, but we should perhaps add either an issue or a comment about this. |
||
{ | ||
if (size > max_allocation_size) | ||
return nullptr; | ||
auto alignment = natural_alignment(size); | ||
return singleton.allocate(size, alignment); | ||
} | ||
return nullptr; | ||
} | ||
|
||
SNMALLOC_FAST_PATH | ||
static void deallocate(void* pointer) | ||
{ | ||
if (SNMALLOC_LIKELY(pointer == nullptr)) | ||
return; | ||
|
||
snmalloc_check_client( | ||
mitigations(sanity_checks), | ||
singleton.pointerIsMine(pointer), | ||
"Not allocated by snmalloc or secondary allocator"); | ||
|
||
singleton.deallocate(pointer); | ||
} | ||
|
||
SNMALLOC_FAST_PATH | ||
static bool has_secondary_ownership([[maybe_unused]] const void* pointer) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: do not think [maybe_unused] is necessary here ? |
||
{ | ||
return singleton.pointerIsMine(pointer); | ||
} | ||
|
||
SNMALLOC_FAST_PATH | ||
static size_t alloc_size(const void* pointer) | ||
{ | ||
return singleton.getSize(pointer); | ||
} | ||
}; | ||
} // namespace snmalloc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about this a bit more. I wonder if this should be at the start of
small_alloc
. I think there are allocation patterns that would never hit this path. It is possible to work completely without having to grab a new chunk from the backend.Sorry, I think I had this wrong in my original issue.