verilator/src/V3Scoreboard.h
2020-05-16 18:02:54 -04:00

476 lines
18 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Scoreboards for thread partitioner
//
// Provides scoreboard classes:
//
// * SortByValueMap
// * V3Scoreboard
//
// See details below
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2020 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
#ifndef _V3SCOREBOARD_H_
#define _V3SCOREBOARD_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Error.h"
#include <map>
#include VL_INCLUDE_UNORDERED_MAP
#include VL_INCLUDE_UNORDERED_SET
//######################################################################
// SortByValueMap
/// A generic key-value map, except it also supports iterating in
/// value-sorted order. Values need not be unique. Uses T_KeyCompare to
/// break ties in the sort when values collide.
template <typename T_Key, typename T_Value, class T_KeyCompare = std::less<T_Key> >
class SortByValueMap {
// TYPES
private:
typedef vl_unordered_map<T_Key, T_Value> Key2Val;
typedef std::set<T_Key, T_KeyCompare> KeySet;
typedef std::map<T_Value, KeySet> Val2Keys;
// MEMBERS
Key2Val m_keys; // Map each key to its value. Not sorted.
Val2Keys m_vals; // Map each value to its keys. Sorted.
public:
// CONSTRUCTORS
SortByValueMap() {}
class const_iterator {
// TYPES
public:
typedef const_iterator value_type;
typedef const_iterator reference; // See comment on operator*()
typedef void pointer;
typedef std::ptrdiff_t difference_type;
typedef std::bidirectional_iterator_tag iterator_category;
protected:
friend class SortByValueMap;
// MEMBERS
typename KeySet::iterator m_keyIt;
typename Val2Keys::iterator m_valIt;
SortByValueMap* m_sbvmp;
bool m_end; // At the end()
// CONSTRUCTORS
explicit const_iterator(SortByValueMap* sbmvp) // for end()
: m_sbvmp(sbmvp)
, m_end(true) {}
const_iterator(typename Val2Keys::iterator valIt, typename KeySet::iterator keyIt,
SortByValueMap* sbvmp)
: m_keyIt(keyIt)
, m_valIt(valIt)
, m_sbvmp(sbvmp)
, m_end(false) {}
// METHODS
void advanceUntilValid() {
++m_keyIt;
if (m_keyIt != m_valIt->second.end()) { // Valid iterator, done.
return;
}
// Try the next value?
++m_valIt;
if (m_valIt == m_sbvmp->m_vals.end()) { // No more values
m_end = true;
return;
}
// Should find a value here, as every value bucket is supposed
// to have at least one key, even after keys get removed.
m_keyIt = m_valIt->second.begin();
UASSERT(m_keyIt != m_valIt->second.end(), "Algorithm should have key left");
}
void reverseUntilValid() {
if (m_end) {
UASSERT(!m_sbvmp->m_vals.empty(), "Reverse iterator causes underflow");
m_valIt = m_sbvmp->m_vals.end();
--m_valIt;
UASSERT(!m_valIt->second.empty(), "Reverse iterator causes underflow");
m_keyIt = m_valIt->second.end();
--m_keyIt;
m_end = false;
return;
}
if (m_keyIt != m_valIt->second.begin()) {
// Valid iterator, we're done.
--m_keyIt;
return;
}
// Try the previous value?
if (VL_UNCOVERABLE(m_valIt == m_sbvmp->m_vals.begin())) {
// No more values but it's not defined to decrement an
// iterator past the beginning.
v3fatalSrc("Decremented iterator past beginning");
return; // LCOV_EXCL_LINE
}
--m_valIt;
// Should find a value here, as Every value bucket is supposed
// to have at least one key, even after keys get removed.
UASSERT(!m_valIt->second.empty(), "Value bucket should have key");
m_keyIt = m_valIt->second.end();
--m_keyIt;
UASSERT(m_keyIt != m_valIt->second.end(), "Value bucket should have key");
}
public:
const T_Key& key() const { return *m_keyIt; }
const T_Value& value() const { return m_valIt->first; }
const_iterator& operator++() {
advanceUntilValid();
return *this;
}
const_iterator& operator--() {
reverseUntilValid();
return *this;
}
bool operator==(const const_iterator& other) const {
// It's not legal to compare iterators from different
// sequences. So check m_end before comparing m_valIt, and
// compare m_valIt's before comparing m_keyIt to ensure nothing
// here is undefined.
if (m_end || other.m_end) return m_end && other.m_end;
return ((m_valIt == other.m_valIt) && (m_keyIt == other.m_keyIt));
}
bool operator!=(const const_iterator& other) const { return (!this->operator==(other)); }
// WARNING: Cleverness.
//
// The "reference" returned by *it must remain valid after 'it'
// gets destroyed. The reverse_iterator relies on this for its
// operator*(), so it's not just a theoretical requirement, it's a
// real requirement.
//
// To make that work, define the "reference" type to be the
// iterator itself. So clients can do (*it).key() and
// (*it).value(). This is the clever part.
//
// That's mostly useful for a reverse iterator, where *rit returns
// the forward iterator pointing the to same element, so
// (*rit).key() and (*rit).value() work where rit.key() and
// rit.value() cannot.
//
// It would be nice to support it->key() and it->value(), however
// uncertain what would be an appropriate 'pointer' type define
// that makes this work safely through a reverse iterator. So this
// class does not provide an operator->().
//
// Q) Why not make our value_type be a pair<T_Key, T_Value> like a
// normal map, and return a reference to that? This could
// return a reference to one of the pairs inside m_keys, that
// would satisfy the constraint above.
//
// A) It would take a lookup to find that pair within m_keys. This
// iterator is designed to minimize the number of hashtable and
// tree lookups. Increment, decrement, key(), value(), erase()
// by iterator, begin(), end() -- none of these require a
// container lookup. That's true for reverse_iterators too.
reference operator*() const {
UASSERT(!m_end, "Dereferencing iterator that is at end()");
return *this;
}
};
class iterator : public const_iterator {
public:
// TYPES
typedef iterator value_type;
typedef iterator reference;
// pointer, difference_type, and iterator_category inherit from
// const_iterator
// CONSTRUCTORS
explicit iterator(SortByValueMap* sbvmp)
: const_iterator(sbvmp) {}
iterator(typename Val2Keys::iterator valIt, typename KeySet::iterator keyIt,
SortByValueMap* sbvmp)
: const_iterator(valIt, keyIt, sbvmp) {}
// METHODS
iterator& operator++() {
this->advanceUntilValid();
return *this;
}
iterator& operator--() {
this->reverseUntilValid();
return *this;
}
reference operator*() const {
UASSERT(!this->m_end, "Dereferencing iterator that is at end()");
return *this;
}
};
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
// METHODS
private:
void removeKeyFromOldVal(const T_Key& k, const T_Value& oldVal) {
// The value of 'k' is about to change, or, 'k' is about to be
// removed from the map.
// Clear the m_vals mapping for k.
KeySet& keysAtOldVal = m_vals[oldVal];
size_t erased = keysAtOldVal.erase(k);
UASSERT(erased == 1, "removeKeyFromOldVal() removal key not found");
if (keysAtOldVal.empty()) {
// Don't keep empty sets in the value map.
m_vals.erase(oldVal);
}
}
void removeKeyFromOldVal(iterator it) {
it.m_valIt->second.erase(it.m_keyIt);
if (it.m_valIt->second.empty()) m_vals.erase(it.m_valIt);
}
public:
iterator begin() {
typename Val2Keys::iterator valIt = m_vals.begin();
if (valIt == m_vals.end()) return end();
typename KeySet::const_iterator keyIt = valIt->second.begin();
return iterator(valIt, keyIt, this);
}
const_iterator begin() const {
SortByValueMap* mutp = const_cast<SortByValueMap*>(this);
typename Val2Keys::iterator valIt = mutp->m_vals.begin();
if (valIt == mutp->m_vals.end()) return end();
typename KeySet::const_iterator keyIt = valIt->second.begin();
return const_iterator(valIt, keyIt, mutp);
}
iterator end() { return iterator(this); }
const_iterator end() const {
// Safe to cast away const; the const_iterator will still enforce
// it. Same for the const begin() below.
return const_iterator(const_cast<SortByValueMap*>(this));
}
reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
iterator find(const T_Key& k) {
typename Key2Val::iterator kvit = m_keys.find(k);
if (kvit == m_keys.end()) return end();
typename Val2Keys::iterator valIt = m_vals.find(kvit->second);
typename KeySet::iterator keyIt = valIt->second.find(k);
return iterator(valIt, keyIt, this);
}
const_iterator find(const T_Key& k) const {
SortByValueMap* mutp = const_cast<SortByValueMap*>(this);
typename Key2Val::iterator kvit = mutp->m_keys.find(k);
if (kvit == mutp->m_keys.end()) return end();
typename Val2Keys::iterator valIt = mutp->m_vals.find(kvit->second);
typename KeySet::iterator keyIt = valIt->second.find(k);
return const_iterator(valIt, keyIt, mutp);
}
void set(const T_Key& k, const T_Value& v) {
typename Key2Val::iterator kvit = m_keys.find(k);
if (kvit != m_keys.end()) {
if (kvit->second == v) {
return; // LCOV_EXCL_LINE // Same value already present; stop.
}
// Must remove element from m_vals[oldValue]
removeKeyFromOldVal(k, kvit->second);
}
m_keys[k] = v;
m_vals[v].insert(k);
}
size_t erase(const T_Key& k) {
typename Key2Val::iterator kvit = m_keys.find(k);
if (kvit == m_keys.end()) return 0;
removeKeyFromOldVal(k, kvit->second);
m_keys.erase(kvit);
return 1;
}
void erase(const iterator& it) {
m_keys.erase(it.key());
removeKeyFromOldVal(it);
}
void erase(const reverse_iterator& it) {
erase(*it); // Dereferencing returns a copy of the forward iterator
}
bool has(const T_Key& k) const { return (m_keys.find(k) != m_keys.end()); }
bool empty() const { return m_keys.empty(); }
// Look up a value. Returns a reference for efficiency. Note this must
// be a const reference, otherwise the client could corrupt the sorted
// order of m_byValue by reaching through and changing the value.
const T_Value& at(const T_Key& k) const {
typename Key2Val::const_iterator kvit = m_keys.find(k);
UASSERT(kvit != m_keys.end(), "at() lookup key not found");
return kvit->second;
}
private:
VL_UNCOPYABLE(SortByValueMap);
};
//######################################################################
/// V3Scoreboard takes a set of Elem*'s, each having some score.
/// Scores are assigned by a user-supplied scoring function.
///
/// At any time, the V3Scoreboard can return the elem with the "best" score
/// among those elements whose scores are known.
///
/// The best score is the _lowest_ score. This makes sense in contexts
/// where scores represent costs.
///
/// The Scoreboard supports mutating element scores efficiently. The client
/// must hint to the V3Scoreboard when an element's score may have
/// changed. When it receives this hint, the V3Scoreboard will move the
/// element into the set of elements whose scores are unknown. Later the
/// client can tell V3Scoreboard to re-sort the list, which it does
/// incrementally, by re-scoring all elements whose scores are unknown, and
/// then moving these back into the score-sorted map. This is efficient
/// when the subset of elements whose scores change is much smaller than
/// the full set size.
template <typename T_Elem, typename T_Score, class T_ElemCompare = std::less<T_Elem> >
class V3Scoreboard {
private:
// TYPES
typedef vl_unordered_set<const T_Elem*> NeedRescoreSet;
class CmpElems {
public:
bool operator()(const T_Elem* const& ap, const T_Elem* const& bp) const {
T_ElemCompare cmp;
return cmp.operator()(*ap, *bp);
}
};
typedef SortByValueMap<const T_Elem*, T_Score, CmpElems> SortedMap;
typedef T_Score (*UserScoreFnp)(const T_Elem*);
// MEMBERS
NeedRescoreSet m_unknown; // Elements with unknown scores
SortedMap m_sorted; // Set of elements with known scores
UserScoreFnp m_scoreFnp; // Scoring function
bool m_slowAsserts; // Do some asserts that require extra lookups
public:
// CONSTRUCTORS
explicit V3Scoreboard(UserScoreFnp scoreFnp, bool slowAsserts)
: m_scoreFnp(scoreFnp)
, m_slowAsserts(slowAsserts) {}
~V3Scoreboard() {}
// METHODS
// Add an element to the scoreboard.
// Element begins in needs-rescore state; it won't be returned by
// bestp() until after the next rescore().
void addElem(const T_Elem* elp) {
if (m_slowAsserts) {
UASSERT(!contains(elp), "Adding element to scoreboard that was already in scoreboard");
}
m_unknown.insert(elp);
}
// Remove elp from scoreboard.
void removeElem(const T_Elem* elp) {
if (0 == m_sorted.erase(elp)) {
UASSERT(m_unknown.erase(elp),
"Could not find requested elem to remove from scoreboard");
}
}
// Returns true if elp is present in the scoreboard, false otherwise.
//
// Note: every other V3Scoreboard routine that takes an T_Elem* has
// undefined behavior if the element is not in the scoreboard.
bool contains(const T_Elem* elp) const {
if (m_unknown.find(elp) != m_unknown.end()) return true;
return (m_sorted.find(elp) != m_sorted.end());
}
// Get the best element, with the lowest score (lower is better), among
// elements whose scores are known. Returns NULL if no elements with
// known scores exist.
//
// Note: This does not automatically rescore. Client must call
// rescore() periodically to ensure all elems in the scoreboard are
// reflected in the result of bestp(). Otherwise, bestp() only
// considers elements that aren't pending rescore.
const T_Elem* bestp() {
typename SortedMap::iterator result = m_sorted.begin();
if (VL_UNLIKELY(result == m_sorted.end())) return NULL;
return (*result).key();
}
// Tell the scoreboard that this element's score may have changed.
//
// At the time of this call, the element's score becomes "unknown"
// to the V3Scoreboard. Unknown elements won't be returned by bestp().
// The element's score will remain unknown until the next rescore().
//
// The client MUST call this for each element whose score has changed.
//
// The client MAY call this for elements whose score has not changed.
// Doing so incurs some compute cost (to re-sort the element back to
// its original location) and still makes it ineligible to be returned
// by bestp() until the next rescore().
void hintScoreChanged(const T_Elem* elp) {
m_unknown.insert(elp);
m_sorted.erase(elp);
}
// True if any element's score is unknown to V3Scoreboard.
bool needsRescore() { return !m_unknown.empty(); }
// False if elp's score is known to V3Scoreboard,
// else true if elp's score is unknown until the next rescore().
bool needsRescore(const T_Elem* elp) { return (m_unknown.find(elp) != m_unknown.end()); }
// Retrieve the last known score for an element.
T_Score cachedScore(const T_Elem* elp) {
typename SortedMap::iterator result = m_sorted.find(elp);
UASSERT(result != m_sorted.end(), "V3Scoreboard::cachedScore() failed to find element");
return (*result).value();
}
// For each element whose score is unknown to V3Scoreboard,
// call the client's scoring function to get a new score,
// and sort all elements by their current score.
void rescore() {
for (typename NeedRescoreSet::iterator it = m_unknown.begin(); it != m_unknown.end();
++it) {
const T_Elem* elp = *it;
T_Score sortScore = m_scoreFnp(elp);
m_sorted.set(elp, sortScore);
}
m_unknown.clear();
}
private:
VL_UNCOPYABLE(V3Scoreboard);
};
//######################################################################
namespace V3ScoreboardBase {
void selfTest();
} // namespace V3ScoreboardBase
#endif // Guard