// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Interface typedef capture helper // // Code available from: https://verilator.org // //************************************************************************* // // 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-FileCopyrightText: 2003-2026 Wilson Snyder // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // ARCHITECTURE - Separation of Concerns (do not change without reading): // // The IfaceCapture system has three phases with strict responsibilities: // // 1. CAPTURE (V3LinkDot, primary pass): // add() / addParamType() / addTypedef() record template entries. // Template entries store the REFDTYPE, its cellPath, and the // original paramTypep / typedefp from the template module. // Template entries have cloneCellPath = "". // // 2. CLONE REGISTRATION (V3Param, deepCloneModule): // propagateClone() creates clone entries in the ledger. // ** LEDGER-ONLY - no target lookup, no AST mutation. ** // At this point the cloned module's cells still reference template // interface modules (cell->modp() is stale). Any attempt to walk // cellPath here finds the wrong module. Clone entries store the // cloned REFDTYPE and cloneCellPath but clear paramTypep/typedefp // so that stale template pointers are never carried forward. // // 3. TARGET RESOLUTION (finalizeIfaceCapture, after V3Param): // Runs after all cloning is complete and cell pointers are wired // to the correct interface clones. For each entry, walks cellPath // starting from the stored owner module to find the correct target, // then uses targetKind and the captured name to apply the replacement // without inspecting the REFDTYPE's inherited target pointers. // ** This is the ONLY place that resolves targets and mutates AST. ** // // KEY INVARIANT: The path {ownerModName, refName, cellPath, cloneCellPath} // plus targetKind is the stable identity. Inherited target pointers are // not used for final resolution. // // Template entries have cloneCellPath = ""; clone entries get it set by // propagateClone. TemplateKey (ownerModName, refName, cellPath) matches // all entries regardless of cloneCellPath - used for propagation and debug. // #include "V3LinkDotIfaceCapture.h" #include "V3Error.h" #include "V3Global.h" #include "V3Stats.h" #include "V3SymTable.h" #include #include VL_DEFINE_DEBUG_FUNCTIONS; V3LinkDotIfaceCapture::CapturedMap V3LinkDotIfaceCapture::s_map{}; bool V3LinkDotIfaceCapture::s_enabled = true; // LCOV_EXCL_START void V3LinkDotIfaceCapture::enable(bool flag) { s_enabled = flag; if (!flag) { s_map.clear(); clearModuleCache(); } } // LCOV_EXCL_STOP void V3LinkDotIfaceCapture::reset() { s_map.clear(); clearModuleCache(); } // Per-module cache of statement-level names to avoid O(N*M) linear scans. // Lazily built on first access for a given module; cleared at phase boundaries. // Uses vectors per name to handle rare cases where different node types share a name // (e.g. a Typedef and a ParamTypeDType both named 'sc_tag_status_t'). namespace { struct StmtNameMap final { std::unordered_map> m_byName; }; std::unordered_map s_moduleCache; const StmtNameMap& getOrBuild(AstNodeModule* modp) { auto it = s_moduleCache.find(modp); if (it != s_moduleCache.end()) return it->second; StmtNameMap& cache = s_moduleCache[modp]; for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { const string& nm = stmtp->name(); if (!nm.empty()) cache.m_byName[nm].push_back(stmtp); } return cache; } } // namespace void V3LinkDotIfaceCapture::clearModuleCache() { s_moduleCache.clear(); } AstIfaceRefDType* V3LinkDotIfaceCapture::ifaceRefFromVarDType(AstNodeDType* dtypep) { AstIfaceRefDType* resultp = nullptr; for (AstNodeDType* curp = dtypep; curp;) { if (AstIfaceRefDType* const irefp = VN_CAST(curp, IfaceRefDType)) { resultp = irefp; break; } else if (AstBracketArrayDType* const bracketp = VN_CAST(curp, BracketArrayDType)) { curp = bracketp->subDTypep(); } else if (AstUnpackArrayDType* const unpackp = VN_CAST(curp, UnpackArrayDType)) { curp = unpackp->subDTypep(); } else { v3fatalSrc("ifaceRefFromVarDType: unexpected dtype " << curp->prettyTypeName() << " in chain"); } } return resultp; } namespace { // Resolve the owner module name for a typedef/paramType node. // Returns hint if non-empty, otherwise walks backp() to find the owner module name. string resolveOwnerName(const string& hint, AstNode* nodep) { if (!hint.empty()) return hint; if (!nodep) return ""; AstNodeModule* const ownerp = V3LinkDotIfaceCapture::findOwnerModule(nodep); return ownerp ? ownerp->name() : string{}; } } // namespace AstTypedef* V3LinkDotIfaceCapture::findTypedefInModule(AstNodeModule* modp, const string& name) { AstTypedef* resultp = nullptr; const StmtNameMap& cache = getOrBuild(modp); const auto it = cache.m_byName.find(name); if (!(it == cache.m_byName.end())) { for (AstNode* nodep : it->second) { if (AstTypedef* const tdp = VN_CAST(nodep, Typedef)) { resultp = tdp; break; } } } return resultp; } AstNodeDType* V3LinkDotIfaceCapture::findDTypeInModule(AstNodeModule* modp, const string& name, VNType type) { AstNodeDType* resultp = nullptr; const StmtNameMap& cache = getOrBuild(modp); const auto it = cache.m_byName.find(name); if (!(it == cache.m_byName.end())) { for (AstNode* nodep : it->second) { if (AstNodeDType* const dtp = VN_CAST(nodep, NodeDType)) { if (dtp->type() == type) { resultp = dtp; break; } } } } return resultp; } AstParamTypeDType* V3LinkDotIfaceCapture::findParamTypeInModule(AstNodeModule* modp, const string& name) { AstParamTypeDType* resultp = nullptr; const StmtNameMap& cache = getOrBuild(modp); const auto it = cache.m_byName.find(name); if (!(it == cache.m_byName.end())) { for (AstNode* nodep : it->second) { if (AstParamTypeDType* const ptdp = VN_CAST(nodep, ParamTypeDType)) { resultp = ptdp; break; } } } return resultp; } bool V3LinkDotIfaceCapture::retargetRefToModule(const CapturedEntry& entry, AstNodeModule* targetModp) { if (!entry.refp || !targetModp) return false; if (entry.targetKind == TargetKind::PARAM_TYPE) { AstParamTypeDType* const paramTypep = findParamTypeInModule(targetModp, entry.refp->name()); if (!paramTypep) return false; const auto retarget = [&](AstRefDType* refp) { if (!refp) return; refp->refDTypep(paramTypep); refp->dtypep(paramTypep); }; retarget(entry.refp); for (AstRefDType* const refp : entry.extraRefps) retarget(refp); return true; } AstTypedef* const typedefp = findTypedefInModule(targetModp, entry.refp->name()); if (!typedefp) return false; AstNodeDType* const dtypep = typedefp->subDTypep(); const auto retarget = [&](AstRefDType* refp) { if (!refp) return; refp->typedefp(typedefp); // An incomplete typedef is still a successful name resolution, but // must not erase type links that a later width pass can complete. if (dtypep) { refp->refDTypep(dtypep); refp->dtypep(dtypep); } }; retarget(entry.refp); for (AstRefDType* const refp : entry.extraRefps) retarget(refp); return true; } AstNodeModule* V3LinkDotIfaceCapture::findCloneViaHierarchy(AstNodeModule* containingModp, AstNodeModule* deadTargetModp, int depth) { if (depth > 20) return nullptr; // Safety limit for (AstNode* stmtp = containingModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { if (AstCell* const cellp = VN_CAST(stmtp, Cell)) { AstNodeModule* const cellModp = cellp->modp(); if (!cellModp || cellModp->dead()) continue; // Check if cellModp is a clone of deadTargetModp by comparing // the template name (part before "__") const string& cellModName = cellModp->name(); const string& deadName = deadTargetModp->name(); const size_t pos = cellModName.find("__"); if (pos != string::npos && cellModName.substr(0, pos) == deadName) { return cellModp; } // Recurse into sub-cells AstNodeModule* const found = findCloneViaHierarchy(cellModp, deadTargetModp, depth + 1); if (found) return found; } } return nullptr; } namespace { using LiveNodes = std::unordered_set; // A scoped snapshot of every node currently in the tree. V3Broken::isLinkable() // cannot serve this role: its table is populated only while V3Broken::brokenAll() // runs and is cleared before it returns, and brokenAll() would itself assert on // the dangling cross-links this pass has yet to repair. LiveNodes collectLiveNodes() { LiveNodes liveNodes; v3Global.rootp()->foreach([&](AstNode* nodep) { liveNodes.insert(nodep); }); return liveNodes; } // A live snapshot, when supplied, stops the walk at the first stale back link; // callers without one fall back to the sentinel guard below. AstNodeModule* findOwnerModuleImpl(AstNode* nodep, const LiveNodes* liveNodesp) { for (AstNode* curp = nodep; curp; curp = curp->backp()) { if (liveNodesp) { if (!liveNodesp->count(curp)) return nullptr; } else if (reinterpret_cast(curp) < 0x1000) { // Legacy callers lack a liveness snapshot; retain the existing guard // against sentinel values encountered in corrupted backp() chains. // It cannot prove an arbitrary freed pointer safe - invalidating // ledger entries at deletion time would make it unnecessary. return nullptr; } if (AstNodeModule* const modp = VN_CAST(curp, NodeModule)) return modp; } return nullptr; } AstNodeModule* findOwnerModuleIfLive(AstNode* nodep, const LiveNodes& liveNodes) { return findOwnerModuleImpl(nodep, &liveNodes); } bool moduleMatchesOwner(const AstNodeModule* modp, const string& ownerName) { if (!modp || ownerName.empty()) return false; return modp->name() == ownerName || modp->origName() == ownerName; } } // namespace int V3LinkDotIfaceCapture::fixDeadRefs(AstRefDType* refp, AstNodeModule* containingModp, const char* location, const LiveNodes& liveNodes) { int fixed = 0; // Fix typedefp pointing to dead module AstTypedef* const oldTypedefp = refp->typedefp(); if (oldTypedefp && liveNodes.count(oldTypedefp)) { AstNodeModule* const typedefModp = findOwnerModuleIfLive(oldTypedefp, liveNodes); if (typedefModp && typedefModp->dead()) { AstNodeModule* cloneModp = nullptr; if (containingModp) { cloneModp = findCloneViaHierarchy(containingModp, typedefModp); } if (cloneModp) { const string& tdName = oldTypedefp->name(); if (AstTypedef* const newTdp = findTypedefInModule(cloneModp, tdName)) { UINFO(9, "iface capture finalizeCapture (" << location << "): fixing typedefp refp=" << refp << " dead=" << typedefModp->name() << " -> " << cloneModp->name()); refp->typedefp(newTdp); ++fixed; } } } } // refDTypep is retargeted for captured refs by resolveCapturedRefs, and no // non-captured ref resolves refDTypep into a template that then dies, so it // never survives pointing at a dead module here (verifyNoDeadRefs re-checks). AstNodeDType* const oldRefDTypep = refp->refDTypep(); if (oldRefDTypep && liveNodes.count(oldRefDTypep)) { AstNodeModule* const targetModp = findOwnerModuleIfLive(oldRefDTypep, liveNodes); UASSERT_OBJ(!targetModp || !targetModp->dead(), refp, "refDTypep of '" << refp->prettyNameQ() << "' points to dead module '" << (targetModp ? targetModp->name() : "") << "'"); } // dtypep (checked later by V3Broken) likewise never points at a dead module. AstNodeDType* const oldDTypep = refp->dtypep(); if (oldDTypep && liveNodes.count(oldDTypep)) { AstNodeModule* const dtOwnerp = findOwnerModuleIfLive(oldDTypep, liveNodes); UASSERT_OBJ(!dtOwnerp || !dtOwnerp->dead(), refp, "dtypep of '" << refp->prettyNameQ() << "' points to dead module '" << (dtOwnerp ? dtOwnerp->name() : "") << "'"); } return fixed; } AstNodeModule* V3LinkDotIfaceCapture::findLiveCloneOf(AstNodeModule* deadTargetModp, AstNodeModule** containerp) { for (AstNode* np = v3Global.rootp()->modulesp(); np; np = np->nextp()) { if (AstNodeModule* const modp = VN_CAST(np, NodeModule)) { if (modp->dead()) continue; AstNodeModule* const found = findCloneViaHierarchy(modp, deadTargetModp); if (found) { if (containerp) *containerp = modp; return found; } } } if (containerp) *containerp = nullptr; return nullptr; } AstNodeModule* V3LinkDotIfaceCapture::findOwnerModule(AstNode* nodep) { return findOwnerModuleImpl(nodep, nullptr); } void V3LinkDotIfaceCapture::nullStaleLedgerRefs(const std::unordered_set& live) { for (auto& kv : s_map) { kv.second.foreachLink([&](AstNode*& nodep) { if (nodep && !live.count(nodep)) nodep = nullptr; }); } } void V3LinkDotIfaceCapture::purgeStaleRefs() { if (!s_enabled || s_map.empty() || !v3Global.rootp()) return; // Collect every live AstNode* in the AST so we can detect stale pointers // in the ledger (refp, ownerModp, typedefp, paramTypep, etc.). const LiveNodes liveNodes = collectLiveNodes(); nullStaleLedgerRefs(liveNodes); } void V3LinkDotIfaceCapture::dumpEntries(const string& label) { UINFO(9, "========== iface capture dumpEntries: " << label << " (entries=" << s_map.size() << ") =========="); int idx = 0; for (const auto& pair : s_map) { const CaptureKey& key = pair.first; const CapturedEntry& entry = pair.second; const char* captType = (entry.captureType == CaptureType::IFACE) ? "IFACE" : "CLASS"; UINFO(9, " [" << idx << "] " << captType << " key={" << key.ownerModName << "," << key.refName << "," << key.cellPath << "," << key.cloneCellPath << "}" << " ref=" << (entry.refp ? entry.refp->name() : "") << " refp=" << cvtToHex(entry.refp) << " cellPath='" << entry.cellPath << "'" << " ownerMod=" << (entry.ownerModp ? entry.ownerModp->name() : "") << " typedefp=" << (entry.typedefp ? entry.typedefp->name() : "") << " typedefOwnerModName='" << entry.typedefOwnerModName << "'" << " paramTypep=" << (entry.paramTypep ? entry.paramTypep->name() : "") << " ifacePortVarp=" << (entry.ifacePortVarp ? entry.ifacePortVarp->name() : "")); ++idx; } UINFO(9, "========== end iface capture dumpEntries =========="); } string V3LinkDotIfaceCapture::extractIfacePortName(const string& dotText) { string name = dotText; const size_t dotPos = name.find('.'); if (dotPos != string::npos) name = name.substr(0, dotPos); const size_t braPos = name.find("__BRA__"); if (braPos != string::npos) name = name.substr(0, braPos); return name; } void V3LinkDotIfaceCapture::add(AstRefDType* refp, const string& cellPath, AstNodeModule* ownerModp, AstTypedef* typedefp, const string& typedefOwnerModName, AstVar* ifacePortVarp) { UASSERT(refp, "add() called with null refp"); UASSERT(ownerModp, "add() called with null ownerModp for refp=" << refp->prettyNameQ()); if (!typedefp) typedefp = refp->typedefp(); const string tdOwnerName = resolveOwnerName(typedefOwnerModName, typedefp); const string ownerModName = ownerModp->name(); const CaptureKey key{ownerModName, refp->name(), cellPath, ""}; auto it = s_map.find(key); if (it != s_map.end()) { // Key already exists - append this refp as an extra it->second.extraRefps.push_back(refp); UINFO(9, "iface capture add (extra): refp=" << refp->name() << " cellPath='" << cellPath << "'" << " ownerMod=" << ownerModName << " extraRefps.size=" << it->second.extraRefps.size()); } else { s_map[key] = CapturedEntry{CaptureType::IFACE, TargetKind::TYPEDEF, refp, cellPath, /*cloneCellPath=*/"", /*origClassp=*/nullptr, ownerModp, typedefp, nullptr, tdOwnerName, ifacePortVarp, {}}; UINFO(9, "iface capture add: refp=" << refp->name() << " cellPath='" << cellPath << "'" << " ownerMod=" << ownerModName << " typedefp=" << (typedefp ? typedefp->name() : "") << " typedefOwnerModName='" << tdOwnerName << "'"); } } void V3LinkDotIfaceCapture::addClass(AstRefDType* refp, AstClass* origClassp, AstNodeModule* ownerModp, AstTypedef* typedefp, const string& typedefOwnerModName) { UASSERT(refp, "addClass() called with null refp"); UASSERT(ownerModp, "addClass() called with null ownerModp"); if (!typedefp) typedefp = refp->typedefp(); const string tdOwnerName = resolveOwnerName(typedefOwnerModName, typedefp); // For CLASS captures, use the class name as cellPath UASSERT_OBJ(origClassp, refp, "addClass() called with null origClassp for refp=" << refp); const string cellPath = origClassp->name(); UASSERT_OBJ(!cellPath.empty(), origClassp, "addClass() produced empty cellPath"); const string ownerModName = ownerModp->name(); const CaptureKey key{ownerModName, refp->name(), cellPath, ""}; s_map[key] = CapturedEntry{CaptureType::CLASS, TargetKind::TYPEDEF, refp, cellPath, /*cloneCellPath=*/"", origClassp, ownerModp, typedefp, nullptr, tdOwnerName, nullptr, {}}; UINFO(9, "iface capture addClass: refp=" << refp->name() << " cellPath='" << cellPath << "'" << " ownerMod=" << (ownerModp ? ownerModp->name() : "")); } // Not called in production - retained as a diagnostic/debug entry point // for inspecting the capture ledger by key (e.g. from GDB or future code). const V3LinkDotIfaceCapture::CapturedEntry* // LCOV_EXCL_START V3LinkDotIfaceCapture::find(const CaptureKey& key) { const auto it = s_map.find(key); if (VL_UNLIKELY(it == s_map.end())) return nullptr; return &it->second; } // LCOV_EXCL_STOP const V3LinkDotIfaceCapture::CapturedEntry* V3LinkDotIfaceCapture::find(const AstRefDType* refp) { if (!refp) return nullptr; for (const auto& kv : s_map) { if (kv.second.refp == refp) return &kv.second; } return nullptr; } // Walk a dot-separated cell path through the cell / IFACEREFDTYPE hierarchy // starting from startModp. Returns the module at the end of the path, or // nullptr if any component cannot be resolved. // Cell/port names are preserved across clones by cloneTree, so this works // identically on template and cloned modules. AstNodeModule* V3LinkDotIfaceCapture::followCellPath(AstNodeModule* startModp, const string& cellPath) { if (cellPath.empty() || !startModp) return nullptr; AstNodeModule* curModp = startModp; string remaining = cellPath; while (!remaining.empty() && curModp) { string component; const size_t dotPos = remaining.find('.'); if (dotPos == string::npos) { component = remaining; remaining.clear(); } else { component = remaining.substr(0, dotPos); remaining = remaining.substr(dotPos + 1); } const size_t braPos = component.find("__BRA__"); const string componentBase = (braPos == string::npos) ? component : component.substr(0, braPos); AstNodeModule* nextModp = nullptr; for (AstNode* sp = curModp->stmtsp(); sp; sp = sp->nextp()) { if (AstCell* const cellp = VN_CAST(sp, Cell)) { if ((cellp->name() == component || cellp->name() == componentBase) && cellp->modp()) { nextModp = cellp->modp(); break; } } if (AstVar* const varp = VN_CAST(sp, Var)) { if (varp->isIfaceRef() && varp->subDTypep()) { string varBaseName = varp->name(); const size_t viftopPos = varBaseName.find("__Viftop"); if (viftopPos != string::npos) { varBaseName = varBaseName.substr(0, viftopPos); } if (varBaseName == component || varBaseName == componentBase) { if (AstIfaceRefDType* const irefp = ifaceRefFromVarDType(varp->subDTypep())) { AstIface* const ifacep = irefp->ifaceViaCellp(); if (ifacep) { nextModp = ifacep; break; } } } } } } curModp = nextModp; } return curModp; } // Phase 2: CLONE REGISTRATION - ledger only. // Called from V3Param::deepCloneModule. At this point the cloned module's // cells still reference template interface modules (cell->modp() is stale), // so we MUST NOT walk cellPath or resolve targets here. We only record the // clone entry. Target resolution happens in finalizeIfaceCapture (Phase 3) // after all cell pointers are wired to the correct interface clones. // See header ARCHITECTURE comment for the full picture. void V3LinkDotIfaceCapture::propagateClone(const TemplateKey& tkey, AstRefDType* newRefp, AstNodeModule* newOwnerModp, const string& cloneCellPath) { UASSERT(newRefp, "propagateClone() called with null newRefp"); UASSERT(newOwnerModp, "propagateClone() called with null newOwnerModp"); // Find the template entry by exact key. The entry was captured during // the primary LinkDot pass, so it must exist. If this fires, either the // capture was missed or the key components (ownerModName, refName, // cellPath) diverged between capture and clone time. const CaptureKey templateKey{tkey.ownerModName, tkey.refName, tkey.cellPath, ""}; auto it = s_map.find(templateKey); UASSERT(it != s_map.end(), "propagateClone: no template entry for tkey={" << tkey.ownerModName << "," << tkey.refName << "," << tkey.cellPath << "} cloneCellPath='" << cloneCellPath << "'"); // Create a new clone entry - ledger only. // Target resolution (paramTypep/typedefp) happens in finalizeIfaceCapture // where cell pointers are already wired to the correct interface clones. CapturedEntry newEntry = it->second; newEntry.refp = newRefp; newEntry.ownerModp = newOwnerModp; newEntry.cellPath = tkey.cellPath; newEntry.cloneCellPath = cloneCellPath; newEntry.clearStaleRefs(); const CaptureKey newKey{tkey.ownerModName, tkey.refName, tkey.cellPath, cloneCellPath}; s_map[newKey] = newEntry; UINFO(9, "propagateClone: tkey={" << tkey.ownerModName << "," << tkey.refName << "," << tkey.cellPath << "} refp=" << newRefp->name() << " cloneCellPath='" << cloneCellPath << "'"); } template void V3LinkDotIfaceCapture::forEachImpl(T_FilterFn&& filter, T_Fn&& fn) { if (s_map.empty()) return; std::vector keys; keys.reserve(s_map.size()); for (const auto& kv : s_map) keys.push_back(kv.first); for (const CaptureKey& key : keys) { const auto it = s_map.find(key); if (it == s_map.end()) continue; const CapturedEntry& entry = it->second; if (!filter(entry)) continue; fn(entry); } } void V3LinkDotIfaceCapture::forEach(const std::function& fn) { if (!fn || s_map.empty()) return; forEachImpl([](const CapturedEntry&) { return true; }, fn); } void V3LinkDotIfaceCapture::forEachOwned(const AstNodeModule* ownerModp, const std::function& fn) { if (!ownerModp || !fn || s_map.empty()) return; const string ownerName = ownerModp->name(); UINFO(9, "iface capture forEachOwned: ownerModp=" << ownerName << " map size=" << s_map.size()); forEachImpl( [ownerModp, &ownerName](const CapturedEntry& e) { // Only match template entries (cloneCellPath=''). // Clone entries are created by propagateClone and must not be // re-processed - each clone gets its own target independently. if (!e.cloneCellPath.empty()) return false; // Match by ownerModp pointer or typedefOwnerModName string const bool matches = e.ownerModp == ownerModp || e.typedefOwnerModName == ownerName; UINFO(9, "iface capture forEachOwned filter: ref=" << (e.refp ? e.refp->name() : "") << " cellPath='" << e.cellPath << "' ownerMod=" << (e.ownerModp ? e.ownerModp->name() : "") << " typedefOwnerModName='" << e.typedefOwnerModName << "' matches=" << matches); return matches; }, fn); } // replaces the lambda used in V3LinkDot.cpp for iface capture void V3LinkDotIfaceCapture::captureTypedefContext(AstRefDType* refp, const char* stageLabel, int dotPos, bool /*dotIsFinal*/, const std::string& dotText, VSymEnt* dotSymp, VSymEnt* curSymp, AstNodeModule* modp, AstNode* /*nodep*/, const std::function& indentFn) { if (!enabled() || !refp) return; UINFO(9, indentFn() << "iface capture capture request stage=" << stageLabel << " typedef=" << refp << " name=" << refp->name() << " dotPos=" << dotPos << " dotText='" << dotText << "' dotSym=" << dotSymp); const AstCell* ifaceCellp = nullptr; if (dotSymp && VN_IS(dotSymp->nodep(), Cell)) { const AstCell* const cellp = VN_AS(dotSymp->nodep(), Cell); if (cellp->modp() && VN_IS(cellp->modp(), Iface)) ifaceCellp = cellp; } if (!ifaceCellp) { UINFO(9, indentFn() << "iface capture capture skipped typedef=" << refp << " (no iface context)"); return; } // Skip internal interface typedef references (typedef used within its own interface) if (ifaceCellp->modp() == modp) { UINFO(9, indentFn() << "iface capture capture skipped typedef=" << refp << " (internal to interface " << modp->name() << ")"); return; } // dotText is always non-empty for interface typedef captures. If this // fires, the caller resolved to an interface Cell but did not accumulate // a dotText path - investigate the dot-state in visitParseRef. UASSERT_OBJ(!dotText.empty(), refp, "captureTypedefContext: dotText empty"); const string cellPath = dotText; AstVar* ifacePortVarp = nullptr; if (curSymp) { const std::string portName = extractIfacePortName(dotText); if (VSymEnt* const portSymp = curSymp->findIdFallback(portName)) { ifacePortVarp = VN_CAST(portSymp->nodep(), Var); UINFO(9, indentFn() << "iface capture found port var '" << portName << "' -> " << ifacePortVarp); } } // Check if refDTypep is a ParamTypeDType - if so, use addParamType instead of add if (AstParamTypeDType* const paramTypep = VN_CAST(refp->refDTypep(), ParamTypeDType)) { V3LinkDotIfaceCapture::addParamType(refp, cellPath, modp, paramTypep, "", ifacePortVarp); } else { V3LinkDotIfaceCapture::add(refp, cellPath, modp, refp->typedefp(), "", ifacePortVarp); } UINFO(9, indentFn() << "iface capture capture success typedef=" << refp << " cell=" << ifaceCellp << " cellPath='" << cellPath << "'" << " mod=" << (ifaceCellp->modp() ? ifaceCellp->modp()->name() : "") << " dotPos=" << dotPos); // Note: the enclosingVar walk + promoteVarToParamType callback was removed. // It supported 'localparam xyz_t = iface.rq_t;' without the 'type' keyword, // which was never valid SystemVerilog. CI-CD with v3fatalSrc asserts on // the promoteVarCb path and replaceRef confirmed this was dead code. } void V3LinkDotIfaceCapture::captureInnerParamTypeRefs(AstParamTypeDType* paramTypep, AstRefDType* refp, const string& cellPath, const string& ownerModName, const string& ptOwnerName) { if (!paramTypep) return; paramTypep->foreach([&](AstRefDType* innerRefp) { if (innerRefp == refp) return; if (!innerRefp->refDTypep()) return; AstNodeModule* const refOwnerModp = findOwnerModule(innerRefp->refDTypep()); if (refOwnerModp && VN_IS(refOwnerModp, Iface) && refOwnerModp->name() != ptOwnerName) { AstNodeModule* const innerOwnerModp = findOwnerModule(innerRefp); const string innerOwnerName = innerOwnerModp ? innerOwnerModp->name() : ownerModName; const CaptureKey innerKey{innerOwnerName, innerRefp->name(), cellPath, ""}; if (s_map.find(innerKey) == s_map.end()) { // Find the cell name for the nested interface string nestedCellName; AstNodeModule* const ptOwnerModp = findOwnerModule(paramTypep); if (ptOwnerModp) { for (AstNode* stmtp = ptOwnerModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { if (AstCell* const cp = VN_CAST(stmtp, Cell)) { if (cp->modp() == refOwnerModp) { nestedCellName = cp->name(); break; } } } } if (VL_UNCOVERABLE(nestedCellName.empty())) { // The nested interface cell should always be found in the // owner module's statements. If this fires, either // ptOwnerModp is wrong (findOwnerModule returned the wrong // module) or the cell was pruned before capture. v3fatalSrc("captureInnerParamTypeRefs: could not find cell for nested iface '" << refOwnerModp->prettyNameQ() << "' in '" << (ptOwnerModp ? ptOwnerModp->prettyNameQ() : "") << "'"); } UINFO(9, "addParamType: also capturing inner RefDType " << innerRefp << " refDTypep owner=" << refOwnerModp->name() << " nestedCellName='" << nestedCellName << "'"); s_map[innerKey] = CapturedEntry{CaptureType::IFACE, TargetKind::TYPEDEF, innerRefp, nestedCellName.empty() ? cellPath : nestedCellName, /*cloneCellPath=*/"", /*origClassp=*/nullptr, ptOwnerModp, innerRefp->typedefp(), nullptr, refOwnerModp->name(), nullptr, {}}; } } }); } void V3LinkDotIfaceCapture::addParamType(AstRefDType* refp, const string& cellPath, AstNodeModule* ownerModp, AstParamTypeDType* paramTypep, const string& paramTypeOwnerModName, AstVar* ifacePortVarp) { UASSERT(refp, "addParamType() called with null refp"); UASSERT(ownerModp, "addParamType() called with null ownerModp for refp='" << refp->prettyNameQ() << "'"); UASSERT_OBJ(paramTypep, refp, "addParamType() called with null paramTypep for refp='" << refp->prettyNameQ() << "'"); const string ptOwnerName = resolveOwnerName(paramTypeOwnerModName, paramTypep); UINFO(9, "addParamType: refp=" << refp << " cellPath='" << cellPath << "'" << " ownerModp=" << (ownerModp ? ownerModp->name() : "") << " paramTypep=" << paramTypep << " paramTypeOwnerModName='" << ptOwnerName << "'"); if (paramTypep) { UINFO(9, "addParamType: paramTypep subDTypep chain:"); paramTypep->foreach([&](AstRefDType* innerRefp) { UINFO(9, " inner RefDType: " << innerRefp << " refDTypep=" << innerRefp->refDTypep() << (innerRefp->refDTypep() ? " refDTypep->name=" : "") << (innerRefp->refDTypep() ? innerRefp->refDTypep()->prettyTypeName() : "")); }); } const string ownerModName = ownerModp->name(); const CaptureKey key{ownerModName, refp->name(), cellPath, ""}; auto it = s_map.find(key); if (it != s_map.end()) { // Key already exists - append this refp as an extra it->second.extraRefps.push_back(refp); UINFO(9, "addParamType (extra): refp=" << refp->name() << " cellPath='" << cellPath << "'" << " ownerMod=" << ownerModName << " extraRefps.size=" << it->second.extraRefps.size()); } else { s_map[key] = CapturedEntry{CaptureType::IFACE, TargetKind::PARAM_TYPE, refp, cellPath, /*cloneCellPath=*/"", /*origClassp=*/nullptr, ownerModp, nullptr, paramTypep, ptOwnerName, ifacePortVarp, {}}; } // Also capture REFDTYPEs inside the PARAMTYPEDTYPE's subDTypep chain. captureInnerParamTypeRefs(paramTypep, refp, cellPath, ownerModName, ptOwnerName); } // Visitor that fixes dead references in the global type table. // // When interface templates are cloned, REFDTYPEs in the global type table may // still point to the dead template module. This visitor traverses the type // table and redirects those references to the appropriate live clone. // // Handles both AstRefDType (direct typedef references) and AstMemberDType // (struct/union member types) in a single traversal for efficiency. class TypeTableDeadRefVisitor final : public VNVisitor { const LiveNodes& m_liveNodes; int m_fixed = 0; void visit(AstRefDType* refp) override { iterateChildren(refp); // For type table entries, find the first live module that contains // a cell hierarchy leading to the dead target AstNodeModule* containingModp = nullptr; AstNodeModule* deadTargetModp = nullptr; // Check BOTH typedefp and refDTypep for dead owners. // Either (or both) may point to a dead module. if (refp->typedefp() && m_liveNodes.count(refp->typedefp())) { AstNodeModule* const tdOwnerp = findOwnerModuleIfLive(refp->typedefp(), m_liveNodes); if (tdOwnerp && tdOwnerp->dead()) deadTargetModp = tdOwnerp; } if (!deadTargetModp && refp->refDTypep() && m_liveNodes.count(refp->refDTypep())) { AstNodeModule* const rdOwnerp = findOwnerModuleIfLive(refp->refDTypep(), m_liveNodes); if (rdOwnerp && rdOwnerp->dead()) deadTargetModp = rdOwnerp; } if (deadTargetModp) { V3LinkDotIfaceCapture::findLiveCloneOf(deadTargetModp, &containingModp); } m_fixed += V3LinkDotIfaceCapture::fixDeadRefs(refp, containingModp, "type table", m_liveNodes); } void visit(AstMemberDType* memberp) override { iterateChildren(memberp); if (!memberp->dtypep()) return; UASSERT_OBJ(m_liveNodes.count(memberp->dtypep()), memberp, "MemberDType has a dangling dtypep"); AstNodeModule* const dtOwnerp = findOwnerModuleIfLive(memberp->dtypep(), m_liveNodes); if (!dtOwnerp || !dtOwnerp->dead()) return; // Try to find the clone of the dead module AstNodeModule* const cloneModp = V3LinkDotIfaceCapture::findLiveCloneOf(dtOwnerp); if (cloneModp) { // Find matching type by name in the clone const string& dtName = memberp->dtypep()->prettyName(); // Try typedef children for (AstNode* sp = cloneModp->stmtsp(); sp; sp = sp->nextp()) { if (AstTypedef* const tdp = VN_CAST(sp, Typedef)) { if (tdp->subDTypep() && tdp->subDTypep()->prettyName() == dtName) { UINFO(9, "iface capture type table MEMBERDTYPE fixup (via typedef): " << memberp->name() << " dtypep " << dtOwnerp->name() << " -> " << cloneModp->name()); memberp->dtypep(tdp->subDTypep()); ++m_fixed; return; } } } } // One of the above fixup paths (prettyName or typedef) always succeeds // when cloneModp is found. If this fires, either cloneModp is null // (findLiveCloneOf failed - check that the dead template has a clone) // or the dtype name doesn't match any statement in the clone (check // memberp->dtypep()->prettyName() against cloneModp's statements). v3fatalSrc("MemberDType fixup: could not fix member '" << memberp->name() << "' dtypep points to dead " << dtOwnerp->name() << " cloneModp=" << (cloneModp ? cloneModp->name() : "")); } void visit(AstNode* nodep) override { iterateChildren(nodep); } public: int fixed() const { return m_fixed; } TypeTableDeadRefVisitor(AstNode* nodep, const LiveNodes& liveNodes) : m_liveNodes{liveNodes} { iterate(nodep); } }; int V3LinkDotIfaceCapture::fixDeadRefsInTypeTable(const LiveNodes& liveNodes) { if (!v3Global.rootp()->typeTablep()) return 0; const TypeTableDeadRefVisitor visitor{v3Global.rootp()->typeTablep(), liveNodes}; return visitor.fixed(); } int V3LinkDotIfaceCapture::fixDeadRefsInModules(const LiveNodes& liveNodes) { int fixed = 0; for (AstNode* nodep = v3Global.rootp()->modulesp(); nodep; nodep = nodep->nextp()) { if (AstNodeModule* const modp = VN_CAST(nodep, NodeModule)) { if (modp->dead()) continue; const string modName = modp->name(); modp->foreach([&](AstRefDType* refp) { fixed += fixDeadRefs(refp, modp, modName.c_str(), liveNodes); }); } } return fixed; } int V3LinkDotIfaceCapture::resolveCapturedRefs() { int fixed = 0; // TARGET RESOLUTION - the ONLY place that resolves targets and // mutates AST. By this point all cloning is complete and cell pointers // are wired to the correct interface clones. For each entry we walk // cellPath from the owner module to find the correct target module, then // locate the PARAMTYPEDTYPE / TYPEDEF by name. // See V3LinkDotIfaceCapture.h ARCHITECTURE comment for the full picture. forEach([&](const CapturedEntry& entry) { AstRefDType* const refp = entry.refp; if (!refp) return; // Parameterized class typedefs are relinked by // ParamClassRefDTypeRelinkVisitor. Their class name is not a cell path. if (entry.captureType == CaptureType::CLASS) return; AstNodeModule* const ownerModp = entry.ownerModp; if (!ownerModp || ownerModp->dead() || VN_IS(ownerModp, Package)) return; UINFO(9, "finalizeIfaceCapture Phase3 entry: refp=" << refp->name() << " (" << cvtToHex(refp) << ")" << " ownerMod=" << ownerModp->name() << " (dead=" << ownerModp->dead() << ")" << " storedOwnerMod=" << (entry.ownerModp ? entry.ownerModp->name() : "") << " cellPath='" << entry.cellPath << "' cloneCellPath='" << entry.cloneCellPath << "' targetKind=" << (entry.targetKind == TargetKind::PARAM_TYPE ? "param type" : "typedef")); // Prefer the owner itself when its stable template identity matches the // captured target owner. Otherwise resolve and validate the cell path. AstNodeModule* correctModp = nullptr; if (moduleMatchesOwner(ownerModp, entry.typedefOwnerModName)) { correctModp = ownerModp; } else { // A non-matching owner always carries a cell path to the target owner. UASSERT_OBJ(!entry.cellPath.empty(), refp, "captured ref '" << refp->prettyNameQ() << "' owner '" << ownerModp->prettyNameQ() << "' does not match target owner '" << entry.typedefOwnerModName << "' and has no cell path"); correctModp = followCellPath(ownerModp, entry.cellPath); UINFO(9, " followCellPath('" << ownerModp->name() << "', '" << entry.cellPath << "') = " << (correctModp ? correctModp->name() : "") << (correctModp ? (correctModp->dead() ? " (DEAD)" : " (live)") : "")); UASSERT_OBJ(correctModp && !correctModp->dead() && moduleMatchesOwner(correctModp, entry.typedefOwnerModName), refp, "captured ref '" << refp->prettyNameQ() << "' cell path '" << entry.cellPath << "' did not resolve to live owner '" << entry.typedefOwnerModName << "'"); } UASSERT_OBJ( retargetRefToModule(entry, correctModp), refp, "could not retarget captured " << (entry.targetKind == TargetKind::PARAM_TYPE ? "parameter type " : "typedef ") << refp->prettyNameQ() << " in " << correctModp->prettyNameQ()); refp->user3(true); ++fixed; }); return fixed; } void V3LinkDotIfaceCapture::verifyNoDeadRefs(const LiveNodes& liveNodes) { // Assert: no REFDTYPE in any live module should have typedefp or refDTypep // pointing to a dead module. for (AstNode* nodep = v3Global.rootp()->modulesp(); nodep; nodep = nodep->nextp()) { if (AstNodeModule* const modp = VN_CAST(nodep, NodeModule)) { if (modp->dead()) continue; modp->foreach([&](AstRefDType* refp) { if (refp->typedefp()) { UASSERT_OBJ(liveNodes.count(refp->typedefp()), refp, "REFDTYPE '" << refp->prettyNameQ() << "' in live module '" << modp->prettyNameQ() << "' has a dangling typedefp"); AstNodeModule* const ownerModp = findOwnerModuleIfLive(refp->typedefp(), liveNodes); UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, "REFDTYPE '" << refp->prettyNameQ() << "' in live module '" << modp->prettyNameQ() << "' has typedefp pointing to dead module '" << ownerModp->prettyNameQ() << "'"); } if (refp->refDTypep()) { UASSERT_OBJ(liveNodes.count(refp->refDTypep()), refp, "REFDTYPE '" << refp->prettyNameQ() << "' in live module '" << modp->prettyNameQ() << "' has a dangling refDTypep"); AstNodeModule* const ownerModp = findOwnerModuleIfLive(refp->refDTypep(), liveNodes); UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, "REFDTYPE '" << refp->prettyNameQ() << "' in live module '" << modp->prettyNameQ() << "' has refDTypep pointing to dead module '" << ownerModp->prettyNameQ() << "'"); } }); } } if (v3Global.rootp()->typeTablep()) { for (AstNode* nodep = v3Global.rootp()->typeTablep()->typesp(); nodep; nodep = nodep->nextp()) { nodep->foreach([&](AstRefDType* refp) { if (refp->typedefp()) { UASSERT_OBJ(liveNodes.count(refp->typedefp()), refp, "REFDTYPE '" << refp->prettyNameQ() << "' in type table has a dangling typedefp"); AstNodeModule* const ownerModp = findOwnerModuleIfLive(refp->typedefp(), liveNodes); UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, "REFDTYPE '" << refp->prettyNameQ() << "' in type table has typedefp pointing to dead module '" << ownerModp->prettyNameQ() << "'"); } if (refp->refDTypep()) { UASSERT_OBJ(liveNodes.count(refp->refDTypep()), refp, "REFDTYPE '" << refp->prettyNameQ() << "' in type table has a dangling refDTypep"); AstNodeModule* const ownerModp = findOwnerModuleIfLive(refp->refDTypep(), liveNodes); UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, "REFDTYPE '" << refp->prettyNameQ() << "' in type table has refDTypep pointing to dead module '" << ownerModp->prettyNameQ() << "'"); } }); } } } void V3LinkDotIfaceCapture::finalizeIfaceCapture() { if (!s_enabled) return; UINFO(4, "finalizeIfaceCapture: fixing remaining cross-interface refs"); if (!v3Global.rootp()) return; clearModuleCache(); // Ensure fresh view after all cloning/widthing // purgeStaleRefs() snapshotted liveness at the end of V3Param::param() and // discarded it; linkDotParamed, finalizeDeferredParams, linkLValue and linkWith // have since mutated the tree. Take a fresh snapshot and reuse it here both to // purge the ledger and to guard every legacy cross-link inspection. const LiveNodes liveNodes = collectLiveNodes(); nullStaleLedgerRefs(liveNodes); // Resolve live captured refs from stable path metadata before inspecting // any inherited target pointers, which may refer to replaced template nodes. const int capturedFixed = resolveCapturedRefs(); UINFO(4, "finalizeIfaceCapture: structurally resolved " << capturedFixed << " captured refs"); const int typeTableFixed = fixDeadRefsInTypeTable(liveNodes); const int moduleFixed = fixDeadRefsInModules(liveNodes); UINFO(4, "finalizeIfaceCapture: fixed " << typeTableFixed << " in type table, " << moduleFixed << " in modules (dead refs)"); if (debug() >= 9) dumpEntries("after finalizeIfaceCapture"); // Emit statistics for --stats int templates = 0; int clones = 0; for (const auto& kv : s_map) { if (kv.first.cloneCellPath.empty()) { ++templates; } else { ++clones; } } V3Stats::addStat("IfaceCapture, Entries total", s_map.size()); V3Stats::addStat("IfaceCapture, Entries template", templates); V3Stats::addStat("IfaceCapture, Entries cloned", clones); V3Stats::addStat("IfaceCapture, Dead refs fixed in type table", typeTableFixed); V3Stats::addStat("IfaceCapture, Dead refs fixed in modules", moduleFixed); V3Stats::addStat("IfaceCapture, Captured refs resolved", capturedFixed); // Independent debug-only audit of the repairs above; kept separate from the // repair traversal so it can catch omissions in that repair. if (debug() >= 9) verifyNoDeadRefs(liveNodes); reset(); }