Skip to content

Commit

Permalink
Port a simple linked-list buffer-backed heap implementation to chip (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
andy31415 authored and pull[bot] committed Mar 26, 2021
1 parent 705ac35 commit c10ecaa
Show file tree
Hide file tree
Showing 5 changed files with 640 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/lib/support/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ static_library("support") {
"PersistedCounter.h",
"Pool.cpp",
"Pool.h",
"PrivateHeap.cpp",
"PrivateHeap.h",
"RandUtils.cpp",
"RandUtils.h",
"ReturnMacros.h",
Expand Down
283 changes: 283 additions & 0 deletions src/lib/support/PrivateHeap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
/*
*
* Copyright (c) 2021 Project CHIP Authors
* Copyright 2019 Google Inc. All Rights Reserved.
*
* 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 "PrivateHeap.h"

#include <support/CodeUtils.h>
#include <support/logging/CHIPLogging.h>

namespace {

constexpr uint32_t kInvalidHeapBlockSize = 0xFFFFFFFF;
constexpr int32_t kHeapBlockInUse = 0x01;
constexpr int32_t kHeapBlockFree = 0x10;
constexpr int32_t kInvalidHeaderState = 0xff;

using internal::PrivateHeapBlockHeader;

// this makes life easier, no need to add align offsets.
static_assert(sizeof(PrivateHeapBlockHeader) % kPrivateHeapAllocationAlignment == 0, "Invalid block size.");

uint32_t ComputeHeapBlockChecksum(const PrivateHeapBlockHeader * header)
{
uint32_t checksum = header->prevBytes;
checksum *= 31;
checksum += header->nextBytes;
checksum *= 31;
checksum += header->state;
return checksum;
}

// Advances the heap block to the next value
PrivateHeapBlockHeader * NextHeader(PrivateHeapBlockHeader * start)
{
if (start->nextBytes == kInvalidHeapBlockSize)
{
return nullptr;
}

if (start->checksum != ComputeHeapBlockChecksum(start))
{
ChipLogError(Support, "Corrupted heap: checksum is invalid");
chipDie();
}

return reinterpret_cast<PrivateHeapBlockHeader *>(reinterpret_cast<char *>(start) + sizeof(PrivateHeapBlockHeader) +
start->nextBytes);
}

// Advances the heap block to the previous value
PrivateHeapBlockHeader * PreviousHeader(PrivateHeapBlockHeader * start)
{
if (start->prevBytes == kInvalidHeapBlockSize)
{
return nullptr;
}

if (start->checksum != ComputeHeapBlockChecksum(start))
{
ChipLogError(Support, "Corrupted heap: checksum is invalid");
chipDie();
}

return reinterpret_cast<PrivateHeapBlockHeader *>(reinterpret_cast<char *>(start) - sizeof(PrivateHeapBlockHeader) -
start->prevBytes);
}

void ValidateHeader(const PrivateHeapBlockHeader * header)
{
if (header->state != kHeapBlockFree && header->state != kHeapBlockInUse)
{
ChipLogError(Support, "Invalid header state (neither free nor in use) at %p", header);
chipDie();
}

if (header->checksum != ComputeHeapBlockChecksum(header))
{
ChipLogError(Support, "Corrupted heap: checksum is invalid at %p", header);
chipDie();
}
}

} // namespace

extern "C" void PrivateHeapInit(void * heap, size_t size)
{
if (heap == nullptr)
{
ChipLogError(Support, "Cannot initialize null heap");
chipDie();
}

if (size < 2 * sizeof(PrivateHeapBlockHeader))
{
ChipLogError(Support, "Insufficient space in private heap");
chipDie();
}

if (reinterpret_cast<uintptr_t>(heap) % kPrivateHeapAllocationAlignment != 0)
{
ChipLogError(Support, "Invalid alignment for private heap initialization");
chipDie();
}

PrivateHeapBlockHeader * header = reinterpret_cast<PrivateHeapBlockHeader *>(heap);

header->prevBytes = kInvalidHeapBlockSize;
header->nextBytes = static_cast<uint32_t>(size - 2 * sizeof(PrivateHeapBlockHeader));
header->state = kHeapBlockFree;
header->checksum = ComputeHeapBlockChecksum(header);

header = NextHeader(header);
header->nextBytes = kInvalidHeapBlockSize;
header->prevBytes = static_cast<uint32_t>(size - 2 * sizeof(PrivateHeapBlockHeader));
header->state = kHeapBlockFree; // does not matter really
header->checksum = ComputeHeapBlockChecksum(header);
}

extern "C" void * PrivateHeapAlloc(void * heap, size_t size)
{
PrivateHeapBlockHeader * header = reinterpret_cast<PrivateHeapBlockHeader *>(heap);

// we allocate aligned, no matter what
if (size % kPrivateHeapAllocationAlignment != 0)
{
size += kPrivateHeapAllocationAlignment - (size % kPrivateHeapAllocationAlignment);
}

for (; header != nullptr; header = NextHeader(header))
{
ValidateHeader(header);

if (header->nextBytes == kInvalidHeapBlockSize)
{
continue;
}

if (header->state != kHeapBlockFree)
{
continue; // not free
}

if (header->nextBytes < size)
{
continue; // insufficient space
}

if (header->nextBytes - size < sizeof(PrivateHeapBlockHeader) + kPrivateHeapAllocationAlignment)
{
// allocate the entire block
header->state = kHeapBlockInUse;
header->checksum = ComputeHeapBlockChecksum(header);
}
else
{
// splits the block into two
//
// +--------+ +--------+ +------+
// | header | ---> | middle | ---> | next |
// +--------+ +--------+ +------+
//
PrivateHeapBlockHeader * next = NextHeader(header);
PrivateHeapBlockHeader * middle =
reinterpret_cast<PrivateHeapBlockHeader *>(reinterpret_cast<char *>(header + 1) + size);

// middle is a new block
middle->nextBytes = static_cast<uint32_t>(header->nextBytes - size - sizeof(PrivateHeapBlockHeader));
middle->prevBytes = static_cast<uint32_t>(size);
middle->state = kHeapBlockFree;
middle->checksum = ComputeHeapBlockChecksum(middle);

// fix up the header
header->nextBytes = static_cast<uint32_t>(size);
header->state = kHeapBlockInUse;
header->checksum = ComputeHeapBlockChecksum(header);

// fix up the final block
if (next != nullptr)
{
next->prevBytes = middle->nextBytes;
next->checksum = ComputeHeapBlockChecksum(next);
}
}

// we can now use the header
return header + 1; // data right after the header
}

// no space found
return nullptr;
}

extern "C" void PrivateHeapFree(void * ptr)
{
if (ptr == nullptr)
{
// freeing NULL pointers is always acceptable and a noop
return;
}

PrivateHeapBlockHeader * header =
reinterpret_cast<PrivateHeapBlockHeader *>(static_cast<char *>(ptr) - sizeof(PrivateHeapBlockHeader));

ValidateHeader(header);
header->state = kHeapBlockFree;
header->checksum = ComputeHeapBlockChecksum(header);

// Merge with previous
//
// +-------+ +--------+
// | other | ----- nextBytes -----> | header |
// +-------+ +--------+
//
PrivateHeapBlockHeader * other = PreviousHeader(header);
if (other != nullptr && other->state == kHeapBlockFree && other->nextBytes != kInvalidHeapBlockSize)
{
// includes the free bytes in this block in the previous
other->nextBytes += static_cast<uint32_t>(header->nextBytes + sizeof(PrivateHeapBlockHeader));
other->checksum = ComputeHeapBlockChecksum(other);
header->state = kInvalidHeaderState;
header = other;

// fixes up the next block
other = NextHeader(header);
if (other != nullptr)
{
other->prevBytes = header->nextBytes;
other->checksum = ComputeHeapBlockChecksum(other);
}
}

// Merge with next
//
// +--------+ +-------+
// | header | ----- nextBytes -----> | other |
// +--------+ +-------+
//
other = NextHeader(header);
if (other != nullptr && other->state == kHeapBlockFree && other->nextBytes != kInvalidHeapBlockSize)
{
// includes the free bytes in the next block
other->state = kInvalidHeaderState;
header->nextBytes += static_cast<uint32_t>(other->nextBytes + sizeof(PrivateHeapBlockHeader));
header->checksum = ComputeHeapBlockChecksum(header);

// fixes up the next block
other = NextHeader(header);
if (other != nullptr)
{
other->prevBytes = header->nextBytes;
other->checksum = ComputeHeapBlockChecksum(other);
}
}
}

extern "C" void PrivateHeapDump(void * top)
{
PrivateHeapBlockHeader * header = reinterpret_cast<PrivateHeapBlockHeader *>(top);

ChipLogProgress(Support, "========= HEAP ===========");
while (header->nextBytes != kInvalidHeapBlockSize)
{
intptr_t offset = reinterpret_cast<char *>(header) - reinterpret_cast<char *>(top);
ChipLogProgress(Support, " %ld: size: %d, state: %d", static_cast<long>(offset), static_cast<int>(header->nextBytes),
static_cast<int>(header->state));

header = NextHeader(header);
}
}
66 changes: 66 additions & 0 deletions src/lib/support/PrivateHeap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
*
* Copyright (c) 2021 Project CHIP Authors
* Copyright 2019 Google Inc. All Rights Reserved.
*
* 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.
*/

#pragma once

#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>

#include <cstddef>
#include <type_traits>

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

// Initializes the heap (set start and end blocks)
void PrivateHeapInit(void * heap, size_t size);

// Allocates a new block on the specified heap
void * PrivateHeapAlloc(void * heap, size_t size);

// Marks the specified block as free
void PrivateHeapFree(void * ptr);

void PrivateHeapDump(void * ptr);

#ifdef __cplusplus
} // extern "C"

namespace internal {

// Heap structure, exposed for tests
//
// +---------+---------+-----+ +---------+-----------+
// | prev: 0 | next: n | ... | ....<n_bytes> .... | prev: n | next: ... |
// +---------+---------+-----+ +---------+-----------+
//
struct PrivateHeapBlockHeader
{
uint32_t prevBytes;
uint32_t nextBytes;
uint32_t state;
uint32_t checksum; // super-basic attempt to detect errors
};

} // namespace internal

constexpr size_t kPrivateHeapAllocationAlignment = std::alignment_of<max_align_t>::value;

#endif // ifdef __cplusplus
1 change: 1 addition & 0 deletions src/lib/support/tests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ chip_test_suite("tests") {
"TestCHIPMem.cpp",
"TestErrorStr.cpp",
"TestPool.cpp",
"TestPrivateHeap.cpp",
"TestSafeInt.cpp",
"TestSafeString.cpp",
"TestScopedBuffer.cpp",
Expand Down
Loading

0 comments on commit c10ecaa

Please sign in to comment.