#ifndef YOSYS_TWINE_H #define YOSYS_TWINE_H #include "kernel/yosys_common.h" #include #include #include #include #include #include #include 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->src_twines). struct Twine { using Id = uint32_t; static constexpr Id Null = std::numeric_limits::max(); // Suffix shares a `parent` prefix with other suffixes and contributes // its own `tail` string. The materialized leaf string is // flat_string(parent) + tail, i.e. suffixes form trees whose leaves // (string variant) are the roots — like a reverse-trie of common // prefixes. The parent is itself flat (Leaf or Suffix), never a // Concat. struct Suffix { Id parent; std::string tail; }; // Leaf holds the literal path:line.col string. Suffix holds a parent // 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, Suffix> data; bool is_dead() const { return std::holds_alternative(data); } bool is_leaf() const { return std::holds_alternative(data); } bool is_concat() const { return std::holds_alternative>(data); } bool is_suffix() const { return std::holds_alternative(data); } bool is_flat() const { return is_leaf() || is_suffix(); } const std::string &leaf() const { return std::get(data); } const std::vector &children() const { return std::get>(data); } const Suffix &suffix() const { return std::get(data); } }; class TwinePool { public: TwinePool() = default; // 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(parent) + tail. `parent` 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 (parent, tail) dedup. The // returned Id carries one reference for the caller. Internally the // new suffix retains a reference on `parent`; releasing the suffix // releases that internal parent ref. Empty `tail` returns `parent` // (with +1 ref for the caller). Twine::Id intern_suffix(Twine::Id parent, 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 parts); Twine::Id concat(Twine::Id a, Twine::Id b); // 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. void retain(Twine::Id id); void release(Twine::Id id); uint32_t refcount(Twine::Id id) const; bool is_alive(Twine::Id id) const; // 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. static std::string format_ref(Twine::Id id); // Parse an "@N" reference back to an Id. Returns Twine::Null if `s` is // not exactly "@" followed by one or more decimal digits — so legacy // literal src strings (which always contain ':' separators and have no // reason to start with '@') are passed through unrecognized. static Twine::Id parse_ref(std::string_view s); // Lookup. Bounds-checked: out-of-range Id triggers log_assert via op. const Twine &operator[](Twine::Id id) const { return nodes_.at(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 gc(const pool &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 void for_each_live(Fn fn) const { for (size_t i = 0; i < nodes_.size(); i++) { const Twine &n = nodes_[i]; if (n.is_dead()) continue; fn(static_cast(i), n); } } private: std::vector nodes_; std::vector refcount_; std::vector free_list_; dict leaf_index_; dict, Twine::Id> concat_index_; dict, Twine::Id> suffix_index_; Twine::Id alloc_slot_(Twine &&node); void destroy_slot_(Twine::Id id); void collect_leaves(Twine::Id id, pool &out) const; // Materialize a flat node (Leaf or Suffix) into its full string. std::string flat_string_(Twine::Id id) const; }; // 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_); } }; YOSYS_NAMESPACE_END #endif