// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Symbol table // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2024 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 // //************************************************************************* #ifndef VERILATOR_V3SYMTABLE_H_ #define VERILATOR_V3SYMTABLE_H_ #include "config_build.h" #include "verilatedos.h" #include "V3Ast.h" #include "V3File.h" #include "V3Global.h" #include "V3String.h" #include #include #include #include #include #include class VSymGraph; class VSymEnt; //###################################################################### // Symbol table using VSymConstMap = std::unordered_set; class VSymEnt final { // Symbol table that can have a "superior" table for resolving upper references // MEMBERS using IdNameMap = std::multimap; IdNameMap m_idNameMap; // Hash of variables by name AstNode* m_nodep; // Node that entry belongs to VSymEnt* m_fallbackp; // Table "above" this one in name scope, for fallback resolution VSymEnt* m_parentp; // Table that created this table, dot notation needed to resolve into it AstNodeModule* m_classOrPackagep; // Package node is in (for V3LinkDot, unused here) string m_symPrefix; // String to prefix symbols with (for V3LinkDot, unused here) bool m_exported; // Allow importing bool m_imported; // Was imported #ifdef VL_DEBUG VL_DEFINE_DEBUG_FUNCTIONS; #else static constexpr int debug() { return 0; } // NOT runtime, too hot of a function #endif public: using const_iterator = IdNameMap::const_iterator; const_iterator begin() const { return m_idNameMap.begin(); } const_iterator end() const { return m_idNameMap.end(); } void dumpIterate(std::ostream& os, VSymConstMap& doneSymsr, const string& indent, int numLevels, const string& searchName) const { os << indent << "+ " << std::left << std::setw(30) << ("'"s + searchName + "'"s) << std::setw(0) << std::right; os << " se" << cvtToHex(this) << std::setw(0); os << " fallb=se" << cvtToHex(m_fallbackp); if (m_symPrefix != "") os << " symPrefix=" << m_symPrefix; if (nodep()) os << " n=" << nodep(); os << '\n'; if (VL_UNCOVERABLE(!doneSymsr.insert(this).second)) { os << indent << "| ^ duplicate, so no children printed\n"; // LCOV_EXCL_LINE } else { for (auto it = m_idNameMap.begin(); it != m_idNameMap.end(); ++it) { if (numLevels >= 1) { it->second->dumpIterate(os, doneSymsr, indent + "| ", numLevels - 1, it->first); } } } } void dumpSelf(std::ostream& os, const string& indent = "", int numLevels = 1) const { VSymConstMap doneSyms; dumpIterate(os, doneSyms, indent, numLevels, "TOP"); } // METHODS VSymEnt(VSymGraph* graphp, const VSymEnt* symp); // Below VSymEnt(VSymGraph* graphp, AstNode* nodep); // Below ~VSymEnt() { // Change links so we coredump if used #ifdef VL_DEBUG m_nodep = reinterpret_cast(1); m_fallbackp = reinterpret_cast(1); m_parentp = reinterpret_cast(1); m_classOrPackagep = reinterpret_cast(1); #endif } #if defined(VL_DEBUG) && !defined(VL_LEAK_CHECKS) // For testing, leak so above destructor 1 assignments work void* operator new(size_t size) { return std::malloc(size); } void operator delete(void* objp, size_t size) {} #endif void fallbackp(VSymEnt* entp) { m_fallbackp = entp; } VSymEnt* fallbackp() const { return m_fallbackp; } void parentp(VSymEnt* entp) { m_parentp = entp; } VSymEnt* parentp() const { return m_parentp; } void classOrPackagep(AstNodeModule* entp) { m_classOrPackagep = entp; } AstNodeModule* classOrPackagep() const { return m_classOrPackagep; } AstNode* nodep() const { return m_nodep; } string symPrefix() const { return m_symPrefix; } void symPrefix(const string& name) { m_symPrefix = name; } bool exported() const { return m_exported; } void exported(bool flag) { m_exported = flag; } bool imported() const { return m_imported; } void imported(bool flag) { m_imported = flag; } void insert(const string& name, VSymEnt* entp) { UINFO(9, " SymInsert se" << cvtToHex(this) << " '" << name << "' se" << cvtToHex(entp) << " " << entp->nodep() << endl); if (name != "" && m_idNameMap.find(name) != m_idNameMap.end()) { // If didn't already report warning if (!V3Error::errorCount()) { // LCOV_EXCL_START if (debug() >= 9 || V3Error::debugDefault()) dumpSelf(std::cout, "- err-dump: ", 1); entp->nodep()->v3fatalSrc("Inserting two symbols with same name: " << name); } // LCOV_EXCL_STOP } else { m_idNameMap.emplace(name, entp); } } void reinsert(const string& name, VSymEnt* entp) { const auto it = m_idNameMap.find(name); if (name != "" && it != m_idNameMap.end()) { UINFO(9, " SymReinsert se" << cvtToHex(this) << " '" << name << "' se" << cvtToHex(entp) << " " << entp->nodep() << endl); it->second = entp; // Replace } else { insert(name, entp); } } VSymEnt* findIdFlat(const string& name) const { // Find identifier without looking upward through symbol hierarchy // First, scan this begin/end block or module for the name const auto it = m_idNameMap.find(name); UINFO(9, " SymFind se" << cvtToHex(this) << " '" << name << "' -> " << (it == m_idNameMap.end() ? "NONE" : "se" + cvtToHex(it->second) + " n=" + cvtToHex(it->second->nodep())) << endl); if (it != m_idNameMap.end()) return (it->second); return nullptr; } VSymEnt* findIdFallback(const string& name) const { // Find identifier looking upward through symbol hierarchy // First, scan this begin/end block or module for the name if (VSymEnt* const entp = findIdFlat(name)) return entp; // Then scan the upper begin/end block or module for the name if (m_fallbackp) return m_fallbackp->findIdFallback(name); return nullptr; } void candidateIdFlat(VSpellCheck* spellerp, const VNodeMatcher* matcherp) const { // Suggest alternative symbol candidates without looking upward through symbol hierarchy for (IdNameMap::const_iterator it = m_idNameMap.begin(); it != m_idNameMap.end(); ++it) { const AstNode* const itemp = it->second->nodep(); if (itemp && (!matcherp || matcherp->nodeMatch(itemp))) { spellerp->pushCandidate(itemp->prettyName()); } } } void candidateIdFallback(VSpellCheck* spellerp, const VNodeMatcher* matcherp) const { // Suggest alternative symbol candidates with looking upward through symbol hierarchy // Note VSpellCheck wants the most important (closest) items pushed first candidateIdFlat(spellerp, matcherp); // Then suggest the upper begin/end block or module if (m_fallbackp) m_fallbackp->candidateIdFallback(spellerp, matcherp); } private: void importOneSymbol(VSymGraph* graphp, const string& name, const VSymEnt* srcp, bool honorExport) { if ((!honorExport || srcp->exported()) && !findIdFlat(name)) { // Don't insert over existing entry VSymEnt* const symp = new VSymEnt{graphp, srcp}; symp->exported(false); // Can't reimport an import without an export symp->imported(true); reinsert(name, symp); } } void exportOneSymbol(VSymGraph* graphp, const string& name, const VSymEnt* srcp) const { if (srcp->exported()) { if (VSymEnt* const symp = findIdFlat(name)) { // Should already exist in current table if (!symp->exported()) symp->exported(true); } } } public: void importFromClass(VSymGraph* graphp, const VSymEnt* srcp) { // Import tokens from source symbol table into this symbol table // Used for classes in early parsing only to handle "extends" for (IdNameMap::const_iterator it = srcp->m_idNameMap.begin(); it != srcp->m_idNameMap.end(); ++it) { importOneSymbol(graphp, it->first, it->second, false); } } void importFromPackage(VSymGraph* graphp, const VSymEnt* srcp, const string& id_or_star) { // Import tokens from source symbol table into this symbol table if (id_or_star != "*") { const auto it = srcp->m_idNameMap.find(id_or_star); if (it != srcp->m_idNameMap.end()) { importOneSymbol(graphp, it->first, it->second, true); } } else { for (IdNameMap::const_iterator it = srcp->m_idNameMap.begin(); it != srcp->m_idNameMap.end(); ++it) { importOneSymbol(graphp, it->first, it->second, true); } } } void exportFromPackage(VSymGraph* graphp, const VSymEnt* srcp, const string& id_or_star) { // Export tokens from source symbol table into this symbol table if (id_or_star != "*") { const auto it = vlstd::as_const(srcp->m_idNameMap).find(id_or_star); if (it != srcp->m_idNameMap.end()) exportOneSymbol(graphp, it->first, it->second); } else { for (IdNameMap::const_iterator it = srcp->m_idNameMap.begin(); it != srcp->m_idNameMap.end(); ++it) { exportOneSymbol(graphp, it->first, it->second); } } } void exportStarStar(VSymGraph* graphp) { // Export *:*: Export all tokens from imported packages for (IdNameMap::const_iterator it = m_idNameMap.begin(); it != m_idNameMap.end(); ++it) { VSymEnt* const symp = it->second; if (!symp->exported()) symp->exported(true); } } void importFromIface(VSymGraph* graphp, const VSymEnt* srcp, bool onlyUnmodportable = false) { // Import interface tokens from source symbol table into this symbol table, recursively UINFO(9, " importIf se" << cvtToHex(this) << " from se" << cvtToHex(srcp) << endl); for (IdNameMap::const_iterator it = srcp->m_idNameMap.begin(); it != srcp->m_idNameMap.end(); ++it) { const string& name = it->first; VSymEnt* const subSrcp = it->second; const AstVar* const varp = VN_CAST(subSrcp->nodep(), Var); if (!onlyUnmodportable || (varp && varp->isParam())) { VSymEnt* const subSymp = new VSymEnt{graphp, subSrcp}; reinsert(name, subSymp); // And recurse to create children subSymp->importFromIface(graphp, subSrcp); } } } void cellErrorScopes(AstNode* lookp, string prettyName = "") { if (prettyName == "") prettyName = lookp->prettyName(); string scopes; for (IdNameMap::iterator it = m_idNameMap.begin(); it != m_idNameMap.end(); ++it) { AstNode* const itemp = it->second->nodep(); if (VN_IS(itemp, Cell) || (VN_IS(itemp, Module) && VN_AS(itemp, Module)->isTop())) { if (scopes != "") scopes += ", "; scopes += AstNode::prettyName(it->first); } } if (scopes == "") scopes = ""; std::cerr << V3Error::warnMoreStandalone() << "... Known scopes under '" << prettyName << "': " << scopes << endl; if (debug()) dumpSelf(std::cerr, " KnownScope: ", 1); } }; //###################################################################### // Symbol tables class VSymGraph final { // Collection of symbol tables // TYPES using SymStack = std::vector; // MEMBERS VSymEnt* m_symRootp; // Root symbol table SymStack m_symsp; // All symbol tables, to cleanup // CONSTRUCTORS VL_UNCOPYABLE(VSymGraph); VL_DEFINE_DEBUG_FUNCTIONS; public: explicit VSymGraph(AstNetlist* nodep) { m_symRootp = new VSymEnt{this, nodep}; } ~VSymGraph() { for (const VSymEnt* entp : m_symsp) delete entp; } // METHODS VSymEnt* rootp() const { return m_symRootp; } // Debug void dumpSelf(std::ostream& os, const string& indent = "") { VSymConstMap doneSyms; os << "SymEnt Dump:\n"; m_symRootp->dumpIterate(os, doneSyms, indent, 9999, "$root"); bool first = true; for (SymStack::iterator it = m_symsp.begin(); it != m_symsp.end(); ++it) { if (doneSyms.find(*it) == doneSyms.end()) { if (first) { first = false; os << "%%Warning: SymEnt Orphans:\n"; } (*it)->dumpIterate(os, doneSyms, indent, 9999, "Orphan"); } } } void dumpFilePrefixed(const string& nameComment) { if (dumpTreeLevel()) { const string filename = v3Global.debugFilename(nameComment) + ".txt"; UINFO(2, "Dumping " << filename << endl); const std::unique_ptr logp{V3File::new_ofstream(filename)}; if (logp->fail()) v3fatal("Can't write " << filename); dumpSelf(*logp, ""); } } protected: friend class VSymEnt; void pushNewEnt(VSymEnt* entp) { m_symsp.push_back(entp); } }; //###################################################################### inline VSymEnt::VSymEnt(VSymGraph* graphp, AstNode* nodep) : m_nodep(nodep) { // No argument to set fallbackp, as generally it's wrong to set it in the new call, // Instead it needs to be set on a "findOrNew()" return, as it may have been new'ed // by an earlier search insertion. m_fallbackp = nullptr; m_parentp = nullptr; m_classOrPackagep = nullptr; m_exported = true; m_imported = false; graphp->pushNewEnt(this); } inline VSymEnt::VSymEnt(VSymGraph* graphp, const VSymEnt* symp) : m_nodep(symp->m_nodep) { m_fallbackp = symp->m_fallbackp; m_parentp = symp->m_parentp; m_classOrPackagep = symp->m_classOrPackagep; m_exported = symp->m_exported; m_imported = symp->m_imported; graphp->pushNewEnt(this); } #endif // guard