blob: d14d3bd27dfc1dca65802923421829cd21160218 [file] [log] [blame]
// Copyright 2020 The Pigweed Authors
//
// 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
//
// https://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 "pw_allocator/freelist.h"
namespace pw::allocator {
Status FreeList::AddChunk(span<std::byte> chunk) {
// Check that the size is enough to actually store what we need
if (chunk.size() < sizeof(FreeListNode)) {
return Status::OutOfRange();
}
union {
FreeListNode* node;
std::byte* bytes;
} aliased;
aliased.bytes = chunk.data();
unsigned short chunk_ptr = FindChunkPtrForSize(chunk.size(), false);
// Add it to the correct list.
aliased.node->size = chunk.size();
aliased.node->next = chunks_[chunk_ptr];
chunks_[chunk_ptr] = aliased.node;
return OkStatus();
}
span<std::byte> FreeList::FindChunk(size_t size) const {
if (size == 0) {
return span<std::byte>();
}
unsigned short chunk_ptr = FindChunkPtrForSize(size, true);
// Check that there's data. This catches the case where we run off the
// end of the array
if (chunks_[chunk_ptr] == nullptr) {
return span<std::byte>();
}
// Now iterate up the buckets, walking each list to find a good candidate
for (size_t i = chunk_ptr; i < chunks_.size(); i++) {
union {
FreeListNode* node;
std::byte* data;
} aliased;
aliased.node = chunks_[static_cast<unsigned short>(i)];
while (aliased.node != nullptr) {
if (aliased.node->size >= size) {
return span<std::byte>(aliased.data, aliased.node->size);
}
aliased.node = aliased.node->next;
}
}
// If we get here, we've checked every block in every bucket. There's
// nothing that can support this allocation.
return span<std::byte>();
}
Status FreeList::RemoveChunk(span<std::byte> chunk) {
unsigned short chunk_ptr = FindChunkPtrForSize(chunk.size(), true);
// Walk that list, finding the chunk.
union {
FreeListNode* node;
std::byte* data;
} aliased, aliased_next;
// Check head first.
if (chunks_[chunk_ptr] == nullptr) {
return Status::NotFound();
}
aliased.node = chunks_[chunk_ptr];
if (aliased.data == chunk.data()) {
chunks_[chunk_ptr] = aliased.node->next;
return OkStatus();
}
// No? Walk the nodes.
aliased.node = chunks_[chunk_ptr];
while (aliased.node->next != nullptr) {
aliased_next.node = aliased.node->next;
if (aliased_next.data == chunk.data()) {
// Found it, remove this node out of the chain
aliased.node->next = aliased_next.node->next;
return OkStatus();
}
aliased.node = aliased.node->next;
}
return Status::NotFound();
}
unsigned short FreeList::FindChunkPtrForSize(size_t size, bool non_null) const {
unsigned short chunk_ptr = 0;
for (chunk_ptr = 0u; chunk_ptr < sizes_.size(); chunk_ptr++) {
if (sizes_[chunk_ptr] >= size &&
(!non_null || chunks_[chunk_ptr] != nullptr)) {
break;
}
}
return chunk_ptr;
}
} // namespace pw::allocator