From e3dde40848237afabe232c309226d0bdf83b155b Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 23 Sep 2026 16:10:20 +0200 Subject: [PATCH] twines: LeafIdString --- kernel/twine.cc | 27 +++++++++++++++++++++++---- kernel/twine.h | 49 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/kernel/twine.cc b/kernel/twine.cc index 7166e8b31..047ba4674 100644 --- a/kernel/twine.cc +++ b/kernel/twine.cc @@ -176,6 +176,10 @@ IdString TwinePool::stamp(IdString ref) const { return ref.stamped(serial_); } +LeafIdString TwinePool::stamp(LeafIdString ref) const { + return LeafIdString(stamp(IdString(ref))); +} + void TwinePool::check_owned(IdString ref) const { if constexpr (IdString::MAX_SERIAL != 0) log_assert(owns(ref)); @@ -295,12 +299,12 @@ IdString TwinePool::intern(uint32_t prefix, std::string_view text) { return add_inner(TwineNode{prefix, text}); } -IdString TwinePool::find(const std::string &name) const { +LeafIdString TwinePool::find(const std::string &name) const { bool is_public = !name.empty() && name[0] == '\\'; std::string_view content = name; if (is_public) content.remove_prefix(1); - return stamp(find_content(TwineNode::NO_PREFIX, content).tag(is_public)); + return stamp(LeafIdString(find_content(TwineNode::NO_PREFIX, content)).tag(is_public)); } IdString TwinePool::find(TwineSpec t) const { @@ -340,11 +344,17 @@ 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) { +LeafIdString TwinePool::add(std::string s) { if (s.empty()) return IdString::Null; auto [content, is_public] = twine_unescape(std::move(s)); - return stamp(intern(TwineNode::NO_PREFIX, content).tag(is_public)); + return stamp(LeafIdString(intern(TwineNode::NO_PREFIX, content)).tag(is_public)); +} + +LeafIdString TwinePool::flatten(IdString ref) { + if (ref == IdString::Null || ID::is_static(ref) || (*this)[ref].is_leaf()) + return LeafIdString(stamp(ref)); + return stamp(LeafIdString(intern(TwineNode::NO_PREFIX, unescaped_str(ref))).tag(ref.isPublic())); } IdString TwinePool::copy_from(const TwinePool& src, IdString ref) { @@ -369,6 +379,11 @@ IdString TwinePool::copy_from(const TwinePool& src, IdString ref) { return IdString::Null; } +// Copying preserves structure, so leaves stay leaves +LeafIdString TwinePool::copy_from(const TwinePool& src, LeafIdString ref) { + return LeafIdString(copy_from(src, IdString(ref))); +} + IdString TwinePool::find_from(const TwinePool& src, IdString ref) const { if (ref == IdString::Null) return ref; @@ -393,6 +408,10 @@ IdString TwinePool::find_from(const TwinePool& src, IdString ref) const { return IdString::Null; } +LeafIdString TwinePool::find_from(const TwinePool& src, LeafIdString ref) const { + return LeafIdString(find_from(src, IdString(ref))); +} + std::string TwinePool::ref_token(IdString ref) const { return "#" + std::to_string((uint64_t)serial_) + ":" + std::to_string((uint64_t)stamp(ref).bits()); diff --git a/kernel/twine.h b/kernel/twine.h index 0bc54d45e..559abdc62 100644 --- a/kernel/twine.h +++ b/kernel/twine.h @@ -150,6 +150,30 @@ public: constexpr NullIdString::operator IdString() const { return IdString(); } constexpr bool NullIdString::operator==(IdString ref) const { return ref.empty(); } +/** + * LeafIdString is an IdString that is statically known to be a leaf twine + * (or Null). Handles only compare equal when they're structurally equal, + * so a key that's looked up with static ids or strings interned with + * TwinePool::add(std::string), like attribute and parameter keys, + * has to be a leaf: a suffix twine spelling the same name wouldn't be found. + * + * Static ids are leaves. Otherwise, only the TwinePool methods that + * intern leaves construct one, so where a suffix would sneak in, + * the conversion has to be spelled out with TwinePool::flatten. + */ +struct LeafIdString : IdString { + constexpr LeafIdString() = default; + constexpr LeafIdString(NullIdString) {} + + constexpr LeafIdString untag() const { return LeafIdString(IdString::untag()); } + constexpr LeafIdString tag(bool pub) const { return LeafIdString(IdString::tag(pub)); } + +private: + explicit constexpr LeafIdString(IdString ref) : IdString(ref) {} + friend struct ID; + friend struct TwinePool; +}; + namespace hashlib { template<> struct hash_ops { @@ -164,6 +188,8 @@ namespace hashlib { return h; } }; + template<> + struct hash_ops : hash_ops {}; } @@ -178,7 +204,7 @@ enum : short { struct ID { // Static ids are name handles: non-'$' constids were '\'-escaped publics, // so their handles carry the publicity bit baked in at compile time. -#define X(N) static constexpr IdString N = IdString(IDX_##N).tag((#N)[0] != '$'); +#define X(N) static constexpr LeafIdString N = LeafIdString(IdString(IDX_##N)).tag((#N)[0] != '$'); #include "kernel/constids.inc" #undef X @@ -188,7 +214,7 @@ struct ID { #undef X }; - static constexpr IdString lookup(std::string_view name) + static constexpr LeafIdString lookup(std::string_view name) { int low = 0, high = STATIC_TWINE_END; while (high - low >= 2) { @@ -202,19 +228,26 @@ struct ID { if (name != static_names[low]) throw "unknown twine id"; - return IdString(low).tag(name[0] != '$'); + return LeafIdString(IdString(low)).tag(name[0] != '$'); } static constexpr bool is_static(IdString ref) { return ref.untag().raw() < STATIC_TWINE_END; } + // All static twines are leaves + static constexpr LeafIdString static_leaf(size_t raw) { + if (!is_static(IdString(raw))) + throw "not a static twine id"; + return LeafIdString(IdString(raw)); + } + // Static IdString can be constructed without a design pointer static std::string str(IdString ref); static std::string unescaped_str(IdString ref); }; -template inline constexpr IdString constid = IdString(Raw); +template inline constexpr LeafIdString constid = ID::static_leaf(Raw); #define ID(id) (YOSYS_NAMESPACE_PREFIX constid) @@ -354,6 +387,7 @@ struct TwinePool : HashConsPool { size_t serial() const; bool owns(IdString ref) const; IdString stamp(IdString ref) const; + LeafIdString stamp(LeafIdString ref) const; void check_owned(IdString ref) const; const TwineNode& operator[](IdString ref) const; @@ -396,17 +430,20 @@ struct TwinePool : HashConsPool { IdString prefix_of(IdString ref) const; // Only finds leaves. For compatibility only - IdString find(const std::string &name) const; + LeafIdString find(const std::string &name) const; IdString find(TwineSpec t) const; // Doesn't infer publicity IdString add(TwineSpec t); IdString add(IdString prefix, std::string_view tail); IdString auto_prefix(const std::string *prefix); // Infers publicity from first character - IdString add(std::string s); + LeafIdString add(std::string s); + LeafIdString flatten(IdString ref); IdString copy_from(const TwinePool& src, IdString ref); + LeafIdString copy_from(const TwinePool& src, LeafIdString ref); // Non-mutating counterpart of copy_from IdString find_from(const TwinePool& src, IdString ref) const; + LeafIdString find_from(const TwinePool& src, LeafIdString ref) const; // Opaque handle for files that never leave one run of yosys. // Only valid until garbage collection reuses the slot. std::string ref_token(IdString ref) const;