3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2026-07-17 20:55:45 +00:00
yosys/kernel/twine.h
Emil J. Tywoniak d13dfc21f4 WIP
2026-06-10 14:54:48 +02:00

409 lines
15 KiB
C++

#ifndef YOSYS_TWINE_H
#define YOSYS_TWINE_H
#include "kernel/yosys_common.h"
#include <cstdint>
#include <limits>
#include <span>
#include <string>
#include <string_view>
#include <unordered_set>
#include <list>
#include <variant>
#include <vector>
YOSYS_NAMESPACE_BEGIN
// A Twine is an interned, possibly composite source-location string. Leaves
// are flat path:line.col substrings (the existing src-attribute literal). A
// Concat node holds an ordered sequence of child twines, so merging the src
// of N cells is O(N) lookups plus one concat-table probe — independent of
// the total path-string length the materialized result would have.
//
// Twines are valid only relative to the TwinePool that minted them. The pool
// lives on RTLIL::Design (design->twines).
struct Twine
{
using Id = Twine*;
static constexpr Id Null = nullptr;
// Suffix shares a `prefix` prefix with other suffixes and contributes
// its own `tail` string. The materialized leaf string is
// flat_string(prefix) + tail, i.e. suffixes form trees whose leaves
// (string variant) are the roots — like a reverse-trie of common
// prefixes. The prefix is itself flat (Leaf or Suffix), never a
// Concat.
struct Suffix {
Id prefix;
std::string tail;
};
// Leaf holds the literal path:line.col string. Suffix holds a prefix
// id + own tail (see above). Concat holds the ordered children.
// Concats are kept flat by TwinePool::concat — children are always
// flat (Leaf or Suffix), never other Concats. monostate is the
// tombstone marker for freed slots awaiting reuse via the free list.
std::variant<std::monostate, std::string, std::vector<Id>, Suffix> data;
bool is_dead() const { return std::holds_alternative<std::monostate>(data); }
bool is_leaf() const { return std::holds_alternative<std::string>(data); }
bool is_concat() const { return std::holds_alternative<std::vector<Id>>(data); }
bool is_suffix() const { return std::holds_alternative<Suffix>(data); }
bool is_flat() const { return is_leaf() || is_suffix(); }
const std::string &leaf() const { return std::get<std::string>(data); }
const std::vector<Id> &children() const { return std::get<std::vector<Id>>(data); }
const Suffix &suffix() const { return std::get<Suffix>(data); }
};
struct TwinePoolExtender;
class TwinePool
{
private:
friend struct TwinePoolExtender;
uint32_t& refcount(Twine::Id id);
public:
TwinePool();
// Custom copy: functor pointers must target the NEW pool's nodes_.
TwinePool(const TwinePool &other);
TwinePool &operator=(const TwinePool &other);
// Move is deleted; the intrusive functors hold `this`, so a move would
// silently leave them pointing at the old (now-empty) pool.
TwinePool(TwinePool &&) = delete;
TwinePool &operator=(TwinePool &&) = delete;
// Intern a leaf string. Returns the same Id for byte-equal inputs. The
// returned Id carries one reference for the caller — release it when
// you are done holding it. Empty input returns Twine::Null.
Twine::Id intern(std::string_view leaf);
// Intern a Suffix node. The resulting flat string is
// flat_string(prefix) + tail. `prefix` must be a flat node (Leaf or
// Suffix) — pass Twine::Null with a non-empty `tail` to fall back to
// intern(tail). Suffixes with the same (prefix, tail) dedup. The
// returned Id carries one reference for the caller. Internally the
// new suffix retains a reference on `prefix`; releasing the suffix
// releases that internal prefix ref. Empty `tail` returns `prefix`
// (with +1 ref for the caller).
Twine::Id intern_suffix(Twine::Id prefix, std::string_view tail);
// Build a Concat node referencing `parts` in order. Concat children are
// always leaves (flat-leaf invariant): any Concat passed in `parts` has
// its leaves spliced in instead. Duplicate leaves and Twine::Null are
// dropped. If only one distinct leaf remains its Id is returned directly
// (no Concat node created). Concats with the same child sequence dedup.
// The returned Id carries one reference for the caller. Internally the
// concat retains each child it stores; releasing the concat releases
// those internal child references.
Twine::Id concat(std::span<const Twine::Id> parts);
Twine::Id concat(Twine::Id a, Twine::Id b);
// Non-interning lookup: return the Id of the leaf whose string equals
// `sv`, or Twine::Null if no such leaf exists. Does not allocate.
Twine::Id lookup(std::string_view sv) const;
// Refcount control. retain bumps; release decrements and, on reaching
// zero, marks the slot dead, drops it from the dedup indexes, releases
// any child refs the slot owned, and pushes the slot id onto the free
// list for reuse by the next intern/concat. Both no-op on Twine::Null.
size_t index(Twine* p) const;
void retain(Twine::Id id);
void release(Twine::Id id);
uint32_t refcount(Twine::Id id) const;
bool is_alive(Twine::Id id) const;
// Quick character queries on any flat node — avoids materializing the
// full string for the common `name[0] == '$'` / `.isPublic()` tests.
char first_char(Twine::Id id) const;
bool is_public(Twine::Id id) const { return first_char(id) == '\\'; }
// Materialize a Twine to the pipe-separated flat string used by the
// existing src attribute convention. Leaves visit in left-to-right DFS
// order; duplicate leaves are skipped to match `pool`-style semantics.
std::string flatten(Twine::Id id, char sep = '|') const;
// Materialize a flat node (Leaf or Suffix) to its single string. id
// must be a flat node (not a Concat) and not Twine::Null.
std::string flat_string(Twine::Id id) const { return flat_string_(id); }
// Format an interned Id as the canonical src-attribute reference "@N".
// Twine::Null formats as the empty string.
std::string format_ref(Twine::Id id) const;
// Parse an "@N" reference back to an Id
static std::optional<size_t> parse_ref(std::string_view s);
Twine::Id get_ref(std::string_view s);
const Twine &operator[](Twine::Id id) const { return *id; }
size_t size() const { return nodes_.size(); }
size_t leaf_count() const { return leaf_index_.size(); }
size_t concat_count() const { return concat_index_.size(); }
size_t suffix_count() const { return suffix_index_.size(); }
// One-shot debug dump of the entire pool to stdout via log(). Each leaf
// shows its string; each concat shows its child id list. Intended for
// `dump -twines` or ad-hoc tracing — output volume scales with size().
void dump(const char *banner = nullptr) const;
// Rebuild the pool to contain only the nodes named in `live` plus the
// transitive children of any live concats. Returns an old-id -> new-id
// remap; ids not in the result are dead. Callers must rewrite every
// stored "@N" cell src through the returned remap immediately, since
// after this call the old ids no longer mean what they used to.
dict<Twine::Id, Twine::Id> gc(const pool<Twine::Id> &live);
// Reconstruct `src->nodes_[src_id]` inside *this. Walks the structure
// — intern leaves, concat children — so a concat in `src` becomes a
// concat in this pool, not a flat literal of its leaves. Returns the
// id in this pool with +1 for the caller (release when done). Both
// pools may differ; the source is consulted read-only.
Twine::Id copy_from(const TwinePool &src, Twine::Id src_id);
// Iterate every live (non-tombstoned) node. fn is `void(Twine::Id, const Twine&)`.
template <typename Fn>
void for_each_live(Fn fn) const {
for (auto& n : nodes_) {
if (n.is_dead())
continue;
fn(&n, n); // TODO de-stupid this
}
}
private:
std::vector<Twine> nodes_;
std::vector<uint32_t> refcount_;
std::list<Twine::Id> free_list_;
// --- Intrusive dedup indexes (Step 0) -----------------------------------
// Each set stores only the Twine::Id; hash/eq functors reach into
// nodes_[id] for the keying data. This avoids the duplicate-string cost
// of the old dict<std::string, Twine::Id> approach.
// All functors hold a raw pointer to *this; TwinePool is non-movable
// and copy-assignment rebuilds the sets from scratch so the pointer
// always stays valid.
using SuffixKey = std::pair<Twine::Id, std::string_view>;
struct LeafHash {
using is_transparent = void;
const TwinePool *pool;
size_t operator()(Twine::Id id) const noexcept {
return std::hash<std::string_view>{}(id->leaf());
}
size_t operator()(std::string_view sv) const noexcept {
return std::hash<std::string_view>{}(sv);
}
};
struct LeafEq {
using is_transparent = void;
const TwinePool *pool;
bool operator()(Twine::Id a, Twine::Id b) const noexcept {
return a->leaf() == b->leaf();
}
bool operator()(Twine::Id id, std::string_view sv) const noexcept {
return id->leaf() == sv;
}
bool operator()(std::string_view sv, Twine::Id id) const noexcept {
return sv == id->leaf();
}
};
struct SuffixHash {
using is_transparent = void;
const TwinePool *pool;
static size_t combine(size_t a, size_t b) noexcept {
return a ^ (b + 0x9e3779b9u + (a << 6) + (a >> 2));
}
size_t operator()(Twine::Id id) const noexcept {
const auto &s = id->suffix();
return combine(std::hash<Twine::Id>{}(s.prefix),
std::hash<std::string_view>{}(s.tail));
}
size_t operator()(SuffixKey k) const noexcept {
return combine(std::hash<Twine::Id>{}(k.first),
std::hash<std::string_view>{}(k.second));
}
};
struct SuffixEq {
using is_transparent = void;
const TwinePool *pool;
bool operator()(Twine::Id a, Twine::Id b) const noexcept {
const auto &sa = a->suffix();
const auto &sb = b->suffix();
return sa.prefix == sb.prefix && sa.tail == sb.tail;
}
bool operator()(Twine::Id id, SuffixKey k) const noexcept {
const auto &s = id->suffix();
return s.prefix == k.first && s.tail == k.second;
}
bool operator()(SuffixKey k, Twine::Id id) const noexcept {
return (*this)(id, k);
}
};
struct ConcatHash {
using is_transparent = void;
const TwinePool *pool;
static size_t hash_ids(std::span<const Twine::Id> v) noexcept {
size_t h = 0;
for (Twine::Id c : v)
h ^= std::hash<Twine::Id>{}(c) + 0x9e3779b9u + (h << 6) + (h >> 2);
return h;
}
size_t operator()(Twine::Id id) const noexcept {
return hash_ids(id->children());
}
size_t operator()(std::span<const Twine::Id> v) const noexcept {
return hash_ids(v);
}
};
struct ConcatEq {
using is_transparent = void;
const TwinePool *pool;
bool operator()(Twine::Id a, Twine::Id b) const noexcept {
return a->children() == b->children();
}
bool operator()(Twine::Id id, std::span<const Twine::Id> v) const noexcept {
const auto &ch = id->children();
return ch.size() == v.size() &&
std::equal(ch.begin(), ch.end(), v.begin());
}
bool operator()(std::span<const Twine::Id> v, Twine::Id id) const noexcept {
return (*this)(id, v);
}
};
std::unordered_set<Twine::Id, LeafHash, LeafEq> leaf_index_;
std::unordered_set<Twine::Id, SuffixHash, SuffixEq> suffix_index_;
std::unordered_set<Twine::Id, ConcatHash, ConcatEq> concat_index_;
// -------------------------------------------------------------------------
Twine::Id alloc_slot_(Twine &&node);
void destroy_slot_(Twine::Id id);
void collect_leaves(Twine::Id id, pool<std::string> &out) const;
// Materialize a flat node (Leaf or Suffix) into its full string.
std::string flat_string_(Twine::Id id) const;
// Populate the three indexes from the current nodes_ vector (used by
// the copy constructor/assignment and by gc()).
void rebuild_indexes_();
};
// // Owning reference to a Twine slot. Retains on construction (and on copy
// // of a non-empty ref), releases on destruction. Use this in transient
// // container types — FfData, Mem helpers — that need to keep a src_id_
// // alive across destruction of the original AttrObject that minted it,
// // without having to fall back to a flattened path-string stash.
// //
// // Empty (no pool/no id) by default. A non-empty ref always carries a
// // non-null pool and a live id.
// class OwnedTwine
// {
// public:
// OwnedTwine() = default;
// // Adopt the +1 reference returned by `intern` / `concat` / `intern_suffix`
// // / `copy_from`. Use OwnedTwine(pool, id, retain=true) when copying an
// // id already held elsewhere (e.g. another AttrObject's src_id_).
// OwnedTwine(TwinePool *pool, Twine::Id id, bool retain = true) : pool_(pool), id_(id) {
// if (retain && pool_ && id_ != Twine::Null)
// pool_->retain(id_);
// }
// OwnedTwine(const OwnedTwine &other) : pool_(other.pool_), id_(other.id_) {
// if (pool_ && id_ != Twine::Null)
// pool_->retain(id_);
// }
// OwnedTwine(OwnedTwine &&other) noexcept : pool_(other.pool_), id_(other.id_) {
// other.pool_ = nullptr;
// other.id_ = Twine::Null;
// }
// OwnedTwine &operator=(const OwnedTwine &other) {
// if (this == &other)
// return *this;
// release_();
// pool_ = other.pool_;
// id_ = other.id_;
// if (pool_ && id_ != Twine::Null)
// pool_->retain(id_);
// return *this;
// }
// OwnedTwine &operator=(OwnedTwine &&other) noexcept {
// if (this == &other)
// return *this;
// release_();
// pool_ = other.pool_;
// id_ = other.id_;
// other.pool_ = nullptr;
// other.id_ = Twine::Null;
// return *this;
// }
// ~OwnedTwine() { release_(); }
// void reset() {
// release_();
// pool_ = nullptr;
// id_ = Twine::Null;
// }
// TwinePool *pool() const { return pool_; }
// Twine::Id id() const { return id_; }
// bool empty() const { return id_ == Twine::Null; }
// private:
// TwinePool *pool_ = nullptr;
// Twine::Id id_ = Twine::Null;
// void release_() {
// if (pool_ && id_ != Twine::Null)
// pool_->release(id_);
// }
// };
struct TwinePoolExtender {
TwinePool& pool;
size_t offset;
private:
size_t resize_for_idx(size_t idx) {
auto real_idx = offset + idx;
pool.nodes_.resize(std::max(pool.nodes_.size(), real_idx + 1));
return real_idx;
}
void commit(Twine&& twine, size_t idx) {
pool.nodes_[idx] = std::move(twine);
pool.leaf_index_.insert(&pool.nodes_[idx]);
}
public:
// TwinePoolExtender(Design* design) : pool(design->twines), offset(design->twines.size()) {}
void extend_leaf(std::string leaf, size_t idx) {
auto real_idx = resize_for_idx(idx);
commit(Twine(leaf), real_idx);
}
void extend_concat(std::vector<size_t> children, size_t idx) {
auto real_idx = resize_for_idx(idx);
Twine* first = &pool.nodes_.front() + offset;
std::vector<Twine*> real_children;
real_children.reserve(children.size());
for (auto child : children)
real_children.push_back(first + child);
commit(Twine(std::move(real_children)), real_idx);
}
void extend_suffix(size_t prefix, std::string tail, size_t idx) {
auto real_idx = resize_for_idx(idx);
Twine* first = &pool.nodes_.front() + offset;
Twine* real_prefix = first + prefix;
commit(Twine(Twine::Suffix(real_prefix, std::move(tail))), real_idx);
}
void finish() {
for (size_t i = offset; i < pool.nodes_.size(); i++)
if (pool.nodes_[i].is_dead())
pool.free_list_.push_back(&pool.nodes_[i]);
}
};
YOSYS_NAMESPACE_END
#endif