-
Notifications
You must be signed in to change notification settings - Fork 113
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
Conversation
427e852
to
f6709db
Compare
998c17f
to
239a366
Compare
239a366
to
121102c
Compare
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.
This looks great to me. Thanks for doing this.
I think you are missing the malloc_usable_size
path, which if called on a GWP Asan allocation will not be correct.
I am keen to explore using this for two other things:
- Sampling profiler - We could pass allocations to a sampling allocator as the secondary allocator. This secondary allocator would keep track of stack traces, etc.
- Windows malloc replacement using Detours. This is tricky to do, and if you don't get in quick enough, then deallocations can be passed in that were from the Windows Heap. With this, we could take the undetours functions as the secondary free path, and _msize path. We would ignore the allocation path.
I think you design mostly works for this, and certainly meets the #697. I think tracking enough information to unbias the sampling profiler would need more work, but shouldn't be part of this PR.
src/snmalloc/mem/localalloc.h
Outdated
@@ -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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
SNMALLOC_FAST_PATH size_t alloc_size(const void* p_raw)
{
#ifdef SNMALLOC_PASS_THROUGH
return external_alloc::malloc_usable_size(const_cast<void*>(p_raw));
#else
if (!check_domestication(p_raw))
return SecondaryAllocator::alloc_size(p_raw);
// ......
}
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 comment
The 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);
|
||
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
shouldSample
is implemented as
// 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I meant. Normally usage of GWP Asan, every call to malloc
will call shouldSample
. But this usage has moved it from the fast path, so we will call it every new free list for that size. This in the case of 16 byte allocations worst case could be 1024 allocations between calls. This will alter the likelihood of it being called, and might bias away from small allocations. I think for GWP Asan on we would want randomisation too, so that the free list that gets exhausted.
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.
} | ||
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
nit: do not think [maybe_unused] is necessary here ?
src/snmalloc/mem/secondary/default.h
Outdated
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 comment
The reason will be displayed to describe this comment to others. Learn more.
static void* allocate([[maybe_unused]] size_t size) | |
static void* allocate(size_t) |
src/snmalloc/mem/secondary/default.h
Outdated
} | ||
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
static bool has_secondary_ownership([[maybe_unused]] const void* pointer) | |
static bool has_secondary_ownership(const void*) |
src/snmalloc/mem/secondary/default.h
Outdated
} | ||
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
static size_t alloc_size([[maybe_unused]] const void* pointer) | |
static size_t alloc_size(const void*) |
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I meant. Normally usage of GWP Asan, every call to malloc
will call shouldSample
. But this usage has moved it from the fast path, so we will call it every new free list for that size. This in the case of 16 byte allocations worst case could be 1024 allocations between calls. This will alter the likelihood of it being called, and might bias away from small allocations. I think for GWP Asan on we would want randomisation too, so that the free list that gets exhausted.
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.
src/snmalloc/mem/localalloc.h
Outdated
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 comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should probably move this inside the check_init
call. I.e. just before // Grab slab of correct size
, I think we might have initialisation issues if we haven't fully initialised snmalloc as the pagemap won't exist when we look up a GWP Asan allocation.
src/snmalloc/mem/corealloc.h
Outdated
void* result = SecondaryAllocator::allocate(rsize); | ||
if (result != nullptr) | ||
return capptr::Alloc<void>::unsafe_from(result); | ||
|
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.
d53aa2e
to
87dcba3
Compare
87dcba3
to
dbaae40
Compare
@mjp41 could you help rerun the failed test in the latest action submission |
seems working :D |
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.
Thanks for all the changes. Just one small bit left.
@nwf would you be able to take a look at the comment below?
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.
Awesome work. Thanks
No description provided.