3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2026-07-15 03:35:40 +00:00

twine: fix replayability, reduce TwineSearch usage

This commit is contained in:
Emil J. Tywoniak 2026-06-22 17:53:19 +02:00
parent e9eb3889b7
commit 7c73fd62e4
41 changed files with 273 additions and 272 deletions

View file

@ -39,12 +39,6 @@ void manufacture_info(InputType flop, OutputType& info, FfInitVals *initvals) {
info.sig_q = cell->getPort(TW::Q);
info.width = GetSize(info.sig_q);
info.attributes = cell->attributes;
// Carry src across construction → emit() as an owning Twine
// reference. Retaining a slot on the source pool keeps it
// alive even if the source cell gets removed between
// manufacture_info() and emit(); emit() then transfers the
// id verbatim into the new cell — no flatten/re-intern, no
// pipe-leaf risk for cells whose src is a Concat.
if (cell->src_id() != Twine::Null && cell->module && cell->module->design)
info.src_twine = cell->src_id();
if (initvals)
@ -762,7 +756,6 @@ Cell *FfData::emit() {
}
}
// src is carried in info.src_twine (an OwnedTwine retaining the
// source slot). Transfer the id verbatim to the new cell — same
// pool, no flatten. The OwnedTwine still holds its own ref until
// FfData is destroyed; set_src_id retains on the cell's behalf.
cell->attributes = attributes;

View file

