mirror of
https://github.com/verilator/verilator.git
synced 2026-09-02 02:38:15 +02:00
1080 lines
51 KiB
C++
1080 lines
51 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):
|
|
//
|
|
// 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 <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
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<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(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<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, 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<uintptr_t>(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<const AstNode*>& 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() : "<null>")
|
|
<< " refp=" << cvtToHex(entry.refp) << " cellPath='" << entry.cellPath << "'"
|
|
<< " ownerMod=" << (entry.ownerModp ? entry.ownerModp->name() : "<null>")
|
|
<< " typedefp=" << (entry.typedefp ? entry.typedefp->name() : "<null>")
|
|
<< " typedefOwnerModName='" << entry.typedefOwnerModName << "'"
|
|
<< " paramTypep=" << (entry.paramTypep ? entry.paramTypep->name() : "<null>")
|
|
<< " ifacePortVarp="
|
|
<< (entry.ifacePortVarp ? entry.ifacePortVarp->name() : "<null>"));
|
|
++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() : "<null>")
|
|
<< " 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() : "<null>"));
|
|
}
|
|
|
|
// 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 <typename T_FilterFn, typename T_Fn>
|
|
void V3LinkDotIfaceCapture::forEachImpl(T_FilterFn&& filter, T_Fn&& fn) {
|
|
if (s_map.empty()) return;
|
|
std::vector<CaptureKey> 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<void(const CapturedEntry&)>& fn) {
|
|
if (!fn || s_map.empty()) return;
|
|
forEachImpl([](const CapturedEntry&) { return true; }, fn);
|
|
}
|
|
|
|
void V3LinkDotIfaceCapture::forEachOwned(const AstNodeModule* ownerModp,
|
|
const std::function<void(const CapturedEntry&)>& 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() : "<null>") << " cellPath='" << e.cellPath
|
|
<< "' ownerMod=" << (e.ownerModp ? e.ownerModp->name() : "<null>")
|
|
<< " 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<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;
|
|
|
|
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() : "<null>")
|
|
<< " 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() : "<null>") << "'");
|
|
}
|
|
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() : "<null>")
|
|
<< " 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() : "<null>"));
|
|
}
|
|
|
|
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() : "<null>")
|
|
<< " 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() : "<null>")
|
|
<< (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();
|
|
}
|