| /* |
| * Copyright (C) 2023 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 "src/trace_processor/util/bump_allocator.h" |
| |
| #include <limits> |
| #include <optional> |
| |
| #include "perfetto/base/compiler.h" |
| #include "perfetto/base/logging.h" |
| #include "perfetto/ext/base/utils.h" |
| |
| namespace perfetto { |
| namespace trace_processor { |
| namespace { |
| |
| // TODO(b/266983484): consider using base::PagedMemory unless a) we are on a |
| // platform where that doesn't make sense (WASM) b) we are trying to do heap |
| // profiling. |
| base::AlignedUniquePtr<uint8_t[]> Allocate(uint32_t size) { |
| uint8_t* ptr = static_cast<uint8_t*>(base::AlignedAlloc(8, size)); |
| // Poison the region to try and catch out of bound accesses. |
| PERFETTO_ASAN_POISON(ptr, size); |
| return base::AlignedUniquePtr<uint8_t[]>(ptr); |
| } |
| |
| } // namespace |
| |
| BumpAllocator::BumpAllocator() = default; |
| |
| BumpAllocator::~BumpAllocator() { |
| for (const auto& chunk : chunks_) { |
| PERFETTO_CHECK(chunk.unfreed_allocations == 0); |
| } |
| } |
| |
| BumpAllocator::AllocId BumpAllocator::Alloc(uint32_t size) { |
| // Size is required to be a multiple of 8 to avoid needing to deal with |
| // alignment. It must also be at most kChunkSize as we do not support cross |
| // chunk spanning allocations. |
| PERFETTO_DCHECK(size % 8 == 0); |
| PERFETTO_DCHECK(size <= kChunkSize); |
| |
| // Fast path: check if we have space to service this allocation in the current |
| // chunk. |
| std::optional<AllocId> alloc_id = TryAllocInLastChunk(size); |
| if (alloc_id) { |
| return *alloc_id; |
| } |
| |
| // Slow path: we don't have enough space in the last chunk so we create one. |
| Chunk chunk; |
| chunk.allocation = Allocate(kChunkSize); |
| chunks_.emplace_back(std::move(chunk)); |
| |
| // Ensure that we haven't exceeded the maximum number of chunks. |
| PERFETTO_CHECK(LastChunkIndex() < kMaxChunkCount); |
| |
| // This time the allocation should definitely succeed in the last chunk (which |
| // we just added). |
| alloc_id = TryAllocInLastChunk(size); |
| PERFETTO_CHECK(alloc_id); |
| return *alloc_id; |
| } |
| |
| void BumpAllocator::Free(AllocId id) { |
| uint64_t queue_index = ChunkIndexToQueueIndex(id.chunk_index); |
| PERFETTO_DCHECK(queue_index <= std::numeric_limits<size_t>::max()); |
| Chunk& chunk = chunks_.at(static_cast<size_t>(queue_index)); |
| PERFETTO_DCHECK(chunk.unfreed_allocations > 0); |
| chunk.unfreed_allocations--; |
| } |
| |
| void* BumpAllocator::GetPointer(AllocId id) { |
| uint64_t queue_index = ChunkIndexToQueueIndex(id.chunk_index); |
| PERFETTO_CHECK(queue_index <= std::numeric_limits<size_t>::max()); |
| return chunks_.at(static_cast<size_t>(queue_index)).allocation.get() + |
| id.chunk_offset; |
| } |
| |
| uint64_t BumpAllocator::EraseFrontFreeChunks() { |
| size_t to_erase_chunks = 0; |
| for (; to_erase_chunks < chunks_.size(); ++to_erase_chunks) { |
| // Break on the first chunk which still has unfreed allocations. |
| if (chunks_.at(to_erase_chunks).unfreed_allocations > 0) { |
| break; |
| } |
| } |
| chunks_.erase_front(to_erase_chunks); |
| erased_front_chunks_count_ += to_erase_chunks; |
| return to_erase_chunks; |
| } |
| |
| BumpAllocator::AllocId BumpAllocator::PastTheEndId() { |
| if (chunks_.empty()) { |
| return AllocId{erased_front_chunks_count_, 0}; |
| } |
| if (chunks_.back().bump_offset == kChunkSize) { |
| return AllocId{LastChunkIndex() + 1, 0}; |
| } |
| return AllocId{LastChunkIndex(), chunks_.back().bump_offset}; |
| } |
| |
| std::optional<BumpAllocator::AllocId> BumpAllocator::TryAllocInLastChunk( |
| uint32_t size) { |
| if (chunks_.empty()) { |
| return std::nullopt; |
| } |
| |
| // TODO(266983484): consider switching this to bump downwards instead of |
| // upwards for more efficient code generation. |
| Chunk& chunk = chunks_.back(); |
| |
| // Verify some invariants: |
| // 1) The allocation must exist |
| // 2) The bump must be in the bounds of the chunk. |
| PERFETTO_DCHECK(chunk.allocation); |
| PERFETTO_DCHECK(chunk.bump_offset <= kChunkSize); |
| |
| // If the end of the allocation ends up after this chunk, we cannot service it |
| // in this chunk. |
| uint32_t alloc_offset = chunk.bump_offset; |
| uint32_t new_bump_offset = chunk.bump_offset + size; |
| if (new_bump_offset > kChunkSize) { |
| return std::nullopt; |
| } |
| |
| // Set the new offset equal to the end of this allocation and increment the |
| // unfreed allocation counter. |
| chunk.bump_offset = new_bump_offset; |
| chunk.unfreed_allocations++; |
| |
| // Unpoison the allocation range to allow access to it on ASAN builds. |
| PERFETTO_ASAN_UNPOISON(chunk.allocation.get() + alloc_offset, size); |
| |
| return AllocId{LastChunkIndex(), alloc_offset}; |
| } |
| |
| } // namespace trace_processor |
| } // namespace perfetto |