Files
verilator/src/V3LinkDotIfaceCapture.cpp
T

551 lines
25 KiB
C++

// -*- 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 <unordered_map>
#include <unordered_set>
VL_DEFINE_DEBUG_FUNCTIONS;
std::deque<VIfaceCaptureTag> 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<string, std::vector<AstNode*>> m_byName;
};
std::unordered_map<AstNodeModule*, StmtNameMap> 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<const AstNode*>;
// 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() : "<null>")
<< " 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<std::string()>& 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() : "<null>")
<< " 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() : "<null>")
<< " 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() : "<null>")
<< (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();
}