blob: 43fb209feefbf15591a1b502b546cd52d8fd737a [file] [log] [blame]
Jamie Garside558e1442020-03-27 17:05:55 +00001// Copyright 2020 The Pigweed Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not
4// use this file except in compliance with the License. You may obtain a copy of
5// the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations under
13// the License.
14
15#include "pw_allocator/block.h"
16
17namespace pw::allocator {
18
Wyatt Heplere2cbadf2020-06-22 11:21:45 -070019Status Block::Init(const std::span<std::byte> region, Block** block) {
Jamie Garside558e1442020-03-27 17:05:55 +000020 // Ensure the region we're given is aligned and sized accordingly
21 if (reinterpret_cast<uintptr_t>(region.data()) % alignof(Block) != 0) {
22 return Status::INVALID_ARGUMENT;
23 }
24
25 if (region.size() < sizeof(Block)) {
26 return Status::INVALID_ARGUMENT;
27 }
28
29 union {
30 Block* block;
31 std::byte* bytes;
32 } aliased;
33 aliased.bytes = region.data();
34
35 // Make "next" point just past the end of this block; forming a linked list
36 // with the following storage. Since the space between this block and the
37 // next are implicitly part of the raw data, size can be computed by
38 // subtracting the pointers.
39 aliased.block->next = reinterpret_cast<Block*>(region.end());
40 aliased.block->MarkLast();
41
42 aliased.block->prev = nullptr;
43 *block = aliased.block;
44 return Status::OK;
45}
46
47Status Block::Split(size_t head_block_inner_size, Block** new_block) {
48 if (new_block == nullptr) {
49 return Status::INVALID_ARGUMENT;
50 }
51
52 // Don't split used blocks.
53 // TODO: Relax this restriction? Flag to enable/disable this check?
54 if (Used()) {
55 return Status::FAILED_PRECONDITION;
56 }
57
58 // First round the head_block_inner_size up to a alignof(Block) bounary.
59 // This ensures that the next block header is aligned accordingly.
60 // Alignment must be a power of two, hence align()-1 will return the
61 // remainder.
62 auto align_bit_mask = alignof(Block) - 1;
63 size_t aligned_head_block_inner_size = head_block_inner_size;
64 if ((head_block_inner_size & align_bit_mask) != 0) {
65 aligned_head_block_inner_size =
66 (head_block_inner_size & ~align_bit_mask) + alignof(Block);
67 }
68
69 // (1) Are we trying to allocate a head block larger than the current head
70 // block? This may happen because of the alignment above.
71 if (aligned_head_block_inner_size > InnerSize()) {
72 return Status::OUT_OF_RANGE;
73 }
74
75 // (2) Does the resulting block have enough space to store the header?
76 // TODO: What to do if the returned section is empty (i.e. remaining
77 // size == sizeof(Block))?
78 if (InnerSize() - aligned_head_block_inner_size < sizeof(Block)) {
79 return Status::RESOURCE_EXHAUSTED;
80 }
81
82 // Create the new block inside the current one.
83 Block* new_next = reinterpret_cast<Block*>(
84 // From the current position...
85 reinterpret_cast<intptr_t>(this) +
86 // skip past the current header...
87 sizeof(*this) +
88 // into the usable bytes by the new inner size.
89 aligned_head_block_inner_size);
90
91 // If we're inserting in the middle, we need to update the current next
92 // block to point to what we're inserting
93 if (!Last()) {
94 NextBlock()->prev = new_next;
95 }
96
97 // Copy next verbatim so the next block also gets the "last"-ness
98 new_next->next = next;
99 new_next->prev = this;
100
101 // Update the current block to point to the new head.
102 next = new_next;
103
104 *new_block = next;
105 return Status::OK;
106}
107
108Status Block::MergeNext() {
109 // Anything to merge with?
110 if (Last()) {
111 return Status::OUT_OF_RANGE;
112 }
113
114 // Is this or the next block in use?
115 if (Used() || NextBlock()->Used()) {
116 return Status::FAILED_PRECONDITION;
117 }
118
119 // Simply enough, this block's next pointer becomes the next block's
120 // next pointer. We then need to re-wire the "next next" block's prev
121 // pointer to point back to us though.
122 next = NextBlock()->next;
123
124 // Copying the pointer also copies the "last" status, so this is safe.
125 if (!Last()) {
126 NextBlock()->prev = this;
127 }
128
129 return Status::OK;
130}
131
132Status Block::MergePrev() {
133 // We can't merge if we have no previous. After that though, merging with
134 // the previous block is just MergeNext from the previous block.
135 if (prev == nullptr) {
136 return Status::OUT_OF_RANGE;
137 }
138
139 // WARNING: This class instance will still exist, but technically be invalid
140 // after this has been invoked. Be careful when doing anything with `this`
141 // After doing the below.
142 return prev->MergeNext();
143}
144
145} // namespace pw::allocator