[scudo][standalone] Small changes to the fastpath

There are a few things that I wanted to reorganize for a while:
- the loop that incrementally goes through classes on failure looked
  horrible in assembly, mostly because of `LIKELY`/`UNLIKELY` within
  the loop. So remove those, we are already in an unlikely scenario
- hooks are not used by default on Android/Fuchsia/etc so mark the
  tests for the existence of the weak functions as unlikely
- mark of couple of conditions as likely/unlikely
- in `reallocate`, the old size was computed again while we already
  have it in a variable. So just use the one we have.
- remove the bitwise AND trick and use a logical AND, that has one
  less test by using a purposeful underflow when `Size` is 0 (I
  actually looked at the assembly of the previous code to steal that
  trick)
- move the read of the options closer to where they are used, mark them
  as `const`

Overall this makes things a tiny bit faster, but cleaner.

Differential Revision: https://reviews.llvm.org/D92689
This commit is contained in:
Kostya Kortchinsky 2020-12-04 14:07:49 -08:00
parent bdaeb82a5f
commit 3f70987b35
1 changed files with 15 additions and 19 deletions

View File

@ -277,7 +277,6 @@ public:
uptr Alignment = MinAlignment, uptr Alignment = MinAlignment,
bool ZeroContents = false) { bool ZeroContents = false) {
initThreadMaybe(); initThreadMaybe();
Options Options = Primary.Options.load();
#ifdef GWP_ASAN_HOOKS #ifdef GWP_ASAN_HOOKS
if (UNLIKELY(GuardedAlloc.shouldSample())) { if (UNLIKELY(GuardedAlloc.shouldSample())) {
@ -286,6 +285,7 @@ public:
} }
#endif // GWP_ASAN_HOOKS #endif // GWP_ASAN_HOOKS
const Options Options = Primary.Options.load();
const FillContentsMode FillContents = ZeroContents ? ZeroFill const FillContentsMode FillContents = ZeroContents ? ZeroFill
: TSDRegistry.getDisableMemInit() : TSDRegistry.getDisableMemInit()
? NoFill ? NoFill
@ -296,7 +296,7 @@ public:
return nullptr; return nullptr;
reportAlignmentTooBig(Alignment, MaxAlignment); reportAlignmentTooBig(Alignment, MaxAlignment);
} }
if (UNLIKELY(Alignment < MinAlignment)) if (Alignment < MinAlignment)
Alignment = MinAlignment; Alignment = MinAlignment;
// If the requested size happens to be 0 (more common than you might think), // If the requested size happens to be 0 (more common than you might think),
@ -331,12 +331,9 @@ public:
// larger class until it fits. If it fails to fit in the largest class, // larger class until it fits. If it fails to fit in the largest class,
// fallback to the Secondary. // fallback to the Secondary.
if (UNLIKELY(!Block)) { if (UNLIKELY(!Block)) {
while (ClassId < SizeClassMap::LargestClassId) { while (ClassId < SizeClassMap::LargestClassId && !Block)
Block = TSD->Cache.allocate(++ClassId); Block = TSD->Cache.allocate(++ClassId);
if (LIKELY(Block)) if (!Block)
break;
}
if (UNLIKELY(!Block))
ClassId = 0; ClassId = 0;
} }
if (UnlockRequired) if (UnlockRequired)
@ -467,7 +464,7 @@ public:
Chunk::SizeOrUnusedBytesMask; Chunk::SizeOrUnusedBytesMask;
Chunk::storeHeader(Cookie, Ptr, &Header); Chunk::storeHeader(Cookie, Ptr, &Header);
if (&__scudo_allocate_hook) if (UNLIKELY(&__scudo_allocate_hook))
__scudo_allocate_hook(TaggedPtr, Size); __scudo_allocate_hook(TaggedPtr, Size);
return TaggedPtr; return TaggedPtr;
@ -482,7 +479,6 @@ public:
// the TLS destructors, ending up in initialized thread specific data never // the TLS destructors, ending up in initialized thread specific data never
// being destroyed properly. Any other heap operation will do a full init. // being destroyed properly. Any other heap operation will do a full init.
initThreadMaybe(/*MinimalInit=*/true); initThreadMaybe(/*MinimalInit=*/true);
Options Options = Primary.Options.load();
#ifdef GWP_ASAN_HOOKS #ifdef GWP_ASAN_HOOKS
if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr))) { if (UNLIKELY(GuardedAlloc.pointerIsMine(Ptr))) {
@ -491,7 +487,7 @@ public:
} }
#endif // GWP_ASAN_HOOKS #endif // GWP_ASAN_HOOKS
if (&__scudo_deallocate_hook) if (UNLIKELY(&__scudo_deallocate_hook))
__scudo_deallocate_hook(Ptr); __scudo_deallocate_hook(Ptr);
if (UNLIKELY(!Ptr)) if (UNLIKELY(!Ptr))
@ -506,11 +502,13 @@ public:
if (UNLIKELY(Header.State != Chunk::State::Allocated)) if (UNLIKELY(Header.State != Chunk::State::Allocated))
reportInvalidChunkState(AllocatorAction::Deallocating, Ptr); reportInvalidChunkState(AllocatorAction::Deallocating, Ptr);
const Options Options = Primary.Options.load();
if (Options.get(OptionBit::DeallocTypeMismatch)) { if (Options.get(OptionBit::DeallocTypeMismatch)) {
if (Header.OriginOrWasZeroed != Origin) { if (UNLIKELY(Header.OriginOrWasZeroed != Origin)) {
// With the exception of memalign'd chunks, that can be still be free'd. // With the exception of memalign'd chunks, that can be still be free'd.
if (UNLIKELY(Header.OriginOrWasZeroed != Chunk::Origin::Memalign || if (Header.OriginOrWasZeroed != Chunk::Origin::Memalign ||
Origin != Chunk::Origin::Malloc)) Origin != Chunk::Origin::Malloc)
reportDeallocTypeMismatch(AllocatorAction::Deallocating, Ptr, reportDeallocTypeMismatch(AllocatorAction::Deallocating, Ptr,
Header.OriginOrWasZeroed, Origin); Header.OriginOrWasZeroed, Origin);
} }
@ -527,8 +525,8 @@ public:
void *reallocate(void *OldPtr, uptr NewSize, uptr Alignment = MinAlignment) { void *reallocate(void *OldPtr, uptr NewSize, uptr Alignment = MinAlignment) {
initThreadMaybe(); initThreadMaybe();
Options Options = Primary.Options.load();
const Options Options = Primary.Options.load();
if (UNLIKELY(NewSize >= MaxAllowedMallocSize)) { if (UNLIKELY(NewSize >= MaxAllowedMallocSize)) {
if (Options.get(OptionBit::MayReturnNull)) if (Options.get(OptionBit::MayReturnNull))
return nullptr; return nullptr;
@ -611,8 +609,7 @@ public:
// allow for potential further in-place realloc. The gains of such a trick // allow for potential further in-place realloc. The gains of such a trick
// are currently unclear. // are currently unclear.
void *NewPtr = allocate(NewSize, Chunk::Origin::Malloc, Alignment); void *NewPtr = allocate(NewSize, Chunk::Origin::Malloc, Alignment);
if (NewPtr) { if (LIKELY(NewPtr)) {
const uptr OldSize = getSize(OldPtr, &OldHeader);
memcpy(NewPtr, OldTaggedPtr, Min(NewSize, OldSize)); memcpy(NewPtr, OldTaggedPtr, Min(NewSize, OldSize));
quarantineOrDeallocateChunk(Options, OldPtr, &OldHeader, OldSize); quarantineOrDeallocateChunk(Options, OldPtr, &OldHeader, OldSize);
} }
@ -1057,10 +1054,9 @@ private:
} }
// If the quarantine is disabled, the actual size of a chunk is 0 or larger // If the quarantine is disabled, the actual size of a chunk is 0 or larger
// than the maximum allowed, we return a chunk directly to the backend. // than the maximum allowed, we return a chunk directly to the backend.
// Logical Or can be short-circuited, which introduces unnecessary // This purposefully underflows for Size == 0.
// conditional jumps, so use bitwise Or and let the compiler be clever.
const bool BypassQuarantine = const bool BypassQuarantine =
!Quarantine.getCacheSize() | !Size | (Size > QuarantineMaxChunkSize); !Quarantine.getCacheSize() || ((Size - 1) >= QuarantineMaxChunkSize);
if (BypassQuarantine) { if (BypassQuarantine) {
NewHeader.State = Chunk::State::Available; NewHeader.State = Chunk::State::Available;
Chunk::compareExchangeHeader(Cookie, Ptr, &NewHeader, Header); Chunk::compareExchangeHeader(Cookie, Ptr, &NewHeader, Header);