Skip to content
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

Merged
merged 11 commits into from
Feb 5, 2025

Conversation

SchrodingerZhu
Copy link
Collaborator

No description provided.

@SchrodingerZhu SchrodingerZhu force-pushed the secondary branch 7 times, most recently from 998c17f to 239a366 Compare January 27, 2025 09:21
@SchrodingerZhu SchrodingerZhu changed the title [WIP] support secondary allocation support secondary allocation Jan 27, 2025
Copy link
Member

@mjp41 mjp41 left a 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.

@@ -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))
Copy link
Member

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.

Copy link
Collaborator Author

@SchrodingerZhu SchrodingerZhu Jan 29, 2025

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.

Copy link
Member

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()))
Copy link
Member

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?

Copy link
Collaborator Author

@SchrodingerZhu SchrodingerZhu Jan 28, 2025

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);

Copy link
Member

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)
Copy link
Collaborator

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 ?

static void initialize() {}

SNMALLOC_FAST_PATH
static void* allocate([[maybe_unused]] size_t size)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static void* allocate([[maybe_unused]] size_t size)
static void* allocate(size_t)

}

SNMALLOC_FAST_PATH
static bool has_secondary_ownership([[maybe_unused]] const void* pointer)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static bool has_secondary_ownership([[maybe_unused]] const void* pointer)
static bool has_secondary_ownership(const void*)

}

SNMALLOC_FAST_PATH
static size_t alloc_size([[maybe_unused]] const void* pointer)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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()))
Copy link
Member

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.

Comment on lines 190 to 193
void* result = SecondaryAllocator::allocate(size);
if (result != nullptr)
return capptr::Alloc<void>::unsafe_from(result);

Copy link
Member

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.

Comment on lines 889 to 892
void* result = SecondaryAllocator::allocate(rsize);
if (result != nullptr)
return capptr::Alloc<void>::unsafe_from(result);

Copy link
Member

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.

@SchrodingerZhu SchrodingerZhu force-pushed the secondary branch 3 times, most recently from d53aa2e to 87dcba3 Compare January 29, 2025 13:20
@SchrodingerZhu
Copy link
Collaborator Author

@mjp41 could you help rerun the failed test in the latest action submission

@SchrodingerZhu
Copy link
Collaborator Author

seems working :D

Copy link
Member

@mjp41 mjp41 left a 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?

src/snmalloc/mem/localalloc.h Outdated Show resolved Hide resolved
Copy link
Member

@mjp41 mjp41 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work. Thanks

@mjp41 mjp41 changed the title support secondary allocation Enable a seconary allocator support (e.g. GWP-Asan) Feb 5, 2025
@mjp41 mjp41 merged commit 16b9624 into microsoft:main Feb 5, 2025
66 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants