Optimize DefUseManager allocations (#4709)
* Optimize DefUseManager allocations Saves around 30-35% of compilation time. For inst->use_ids, use a pool linked list instead of allocating vectors for every instruction. For inst->uses, use a "PooledLinkedList"' -- a linked list that has shared storage for all nodes. Neither re-use nodes, instead we do a bulk compaction operation when too much memory is being wasted (tuneable). Includes separate PooledLinkedList templated datastructure, a very special case construct, but split out to make the code a little easier to understand.
This commit is contained in:
Родитель
a123632ed9
Коммит
d18d0d92e5
1
BUILD.gn
1
BUILD.gn
|
@ -455,6 +455,7 @@ static_library("spvtools") {
|
|||
"source/util/make_unique.h",
|
||||
"source/util/parse_number.cpp",
|
||||
"source/util/parse_number.h",
|
||||
"source/util/pooled_linked_list.h",
|
||||
"source/util/small_vector.h",
|
||||
"source/util/string_utils.cpp",
|
||||
"source/util/string_utils.h",
|
||||
|
|
|
@ -228,6 +228,7 @@ set(SPIRV_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/util/hex_float.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util/make_unique.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util/parse_number.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util/pooled_linked_list.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util/small_vector.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util/string_utils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util/timer.h
|
||||
|
|
|
@ -13,11 +13,23 @@
|
|||
// limitations under the License.
|
||||
|
||||
#include "source/opt/def_use_manager.h"
|
||||
#include "source/util/make_unique.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
namespace analysis {
|
||||
|
||||
// Don't compact before we have a reasonable number of ids allocated (~32kb).
|
||||
static const size_t kCompactThresholdMinTotalIds = (8 * 1024);
|
||||
// Compact when fewer than this fraction of the storage is used (should be 2^n
|
||||
// for performance).
|
||||
static const size_t kCompactThresholdFractionFreeIds = 8;
|
||||
|
||||
DefUseManager::DefUseManager() {
|
||||
use_pool_ = MakeUnique<UseListPool>();
|
||||
used_id_pool_ = MakeUnique<UsedIdListPool>();
|
||||
}
|
||||
|
||||
void DefUseManager::AnalyzeInstDef(Instruction* inst) {
|
||||
const uint32_t def_id = inst->result_id();
|
||||
if (def_id != 0) {
|
||||
|
@ -34,15 +46,15 @@ void DefUseManager::AnalyzeInstDef(Instruction* inst) {
|
|||
}
|
||||
|
||||
void DefUseManager::AnalyzeInstUse(Instruction* inst) {
|
||||
// It might have existed before.
|
||||
EraseUseRecordsOfOperandIds(inst);
|
||||
|
||||
// Create entry for the given instruction. Note that the instruction may
|
||||
// not have any in-operands. In such cases, we still need a entry for those
|
||||
// instructions so this manager knows it has seen the instruction later.
|
||||
auto* used_ids = &inst_to_used_ids_[inst];
|
||||
if (used_ids->size()) {
|
||||
EraseUseRecordsOfOperandIds(inst);
|
||||
used_ids = &inst_to_used_ids_[inst];
|
||||
}
|
||||
used_ids->clear(); // It might have existed before.
|
||||
UsedIdList& used_ids =
|
||||
inst_to_used_id_.insert({inst, UsedIdList(used_id_pool_.get())})
|
||||
.first->second;
|
||||
|
||||
for (uint32_t i = 0; i < inst->NumOperands(); ++i) {
|
||||
switch (inst->GetOperand(i).type) {
|
||||
|
@ -54,8 +66,17 @@ void DefUseManager::AnalyzeInstUse(Instruction* inst) {
|
|||
uint32_t use_id = inst->GetSingleWordOperand(i);
|
||||
Instruction* def = GetDef(use_id);
|
||||
assert(def && "Definition is not registered.");
|
||||
id_to_users_.insert(UserEntry{def, inst});
|
||||
used_ids->push_back(use_id);
|
||||
|
||||
// Add to inst's use records
|
||||
used_ids.push_back(use_id);
|
||||
|
||||
// Add to the users, taking care to avoid adding duplicates. We know
|
||||
// the duplicate for this instruction will always be at the tail.
|
||||
UseList& list = inst_to_users_.insert({def, UseList(use_pool_.get())})
|
||||
.first->second;
|
||||
if (list.empty() || list.back() != inst) {
|
||||
list.push_back(inst);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
|
@ -94,23 +115,6 @@ const Instruction* DefUseManager::GetDef(uint32_t id) const {
|
|||
return iter->second;
|
||||
}
|
||||
|
||||
DefUseManager::IdToUsersMap::const_iterator DefUseManager::UsersBegin(
|
||||
const Instruction* def) const {
|
||||
return id_to_users_.lower_bound(
|
||||
UserEntry{const_cast<Instruction*>(def), nullptr});
|
||||
}
|
||||
|
||||
bool DefUseManager::UsersNotEnd(const IdToUsersMap::const_iterator& iter,
|
||||
const IdToUsersMap::const_iterator& cached_end,
|
||||
const Instruction* inst) const {
|
||||
return (iter != cached_end && iter->def == inst);
|
||||
}
|
||||
|
||||
bool DefUseManager::UsersNotEnd(const IdToUsersMap::const_iterator& iter,
|
||||
const Instruction* inst) const {
|
||||
return UsersNotEnd(iter, id_to_users_.end(), inst);
|
||||
}
|
||||
|
||||
bool DefUseManager::WhileEachUser(
|
||||
const Instruction* def, const std::function<bool(Instruction*)>& f) const {
|
||||
// Ensure that |def| has been registered.
|
||||
|
@ -118,9 +122,11 @@ bool DefUseManager::WhileEachUser(
|
|||
"Definition is not registered.");
|
||||
if (!def->HasResultId()) return true;
|
||||
|
||||
auto end = id_to_users_.end();
|
||||
for (auto iter = UsersBegin(def); UsersNotEnd(iter, end, def); ++iter) {
|
||||
if (!f(iter->user)) return false;
|
||||
auto iter = inst_to_users_.find(def);
|
||||
if (iter != inst_to_users_.end()) {
|
||||
for (Instruction* user : iter->second) {
|
||||
if (!f(user)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -151,14 +157,15 @@ bool DefUseManager::WhileEachUse(
|
|||
"Definition is not registered.");
|
||||
if (!def->HasResultId()) return true;
|
||||
|
||||
auto end = id_to_users_.end();
|
||||
for (auto iter = UsersBegin(def); UsersNotEnd(iter, end, def); ++iter) {
|
||||
Instruction* user = iter->user;
|
||||
for (uint32_t idx = 0; idx != user->NumOperands(); ++idx) {
|
||||
const Operand& op = user->GetOperand(idx);
|
||||
if (op.type != SPV_OPERAND_TYPE_RESULT_ID && spvIsIdType(op.type)) {
|
||||
if (def->result_id() == op.words[0]) {
|
||||
if (!f(user, idx)) return false;
|
||||
auto iter = inst_to_users_.find(def);
|
||||
if (iter != inst_to_users_.end()) {
|
||||
for (Instruction* user : iter->second) {
|
||||
for (uint32_t idx = 0; idx != user->NumOperands(); ++idx) {
|
||||
const Operand& op = user->GetOperand(idx);
|
||||
if (op.type != SPV_OPERAND_TYPE_RESULT_ID && spvIsIdType(op.type)) {
|
||||
if (def->result_id() == op.words[0]) {
|
||||
if (!f(user, idx)) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -230,17 +237,18 @@ void DefUseManager::AnalyzeDefUse(Module* module) {
|
|||
}
|
||||
|
||||
void DefUseManager::ClearInst(Instruction* inst) {
|
||||
auto iter = inst_to_used_ids_.find(inst);
|
||||
if (iter != inst_to_used_ids_.end()) {
|
||||
if (inst_to_used_id_.find(inst) != inst_to_used_id_.end()) {
|
||||
EraseUseRecordsOfOperandIds(inst);
|
||||
if (inst->result_id() != 0) {
|
||||
// Remove all uses of this inst.
|
||||
auto users_begin = UsersBegin(inst);
|
||||
auto end = id_to_users_.end();
|
||||
auto new_end = users_begin;
|
||||
for (; UsersNotEnd(new_end, end, inst); ++new_end) {
|
||||
uint32_t const result_id = inst->result_id();
|
||||
if (result_id != 0) {
|
||||
// For each using instruction, remove result_id from their used ids.
|
||||
auto iter = inst_to_users_.find(inst);
|
||||
if (iter != inst_to_users_.end()) {
|
||||
for (Instruction* use : iter->second) {
|
||||
inst_to_used_id_.at(use).remove_first(result_id);
|
||||
}
|
||||
inst_to_users_.erase(iter);
|
||||
}
|
||||
id_to_users_.erase(users_begin, new_end);
|
||||
id_to_def_.erase(inst->result_id());
|
||||
}
|
||||
}
|
||||
|
@ -249,16 +257,48 @@ void DefUseManager::ClearInst(Instruction* inst) {
|
|||
void DefUseManager::EraseUseRecordsOfOperandIds(const Instruction* inst) {
|
||||
// Go through all ids used by this instruction, remove this instruction's
|
||||
// uses of them.
|
||||
auto iter = inst_to_used_ids_.find(inst);
|
||||
if (iter != inst_to_used_ids_.end()) {
|
||||
for (auto use_id : iter->second) {
|
||||
id_to_users_.erase(
|
||||
UserEntry{GetDef(use_id), const_cast<Instruction*>(inst)});
|
||||
auto iter = inst_to_used_id_.find(inst);
|
||||
if (iter != inst_to_used_id_.end()) {
|
||||
const UsedIdList& used_ids = iter->second;
|
||||
for (uint32_t def_id : used_ids) {
|
||||
auto def_iter = inst_to_users_.find(GetDef(def_id));
|
||||
if (def_iter != inst_to_users_.end()) {
|
||||
def_iter->second.remove_first(const_cast<Instruction*>(inst));
|
||||
}
|
||||
}
|
||||
inst_to_used_id_.erase(inst);
|
||||
|
||||
// If we're using only a fraction of the space in used_ids_, compact storage
|
||||
// to prevent memory usage from being unbounded.
|
||||
if (used_id_pool_->total_nodes() > kCompactThresholdMinTotalIds &&
|
||||
used_id_pool_->used_nodes() <
|
||||
used_id_pool_->total_nodes() / kCompactThresholdFractionFreeIds) {
|
||||
CompactStorage();
|
||||
}
|
||||
inst_to_used_ids_.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void DefUseManager::CompactStorage() {
|
||||
CompactUseRecords();
|
||||
CompactUsedIds();
|
||||
}
|
||||
|
||||
void DefUseManager::CompactUseRecords() {
|
||||
std::unique_ptr<UseListPool> new_pool = MakeUnique<UseListPool>();
|
||||
for (auto& iter : inst_to_users_) {
|
||||
iter.second.move_nodes(new_pool.get());
|
||||
}
|
||||
use_pool_ = std::move(new_pool);
|
||||
}
|
||||
|
||||
void DefUseManager::CompactUsedIds() {
|
||||
std::unique_ptr<UsedIdListPool> new_pool = MakeUnique<UsedIdListPool>();
|
||||
for (auto& iter : inst_to_used_id_) {
|
||||
iter.second.move_nodes(new_pool.get());
|
||||
}
|
||||
used_id_pool_ = std::move(new_pool);
|
||||
}
|
||||
|
||||
bool CompareAndPrintDifferences(const DefUseManager& lhs,
|
||||
const DefUseManager& rhs) {
|
||||
bool same = true;
|
||||
|
@ -277,34 +317,52 @@ bool CompareAndPrintDifferences(const DefUseManager& lhs,
|
|||
same = false;
|
||||
}
|
||||
|
||||
if (lhs.id_to_users_ != rhs.id_to_users_) {
|
||||
for (auto p : lhs.id_to_users_) {
|
||||
if (rhs.id_to_users_.count(p) == 0) {
|
||||
printf("Diff in id_to_users: missing value in rhs\n");
|
||||
}
|
||||
for (const auto& l : lhs.inst_to_used_id_) {
|
||||
std::set<uint32_t> ul, ur;
|
||||
lhs.ForEachUse(l.first,
|
||||
[&ul](Instruction*, uint32_t id) { ul.insert(id); });
|
||||
rhs.ForEachUse(l.first,
|
||||
[&ur](Instruction*, uint32_t id) { ur.insert(id); });
|
||||
if (ul.size() != ur.size()) {
|
||||
printf(
|
||||
"Diff in inst_to_used_id_: different number of used ids (%zu != %zu)",
|
||||
ul.size(), ur.size());
|
||||
same = false;
|
||||
} else if (ul != ur) {
|
||||
printf("Diff in inst_to_used_id_: different used ids\n");
|
||||
same = false;
|
||||
}
|
||||
for (auto p : rhs.id_to_users_) {
|
||||
if (lhs.id_to_users_.count(p) == 0) {
|
||||
printf("Diff in id_to_users: missing value in lhs\n");
|
||||
}
|
||||
}
|
||||
for (const auto& r : rhs.inst_to_used_id_) {
|
||||
auto iter_l = lhs.inst_to_used_id_.find(r.first);
|
||||
if (r.second.empty() &&
|
||||
!(iter_l == lhs.inst_to_used_id_.end() || iter_l->second.empty())) {
|
||||
printf("Diff in inst_to_used_id_: unexpected instr in rhs\n");
|
||||
same = false;
|
||||
}
|
||||
same = false;
|
||||
}
|
||||
|
||||
if (lhs.inst_to_used_ids_ != rhs.inst_to_used_ids_) {
|
||||
for (auto p : lhs.inst_to_used_ids_) {
|
||||
if (rhs.inst_to_used_ids_.count(p.first) == 0) {
|
||||
printf("Diff in inst_to_used_ids: missing value in rhs\n");
|
||||
}
|
||||
for (const auto& l : lhs.inst_to_users_) {
|
||||
std::set<Instruction*> ul, ur;
|
||||
lhs.ForEachUser(l.first, [&ul](Instruction* use) { ul.insert(use); });
|
||||
rhs.ForEachUser(l.first, [&ur](Instruction* use) { ur.insert(use); });
|
||||
if (ul.size() != ur.size()) {
|
||||
printf("Diff in inst_to_users_: different number of users (%zu != %zu)",
|
||||
ul.size(), ur.size());
|
||||
same = false;
|
||||
} else if (ul != ur) {
|
||||
printf("Diff in inst_to_users_: different users\n");
|
||||
same = false;
|
||||
}
|
||||
}
|
||||
for (const auto& r : rhs.inst_to_users_) {
|
||||
auto iter_l = lhs.inst_to_users_.find(r.first);
|
||||
if (r.second.empty() &&
|
||||
!(iter_l == lhs.inst_to_users_.end() || iter_l->second.empty())) {
|
||||
printf("Diff in inst_to_users_: unexpected instr in rhs\n");
|
||||
same = false;
|
||||
}
|
||||
for (auto p : rhs.inst_to_used_ids_) {
|
||||
if (lhs.inst_to_used_ids_.count(p.first) == 0) {
|
||||
printf("Diff in inst_to_used_ids: missing value in lhs\n");
|
||||
}
|
||||
}
|
||||
same = false;
|
||||
}
|
||||
|
||||
return same;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "source/opt/instruction.h"
|
||||
#include "source/opt/module.h"
|
||||
#include "source/util/pooled_linked_list.h"
|
||||
#include "spirv-tools/libspirv.hpp"
|
||||
|
||||
namespace spvtools {
|
||||
|
@ -49,50 +50,6 @@ inline bool operator<(const Use& lhs, const Use& rhs) {
|
|||
return lhs.operand_index < rhs.operand_index;
|
||||
}
|
||||
|
||||
// Definition should never be null. User can be null, however, such an entry
|
||||
// should be used only for searching (e.g. all users of a particular definition)
|
||||
// and never stored in a container.
|
||||
struct UserEntry {
|
||||
Instruction* def;
|
||||
Instruction* user;
|
||||
};
|
||||
|
||||
inline bool operator==(const UserEntry& lhs, const UserEntry& rhs) {
|
||||
return lhs.def == rhs.def && lhs.user == rhs.user;
|
||||
}
|
||||
|
||||
// Orders UserEntry for use in associative containers (i.e. less than ordering).
|
||||
//
|
||||
// The definition of an UserEntry is treated as the major key and the users as
|
||||
// the minor key so that all the users of a particular definition are
|
||||
// consecutive in a container.
|
||||
//
|
||||
// A null user always compares less than a real user. This is done to provide
|
||||
// easy values to search for the beginning of the users of a particular
|
||||
// definition (i.e. using {def, nullptr}).
|
||||
struct UserEntryLess {
|
||||
bool operator()(const UserEntry& lhs, const UserEntry& rhs) const {
|
||||
// If lhs.def and rhs.def are both null, fall through to checking the
|
||||
// second entries.
|
||||
if (!lhs.def && rhs.def) return true;
|
||||
if (lhs.def && !rhs.def) return false;
|
||||
|
||||
// If neither definition is null, then compare unique ids.
|
||||
if (lhs.def && rhs.def) {
|
||||
if (lhs.def->unique_id() < rhs.def->unique_id()) return true;
|
||||
if (rhs.def->unique_id() < lhs.def->unique_id()) return false;
|
||||
}
|
||||
|
||||
// Return false on equality.
|
||||
if (!lhs.user && !rhs.user) return false;
|
||||
if (!lhs.user) return true;
|
||||
if (!rhs.user) return false;
|
||||
|
||||
// If neither user is null then compare unique ids.
|
||||
return lhs.user->unique_id() < rhs.user->unique_id();
|
||||
}
|
||||
};
|
||||
|
||||
// A class for analyzing and managing defs and uses in an Module.
|
||||
class DefUseManager {
|
||||
public:
|
||||
|
@ -102,7 +59,7 @@ class DefUseManager {
|
|||
// will be communicated to the outside via the given message |consumer|. This
|
||||
// instance only keeps a reference to the |consumer|, so the |consumer| should
|
||||
// outlive this instance.
|
||||
DefUseManager(Module* module) { AnalyzeDefUse(module); }
|
||||
DefUseManager(Module* module) : DefUseManager() { AnalyzeDefUse(module); }
|
||||
|
||||
DefUseManager(const DefUseManager&) = delete;
|
||||
DefUseManager(DefUseManager&&) = delete;
|
||||
|
@ -214,35 +171,36 @@ class DefUseManager {
|
|||
// uses.
|
||||
void UpdateDefUse(Instruction* inst);
|
||||
|
||||
// Compacts any internal storage to save memory.
|
||||
void CompactStorage();
|
||||
|
||||
private:
|
||||
using IdToUsersMap = std::set<UserEntry, UserEntryLess>;
|
||||
using InstToUsedIdsMap =
|
||||
std::unordered_map<const Instruction*, std::vector<uint32_t>>;
|
||||
using UseList = spvtools::utils::PooledLinkedList<Instruction*>;
|
||||
using UseListPool = spvtools::utils::PooledLinkedListNodes<Instruction*>;
|
||||
// Stores linked lists of Instructions using a def.
|
||||
using InstToUsersMap = std::unordered_map<const Instruction*, UseList>;
|
||||
|
||||
// Returns the first location that {|def|, nullptr} could be inserted into the
|
||||
// users map without violating ordering.
|
||||
IdToUsersMap::const_iterator UsersBegin(const Instruction* def) const;
|
||||
using UsedIdList = spvtools::utils::PooledLinkedList<uint32_t>;
|
||||
using UsedIdListPool = spvtools::utils::PooledLinkedListNodes<uint32_t>;
|
||||
// Stores mapping from instruction to their UsedIdRange.
|
||||
using InstToUsedIdMap = std::unordered_map<const Instruction*, UsedIdList>;
|
||||
|
||||
// Returns true if |iter| has not reached the end of |def|'s users.
|
||||
//
|
||||
// In the first version |iter| is compared against the end of the map for
|
||||
// validity before other checks. In the second version, |iter| is compared
|
||||
// against |cached_end| for validity before other checks. This allows caching
|
||||
// the map's end which is a performance improvement on some platforms.
|
||||
bool UsersNotEnd(const IdToUsersMap::const_iterator& iter,
|
||||
const Instruction* def) const;
|
||||
bool UsersNotEnd(const IdToUsersMap::const_iterator& iter,
|
||||
const IdToUsersMap::const_iterator& cached_end,
|
||||
const Instruction* def) const;
|
||||
DefUseManager();
|
||||
|
||||
// Analyzes the defs and uses in the given |module| and populates data
|
||||
// structures in this class. Does nothing if |module| is nullptr.
|
||||
void AnalyzeDefUse(Module* module);
|
||||
|
||||
IdToDefMap id_to_def_; // Mapping from ids to their definitions
|
||||
IdToUsersMap id_to_users_; // Mapping from ids to their users
|
||||
// Mapping from instructions to the ids used in the instruction.
|
||||
InstToUsedIdsMap inst_to_used_ids_;
|
||||
// Removes unused entries in used_records_ and used_ids_.
|
||||
void CompactUseRecords();
|
||||
void CompactUsedIds();
|
||||
|
||||
IdToDefMap id_to_def_; // Mapping from ids to their definitions
|
||||
InstToUsersMap inst_to_users_; // Map from def to uses.
|
||||
std::unique_ptr<UseListPool> use_pool_;
|
||||
|
||||
std::unique_ptr<UsedIdListPool> used_id_pool_;
|
||||
InstToUsedIdMap inst_to_used_id_; // Map from instruction to used ids.
|
||||
};
|
||||
|
||||
} // namespace analysis
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
// Copyright (c) 2021 The Khronos Group Inc.
|
||||
//
|
||||
// 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.
|
||||
|
||||
#ifndef SOURCE_UTIL_POOLED_LINKED_LIST_H_
|
||||
#define SOURCE_UTIL_POOLED_LINKED_LIST_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace spvtools {
|
||||
namespace utils {
|
||||
|
||||
// Shared storage of nodes for PooledLinkedList.
|
||||
template <typename T>
|
||||
class PooledLinkedListNodes {
|
||||
public:
|
||||
struct Node {
|
||||
Node(T e, int32_t n = -1) : element(e), next(n) {}
|
||||
|
||||
T element = {};
|
||||
int32_t next = -1;
|
||||
};
|
||||
|
||||
PooledLinkedListNodes() = default;
|
||||
PooledLinkedListNodes(const PooledLinkedListNodes&) = delete;
|
||||
PooledLinkedListNodes& operator=(const PooledLinkedListNodes&) = delete;
|
||||
|
||||
PooledLinkedListNodes(PooledLinkedListNodes&& that) {
|
||||
*this = std::move(that);
|
||||
}
|
||||
|
||||
PooledLinkedListNodes& operator=(PooledLinkedListNodes&& that) {
|
||||
vec_ = std::move(that.vec_);
|
||||
free_nodes_ = that.free_nodes_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
size_t total_nodes() { return vec_.size(); }
|
||||
size_t free_nodes() { return free_nodes_; }
|
||||
size_t used_nodes() { return total_nodes() - free_nodes(); }
|
||||
|
||||
private:
|
||||
template <typename ListT>
|
||||
friend class PooledLinkedList;
|
||||
|
||||
Node& at(int32_t index) { return vec_[index]; }
|
||||
const Node& at(int32_t index) const { return vec_[index]; }
|
||||
|
||||
int32_t insert(T element) {
|
||||
int32_t index = int32_t(vec_.size());
|
||||
vec_.emplace_back(element);
|
||||
return index;
|
||||
}
|
||||
|
||||
std::vector<Node> vec_;
|
||||
size_t free_nodes_ = 0;
|
||||
};
|
||||
|
||||
// Implements a linked-list where list nodes come from a shared pool. This is
|
||||
// meant to be used in scenarios where it is desirable to avoid many small
|
||||
// allocations.
|
||||
//
|
||||
// Instead of pointers, the list uses indices to allow the underlying storage
|
||||
// to be modified without needing to modify the list. When removing elements
|
||||
// from the list, nodes are not deleted or recycled: to reclaim unused space,
|
||||
// perform a sequence of |move_nodes| operations into a temporary pool, which
|
||||
// then is moved into the old pool.
|
||||
//
|
||||
// This does *not* attempt to implement a full stl-compatible interface.
|
||||
template <typename T>
|
||||
class PooledLinkedList {
|
||||
public:
|
||||
using NodePool = PooledLinkedListNodes<T>;
|
||||
using Node = typename NodePool::Node;
|
||||
|
||||
PooledLinkedList() = delete;
|
||||
PooledLinkedList(NodePool* nodes) : nodes_(nodes) {}
|
||||
|
||||
// Shared iterator implementation (for iterator and const_iterator).
|
||||
template <typename ElementT, typename PoolT>
|
||||
class iterator_base {
|
||||
public:
|
||||
iterator_base(const iterator_base& i)
|
||||
: nodes_(i.nodes_), index_(i.index_) {}
|
||||
|
||||
iterator_base& operator++() {
|
||||
index_ = nodes_->at(index_).next;
|
||||
return *this;
|
||||
}
|
||||
|
||||
iterator_base& operator=(const iterator_base& i) {
|
||||
nodes_ = i.nodes_;
|
||||
index_ = i.index_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ElementT& operator*() const { return nodes_->at(index_).element; }
|
||||
ElementT* operator->() const { return &nodes_->at(index_).element; }
|
||||
|
||||
friend inline bool operator==(const iterator_base& lhs,
|
||||
const iterator_base& rhs) {
|
||||
return lhs.nodes_ == rhs.nodes_ && lhs.index_ == rhs.index_;
|
||||
}
|
||||
friend inline bool operator!=(const iterator_base& lhs,
|
||||
const iterator_base& rhs) {
|
||||
return lhs.nodes_ != rhs.nodes_ || lhs.index_ != rhs.index_;
|
||||
}
|
||||
|
||||
// Define standard iterator types needs so this class can be
|
||||
// used with <algorithms>.
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using value_type = ElementT;
|
||||
using pointer = ElementT*;
|
||||
using const_pointer = const ElementT*;
|
||||
using reference = ElementT&;
|
||||
using const_reference = const ElementT&;
|
||||
using size_type = size_t;
|
||||
|
||||
private:
|
||||
friend PooledLinkedList;
|
||||
|
||||
iterator_base(PoolT* pool, int32_t index) : nodes_(pool), index_(index) {}
|
||||
|
||||
PoolT* nodes_;
|
||||
int32_t index_ = -1;
|
||||
};
|
||||
|
||||
using iterator = iterator_base<T, std::vector<Node>>;
|
||||
using const_iterator = iterator_base<const T, const std::vector<Node>>;
|
||||
|
||||
bool empty() const { return head_ == -1; }
|
||||
|
||||
T& front() { return nodes_->at(head_).element; }
|
||||
T& back() { return nodes_->at(tail_).element; }
|
||||
const T& front() const { return nodes_->at(head_).element; }
|
||||
const T& back() const { return nodes_->at(tail_).element; }
|
||||
|
||||
iterator begin() { return iterator(&nodes_->vec_, head_); }
|
||||
iterator end() { return iterator(&nodes_->vec_, -1); }
|
||||
const_iterator begin() const { return const_iterator(&nodes_->vec_, head_); }
|
||||
const_iterator end() const { return const_iterator(&nodes_->vec_, -1); }
|
||||
|
||||
// Inserts |element| at the back of the list.
|
||||
void push_back(T element) {
|
||||
int32_t new_tail = nodes_->insert(element);
|
||||
if (head_ == -1) {
|
||||
head_ = new_tail;
|
||||
tail_ = new_tail;
|
||||
} else {
|
||||
nodes_->at(tail_).next = new_tail;
|
||||
tail_ = new_tail;
|
||||
}
|
||||
}
|
||||
|
||||
// Removes the first occurrence of |element| from the list.
|
||||
// Returns if |element| was removed.
|
||||
bool remove_first(T element) {
|
||||
int32_t* prev_next = &head_;
|
||||
for (int32_t prev_index = -1, index = head_; index != -1; /**/) {
|
||||
auto& node = nodes_->at(index);
|
||||
if (node.element == element) {
|
||||
// Snip from of the list, optionally fixing up tail pointer.
|
||||
if (tail_ == index) {
|
||||
assert(node.next == -1);
|
||||
tail_ = prev_index;
|
||||
}
|
||||
*prev_next = node.next;
|
||||
nodes_->free_nodes_++;
|
||||
return true;
|
||||
} else {
|
||||
prev_next = &node.next;
|
||||
}
|
||||
prev_index = index;
|
||||
index = node.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns the PooledLinkedListNodes that owns this list's nodes.
|
||||
NodePool* pool() { return nodes_; }
|
||||
|
||||
// Moves the nodes in this list into |new_pool|, providing a way to compact
|
||||
// storage and reclaim unused space.
|
||||
//
|
||||
// Upon completing a sequence of |move_nodes| calls, you must ensure you
|
||||
// retain ownership of the new storage your lists point to. Example usage:
|
||||
//
|
||||
// unique_ptr<NodePool> new_pool = ...;
|
||||
// for (PooledLinkedList& list : lists) {
|
||||
// list.move_to(new_pool);
|
||||
// }
|
||||
// my_pool_ = std::move(new_pool);
|
||||
void move_nodes(NodePool* new_pool) {
|
||||
// Be sure to construct the list in the same order, instead of simply
|
||||
// doing a sequence of push_backs.
|
||||
int32_t prev_entry = -1;
|
||||
int32_t nodes_freed = 0;
|
||||
for (int32_t index = head_; index != -1; nodes_freed++) {
|
||||
const auto& node = nodes_->at(index);
|
||||
int32_t this_entry = new_pool->insert(node.element);
|
||||
index = node.next;
|
||||
if (prev_entry == -1) {
|
||||
head_ = this_entry;
|
||||
} else {
|
||||
new_pool->at(prev_entry).next = this_entry;
|
||||
}
|
||||
prev_entry = this_entry;
|
||||
}
|
||||
tail_ = prev_entry;
|
||||
// Update our old pool's free count, now we're a member of the new pool.
|
||||
nodes_->free_nodes_ += nodes_freed;
|
||||
nodes_ = new_pool;
|
||||
}
|
||||
|
||||
private:
|
||||
NodePool* nodes_;
|
||||
int32_t head_ = -1;
|
||||
int32_t tail_ = -1;
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_UTIL_POOLED_LINKED_LIST_H_
|
|
@ -950,6 +950,369 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
);
|
||||
// clang-format on
|
||||
|
||||
// We re-use the same replace usecases, we need to similarly exercise the
|
||||
// DefUseManager by replacing instructions and uses.
|
||||
using CompactIdempotence = ::testing::TestWithParam<ReplaceUseCase>;
|
||||
|
||||
TEST_P(CompactIdempotence, Case) {
|
||||
const auto& tc = GetParam();
|
||||
|
||||
// Build module.
|
||||
const std::vector<const char*> text = {tc.before};
|
||||
std::unique_ptr<IRContext> context =
|
||||
BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text),
|
||||
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
ASSERT_NE(nullptr, context);
|
||||
|
||||
// Force a re-build of def-use manager.
|
||||
context->InvalidateAnalyses(IRContext::Analysis::kAnalysisDefUse);
|
||||
(void)context->get_def_use_mgr();
|
||||
|
||||
// Do the substitution.
|
||||
for (const auto& candidate : tc.candidates) {
|
||||
context->ReplaceAllUsesWith(candidate.first, candidate.second);
|
||||
}
|
||||
|
||||
// Ensure new/uncompacted managers produce the same result
|
||||
EXPECT_TRUE(CompareAndPrintDifferences(*context->get_def_use_mgr(),
|
||||
DefUseManager(context->module())));
|
||||
|
||||
EXPECT_EQ(tc.after, DisassembleModule(context->module()));
|
||||
CheckDef(tc.du, context->get_def_use_mgr()->id_to_defs());
|
||||
CheckUse(tc.du, context->get_def_use_mgr(), context->module()->IdBound());
|
||||
|
||||
// Compare again after compacting the defuse manager's storage
|
||||
context->get_def_use_mgr()->CompactStorage();
|
||||
|
||||
CheckDef(tc.du, context->get_def_use_mgr()->id_to_defs());
|
||||
CheckUse(tc.du, context->get_def_use_mgr(), context->module()->IdBound());
|
||||
|
||||
EXPECT_TRUE(CompareAndPrintDifferences(*context->get_def_use_mgr(),
|
||||
DefUseManager(context->module())));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
TestCase, CompactIdempotence,
|
||||
::testing::ValuesIn(std::vector<ReplaceUseCase>{
|
||||
{ // no use, no replace request
|
||||
"", {}, "", {},
|
||||
},
|
||||
{ // replace one use
|
||||
"%1 = OpTypeBool "
|
||||
"%2 = OpTypeVector %1 3 "
|
||||
"%3 = OpTypeInt 32 0 ",
|
||||
{{1, 3}},
|
||||
"%1 = OpTypeBool\n"
|
||||
"%2 = OpTypeVector %3 3\n"
|
||||
"%3 = OpTypeInt 32 0",
|
||||
{
|
||||
{ // defs
|
||||
{1, "%1 = OpTypeBool"},
|
||||
{2, "%2 = OpTypeVector %3 3"},
|
||||
{3, "%3 = OpTypeInt 32 0"},
|
||||
},
|
||||
{ // uses
|
||||
{3, {"%2 = OpTypeVector %3 3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // replace and then replace back
|
||||
"%1 = OpTypeBool "
|
||||
"%2 = OpTypeVector %1 3 "
|
||||
"%3 = OpTypeInt 32 0",
|
||||
{{1, 3}, {3, 1}},
|
||||
"%1 = OpTypeBool\n"
|
||||
"%2 = OpTypeVector %1 3\n"
|
||||
"%3 = OpTypeInt 32 0",
|
||||
{
|
||||
{ // defs
|
||||
{1, "%1 = OpTypeBool"},
|
||||
{2, "%2 = OpTypeVector %1 3"},
|
||||
{3, "%3 = OpTypeInt 32 0"},
|
||||
},
|
||||
{ // uses
|
||||
{1, {"%2 = OpTypeVector %1 3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // replace with the same id
|
||||
"%1 = OpTypeBool "
|
||||
"%2 = OpTypeVector %1 3",
|
||||
{{1, 1}, {2, 2}, {3, 3}},
|
||||
"%1 = OpTypeBool\n"
|
||||
"%2 = OpTypeVector %1 3",
|
||||
{
|
||||
{ // defs
|
||||
{1, "%1 = OpTypeBool"},
|
||||
{2, "%2 = OpTypeVector %1 3"},
|
||||
},
|
||||
{ // uses
|
||||
{1, {"%2 = OpTypeVector %1 3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // replace in sequence
|
||||
"%1 = OpTypeBool "
|
||||
"%2 = OpTypeVector %1 3 "
|
||||
"%3 = OpTypeInt 32 0 "
|
||||
"%4 = OpTypeInt 32 1 ",
|
||||
{{1, 3}, {3, 4}},
|
||||
"%1 = OpTypeBool\n"
|
||||
"%2 = OpTypeVector %4 3\n"
|
||||
"%3 = OpTypeInt 32 0\n"
|
||||
"%4 = OpTypeInt 32 1",
|
||||
{
|
||||
{ // defs
|
||||
{1, "%1 = OpTypeBool"},
|
||||
{2, "%2 = OpTypeVector %4 3"},
|
||||
{3, "%3 = OpTypeInt 32 0"},
|
||||
{4, "%4 = OpTypeInt 32 1"},
|
||||
},
|
||||
{ // uses
|
||||
{4, {"%2 = OpTypeVector %4 3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // replace multiple uses
|
||||
"%1 = OpTypeBool "
|
||||
"%2 = OpTypeVector %1 2 "
|
||||
"%3 = OpTypeVector %1 3 "
|
||||
"%4 = OpTypeVector %1 4 "
|
||||
"%5 = OpTypeMatrix %2 2 "
|
||||
"%6 = OpTypeMatrix %3 3 "
|
||||
"%7 = OpTypeMatrix %4 4 "
|
||||
"%8 = OpTypeInt 32 0 "
|
||||
"%9 = OpTypeInt 32 1 "
|
||||
"%10 = OpTypeInt 64 0",
|
||||
{{1, 8}, {2, 9}, {4, 10}},
|
||||
"%1 = OpTypeBool\n"
|
||||
"%2 = OpTypeVector %8 2\n"
|
||||
"%3 = OpTypeVector %8 3\n"
|
||||
"%4 = OpTypeVector %8 4\n"
|
||||
"%5 = OpTypeMatrix %9 2\n"
|
||||
"%6 = OpTypeMatrix %3 3\n"
|
||||
"%7 = OpTypeMatrix %10 4\n"
|
||||
"%8 = OpTypeInt 32 0\n"
|
||||
"%9 = OpTypeInt 32 1\n"
|
||||
"%10 = OpTypeInt 64 0",
|
||||
{
|
||||
{ // defs
|
||||
{1, "%1 = OpTypeBool"},
|
||||
{2, "%2 = OpTypeVector %8 2"},
|
||||
{3, "%3 = OpTypeVector %8 3"},
|
||||
{4, "%4 = OpTypeVector %8 4"},
|
||||
{5, "%5 = OpTypeMatrix %9 2"},
|
||||
{6, "%6 = OpTypeMatrix %3 3"},
|
||||
{7, "%7 = OpTypeMatrix %10 4"},
|
||||
{8, "%8 = OpTypeInt 32 0"},
|
||||
{9, "%9 = OpTypeInt 32 1"},
|
||||
{10, "%10 = OpTypeInt 64 0"},
|
||||
},
|
||||
{ // uses
|
||||
{8,
|
||||
{
|
||||
"%2 = OpTypeVector %8 2",
|
||||
"%3 = OpTypeVector %8 3",
|
||||
"%4 = OpTypeVector %8 4",
|
||||
}
|
||||
},
|
||||
{9, {"%5 = OpTypeMatrix %9 2"}},
|
||||
{3, {"%6 = OpTypeMatrix %3 3"}},
|
||||
{10, {"%7 = OpTypeMatrix %10 4"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // OpPhi.
|
||||
kOpPhiTestFunction,
|
||||
// replace one id used by OpPhi, replace one id generated by OpPhi
|
||||
{{9, 13}, {11, 9}},
|
||||
"%1 = OpTypeVoid\n"
|
||||
"%6 = OpTypeInt 32 0\n"
|
||||
"%10 = OpTypeFloat 32\n"
|
||||
"%16 = OpTypeBool\n"
|
||||
"%3 = OpTypeFunction %1\n"
|
||||
"%8 = OpConstant %6 0\n"
|
||||
"%18 = OpConstant %6 1\n"
|
||||
"%12 = OpConstant %10 1\n"
|
||||
"%2 = OpFunction %1 None %3\n"
|
||||
"%4 = OpLabel\n"
|
||||
"OpBranch %5\n"
|
||||
|
||||
"%5 = OpLabel\n"
|
||||
"%7 = OpPhi %6 %8 %4 %13 %5\n" // %9 -> %13
|
||||
"%11 = OpPhi %10 %12 %4 %13 %5\n"
|
||||
"%9 = OpIAdd %6 %7 %8\n"
|
||||
"%13 = OpFAdd %10 %9 %12\n" // %11 -> %9
|
||||
"%17 = OpSLessThan %16 %7 %18\n"
|
||||
"OpLoopMerge %19 %5 None\n"
|
||||
"OpBranchConditional %17 %5 %19\n"
|
||||
|
||||
"%19 = OpLabel\n"
|
||||
"OpReturn\n"
|
||||
"OpFunctionEnd",
|
||||
{
|
||||
{ // defs.
|
||||
{1, "%1 = OpTypeVoid"},
|
||||
{2, "%2 = OpFunction %1 None %3"},
|
||||
{3, "%3 = OpTypeFunction %1"},
|
||||
{4, "%4 = OpLabel"},
|
||||
{5, "%5 = OpLabel"},
|
||||
{6, "%6 = OpTypeInt 32 0"},
|
||||
{7, "%7 = OpPhi %6 %8 %4 %13 %5"},
|
||||
{8, "%8 = OpConstant %6 0"},
|
||||
{9, "%9 = OpIAdd %6 %7 %8"},
|
||||
{10, "%10 = OpTypeFloat 32"},
|
||||
{11, "%11 = OpPhi %10 %12 %4 %13 %5"},
|
||||
{12, "%12 = OpConstant %10 1.0"},
|
||||
{13, "%13 = OpFAdd %10 %9 %12"},
|
||||
{16, "%16 = OpTypeBool"},
|
||||
{17, "%17 = OpSLessThan %16 %7 %18"},
|
||||
{18, "%18 = OpConstant %6 1"},
|
||||
{19, "%19 = OpLabel"},
|
||||
},
|
||||
{ // uses
|
||||
{1,
|
||||
{
|
||||
"%2 = OpFunction %1 None %3",
|
||||
"%3 = OpTypeFunction %1",
|
||||
}
|
||||
},
|
||||
{3, {"%2 = OpFunction %1 None %3"}},
|
||||
{4,
|
||||
{
|
||||
"%7 = OpPhi %6 %8 %4 %13 %5",
|
||||
"%11 = OpPhi %10 %12 %4 %13 %5",
|
||||
}
|
||||
},
|
||||
{5,
|
||||
{
|
||||
"OpBranch %5",
|
||||
"%7 = OpPhi %6 %8 %4 %13 %5",
|
||||
"%11 = OpPhi %10 %12 %4 %13 %5",
|
||||
"OpLoopMerge %19 %5 None",
|
||||
"OpBranchConditional %17 %5 %19",
|
||||
}
|
||||
},
|
||||
{6,
|
||||
{
|
||||
// Can't properly check constants
|
||||
// "%8 = OpConstant %6 0",
|
||||
// "%18 = OpConstant %6 1",
|
||||
"%7 = OpPhi %6 %8 %4 %13 %5",
|
||||
"%9 = OpIAdd %6 %7 %8"
|
||||
}
|
||||
},
|
||||
{7,
|
||||
{
|
||||
"%9 = OpIAdd %6 %7 %8",
|
||||
"%17 = OpSLessThan %16 %7 %18",
|
||||
}
|
||||
},
|
||||
{8,
|
||||
{
|
||||
"%7 = OpPhi %6 %8 %4 %13 %5",
|
||||
"%9 = OpIAdd %6 %7 %8",
|
||||
}
|
||||
},
|
||||
{9, {"%13 = OpFAdd %10 %9 %12"}}, // uses of %9 changed from %7 to %13
|
||||
{10,
|
||||
{
|
||||
"%11 = OpPhi %10 %12 %4 %13 %5",
|
||||
// "%12 = OpConstant %10 1",
|
||||
"%13 = OpFAdd %10 %9 %12"
|
||||
}
|
||||
},
|
||||
// no more uses of %11
|
||||
{12,
|
||||
{
|
||||
"%11 = OpPhi %10 %12 %4 %13 %5",
|
||||
"%13 = OpFAdd %10 %9 %12"
|
||||
}
|
||||
},
|
||||
{13, {
|
||||
"%7 = OpPhi %6 %8 %4 %13 %5",
|
||||
"%11 = OpPhi %10 %12 %4 %13 %5",
|
||||
}
|
||||
},
|
||||
{16, {"%17 = OpSLessThan %16 %7 %18"}},
|
||||
{17, {"OpBranchConditional %17 %5 %19"}},
|
||||
{18, {"%17 = OpSLessThan %16 %7 %18"}},
|
||||
{19,
|
||||
{
|
||||
"OpLoopMerge %19 %5 None",
|
||||
"OpBranchConditional %17 %5 %19",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // OpPhi defining and referencing the same id.
|
||||
"%1 = OpTypeBool "
|
||||
"%3 = OpTypeFunction %1 "
|
||||
"%2 = OpConstantTrue %1 "
|
||||
|
||||
"%4 = OpFunction %3 None %1 "
|
||||
"%6 = OpLabel "
|
||||
" OpBranch %7 "
|
||||
"%7 = OpLabel "
|
||||
"%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8
|
||||
" OpBranch %7 "
|
||||
" OpFunctionEnd",
|
||||
{{8, 2}},
|
||||
"%1 = OpTypeBool\n"
|
||||
"%3 = OpTypeFunction %1\n"
|
||||
"%2 = OpConstantTrue %1\n"
|
||||
|
||||
"%4 = OpFunction %3 None %1\n"
|
||||
"%6 = OpLabel\n"
|
||||
"OpBranch %7\n"
|
||||
"%7 = OpLabel\n"
|
||||
"%8 = OpPhi %1 %2 %7 %2 %6\n" // use of %8 changed to %2
|
||||
"OpBranch %7\n"
|
||||
"OpFunctionEnd",
|
||||
{
|
||||
{ // defs
|
||||
{1, "%1 = OpTypeBool"},
|
||||
{2, "%2 = OpConstantTrue %1"},
|
||||
{3, "%3 = OpTypeFunction %1"},
|
||||
{4, "%4 = OpFunction %3 None %1"},
|
||||
{6, "%6 = OpLabel"},
|
||||
{7, "%7 = OpLabel"},
|
||||
{8, "%8 = OpPhi %1 %2 %7 %2 %6"},
|
||||
},
|
||||
{ // uses
|
||||
{1,
|
||||
{
|
||||
"%2 = OpConstantTrue %1",
|
||||
"%3 = OpTypeFunction %1",
|
||||
"%4 = OpFunction %3 None %1",
|
||||
"%8 = OpPhi %1 %2 %7 %2 %6",
|
||||
}
|
||||
},
|
||||
{2,
|
||||
{
|
||||
// Only checking users
|
||||
"%8 = OpPhi %1 %2 %7 %2 %6",
|
||||
}
|
||||
},
|
||||
{3, {"%4 = OpFunction %3 None %1"}},
|
||||
{6, {"%8 = OpPhi %1 %2 %7 %2 %6"}},
|
||||
{7,
|
||||
{
|
||||
"OpBranch %7",
|
||||
"%8 = OpPhi %1 %2 %7 %2 %6",
|
||||
"OpBranch %7",
|
||||
}
|
||||
},
|
||||
// {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
struct KillDefCase {
|
||||
const char* before;
|
||||
std::vector<uint32_t> ids_to_kill;
|
||||
|
|
|
@ -17,6 +17,7 @@ add_spvtools_unittest(TARGET utils
|
|||
bit_vector_test.cpp
|
||||
bitutils_test.cpp
|
||||
hash_combine_test.cpp
|
||||
pooled_linked_list_test.cpp
|
||||
small_vector_test.cpp
|
||||
LIBS SPIRV-Tools-opt
|
||||
)
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright (c) 2021 The Khronos Group Inc.
|
||||
//
|
||||
// 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 <algorithm>
|
||||
#include <list>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "source/util/pooled_linked_list.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace utils {
|
||||
namespace {
|
||||
|
||||
using PooledLinkedListTest = ::testing::Test;
|
||||
|
||||
template <typename T>
|
||||
static std::vector<T> ToVector(const PooledLinkedList<T>& list) {
|
||||
std::vector<T> vec;
|
||||
for (auto it = list.begin(); it != list.end(); ++it) {
|
||||
vec.push_back(*it);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void AppendVector(PooledLinkedList<T>& list, const std::vector<T>& vec) {
|
||||
for (const T& t : vec) {
|
||||
list.push_back(t);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PooledLinkedListTest, Empty) {
|
||||
PooledLinkedListNodes<uint32_t> pool;
|
||||
PooledLinkedList<uint32_t> ll(&pool);
|
||||
EXPECT_TRUE(ll.empty());
|
||||
|
||||
ll.push_back(1u);
|
||||
EXPECT_TRUE(!ll.empty());
|
||||
}
|
||||
|
||||
TEST(PooledLinkedListTest, Iterator) {
|
||||
PooledLinkedListNodes<uint32_t> pool;
|
||||
PooledLinkedList<uint32_t> ll(&pool);
|
||||
|
||||
EXPECT_EQ(ll.begin(), ll.end());
|
||||
|
||||
ll.push_back(1);
|
||||
EXPECT_NE(ll.begin(), ll.end());
|
||||
|
||||
auto it = ll.begin();
|
||||
EXPECT_EQ(*it, 1);
|
||||
++it;
|
||||
EXPECT_EQ(it, ll.end());
|
||||
}
|
||||
|
||||
TEST(PooledLinkedListTest, Iterator_algorithms) {
|
||||
PooledLinkedListNodes<uint32_t> pool;
|
||||
PooledLinkedList<uint32_t> ll(&pool);
|
||||
|
||||
AppendVector(ll, {3, 2, 0, 1});
|
||||
EXPECT_EQ(std::distance(ll.begin(), ll.end()), 4);
|
||||
EXPECT_EQ(*std::min_element(ll.begin(), ll.end()), 0);
|
||||
EXPECT_EQ(*std::max_element(ll.begin(), ll.end()), 3);
|
||||
}
|
||||
|
||||
TEST(PooledLinkedListTest, FrontBack) {
|
||||
PooledLinkedListNodes<uint32_t> pool;
|
||||
PooledLinkedList<uint32_t> ll(&pool);
|
||||
|
||||
ll.push_back(1);
|
||||
EXPECT_EQ(ll.front(), 1);
|
||||
EXPECT_EQ(ll.back(), 1);
|
||||
|
||||
ll.push_back(2);
|
||||
EXPECT_EQ(ll.front(), 1);
|
||||
EXPECT_EQ(ll.back(), 2);
|
||||
}
|
||||
|
||||
TEST(PooledLinkedListTest, PushBack) {
|
||||
const std::vector<uint32_t> vec = {1, 2, 3, 4, 5, 6};
|
||||
|
||||
PooledLinkedListNodes<uint32_t> pool;
|
||||
PooledLinkedList<uint32_t> ll(&pool);
|
||||
|
||||
AppendVector(ll, vec);
|
||||
EXPECT_EQ(vec, ToVector(ll));
|
||||
}
|
||||
|
||||
TEST(PooledLinkedListTest, RemoveFirst) {
|
||||
const std::vector<uint32_t> vec = {1, 2, 3, 4, 5, 6};
|
||||
|
||||
PooledLinkedListNodes<uint32_t> pool;
|
||||
PooledLinkedList<uint32_t> ll(&pool);
|
||||
|
||||
EXPECT_FALSE(ll.remove_first(0));
|
||||
AppendVector(ll, vec);
|
||||
EXPECT_FALSE(ll.remove_first(0));
|
||||
|
||||
std::vector<uint32_t> tmp = vec;
|
||||
while (!tmp.empty()) {
|
||||
size_t mid = tmp.size() / 2;
|
||||
uint32_t elt = tmp[mid];
|
||||
tmp.erase(tmp.begin() + mid);
|
||||
|
||||
EXPECT_TRUE(ll.remove_first(elt));
|
||||
EXPECT_FALSE(ll.remove_first(elt));
|
||||
EXPECT_EQ(tmp, ToVector(ll));
|
||||
}
|
||||
EXPECT_TRUE(ll.empty());
|
||||
}
|
||||
|
||||
TEST(PooledLinkedListTest, RemoveFirst_Duplicates) {
|
||||
const std::vector<uint32_t> vec = {3, 1, 2, 3, 3, 3, 3, 4, 3, 5, 3, 6, 3};
|
||||
|
||||
PooledLinkedListNodes<uint32_t> pool;
|
||||
PooledLinkedList<uint32_t> ll(&pool);
|
||||
AppendVector(ll, vec);
|
||||
|
||||
std::vector<uint32_t> tmp = vec;
|
||||
while (!tmp.empty()) {
|
||||
size_t mid = tmp.size() / 2;
|
||||
uint32_t elt = tmp[mid];
|
||||
tmp.erase(std::find(tmp.begin(), tmp.end(), elt));
|
||||
|
||||
EXPECT_TRUE(ll.remove_first(elt));
|
||||
EXPECT_EQ(tmp, ToVector(ll));
|
||||
}
|
||||
EXPECT_TRUE(ll.empty());
|
||||
}
|
||||
|
||||
TEST(PooledLinkedList, MoveTo) {
|
||||
const std::vector<uint32_t> vec = {1, 2, 3, 4, 5, 6};
|
||||
|
||||
PooledLinkedListNodes<uint32_t> pool;
|
||||
PooledLinkedList<uint32_t> ll1(&pool);
|
||||
PooledLinkedList<uint32_t> ll2(&pool);
|
||||
PooledLinkedList<uint32_t> ll3(&pool);
|
||||
|
||||
AppendVector(ll1, vec);
|
||||
AppendVector(ll2, vec);
|
||||
AppendVector(ll3, vec);
|
||||
EXPECT_EQ(pool.total_nodes(), vec.size() * 3);
|
||||
EXPECT_EQ(pool.total_nodes(), vec.size() * 3);
|
||||
EXPECT_EQ(pool.free_nodes(), 0);
|
||||
|
||||
// Move two lists to the new pool
|
||||
PooledLinkedListNodes<uint32_t> pool_new;
|
||||
ll1.move_nodes(&pool_new);
|
||||
ll2.move_nodes(&pool_new);
|
||||
|
||||
// Moved nodes should belong to new pool
|
||||
EXPECT_EQ(ll1.pool(), &pool_new);
|
||||
EXPECT_EQ(ll2.pool(), &pool_new);
|
||||
|
||||
// Old pool should be smaller & have free nodes.
|
||||
EXPECT_EQ(pool.used_nodes(), vec.size());
|
||||
EXPECT_EQ(pool.free_nodes(), vec.size() * 2);
|
||||
|
||||
// New pool should be sized exactly and no free nodes.
|
||||
EXPECT_EQ(pool_new.total_nodes(), vec.size() * 2);
|
||||
EXPECT_EQ(pool_new.used_nodes(), vec.size() * 2);
|
||||
EXPECT_EQ(pool_new.free_nodes(), 0);
|
||||
|
||||
// All lists should be preserved
|
||||
EXPECT_EQ(ToVector(ll1), vec);
|
||||
EXPECT_EQ(ToVector(ll2), vec);
|
||||
EXPECT_EQ(ToVector(ll3), vec);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace utils
|
||||
} // namespace spvtools
|
Загрузка…
Ссылка в новой задаче