@ -172,8 +172,9 @@ struct FfData : FfTypeData {
dict<IdString, Const> attributes;
// Stashed src across construction → emit. Refcount-managed so the
// source cell's pool slot survives if the cell itself is removed
// before emit() runs. Empty when the source cell had no src.
TwineRef src_twine;
// before emit() runs. Null when the source cell had no src (default
// TwineRef() is index 0, a valid constid, so it must be Null here).
TwineRef src_twine = Twine::Null;
FfData(Module *module = nullptr, FfInitVals *initvals = nullptr, IdString name = IdString()) : module(module), initvals(initvals), cell(nullptr), name(name) {
width = 0;

View file

@ -887,7 +887,6 @@ Cell *Mem::extract_rdff(int idx, FfInitVals *initvals) {
return nullptr;
// Keep src as a "@N" reference into the design's twine pool throughout
// — never flatten to a literal path string. That way every addX call
// below adopts the same slot as Mem itself (via set_src_attribute's
// "@N" parse_ref path), and there's no flatten → re-intern → pipe-
// leaf round-trip on cells whose src is a Concat node.
@ -998,7 +997,6 @@ Cell *Mem::extract_rdff(int idx, FfInitVals *initvals) {
IdString name = stringf("$%s$rdreg[%d]", memid, idx);
FfData ff(module, initvals, name);
// Carry mem's src into the ff via the OwnedTwine handle — same
// pool, direct id retain. emit() transfers verbatim.
ff.src_twine = mem_src;
ff.width = GetSize(port.data);

View file

@ -953,7 +953,6 @@ bool RTLIL::AttrObject::get_bool_attribute(IdString id) const
void RTLIL::AttrObject::set_string_attribute(IdString id, string value)
{
// ID::src on the base AttrObject is not routable here because the base
// has no Design context — callers needing string-form src must go
// through the subtype helper (Cell::set_src_attribute / Wire::… / …)
// which derives the design from context.
log_assert(id != ID::src && "set_string_attribute(ID::src,...) on AttrObject base; use the subtype helper");
@ -965,7 +964,6 @@ void RTLIL::AttrObject::set_string_attribute(IdString id, string value)
string RTLIL::AttrObject::get_string_attribute(IdString id) const
{
// ID::src is not in the dict — callers must use the subtype helper.
log_assert(id != ID::src && "get_string_attribute(ID::src) on AttrObject base; use the subtype helper");
std::string value;
const auto it = attributes.find(id);
@ -1821,22 +1819,22 @@ std::vector<RTLIL::Module*> RTLIL::Design::selected_modules(RTLIL::SelectPartial
switch (boxes)
{
case RTLIL::SB_UNBOXED_WARN:
log_warning("Ignoring boxed module %s.\n", twines.str(it.first).c_str());
log_warning("Ignoring boxed module %s.\n", log_id(it.second));
break;
case RTLIL::SB_EXCL_BB_WARN:
log_warning("Ignoring blackbox module %s.\n", twines.str(it.first).c_str());
log_warning("Ignoring blackbox module %s.\n", log_id(it.second));
break;
case RTLIL::SB_UNBOXED_ERR:
log_error("Unsupported boxed module %s.\n", twines.str(it.first).c_str());
log_error("Unsupported boxed module %s.\n", log_id(it.second));
break;
case RTLIL::SB_EXCL_BB_ERR:
log_error("Unsupported blackbox module %s.\n", twines.str(it.first).c_str());
log_error("Unsupported blackbox module %s.\n", log_id(it.second));
break;
case RTLIL::SB_UNBOXED_CMDERR:
log_cmd_error("Unsupported boxed module %s.\n", twines.str(it.first).c_str());
log_cmd_error("Unsupported boxed module %s.\n", log_id(it.second));
break;
case RTLIL::SB_EXCL_BB_CMDERR:
log_cmd_error("Unsupported blackbox module %s.\n", twines.str(it.first).c_str());
log_cmd_error("Unsupported blackbox module %s.\n", log_id(it.second));
break;
default:
break;
@ -1845,13 +1843,13 @@ std::vector<RTLIL::Module*> RTLIL::Design::selected_modules(RTLIL::SelectPartial
switch(partials)
{
case RTLIL::SELECT_WHOLE_WARN:
log_warning("Ignoring partially selected module %s.\n", twines.str(it.first).c_str());
log_warning("Ignoring partially selected module %s.\n", log_id(it.second));
break;
case RTLIL::SELECT_WHOLE_ERR:
log_error("Unsupported partially selected module %s.\n", twines.str(it.first).c_str());
log_error("Unsupported partially selected module %s.\n", log_id(it.second));
break;
case RTLIL::SELECT_WHOLE_CMDERR:
log_cmd_error("Unsupported partially selected module %s.\n", twines.str(it.first).c_str());
log_cmd_error("Unsupported partially selected module %s.\n", log_id(it.second));
break;
default:
break;
@ -1893,7 +1891,6 @@ RTLIL::Module::~Module()
delete pr.second;
for (auto binding : bindings_)
delete binding;
// Module's own src — release last so the pool stays valid for
// inner releases above.
if (design)
design->obj_release_src(this);
@ -3172,7 +3169,6 @@ void RTLIL::Module::cloneInto(RTLIL::Module *new_mod, bool src_id_verbatim) cons
// Transfer src across designs. Both modules must be attached
// to a design for the migration to happen; in the
// detached-clone() scratch flow (equiv_make, etc.) src is
// dropped here — those callers don't preserve src across the
// temp clone by design.
if (this->design && new_mod->design)
copy_src_into(this, this->design, new_mod, new_mod->design);
@ -3189,7 +3185,6 @@ void RTLIL::Module::cloneInto(RTLIL::Module *new_mod, bool src_id_verbatim) cons
return;
// Preserve name already set by addWire/addCell (in dst's pool).
TwineRef saved_name = dst_obj->meta_ ? dst_obj->meta_->name : Twine::Null;
// Recycle old meta slot (no field releases — name's retain lives on).
if (dst_obj->meta_) {
dst_obj->meta_->name = Twine::Null;
dst_obj->meta_->src = Twine::Null;
@ -3274,7 +3269,6 @@ void RTLIL::Module::cloneInto(RTLIL::Module *new_mod, bool src_id_verbatim) cons
// TwinePool allocates slots sequentially as copy_src_into →
// copy_from interns each wire's src, so the destination pool
// ends up with leaves in the same order the frontend
// originally interned them — that lets write_rtlil emit
// byte-equal "@N" refs across single-module clones into an
// existing destination design.
// Re-intern each wire/cell name from the source design's pool into
@ -3472,7 +3466,6 @@ void RTLIL::Module::add(RTLIL::Process *process)
processes[process->meta_->name] = process;
process->module = this;
// Propagate module back-pointer to every CaseRule/SwitchRule in the
// root case tree and every MemWriteAction in the sync rules — so the
// per-Design src meta vector can be resolved from any inner-process
// AttrObject via `module->design` after attach.
process->root_case.setModuleRecursive(this);
@ -3853,7 +3846,6 @@ RTLIL::Memory *RTLIL::Module::addMemory(TwineRef name, const RTLIL::Memory *othe
mem->size = other->size;
mem->attributes = other->attributes;
{
// Clone path drops src for now — caller responsible for migrating
// src across the design boundary if needed. addMemory(name) is the
// common case.
(void)other;
@ -6783,7 +6775,6 @@ RTLIL::CaseRule *RTLIL::CaseRule::clone() const
new_caserule->compare = compare;
new_caserule->actions = actions;
new_caserule->attributes = attributes;
// clone() drops src — CaseRule has no pool backpointer, so we can't
// retain. The caller (Module::addProcess(name, other)) is responsible
// for walking the cloned tree and migrating src via context.
for (auto &it : switches)
@ -6807,7 +6798,6 @@ RTLIL::SwitchRule *RTLIL::SwitchRule::clone() const
RTLIL::SwitchRule *new_switchrule = new RTLIL::SwitchRule;
new_switchrule->signal = signal;
new_switchrule->attributes = attributes;
// clone() drops src — see CaseRule::clone for rationale.
for (auto &it : cases)
new_switchrule->cases.push_back(it->clone());
return new_switchrule;
@ -6821,7 +6811,6 @@ RTLIL::SyncRule *RTLIL::SyncRule::clone() const
new_syncrule->signal = signal;
new_syncrule->actions = actions;
new_syncrule->mem_write_actions = mem_write_actions;
// Drop meta_idx_ on the cloned MemWriteActions — the integer was
// copied by the vector assignment above without registering with
// any pool; the caller is responsible for migrating src across the
// clone via context (see Process::clone).
@ -6974,7 +6963,6 @@ void RTLIL::Memory::absorb_attrs(dict<IdString, RTLIL::Const> &&buf)
module->design->absorb_attrs(this, std::move(buf));
}
// CaseRule / SwitchRule / MemWriteAction src helpers — all delegate to
// module->design->obj_* via the back-pointer added in the earlier commit.
TwineRef RTLIL::CaseRule::src_id() const
{

View file

@ -555,12 +555,16 @@ struct RTLIL::IdString
// often one needs to check if a given IdString is part of a list (for example a list
// of cell types). the following functions helps with that.
// Constrained to 2+ args so a single argument always resolves to a concrete
// overload below; otherwise a single argument matching none of them (e.g. a
// TwineRef) would re-match this template and recurse infinitely.
template<typename... Args>
bool in(const Args &... args) const {
bool in(const Args &... args) const requires (sizeof...(Args) != 1) {
return (... || in(args));
}
bool in(IdString rhs) const { return *this == rhs; }
inline bool in(TwineRef rhs) const;
bool in(const char *rhs) const { return *this == rhs; }
bool in(const std::string &rhs) const { return *this == rhs; }
inline bool in(const pool<IdString> &rhs) const;
@ -600,6 +604,8 @@ inline bool operator==(TwineRef a, RTLIL::IdString b) {
}
inline bool operator==(RTLIL::IdString a, TwineRef b) { return b == a; }
inline bool RTLIL::IdString::in(TwineRef rhs) const { return *this == rhs; }
struct RTLIL::OwningIdString : public RTLIL::IdString {
inline OwningIdString() { }
inline OwningIdString(const OwningIdString &str) : IdString(str) { get_reference(); }
@ -1402,7 +1408,6 @@ inline bool operator!=(RTLIL::IdString lhs, const RTLIL::NameMasqBase<Derived> &
// Read-only masquerade for Wire::name. Reads materialise the TwineRef in
// the owning Design's twines pool into a temporary IdString. Writes are
// intentionally unsupported — use Module::rename(wire, new_name) instead.
// Defined before Wire so it can be used as a [[no_unique_address]] member.
struct RTLIL::WireNameMasq : RTLIL::NameMasqBase<RTLIL::WireNameMasq> {
WireNameMasq() = default;
@ -2194,7 +2199,6 @@ struct RTLIL::Design
// Wholesale-copy this design into `dst`. `dst` must be empty (no
// modules). Copies twines verbatim and clones each module
// preserving src_id_ values directly — avoids the per-module
// copy_from pool rebuild and yields byte-identical RTLIL output
// across design -push/-pop, -save/-load, etc.
void clone_into(RTLIL::Design *dst) const;
@ -2325,7 +2329,6 @@ private:
friend struct RTLIL::Patch;
public:
// Shadows NamedObject::name. Reads materialise via twines; writes
// are a compile error — use Module::rename(wire, new_name) instead.
[[no_unique_address]] RTLIL::WireNameMasq name;
Hasher::hash_t hashidx_;
@ -2395,7 +2398,6 @@ struct RTLIL::Memory : public RTLIL::AttrObject
Memory();
~Memory();
// Back-pointer to the owning module — same role as Cell::module /
// Wire::module. Set by Module::addMemory / the frontends that
// construct Memory free-standing before attaching to a module.
// Lets Memory's src access resolve uniformly via module->design.
@ -2436,7 +2438,6 @@ private:
bool bufnorm_handle_setPort(TwineRef portname, RTLIL::SigSpec &signal, dict<TwineRef, RTLIL::SigSpec>::iterator conn_it);
public:
// Shadows NamedObject::name. Reads materialise via twines; writes
// are a compile error — use Module::rename(cell, new_name) instead.
[[no_unique_address]] RTLIL::CellNameMasq name;
Hasher::hash_t hashidx_;
@ -2525,7 +2526,6 @@ struct RTLIL::CaseRule : public RTLIL::AttrObject
// Walk the whole CaseRule subtree (this case, every switch, every
// nested case, every MemWriteAction inside this process's sync rules
// — those are reached through Process, not here) and set `module` on
// each. Idempotent.
void setModuleRecursive(RTLIL::Module *m);
@ -2706,7 +2706,6 @@ inline Hasher RTLIL::SigBit::hash_into(Hasher h) const {
inline Hasher RTLIL::SigBit::hash_top() const {
Hasher h;
if (wire) {
// Use the wire's name (TwineRef) directly — avoids IdString materialisation.
TwineRef name = wire->meta_ ? wire->meta_->name : Twine::Null;
h.eat(name);
h.eat(offset);
@ -3075,7 +3074,6 @@ public:
virtual RTLIL::Module *clone() const;
// Clone variant that attaches the new module to `dst` BEFORE cloneInto
// runs. This is the right pattern when the destination design is known
// up front — it avoids the "detached module, attach later" flow and
// the pending-literal src stashing it entails. Subtypes override to
// preserve their type (AstModule). `src_id_verbatim` is forwarded to
// cloneInto.
@ -3104,7 +3102,6 @@ public:
return design->selected_member(meta_->name, member->meta_->name);
}
// Primary (fast) overloads — key directly into the dict.
RTLIL::Wire* wire(TwineRef id) {
auto it = wires_.find(id);
return it == wires_.end() ? nullptr : it->second;

View file

@ -1172,7 +1172,6 @@ bool RTLIL::Cell::bufnorm_handle_setPort(TwineRef portname, SigSpec &signal, dic
}
auto dir = port_dir(portname);
// Fast path: connecting a full driverless wire to an output port — everything else
// goes through the bufnorm queues and is handled during the next bufNormalize call
if ((dir == RTLIL::PD_OUTPUT || dir == RTLIL::PD_INOUT) && signal.is_wire()) {
Wire *w = signal.as_wire();

View file

@ -145,7 +145,6 @@ void twine_prepopulate() {
// log_assert(parent->is_flat() && "Suffix parent must be a flat node (Leaf or Suffix)");
// if (tail.empty()) {
// // No tail means "the same string as parent". Hand back a fresh
// // owning ref on parent — semantically equivalent to a degenerate
// // suffix node, but we avoid allocating a slot for it.
// retain(parent);
// return parent;

View file

@ -109,7 +109,6 @@ struct TW {
};
#define TW(id) ((size_t)std::integral_constant<int, lookup_well_known_id(#id)>::value)
// #define TW(name) TW::lookup(#name)
struct Twine {
static constexpr TwineRef Null = std::numeric_limits<size_t>::max();
@ -120,19 +119,31 @@ struct Twine {
auto operator<=>(const Suffix&) const = default;
};
struct AutoPrefix {
struct AutoSuffix {
const std::string *prefix;
std::string tail;
auto operator<=>(const AutoPrefix&) const = default;
auto operator<=>(const AutoSuffix&) const = default;
};
std::variant<std::monostate, std::string, std::vector<TwineRef>, Suffix, AutoPrefix> data;
std::variant<
// Unused slot
std::monostate,
// "leaf", regular deduplicated string
std::string,
// "concat", for src only, requires concatenating character convention - '|' for src attributes, others for others in the future
std::vector<TwineRef>,
// "suffix", deduplicates shared prefixes
Suffix,
// transient suffix constructed with NEW_TWINE and NEW_TWINE_SUFFIX
// turned into a regular Suffix when added to a TwinePool
AutoSuffix> 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<TwineRef>>(data); }
bool is_suffix() const { return std::holds_alternative<Suffix>(data); }
bool is_auto_prefix() const { return std::holds_alternative<AutoPrefix>(data); }
bool is_auto_prefix() const { return std::holds_alternative<AutoSuffix>(data); }
bool is_flat() const { return is_leaf() || is_suffix(); }
const std::string &leaf() const { return std::get<std::string>(data); }
const std::vector<TwineRef> &children() const { return std::get<std::vector<TwineRef>>(data); }
@ -150,7 +161,6 @@ struct TwineHash {
size_t operator()(const Twine& t) const noexcept;
size_t operator()(TwineRef ref) const noexcept;
// size_t operator()(std::string_view v) const noexcept;
};
struct TwineEq {
@ -161,8 +171,6 @@ struct TwineEq {
bool operator()(TwineRef a, TwineRef b) const noexcept;
bool operator()(TwineRef a, const Twine& b) const noexcept;
bool operator()(const Twine& a, TwineRef b) const noexcept;
// bool operator()(TwineRef a, std::string_view b) const noexcept;
// bool operator()(std::string_view a, TwineRef b) const noexcept;
};
@ -365,12 +373,8 @@ struct TwinePool {
return ref;
}
// Interns a structural Twine verbatim and returns the handle. Leaf content
// is stored as-is — callers holding an escaped string must strip the '\'
// and tag publicity themselves (or use the add(std::string) overload).
// Suffix names inherit the prefix handle's publicity.
TwineRef add(Twine t) {
if (auto *ap = std::get_if<Twine::AutoPrefix>(&t.data)) {
if (auto *ap = std::get_if<Twine::AutoSuffix>(&t.data)) {
TwineRef pref = add_inner(Twine{*ap->prefix});
return add_inner(Twine{Twine::Suffix{pref, std::move(ap->tail)}});
}
@ -612,7 +616,7 @@ struct DeepTwineEq {
// Required by unordered_set to handle hash collisions between two TwineRefs.
bool operator()(TwineRef a, TwineRef b) const {
if (a == b) return true; // Index or structural equality shortcut
std::string fb = flatten(b);
std::string fb = pool->unescaped_str(b);
return (*this)(a, std::string_view(fb));
}
@ -660,7 +664,7 @@ struct TwineChildPool {
// Local analog of TwinePool::add; see there for the convention.
TwineRef add(Twine t) {
if (auto *ap = std::get_if<Twine::AutoPrefix>(&t.data)) {
if (auto *ap = std::get_if<Twine::AutoSuffix>(&t.data)) {
TwineRef pref = add_inner(Twine{*ap->prefix});
return add_inner(Twine{Twine::Suffix{pref, std::move(ap->tail)}});
}
@ -731,6 +735,11 @@ struct TwineSearch {
index.insert(STATIC_TWINE_END + idx);
}
}
// Keep a hoisted search current after adding a ref to the pool, so the
// search need not be rebuilt (O(pool)) between finds in a loop.
void insert(TwineRef ref) {
index.insert(twine_untag(ref));
}
// Escaped-name aware. Resolves both statics and locals by content.
TwineRef find(std::string_view sv) const {
bool is_public = !sv.empty() && sv[0] == '\\';

View file

@ -134,7 +134,6 @@ namespace {
void apply_src(Module* mod, Cell* root, const std::vector<Cell*>& extras,
const std::vector<Cell*>& targets, Cell* merge_src_into)
{
// Without a design there's no pool — the cells can't carry typed
// src, so silently drop merge-of-src in that path.
if (!mod || !mod->design)
return;
@ -205,7 +204,6 @@ void Patch::patch(Cell* root_cell, TwineRef old_port, SigSpec new_sig,
apply_src(mod, root_cell, extras, committed, merge_src_into);
// Drop root_cell's driver on the output port BEFORE wiring old_sig to
// new_sig — otherwise old_sig would briefly have two drivers (root_cell
// and new_sig) which signorm flags as conflicting.
root_cell->unsetPort(old_port);
@ -213,7 +211,6 @@ void Patch::patch(Cell* root_cell, TwineRef old_port, SigSpec new_sig,
map->add(old_sig, new_sig);
mod->connect_incremental(old_sig, new_sig);
// Remove root cell only — no input-cone walk.
mod->remove(root_cell);
}
@ -277,7 +274,6 @@ void Patch::commit_inheriting_src(Cell* src_source) {
Cell *committed = commit_cell(std::move(cell));
// commit_cell attaches the cell to mod, so adopt_src_from can
// now resolve the pool via committed->module->design. Direct
// id transfer — no flatten/re-intern detour.
if (src_source)
committed->adopt_src_from(src_source);
}

View file

@ -6,7 +6,6 @@
YOSYS_NAMESPACE_BEGIN
// No virtual methods — subclasses cannot be dispatched through a Patch pointer.
struct RTLIL::Patch : public CellAdderMixin<RTLIL::Patch>
{
protected:

View file

@ -127,7 +127,6 @@ public:
// ---------------------------------------------------
// BitGrouper — partition output bits by a per-bit key
// ---------------------------------------------------
//
// Many passes that split a multi-bit cell or word-level FF into smaller

View file

@ -305,12 +305,12 @@ RTLIL::IdString new_id_suffix(std::string_view file, int line, std::string_view
#define NEW_ID_SUFFIX(suffix) \
YOSYS_NAMESPACE_PREFIX new_id_suffix(__FILE__, __LINE__, __FUNCTION__, suffix)
#define NEW_TWINE \
YOSYS_NAMESPACE_PREFIX Twine{YOSYS_NAMESPACE_PREFIX Twine::AutoPrefix{[](std::string_view func) -> const std::string * { \
YOSYS_NAMESPACE_PREFIX Twine{YOSYS_NAMESPACE_PREFIX Twine::AutoSuffix{[](std::string_view func) -> const std::string * { \
static std::unique_ptr<const std::string> prefix(YOSYS_NAMESPACE_PREFIX create_id_prefix(__FILE__, __LINE__, func)); \
return prefix.get(); \
}(__FUNCTION__), std::to_string(YOSYS_NAMESPACE_PREFIX autoidx++)}}
#define NEW_TWINE_SUFFIX(suffix) \
YOSYS_NAMESPACE_PREFIX Twine{YOSYS_NAMESPACE_PREFIX Twine::AutoPrefix{[](std::string_view func) -> const std::string * { \
YOSYS_NAMESPACE_PREFIX Twine{YOSYS_NAMESPACE_PREFIX Twine::AutoSuffix{[](std::string_view func) -> const std::string * { \
static std::unique_ptr<const std::string> prefix(YOSYS_NAMESPACE_PREFIX create_id_prefix(__FILE__, __LINE__, func)); \
return prefix.get(); \
}(__FUNCTION__), std::string(suffix) + "$" + std::to_string(YOSYS_NAMESPACE_PREFIX autoidx++)}}