Internals: Make V3Broken AllocTable thread safe (#6441)

Added a lock around the table used to detect memory leaks. Note the only
part that is multi-threaded at the moment is V3Emit where we create
AstCFiles and the like, so the lock should be almost always uncontested.
This commit is contained in:
Geza Lore 2025-09-16 21:40:38 +02:00 committed by GitHub
parent b60ad953c4
commit ad530c4b08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 16 additions and 5 deletions

View File

@ -60,29 +60,38 @@ static bool s_brokenAllowMidvisitorCheck = false;
// Table of allocated AstNode pointers // Table of allocated AstNode pointers
static class AllocTable final { static class AllocTable final {
friend class V3Broken;
// MEMBERS // MEMBERS
std::unordered_set<const AstNode*> m_allocated; // Set of all nodes allocated but not freed V3Mutex m_mutex; // Mutex for m_allocated
// Set of all nodes allocated but not freed
std::unordered_set<const AstNode*> m_allocated VL_GUARDED_BY(m_mutex);
public: public:
// METHODS // METHODS
void addNewed(const AstNode* nodep) { void addNewed(const AstNode* nodep) VL_MT_SAFE_EXCLUDES(m_mutex) {
// Called by operator new on any node - only if VL_LEAK_CHECKS // Called by operator new on any node - only if VL_LEAK_CHECKS
// LCOV_EXCL_START // LCOV_EXCL_START
V3LockGuard lock{m_mutex};
if (VL_UNCOVERABLE(!m_allocated.emplace(nodep).second)) { if (VL_UNCOVERABLE(!m_allocated.emplace(nodep).second)) {
nodep->v3fatalSrc("Newing AstNode object that is already allocated"); nodep->v3fatalSrc("Newing AstNode object that is already allocated");
} }
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
void deleted(const AstNode* nodep) { void deleted(const AstNode* nodep) VL_MT_SAFE_EXCLUDES(m_mutex) {
// Called by operator delete on any node - only if VL_LEAK_CHECKS // Called by operator delete on any node - only if VL_LEAK_CHECKS
// LCOV_EXCL_START // LCOV_EXCL_START
V3LockGuard lock{m_mutex};
if (VL_UNCOVERABLE(m_allocated.erase(nodep) == 0)) { if (VL_UNCOVERABLE(m_allocated.erase(nodep) == 0)) {
nodep->v3fatalSrc("Deleting AstNode object that was not allocated or already freed"); nodep->v3fatalSrc("Deleting AstNode object that was not allocated or already freed");
} }
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
bool isAllocated(const AstNode* nodep) const { return m_allocated.count(nodep) != 0; }
void checkForLeaks() { private: // for V3Broken only
bool isAllocated(const AstNode* nodep) const VL_REQUIRES(m_mutex) {
return m_allocated.count(nodep) != 0;
}
void checkForLeaks() VL_REQUIRES(m_mutex) {
if (!v3Global.opt.debugCheck()) return; if (!v3Global.opt.debugCheck()) return;
const uint8_t brokenCntCurrent = s_brokenCntGlobal.get(); const uint8_t brokenCntCurrent = s_brokenCntGlobal.get();
@ -340,6 +349,8 @@ void V3Broken::brokenAll(AstNetlist* nodep) {
} else { } else {
inBroken = true; inBroken = true;
V3LockGuard lock{s_allocTable.m_mutex};
// Mark every node in the tree // Mark every node in the tree
const uint8_t brokenCntCurrent = s_brokenCntGlobal.get(); const uint8_t brokenCntCurrent = s_brokenCntGlobal.get();
nodep->foreach([brokenCntCurrent](AstNode* nodep) { nodep->foreach([brokenCntCurrent](AstNode* nodep) {