Rocket.Chat.ReactNative/ios/Pods/Flipper-Folly/folly/memory/ReentrantAllocator.cpp

164 lines
5.1 KiB
C++
Raw Normal View History

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/memory/ReentrantAllocator.h>
#include <new>
#include <utility>
#include <folly/lang/Bits.h>
#include <folly/lang/SafeAssert.h>
#include <folly/portability/SysMman.h>
namespace folly {
namespace {
max_align_t dummy; // return value for zero-sized allocations
void* reentrant_allocate(std::size_t const n) noexcept {
FOLLY_SAFE_CHECK(n, "zero-sized");
auto const prot = PROT_READ | PROT_WRITE;
auto const flags = MAP_ANONYMOUS | MAP_PRIVATE;
auto const addr = ::mmap(nullptr, n, prot, flags, 0, 0);
FOLLY_SAFE_PCHECK(addr != MAP_FAILED, "mmap failed");
return addr;
}
void reentrant_deallocate(void* const p, std::size_t const n) noexcept {
FOLLY_SAFE_CHECK(p, "null-pointer");
FOLLY_SAFE_CHECK(n, "zero-sized");
auto const err = ::munmap(p, n);
FOLLY_SAFE_PCHECK(!err, "munmap failed");
}
} // namespace
namespace detail {
reentrant_allocator_base::reentrant_allocator_base(
reentrant_allocator_options const& options) noexcept {
meta_ = static_cast<meta_t*>(reentrant_allocate(sizeof(meta_t)));
::new (meta_) meta_t(options);
}
reentrant_allocator_base::reentrant_allocator_base(
reentrant_allocator_base const& that) noexcept {
meta_ = that.meta_;
meta_->refs.fetch_add(1, std::memory_order_relaxed);
}
reentrant_allocator_base& reentrant_allocator_base::operator=(
reentrant_allocator_base const& that) noexcept {
if (this != &that) {
if (meta_->refs.fetch_sub(1, std::memory_order_acq_rel) - 1 == 0) {
obliterate();
}
meta_ = that.meta_;
meta_->refs.fetch_add(1, std::memory_order_relaxed);
}
return *this;
}
reentrant_allocator_base::~reentrant_allocator_base() {
if (meta_->refs.fetch_sub(1, std::memory_order_acq_rel) - 1 == 0) {
obliterate();
}
}
void* reentrant_allocator_base::allocate(
std::size_t const n,
std::size_t const a) noexcept {
if (!n) {
return &dummy;
}
// large requests are handled directly
if (n >= meta_->large_size) {
return reentrant_allocate(n);
}
auto const block_size = meta_->block_size;
// small requests are handled from the shared arena list:
// * if the list is empty or the list head has insufficient space, c/x a new
// list head, starting over on failure
// * then c/x the list head size to the new size, starting over on failure
while (true) {
// load head - non-const because used in c/x below
auto head = meta_->head.load(std::memory_order_acquire);
// load size - non-const because used in c/x below
// size is where the prev allocation ends, if any
auto size = head //
? head->size.load(std::memory_order_acquire)
: block_size;
// offset is where the next allocation starts, and is aligned as a
auto const offset = (size + a - 1) & ~(a - 1);
// if insufficient space in current segment or no current segment at all
if (offset + n > block_size || !head) {
// mmap a new segment and try to c/x it in to be the segment list head
auto const newhead = static_cast<node_t*>(reentrant_allocate(block_size));
::new (newhead) node_t(head);
auto const exchanged = meta_->head.compare_exchange_weak(
head, newhead, std::memory_order_release, std::memory_order_relaxed);
if (!exchanged) {
// lost the race - munmap the new segment and start over
reentrant_deallocate(newhead, block_size);
continue;
}
head = newhead;
}
// compute the new size and try to c/x it in to be the head segment size
auto const newsize = offset + n;
auto const exchanged = head->size.compare_exchange_weak(
size, newsize, std::memory_order_release, std::memory_order_relaxed);
if (!exchanged) {
// lost the race - start over
continue;
}
return reinterpret_cast<char*>(head) + offset;
}
}
void reentrant_allocator_base::deallocate(
void* const p,
std::size_t const n) noexcept {
if (p == &dummy) {
FOLLY_SAFE_CHECK(n == 0, "unexpected non-zero size");
return;
}
if (!n || !p) {
return;
}
// large requests are handled directly
if (n >= meta_->large_size) {
reentrant_deallocate(p, n);
return;
}
// small requests are deferred to allocator destruction, so no-op here
}
void reentrant_allocator_base::obliterate() noexcept {
auto head = meta_->head.load(std::memory_order_acquire);
while (head != nullptr) {
auto const prev = std::exchange(head, head->next);
reentrant_deallocate(prev, meta_->block_size);
}
reentrant_deallocate(meta_, sizeof(meta_));
meta_ = nullptr;
}
} // namespace detail
} // namespace folly