mirror of
https://github.com/YosysHQ/yosys.git
synced 2026-09-05 19:36:55 +02:00
A replayable .il names twines and source location sets by pool index, and a design that has freed slots writes a set with gaps in it. Reading such a file into a fresh design packed the survivors, so write, read, write did not reproduce the file. Let the pools intern at a caller-chosen index and use that while loading into a design with no twines or srcs of its own, falling back to the old remapping when the file's numbering does not fit this build.
698 lines
21 KiB
C++
698 lines
21 KiB
C++
#ifndef YOSYS_TWINE_H
|
|
#define YOSYS_TWINE_H
|
|
|
|
#include "kernel/hashcons.h"
|
|
#include "kernel/yosys_common.h"
|
|
|
|
#include <algorithm>
|
|
#include <bit>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <deque>
|
|
#include <limits>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_set>
|
|
#include <variant>
|
|
#include <vector>
|
|
|
|
YOSYS_NAMESPACE_BEGIN
|
|
|
|
struct TwineSpec;
|
|
struct TwinePool;
|
|
struct IdString;
|
|
struct SrcRef;
|
|
|
|
struct NullSrcRef {
|
|
constexpr operator SrcRef() const;
|
|
constexpr bool operator==(SrcRef ref) const;
|
|
};
|
|
|
|
/**
|
|
* A twine is a data structure designed to deduplicate prefixes.
|
|
* The key idea is that a twine is a suffix node or a leaf node.
|
|
* A leaf node holds a string,
|
|
* while a suffix node appends a string to a twine.
|
|
*
|
|
* Twines are used here to implement IdString, the Yosys interned
|
|
* string type. They're extended to the needs of Yosys, specifically
|
|
* with efficient static NEW_ID prefixes,
|
|
* and IdString holding a special bit marking the publicity of a name.
|
|
* When public, a backslash is prepended when printing an IdString.
|
|
*
|
|
* An IdString references a TwineNode in a per-Design TwinePool
|
|
* by indexing into it. It is constructed by interning a TwineSpec
|
|
* into a TwinePool.
|
|
*
|
|
* A general IdString can't be constructed, converted to a string, or printed
|
|
* without a pointer to the TwinePool.
|
|
* Compile-time allocated indices defined in kernel/constids.inc
|
|
* live in a global read-only pool, so they're an exception to that.
|
|
*
|
|
* Two IdStrings that resolve to the same string won't generally compare
|
|
* equal with operator== so that comparisons are cheap,
|
|
* but alternate mechanisms are provided for cases
|
|
* where that is absolutely necessary but it should be avoided for performance,
|
|
* like DeepTwine and TwineSearch.
|
|
* Twines are deduplicated within a TwinePool in a "shallow" way, similar to FRAIGing.
|
|
* Lookup from a string to IdString is expensive and provided by TwineSearch.
|
|
*
|
|
* TwinePool is backed by an std::deque and a free list
|
|
* and provides stable indices outside of garbage collection.
|
|
* "Content" refers to the untagged backing.
|
|
*/
|
|
|
|
struct NullIdString {
|
|
constexpr operator IdString() const;
|
|
constexpr bool operator==(IdString ref) const;
|
|
};
|
|
|
|
struct IdString {
|
|
private:
|
|
#ifdef YOSYS_ENABLE_TWINE_PROVENANCE
|
|
using bits_t = uint64_t;
|
|
#else
|
|
using bits_t = uint32_t;
|
|
#endif
|
|
bits_t value;
|
|
|
|
static constexpr int TWINE_WIDTH = std::numeric_limits<bits_t>::digits;
|
|
static constexpr int TWINE_PUBLIC_SHIFT = TWINE_WIDTH - 1;
|
|
static constexpr bits_t TWINE_PUBLIC_BIT = bits_t{1} << TWINE_PUBLIC_SHIFT;
|
|
static constexpr bits_t TWINE_NULL_VAL = ~bits_t{0};
|
|
static constexpr int TWINE_SERIAL_SHIFT = TWINE_WIDTH == 64 ? 32 : TWINE_PUBLIC_SHIFT;
|
|
static constexpr bits_t TWINE_INDEX_MASK = (bits_t{1} << TWINE_SERIAL_SHIFT) - 1;
|
|
static constexpr bits_t TWINE_SERIAL_MASK = bits_t(~(TWINE_INDEX_MASK | TWINE_PUBLIC_BIT));
|
|
|
|
public:
|
|
static constexpr NullIdString Null{};
|
|
|
|
static constexpr size_t MAX_INDEX = TWINE_INDEX_MASK;
|
|
static constexpr size_t MAX_SERIAL = TWINE_SERIAL_MASK >> TWINE_SERIAL_SHIFT;
|
|
|
|
constexpr size_t raw() const {
|
|
return value == TWINE_NULL_VAL ? value : (value & ~TWINE_SERIAL_MASK);
|
|
}
|
|
|
|
// raw() with the pool serial kept
|
|
constexpr size_t bits() const { return value; }
|
|
|
|
constexpr size_t order_key() const { return std::rotl((bits_t) eq_key(), 1); }
|
|
|
|
constexpr size_t serial() const {
|
|
return value == TWINE_NULL_VAL ? 0 : ((value & TWINE_SERIAL_MASK) >> TWINE_SERIAL_SHIFT);
|
|
}
|
|
|
|
constexpr IdString stamped(size_t pool_serial) const {
|
|
return value == TWINE_NULL_VAL ? *this
|
|
: IdString((value & ~TWINE_SERIAL_MASK)
|
|
| (bits_t(pool_serial << TWINE_SERIAL_SHIFT) & TWINE_SERIAL_MASK));
|
|
}
|
|
|
|
constexpr IdString() : value(TWINE_NULL_VAL) {}
|
|
explicit constexpr IdString(size_t val) : value((bits_t) val) {}
|
|
|
|
constexpr size_t eq_key() const { return value & ~TWINE_SERIAL_MASK; }
|
|
|
|
constexpr bool operator==(const IdString &rhs) const { return eq_key() == rhs.eq_key(); }
|
|
constexpr bool operator!=(const IdString &rhs) const { return eq_key() != rhs.eq_key(); }
|
|
constexpr std::strong_ordering operator<=>(const IdString &rhs) const {
|
|
return order_key() <=> rhs.order_key();
|
|
}
|
|
|
|
template <typename... Args>
|
|
constexpr bool in(const Args&... args) const {
|
|
return ((*this == args) || ...);
|
|
}
|
|
|
|
// Instead of for example std::set<Cell*>
|
|
// use std::set<Cell*, IdString::compare_ptr_by_name<Cell>> if the order of cells in the
|
|
// set has an influence on the algorithm.
|
|
template<typename T> struct compare_ptr_by_name {
|
|
bool operator()(const T *a, const T *b) const {
|
|
return (a == nullptr || b == nullptr) ? (a < b) : (a->name < b->name);
|
|
}
|
|
};
|
|
|
|
// A ref is "empty" when it names nothing at all.
|
|
constexpr bool empty() const { return value == TWINE_NULL_VAL; }
|
|
constexpr bool isPublic() const { return value != TWINE_NULL_VAL && (value & TWINE_PUBLIC_BIT); }
|
|
|
|
constexpr IdString untag() const {
|
|
return value == TWINE_NULL_VAL ? *this : IdString(value & ~TWINE_PUBLIC_BIT);
|
|
}
|
|
constexpr IdString tag(bool pub) const {
|
|
return value == TWINE_NULL_VAL ? *this : IdString(pub ? (value | TWINE_PUBLIC_BIT) : (value & ~TWINE_PUBLIC_BIT));
|
|
}
|
|
|
|
Hasher hash_into(Hasher h) const;
|
|
|
|
std::string handle_token() const;
|
|
static size_t handle_token_prefix(std::string_view token, bool &is_public);
|
|
};
|
|
|
|
constexpr NullIdString::operator IdString() const { return IdString(); }
|
|
constexpr bool NullIdString::operator==(IdString ref) const { return ref.empty(); }
|
|
|
|
/**
|
|
* A source location set. Each member is an untagged twine holding one
|
|
* "file:line.col-line.col" location; a set with more than one member is a
|
|
* location fused from several objects. Interned in a per-design SrcPool so
|
|
* that RTLIL objects carry a single handle instead of a string attribute.
|
|
*/
|
|
struct SrcRef {
|
|
size_t value;
|
|
|
|
static constexpr NullSrcRef Null{};
|
|
|
|
static constexpr size_t kNull = ~size_t{0};
|
|
|
|
constexpr SrcRef() : value(kNull) {}
|
|
explicit constexpr SrcRef(size_t val) : value(val) {}
|
|
|
|
constexpr bool operator==(const SrcRef&) const = default;
|
|
constexpr auto operator<=>(const SrcRef&) const = default;
|
|
|
|
constexpr size_t raw() const { return value; }
|
|
constexpr SrcRef untag() const { return *this; }
|
|
constexpr bool empty() const { return value == kNull; }
|
|
|
|
Hasher hash_into(Hasher h) const { h.hash64(value); return h; }
|
|
};
|
|
|
|
constexpr NullSrcRef::operator SrcRef() const { return SrcRef(); }
|
|
constexpr bool NullSrcRef::operator==(SrcRef ref) const { return ref.value == SrcRef::kNull; }
|
|
|
|
namespace hashlib {
|
|
template<>
|
|
struct hash_ops<IdString> {
|
|
static inline bool cmp(IdString a, IdString b) { return a == b; }
|
|
[[nodiscard]] static inline Hasher hash(IdString id) {
|
|
Hasher h;
|
|
h.force((Hasher::hash_t) id.bits());
|
|
return h;
|
|
}
|
|
[[nodiscard]] static inline Hasher hash_into(IdString id, Hasher h) {
|
|
h.hash64(id.eq_key());
|
|
return h;
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
enum : short {
|
|
// STATIC_TWINE_BEGIN = 0,
|
|
#define X(N) IDX_##N,
|
|
#include "kernel/constids.inc"
|
|
#undef X
|
|
STATIC_TWINE_END
|
|
};
|
|
|
|
struct ID {
|
|
// Static ids are name handles: non-'$' constids were '\'-escaped publics,
|
|
// so their handles carry the publicity bit baked in at compile time.
|
|
#define X(N) static constexpr IdString N = IdString(IDX_##N).tag((#N)[0] != '$');
|
|
#include "kernel/constids.inc"
|
|
#undef X
|
|
|
|
static constexpr const char* static_names[] = {
|
|
#define X(N) #N,
|
|
#include "kernel/constids.inc"
|
|
#undef X
|
|
};
|
|
|
|
static constexpr IdString lookup(std::string_view name)
|
|
{
|
|
int low = 0, high = STATIC_TWINE_END;
|
|
while (high - low >= 2) {
|
|
int mid = (low + high) / 2;
|
|
if (name < static_names[mid])
|
|
high = mid;
|
|
else
|
|
low = mid;
|
|
}
|
|
|
|
if (name != static_names[low])
|
|
throw "unknown twine id";
|
|
|
|
return IdString(low).tag(name[0] != '$');
|
|
}
|
|
|
|
static constexpr bool is_static(IdString ref) {
|
|
return ref.untag().raw() < STATIC_TWINE_END;
|
|
}
|
|
|
|
// Static IdString can be constructed without a design pointer
|
|
static std::string str(IdString ref);
|
|
static std::string unescaped_str(IdString ref);
|
|
};
|
|
|
|
template<size_t Raw> inline constexpr IdString constid = IdString(Raw);
|
|
|
|
#define ID(id) (YOSYS_NAMESPACE_PREFIX constid<YOSYS_NAMESPACE_PREFIX ID::lookup(#id).raw()>)
|
|
|
|
// TwineSpec is the object that lives in the TwinePool,
|
|
// while IdString points to a TwineSpec and carries publicity information
|
|
struct TwineSpec {
|
|
// deduplicates shared prefixes
|
|
struct Suffix {
|
|
IdString prefix;
|
|
std::string tail;
|
|
auto operator<=>(const Suffix&) const = default;
|
|
};
|
|
|
|
// transient suffix constructed with NEW_ID and NEW_ID_SUFFIX
|
|
// turned into a regular Suffix when added to a TwinePool
|
|
struct AutoSuffix {
|
|
const std::string *prefix;
|
|
std::string tail;
|
|
auto operator<=>(const AutoSuffix&) const = default;
|
|
};
|
|
|
|
// "leaf", regular deduplicated string
|
|
struct Leaf {
|
|
std::string s;
|
|
auto operator<=>(const Leaf&) const = default;
|
|
};
|
|
|
|
std::variant<Leaf, Suffix, AutoSuffix> data;
|
|
|
|
TwineSpec(Leaf v);
|
|
TwineSpec(Suffix v);
|
|
TwineSpec(AutoSuffix v);
|
|
|
|
bool holds_leaf() const;
|
|
bool holds_suffix() const;
|
|
// Only for the pool-free variants, Leaf and AutoSuffix
|
|
std::string content_str() const;
|
|
};
|
|
|
|
struct SmallString {
|
|
static constexpr uint32_t INLINE_CAP = 8;
|
|
static constexpr size_t MAX_LEN = std::numeric_limits<uint32_t>::max();
|
|
|
|
SmallString() = default;
|
|
explicit SmallString(std::string_view content);
|
|
SmallString(const SmallString &other);
|
|
SmallString(SmallString &&other) noexcept;
|
|
SmallString &operator=(const SmallString &other);
|
|
SmallString &operator=(SmallString &&other) noexcept;
|
|
~SmallString();
|
|
|
|
std::string_view view() const { return {len_ <= INLINE_CAP ? inl_ : ptr_, len_}; }
|
|
|
|
private:
|
|
void store(std::string_view content);
|
|
void release();
|
|
|
|
union {
|
|
char inl_[INLINE_CAP];
|
|
char *ptr_;
|
|
};
|
|
uint32_t len_ = 0;
|
|
};
|
|
|
|
// TwineNode is the in-memory version of TwineSpec
|
|
struct TwineNode {
|
|
static constexpr uint32_t NO_PREFIX = ~uint32_t{0};
|
|
static constexpr uint32_t DEAD = NO_PREFIX - 1;
|
|
|
|
struct Key {
|
|
uint32_t prefix;
|
|
std::string_view text;
|
|
};
|
|
|
|
TwineNode() = default;
|
|
explicit TwineNode(std::string_view content) : text_(content), prefix_(NO_PREFIX) {}
|
|
TwineNode(uint32_t prefix, std::string_view tail) : text_(tail), prefix_(prefix) {}
|
|
TwineNode(const TwineNode &other) = default;
|
|
TwineNode(TwineNode &&other) noexcept;
|
|
TwineNode &operator=(const TwineNode &other) = default;
|
|
TwineNode &operator=(TwineNode &&other) noexcept;
|
|
~TwineNode() = default;
|
|
|
|
enum class Kind { Dead, Leaf, Suffix };
|
|
|
|
constexpr bool is_dead() const { return prefix_ == DEAD; }
|
|
constexpr bool is_leaf() const { return prefix_ == NO_PREFIX; }
|
|
constexpr bool is_suffix() const { return prefix_ < DEAD; }
|
|
|
|
constexpr Kind kind() const {
|
|
return prefix_ == DEAD ? Kind::Dead
|
|
: prefix_ == NO_PREFIX ? Kind::Leaf
|
|
: Kind::Suffix;
|
|
}
|
|
|
|
std::string_view text() const { return text_.view(); }
|
|
IdString prefix() const { return IdString(prefix_); }
|
|
Key key() const { return {prefix_, text()}; }
|
|
|
|
bool operator==(const TwineNode &other) const
|
|
{ return prefix_ == other.prefix_ && text() == other.text(); }
|
|
bool operator==(const Key &other) const
|
|
{ return prefix_ == other.prefix && text() == other.text; }
|
|
|
|
private:
|
|
YS_NO_UNIQUE_ADDRESS SmallString text_;
|
|
uint32_t prefix_ = DEAD;
|
|
};
|
|
|
|
static_assert(sizeof(TwineNode) == 16);
|
|
|
|
struct StaticTwines {
|
|
static constexpr size_t count = STATIC_TWINE_END;
|
|
|
|
static void init();
|
|
static const TwineNode &node(size_t idx);
|
|
static bool ready();
|
|
|
|
private:
|
|
static std::vector<TwineNode> nodes_;
|
|
};
|
|
|
|
extern int64_t twine_gc_ns;
|
|
extern int twine_gc_count;
|
|
|
|
std::pair<std::string, bool> twine_unescape(std::string s);
|
|
|
|
struct TwinePool : HashConsPool<TwinePool, TwineNode, IdString> {
|
|
static constexpr size_t STATIC_COUNT = StaticTwines::count;
|
|
|
|
TwinePool();
|
|
TwinePool(const TwinePool& other);
|
|
TwinePool(TwinePool&& other);
|
|
TwinePool& operator=(const TwinePool& other);
|
|
TwinePool& operator=(TwinePool&& other);
|
|
|
|
size_t serial() const;
|
|
bool owns(IdString ref) const;
|
|
IdString stamp(IdString ref) const;
|
|
void check_owned(IdString ref) const;
|
|
const TwineNode& operator[](IdString ref) const;
|
|
|
|
static const TwineNode& static_node(size_t idx);
|
|
// static IdString untag(IdString ref);
|
|
|
|
static void check_ready();
|
|
static void canonicalize(TwineNode& t);
|
|
static size_t hash_node(const TwineNode& t);
|
|
static size_t hash_key(const TwineNode::Key& k);
|
|
|
|
template<typename F>
|
|
static void for_each_child(const TwineNode& t, F&& f) {
|
|
if (t.is_suffix())
|
|
f(t.prefix());
|
|
}
|
|
|
|
template<typename Roots>
|
|
size_t gc(Roots& roots) {
|
|
for (auto &it : auto_prefixes)
|
|
roots.insert(it.second);
|
|
return HashConsPool::gc(roots);
|
|
}
|
|
|
|
void dump(IdString ref, std::ostream& os = std::cout) const;
|
|
void print(IdString ref, std::ostream& os = std::cout) const;
|
|
void append_str(IdString ref, std::string& out) const;
|
|
|
|
// Publicity bit provides escaping
|
|
std::string str(IdString ref) const;
|
|
// Publicity bit ignored
|
|
std::string unescaped_str(IdString ref) const;
|
|
|
|
size_t str_size(IdString ref) const;
|
|
bool begins_with(IdString ref, std::string_view prefix) const;
|
|
// Publicity bit provides escaping, as in str()
|
|
bool name_equal(IdString ref, std::string_view name) const;
|
|
bool content_equal(IdString a, IdString b) const;
|
|
int compare_by_name(IdString a, IdString b) const;
|
|
IdString prefix_of(IdString ref) const;
|
|
|
|
// Only finds leaves. For compatibility only
|
|
IdString find(const std::string &name) const;
|
|
IdString find(TwineSpec t) const;
|
|
// Doesn't infer publicity
|
|
IdString add(TwineSpec t);
|
|
IdString add(IdString prefix, std::string_view tail);
|
|
IdString auto_prefix(const std::string *prefix);
|
|
// Infers publicity from first character
|
|
IdString add(std::string s);
|
|
// Intern `t` at exactly `idx`; see HashConsPool::place
|
|
IdString place(size_t idx, TwineSpec t);
|
|
void finish_placement();
|
|
IdString copy_from(const TwinePool& src, IdString ref);
|
|
// Non-mutating counterpart of copy_from
|
|
IdString find_from(const TwinePool& src, IdString ref) const;
|
|
// Opaque handle for files that never leave one run of yosys.
|
|
// Only valid until garbage collection reuses the slot.
|
|
std::string ref_token(IdString ref) const;
|
|
// Null unless the token names a live twine stamped by this pool
|
|
IdString ref_from_token(std::string_view token) const;
|
|
void dump(std::ostream& os = std::cout) const;
|
|
bool shares_index_space_with(const TwinePool &other) const;
|
|
using HashConsPool::gc;
|
|
|
|
private:
|
|
IdString add_inner(TwineNode t);
|
|
IdString intern(uint32_t prefix, std::string_view text);
|
|
IdString find_content(uint32_t prefix, std::string_view text) const;
|
|
static size_t next_serial();
|
|
dict<const std::string *, IdString> auto_prefixes;
|
|
size_t serial_;
|
|
};
|
|
|
|
/**
|
|
* A set of source locations. Members are twines with the publicity bit and the
|
|
* pool serial stripped: the strings are not names, and a Src set survives a
|
|
* verbatim clone of the twine pool it points into.
|
|
*/
|
|
struct Src {
|
|
using Key = std::vector<IdString>;
|
|
|
|
std::vector<IdString> data;
|
|
|
|
bool is_dead() const { return data.empty(); }
|
|
const std::vector<IdString> &members() const { return data; }
|
|
|
|
bool operator==(const Src &other) const { return data == other.data; }
|
|
bool operator==(const Key &key) const { return data == key; }
|
|
};
|
|
|
|
struct SrcPool : HashConsPool<SrcPool, Src, SrcRef> {
|
|
static constexpr size_t STATIC_COUNT = 0;
|
|
|
|
TwinePool *twines = nullptr;
|
|
|
|
explicit SrcPool(TwinePool *twines) : twines(twines) {}
|
|
|
|
SrcPool(const SrcPool &) = delete;
|
|
SrcPool(SrcPool &&) = delete;
|
|
SrcPool &operator=(const SrcPool &) = delete;
|
|
SrcPool &operator=(SrcPool &&) = delete;
|
|
|
|
// Src nodes hold handles into `other.twines`, so they are only meaningful
|
|
// here once this pool's twines are a verbatim copy of the source's.
|
|
void clone_from(const SrcPool &other) {
|
|
log_assert(twines->shares_index_space_with(*other.twines));
|
|
HashConsPool::operator=(static_cast<const HashConsPool &>(other));
|
|
}
|
|
|
|
static void canonicalize(Src&) {}
|
|
|
|
static size_t hash_node(const Src &n) { return hash_key(n.data); }
|
|
|
|
static size_t hash_key(const Src::Key &k) {
|
|
Hasher h;
|
|
for (IdString member : k)
|
|
h.eat(member);
|
|
return h.yield();
|
|
}
|
|
|
|
template<typename F>
|
|
static void for_each_child(const Src&, F&&) {}
|
|
|
|
SrcRef add(const std::string &location) {
|
|
return intern({twines->add(TwineSpec{TwineSpec::Leaf{location}})});
|
|
}
|
|
|
|
SrcRef add(const std::string &file, const std::string &tail) {
|
|
IdString prefix = twines->add(TwineSpec{TwineSpec::Leaf{file}});
|
|
return intern({twines->add(TwineSpec{TwineSpec::Suffix{prefix, tail}})});
|
|
}
|
|
|
|
SrcRef merge(std::span<const SrcRef> refs) {
|
|
std::vector<IdString> members;
|
|
for (SrcRef ref : refs)
|
|
if (ref != SrcRef::Null)
|
|
members.insert(members.end(), (*this)[ref].data.begin(), (*this)[ref].data.end());
|
|
return intern(std::move(members));
|
|
}
|
|
|
|
SrcRef merge(SrcRef a, SrcRef b) {
|
|
SrcRef both[] = {a, b};
|
|
return merge(std::span<const SrcRef>{both});
|
|
}
|
|
|
|
SrcRef adopt(std::span<const IdString> members) {
|
|
return intern(std::vector<IdString>(members.begin(), members.end()));
|
|
}
|
|
|
|
// Interning at a caller-chosen index; see HashConsPool::place
|
|
SrcRef place(size_t idx, std::span<const IdString> members) {
|
|
std::vector<IdString> canon(members.begin(), members.end());
|
|
canonicalize_members(canon);
|
|
if (canon.empty())
|
|
return SrcRef::Null;
|
|
return HashConsPool::place(idx, Src{std::move(canon)});
|
|
}
|
|
|
|
void finish_placement() { rebuild_index(); }
|
|
|
|
SrcRef find_members(std::span<const IdString> members) const {
|
|
std::vector<IdString> canon(members.begin(), members.end());
|
|
canonicalize_members(canon);
|
|
if (canon.empty())
|
|
return SrcRef::Null;
|
|
return find_key(canon);
|
|
}
|
|
|
|
SrcRef copy_from(const SrcPool &other, SrcRef ref) {
|
|
if (ref == SrcRef::Null)
|
|
return ref;
|
|
std::vector<IdString> members;
|
|
members.reserve(other[ref].data.size());
|
|
for (IdString member : other[ref].data)
|
|
members.push_back(twines->copy_from(*other.twines, member));
|
|
return intern(std::move(members));
|
|
}
|
|
|
|
void append_str(SrcRef ref, std::string &out) const {
|
|
if (ref == SrcRef::Null)
|
|
return;
|
|
bool first = true;
|
|
for (IdString member : (*this)[ref].data) {
|
|
if (!first)
|
|
out += '|';
|
|
first = false;
|
|
twines->append_str(member, out);
|
|
}
|
|
}
|
|
|
|
std::string str(SrcRef ref) const {
|
|
std::string out;
|
|
append_str(ref, out);
|
|
return out;
|
|
}
|
|
|
|
void leaves(SrcRef ref, pool<std::string> &out) const {
|
|
if (ref == SrcRef::Null)
|
|
return;
|
|
for (IdString member : (*this)[ref].data)
|
|
out.insert(twines->str(member));
|
|
}
|
|
|
|
size_t gc_with_twines(const pool<SrcRef> &live_srcs, pool<IdString> &live_twines) {
|
|
for (SrcRef ref : live_srcs)
|
|
for (IdString member : (*this)[ref].data)
|
|
live_twines.insert(member);
|
|
return twines->gc(live_twines) + gc(live_srcs);
|
|
}
|
|
|
|
void dump(SrcRef ref, std::ostream &os = std::cout) const {
|
|
os << "Set[";
|
|
bool first = true;
|
|
for (IdString member : (*this)[ref].data) {
|
|
if (!first)
|
|
os << ", ";
|
|
first = false;
|
|
os << "@" << member.raw();
|
|
}
|
|
os << "]";
|
|
}
|
|
|
|
void dump(std::ostream &os = std::cout) const {
|
|
os << "--- SrcPool Dump (" << size() << " nodes) ---\n";
|
|
for (SrcRef ref : refs()) {
|
|
os << ref.value << " -> ";
|
|
dump(ref, os);
|
|
os << '\n';
|
|
}
|
|
os << "--------------------------------\n";
|
|
}
|
|
|
|
private:
|
|
static void canonicalize_members(std::vector<IdString> &members) {
|
|
for (IdString &member : members)
|
|
member = member.untag().stamped(0);
|
|
std::sort(members.begin(), members.end());
|
|
members.erase(std::unique(members.begin(), members.end()), members.end());
|
|
}
|
|
|
|
SrcRef intern(std::vector<IdString> members) {
|
|
canonicalize_members(members);
|
|
if (members.empty())
|
|
return SrcRef::Null;
|
|
return add_inner(Src{std::move(members)});
|
|
}
|
|
|
|
using HashConsPool::add_inner;
|
|
using HashConsPool::find;
|
|
using HashConsPool::find_key;
|
|
using HashConsPool::gc;
|
|
};
|
|
|
|
/**
|
|
* DeepTwine
|
|
* Compare and hash IdString a and b equal iff their .str() are equal
|
|
* without really constructing those strings.
|
|
*/
|
|
|
|
struct DeepTwineHash {
|
|
// Transparent hashing allows us to compare diverse types, so that
|
|
// we don't have to get a temporary string out of an IdString
|
|
// just to hash it
|
|
using is_transparent = void;
|
|
const TwinePool* pool = nullptr;
|
|
|
|
// Hashes 8 characters as a time
|
|
struct Stream {
|
|
Hasher h;
|
|
uint64_t buf = 0;
|
|
int fill = 0;
|
|
void push(std::string_view sv);
|
|
size_t finish();
|
|
};
|
|
void combine(Stream& s, IdString t) const;
|
|
size_t operator()(std::string_view sv) const;
|
|
size_t operator()(IdString t) const;
|
|
};
|
|
|
|
// see DeepTwineHash explanation above
|
|
struct DeepTwineEq {
|
|
using is_transparent = void;
|
|
const TwinePool* pool = nullptr;
|
|
bool consume(IdString t, std::string_view& sv) const noexcept;
|
|
bool operator()(IdString t, std::string_view sv) const noexcept;
|
|
bool operator()(std::string_view sv, IdString t) const noexcept;
|
|
bool operator()(IdString a, IdString b) const;
|
|
};
|
|
|
|
// EXPENSIVE ephemeral search for string_view -> IdString
|
|
// No automatic tracking abilities,
|
|
// so you won't find things you created after you created the search.
|
|
// Best used only for cases where you're searching for a lot of strings
|
|
// with a single TwineSearch
|
|
struct TwineSearch {
|
|
const TwinePool* pool;
|
|
std::unordered_set<IdString, DeepTwineHash, DeepTwineEq> index;
|
|
TwineSearch(const TwinePool* pool);
|
|
void insert(IdString ref);
|
|
// Infers publicity from first character
|
|
IdString find(std::string_view sv) const;
|
|
};
|
|
|
|
YOSYS_NAMESPACE_END
|
|
|
|
#endif
|