// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Dfg vertex cache to find existing vertices // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2025 by Wilson Snyder. This program is free software; you // can redistribute it and/or modify it under the terms of either the GNU // Lesser General Public License Version 3 or the Perl Artistic License // Version 2.0. // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // // A cache for DfgGraph, to find existing vertices with identical inputs. // // Beware that if you use this data-structure, you must invalidate the // cache any time you change the inputs of an existing vertex, otherwise // you will have a very bad day. // //************************************************************************* #ifndef VERILATOR_V3DFGCACHE_H_ #define VERILATOR_V3DFGCACHE_H_ #include "V3Dfg.h" #include // Template specializatoins must be defiend before they are used, // so this is implemented in a namespace namespace V3DfgCacheInternal { // Hash constants by value, everything else by identity inline V3Hash vertexHash(const DfgVertex* vtxp) { if (const DfgConst* const constp = vtxp->cast()) return constp->num().toHash(); return V3Hash{reinterpret_cast(vtxp)}; } // Constants are equal by value, everything else is equal by identity inline bool vertexEqual(const DfgVertex* ap, const DfgVertex* bp) { if (ap == bp) return true; if (ap->type() != bp->type()) return false; if (const DfgConst* const aConstp = ap->cast()) { const DfgConst* const bConstp = bp->as(); return aConstp->num().isCaseEq(bConstp->num()); } return false; } class KeySel final { const DfgVertex* const m_fromp; const uint32_t m_lsb; const uint32_t m_size; public: KeySel(DfgVertex* fromp, uint32_t lsb, uint32_t size) : m_fromp{fromp} , m_lsb{lsb} , m_size{size} {} struct Hash final { size_t operator()(const KeySel& key) const { // cppcheck-suppress unreadVariable // cppcheck bug V3Hash hash{vertexHash(key.m_fromp)}; hash += key.m_lsb; hash += key.m_size; return hash.value(); } }; struct Equal final { bool operator()(const KeySel& a, const KeySel& b) const { return a.m_lsb == b.m_lsb && a.m_size == b.m_size && vertexEqual(a.m_fromp, b.m_fromp); } }; }; class KeyUnary final { const DfgVertex* const m_source0p; public: // cppcheck-suppress noExplicitConstructor KeyUnary(DfgVertex* source0p) : m_source0p{source0p} {} struct Hash final { size_t operator()(const KeyUnary& key) const { // return vertexHash(key.m_source0p).value(); } }; struct Equal final { bool operator()(const KeyUnary& a, const KeyUnary& b) const { return vertexEqual(a.m_source0p, b.m_source0p); } }; }; class KeyBinary final { const DfgVertex* const m_source0p; const DfgVertex* const m_source1p; public: KeyBinary(DfgVertex* source0p, DfgVertex* source1p) : m_source0p{source0p} , m_source1p{source1p} {} struct Hash final { size_t operator()(const KeyBinary& key) const { // cppcheck-suppress unreadVariable // cppcheck bug V3Hash hash{vertexHash(key.m_source0p)}; hash += vertexHash(key.m_source1p); return hash.value(); } }; struct Equal final { bool operator()(const KeyBinary& a, const KeyBinary& b) const { return vertexEqual(a.m_source0p, b.m_source0p) && vertexEqual(a.m_source1p, b.m_source1p); } }; }; class KeyTernary final { const DfgVertex* const m_source0p; const DfgVertex* const m_source1p; const DfgVertex* const m_source2p; public: KeyTernary(DfgVertex* source0p, DfgVertex* source1p, DfgVertex* source2p) : m_source0p{source0p} , m_source1p{source1p} , m_source2p{source2p} {} struct Hash final { size_t operator()(const KeyTernary& key) const { // cppcheck-suppress unreadVariable // cppcheck bug V3Hash hash{vertexHash(key.m_source0p)}; hash += vertexHash(key.m_source1p); hash += vertexHash(key.m_source2p); return hash.value(); } }; struct Equal final { bool operator()(const KeyTernary& a, const KeyTernary& b) const { return vertexEqual(a.m_source0p, b.m_source0p) && vertexEqual(a.m_source1p, b.m_source1p) && vertexEqual(a.m_source2p, b.m_source2p); } }; }; template using Cache = std::unordered_map; using CacheSel = Cache; using CacheUnary = Cache; using CacheBinary = Cache; using CacheTernary = Cache; // These return a reference to the mapped entry, inserting a nullptr if not yet exists inline DfgSel*& getEntry(CacheSel& cache, const DfgDataType& dtype, DfgVertex* src0p, uint32_t lsb) { const KeySel key{src0p, lsb, dtype.size()}; return cache[key]; } inline DfgVertexUnary*& getEntry(CacheUnary& cache, const DfgDataType&, DfgVertex* src0p) { const KeyUnary key{src0p}; return cache[key]; } inline DfgVertexBinary*& getEntry(CacheBinary& cache, const DfgDataType&, DfgVertex* src0p, DfgVertex* src1p) { const KeyBinary key{src0p, src1p}; return cache[key]; } inline DfgVertexTernary*& getEntry(CacheTernary& cache, const DfgDataType&, DfgVertex* src0p, DfgVertex* src1p, DfgVertex* src2p) { const KeyTernary key{src0p, src1p, src2p}; return cache[key]; } // These return a reference to the mapped entry, inserting a nullptr if not yet exists inline CacheSel::iterator find(CacheSel& cache, DfgVertex* src0p, uint32_t lsb, uint32_t size) { const KeySel key{src0p, lsb, size}; return cache.find(key); } inline CacheUnary::iterator find(CacheUnary& cache, DfgVertex* src0p) { const KeyUnary key{src0p}; return cache.find(key); } inline CacheBinary::iterator find(CacheBinary& cache, DfgVertex* src0p, DfgVertex* src1p) { const KeyBinary key{src0p, src1p}; return cache.find(key); } inline CacheTernary::iterator find(CacheTernary& cache, DfgVertex* src0p, DfgVertex* src1p, DfgVertex* src2p) { const KeyTernary key{src0p, src1p, src2p}; return cache.find(key); } // These set the operands of a new vertex inline void setOperands(DfgSel* vtxp, DfgVertex* fromp, uint32_t lsb) { vtxp->fromp(fromp); vtxp->lsb(lsb); } inline void setOperands(DfgVertexUnary* vtxp, DfgVertex* src0p) { // vtxp->inputp(0, src0p); } inline void setOperands(DfgVertexBinary* vtxp, DfgVertex* src0p, DfgVertex* src1p) { vtxp->inputp(0, src0p); vtxp->inputp(1, src1p); } inline void setOperands(DfgVertexTernary* vtxp, DfgVertex* src0p, DfgVertex* src1p, DfgVertex* src2p) { vtxp->inputp(0, src0p); vtxp->inputp(1, src1p); vtxp->inputp(2, src2p); } // Get or create (and insert) vertex with given operands template inline Vertex* getOrCreate(DfgGraph& dfg, FileLine* flp, T_Cache& cache, const DfgDataType& dtype, Operands... operands) { typename T_Cache::mapped_type& entrypr = getEntry(cache, dtype, operands...); if (!entrypr) { Vertex* const newp = new Vertex{dfg, flp, dtype}; setOperands(newp, operands...); entrypr = newp; } return reinterpret_cast(entrypr); } // These add an existing vertex to the table, if an equivalent does not yet exist inline void cache(CacheSel& cache, DfgSel* vtxp) { DfgSel*& entrypr = getEntry(cache, vtxp->dtype(), vtxp->fromp(), vtxp->lsb()); if (!entrypr) entrypr = vtxp; } inline void cache(CacheUnary& cache, DfgVertexUnary* vtxp) { DfgVertexUnary*& entrypr = getEntry(cache, vtxp->dtype(), vtxp->inputp(0)); if (!entrypr) entrypr = vtxp; } inline void cache(CacheBinary& cache, DfgVertexBinary* vtxp) { DfgVertexBinary*& entrypr = getEntry(cache, vtxp->dtype(), vtxp->inputp(0), vtxp->inputp(1)); if (!entrypr) entrypr = vtxp; } inline void cache(CacheTernary& cache, DfgVertexTernary* vtxp) { DfgVertexTernary*& entrypr = getEntry(cache, vtxp->dtype(), vtxp->inputp(0), vtxp->inputp(1), vtxp->inputp(2)); if (!entrypr) entrypr = vtxp; } // These remove an existing vertex from the cache, if it is the cached vertex inline void invalidateByValue(CacheSel& cache, const DfgSel* vtxp) { const auto it = find(cache, vtxp->fromp(), vtxp->lsb(), vtxp->size()); if (it != cache.end() && it->second == vtxp) cache.erase(it); } inline void invalidateByValue(CacheUnary& cache, const DfgVertexUnary* vtxp) { const auto it = find(cache, vtxp->inputp(0)); if (it != cache.end() && it->second == vtxp) cache.erase(it); } inline void invalidateByValue(CacheBinary& cache, const DfgVertexBinary* vtxp) { const auto it = find(cache, vtxp->inputp(0), vtxp->inputp(1)); if (it != cache.end() && it->second == vtxp) cache.erase(it); } inline void invalidateByValue(CacheTernary& cache, const DfgVertexTernary* vtxp) { const auto it = find(cache, vtxp->inputp(0), vtxp->inputp(1), vtxp->inputp(2)); if (it != cache.end() && it->second == vtxp) cache.erase(it); } // clang-format off // This type defines the cache type corresponding to a vertex type template struct CacheTypeImpl : public CacheTypeImpl {}; template <> struct CacheTypeImpl { using type = CacheSel; }; template <> struct CacheTypeImpl { using type = CacheUnary; }; template <> struct CacheTypeImpl { using type = CacheBinary; }; template <> struct CacheTypeImpl { using type = CacheTernary; }; template using CacheType = typename CacheTypeImpl::type; // Just add new lines in here for new vertex types you need to be able to cache #define FOREACH_CACHED_VERTEX_TYPE(macro) \ macro(DfgSel) \ macro(DfgNot) \ macro(DfgNegate) \ macro(DfgConcat) \ macro(DfgNeq) \ macro(DfgShiftL) \ macro(DfgShiftR) \ macro(DfgShiftRS) \ macro(DfgAdd) \ macro(DfgSub) \ macro(DfgMul) \ macro(DfgMulS) \ macro(DfgEq) \ macro(DfgAnd) \ macro(DfgOr) \ macro(DfgXor) \ macro(DfgRedAnd) \ macro(DfgRedOr) \ macro(DfgRedXor) \ macro(DfgCond) // clang-format on class V3DfgCache final { DfgGraph& m_dfg; // The DfgGraph we are caching the vertices of // The per type caches #define VERTEX_CACHE_DECLARE_LUT(t) CacheType m_cache##t; FOREACH_CACHED_VERTEX_TYPE(VERTEX_CACHE_DECLARE_LUT) #undef VERTEX_CACHE_DECLARE_LUT // Specializations return one of the above m_cache members template inline CacheType& cacheForType(); public: explicit V3DfgCache(DfgGraph& dfg) : m_dfg{dfg} {} // Find a vertex of type 'Vertex', with the given operands, or create a new one and add it. template inline Vertex* getOrCreate(FileLine* flp, const DfgDataType& dtype, Operands... operands); // Add an existing vertex of the table. If an equivalent already exists, then nothing happens. void cache(DfgVertex* vtxp); // Remove an exiting vertex, it is the cached vertex. void invalidateByValue(DfgVertex* vtxp); }; // clang-format off // The per-type specializations of 'V3DfgCache::cacheForType' #define VERTEX_CACHE_DEFINE_LUT_SPECIALIZATION(t) \ template <> inline CacheType& V3DfgCache::cacheForType() { return m_cache ## t; } FOREACH_CACHED_VERTEX_TYPE(VERTEX_CACHE_DEFINE_LUT_SPECIALIZATION) #undef VERTEX_CACHE_DEFINE_LUT_SPECIALIZATION // clang-format on // Find a vertex of type 'Vertex', with the given operands, or create a new one and add it template Vertex* V3DfgCache::getOrCreate(FileLine* flp, const DfgDataType& dtype, Operands... operands) { static_assert(std::is_final::value, "Must invoke on final vertex type"); constexpr bool isSel = std::is_same::value; constexpr bool isUnary = !isSel && std::is_base_of::value; constexpr bool isBinary = std::is_base_of::value; constexpr bool isTernary = std::is_base_of::value; static_assert(isSel || isUnary || isBinary || isTernary, "'get' called with unknown vertex type"); static_assert(!isSel || sizeof...(Operands) == 2, // "Wrong number of operands to DfgSel"); static_assert(!isUnary || sizeof...(Operands) == 1, "Wrong number of operands to DfgVertexUnary"); static_assert(!isBinary || sizeof...(Operands) == 2, "Wrong number of operands to DfgVertexBinary"); static_assert(!isTernary || sizeof...(Operands) == 3, "Wrong number of operands to DfgVertexTernary"); return V3DfgCacheInternal::getOrCreate, Operands...>( m_dfg, flp, cacheForType(), dtype, operands...); } } // namespace V3DfgCacheInternal // Export only the public interface class using V3DfgCacheInternal::V3DfgCache; #endif // VERILATOR_V3DFGCACHE_H_