From 6f0e648543df970483b00dc73d9396d23f741bf4 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Mon, 24 Aug 2026 15:17:25 +0200 Subject: [PATCH] twine: split Twine into in-memory TwineNode that doesn't support autoidx variants and temporary TwineSpec that does --- CMakeLists.txt | 2 + backends/rtlil/rtlil_backend.cc | 17 +- backends/verilog/verilog_backend.cc | 2 +- frontends/ast/ast.cc | 15 +- frontends/ast/ast.h | 4 + frontends/ast/genrtlil.cc | 60 ++-- frontends/rtlil/rtlil_frontend.cc | 8 +- kernel/ff.h | 2 +- kernel/hashcons.h | 19 +- kernel/hashlib.h | 2 +- kernel/mem.h | 2 +- kernel/rtlil.cc | 2 +- kernel/rtlil.h | 22 +- kernel/rtlil_twine_compat.h | 19 ++ kernel/twine.cc | 392 +++++++++++++++--------- kernel/twine.h | 223 ++++++++++---- kernel/yosys_common.h | 14 +- kernel/yosys_config.h.in | 1 + passes/cmds/design.cc | 2 +- passes/cmds/dft_tag.cc | 18 +- passes/hierarchy/flatten.cc | 14 +- passes/techmap/abc_ops_reintegrate.cc | 2 +- passes/techmap/booth.cc | 2 +- passes/techmap/techmap.cc | 20 +- tests/unit/kernel/CMakeLists.txt | 1 + tests/unit/kernel/nameMasqTest.cc | 2 +- tests/unit/kernel/sigbitPerfTest.cc | 158 ++++++++++ tests/unit/kernel/twineHashTest.cc | 6 +- tests/unit/kernel/twinePublicityTest.cc | 12 +- tests/unit/kernel/twineSortTest.cc | 6 +- tests/various/rename_scramble_name.ys | 2 +- tests/various/rename_unescape.ys | 4 +- 32 files changed, 756 insertions(+), 299 deletions(-) create mode 100644 tests/unit/kernel/sigbitPerfTest.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index ce402d46c..f0e437214 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ option(YOSYS_ENABLE_UNIT_TESTS "Enable unit tests" ON) option(YOSYS_ENABLE_COVERAGE "Enable code coverage" OFF) option(YOSYS_ENABLE_PROFILING "Enable instruction profiling" OFF) option(YOSYS_ENABLE_FUNCTIONAL_TESTS "Enable running functional tests" OFF) +option(YOSYS_ENABLE_TWINE_PROVENANCE "Twine provenance for debugging. Worsens performance" OFF) set(YOSYS_PROGRAM_PREFIX "" CACHE STRING "Name prefix for programs, libraries, and data") set(YOSYS_COMPONENTS "everything" CACHE STRING "List of components to build (use pass names)") @@ -323,6 +324,7 @@ condition(YOSYS_ENABLE_SLANG NOT YOSYS_WITHOUT_SLANG) add_feature_info(have_glob YOSYS_ENABLE_GLOB "Glob expansion in filenames") add_feature_info(have_spawn YOSYS_ENABLE_SPAWN "Passes that invoke external tools") add_feature_info(have_threads YOSYS_ENABLE_THREADS "Multithreaded netlist operations") +add_feature_info(twine_provenance YOSYS_ENABLE_TWINE_PROVENANCE "Twine provenance for debugging. Worsens performance") add_feature_info(have_plugins YOSYS_ENABLE_PLUGINS "Dynamically loadable binary plugins") add_feature_info(with_abc YOSYS_ENABLE_ABC "Production-quality logic synthesis flow") add_feature_info(with_zlib YOSYS_ENABLE_ZLIB "Transparent Gzip decompression and FST file format support") diff --git a/backends/rtlil/rtlil_backend.cc b/backends/rtlil/rtlil_backend.cc index eaf2ff588..4cf4bc9b6 100644 --- a/backends/rtlil/rtlil_backend.cc +++ b/backends/rtlil/rtlil_backend.cc @@ -142,21 +142,26 @@ void RTLIL_BACKEND::dump_twines(std::ostream &f, const RTLIL::Design *design, co for (IdString id = used_id; id != IdString::Null && closed.insert(id).second; ) { ids.push_back(id); const TwineNode &n = design->twines[id]; - id = n.is_suffix() ? n.suffix().prefix.untag() : IdString(); + id = n.is_suffix() ? n.prefix() : IdString(); } std::sort(ids.begin(), ids.end()); f << stringf("twines\n"); for (IdString id : ids) { const TwineNode &n = design->twines[id]; - if (n.is_leaf()) { + switch (n.kind()) { + case TwineNode::Kind::Leaf: f << stringf(" leaf %zu ", id.raw()); - dump_const(f, RTLIL::Const(n.leaf())); + dump_const(f, RTLIL::Const(std::string(n.text()))); f << stringf("\n"); - } else if (n.is_suffix()) { - f << stringf(" suffix %zu %zu ", id.raw(), n.suffix().prefix.raw()); - dump_const(f, RTLIL::Const(n.suffix().tail)); + break; + case TwineNode::Kind::Suffix: + f << stringf(" suffix %zu %zu ", id.raw(), n.prefix().raw()); + dump_const(f, RTLIL::Const(std::string(n.text()))); f << stringf("\n"); + break; + case TwineNode::Kind::Dead: + break; } } f << stringf("end\n"); diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 63dad8c61..ca0d93f7e 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -2514,7 +2514,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module) } f << stringf(");\n"); if (!systemverilog && !module->processes.empty()) { - initial_id = Twine(NEW_ID).content_str(); + initial_id = TwineSpec(NEW_ID).content_str(); f << indent + " " << "reg " << id(initial_id) << " = 0;\n"; } diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 4d866660a..be8a5fe33 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -1117,7 +1117,7 @@ static IdString build_hier_content(TwinePool &pool, std::string_view content) if (dot == std::string_view::npos) return pool.add(std::string{content}).tag(true); IdString prefix = build_hier_content(pool, content.substr(0, dot)); - return pool.add(Twine::Suffix{prefix, std::string{content.substr(dot)}}); + return pool.add(TwineSpec::Suffix{prefix, std::string{content.substr(dot)}}); } IdString AST::intern_hier_name(RTLIL::Design *design, std::string_view escaped) @@ -1127,6 +1127,19 @@ IdString AST::intern_hier_name(RTLIL::Design *design, std::string_view escaped) return design->twines.add(std::string{escaped}); } +IdString AST::intern_src_loc(RTLIL::Design *design, const AstSrcLocType &location) +{ + return design->twines.add(stringf("$%s:%d", + RTLIL::encode_filename(*location.begin.filename), location.begin.line)); +} + +IdString AST::intern_src_name(RTLIL::Design *design, const AstSrcLocType &location, + std::string_view kind, int idx) +{ + return design->twines.add(TwineSpec::Suffix{intern_src_loc(design, location), + stringf("%s$%d", kind, idx)}); +} + void AST::set_src_attr(RTLIL::AttrObject *obj, const AstNode *ast) { obj->attributes[ID::src] = ast->loc_string(); diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index e5a0934c2..26ddaba3d 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -437,6 +437,10 @@ namespace AST IdString intern_hier_name(RTLIL::Design *design, std::string_view escaped); + IdString intern_src_loc(RTLIL::Design *design, const AstSrcLocType &location); + IdString intern_src_name(RTLIL::Design *design, const AstSrcLocType &location, + std::string_view kind, int idx); + // Helper for setting the src attribute. void set_src_attr(RTLIL::AttrObject *obj, const AstNode *ast); diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index 4b9e742f5..e1c54b926 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -44,11 +44,12 @@ using namespace AST_INTERNAL; // helper function for creating RTLIL code for unary operations static RTLIL::SigSpec uniop2rtlil(AstNode *that, IdString type, int result_width, const RTLIL::SigSpec &arg, bool gen_attributes = true) { - IdString name = current_module->design->twines.add(stringf("%s$%s:%d$%d", current_module->design->twines.str(type).c_str(), RTLIL::encode_filename(*that->location.begin.filename), that->location.begin.line, autoidx++)); + IdString name = intern_src_name(current_module->design, that->location, + current_module->design->twines.str(type), autoidx++); RTLIL::Cell *cell = current_module->addCell(name, type); set_src_attr(cell, that); - RTLIL::Wire *wire = current_module->addWire(Twine::Suffix{cell->name, "_Y"}, result_width); + RTLIL::Wire *wire = current_module->addWire(TwineSpec::Suffix{cell->name, "_Y"}, result_width); set_src_attr(wire, that); wire->is_signed = that->is_signed; @@ -76,7 +77,7 @@ static void widthExtend(AstNode *that, RTLIL::SigSpec &sig, int width, bool is_s return; } - IdString name = current_module->design->twines.add(stringf("$extend$%s:%d$%d", RTLIL::encode_filename(*that->location.begin.filename), that->location.begin.line, autoidx++)); + IdString name = intern_src_name(current_module->design, that->location, "$extend", autoidx++); RTLIL::Cell *cell = current_module->addCell(name, ID($pos)); set_src_attr(cell, that); @@ -103,11 +104,12 @@ static void widthExtend(AstNode *that, RTLIL::SigSpec &sig, int width, bool is_s // helper function for creating RTLIL code for binary operations static RTLIL::SigSpec binop2rtlil(AstNode *that, IdString type, int result_width, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right) { - IdString name = current_module->design->twines.add(stringf("%s$%s:%d$%d", current_module->design->twines.str(type).c_str(), RTLIL::encode_filename(*that->location.begin.filename), that->location.begin.line, autoidx++)); + IdString name = intern_src_name(current_module->design, that->location, + current_module->design->twines.str(type), autoidx++); RTLIL::Cell *cell = current_module->addCell(name, type); set_src_attr(cell, that); - RTLIL::Wire *wire = current_module->addWire(Twine::Suffix{cell->name, "_Y"}, result_width); + RTLIL::Wire *wire = current_module->addWire(TwineSpec::Suffix{cell->name, "_Y"}, result_width); set_src_attr(wire, that); wire->is_signed = that->is_signed; @@ -136,10 +138,9 @@ static RTLIL::SigSpec mux2rtlil(AstNode *that, const RTLIL::SigSpec &cond, const { log_assert(cond.size() == 1); - std::stringstream sstr; - sstr << "$ternary$" << RTLIL::encode_filename(*that->location.begin.filename) << ":" << that->location.begin.line << "$" << (autoidx++); + IdString name = intern_src_name(current_module->design, that->location, "$ternary", autoidx++); - RTLIL::Cell *cell = current_module->addCell(sstr.str(), ID($mux)); + RTLIL::Cell *cell = current_module->addCell(name, ID($mux)); set_src_attr(cell, that); RTLIL::Wire *wire = current_module->addWire(cell->name.str() + "_Y", left.size()); @@ -351,7 +352,7 @@ struct AST_INTERNAL::ProcessGenerator LookaheadRewriter la_rewriter(always.get()); // generate process and simple root case - proc = current_module->addProcess(stringf("$proc$%s:%d$%d", RTLIL::encode_filename(*always->location.begin.filename), always->location.begin.line, autoidx++)); + proc = current_module->addProcess(intern_src_name(current_module->design, always->location, "$proc", autoidx++)); set_src_attr(proc, always.get()); for (auto &attr : always->attributes) { if (attr.second->type != AST_CONSTANT) @@ -797,10 +798,9 @@ struct AST_INTERNAL::ProcessGenerator case AST_TCALL: if (ast->str == "$display" || ast->str == "$displayb" || ast->str == "$displayh" || ast->str == "$displayo" || ast->str == "$write" || ast->str == "$writeb" || ast->str == "$writeh" || ast->str == "$writeo") { - std::stringstream sstr; - sstr << ast->str << "$" << ast->location.begin.filename << ":" << ast->location.begin.line << "$" << (autoidx++); + IdString name = intern_src_name(current_module->design, ast->location, ast->str, autoidx++); - Wire *en = current_module->addWire(sstr.str() + "_EN", 1); + Wire *en = current_module->addWire(TwineSpec::Suffix{name, "_EN"}, 1); set_src_attr(en, ast); proc->root_case.actions.push_back({en, SigSpec(false)}); current_case->actions.push_back({en, SigSpec(true)}); @@ -818,7 +818,7 @@ struct AST_INTERNAL::ProcessGenerator } RTLIL::Const polarity = polarity_builder.build(); - RTLIL::Cell *cell = current_module->addCell(sstr.str(), ID($print)); + RTLIL::Cell *cell = current_module->addCell(name, ID($print)); set_src_attr(cell, ast); cell->setParam(ID::TRG_WIDTH, triggers.size()); cell->setParam(ID::TRG_ENABLE, (always->type == AST_INITIAL) || !triggers.empty()); @@ -886,18 +886,19 @@ struct AST_INTERNAL::ProcessGenerator if (ast->type == AST_FAIR) { flavor = "fair"; desc = "assume (eventually)"; } if (ast->type == AST_COVER) { flavor = "cover"; desc = "cover ()"; } - std::string cellname; + IdString cellname; if (ast->str.empty()) - cellname = stringf("$%s$%s:%d$%d", flavor, RTLIL::encode_filename(*ast->location.begin.filename), ast->location.begin.line, autoidx++); + cellname = intern_src_name(current_module->design, ast->location, + stringf("$%s", flavor), autoidx++); else - cellname = ast->str; - check_unique_id(current_module, cellname, ast, "procedural assertion"); + cellname = current_module->design->twines.add(std::string{ast->str}); + check_unique_id(current_module, current_module->design->twines.str(cellname), ast, "procedural assertion"); RTLIL::SigSpec check = ast->children[0]->genWidthRTLIL(-1, false, &subst_rvalue_map.stdmap()); if (GetSize(check) != 1) check = current_module->ReduceBool(NEW_ID, check); - Wire *en = current_module->addWire(cellname + "_EN", 1); + Wire *en = current_module->addWire(TwineSpec::Suffix{cellname, "_EN"}, 1); set_src_attr(en, ast); proc->root_case.actions.push_back({en, SigSpec(false)}); current_case->actions.push_back({en, SigSpec(true)}); @@ -1957,10 +1958,11 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) // generate $memrd cells for memory read ports case AST_MEMRD: { - std::stringstream sstr; - sstr << "$memrd$" << str << "$" << RTLIL::encode_filename(*location.begin.filename) << ":" << location.begin.line << "$" << (autoidx++); + IdString name = current_module->design->twines.add(TwineSpec::Suffix{ + intern_src_name(current_module->design, location, "$memrd", autoidx++), + stringf("$%s", str)}); - RTLIL::Cell *cell = current_module->addCell(sstr.str(), ID($memrd)); + RTLIL::Cell *cell = current_module->addCell(name, ID($memrd)); set_src_attr(cell, this); IdString mem_tw = current_module->design->twines.find(str); @@ -1996,12 +1998,13 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) // generate $meminit cells case AST_MEMINIT: { - std::stringstream sstr; - sstr << "$meminit$" << str << "$" << RTLIL::encode_filename(*location.begin.filename) << ":" << location.begin.line << "$" << (autoidx++); + IdString name = current_module->design->twines.add(TwineSpec::Suffix{ + intern_src_name(current_module->design, location, "$meminit", autoidx++), + stringf("$%s", str)}); SigSpec en_sig = children[2]->genRTLIL(); - RTLIL::Cell *cell = current_module->addCell(sstr.str(), ID($meminit_v2)); + RTLIL::Cell *cell = current_module->addCell(name, ID($meminit_v2)); set_src_attr(cell, this); int mem_width, mem_size, addr_bits; @@ -2041,12 +2044,13 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) if (type == AST_FAIR) { flavor = "fair"; desc = "assume property (eventually)"; } if (type == AST_COVER) { flavor = "cover"; desc = "cover property ()"; } - std::string cellname; + IdString cellname; if (str.empty()) - cellname = stringf("$%s$%s:%d$%d", flavor, RTLIL::encode_filename(*location.begin.filename), location.begin.line, autoidx++); + cellname = intern_src_name(current_module->design, location, + stringf("$%s", flavor), autoidx++); else - cellname = str; - check_unique_id(current_module, cellname, this, "procedural assertion"); + cellname = current_module->design->twines.add(std::string{str}); + check_unique_id(current_module, current_module->design->twines.str(cellname), this, "procedural assertion"); RTLIL::SigSpec check = children[0]->genRTLIL(); if (GetSize(check) != 1) diff --git a/frontends/rtlil/rtlil_frontend.cc b/frontends/rtlil/rtlil_frontend.cc index cfa5f75d4..9970b15ac 100644 --- a/frontends/rtlil/rtlil_frontend.cc +++ b/frontends/rtlil/rtlil_frontend.cc @@ -530,9 +530,9 @@ struct RTLILFrontendWorker { const TwineDesc &desc = twine_descs.at(id); IdString found; if (desc.kind == TwineDesc::Leaf) - found = design->twines.find(Twine{Twine::Leaf{desc.text}}); + found = design->twines.find(TwineSpec{TwineSpec::Leaf{desc.text}}); else - found = design->twines.find(Twine{Twine::Suffix{ + found = design->twines.find(TwineSpec{TwineSpec::Suffix{ IdString(desc.parent), desc.text}}); if (found == IdString::Null || found.untag().raw() != id) return false; @@ -563,10 +563,10 @@ struct RTLILFrontendWorker { IdString ref; switch (desc.kind) { case TwineDesc::Leaf: - ref = design->twines.add(Twine::Leaf{desc.text}); + ref = design->twines.add(TwineSpec::Leaf{desc.text}); break; case TwineDesc::Suffix: - ref = design->twines.add(Twine::Suffix{ + ref = design->twines.add(TwineSpec::Suffix{ materialize_file_twine(desc.parent), desc.text}); break; diff --git a/kernel/ff.h b/kernel/ff.h index 3efac2944..a885ead8d 100644 --- a/kernel/ff.h +++ b/kernel/ff.h @@ -182,7 +182,7 @@ struct FfData : FfTypeData { pol_set = false; } - FfData(Module *module, FfInitVals *initvals, Twine &&name) + FfData(Module *module, FfInitVals *initvals, TwineSpec &&name) : FfData(module, initvals, module->design->twines.add(std::move(name))) {} FfData(FfInitVals *initvals, Cell *cell_); diff --git a/kernel/hashcons.h b/kernel/hashcons.h index 3b5cf22be..d47410410 100644 --- a/kernel/hashcons.h +++ b/kernel/hashcons.h @@ -8,8 +8,8 @@ #include #include -/** - * Implements TwinePool shallow deduplicating backing storage +/** + * Implements shallow deduplicating backing storage */ YOSYS_NAMESPACE_BEGIN @@ -21,6 +21,7 @@ struct HashConsNodeHash { const Derived* pool = nullptr; size_t operator()(const Node& n) const noexcept { return Derived::hash_node(n); } + size_t operator()(const typename Node::Key& k) const noexcept { return Derived::hash_key(k); } size_t operator()(Ref ref) const noexcept { return Derived::hash_node((*pool)[ref]); } }; @@ -44,9 +45,11 @@ struct HashConsPool { struct NodeEq { using is_transparent = void; const Derived* pool = nullptr; - bool operator()(Ref a, Ref b) const noexcept { return (*pool)[a].data == (*pool)[b].data; } - bool operator()(Ref a, const Node& b) const noexcept { return (*pool)[a].data == b.data; } - bool operator()(const Node& a, Ref b) const noexcept { return a.data == (*pool)[b].data; } + bool operator()(Ref a, Ref b) const noexcept { return (*pool)[a] == (*pool)[b]; } + bool operator()(Ref a, const Node& b) const noexcept { return (*pool)[a] == b; } + bool operator()(const Node& a, Ref b) const noexcept { return a == (*pool)[b]; } + bool operator()(Ref a, const typename Node::Key& b) const noexcept { return (*pool)[a] == b; } + bool operator()(const typename Node::Key& a, Ref b) const noexcept { return (*pool)[b] == a; } }; using Index = std::unordered_set; @@ -184,6 +187,12 @@ public: return Ref(); } + Ref find_key(const typename Node::Key& k) const { + if (auto it = index.find(k); it != index.end()) + return *it; + return Ref(); + } + Ref add_inner(Node t) { Derived::canonicalize(t); diff --git a/kernel/hashlib.h b/kernel/hashlib.h index 3e48905fa..9b3600db1 100644 --- a/kernel/hashlib.h +++ b/kernel/hashlib.h @@ -174,7 +174,7 @@ struct hash_ops { return hash_ops::hash_into((u_type) a, h); } else if constexpr (std::is_pointer_v) { return hash_ops::hash_into((uintptr_t) a, h); - } else if constexpr (std::is_same_v) { + } else if constexpr (std::is_same_v || std::is_same_v) { int size = a.size(); int i = 0; while (i + 8 < size) { diff --git a/kernel/mem.h b/kernel/mem.h index 0d89adedd..0f6b191bd 100644 --- a/kernel/mem.h +++ b/kernel/mem.h @@ -223,7 +223,7 @@ struct Mem : RTLIL::AttrObject { void emulate_read_first(FfInitVals *initvals); Mem(Module *module, IdString memid, int width, int start_offset, int size) : module(module), memid(module->design, memid), packed(false), mem(nullptr), cell(nullptr), width(width), start_offset(start_offset), size(size) {} - Mem(Module *module, Twine &&memid, int width, int start_offset, int size) + Mem(Module *module, TwineSpec &&memid, int width, int start_offset, int size) : Mem(module, module->design->twines.add(std::move(memid)), width, start_offset, size) {} }; diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index f1106d0ef..3e0cfea97 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -2955,7 +2955,7 @@ IdString RTLIL::Module::uniquify(IdString name, int &index) } while (1) { - IdString new_name = (design->twines.add(Twine::Suffix{name, stringf("_%d", index)})).tag(name.isPublic()); + IdString new_name = (design->twines.add(TwineSpec::Suffix{name, stringf("_%d", index)})).tag(name.isPublic()); if (count_id(new_name) == 0) return new_name; index++; diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 2a7429469..629adc4e1 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -1270,18 +1270,22 @@ struct define_map_t; template inline constexpr bool is_unpooled_name_v = - std::is_same_v, Twine> || std::is_same_v, Twine::Leaf> || - std::is_same_v, Twine::Suffix> || std::is_same_v, Twine::AutoSuffix> || + std::is_same_v, TwineSpec> || std::is_same_v, TwineSpec::Leaf> || + std::is_same_v, TwineSpec::Suffix> || std::is_same_v, TwineSpec::AutoSuffix> || std::is_same_v, std::string> || std::is_same_v, const char*> || std::is_same_v, char*>; #define YS_UNPOOLED_NAME(N) std::enable_if_t, int> = 0 +// Forwarders from various string-representing types into a canonical IdString method + +// Forwards _func(name, arg1...) #define YS_NAME_FWD_POOL(_func, _pool) \ template \ decltype(auto) _func(N name, Rest&&... rest) \ { return _func(_pool.add(std::move(name)), std::forward(rest)...); } +// Forwards _func(arg0, name, arg2...) #define YS_NAME_FWD_2ND_POOL(_func, _pool) \ template \ decltype(auto) _func(T &&first, N name) \ @@ -1475,7 +1479,7 @@ struct RTLIL::Module : public RTLIL::NamedObject friend struct RTLIL::Cell; friend struct RTLIL::Design; - [[no_unique_address]] RTLIL::ModuleNameMasq name; + YS_NO_UNIQUE_ADDRESS RTLIL::ModuleNameMasq name; Hasher::hash_t hashidx_; [[nodiscard]] Hasher hash_into(Hasher h) const { h.eat(hashidx_); return h; } @@ -1897,7 +1901,7 @@ public: struct RTLIL::Wire : public RTLIL::NamedObject { - [[no_unique_address]] RTLIL::WireNameMasq name; + YS_NO_UNIQUE_ADDRESS RTLIL::WireNameMasq name; Hasher::hash_t hashidx_; [[nodiscard]] Hasher hash_into(Hasher h) const { h.eat(hashidx_); return h; } @@ -1965,7 +1969,7 @@ struct RTLIL::Memory : public RTLIL::NamedObject RTLIL::Design *design() const { return module ? module->design : nullptr; } - [[no_unique_address]] RTLIL::MemoryNameMasq name; + YS_NO_UNIQUE_ADDRESS RTLIL::MemoryNameMasq name; int width, start_offset, size; #ifdef YOSYS_ENABLE_PYTHON @@ -1989,7 +1993,7 @@ private: bool bufnorm_handle_setPort(IdString portname, RTLIL::SigSpec &signal, dict::iterator conn_it); public: - [[no_unique_address]] RTLIL::CellNameMasq name; + YS_NO_UNIQUE_ADDRESS RTLIL::CellNameMasq name; Hasher::hash_t hashidx_; [[nodiscard]] Hasher hash_into(Hasher h) const { h.eat(hashidx_); return h; } @@ -2010,7 +2014,7 @@ public: RTLIL::Design *design() const { return module ? module->design : nullptr; } IdString type_impl; - [[no_unique_address]] RTLIL::CellTypeMasq type; + YS_NO_UNIQUE_ADDRESS RTLIL::CellTypeMasq type; dict connections_; dict parameters; @@ -2130,7 +2134,7 @@ public: RTLIL::Design *design() const { return module ? module->design : nullptr; } - [[no_unique_address]] RTLIL::ProcessNameMasq name; + YS_NO_UNIQUE_ADDRESS RTLIL::ProcessNameMasq name; template void rewrite_sigspecs(T &functor); template void rewrite_sigspecs2(T &functor); @@ -2178,7 +2182,7 @@ inline Hasher RTLIL::SigBit::hash_top() const { Hasher h; if (wire) { IdString name = wire->name.ref(); - uint32_t n = (uint32_t)name.raw() ^ (uint32_t)(name.raw() >> 32); + uint32_t n = (uint32_t)name.bits(); h.force(hashlib::legacy::djb2_add(n, offset)); return h; } diff --git a/kernel/rtlil_twine_compat.h b/kernel/rtlil_twine_compat.h index 0f5deaf1b..77fa84b98 100644 --- a/kernel/rtlil_twine_compat.h +++ b/kernel/rtlil_twine_compat.h @@ -161,4 +161,23 @@ private: } +namespace hashlib { + template + struct masq_hash_ops { + static inline bool cmp(const T &a, const T &b) { return a == b; } + [[nodiscard]] static inline Hasher hash(const T &a) { + return hash_ops::hash(a.ref()); + } + [[nodiscard]] static inline Hasher hash_into(const T &a, Hasher h) { + return hash_ops::hash_into(a.ref(), h); + } + }; + + template + struct hash_ops> : masq_hash_ops> {}; + template<> struct hash_ops : masq_hash_ops {}; + template<> struct hash_ops : masq_hash_ops {}; + template<> struct hash_ops : masq_hash_ops {}; +} + #endif diff --git a/kernel/twine.cc b/kernel/twine.cc index 11216391b..7166e8b31 100644 --- a/kernel/twine.cc +++ b/kernel/twine.cc @@ -10,9 +10,8 @@ void StaticTwines::init() { return; log_assert(nodes_.empty()); nodes_.reserve(count); -#define X(_id) nodes_.push_back(Twine::Leaf{#_id}); -#include "kernel/constids.inc" -#undef X + for (const char *name : ID::static_names) + nodes_.emplace_back(std::string_view(name)); } const TwineNode &StaticTwines::node(size_t idx) { return nodes_[idx]; } @@ -21,7 +20,7 @@ bool StaticTwines::ready() { return nodes_.size() == count; } int64_t twine_gc_ns; int twine_gc_count; -Hasher IdString::hash_into(Hasher h) const { h.hash64(raw()); return h; } +Hasher IdString::hash_into(Hasher h) const { h.hash64(eq_key()); return h; } std::string IdString::handle_token() const { return stringf("%s@%zu", isPublic() ? "$pub" : "$priv", untag().raw()); @@ -53,27 +52,85 @@ std::string ID::unescaped_str(IdString ref) { return static_names[idx.raw()]; } -Twine::Twine(Leaf v) : data(std::move(v)) {} -Twine::Twine(Suffix v) : data(std::move(v)) {} -Twine::Twine(AutoSuffix v) : data(std::move(v)) {} +TwineSpec::TwineSpec(Leaf v) : data(std::move(v)) {} +TwineSpec::TwineSpec(Suffix v) : data(std::move(v)) {} +TwineSpec::TwineSpec(AutoSuffix v) : data(std::move(v)) {} -bool Twine::is_leaf() const { return std::holds_alternative(data); } -bool Twine::is_suffix() const { return std::holds_alternative(data); } +bool TwineSpec::holds_leaf() const { return std::holds_alternative(data); } +bool TwineSpec::holds_suffix() const { return std::holds_alternative(data); } -TwineNode::TwineNode(Twine::Leaf v) : data(std::move(v)) {} -TwineNode::TwineNode(Twine::Suffix v) : data(std::move(v)) {} +void SmallString::store(std::string_view content) +{ + log_assert(content.size() <= MAX_LEN); + release(); + len_ = content.size(); + if (len_ > INLINE_CAP) { + ptr_ = new char[len_]; + memcpy(ptr_, content.data(), len_); + } else { + memcpy(inl_, content.data(), len_); + } +} -bool TwineNode::is_dead() const { return std::holds_alternative(data); } -bool TwineNode::is_leaf() const { return std::holds_alternative(data); } -bool TwineNode::is_suffix() const { return std::holds_alternative(data); } -const std::string &TwineNode::leaf() const { return std::get(data).s; } -const Twine::Suffix &TwineNode::suffix() const { return std::get(data); } +void SmallString::release() +{ + if (len_ > INLINE_CAP) + delete[] ptr_; + len_ = 0; +} -std::string Twine::content_str() const { +SmallString::SmallString(std::string_view content) { store(content); } + +SmallString::SmallString(const SmallString &other) { store(other.view()); } + +SmallString::SmallString(SmallString &&other) noexcept : len_(other.len_) +{ + memcpy(inl_, other.inl_, INLINE_CAP); + other.len_ = 0; +} + +SmallString &SmallString::operator=(const SmallString &other) +{ + if (this != &other) + store(other.view()); + return *this; +} + +SmallString &SmallString::operator=(SmallString &&other) noexcept +{ + if (this == &other) + return *this; + release(); + memcpy(inl_, other.inl_, INLINE_CAP); + len_ = other.len_; + other.len_ = 0; + return *this; +} + +SmallString::~SmallString() { release(); } + +TwineNode::TwineNode(TwineNode &&other) noexcept + : text_(std::move(other.text_)), prefix_(other.prefix_) +{ + other.prefix_ = DEAD; +} + +TwineNode &TwineNode::operator=(TwineNode &&other) noexcept +{ + if (this == &other) + return *this; + text_ = std::move(other.text_); + prefix_ = other.prefix_; + other.prefix_ = DEAD; + return *this; +} + +std::string TwineSpec::content_str() const { if (auto *leaf = std::get_if(&data)) return leaf->s; + log_assert(!holds_suffix()); auto &autosfx = std::get(data); - return std::string(autosfx.prefix) + autosfx.tail; + return *autosfx.prefix + autosfx.tail; } std::pair twine_unescape(std::string s) { @@ -84,16 +141,26 @@ std::pair twine_unescape(std::string s) { } TwinePool::TwinePool() : serial_(next_serial()) {} -TwinePool::TwinePool(const TwinePool& other) : HashConsPool(other), serial_(next_serial()) {} -TwinePool::TwinePool(TwinePool&& other) : HashConsPool(std::move(other)), serial_(next_serial()) {} +TwinePool::TwinePool(const TwinePool& other) + : HashConsPool(other), auto_prefixes(other.auto_prefixes), serial_(next_serial()) {} +TwinePool::TwinePool(TwinePool&& other) + : HashConsPool(std::move(other)), auto_prefixes(std::move(other.auto_prefixes)), serial_(next_serial()) {} TwinePool& TwinePool::operator=(const TwinePool& other) { + if (this == &other) + return *this; HashConsPool::operator=(other); + auto_prefixes = other.auto_prefixes; + serial_ = next_serial(); return *this; } TwinePool& TwinePool::operator=(TwinePool&& other) { + if (this == &other) + return *this; HashConsPool::operator=(std::move(other)); + auto_prefixes = std::move(other.auto_prefixes); + serial_ = next_serial(); return *this; } @@ -110,9 +177,8 @@ IdString TwinePool::stamp(IdString ref) const { } void TwinePool::check_owned(IdString ref) const { -#ifndef NDEBUG - log_assert(owns(ref)); -#endif + if constexpr (IdString::MAX_SERIAL != 0) + log_assert(owns(ref)); } const TwineNode& TwinePool::operator[](IdString ref) const { @@ -123,15 +189,15 @@ const TwineNode& TwinePool::operator[](IdString ref) const { const TwineNode& TwinePool::static_node(size_t idx) { return StaticTwines::node(idx); } void TwinePool::check_ready() { log_assert(StaticTwines::ready()); } -void TwinePool::canonicalize(TwineNode& t) { - if (auto *sfx = std::get_if(&t.data)) - sfx->prefix = sfx->prefix.untag().stamped(0); -} +void TwinePool::canonicalize(TwineNode&) {} size_t TwinePool::next_serial() { static size_t counter = 0; size_t serial = ++counter; - return serial > IdString::MAX_SERIAL ? (serial % IdString::MAX_SERIAL) + 1 : serial; + if constexpr (IdString::MAX_SERIAL == 0) + return serial; + else + return serial > IdString::MAX_SERIAL ? (serial % IdString::MAX_SERIAL) + 1 : serial; } IdString TwinePool::add_inner(TwineNode t) { @@ -142,35 +208,32 @@ IdString TwinePool::add_inner(TwineNode t) { } size_t TwinePool::hash_node(const TwineNode& t) { + return hash_key(t.key()); +} + +size_t TwinePool::hash_key(const TwineNode::Key& k) { Hasher h; - - std::visit([&h](const auto& val) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - h.eat(val.s); - } else if constexpr (std::is_same_v) { - h.eat(val.prefix); - h.eat(val.tail); - } - }, t.data); - + if (k.prefix < TwineNode::DEAD) + h.eat(IdString(k.prefix)); + h.eat(k.text); return h.yield(); } void TwinePool::dump(IdString ref, std::ostream& os) const { const TwineNode& twine = (*this)[ref]; - std::visit([&](const auto& val) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - os << "Dead()"; - } else if constexpr (std::is_same_v) { - os << "Leaf(\"" << val.s << "\")"; - } else if constexpr (std::is_same_v) { - os << "Suffix(prefix: "; - dump(val.prefix, os); - os << ", tail: \"" << val.tail << "\")"; - } - }, twine.data); + switch (twine.kind()) { + case TwineNode::Kind::Dead: + os << "Dead()"; + break; + case TwineNode::Kind::Leaf: + os << "Leaf(\"" << twine.text() << "\")"; + break; + case TwineNode::Kind::Suffix: + os << "Suffix(prefix: "; + dump(twine.prefix(), os); + os << ", tail: \"" << twine.text() << "\")"; + break; + } if (ref.isPublic()) os << " pub"; } @@ -180,16 +243,17 @@ void TwinePool::print(IdString ref, std::ostream& os) const { return; if (ref.isPublic()) os << '\\'; - std::visit([&](const auto& val) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - } else if constexpr (std::is_same_v) { - os << val.s; - } else if constexpr (std::is_same_v) { - print(val.prefix, os); - os << val.tail; - } - }, (*this)[ref].data); + const TwineNode& twine = (*this)[ref]; + switch (twine.kind()) { + case TwineNode::Kind::Dead: + break; + case TwineNode::Kind::Suffix: + print(twine.prefix(), os); + [[fallthrough]]; + case TwineNode::Kind::Leaf: + os << twine.text(); + break; + } } void TwinePool::append_str(IdString ref, std::string& out) const { @@ -197,16 +261,17 @@ void TwinePool::append_str(IdString ref, std::string& out) const { return; if (ref.isPublic()) out += '\\'; - std::visit([&](const auto& val) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - } else if constexpr (std::is_same_v) { - out += val.s; - } else if constexpr (std::is_same_v) { - append_str(val.prefix, out); - out += val.tail; - } - }, (*this)[ref].data); + const TwineNode& twine = (*this)[ref]; + switch (twine.kind()) { + case TwineNode::Kind::Dead: + break; + case TwineNode::Kind::Suffix: + append_str(twine.prefix(), out); + [[fallthrough]]; + case TwineNode::Kind::Leaf: + out += twine.text(); + break; + } } std::string TwinePool::str(IdString ref) const { @@ -219,36 +284,67 @@ std::string TwinePool::unescaped_str(IdString ref) const { return str(ref.untag()); } +IdString TwinePool::find_content(uint32_t prefix, std::string_view text) const { + return HashConsPool::find_key(TwineNode::Key{prefix, text}); +} + +IdString TwinePool::intern(uint32_t prefix, std::string_view text) { + IdString ref = HashConsPool::find_key(TwineNode::Key{prefix, text}); + if (ref != IdString::Null) + return ref; + return add_inner(TwineNode{prefix, text}); +} + IdString TwinePool::find(const std::string &name) const { bool is_public = !name.empty() && name[0] == '\\'; - return stamp(find(Twine::Leaf{is_public ? name.substr(1) : name}).tag(is_public)); + std::string_view content = name; + if (is_public) + content.remove_prefix(1); + return stamp(find_content(TwineNode::NO_PREFIX, content).tag(is_public)); } -IdString TwinePool::find(Twine t) const { - if (auto *ap = std::get_if(&t.data)) { - IdString prefix = HashConsPool::find(Twine::Leaf{std::string(ap->prefix)}); +IdString TwinePool::find(TwineSpec t) const { + if (auto *ap = std::get_if(&t.data)) { + IdString prefix = find_content(TwineNode::NO_PREFIX, *ap->prefix); if (prefix == IdString::Null) return IdString::Null; - t = Twine::Suffix{prefix, std::move(ap->tail)}; + return stamp(find_content((uint32_t)prefix.untag().raw(), ap->tail).tag(prefix.isPublic())); } - bool is_public = inherits_publicity(t); - return stamp(HashConsPool::find(to_node(std::move(t))).tag(is_public)); + if (auto *leaf = std::get_if(&t.data)) + return stamp(find_content(TwineNode::NO_PREFIX, leaf->s)); + const TwineSpec::Suffix &sfx = std::get(t.data); + return stamp(find_content((uint32_t)sfx.prefix.untag().raw(), sfx.tail).tag(sfx.prefix.isPublic())); } -IdString TwinePool::add(Twine t) { - if (auto *ap = std::get_if(&t.data)) { - IdString prefix = add_inner(Twine::Leaf{std::string(ap->prefix)}); - t = Twine::Suffix{prefix, std::move(ap->tail)}; +IdString TwinePool::add(TwineSpec t) { + if (auto *ap = std::get_if(&t.data)) { + IdString prefix = auto_prefix(ap->prefix); + return stamp(intern((uint32_t)prefix.untag().raw(), ap->tail).tag(prefix.isPublic())); } - bool is_public = inherits_publicity(t); - return stamp(add_inner(to_node(std::move(t))).tag(is_public)); + if (auto *leaf = std::get_if(&t.data)) + return stamp(intern(TwineNode::NO_PREFIX, leaf->s)); + const TwineSpec::Suffix &sfx = std::get(t.data); + return stamp(intern((uint32_t)sfx.prefix.untag().raw(), sfx.tail).tag(sfx.prefix.isPublic())); +} + +IdString TwinePool::auto_prefix(const std::string *prefix) { + auto it = auto_prefixes.find(prefix); + if (it != auto_prefixes.end()) + return it->second; + IdString ref = intern(TwineNode::NO_PREFIX, *prefix); + auto_prefixes[prefix] = ref; + return ref; +} + +IdString TwinePool::add(IdString prefix, std::string_view tail) { + return stamp(intern((uint32_t)prefix.untag().raw(), tail).tag(prefix.isPublic())); } IdString TwinePool::add(std::string s) { if (s.empty()) return IdString::Null; auto [content, is_public] = twine_unescape(std::move(s)); - return stamp(add_inner(Twine::Leaf{std::move(content)}).tag(is_public)); + return stamp(intern(TwineNode::NO_PREFIX, content).tag(is_public)); } IdString TwinePool::copy_from(const TwinePool& src, IdString ref) { @@ -260,10 +356,16 @@ IdString TwinePool::copy_from(const TwinePool& src, IdString ref) { if (ID::is_static(untagged)) return ref; const TwineNode& t = src[untagged]; - if (t.is_leaf()) - return (add(Twine::Leaf{t.leaf()})).tag(is_public); - if (t.is_suffix()) - return (add(Twine::Suffix{copy_from(src, t.suffix().prefix), t.suffix().tail})).tag(is_public); + switch (t.kind()) { + case TwineNode::Kind::Leaf: + return stamp(intern(TwineNode::NO_PREFIX, t.text()).tag(is_public)); + case TwineNode::Kind::Suffix: { + IdString prefix = copy_from(src, t.prefix()); + return stamp(intern((uint32_t)prefix.untag().raw(), t.text()).tag(is_public)); + } + case TwineNode::Kind::Dead: + break; + } return IdString::Null; } @@ -276,32 +378,47 @@ IdString TwinePool::find_from(const TwinePool& src, IdString ref) const { if (ID::is_static(untagged)) return ref; const TwineNode& t = src[untagged]; - if (t.is_leaf()) - return find(Twine::Leaf{t.leaf()}).tag(is_public); - if (t.is_suffix()) { - IdString prefix = find_from(src, t.suffix().prefix); + switch (t.kind()) { + case TwineNode::Kind::Leaf: + return stamp(find_content(TwineNode::NO_PREFIX, t.text()).tag(is_public)); + case TwineNode::Kind::Suffix: { + IdString prefix = find_from(src, t.prefix()); if (prefix == IdString::Null) return IdString::Null; - return find(Twine::Suffix{prefix, t.suffix().tail}).tag(is_public); + return stamp(find_content((uint32_t)prefix.untag().raw(), t.text()).tag(is_public)); + } + case TwineNode::Kind::Dead: + break; } return IdString::Null; } std::string TwinePool::ref_token(IdString ref) const { - return "#" + std::to_string((uint64_t)stamp(ref).bits()); + return "#" + std::to_string((uint64_t)serial_) + ":" + + std::to_string((uint64_t)stamp(ref).bits()); } IdString TwinePool::ref_from_token(std::string_view token) const { if (token.size() < 2 || token[0] != '#') return IdString::Null; - size_t value = 0; - for (char c : token.substr(1)) { - if (c < '0' || c > '9') + size_t sep = token.find(':'); + if (sep == std::string_view::npos) + return IdString::Null; + size_t fields[2] = {0, 0}; + std::string_view parts[2] = {token.substr(1, sep - 1), token.substr(sep + 1)}; + for (int i = 0; i < 2; i++) { + if (parts[i].empty()) return IdString::Null; - value = value * 10 + (c - '0'); + for (char c : parts[i]) { + if (c < '0' || c > '9') + return IdString::Null; + fields[i] = fields[i] * 10 + (c - '0'); + } } - IdString ref(value); - if (ref == IdString::Null || (!ID::is_static(ref) && ref.serial() != serial_)) + if (fields[0] != serial_) + return IdString::Null; + IdString ref(fields[1]); + if (ref == IdString::Null) return IdString::Null; return is_live(ref) ? ref : IdString::Null; } @@ -317,18 +434,7 @@ void TwinePool::dump(std::ostream& os) const { os << "--------------------------------\n"; } -bool TwinePool::inherits_publicity(const Twine &t) { - auto *sfx = std::get_if(&t.data); - return sfx != nullptr && sfx->prefix.isPublic(); -} - -TwineNode TwinePool::to_node(Twine t) { - if (auto *leaf = std::get_if(&t.data)) - return TwineNode{std::move(*leaf)}; - return TwineNode{std::move(std::get(t.data))}; -} - -/** +/** * TwineSegments holds a sequence of string_views refering to the strings * an IdString is composed of. It's used for lightweight comparisons. * The sequences is implemented in a "small vector" style, @@ -358,13 +464,17 @@ TwineSegments::TwineSegments(const TwinePool &pool, IdString ref) return; for (IdString cur = ref.untag(); ;) { const TwineNode &t = pool[cur]; - if (t.is_suffix()) { - push(t.suffix().tail); - cur = t.suffix().prefix.untag(); + switch (t.kind()) { + case TwineNode::Kind::Suffix: + push(t.text()); + cur = t.prefix(); continue; + case TwineNode::Kind::Leaf: + push(t.text()); + break; + case TwineNode::Kind::Dead: + break; } - if (t.is_leaf()) - push(t.leaf()); break; } if (ref.isPublic()) @@ -458,6 +568,13 @@ bool TwinePool::content_equal(IdString a, IdString b) const return compare_segments(sa, sb) == 0; } +IdString TwinePool::prefix_of(IdString ref) const { + const TwineNode &t = (*this)[ref]; + if (!t.is_suffix()) + return IdString::Null; + return stamp(t.prefix().tag(ref.isPublic())); +} + int TwinePool::compare_by_name(IdString a, IdString b) const { if (a == b) @@ -471,9 +588,9 @@ int TwinePool::compare_by_name(IdString a, IdString b) const const TwineNode &ta = (*this)[a.untag()]; const TwineNode &tb = (*this)[b.untag()]; if (ta.is_leaf() && tb.is_leaf()) - return ta.leaf().compare(tb.leaf()); - if (ta.is_suffix() && tb.is_suffix() && ta.suffix().prefix == tb.suffix().prefix) - return ta.suffix().tail.compare(tb.suffix().tail); + return ta.text().compare(tb.text()); + if (ta.is_suffix() && tb.is_suffix() && ta.prefix() == tb.prefix()) + return ta.text().compare(tb.text()); } TwineSegments sa(*this, a), sb(*this, b); @@ -500,13 +617,15 @@ void DeepTwineHash::combine(Stream& s, IdString t) const { if (t == IdString::Null) return; const TwineNode& n = (*pool)[t]; - if (n.is_dead()) return; - - if (n.is_leaf()) { - s.push(n.leaf()); - } else if (n.is_suffix()) { - combine(s, n.suffix().prefix); - s.push(n.suffix().tail); + switch (n.kind()) { + case TwineNode::Kind::Dead: + break; + case TwineNode::Kind::Suffix: + combine(s, n.prefix()); + [[fallthrough]]; + case TwineNode::Kind::Leaf: + s.push(n.text()); + break; } } @@ -526,16 +645,15 @@ bool DeepTwineEq::consume(IdString t, std::string_view& sv) const noexcept { if (t == IdString::Null) return true; const TwineNode& n = (*pool)[t]; - if (n.is_dead()) return true; - - if (n.is_leaf()) { - if (!sv.starts_with(n.leaf())) return false; - sv.remove_prefix(n.leaf().size()); + switch (n.kind()) { + case TwineNode::Kind::Dead: return true; - } else if (n.is_suffix()) { - if (!consume(n.suffix().prefix, sv)) return false; - if (!sv.starts_with(n.suffix().tail)) return false; - sv.remove_prefix(n.suffix().tail.size()); + case TwineNode::Kind::Suffix: + if (!consume(n.prefix(), sv)) return false; + [[fallthrough]]; + case TwineNode::Kind::Leaf: + if (!sv.starts_with(n.text())) return false; + sv.remove_prefix(n.text().size()); return true; } return false; diff --git a/kernel/twine.h b/kernel/twine.h index 932b21c91..056b08f32 100644 --- a/kernel/twine.h +++ b/kernel/twine.h @@ -5,8 +5,11 @@ #include "kernel/yosys_common.h" #include +#include +#include #include #include +#include #include #include #include @@ -16,27 +19,43 @@ YOSYS_NAMESPACE_BEGIN -struct Twine; +struct TwineSpec; struct TwinePool; struct IdString; -// An IdString now references a Twine in a per-Design TwinePool. -// Twines are designed to deduplicate prefixes if appropriately constructed. -// Statically, compile-time allocated indices defined in kernel/constids.inc -// are handled with a global read-only pool. -// A general IdString can't be constructed, converted to a string, or printed -// without a pointer to the pool. -// Two IdStrings that resolve to the same string won't generally compare -// equal with operator== but alternate mechanisms are provided for cases -// where that is absolutely necessary but it should be avoided for performance, -// see 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. -// Instead of backslash escaping, identifier "publicity" is implemented -// with a publicity bit on the IdString reference integer. -// 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. +/** + * 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; @@ -45,13 +64,20 @@ struct NullIdString { struct IdString { private: - size_t value; +#ifdef YOSYS_ENABLE_TWINE_PROVENANCE + using bits_t = uint64_t; +#else + using bits_t = uint32_t; +#endif + bits_t value; - static constexpr size_t TWINE_PUBLIC_BIT = 1ULL << 63; - static constexpr size_t TWINE_NULL_VAL = ~size_t{0}; - static constexpr int TWINE_SERIAL_SHIFT = 32; - static constexpr size_t TWINE_INDEX_MASK = (size_t{1} << TWINE_SERIAL_SHIFT) - 1; - static constexpr size_t TWINE_SERIAL_MASK = ~(TWINE_INDEX_MASK | TWINE_PUBLIC_BIT); + static constexpr int TWINE_WIDTH = std::numeric_limits::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{}; @@ -66,25 +92,27 @@ public: // 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) | (pool_serial << TWINE_SERIAL_SHIFT)); + : 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(val) {} + explicit constexpr IdString(size_t val) : value((bits_t) val) {} - constexpr bool operator==(const IdString &rhs) const { return raw() == rhs.raw(); } - constexpr bool operator!=(const IdString &rhs) const { return raw() != rhs.raw(); } + 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 { - size_t a = raw(), b = rhs.raw(); - if (auto cmp = (a & ~TWINE_PUBLIC_BIT) <=> (b & ~TWINE_PUBLIC_BIT); cmp != 0) - return cmp; - return (a & TWINE_PUBLIC_BIT) <=> (b & TWINE_PUBLIC_BIT); + return order_key() <=> rhs.order_key(); } template @@ -121,6 +149,22 @@ public: constexpr NullIdString::operator IdString() const { return IdString(); } constexpr bool NullIdString::operator==(IdString ref) const { return ref.empty(); } +namespace hashlib { + template<> + struct hash_ops { + 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, @@ -173,9 +217,9 @@ template inline constexpr IdString constid = IdString(Raw); #define ID(id) (YOSYS_NAMESPACE_PREFIX constid) -// Twine is the object that lives in the TwinePool, -// while IdString points to a Twine and carries publicity information -struct Twine { +// 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; @@ -186,7 +230,7 @@ struct Twine { // transient suffix constructed with NEW_ID and NEW_ID_SUFFIX // turned into a regular Suffix when added to a TwinePool struct AutoSuffix { - std::string_view prefix; + const std::string *prefix; std::string tail; auto operator<=>(const AutoSuffix&) const = default; }; @@ -199,30 +243,88 @@ struct Twine { std::variant data; - Twine(Leaf v); - Twine(Suffix v); - Twine(AutoSuffix v); + TwineSpec(Leaf v); + TwineSpec(Suffix v); + TwineSpec(AutoSuffix v); - bool is_leaf() const; - bool is_suffix() const; + 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::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 { - std::variant data; + 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; - TwineNode(Twine::Leaf v); - TwineNode(Twine::Suffix v); + 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; - bool is_dead() const; - bool is_leaf() const; - bool is_suffix() const; - const std::string &leaf() const; - const Twine::Suffix &suffix() const; + 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; @@ -260,11 +362,19 @@ struct TwinePool : HashConsPool { 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 static void for_each_child(const TwineNode& t, F&& f) { if (t.is_suffix()) - f(t.suffix().prefix); + f(t.prefix()); + } + + template + 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; @@ -282,12 +392,15 @@ struct TwinePool : HashConsPool { 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(Twine t) const; + IdString find(TwineSpec t) const; // Doesn't infer publicity - IdString add(Twine t); + 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); IdString copy_from(const TwinePool& src, IdString ref); @@ -303,11 +416,11 @@ struct TwinePool : HashConsPool { 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 auto_prefixes; size_t serial_; - - static bool inherits_publicity(const Twine &t); - static TwineNode to_node(Twine t); }; /** diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index 1ae5eae54..b9df3b78e 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -123,6 +123,12 @@ # define YS_MAYBE_UNUSED #endif +#if defined(_MSC_VER) +# define YS_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#else +# define YS_NO_UNIQUE_ADDRESS [[no_unique_address]] +#endif + #if __cplusplus >= 202002L # define YS_FALLTHROUGH [[fallthrough]]; #else @@ -309,14 +315,14 @@ extern bool yosys_write_versions; const std::string *create_id_prefix(std::string_view file, int line, std::string_view func); #define NEW_ID \ - YOSYS_NAMESPACE_PREFIX Twine{YOSYS_NAMESPACE_PREFIX Twine::AutoSuffix{[](std::string_view func) -> std::string_view { \ + YOSYS_NAMESPACE_PREFIX TwineSpec{YOSYS_NAMESPACE_PREFIX TwineSpec::AutoSuffix{[](std::string_view func) -> const std::string * { \ static std::unique_ptr prefix(YOSYS_NAMESPACE_PREFIX create_id_prefix(__FILE__, __LINE__, func)); \ - return *prefix; \ + return prefix.get(); \ }(__FUNCTION__), std::to_string(YOSYS_NAMESPACE_PREFIX autoidx++)}} #define NEW_ID_SUFFIX(suffix) \ - YOSYS_NAMESPACE_PREFIX Twine{YOSYS_NAMESPACE_PREFIX Twine::AutoSuffix{[](std::string_view func) -> std::string_view { \ + YOSYS_NAMESPACE_PREFIX TwineSpec{YOSYS_NAMESPACE_PREFIX TwineSpec::AutoSuffix{[](std::string_view func) -> const std::string * { \ static std::unique_ptr prefix(YOSYS_NAMESPACE_PREFIX create_id_prefix(__FILE__, __LINE__, func)); \ - return *prefix; \ + return prefix.get(); \ }(__FUNCTION__), std::string(suffix) + "$" + std::to_string(YOSYS_NAMESPACE_PREFIX autoidx++)}} YOSYS_NAMESPACE_END diff --git a/kernel/yosys_config.h.in b/kernel/yosys_config.h.in index e68194271..0c4966652 100644 --- a/kernel/yosys_config.h.in +++ b/kernel/yosys_config.h.in @@ -18,5 +18,6 @@ #cmakedefine YOSYS_ENABLE_TCL #cmakedefine YOSYS_ENABLE_PYTHON #cmakedefine YOSYS_ENABLE_VERIFIC +#cmakedefine YOSYS_ENABLE_TWINE_PROVENANCE #endif diff --git a/passes/cmds/design.cc b/passes/cmds/design.cc index de90cbd4e..86102a979 100644 --- a/passes/cmds/design.cc +++ b/passes/cmds/design.cc @@ -297,7 +297,7 @@ struct DesignPass : public Pass { if (done.count(cell->type) == 0) { IdString trg_ref = copy_to_design->twines.add( - Twine::Suffix{as_name_ref, "." + cell->type.unescape()}); + TwineSpec::Suffix{as_name_ref, "." + cell->type.unescape()}); log("Importing %s as %s.\n", fmod, PooledName(copy_to_design, trg_ref).unescape()); diff --git a/passes/cmds/dft_tag.cc b/passes/cmds/dft_tag.cc index d7bbae349..d4ea52469 100644 --- a/passes/cmds/dft_tag.cc +++ b/passes/cmds/dft_tag.cc @@ -834,7 +834,7 @@ struct DftTagWorker { } - SigSpec autoAnd(Twine &&name, const SigSpec &sig_a, const SigSpec &sig_b) + SigSpec autoAnd(TwineSpec &&name, const SigSpec &sig_a, const SigSpec &sig_b) { log_assert(GetSize(sig_a) == GetSize(sig_b)); if (sig_a.is_fully_zero() || sig_b.is_fully_ones() || sig_a == sig_b) @@ -845,7 +845,7 @@ struct DftTagWorker { return module->And(std::move(name), sig_a, sig_b); } - SigSpec autoOr(Twine &&name, const SigSpec &sig_a, const SigSpec &sig_b) + SigSpec autoOr(TwineSpec &&name, const SigSpec &sig_a, const SigSpec &sig_b) { log_assert(GetSize(sig_a) == GetSize(sig_b)); if (sig_a.is_fully_ones() || sig_b.is_fully_zero() || sig_a == sig_b) @@ -856,7 +856,7 @@ struct DftTagWorker { return module->Or(std::move(name), sig_a, sig_b); } - SigSpec autoXor(Twine &&name, const SigSpec &sig_a, const SigSpec &sig_b) + SigSpec autoXor(TwineSpec &&name, const SigSpec &sig_a, const SigSpec &sig_b) { log_assert(GetSize(sig_a) == GetSize(sig_b)); if (sig_a == sig_b) @@ -872,7 +872,7 @@ struct DftTagWorker { return module->Xor(std::move(name), sig_a, sig_b); } - SigSpec autoXnor(Twine &&name, const SigSpec &sig_a, const SigSpec &sig_b) + SigSpec autoXnor(TwineSpec &&name, const SigSpec &sig_a, const SigSpec &sig_b) { log_assert(GetSize(sig_a) == GetSize(sig_b)); if (sig_a == sig_b) @@ -888,7 +888,7 @@ struct DftTagWorker { return module->Xnor(std::move(name), sig_a, sig_b); } - SigSpec autoNot(Twine &&name, const SigSpec &sig_a) + SigSpec autoNot(TwineSpec &&name, const SigSpec &sig_a) { if (sig_a.is_fully_const()) { auto const_val = sig_a.as_const(); @@ -901,7 +901,7 @@ struct DftTagWorker { return module->Not(std::move(name), sig_a); } - SigSpec autoEq(Twine &&name, const SigSpec &sig_a, const SigSpec &sig_b) + SigSpec autoEq(TwineSpec &&name, const SigSpec &sig_a, const SigSpec &sig_b) { log_assert(GetSize(sig_a) == GetSize(sig_b)); if (sig_a == sig_b) @@ -919,7 +919,7 @@ struct DftTagWorker { return module->Eq(std::move(name), sig_a, sig_b); } - SigSpec autoGe(Twine &&name, const SigSpec &sig_a, const SigSpec &sig_b) + SigSpec autoGe(TwineSpec &&name, const SigSpec &sig_a, const SigSpec &sig_b) { log_assert(GetSize(sig_a) == GetSize(sig_b)); if (sig_a == sig_b || sig_a.is_fully_ones()) @@ -930,7 +930,7 @@ struct DftTagWorker { return module->Ge(std::move(name), sig_a, sig_b); } - SigSpec autoReduceAnd(Twine &&name, const SigSpec &sig_a) + SigSpec autoReduceAnd(TwineSpec &&name, const SigSpec &sig_a) { if (GetSize(sig_a) == 0) return State::S1; @@ -945,7 +945,7 @@ struct DftTagWorker { return module->ReduceAnd(std::move(name), sig_a); } - SigSpec autoReduceOr(Twine &&name, const SigSpec &sig_a) + SigSpec autoReduceOr(TwineSpec &&name, const SigSpec &sig_a) { if (GetSize(sig_a) == 0) return State::S0; diff --git a/passes/hierarchy/flatten.cc b/passes/hierarchy/flatten.cc index 324e014c5..5854b1f46 100644 --- a/passes/hierarchy/flatten.cc +++ b/passes/hierarchy/flatten.cc @@ -72,21 +72,21 @@ IdString remap_flattened_name(RTLIL::Design *design, IdString obj_ref, return it->second; const TwineNode &node = design->twines[obj_ref]; + IdString parent = design->twines.prefix_of(obj_ref); IdString result; - if (node.is_suffix()) { - const Twine::Suffix &sfx = node.suffix(); - IdString prefix = remap_flattened_name(design, sfx.prefix.tag(obj_ref.isPublic()), + if (parent != IdString::Null) { + IdString prefix = remap_flattened_name(design, parent, pub_prefix_ref, priv_prefix_ref, separator, memo); - result = design->twines.add(Twine::Suffix{prefix, sfx.tail}); + result = design->twines.add(TwineSpec::Suffix{prefix, std::string(node.text())}); } else { - std::string_view obj = node.leaf(); + std::string_view obj = node.text(); if (obj_ref.isPublic()) { - result = design->twines.add(Twine::Suffix{pub_prefix_ref, separator + std::string(obj)}); + result = design->twines.add(TwineSpec::Suffix{pub_prefix_ref, separator + std::string(obj)}); } else { constexpr std::string_view flatten_prefix = "$flatten"; if (obj.substr(0, flatten_prefix.size()) == flatten_prefix) obj.remove_prefix(flatten_prefix.size()); - result = design->twines.add(Twine::Suffix{priv_prefix_ref, std::string(obj)}); + result = design->twines.add(TwineSpec::Suffix{priv_prefix_ref, std::string(obj)}); } } memo[obj_ref] = result; diff --git a/passes/techmap/abc_ops_reintegrate.cc b/passes/techmap/abc_ops_reintegrate.cc index b6223e83d..0864a3f07 100644 --- a/passes/techmap/abc_ops_reintegrate.cc +++ b/passes/techmap/abc_ops_reintegrate.cc @@ -49,7 +49,7 @@ inline IdString ref_from_token(RTLIL::Design *design, const std::string &tok) inline IdString indexed_name(RTLIL::Design *design, IdString base, int index) { - return design->twines.add(Twine::Suffix{base.untag(), stringf("[%d]", index)}) + return design->twines.add(TwineSpec::Suffix{base.untag(), stringf("[%d]", index)}) .tag(base.isPublic()); } diff --git a/passes/techmap/booth.cc b/passes/techmap/booth.cc index 301ab7460..fd6f0c70d 100644 --- a/passes/techmap/booth.cc +++ b/passes/techmap/booth.cc @@ -201,7 +201,7 @@ struct BoothPassWorker { log_assert(sig_a.size() == sig_y.size()); for (int i = 0; i < sig_a.size(); i++) - mod->addFa(Twine::Suffix{name, stringf("[%d]", i)}, sig_a[i], sig_b[i], + mod->addFa(TwineSpec::Suffix{name, stringf("[%d]", i)}, sig_a[i], sig_b[i], sig_c[i], sig_x[i], sig_y[i]); } diff --git a/passes/techmap/techmap.cc b/passes/techmap/techmap.cc index de4b2257b..581e8f103 100644 --- a/passes/techmap/techmap.cc +++ b/passes/techmap/techmap.cc @@ -59,7 +59,7 @@ struct PrefixApplier PrefixApplier(RTLIL::Design *dst, IdString prefix, RTLIL::Design *src) : dst(dst), src(src), cell_name(prefix) { - pub_prefix = dst->twines.add(Twine::Suffix{prefix, "."}); + pub_prefix = dst->twines.add(TwineSpec::Suffix{prefix, "."}); } IdString techmap_prefix() @@ -75,14 +75,14 @@ struct PrefixApplier return it->second; const TwineNode &node = src->twines[obj_ref]; + IdString parent = src->twines.prefix_of(obj_ref); IdString result; - if (node.is_suffix()) { - const Twine::Suffix &sfx = node.suffix(); - IdString prefix = name(sfx.prefix.tag(obj_ref.isPublic())); - result = dst->twines.add(Twine::Suffix{prefix, sfx.tail}); + if (parent != IdString::Null) { + IdString prefix = name(parent); + result = dst->twines.add(prefix, node.text()); } else { IdString prefix = obj_ref.isPublic() ? pub_prefix : techmap_prefix(); - result = dst->twines.add(Twine::Suffix{prefix, node.leaf()}); + result = dst->twines.add(prefix, node.text()); } memo[obj_ref] = result; return result; @@ -103,7 +103,7 @@ struct PrefixApplier static RTLIL::Wire *map_port(RTLIL::Module *tpl, RTLIL::Design *src, IdString name) { - return tpl->wire(tpl->design->twines.find(src->twines.str(name))); + return tpl->wire(tpl->design->twines.find_from(src->twines, name)); } struct TechmapWorker @@ -165,7 +165,7 @@ struct TechmapWorker return result; for (auto w : module->wires()) { - if (w->name.str()[0] == '$') + if (!w->name.isPublic()) continue; if (w->name.contains("_TECHMAP_") && !w->name.contains("_TECHMAP_REPLACE_")) { @@ -239,7 +239,7 @@ struct TechmapWorker IdString posportref = module->design->twines.add(std::string{stringf("$%d", tpl_w->port_id)}); positional_ports.emplace(posportref, tpl_w->name); - IdString tpl_portname = module->design->twines.find(tpl_w->name.str()); + IdString tpl_portname = module->design->twines.find_from(tpl->design->twines, tpl_w->name); if (tpl_w->get_bool_attribute(ID::techmap_autopurge) && (!cell->hasPort(tpl_portname) || !GetSize(cell->getPort(tpl_portname))) && (!cell->hasPort(posportref) || !GetSize(cell->getPort(posportref)))) @@ -300,7 +300,7 @@ struct TechmapWorker else w = map_port(tpl, design, portname); if (w == nullptr || w->port_id == 0) { - if (design->twines.str(portname).starts_with("$")) + if (!portname.isPublic()) log_error("Can't map port `%s' of cell `%s' to template `%s'!\n", PooledName(design, portname).unescape(), cell->name.unescape(), tpl->name.unescape()); continue; } diff --git a/tests/unit/kernel/CMakeLists.txt b/tests/unit/kernel/CMakeLists.txt index 88f0f5d67..d2461ef4f 100644 --- a/tests/unit/kernel/CMakeLists.txt +++ b/tests/unit/kernel/CMakeLists.txt @@ -11,6 +11,7 @@ yosys_gtest(kernel rtlilHelpers.h rtlilStringTest.cc rtlilTest.cc + sigbitPerfTest.cc sigspecExtractTest.cc sigspecRemove2Test.cc threadingTest.cc diff --git a/tests/unit/kernel/nameMasqTest.cc b/tests/unit/kernel/nameMasqTest.cc index f53e1e1bf..bd0a9d929 100644 --- a/tests/unit/kernel/nameMasqTest.cc +++ b/tests/unit/kernel/nameMasqTest.cc @@ -46,7 +46,7 @@ TEST(NameMasqTest, SuffixQueries) Design design; Module *mod = design.addModule(std::string("\\zz_top")); IdString prefix = design.twines.add(std::string("\\zz_bus")); - IdString suffixed = design.twines.add(Twine{Twine::Suffix{prefix, "_hi"}}); + IdString suffixed = design.twines.add(TwineSpec{TwineSpec::Suffix{prefix, "_hi"}}); Wire *w = mod->addWire(suffixed, 1); EXPECT_EQ(w->name.escaped(), "\\zz_bus_hi"); diff --git a/tests/unit/kernel/sigbitPerfTest.cc b/tests/unit/kernel/sigbitPerfTest.cc new file mode 100644 index 000000000..b5cc8a929 --- /dev/null +++ b/tests/unit/kernel/sigbitPerfTest.cc @@ -0,0 +1,158 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include "kernel/rtlil.h" +#include "kernel/sigtools.h" +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +namespace { + +int env_int(const char *name, int fallback) +{ + const char *value = getenv(name); + if (value == nullptr || *value == 0) + return fallback; + return atoi(value); +} + +size_t peak_rss_bytes() +{ + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) != 0) + return 0; + return (size_t)ru.ru_maxrss * 1024; +} + +double ms(std::chrono::steady_clock::duration d) +{ + return std::chrono::duration(d).count(); +} + +uint32_t next_rand(uint32_t &state) +{ + state = state * 1664525u + 1013904223u; + return state >> 8; +} + +std::string padded(const char *stem, int index, int extra_chars) +{ + std::string name = stringf("\\%s_%d", stem, index); + name += std::string(extra_chars, 'n'); + return name; +} + +} + +TEST(SigBitPerf, hash_and_sigmap_stress) +{ + int wire_count = env_int("YOSYS_SIGBIT_PERF_WIRES", 4000); + int wire_width = env_int("YOSYS_SIGBIT_PERF_WIDTH", 16); + int alias_every = env_int("YOSYS_SIGBIT_PERF_ALIAS", 4); + int probe_rounds = env_int("YOSYS_SIGBIT_PERF_ROUNDS", 20); + int name_padding = env_int("YOSYS_SIGBIT_PERF_NAMEPAD", 24); + + ASSERT_GE(wire_count, 2); + ASSERT_GE(wire_width, 1); + ASSERT_GE(alias_every, 2); + ASSERT_GE(probe_rounds, 1); + + RTLIL::Design *design = new RTLIL::Design; + RTLIL::Module *module = design->addModule(padded("sigbit_perf_module", 0, name_padding)); + + std::vector wires; + wires.reserve(wire_count); + for (int i = 0; i < wire_count; i++) + wires.push_back(module->addWire(padded("sigbit_perf_wire", i, name_padding), wire_width)); + + int aliased = 0; + for (int i = alias_every; i < wire_count; i += alias_every) { + module->connect(RTLIL::SigSpec(wires[i]), RTLIL::SigSpec(wires[i - 1])); + aliased++; + } + + std::vector bits; + bits.reserve((size_t)wire_count * wire_width); + for (RTLIL::Wire *wire : wires) + for (int i = 0; i < wire_width; i++) + bits.push_back(RTLIL::SigBit(wire, i)); + + uint32_t rng = 12345; + std::vector probes = bits; + for (size_t i = probes.size(); i > 1; i--) + std::swap(probes[i - 1], probes[next_rand(rng) % i]); + + size_t rss_before = peak_rss_bytes(); + + auto t0 = std::chrono::steady_clock::now(); + SigMap sigmap(module); + auto t1 = std::chrono::steady_clock::now(); + + size_t sigmap_checksum = 0; + for (int round = 0; round < probe_rounds; round++) + for (const RTLIL::SigBit &bit : probes) + sigmap_checksum += sigmap(bit).offset; + auto t2 = std::chrono::steady_clock::now(); + + pool seen; + for (int round = 0; round < probe_rounds; round++) { + seen.clear(); + for (const RTLIL::SigBit &bit : probes) + seen.insert(bit); + } + auto t3 = std::chrono::steady_clock::now(); + + size_t hits = 0; + for (int round = 0; round < probe_rounds; round++) + for (const RTLIL::SigBit &bit : probes) + hits += seen.count(bit); + auto t4 = std::chrono::steady_clock::now(); + + dict owner; + for (int round = 0; round < probe_rounds; round++) { + owner.clear(); + int n = 0; + for (const RTLIL::SigBit &bit : probes) + owner[bit] = n++; + } + auto t5 = std::chrono::steady_clock::now(); + + size_t dict_checksum = 0; + for (int round = 0; round < probe_rounds; round++) + for (const RTLIL::SigBit &bit : probes) + dict_checksum += owner.at(bit); + auto t6 = std::chrono::steady_clock::now(); + + size_t bit_count = probes.size(); + size_t ops = bit_count * (size_t)probe_rounds; + + EXPECT_EQ(seen.size(), bit_count); + EXPECT_EQ(owner.size(), bit_count); + EXPECT_EQ(hits, ops); + + printf("[ PERF ] wires=%d width=%d alias_every=%d aliased=%d namepad=%d rounds=%d\n", + wire_count, wire_width, alias_every, aliased, name_padding, probe_rounds); + printf("[ PERF ] bits=%zu ops_per_phase=%zu checksums=%zu,%zu\n", + bit_count, ops, sigmap_checksum, dict_checksum); + printf("[ PERF ] sigmap_build_ms=%.1f sigmap_lookup_ns=%.2f\n", + ms(t1 - t0), 1e6 * ms(t2 - t1) / (double)ops); + printf("[ PERF ] pool_insert_ns=%.2f pool_lookup_ns=%.2f\n", + 1e6 * ms(t3 - t2) / (double)ops, 1e6 * ms(t4 - t3) / (double)ops); + printf("[ PERF ] dict_insert_ns=%.2f dict_lookup_ns=%.2f\n", + 1e6 * ms(t5 - t4) / (double)ops, 1e6 * ms(t6 - t5) / (double)ops); + printf("[ PERF ] total_ms=%.1f peak_rss_mb=%.1f rss_before_mb=%.1f\n", + ms(t6 - t0), peak_rss_bytes() / 1048576.0, rss_before / 1048576.0); + fflush(stdout); + + delete design; +} + +YOSYS_NAMESPACE_END diff --git a/tests/unit/kernel/twineHashTest.cc b/tests/unit/kernel/twineHashTest.cc index 5558462cb..bea19e309 100644 --- a/tests/unit/kernel/twineHashTest.cc +++ b/tests/unit/kernel/twineHashTest.cc @@ -18,7 +18,7 @@ TEST(TwineHashTest, Fragmentation) IdString flat = pool.add(std::string("$abcdefghij")); IdString base = pool.add(std::string("$abcde")); - IdString split = pool.add(Twine::Suffix{base, "fghij"}); + IdString split = pool.add(TwineSpec::Suffix{base, "fghij"}); EXPECT_EQ(hash(flat.untag()), hash(split.untag())); EXPECT_EQ(hash(flat.untag()), hash(std::string_view("$abcdefghij"))); @@ -35,7 +35,7 @@ TEST(TwineHashTest, SplitPoints) for (size_t cut = 1; cut < content.size(); cut++) { IdString base = pool.add(content.substr(0, cut)); - IdString split = pool.add(Twine::Suffix{base, content.substr(cut)}); + IdString split = pool.add(TwineSpec::Suffix{base, content.substr(cut)}); EXPECT_EQ(hash(split.untag()), want) << "split after " << cut; } } @@ -63,7 +63,7 @@ TEST(TwineHashTest, BenchmarkIntern) std::vector refs; refs.reserve(kNames); for (int i = 0; i < kNames; i++) - refs.push_back(pool.add(Twine::Suffix{prefix, stringf("$%d", i)})); + refs.push_back(pool.add(TwineSpec::Suffix{prefix, stringf("$%d", i)})); auto t1 = std::chrono::steady_clock::now(); TwineSearch search(&pool); diff --git a/tests/unit/kernel/twinePublicityTest.cc b/tests/unit/kernel/twinePublicityTest.cc index 7ca5d9991..dc8c584b8 100644 --- a/tests/unit/kernel/twinePublicityTest.cc +++ b/tests/unit/kernel/twinePublicityTest.cc @@ -47,8 +47,8 @@ TEST(TwinePublicityTest, SuffixPublicity) IdString pub = pool.add(std::string("\\base")); IdString priv = pool.add(std::string("$base")); - IdString pub_sfx = pool.add(Twine{Twine::Suffix{pub, "_1"}}); - IdString priv_sfx = pool.add(Twine{Twine::Suffix{priv, "_1"}}); + IdString pub_sfx = pool.add(TwineSpec{TwineSpec::Suffix{pub, "_1"}}); + IdString priv_sfx = pool.add(TwineSpec{TwineSpec::Suffix{priv, "_1"}}); EXPECT_TRUE(pub_sfx.isPublic()); EXPECT_FALSE(priv_sfx.isPublic()); @@ -82,9 +82,9 @@ TEST(TwinePublicityTest, LookupTag) TEST(TwinePublicityTest, SearchUnifies) { TwinePool pool; - IdString flat = pool.add(Twine::Leaf{"$abc"}); - IdString head = pool.add(Twine::Leaf{"$a"}); - IdString split = pool.add(Twine::Suffix{head, "bc"}); + IdString flat = pool.add(TwineSpec::Leaf{"$abc"}); + IdString head = pool.add(TwineSpec::Leaf{"$a"}); + IdString split = pool.add(TwineSpec::Suffix{head, "bc"}); ASSERT_NE(flat, split); ASSERT_EQ(pool.str(flat), pool.str(split)); @@ -100,7 +100,7 @@ TEST(TwinePublicityTest, SearchUnifies) TEST(TwinePublicityTest, SearchPublicity) { TwinePool pool; - IdString priv = pool.add(Twine::Leaf{"sig"}); + IdString priv = pool.add(TwineSpec::Leaf{"sig"}); IdString pub = pool.add(std::string("\\sig")); ASSERT_EQ(priv, pub.untag()); diff --git a/tests/unit/kernel/twineSortTest.cc b/tests/unit/kernel/twineSortTest.cc index 7052d4c90..dc959a2e0 100644 --- a/tests/unit/kernel/twineSortTest.cc +++ b/tests/unit/kernel/twineSortTest.cc @@ -15,7 +15,7 @@ std::vector bench_refs(TwinePool &twines, int count) std::vector refs; refs.reserve(count); for (int i = 0; i < count; i++) - refs.push_back(twines.add(Twine::Suffix{prefix, stringf("$%08d", (count - i) * 7919 % count)})); + refs.push_back(twines.add(TwineSpec::Suffix{prefix, stringf("$%08d", (count - i) * 7919 % count)})); return refs; } @@ -69,14 +69,14 @@ TEST(TwineSortTest, GeneralWalk) IdString deep = a; for (int i = 0; i < 12; i++) { - deep = twines.add(Twine::Suffix{deep, stringf(".lvl%d", i)}); + deep = twines.add(TwineSpec::Suffix{deep, stringf(".lvl%d", i)}); refs.push_back(deep); refs.push_back(deep.tag(true)); } IdString other = twines.add(std::string("$alpha.")); for (int i = 0; i < 12; i++) { - other = twines.add(Twine::Suffix{other, stringf("lvl%d.", i)}); + other = twines.add(TwineSpec::Suffix{other, stringf("lvl%d.", i)}); refs.push_back(other); } diff --git a/tests/various/rename_scramble_name.ys b/tests/various/rename_scramble_name.ys index ca0a15921..02bc5d3ff 100644 --- a/tests/various/rename_scramble_name.ys +++ b/tests/various/rename_scramble_name.ys @@ -28,4 +28,4 @@ hierarchy -top top rename -seed 2 -scramble-name c:bar select -assert-none c:bar select -assert-count 1 c:$_*_ -select -assert-none c:$_*_ w:* foo/c:$add$:2$1 %% %n +select -assert-none c:$_*_ w:* foo/c:$:2$add$1 %% %n diff --git a/tests/various/rename_unescape.ys b/tests/various/rename_unescape.ys index ee8dfa712..f338510ad 100644 --- a/tests/various/rename_unescape.ys +++ b/tests/various/rename_unescape.ys @@ -27,7 +27,7 @@ select -assert-count 1 w:$e # Colliding with keyword select -assert-count 1 w:wire # Don't touch internal names -select -assert-count 1 w:$add$:*$1_Y +select -assert-count 1 w:$:*$add$*_Y rename -unescape @@ -38,7 +38,7 @@ select -assert-count 1 w:d_ select -assert-count 1 w:d__1 select -assert-count 1 w:_e select -assert-count 1 w:wire_ -select -assert-count 1 w:$add$:*$1_Y +select -assert-count 1 w:$:*$add$*_Y # Ports are updated during rename design -reset