// -*- 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): // // Each captured REFDTYPE carries its own capture record (a VIfaceCaptureTag, // see AstRefDType::captureTagp): the cell path from its owner module to the // interface, the interface's name, and whether the target is a typedef or a // parameter type. Nothing here points into the AST, so a freed reference // takes its record with it, and a cloned reference shares it. // // 1. CAPTURE (V3LinkDot, primary pass): // captureTypedefContext() / addParamType() tag the reference. // // 2. PARAMETERIZATION (V3Param): // After a module is cloned, and after an interface cell is specialized, // V3Param walks that one module and retargets its tagged references by // cell path, so widths computed during parameterization use the // specialized interface. // // 3. TARGET RESOLUTION (finalizeIfaceCapture, after V3Param): // Walks every tagged reference in a live module, follows its cell path // from its owner module and retargets it by name, then clears all tags. // #include "V3LinkDotIfaceCapture.h" #include "V3Error.h" #include "V3Global.h" #include "V3Stats.h" #include "V3SymTable.h" #include #include VL_DEFINE_DEBUG_FUNCTIONS; std::deque V3LinkDotIfaceCapture::s_tags{}; bool V3LinkDotIfaceCapture::s_enabled = true; // LCOV_EXCL_START void V3LinkDotIfaceCapture::enable(bool flag) { s_enabled = flag; if (!flag) clearModuleCache(); } // LCOV_EXCL_STOP void V3LinkDotIfaceCapture::reset() { s_tags.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(AstRefDType* refp, AstNodeModule* targetModp) { const VIfaceCaptureTag* const tagp = refp->captureTagp(); UASSERT_OBJ(tagp, refp, "Retarget of a reference that was not captured"); UASSERT_OBJ(targetModp, refp, "Retarget to a null module"); if (tagp->m_kind == VIfaceCaptureTag::Kind::PARAM_TYPE) { AstParamTypeDType* const paramTypep = findParamTypeInModule(targetModp, refp->name()); if (!paramTypep) return false; retargetRefToParamType(refp, paramTypep); return true; } AstTypedef* const typedefp = findTypedefInModule(targetModp, refp->name()); if (!typedefp) return false; retargetRefToTypedef(refp, typedefp); return true; } void V3LinkDotIfaceCapture::retargetRefToParamType(AstRefDType* refp, AstParamTypeDType* paramTypep) { UASSERT_OBJ(paramTypep, refp, "Retarget to a null parameter type"); refp->refDTypep(paramTypep); refp->dtypep(paramTypep); } void V3LinkDotIfaceCapture::retargetRefToTypedef(AstRefDType* refp, AstTypedef* typedefp) { UASSERT_OBJ(typedefp, refp, "Retarget to a null typedef"); 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 (AstNodeDType* const dtypep = typedefp->subDTypep()) { refp->refDTypep(dtypep); refp->dtypep(dtypep); } } 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. LiveNodes collectLiveNodes() { LiveNodes liveNodes; v3Global.rootp()->foreach([&](AstNode* nodep) { liveNodes.insert(nodep); }); return liveNodes; } // Find the module that owns this node; a snapshot, if given, stops the walk at a stale link. AstNodeModule* findOwnerModuleImpl(AstNode* nodep, const LiveNodes* liveNodesp) { for (AstNode* curp = nodep; curp; curp = curp->backp()) { if (liveNodesp && !liveNodesp->count(curp)) 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 AstNodeModule* V3LinkDotIfaceCapture::findOwnerModule(AstNode* nodep) { return findOwnerModuleImpl(nodep, nullptr); } void V3LinkDotIfaceCapture::dumpEntries(const string& label) { UINFO(9, "========== iface capture dumpEntries: " << label << " =========="); int idx = 0; v3Global.rootp()->foreach([&](AstRefDType* refp) { const VIfaceCaptureTag* const tagp = captureTag(refp); if (!tagp) return; const AstNodeModule* const ownerModp = findOwnerModule(refp); UINFO(9, " [" << idx << "] ref=" << refp->name() << " refp=" << cvtToHex(refp) << " ownerMod=" << (ownerModp ? ownerModp->name() : "") << " cellPath='" << tagp->m_cellPath << "' ownerModName='" << tagp->m_ownerModName << "' kind=" << (tagp->m_kind == VIfaceCaptureTag::Kind::PARAM_TYPE ? "param type" : "typedef")); ++idx; }); UINFO(9, "========== end iface capture dumpEntries (" << idx << " captured) =========="); } void V3LinkDotIfaceCapture::tag(AstRefDType* refp, const AstNodeModule* capturedInp, VIfaceCaptureTag::Kind kind, const string& cellPath, const string& ownerModName) { UASSERT_OBJ(!refp->captureTagp(), refp, "Reference captured twice"); UASSERT_OBJ(capturedInp, refp, "Captured reference is not in a module"); UASSERT_OBJ(!cellPath.empty(), refp, "Captured reference has no cell path"); s_tags.emplace_back(VIfaceCaptureTag{kind, cellPath, ownerModName, capturedInp->origName()}); refp->captureTagp(&s_tags.back()); } const VIfaceCaptureTag* V3LinkDotIfaceCapture::captureTag(AstRefDType* refp) { const VIfaceCaptureTag* const tagp = refp->captureTagp(); if (!tagp) return nullptr; const AstNodeModule* const ownerp = findOwnerModule(refp); if (!ownerp || ownerp->origName() != tagp->m_capturedInName) return nullptr; return tagp; } // 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); } // Matched without any array index; the elements of an instance array (see V3Param) all // instantiate the same module, so any of them will do const string componentBase = AstNode::nameNoArray(component); AstNodeModule* nextModp = nullptr; for (AstNode* sp = curModp->stmtsp(); sp; sp = sp->nextp()) { if (AstCell* const cellp = VN_CAST(sp, Cell)) { if ((cellp->name() == component || AstNode::nameNoArray(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 || AstNode::nameNoArray(varBaseName) == componentBase) { if (AstIfaceRefDType* const irefp = ifaceRefFromVarDType(varp->subDTypep())) { AstIface* const ifacep = irefp->ifaceViaCellp(); if (ifacep) { nextModp = ifacep; break; } } } } } } curModp = nextModp; } return curModp; } // replaces the lambda used in V3LinkDot.cpp for iface capture void V3LinkDotIfaceCapture::captureTypedefContext(AstRefDType* refp, const char* stageLabel, int dotPos, const std::string& dotText, VSymEnt* dotSymp, AstNodeModule* modp, 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; // A reference to a ParamTypeDType is captured as a parameter type, else as a typedef if (AstParamTypeDType* const paramTypep = VN_CAST(refp->refDTypep(), ParamTypeDType)) { V3LinkDotIfaceCapture::addParamType(refp, cellPath, modp, paramTypep, ""); } else { tag(refp, modp, VIfaceCaptureTag::Kind::TYPEDEF, cellPath, resolveOwnerName("", refp->typedefp())); } UINFO(9, indentFn() << "iface capture capture success typedef=" << refp << " cell=" << ifaceCellp << " cellPath='" << cellPath << "'" << " mod=" << (ifaceCellp->modp() ? ifaceCellp->modp()->name() : "") << " dotPos=" << dotPos); } void V3LinkDotIfaceCapture::addParamType(AstRefDType* refp, const string& cellPath, AstNodeModule* ownerModp, AstParamTypeDType* paramTypep, const string& paramTypeOwnerModName) { 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 << "'"); UINFO(9, "addParamType: paramTypep subDTypep chain:"); if (debug()) paramTypep->foreach([&](AstRefDType* innerRefp) { UINFO(9, " inner RefDType: " << innerRefp << " refDTypep=" << innerRefp->refDTypep() << (innerRefp->refDTypep() ? " refDTypep->name=" : "") << (innerRefp->refDTypep() ? innerRefp->refDTypep()->prettyTypeName() : "")); }); tag(refp, ownerModp, VIfaceCaptureTag::Kind::PARAM_TYPE, cellPath, ptOwnerName); } int V3LinkDotIfaceCapture::resolveCapturedRefs() { int fixed = 0; // TARGET RESOLUTION. By this point all cloning is complete and cell // pointers are wired to the correct interface clones. For each tagged // reference we walk its cell path from its owner module to find the // correct target module, then locate the PARAMTYPEDTYPE / TYPEDEF by name. // See the ARCHITECTURE comment above for the full picture. v3Global.rootp()->foreach([&](AstRefDType* refp) { const VIfaceCaptureTag* const tagp = captureTag(refp); if (!tagp) return; AstNodeModule* const ownerModp = findOwnerModule(refp); if (!ownerModp || ownerModp->dead() || VN_IS(ownerModp, Package)) return; UINFO(9, "finalizeIfaceCapture Phase3 entry: refp=" << refp->name() << " (" << cvtToHex(refp) << ")" << " ownerMod=" << ownerModp->name() << " cellPath='" << tagp->m_cellPath << "' targetKind=" << (tagp->m_kind == VIfaceCaptureTag::Kind::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, tagp->m_ownerModName)) { correctModp = ownerModp; } else { correctModp = followCellPath(ownerModp, tagp->m_cellPath); UINFO(9, " followCellPath('" << ownerModp->name() << "', '" << tagp->m_cellPath << "') = " << (correctModp ? correctModp->name() : "") << (correctModp ? (correctModp->dead() ? " (DEAD)" : " (live)") : "")); UASSERT_OBJ(correctModp && !correctModp->dead() && moduleMatchesOwner(correctModp, tagp->m_ownerModName), refp, "captured ref '" << refp->prettyNameQ() << "' cell path '" << tagp->m_cellPath << "' did not resolve to live owner '" << tagp->m_ownerModName << "'"); } UASSERT_OBJ(retargetRefToModule(refp, correctModp), refp, "could not retarget captured " << (tagp->m_kind == VIfaceCaptureTag::Kind::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 // 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"); if (debug() >= 9) dumpEntries("after finalizeIfaceCapture"); // Emit statistics for --stats V3Stats::addStat("IfaceCapture, Captured refs", s_tags.size()); V3Stats::addStat("IfaceCapture, Captured refs resolved", capturedFixed); // No reference may be left pointing into a module about to be removed if (v3Global.opt.debugCheck()) verifyNoDeadRefs(collectLiveNodes()); // Drop every tag before its record is freed. v3Global.rootp()->foreach([](AstRefDType* refp) { refp->captureTagp(nullptr); }); reset(); }