Fix use-after-free in V3LinkDotIfaceCapture (#7943)
This commit is contained in:
parent
3c47e5735a
commit
1356c15e44
|
|
@ -36,14 +36,14 @@
|
|||
// 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 entry's owner module (using findOwnerModule(refp)
|
||||
// for clone entries) to find the correct target module, then locates
|
||||
// the PARAMTYPEDTYPE / TYPEDEF by name and applies it to the REFDTYPE.
|
||||
// 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}
|
||||
// is the sole identity. No clonep(), no pointer matching. The path IS
|
||||
// the disambiguation.
|
||||
// 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
|
||||
|
|
@ -87,7 +87,6 @@ void V3LinkDotIfaceCapture::reset() {
|
|||
namespace {
|
||||
struct StmtNameMap final {
|
||||
std::unordered_map<string, std::vector<AstNode*>> m_byName;
|
||||
std::unordered_map<string, std::vector<AstNodeDType*>> m_byPrettyName;
|
||||
};
|
||||
std::unordered_map<AstNodeModule*, StmtNameMap> s_moduleCache;
|
||||
|
||||
|
|
@ -98,10 +97,6 @@ const StmtNameMap& getOrBuild(AstNodeModule* 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);
|
||||
if (AstNodeDType* const dtp = VN_CAST(stmtp, NodeDType)) {
|
||||
const string pn = dtp->prettyName();
|
||||
if (!pn.empty()) cache.m_byPrettyName[pn].push_back(dtp);
|
||||
}
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
|
@ -187,12 +182,40 @@ AstParamTypeDType* V3LinkDotIfaceCapture::findParamTypeInModule(AstNodeModule* m
|
|||
return resultp;
|
||||
}
|
||||
|
||||
AstNodeDType* V3LinkDotIfaceCapture::findDTypeByPrettyName(AstNodeModule* modp,
|
||||
const string& prettyName) {
|
||||
const StmtNameMap& cache = getOrBuild(modp);
|
||||
const auto it = cache.m_byPrettyName.find(prettyName);
|
||||
if (it == cache.m_byPrettyName.end()) return nullptr;
|
||||
return it->second.front();
|
||||
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,
|
||||
|
|
@ -218,18 +241,60 @@ AstNodeModule* V3LinkDotIfaceCapture::findCloneViaHierarchy(AstNodeModule* conta
|
|||
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 char* location, const LiveNodes& liveNodes) {
|
||||
int fixed = 0;
|
||||
|
||||
// Fix typedefp pointing to dead module
|
||||
if (refp->typedefp()) {
|
||||
AstNodeModule* const typedefModp = findOwnerModule(refp->typedefp());
|
||||
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 = refp->typedefp()->name();
|
||||
const string& tdName = oldTypedefp->name();
|
||||
if (AstTypedef* const newTdp = findTypedefInModule(cloneModp, tdName)) {
|
||||
UINFO(9, "iface capture finalizeCapture ("
|
||||
<< location << "): fixing typedefp refp=" << refp << " dead="
|
||||
|
|
@ -241,75 +306,24 @@ int V3LinkDotIfaceCapture::fixDeadRefs(AstRefDType* refp, AstNodeModule* contain
|
|||
}
|
||||
}
|
||||
|
||||
// Fix refDTypep pointing to dead module
|
||||
if (refp->refDTypep()) {
|
||||
AstNodeModule* const targetModp = findOwnerModule(refp->refDTypep());
|
||||
if (targetModp && targetModp->dead()) {
|
||||
AstNodeModule* cloneModp = nullptr;
|
||||
if (containingModp) { cloneModp = findCloneViaHierarchy(containingModp, targetModp); }
|
||||
bool foundByName = false;
|
||||
if (cloneModp) {
|
||||
const string& targetName = refp->refDTypep()->prettyName();
|
||||
if (AstNodeDType* const newDtp = findDTypeByPrettyName(cloneModp, targetName)) {
|
||||
UINFO(9, "iface capture finalizeCapture ("
|
||||
<< location << "): fixing refDTypep refp=" << refp
|
||||
<< " dead=" << targetModp->name() << " -> " << cloneModp->name());
|
||||
refp->refDTypep(newDtp);
|
||||
++fixed;
|
||||
foundByName = true;
|
||||
}
|
||||
}
|
||||
// If name-based search failed, try to derive refDTypep from
|
||||
// the already-fixed typedefp chain. The typedefp was fixed
|
||||
// above to point to the clone's typedef, so its subDTypep()
|
||||
// returns a live dtype (type-table entry or clone-owned).
|
||||
// This avoids setting refDTypep to nullptr which would force
|
||||
// V3Width to re-walk the dtype tree under TYPETABLE where
|
||||
// module provenance is lost, triggering spurious warnings.
|
||||
if (!foundByName) {
|
||||
AstNodeDType* derivedp = nullptr;
|
||||
if (refp->typedefp() && refp->typedefp()->subDTypep()) {
|
||||
derivedp = refp->typedefp()->subDTypep();
|
||||
AstNodeModule* const derivedOwnerp = findOwnerModule(derivedp);
|
||||
if (derivedOwnerp && derivedOwnerp->dead()) { derivedp = nullptr; }
|
||||
}
|
||||
UINFO(9, "iface capture finalizeCapture ("
|
||||
<< location << "): deriving refDTypep from typedefp refp=" << refp
|
||||
<< " dead=" << targetModp->name() << " derived=" << derivedp);
|
||||
refp->refDTypep(derivedp);
|
||||
++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() : "") << "'");
|
||||
}
|
||||
|
||||
// Fix base-class dtypep() - V3Broken checks this pointer, and V3Width
|
||||
// may have set it to a node in the dead template module. Derive from
|
||||
// the (already fixed) typedefp chain when possible.
|
||||
if (refp->dtypep()) {
|
||||
AstNodeModule* const dtOwnerp = findOwnerModule(refp->dtypep());
|
||||
if (dtOwnerp && dtOwnerp->dead()) {
|
||||
AstNodeDType* newDtp = nullptr;
|
||||
// Derive from the fixed typedef's subDTypep. This always succeeds
|
||||
// because the typedefp was fixed above to point to a clone's typedef
|
||||
// whose subDTypep is a live type-table entry or clone-owned dtype.
|
||||
// If this fires, either typedefp was not fixed or subDTypep is stale.
|
||||
// Dump refp->typedefp() and dtOwnerp to diagnose.
|
||||
if (refp->typedefp() && refp->typedefp()->subDTypep()) {
|
||||
newDtp = refp->typedefp()->subDTypep();
|
||||
AstNodeModule* const newDtOwnerp = findOwnerModule(newDtp);
|
||||
if (newDtOwnerp && newDtOwnerp->dead()) newDtp = nullptr;
|
||||
}
|
||||
UASSERT_OBJ(newDtp, refp,
|
||||
"fixDeadRefs dtypep: could not derive live dtypep for "
|
||||
<< refp->prettyNameQ() << " dead owner=" << dtOwnerp->name()
|
||||
<< " typedefp="
|
||||
<< (refp->typedefp() ? refp->typedefp()->name() : "<null>"));
|
||||
UINFO(9, "iface capture finalizeCapture ("
|
||||
<< location << "): fixing dtypep refp=" << refp
|
||||
<< " dead=" << dtOwnerp->name() << " -> " << newDtp);
|
||||
refp->dtypep(newDtp);
|
||||
++fixed;
|
||||
}
|
||||
// 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;
|
||||
|
|
@ -332,26 +346,23 @@ AstNodeModule* V3LinkDotIfaceCapture::findLiveCloneOf(AstNodeModule* deadTargetM
|
|||
}
|
||||
|
||||
AstNodeModule* V3LinkDotIfaceCapture::findOwnerModule(AstNode* nodep) {
|
||||
for (AstNode* curp = nodep; curp; curp = curp->backp()) {
|
||||
// Guard against corrupted backp() chains (e.g. freed memory,
|
||||
// low addresses like 0x1) from nodes unlinked by linkDotParamed.
|
||||
if (reinterpret_cast<uintptr_t>(curp) < 0x1000) return nullptr;
|
||||
if (AstNodeModule* const modp = VN_CAST(curp, NodeModule)) return modp;
|
||||
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;
|
||||
});
|
||||
}
|
||||
return 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.).
|
||||
std::unordered_set<const AstNode*> liveNodes;
|
||||
v3Global.rootp()->foreach([&](AstNode* np) { liveNodes.insert(np); });
|
||||
for (auto& kv : s_map) {
|
||||
kv.second.foreachLink([&](AstNode*& nodep) {
|
||||
if (nodep && !liveNodes.count(nodep)) nodep = nullptr;
|
||||
});
|
||||
}
|
||||
const LiveNodes liveNodes = collectLiveNodes();
|
||||
nullStaleLedgerRefs(liveNodes);
|
||||
}
|
||||
|
||||
void V3LinkDotIfaceCapture::dumpEntries(const string& label) {
|
||||
|
|
@ -404,10 +415,18 @@ void V3LinkDotIfaceCapture::add(AstRefDType* refp, const string& cellPath,
|
|||
<< refp->name() << " cellPath='" << cellPath << "'" << " ownerMod="
|
||||
<< ownerModName << " extraRefps.size=" << it->second.extraRefps.size());
|
||||
} else {
|
||||
s_map[key] = CapturedEntry{
|
||||
CaptureType::IFACE, refp, cellPath,
|
||||
/*cloneCellPath=*/"",
|
||||
/*origClassp=*/nullptr, ownerModp, typedefp, nullptr, tdOwnerName, ifacePortVarp, {}};
|
||||
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>")
|
||||
|
|
@ -428,9 +447,18 @@ void V3LinkDotIfaceCapture::addClass(AstRefDType* refp, AstClass* origClassp,
|
|||
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, refp, cellPath,
|
||||
/*cloneCellPath=*/"", origClassp, ownerModp, typedefp, nullptr,
|
||||
tdOwnerName, nullptr, {}};
|
||||
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>"));
|
||||
|
|
@ -518,8 +546,10 @@ AstNodeModule* V3LinkDotIfaceCapture::followCellPath(AstNodeModule* startModp,
|
|||
// 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,
|
||||
|
|
@ -536,6 +566,7 @@ void V3LinkDotIfaceCapture::propagateClone(const TemplateKey& tkey, AstRefDType*
|
|||
// 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();
|
||||
|
|
@ -696,6 +727,7 @@ void V3LinkDotIfaceCapture::captureInnerParamTypeRefs(AstParamTypeDType* paramTy
|
|||
<< innerRefp << " refDTypep owner=" << refOwnerModp->name()
|
||||
<< " nestedCellName='" << nestedCellName << "'");
|
||||
s_map[innerKey] = CapturedEntry{CaptureType::IFACE,
|
||||
TargetKind::TYPEDEF,
|
||||
innerRefp,
|
||||
nestedCellName.empty() ? cellPath : nestedCellName,
|
||||
/*cloneCellPath=*/"",
|
||||
|
|
@ -746,11 +778,18 @@ void V3LinkDotIfaceCapture::addParamType(AstRefDType* refp, const string& cellPa
|
|||
<< refp->name() << " cellPath='" << cellPath << "'" << " ownerMod="
|
||||
<< ownerModName << " extraRefps.size=" << it->second.extraRefps.size());
|
||||
} else {
|
||||
s_map[key]
|
||||
= CapturedEntry{CaptureType::IFACE, refp, cellPath,
|
||||
/*cloneCellPath=*/"",
|
||||
/*origClassp=*/nullptr, ownerModp, nullptr, paramTypep, ptOwnerName,
|
||||
ifacePortVarp, {}};
|
||||
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.
|
||||
|
|
@ -766,6 +805,7 @@ void V3LinkDotIfaceCapture::addParamType(AstRefDType* refp, const string& cellPa
|
|||
// 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 {
|
||||
|
|
@ -776,26 +816,27 @@ class TypeTableDeadRefVisitor final : public VNVisitor {
|
|||
AstNodeModule* deadTargetModp = nullptr;
|
||||
// Check BOTH typedefp and refDTypep for dead owners.
|
||||
// Either (or both) may point to a dead module.
|
||||
if (refp->typedefp()) {
|
||||
AstNodeModule* const tdOwnerp
|
||||
= V3LinkDotIfaceCapture::findOwnerModule(refp->typedefp());
|
||||
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()) {
|
||||
AstNodeModule* const rdOwnerp
|
||||
= V3LinkDotIfaceCapture::findOwnerModule(refp->refDTypep());
|
||||
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_fixed
|
||||
+= V3LinkDotIfaceCapture::fixDeadRefs(refp, containingModp, "type table", m_liveNodes);
|
||||
}
|
||||
|
||||
void visit(AstMemberDType* memberp) override {
|
||||
iterateChildren(memberp);
|
||||
if (!memberp->dtypep()) return;
|
||||
AstNodeModule* const dtOwnerp = V3LinkDotIfaceCapture::findOwnerModule(memberp->dtypep());
|
||||
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);
|
||||
|
|
@ -830,29 +871,33 @@ class TypeTableDeadRefVisitor final : public VNVisitor {
|
|||
|
||||
public:
|
||||
int fixed() const { return m_fixed; }
|
||||
explicit TypeTableDeadRefVisitor(AstNode* nodep) { iterate(nodep); }
|
||||
TypeTableDeadRefVisitor(AstNode* nodep, const LiveNodes& liveNodes)
|
||||
: m_liveNodes{liveNodes} {
|
||||
iterate(nodep);
|
||||
}
|
||||
};
|
||||
|
||||
int V3LinkDotIfaceCapture::fixDeadRefsInTypeTable() {
|
||||
int V3LinkDotIfaceCapture::fixDeadRefsInTypeTable(const LiveNodes& liveNodes) {
|
||||
if (!v3Global.rootp()->typeTablep()) return 0;
|
||||
const TypeTableDeadRefVisitor visitor{v3Global.rootp()->typeTablep()};
|
||||
const TypeTableDeadRefVisitor visitor{v3Global.rootp()->typeTablep(), liveNodes};
|
||||
return visitor.fixed();
|
||||
}
|
||||
|
||||
int V3LinkDotIfaceCapture::fixDeadRefsInModules() {
|
||||
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()); });
|
||||
modp->foreach([&](AstRefDType* refp) {
|
||||
fixed += fixDeadRefs(refp, modp, modName.c_str(), liveNodes);
|
||||
});
|
||||
}
|
||||
}
|
||||
return fixed;
|
||||
}
|
||||
|
||||
int V3LinkDotIfaceCapture::fixWrongCloneRefs() {
|
||||
int V3LinkDotIfaceCapture::resolveCapturedRefs() {
|
||||
int fixed = 0;
|
||||
|
||||
// TARGET RESOLUTION - the ONLY place that resolves targets and
|
||||
|
|
@ -865,78 +910,58 @@ int V3LinkDotIfaceCapture::fixWrongCloneRefs() {
|
|||
forEach([&](const CapturedEntry& entry) {
|
||||
AstRefDType* const refp = entry.refp;
|
||||
if (!refp) return;
|
||||
// For clone entries the stored ownerModp is the template (stale cells).
|
||||
// Use the actual module containing the REFDTYPE - its cells are wired
|
||||
// to the correct interface clones by this point.
|
||||
// findOwnerModule handles corrupted backp() chains gracefully.
|
||||
AstNodeModule* const ownerModp
|
||||
= !entry.cloneCellPath.empty() ? findOwnerModule(refp) : entry.ownerModp;
|
||||
// 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;
|
||||
|
||||
AstNodeModule* const rdOwnerBefore
|
||||
= (refp->refDTypep() ? findOwnerModule(refp->refDTypep()) : nullptr);
|
||||
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
|
||||
<< "' typedefp=" << (refp->typedefp() ? refp->typedefp()->name() : "<null>")
|
||||
<< " refDTypep=" << (refp->refDTypep() ? refp->refDTypep()->name() : "<null>")
|
||||
<< " refDTypepOwner=" << (rdOwnerBefore ? rdOwnerBefore->name() : "<null>")
|
||||
<< " refDTypepDead=" << (rdOwnerBefore ? rdOwnerBefore->dead() : 0));
|
||||
<< "' targetKind="
|
||||
<< (entry.targetKind == TargetKind::PARAM_TYPE ? "param type" : "typedef"));
|
||||
|
||||
// Determine the correct target module using cellPath
|
||||
// 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 (!entry.cellPath.empty()) {
|
||||
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)") : ""));
|
||||
if (correctModp && correctModp->dead()) { correctModp = nullptr; }
|
||||
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 << "'");
|
||||
}
|
||||
|
||||
// Proactive target resolution: when cellPath resolved to a valid
|
||||
// correctModp, find the PARAMTYPEDTYPE or TYPEDEF by name and apply.
|
||||
if (correctModp) {
|
||||
const string& refName = refp->name();
|
||||
bool resolved = false;
|
||||
if (AstParamTypeDType* const ptdp = findParamTypeInModule(correctModp, refName)) {
|
||||
refp->refDTypep(ptdp);
|
||||
refp->user3(true);
|
||||
resolved = true;
|
||||
UINFO(9, "finalizeIfaceCapture Phase3: resolved paramTypep '"
|
||||
<< refName << "' in " << correctModp->name() << " for refp in "
|
||||
<< ownerModp->name() << " cloneCellPath='" << entry.cloneCellPath
|
||||
<< "'");
|
||||
} else if (AstTypedef* const tdp = findTypedefInModule(correctModp, refName)) {
|
||||
refp->typedefp(tdp);
|
||||
refp->user3(true);
|
||||
resolved = true;
|
||||
UINFO(9, "finalizeIfaceCapture Phase3: resolved typedefp '"
|
||||
<< refName << "' in " << correctModp->name() << " for refp in "
|
||||
<< ownerModp->name() << " cloneCellPath='" << entry.cloneCellPath
|
||||
<< "'");
|
||||
}
|
||||
if (resolved) {
|
||||
++fixed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: the structural disambiguation infrastructure (collectReachable,
|
||||
// findCorrectClone, wrong-clone fixup blocks) was removed. All captured
|
||||
// entries have non-empty cellPath, and disambiguateTarget always returns
|
||||
// nullptr through the "cellPath unresolved" path. The wrong-clone fixup
|
||||
// blocks were dead code - CI-CD with v3fatalSrc asserts confirmed this.
|
||||
// Unresolved entries are fixed by fixDeadRefs in a later phase.
|
||||
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() {
|
||||
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()) {
|
||||
|
|
@ -944,40 +969,26 @@ void V3LinkDotIfaceCapture::verifyNoDeadRefs() {
|
|||
if (modp->dead()) continue;
|
||||
modp->foreach([&](AstRefDType* refp) {
|
||||
if (refp->typedefp()) {
|
||||
AstNodeModule* const ownerModp = findOwnerModule(refp->typedefp());
|
||||
// Diagnostic block: only entered when fixup logic has a bug
|
||||
// and leaves a typedefp pointing to a dead module.
|
||||
if (ownerModp && ownerModp->dead()) { // LCOV_EXCL_START
|
||||
bool inLedger = false;
|
||||
forEach([&](const CapturedEntry& e) {
|
||||
if (e.refp == refp) inLedger = true;
|
||||
});
|
||||
UINFO(9, "VERIFY FAIL typedefp: refp="
|
||||
<< refp->name() << " (" << cvtToHex(refp) << ")" << " in mod="
|
||||
<< modp->name() << " typedefp->owner=" << ownerModp->name()
|
||||
<< " inLedger=" << inLedger);
|
||||
} // LCOV_EXCL_STOP
|
||||
UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, // LCOV_EXCL_LINE
|
||||
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()) {
|
||||
AstNodeModule* const ownerModp = findOwnerModule(refp->refDTypep());
|
||||
// Diagnostic block: only entered when fixup logic has a bug
|
||||
// and leaves a refDTypep pointing to a dead module.
|
||||
if (ownerModp && ownerModp->dead()) { // LCOV_EXCL_START
|
||||
bool inLedger = false;
|
||||
forEach([&](const CapturedEntry& e) {
|
||||
if (e.refp == refp) inLedger = true;
|
||||
});
|
||||
UINFO(9, "VERIFY FAIL refDTypep: refp="
|
||||
<< refp->name() << " (" << cvtToHex(refp) << ")" << " in mod="
|
||||
<< modp->name() << " refDTypep->owner=" << ownerModp->name()
|
||||
<< " inLedger=" << inLedger);
|
||||
} // LCOV_EXCL_STOP
|
||||
UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, // LCOV_EXCL_LINE
|
||||
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 '"
|
||||
|
|
@ -991,20 +1002,24 @@ void V3LinkDotIfaceCapture::verifyNoDeadRefs() {
|
|||
nodep = nodep->nextp()) {
|
||||
nodep->foreach([&](AstRefDType* refp) {
|
||||
if (refp->typedefp()) {
|
||||
AstNodeModule* const ownerModp = findOwnerModule(refp->typedefp());
|
||||
// Bug-only assertion: fires only if fixup logic fails to
|
||||
// resolve a type-table typedefp away from a dead module.
|
||||
UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, // LCOV_EXCL_LINE
|
||||
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()) {
|
||||
AstNodeModule* const ownerModp = findOwnerModule(refp->refDTypep());
|
||||
// Bug-only assertion: fires only if fixup logic fails to
|
||||
// resolve a type-table refDTypep away from a dead module.
|
||||
UASSERT_OBJ(!ownerModp || !ownerModp->dead(), refp, // LCOV_EXCL_LINE
|
||||
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 '"
|
||||
|
|
@ -1021,14 +1036,23 @@ void V3LinkDotIfaceCapture::finalizeIfaceCapture() {
|
|||
if (!v3Global.rootp()) return;
|
||||
clearModuleCache(); // Ensure fresh view after all cloning/widthing
|
||||
|
||||
const int typeTableFixed = fixDeadRefsInTypeTable();
|
||||
const int moduleFixed = fixDeadRefsInModules();
|
||||
// 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)");
|
||||
|
||||
const int wrongCloneFixed = fixWrongCloneRefs();
|
||||
UINFO(4, "finalizeIfaceCapture: fixed " << wrongCloneFixed << " wrong-live-clone pointers");
|
||||
|
||||
if (debug() >= 9) dumpEntries("after finalizeIfaceCapture");
|
||||
|
||||
// Emit statistics for --stats
|
||||
|
|
@ -1046,8 +1070,10 @@ void V3LinkDotIfaceCapture::finalizeIfaceCapture() {
|
|||
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, Wrong-clone refs fixed", wrongCloneFixed);
|
||||
V3Stats::addStat("IfaceCapture, Captured refs resolved", capturedFixed);
|
||||
|
||||
verifyNoDeadRefs();
|
||||
// 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
class VSymEnt;
|
||||
|
|
@ -35,6 +36,7 @@ class VSymEnt;
|
|||
class V3LinkDotIfaceCapture final {
|
||||
public:
|
||||
enum class CaptureType : uint8_t { IFACE, CLASS };
|
||||
enum class TargetKind : uint8_t { TYPEDEF, PARAM_TYPE };
|
||||
|
||||
// Path-based map key: no pointers, only stable strings.
|
||||
// {ownerModName, refName, cellPath, cloneCellPath} uniquely identifies
|
||||
|
|
@ -70,6 +72,8 @@ public:
|
|||
|
||||
struct CapturedEntry final {
|
||||
CaptureType captureType = CaptureType::IFACE;
|
||||
// Semantic target identity, retained when template pointers are cleared.
|
||||
TargetKind targetKind = TargetKind::TYPEDEF;
|
||||
AstRefDType* refp = nullptr;
|
||||
string cellPath; // Template path (e.g. "cca_io.tlb_io") - immutable key component
|
||||
string cloneCellPath; // Instance-specific path (e.g. "cca_io1.tlb_io") - set by
|
||||
|
|
@ -131,19 +135,20 @@ private:
|
|||
static void clearModuleCache();
|
||||
static AstIfaceRefDType* ifaceRefFromVarDType(AstNodeDType* dtypep);
|
||||
static string extractIfacePortName(const string& dotText);
|
||||
static AstNodeDType* findDTypeByPrettyName(AstNodeModule* modp, const string& prettyName);
|
||||
static AstNodeModule* findCloneViaHierarchy(AstNodeModule* containingModp,
|
||||
AstNodeModule* deadTargetModp, int depth = 0);
|
||||
static AstNodeModule* findLiveCloneOf(AstNodeModule* deadTargetModp,
|
||||
AstNodeModule** containerp = nullptr);
|
||||
static int fixDeadRefs(AstRefDType* refp, AstNodeModule* containingModp, const char* location);
|
||||
static int fixDeadRefs(AstRefDType* refp, AstNodeModule* containingModp, const char* location,
|
||||
const std::unordered_set<const AstNode*>& liveNodes);
|
||||
static void captureInnerParamTypeRefs(AstParamTypeDType* paramTypep, AstRefDType* refp,
|
||||
const string& cellPath, const string& ownerModName,
|
||||
const string& ptOwnerName);
|
||||
static int fixDeadRefsInTypeTable();
|
||||
static int fixDeadRefsInModules();
|
||||
static int fixWrongCloneRefs();
|
||||
static void verifyNoDeadRefs();
|
||||
static void nullStaleLedgerRefs(const std::unordered_set<const AstNode*>& liveNodes);
|
||||
static int fixDeadRefsInTypeTable(const std::unordered_set<const AstNode*>& liveNodes);
|
||||
static int fixDeadRefsInModules(const std::unordered_set<const AstNode*>& liveNodes);
|
||||
static int resolveCapturedRefs();
|
||||
static void verifyNoDeadRefs(const std::unordered_set<const AstNode*>& liveNodes);
|
||||
template <typename T_FilterFn, typename T_Fn>
|
||||
static void forEachImpl(T_FilterFn&& filter, T_Fn&& fn);
|
||||
|
||||
|
|
@ -156,6 +161,8 @@ public:
|
|||
static AstNodeDType* findDTypeInModule(AstNodeModule* modp, const string& name, VNType type);
|
||||
// Find a ParamTypeDType by name in a module's top-level statements
|
||||
static AstParamTypeDType* findParamTypeInModule(AstNodeModule* modp, const string& name);
|
||||
// Retarget every live RefDType in an entry using only stable capture metadata.
|
||||
static bool retargetRefToModule(const CapturedEntry& entry, AstNodeModule* targetModp);
|
||||
static void add(AstRefDType* refp, const string& cellPath, AstNodeModule* ownerModp,
|
||||
AstTypedef* typedefp = nullptr, const string& typedefOwnerModName = "",
|
||||
AstVar* ifacePortVarp = nullptr);
|
||||
|
|
@ -182,7 +189,7 @@ public:
|
|||
// Ledger-only: no target lookup or AST mutation. Target resolution
|
||||
// happens later in finalizeIfaceCapture where cell pointers are wired up.
|
||||
static void propagateClone(const TemplateKey& tkey, AstRefDType* newRefp,
|
||||
const string& cloneCellPath);
|
||||
AstNodeModule* newOwnerModp, const string& cloneCellPath);
|
||||
|
||||
static void captureTypedefContext(AstRefDType* refp, const char* stageLabel, int dotPos,
|
||||
bool dotIsFinal, const std::string& dotText,
|
||||
|
|
@ -190,8 +197,8 @@ public:
|
|||
AstNode* nodep,
|
||||
const std::function<std::string()>& indentFn);
|
||||
|
||||
// Null out ledger refp entries that point to freed nodes (not in the live AST).
|
||||
// Called once after V3Param completes, before any code touches the ledger.
|
||||
// Null out ledger entries that point to freed nodes (not in the live AST).
|
||||
// Called at pass boundaries before code dereferences ledger pointers.
|
||||
static void purgeStaleRefs();
|
||||
|
||||
// Debug: dump all captured entries
|
||||
|
|
|
|||
|
|
@ -850,54 +850,6 @@ class ParamProcessor final {
|
|||
return resolvedp == expectModp;
|
||||
}
|
||||
|
||||
// Retarget entry.refp (and extraRefps) to the typedef/paramType found
|
||||
// in targetModp. Returns true if anything was retargeted.
|
||||
static bool retargetRefToModule(const V3LinkDotIfaceCapture::CapturedEntry& entry,
|
||||
AstNodeModule* targetModp) {
|
||||
if (entry.refp->typedefp()) {
|
||||
if (AstTypedef* const tdp = V3LinkDotIfaceCapture::findTypedefInModule(
|
||||
targetModp, entry.refp->typedefp()->name())) {
|
||||
entry.refp->typedefp(tdp);
|
||||
if (tdp->subDTypep()) {
|
||||
entry.refp->refDTypep(tdp->subDTypep());
|
||||
entry.refp->dtypep(tdp->subDTypep());
|
||||
}
|
||||
for (AstRefDType* const xrefp : entry.extraRefps) {
|
||||
xrefp->typedefp(tdp);
|
||||
if (tdp->subDTypep()) {
|
||||
xrefp->refDTypep(tdp->subDTypep());
|
||||
xrefp->dtypep(tdp->subDTypep());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (entry.paramTypep) {
|
||||
if (AstParamTypeDType* const ptp = V3LinkDotIfaceCapture::findParamTypeInModule(
|
||||
targetModp, entry.paramTypep->name())) {
|
||||
entry.refp->refDTypep(ptp);
|
||||
entry.refp->dtypep(ptp);
|
||||
for (AstRefDType* const xrefp : entry.extraRefps) {
|
||||
xrefp->refDTypep(ptp);
|
||||
xrefp->dtypep(ptp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (!entry.cloneCellPath.empty()) {
|
||||
// Clone entry has no paramTypep stored; look up the type by name.
|
||||
if (AstParamTypeDType* const ptp
|
||||
= V3LinkDotIfaceCapture::findParamTypeInModule(targetModp, entry.refp->name())) {
|
||||
entry.refp->refDTypep(ptp);
|
||||
entry.refp->dtypep(ptp);
|
||||
for (AstRefDType* const xrefp : entry.extraRefps) {
|
||||
xrefp->refDTypep(ptp);
|
||||
xrefp->dtypep(ptp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fix cross-module REFDTYPE pointers in newModp after cloneTree.
|
||||
// Phase A: path-based fixup using ledger entries with cellPath.
|
||||
// Phase B: reachable-set fallback for remaining REFDTYPEs.
|
||||
|
|
@ -915,7 +867,10 @@ class ParamProcessor final {
|
|||
V3LinkDotIfaceCapture::forEach([&](const V3LinkDotIfaceCapture::CapturedEntry& entry) {
|
||||
if (!entry.refp) return;
|
||||
if (entry.cloneCellPath != cloneCP) return;
|
||||
if (!entry.ownerModp || entry.ownerModp->name() != srcName) return;
|
||||
UASSERT_OBJ(
|
||||
entry.ownerModp
|
||||
&& (entry.ownerModp == newModp || entry.ownerModp->name() == srcName),
|
||||
entry.refp, "clone ledger entry for '" << cloneCP << "' has unexpected owner");
|
||||
if (entry.cellPath.empty()) return;
|
||||
|
||||
AstRefDType* const refp = entry.refp;
|
||||
|
|
@ -1048,17 +1003,24 @@ class ParamProcessor final {
|
|||
if (AstRefDType* const clonedRefp = entry.refp->clonep()) {
|
||||
// Use newname (unique specialized module name) as cloneCellPath.
|
||||
const string cloneCP = newname;
|
||||
// A cloned captured ref lives inside srcModp's tree, so its owner
|
||||
// is srcModp (SV has no nested module definitions).
|
||||
UASSERT_OBJ(
|
||||
entry.ownerModp == srcModp, clonedRefp,
|
||||
"cloned captured RefDType owner is not the specialized module");
|
||||
AstNodeModule* const clonedOwnerp = newModp;
|
||||
const V3LinkDotIfaceCapture::TemplateKey tkey{
|
||||
entry.ownerModp ? entry.ownerModp->name() : "", entry.refp->name(),
|
||||
entry.cellPath};
|
||||
V3LinkDotIfaceCapture::propagateClone(tkey, clonedRefp, cloneCP);
|
||||
V3LinkDotIfaceCapture::propagateClone(tkey, clonedRefp, clonedOwnerp,
|
||||
cloneCP);
|
||||
} else if (entry.ownerModp != srcModp) {
|
||||
// REFDTYPE lives in a parent module; clonep() is null.
|
||||
AstNodeModule* const actualOwnerp
|
||||
= V3LinkDotIfaceCapture::findOwnerModule(entry.refp);
|
||||
if (actualOwnerp && actualOwnerp->hasGParam()) return;
|
||||
// Owner won't be cloned - directly retarget now.
|
||||
if (retargetRefToModule(entry, newModp)) {
|
||||
if (V3LinkDotIfaceCapture::retargetRefToModule(entry, newModp)) {
|
||||
UINFO(9, "iface capture direct retarget: " << entry.refp << " -> "
|
||||
<< newModp->prettyNameQ());
|
||||
}
|
||||
|
|
@ -1078,7 +1040,7 @@ class ParamProcessor final {
|
|||
&& !cellPathMatchesClone(entry.cellPath, cloneCellp, actualOwnerp, m_modp)) {
|
||||
return;
|
||||
}
|
||||
if (retargetRefToModule(entry, newModp)) {
|
||||
if (V3LinkDotIfaceCapture::retargetRefToModule(entry, newModp)) {
|
||||
UINFO(9, "iface capture clone-entry retarget: " << entry.refp << " -> "
|
||||
<< newModp->prettyNameQ());
|
||||
}
|
||||
|
|
@ -2185,7 +2147,7 @@ public:
|
|||
&& !(ownerp == nullptr && entry.cloneCellPath == parentModp->name())) {
|
||||
return;
|
||||
}
|
||||
if (retargetRefToModule(entry, correctModp)) {
|
||||
if (V3LinkDotIfaceCapture::retargetRefToModule(entry, correctModp)) {
|
||||
UINFO(9,
|
||||
"retargetIfaceRefs: " << entry.refp << " -> " << correctModp->prettyNameQ());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# 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: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
test.compile(v_flags2=["--binary"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// DESCRIPTION: Verilator: Test interface typedef capture after parameter cloning
|
||||
//
|
||||
// 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: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
// verilator lint_off DECLFILENAME
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0)
|
||||
// verilog_format: on
|
||||
|
||||
interface avmm_if #(
|
||||
parameter int DW = 32
|
||||
);
|
||||
typedef struct packed {
|
||||
logic [DW-1:0] writedata;
|
||||
logic write;
|
||||
} request_t;
|
||||
|
||||
request_t req;
|
||||
|
||||
modport master(output req);
|
||||
modport slave(input req);
|
||||
endinterface
|
||||
|
||||
module avmm_autopipeline (
|
||||
avmm_if.slave master,
|
||||
avmm_if.master slave
|
||||
);
|
||||
typedef master.request_t request_t;
|
||||
request_t internal_req;
|
||||
|
||||
assign internal_req = master.req;
|
||||
always_comb slave.req = internal_req;
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
for (genvar g = 0; g < 2; ++g) begin : gen_block
|
||||
avmm_if #(.DW(32 * (g + 1))) m_if ();
|
||||
avmm_if #(.DW(32 * (g + 1))) s_if ();
|
||||
avmm_autopipeline pipe (.master(m_if), .slave(s_if));
|
||||
end
|
||||
|
||||
initial begin
|
||||
#1;
|
||||
`checkd($bits(gen_block[0].pipe.internal_req), 33);
|
||||
`checkd($bits(gen_block[1].pipe.internal_req), 65);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -22,7 +22,7 @@ test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 18)
|
|||
test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 8)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 8)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Captured refs resolved\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 0)
|
||||
|
||||
test.execute()
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 25)
|
|||
test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 11)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 14)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 5)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 10)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Captured refs resolved\s+(\d+)', 16)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 0)
|
||||
|
||||
test.execute()
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 20)
|
|||
test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 8)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 12)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 8)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 14)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Captured refs resolved\s+(\d+)', 14)
|
||||
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 0)
|
||||
|
||||
test.execute()
|
||||
|
|
|
|||
Loading…
Reference in New Issue