From d1de31d2c3acae2aa3beeafe883a4bcc668c4816 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 12 Aug 2026 12:49:52 +0200 Subject: [PATCH] kernel: best-effort IdString provenance checking --- kernel/twine.cc | 88 +++++++++++++++++++++++++++++++++++++++++++++---- kernel/twine.h | 62 ++++++++++++++++++++++++++++------ 2 files changed, 134 insertions(+), 16 deletions(-) diff --git a/kernel/twine.cc b/kernel/twine.cc index 0a11b908c..11216391b 100644 --- a/kernel/twine.cc +++ b/kernel/twine.cc @@ -21,7 +21,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(value); return h; } +Hasher IdString::hash_into(Hasher h) const { h.hash64(raw()); return h; } std::string IdString::handle_token() const { return stringf("%s@%zu", isPublic() ? "$pub" : "$priv", untag().raw()); @@ -53,9 +53,16 @@ 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)) {} + bool Twine::is_leaf() const { return std::holds_alternative(data); } bool Twine::is_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)) {} + 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); } @@ -76,12 +83,62 @@ std::pair twine_unescape(std::string s) { return {std::move(s), is_public}; } +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::operator=(const TwinePool& other) { + HashConsPool::operator=(other); + return *this; +} + +TwinePool& TwinePool::operator=(TwinePool&& other) { + HashConsPool::operator=(std::move(other)); + return *this; +} + +size_t TwinePool::serial() const { return serial_; } + +bool TwinePool::owns(IdString ref) const { + return ref == IdString::Null || ref.serial() == 0 || ref.serial() == serial_ || ID::is_static(ref); +} + +IdString TwinePool::stamp(IdString ref) const { + if (ref == IdString::Null || ID::is_static(ref)) + return ref; + return ref.stamped(serial_); +} + +void TwinePool::check_owned(IdString ref) const { +#ifndef NDEBUG + log_assert(owns(ref)); +#endif +} + +const TwineNode& TwinePool::operator[](IdString ref) const { + check_owned(ref); + return HashConsPool::operator[](ref); +} + 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(); + sfx->prefix = sfx->prefix.untag().stamped(0); +} + +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; +} + +IdString TwinePool::add_inner(TwineNode t) { + if (free_list.empty() && STATIC_COUNT + backing.size() > IdString::MAX_INDEX) + log_error("Out of twine handles: a design may name at most %zu distinct twines.\n", + IdString::MAX_INDEX - STATIC_COUNT); + return HashConsPool::add_inner(std::move(t)); } size_t TwinePool::hash_node(const TwineNode& t) { @@ -164,7 +221,7 @@ std::string TwinePool::unescaped_str(IdString ref) const { IdString TwinePool::find(const std::string &name) const { bool is_public = !name.empty() && name[0] == '\\'; - return find(Twine::Leaf{is_public ? name.substr(1) : name}).tag(is_public); + return stamp(find(Twine::Leaf{is_public ? name.substr(1) : name}).tag(is_public)); } IdString TwinePool::find(Twine t) const { @@ -175,7 +232,7 @@ IdString TwinePool::find(Twine t) const { t = Twine::Suffix{prefix, std::move(ap->tail)}; } bool is_public = inherits_publicity(t); - return HashConsPool::find(to_node(std::move(t))).tag(is_public); + return stamp(HashConsPool::find(to_node(std::move(t))).tag(is_public)); } IdString TwinePool::add(Twine t) { @@ -184,14 +241,14 @@ IdString TwinePool::add(Twine t) { t = Twine::Suffix{prefix, std::move(ap->tail)}; } bool is_public = inherits_publicity(t); - return add_inner(to_node(std::move(t))).tag(is_public); + return stamp(add_inner(to_node(std::move(t))).tag(is_public)); } IdString TwinePool::add(std::string s) { if (s.empty()) return IdString::Null; auto [content, is_public] = twine_unescape(std::move(s)); - return add_inner(Twine::Leaf{std::move(content)}).tag(is_public); + return stamp(add_inner(Twine::Leaf{std::move(content)}).tag(is_public)); } IdString TwinePool::copy_from(const TwinePool& src, IdString ref) { @@ -230,6 +287,25 @@ IdString TwinePool::find_from(const TwinePool& src, IdString ref) const { return IdString::Null; } +std::string TwinePool::ref_token(IdString ref) const { + return "#" + 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') + return IdString::Null; + value = value * 10 + (c - '0'); + } + IdString ref(value); + if (ref == IdString::Null || (!ID::is_static(ref) && ref.serial() != serial_)) + return IdString::Null; + return is_live(ref) ? ref : IdString::Null; +} + void TwinePool::dump(std::ostream& os) const { os << "--- TwinePool Dump (" << backing.size() << " nodes) ---\n"; for (size_t idx = 0; idx < backing.size(); ++idx) { diff --git a/kernel/twine.h b/kernel/twine.h index 0568e2cc9..932b21c91 100644 --- a/kernel/twine.h +++ b/kernel/twine.h @@ -49,20 +49,42 @@ private: 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); public: static constexpr NullIdString Null{}; - constexpr size_t raw() const { return value; } + static constexpr size_t MAX_INDEX = TWINE_INDEX_MASK; + static constexpr size_t MAX_SERIAL = TWINE_SERIAL_MASK >> TWINE_SERIAL_SHIFT; + + constexpr size_t raw() const { + return value == TWINE_NULL_VAL ? value : (value & ~TWINE_SERIAL_MASK); + } + + // raw() with the pool serial kept + constexpr size_t bits() const { return value; } + + 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)); + } constexpr IdString() : value(TWINE_NULL_VAL) {} explicit constexpr IdString(size_t val) : value(val) {} - constexpr bool operator==(const IdString&) const = default; + constexpr bool operator==(const IdString &rhs) const { return raw() == rhs.raw(); } + constexpr bool operator!=(const IdString &rhs) const { return raw() != rhs.raw(); } constexpr std::strong_ordering operator<=>(const IdString &rhs) const { - if (auto cmp = (value & ~TWINE_PUBLIC_BIT) <=> (rhs.value & ~TWINE_PUBLIC_BIT); cmp != 0) + size_t a = raw(), b = rhs.raw(); + if (auto cmp = (a & ~TWINE_PUBLIC_BIT) <=> (b & ~TWINE_PUBLIC_BIT); cmp != 0) return cmp; - return (value & TWINE_PUBLIC_BIT) <=> (rhs.value & TWINE_PUBLIC_BIT); + return (a & TWINE_PUBLIC_BIT) <=> (b & TWINE_PUBLIC_BIT); } template @@ -177,9 +199,9 @@ struct Twine { std::variant data; - Twine(Leaf v) : data(std::move(v)) {} - Twine(Suffix v) : data(std::move(v)) {} - Twine(AutoSuffix v) : data(std::move(v)) {} + Twine(Leaf v); + Twine(Suffix v); + Twine(AutoSuffix v); bool is_leaf() const; bool is_suffix() const; @@ -191,8 +213,8 @@ struct TwineNode { std::variant data; TwineNode() = default; - TwineNode(Twine::Leaf v) : data(std::move(v)) {} - TwineNode(Twine::Suffix v) : data(std::move(v)) {} + TwineNode(Twine::Leaf v); + TwineNode(Twine::Suffix v); bool is_dead() const; bool is_leaf() const; @@ -220,6 +242,18 @@ std::pair twine_unescape(std::string s); struct TwinePool : HashConsPool { static constexpr size_t STATIC_COUNT = StaticTwines::count; + TwinePool(); + TwinePool(const TwinePool& other); + TwinePool(TwinePool&& other); + TwinePool& operator=(const TwinePool& other); + TwinePool& operator=(TwinePool&& other); + + size_t serial() const; + bool owns(IdString ref) const; + IdString stamp(IdString ref) const; + void check_owned(IdString ref) const; + const TwineNode& operator[](IdString ref) const; + static const TwineNode& static_node(size_t idx); // static IdString untag(IdString ref); @@ -259,13 +293,21 @@ struct TwinePool : HashConsPool { IdString copy_from(const TwinePool& src, IdString ref); // Non-mutating counterpart of copy_from IdString find_from(const TwinePool& src, IdString 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; + // Null unless the token names a live twine stamped by this pool + IdString ref_from_token(std::string_view token) const; void dump(std::ostream& os = std::cout) const; using HashConsPool::gc; private: + IdString add_inner(TwineNode t); + static size_t next_serial(); + size_t serial_; + static bool inherits_publicity(const Twine &t); static TwineNode to_node(Twine t); - using HashConsPool::add_inner; }; /**