|  | /* | 
|  | * 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 "perfetto/base/compiler.h" | 
|  | #include "perfetto/base/logging.h" | 
|  | #include "perfetto/ext/base/optional.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. | 
|  | base::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) { | 
|  | Chunk& chunk = chunks_.at(ChunkIndexToQueueIndex(id.chunk_index)); | 
|  | PERFETTO_DCHECK(chunk.unfreed_allocations > 0); | 
|  | chunk.unfreed_allocations--; | 
|  | } | 
|  |  | 
|  | void* BumpAllocator::GetPointer(AllocId id) { | 
|  | uint32_t queue_index = ChunkIndexToQueueIndex(id.chunk_index); | 
|  | return chunks_.at(queue_index).allocation.get() + id.chunk_offset; | 
|  | } | 
|  |  | 
|  | uint32_t BumpAllocator::EraseFrontFreeChunks() { | 
|  | uint32_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; | 
|  | } | 
|  |  | 
|  | uint32_t BumpAllocator::PastEndSerializedId() { | 
|  | if (chunks_.empty()) { | 
|  | return AllocId{erased_front_chunks_count_, 0}.Serialize(); | 
|  | } | 
|  | return AllocId{LastChunkIndex(), chunks_.back().bump_offset}.Serialize(); | 
|  | } | 
|  |  | 
|  | base::Optional<BumpAllocator::AllocId> BumpAllocator::TryAllocInLastChunk( | 
|  | uint32_t size) { | 
|  | if (chunks_.empty()) { | 
|  | return base::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 base::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 |