274 lines
7.0 KiB
C++
274 lines
7.0 KiB
C++
// OpenSTA, Static Timing Analyzer
|
|
// Copyright (c) 2024, Parallax Software, Inc.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#pragma once
|
|
|
|
#include <cstring> // memcpy
|
|
#include <vector>
|
|
|
|
#include "ObjectId.hh"
|
|
#include "Error.hh"
|
|
|
|
namespace sta {
|
|
|
|
template <class TYPE>
|
|
class ArrayBlock;
|
|
|
|
// Array tables allocate arrays of objects in blocks and use 32 bit IDs to
|
|
// reference the array. Paging performance is improved by allocating
|
|
// blocks instead of individual arrays, and object sizes are reduced
|
|
// by using 32 bit references instead of 64 bit pointers.
|
|
// They are similar to ObjectTables but do not support delete/destroy or
|
|
// reclaiming deleted arrays.
|
|
|
|
template <class TYPE>
|
|
class ArrayTable
|
|
{
|
|
public:
|
|
ArrayTable();
|
|
~ArrayTable();
|
|
void make(uint32_t count,
|
|
TYPE *&array,
|
|
ObjectId &id);
|
|
void destroy(ObjectId id,
|
|
uint32_t count);
|
|
// Grow as necessary and return pointer for id.
|
|
TYPE *ensureId(ObjectId id);
|
|
TYPE *pointer(ObjectId id) const;
|
|
TYPE &ref(ObjectId id) const;
|
|
size_t size() const { return size_; }
|
|
void clear();
|
|
|
|
static constexpr int idx_bits = 7;
|
|
static constexpr int block_size = (1 << idx_bits);
|
|
static constexpr int block_id_max = 1 << (object_id_bits - idx_bits);
|
|
|
|
private:
|
|
ArrayBlock<TYPE> *makeBlock(uint32_t size);
|
|
void pushBlock(ArrayBlock<TYPE> *block);
|
|
void deleteBlocks();
|
|
|
|
size_t size_;
|
|
// Block index of free block (blocks_[size - 1]).
|
|
BlockIdx free_block_idx_;
|
|
// Index of next free object in free_block_idx_.
|
|
ObjectIdx free_idx_;
|
|
// Don't use std::vector so growing blocks_ can be thread safe.
|
|
size_t blocks_size_;
|
|
size_t blocks_capacity_;
|
|
ArrayBlock<TYPE>* *blocks_;
|
|
ArrayBlock<TYPE>* *prev_blocks_;
|
|
// Linked list of free arrays indexed by array size.
|
|
std::vector<ObjectId> free_list_;
|
|
static constexpr ObjectId idx_mask_ = block_size - 1;
|
|
};
|
|
|
|
template <class TYPE>
|
|
ArrayTable<TYPE>::ArrayTable() :
|
|
size_(0),
|
|
free_block_idx_(block_idx_null),
|
|
free_idx_(object_idx_null),
|
|
blocks_size_(0),
|
|
blocks_capacity_(1024),
|
|
blocks_(new ArrayBlock<TYPE>*[blocks_capacity_]),
|
|
prev_blocks_(nullptr)
|
|
{
|
|
}
|
|
|
|
template <class TYPE>
|
|
ArrayTable<TYPE>::~ArrayTable()
|
|
{
|
|
deleteBlocks();
|
|
delete [] blocks_;
|
|
delete [] prev_blocks_;
|
|
}
|
|
|
|
template <class TYPE>
|
|
void
|
|
ArrayTable<TYPE>::deleteBlocks()
|
|
{
|
|
for (size_t i = 0; i < blocks_size_; i++)
|
|
delete blocks_[i];
|
|
}
|
|
|
|
template <class TYPE>
|
|
void
|
|
ArrayTable<TYPE>::make(uint32_t count,
|
|
TYPE *&array,
|
|
ObjectId &id)
|
|
{
|
|
// Check the free list for a previously destroyed array with the right size.
|
|
if (count < free_list_.size()
|
|
&& free_list_[count] != object_id_null) {
|
|
id = free_list_[count];
|
|
array = pointer(id);
|
|
|
|
ObjectId *head = reinterpret_cast<ObjectId*>(array);
|
|
free_list_[count] = *head;
|
|
}
|
|
else {
|
|
ArrayBlock<TYPE> *block = blocks_size_ ? blocks_[free_block_idx_] : nullptr;
|
|
if ((free_idx_ == object_idx_null
|
|
&& free_block_idx_ == block_idx_null)
|
|
|| free_idx_ + count >= block->size()) {
|
|
uint32_t size = block_size;
|
|
if (blocks_size_ == 0
|
|
// First block starts at idx 1.
|
|
&& count > block_size - 1)
|
|
size = count + 1;
|
|
else if (count > block_size)
|
|
size = count;
|
|
block = makeBlock(size);
|
|
}
|
|
// makeId(free_block_idx_, idx_bits)
|
|
id = (free_block_idx_ << idx_bits) + free_idx_;
|
|
array = block->pointer(free_idx_);
|
|
free_idx_ += count;
|
|
}
|
|
size_ += count;
|
|
}
|
|
|
|
template <class TYPE>
|
|
ArrayBlock<TYPE> *
|
|
ArrayTable<TYPE>::makeBlock(uint32_t size)
|
|
{
|
|
BlockIdx block_idx = blocks_size_;
|
|
ArrayBlock<TYPE> *block = new ArrayBlock<TYPE>(size);
|
|
pushBlock(block);
|
|
free_block_idx_ = block_idx;
|
|
// ObjectId zero is reserved for object_id_null.
|
|
free_idx_ = (block_idx > 0) ? 0 : 1;
|
|
return block;
|
|
}
|
|
|
|
template <class TYPE>
|
|
void
|
|
ArrayTable<TYPE>::pushBlock(ArrayBlock<TYPE> *block)
|
|
{
|
|
blocks_[blocks_size_++] = block;
|
|
if (blocks_size_ >= block_id_max)
|
|
criticalError(223, "max array table block count exceeded.");
|
|
if (blocks_size_ == blocks_capacity_) {
|
|
size_t new_capacity = blocks_capacity_ * 1.5;
|
|
ArrayBlock<TYPE>** new_blocks = new ArrayBlock<TYPE>*[new_capacity];
|
|
memcpy(new_blocks, blocks_, blocks_capacity_ * sizeof(ArrayBlock<TYPE>*));
|
|
if (prev_blocks_)
|
|
delete [] prev_blocks_;
|
|
// Preserve block array for other threads to reference.
|
|
prev_blocks_ = blocks_;
|
|
blocks_ = new_blocks;
|
|
blocks_capacity_ = new_capacity;
|
|
}
|
|
}
|
|
|
|
template <class TYPE>
|
|
void
|
|
ArrayTable<TYPE>::destroy(ObjectId id,
|
|
uint32_t count)
|
|
{
|
|
if (count >= free_list_.size())
|
|
free_list_.resize(count + 1);
|
|
TYPE *array = pointer(id);
|
|
// Prepend id to the free list.
|
|
ObjectId *head = reinterpret_cast<ObjectId*>(array);
|
|
*head = free_list_[count];
|
|
free_list_[count] = id;
|
|
size_ -= count;
|
|
}
|
|
|
|
template <class TYPE>
|
|
TYPE *
|
|
ArrayTable<TYPE>::pointer(ObjectId id) const
|
|
{
|
|
if (id == object_id_null)
|
|
return nullptr;
|
|
else {
|
|
BlockIdx blk_idx = id >> idx_bits;
|
|
ObjectIdx obj_idx = id & idx_mask_;
|
|
return blocks_[blk_idx]->pointer(obj_idx);
|
|
}
|
|
}
|
|
|
|
template <class TYPE>
|
|
TYPE *
|
|
ArrayTable<TYPE>::ensureId(ObjectId id)
|
|
{
|
|
BlockIdx blk_idx = id >> idx_bits;
|
|
ObjectIdx obj_idx = id & idx_mask_;
|
|
// Make enough blocks for blk_idx to be valid.
|
|
for (BlockIdx i = blocks_size_; i <= blk_idx; i++) {
|
|
ArrayBlock<TYPE> *block = new ArrayBlock<TYPE>(block_size);
|
|
pushBlock(block);
|
|
}
|
|
return blocks_[blk_idx]->pointer(obj_idx);
|
|
}
|
|
|
|
template <class TYPE>
|
|
TYPE &
|
|
ArrayTable<TYPE>::ref(ObjectId id) const
|
|
{
|
|
if (id == object_id_null)
|
|
criticalError(222, "null ObjectId reference is undefined.");
|
|
|
|
BlockIdx blk_idx = id >> idx_bits;
|
|
ObjectIdx obj_idx = id & idx_mask_;
|
|
return blocks_[blk_idx]->ref(obj_idx);
|
|
}
|
|
|
|
template <class TYPE>
|
|
void
|
|
ArrayTable<TYPE>::clear()
|
|
{
|
|
deleteBlocks();
|
|
blocks_size_ = 0;
|
|
size_ = 0;
|
|
free_block_idx_ = block_idx_null;
|
|
free_idx_ = object_idx_null;
|
|
free_list_.clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
template <class TYPE>
|
|
class ArrayBlock
|
|
{
|
|
public:
|
|
ArrayBlock(uint32_t size);
|
|
~ArrayBlock();
|
|
uint32_t size() const { return size_; }
|
|
TYPE &ref(ObjectIdx idx) { return objects_[idx]; }
|
|
TYPE *pointer(ObjectIdx idx) { return &objects_[idx]; }
|
|
|
|
private:
|
|
uint32_t size_;
|
|
TYPE *objects_;
|
|
};
|
|
|
|
template <class TYPE>
|
|
ArrayBlock<TYPE>::ArrayBlock(uint32_t size) :
|
|
size_(size),
|
|
objects_(new TYPE[size])
|
|
{
|
|
}
|
|
|
|
template <class TYPE>
|
|
ArrayBlock<TYPE>::~ArrayBlock()
|
|
{
|
|
delete [] objects_;
|
|
}
|
|
|
|
} // Namespace
|