/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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 "perfetto/base/paged_memory.h"
#include <algorithm>
#include <cmath>
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
#include <Windows.h>
#else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
#include <sys/mman.h>
#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
#include "perfetto/base/logging.h"
#include "perfetto/base/utils.h"
namespace perfetto {
namespace base {
namespace {
constexpr size_t kGuardSize = kPageSize;
#if TRACK_COMMITTED_SIZE()
constexpr size_t kCommitChunkSize = kPageSize * 1024; // 4mB
#endif // TRACK_COMMITTED_SIZE()
} // namespace
// static
PagedMemory PagedMemory::Allocate(size_t size, int flags) {
PERFETTO_DCHECK(size % kPageSize == 0);
size_t outer_size = size + kGuardSize * 2;
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
void* ptr = VirtualAlloc(nullptr, outer_size, MEM_RESERVE, PAGE_NOACCESS);
if (!ptr && (flags & kMayFail))
return PagedMemory();
PERFETTO_CHECK(ptr);
char* usable_region = reinterpret_cast<char*>(ptr) + kGuardSize;
#else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
void* ptr = mmap(nullptr, outer_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED && (flags & kMayFail))
return PagedMemory();
PERFETTO_CHECK(ptr && ptr != MAP_FAILED);
char* usable_region = reinterpret_cast<char*>(ptr) + kGuardSize;
int res = mprotect(ptr, kGuardSize, PROT_NONE);
res |= mprotect(usable_region + size, kGuardSize, PROT_NONE);
PERFETTO_CHECK(res == 0);
#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
auto memory = PagedMemory(usable_region, size);
#if TRACK_COMMITTED_SIZE()
size_t initial_commit = size;
if (flags & kDontCommit)
initial_commit = std::min(initial_commit, kCommitChunkSize);
memory.EnsureCommitted(initial_commit);
#endif // TRACK_COMMITTED_SIZE()
return memory;
}
PagedMemory::PagedMemory() {}
PagedMemory::PagedMemory(char* p, size_t size) : p_(p), size_(size) {
ANNOTATE_NEW_BUFFER(p_, size_, committed_size_);
}
PagedMemory::PagedMemory(PagedMemory&& other) noexcept {
*this = other;
other.p_ = nullptr;
}
PagedMemory& PagedMemory::operator=(PagedMemory&& other) {
*this = other;
other.p_ = nullptr;
return *this;
}
PagedMemory::~PagedMemory() {
if (!p_)
return;
PERFETTO_CHECK(size_);
char* start = p_ - kGuardSize;
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
BOOL res = VirtualFree(start, 0, MEM_RELEASE);
PERFETTO_CHECK(res != 0);
#else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
const size_t outer_size = size_ + kGuardSize * 2;
int res = munmap(start, outer_size);
PERFETTO_CHECK(res == 0);
#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
ANNOTATE_DELETE_BUFFER(p_, size_, committed_size_);
}
bool PagedMemory::AdviseDontNeed(void* p, size_t size) {
PERFETTO_DCHECK(p_);
PERFETTO_DCHECK(p >= p_);
PERFETTO_DCHECK(static_cast<char*>(p) + size <= p_ + size_);
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
// Discarding pages on Windows has more CPU cost than is justified for the
// possible memory savings.
return false;
#else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
// http://man7.org/linux/man-pages/man2/madvise.2.html
int res = madvise(p, size, MADV_DONTNEED);
PERFETTO_DCHECK(res == 0);
return true;
#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
}
#if TRACK_COMMITTED_SIZE()
void PagedMemory::EnsureCommitted(size_t committed_size) {
PERFETTO_DCHECK(committed_size > 0u);
PERFETTO_DCHECK(committed_size <= size_);
#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
if (committed_size_ >= committed_size)
return;
// Rounding up.
size_t delta = committed_size - committed_size_;
size_t num_additional_chunks =
(delta + kCommitChunkSize - 1) / kCommitChunkSize;
PERFETTO_DCHECK(num_additional_chunks * kCommitChunkSize >= delta);
// Don't commit more than the total size.
size_t commit_size = std::min(num_additional_chunks * kCommitChunkSize,
size_ - committed_size_);
void* res = VirtualAlloc(p_ + committed_size_, commit_size, MEM_COMMIT,
PAGE_READWRITE);
PERFETTO_CHECK(res);
ANNOTATE_CHANGE_SIZE(p_, size_, committed_size_,
committed_size_ + commit_size);
committed_size_ += commit_size;
#else // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
// mmap commits automatically as needed, so we only track here for ASAN.
committed_size = std::max(committed_size_, committed_size);
ANNOTATE_CHANGE_SIZE(p_, size_, committed_size_, committed_size);
committed_size_ = committed_size;
#endif // PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
}
#endif // TRACK_COMMITTED_SIZE()
} // namespace base
} // namespace perfetto