// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Data flow graph (DFG) representation of logic // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2025 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Dfg.h" #include "V3File.h" VL_DEFINE_DEBUG_FUNCTIONS; //------------------------------------------------------------------------------ // DfgGraph //------------------------------------------------------------------------------ DfgGraph::DfgGraph(AstModule* modulep, const string& name) : m_modulep{modulep} , m_name{name} {} DfgGraph::~DfgGraph() { forEachVertex([](DfgVertex& vtxp) { delete &vtxp; }); } std::unique_ptr DfgGraph::clone() const { const bool scoped = !modulep(); DfgGraph* const clonep = new DfgGraph{modulep(), name()}; // Map from original vertex to clone std::unordered_map vtxp2clonep(size() * 2); // Clone constVertices for (const DfgConst& vtx : m_constVertices) { DfgConst* const cp = new DfgConst{*clonep, vtx.fileline(), vtx.num()}; vtxp2clonep.emplace(&vtx, cp); } // Clone variable vertices for (const DfgVertexVar& vtx : m_varVertices) { const DfgVertexVar* const vp = vtx.as(); DfgVertexVar* cp = nullptr; switch (vtx.type()) { case VDfgType::atVarArray: { if (scoped) { cp = new DfgVarArray{*clonep, vp->varScopep()}; } else { cp = new DfgVarArray{*clonep, vp->varp()}; } vtxp2clonep.emplace(&vtx, cp); break; } case VDfgType::atVarPacked: { if (scoped) { cp = new DfgVarPacked{*clonep, vp->varScopep()}; } else { cp = new DfgVarPacked{*clonep, vp->varp()}; } vtxp2clonep.emplace(&vtx, cp); break; } default: { vtx.v3fatalSrc("Unhandled variable vertex type: " + vtx.typeName()); VL_UNREACHABLE; break; } } } // Clone operation vertices for (const DfgVertex& vtx : m_opVertices) { switch (vtx.type()) { #include "V3Dfg__gen_clone_cases.h" // From ./astgen case VDfgType::atSel: { DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtypep()}; cp->lsb(vtx.as()->lsb()); vtxp2clonep.emplace(&vtx, cp); break; } case VDfgType::atMux: { DfgMux* const cp = new DfgMux{*clonep, vtx.fileline(), vtx.dtypep()}; vtxp2clonep.emplace(&vtx, cp); break; } case VDfgType::atSpliceArray: { DfgSpliceArray* const cp = new DfgSpliceArray{*clonep, vtx.fileline(), vtx.dtypep()}; vtxp2clonep.emplace(&vtx, cp); break; } case VDfgType::atSplicePacked: { DfgSplicePacked* const cp = new DfgSplicePacked{*clonep, vtx.fileline(), vtx.dtypep()}; vtxp2clonep.emplace(&vtx, cp); break; } default: { vtx.v3fatalSrc("Unhandled operation vertex type: " + vtx.typeName()); VL_UNREACHABLE; break; } } } UASSERT(size() == clonep->size(), "Size of clone should be the same"); // Constants have no inputs // Hook up inputs of cloned variables for (const DfgVertexVar& vtx : m_varVertices) { // All variable vertices are unary if (DfgVertex* const srcp = vtx.srcp()) { vtxp2clonep.at(&vtx)->as()->srcp(vtxp2clonep.at(srcp)); } } // Hook up inputs of cloned operation vertices for (const DfgVertex& vtx : m_opVertices) { if (vtx.is()) { switch (vtx.type()) { case VDfgType::atSpliceArray: { const DfgSpliceArray* const vp = vtx.as(); DfgSpliceArray* const cp = vtxp2clonep.at(vp)->as(); vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) { if (DfgVertex* const srcp = edge.sourcep()) { cp->addDriver(vp->driverFileLine(i), // vp->driverIndex(i), // vtxp2clonep.at(srcp)); } }); break; } case VDfgType::atSplicePacked: { const DfgSplicePacked* const vp = vtx.as(); DfgSplicePacked* const cp = vtxp2clonep.at(vp)->as(); vp->forEachSourceEdge([&](const DfgEdge& edge, size_t i) { if (DfgVertex* const srcp = edge.sourcep()) { cp->addDriver(vp->driverFileLine(i), // vp->driverLsb(i), // vtxp2clonep.at(srcp)); } }); break; } default: { vtx.v3fatalSrc("Unhandled DfgVertexVariadic sub type: " + vtx.typeName()); VL_UNREACHABLE; break; } } } else { DfgVertex* const cp = vtxp2clonep.at(&vtx); const auto oSourceEdges = vtx.sourceEdges(); auto cSourceEdges = cp->sourceEdges(); UASSERT_OBJ(oSourceEdges.second == cSourceEdges.second, &vtx, "Mismatched source count"); for (size_t i = 0; i < oSourceEdges.second; ++i) { if (DfgVertex* const srcp = oSourceEdges.first[i].sourcep()) { cSourceEdges.first[i].relinkSource(vtxp2clonep.at(srcp)); } } } } return std::unique_ptr{clonep}; } void DfgGraph::mergeGraphs(std::vector>&& otherps) { if (otherps.empty()) return; // NODE STATE // AstVar/AstVarScope::user2p() -> corresponding DfgVertexVar* in 'this' graph const VNUser2InUse user2InUse; // Set up Ast Variable -> DfgVertexVar map for 'this' graph for (DfgVertexVar& vtx : m_varVertices) vtx.nodep()->user2p(&vtx); // Merge in each of the other graphs for (const std::unique_ptr& otherp : otherps) { // Process variables for (DfgVertexVar* const vtxp : otherp->m_varVertices.unlinkable()) { // Variabels that are present in 'this', make them use the DfgVertexVar in 'this'. if (DfgVertexVar* const altp = vtxp->nodep()->user2u().to()) { DfgVertex* const srcp = vtxp->srcp(); UASSERT_OBJ(!srcp || !altp->srcp(), vtxp, "At most one alias should be driven"); vtxp->replaceWith(altp); if (srcp) altp->srcp(srcp); VL_DO_DANGLING(vtxp->unlinkDelete(*otherp), vtxp); continue; } // Otherwise they will be moved vtxp->nodep()->user2p(vtxp); vtxp->m_userCnt = 0; vtxp->m_graphp = this; } m_varVertices.splice(m_varVertices.end(), otherp->m_varVertices); // Process constants for (DfgConst& vtx : otherp->m_constVertices) { vtx.m_userCnt = 0; vtx.m_graphp = this; } m_constVertices.splice(m_constVertices.end(), otherp->m_constVertices); // Process operations for (DfgVertex& vtx : otherp->m_opVertices) { vtx.m_userCnt = 0; vtx.m_graphp = this; } m_opVertices.splice(m_opVertices.end(), otherp->m_opVertices); // Update graph sizes m_size += otherp->m_size; otherp->m_size = 0; } } std::string DfgGraph::makeUniqueName(const std::string& prefix, size_t n) { // Construct the tmpNameStub if we have not done so yet if (m_tmpNameStub.empty()) { // Use the hash of the graph name (avoid long names and non-identifiers) const std::string name = V3Hash{m_name}.toString(); // We need to keep every variable globally unique, and graph hashed // names might not be, so keep a static table to track multiplicity static std::unordered_map s_multiplicity; m_tmpNameStub += '_' + name + '_' + std::to_string(s_multiplicity[name]++) + '_'; } // Assemble the globally unique name return "__Vdfg" + prefix + m_tmpNameStub + std::to_string(n); } DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& name, AstNodeDType* dtypep, AstScope* scopep) { UASSERT_OBJ(!!scopep != !!modulep(), flp, "makeNewVar scopep should only be provided for a scoped DfgGraph"); // Create AstVar AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep}; if (scopep) { // Add AstVar to the scope's module scopep->modp()->addStmtsp(varp); // Create AstVarScope AstVarScope* const vscp = new AstVarScope{flp, scopep, varp}; // Add to scope scopep->addVarsp(vscp); // Create and return the corresponding variable vertex if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) return new DfgVarArray{*this, vscp}; return new DfgVarPacked{*this, vscp}; } else { // Add AstVar to containing module modulep()->addStmtsp(varp); // Create and return the corresponding variable vertex if (VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType)) return new DfgVarArray{*this, varp}; return new DfgVarPacked{*this, varp}; } } static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; } // Dump one DfgVertex in Graphviz format static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { if (const DfgVarPacked* const varVtxp = vtx.cast()) { AstNode* const nodep = varVtxp->nodep(); AstVar* const varp = varVtxp->varp(); os << toDotId(vtx); os << " [label=\"" << nodep->name() << "\n"; varVtxp->dtypep()->dumpSmall(os); os << " / F" << varVtxp->fanout() << '"'; if (varp->direction() == VDirection::INPUT) { os << ", shape=box, style=filled, fillcolor=chartreuse2"; // Green } else if (varp->direction() == VDirection::OUTPUT) { os << ", shape=box, style=filled, fillcolor=cyan2"; // Cyan } else if (varp->direction() == VDirection::INOUT) { os << ", shape=box, style=filled, fillcolor=darkorchid2"; // Purple } else if (varVtxp->hasExtRefs()) { os << ", shape=box, style=filled, fillcolor=firebrick2"; // Red } else if (varVtxp->hasModRefs()) { os << ", shape=box, style=filled, fillcolor=darkorange1"; // Orange } else if (varVtxp->hasDfgRefs()) { os << ", shape=box, style=filled, fillcolor=gold2"; // Yellow } else { os << ", shape=box"; } os << "]\n"; return; } if (const DfgVarArray* const arrVtxp = vtx.cast()) { AstNode* const nodep = arrVtxp->nodep(); AstVar* const varp = arrVtxp->varp(); os << toDotId(vtx); os << " [label=\"" << nodep->name() << "\n"; arrVtxp->dtypep()->dumpSmall(os); os << " / F" << arrVtxp->fanout() << '"'; if (varp->direction() == VDirection::INPUT) { os << ", shape=box3d, style=filled, fillcolor=chartreuse2"; // Green } else if (varp->direction() == VDirection::OUTPUT) { os << ", shape=box3d, style=filled, fillcolor=cyan2"; // Cyan } else if (varp->direction() == VDirection::INOUT) { os << ", shape=box3d, style=filled, fillcolor=darkorchid2"; // Purple } else if (arrVtxp->hasExtRefs()) { os << ", shape=box3d, style=filled, fillcolor=firebrick2"; // Red } else if (arrVtxp->hasModRefs()) { os << ", shape=box3d, style=filled, fillcolor=darkorange1"; // Orange } else if (arrVtxp->hasDfgRefs()) { os << ", shape=box3d, style=filled, fillcolor=gold2"; // Yellow } else { os << ", shape=box3d"; } os << "]\n"; return; } if (const DfgConst* const constVtxp = vtx.cast()) { const V3Number& num = constVtxp->num(); os << toDotId(vtx); os << " [label=\""; if (num.width() <= 32 && !num.isSigned()) { os << constVtxp->width() << "'d" << num.toUInt() << "\n"; os << constVtxp->width() << "'h" << std::hex << num.toUInt() << std::dec; } else { os << num.ascii(); } os << '"'; os << ", shape=plain"; os << "]\n"; return; } if (const DfgSel* const selVtxp = vtx.cast()) { const uint32_t lsb = selVtxp->lsb(); const uint32_t msb = lsb + selVtxp->width() - 1; os << toDotId(vtx); os << " [label=\"SEL\n_[" << msb << ":" << lsb << "]\n"; vtx.dtypep()->dumpSmall(os); os << " / F" << vtx.fanout() << '"'; if (vtx.hasMultipleSinks()) { os << ", shape=doublecircle"; } else { os << ", shape=circle"; } os << "]\n"; return; } if (vtx.is()) { os << toDotId(vtx); os << " [label=\"" << vtx.typeName() << "\n"; vtx.dtypep()->dumpSmall(os); os << " / F" << vtx.fanout() << '"'; if (vtx.hasMultipleSinks()) { os << ", shape=doubleoctagon"; } else { os << ", shape=octagon"; } os << "]\n"; return; } os << toDotId(vtx); os << " [label=\"" << vtx.typeName() << "\n"; vtx.dtypep()->dumpSmall(os); os << " / F" << vtx.fanout() << '"'; if (vtx.hasMultipleSinks()) { os << ", shape=doublecircle"; } else { os << ", shape=circle"; } os << "]\n"; } // Dump one DfgEdge in Graphviz format static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, const string& headlabel) { os << toDotId(*edge.sourcep()) << " -> " << toDotId(*edge.sinkp()); if (!headlabel.empty()) os << " [headlabel=\"" << headlabel << "\"]"; os << "\n"; } // Dump one DfgVertex and all of its source DfgEdges in Graphviz format static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) { dumpDotVertex(os, vtx); vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { // if (edge.sourcep()) { string headLabel; if (vtx.arity() > 1 || vtx.is()) headLabel = vtx.srcName(idx); dumpDotEdge(os, edge, headLabel); } }); } void DfgGraph::dumpDot(std::ostream& os, const string& label) const { // Header os << "digraph dfg {\n"; os << "graph [label=\"" << name(); if (!label.empty()) os << "-" << label; os << "\", labelloc=t, labeljust=l]\n"; os << "graph [rankdir=LR]\n"; // Emit all vertices forEachVertex([&](const DfgVertex& vtx) { dumpDotVertexAndSourceEdges(os, vtx); }); // Footer os << "}\n"; } void DfgGraph::dumpDotFile(const string& filename, const string& label) const { // This generates a file used by graphviz, https://www.graphviz.org // "hardcoded" parameters: const std::unique_ptr os{V3File::new_ofstream(filename)}; if (os->fail()) v3fatal("Can't write file: " << filename); dumpDot(*os.get(), label); os->close(); } void DfgGraph::dumpDotFilePrefixed(const string& label) const { string filename = name(); if (!label.empty()) filename += "-" + label; dumpDotFile(v3Global.debugFilename(filename) + ".dot", label); } // LCOV_EXCL_START // Debug function for developer use only void DfgGraph::dumpDotUpstreamCone(const string& fileName, const DfgVertex& vtx, const string& name) const { // Open output file const std::unique_ptr os{V3File::new_ofstream(fileName)}; if (os->fail()) v3fatal("Can't write file: " << fileName); // Header *os << "digraph dfg {\n"; if (!name.empty()) *os << "graph [label=\"" << name << "\", labelloc=t, labeljust=l]\n"; *os << "graph [rankdir=LR]\n"; // Work queue for depth first traversal starting from this vertex std::vector queue{&vtx}; // Set of already visited vertices std::unordered_set visited; // Depth first traversal while (!queue.empty()) { // Pop next work item const DfgVertex* const vtxp = queue.back(); queue.pop_back(); // Mark vertex as visited const bool isFirstEncounter = visited.insert(vtxp).second; // If we have already visited this vertex during the traversal, then move on. if (!isFirstEncounter) continue; // Enqueue all sources of this vertex. vtxp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); }); // Emit this vertex and all of its source edges dumpDotVertexAndSourceEdges(*os, *vtxp); } // Footer *os << "}\n"; // Done os->close(); } // LCOV_EXCL_STOP //------------------------------------------------------------------------------ // DfgEdge //------------------------------------------------------------------------------ void DfgEdge::unlinkSource() { if (!m_sourcep) return; #ifdef VL_DEBUG { DfgEdge* sinkp = m_sourcep->m_sinksp; while (sinkp) { if (sinkp == this) break; sinkp = sinkp->m_nextp; } UASSERT(sinkp, "'m_sourcep' does not have this edge as sink"); } #endif // Relink pointers of predecessor and successor if (m_prevp) m_prevp->m_nextp = m_nextp; if (m_nextp) m_nextp->m_prevp = m_prevp; // If head of list in source, update source's head pointer if (m_sourcep->m_sinksp == this) m_sourcep->m_sinksp = m_nextp; // Mark source as unconnected m_sourcep = nullptr; // Clear links. This is not strictly necessary, but might catch bugs. m_prevp = nullptr; m_nextp = nullptr; } void DfgEdge::relinkSource(DfgVertex* newSourcep) { // Unlink current source, if any unlinkSource(); // Link new source m_sourcep = newSourcep; // Prepend to sink list in source m_nextp = newSourcep->m_sinksp; if (m_nextp) m_nextp->m_prevp = this; newSourcep->m_sinksp = this; } //------------------------------------------------------------------------------ // DfgVertex //------------------------------------------------------------------------------ DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep) : m_filelinep{flp} , m_dtypep{dtypep} , m_type{type} { dfg.addVertex(*this); } DfgVertex::~DfgVertex() {} bool DfgVertex::selfEquals(const DfgVertex& that) const { return true; } V3Hash DfgVertex::selfHash() const { return V3Hash{}; } bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const { // If same vertex, then equal if (this == &that) return true; // If different type, then not equal if (this->type() != that.type()) return false; // If different data type, then not equal if (this->dtypep() != that.dtypep()) return false; // If different number of inputs, then not equal auto thisPair = this->sourceEdges(); const DfgEdge* const thisSrcEdgesp = thisPair.first; const size_t thisArity = thisPair.second; auto thatPair = that.sourceEdges(); const DfgEdge* const thatSrcEdgesp = thatPair.first; const size_t thatArity = thatPair.second; if (thisArity != thatArity) return false; // Check vertex specifics if (!this->selfEquals(that)) return false; // Check sources const auto key = (this < &that) ? EqualsCache::key_type{this, &that} // : EqualsCache::key_type{&that, this}; // Note: the recursive invocation can cause a re-hash but that will not invalidate references uint8_t& result = cache[key]; if (!result) { result = 2; // Assume equals for (size_t i = 0; i < thisArity; ++i) { const DfgVertex* const thisSrcVtxp = thisSrcEdgesp[i].m_sourcep; const DfgVertex* const thatSrcVtxp = thatSrcEdgesp[i].m_sourcep; if (thisSrcVtxp == thatSrcVtxp) continue; if (!thisSrcVtxp || !thatSrcVtxp || !thisSrcVtxp->equals(*thatSrcVtxp, cache)) { result = 1; // Mark not equal break; } } } return result >> 1; } V3Hash DfgVertex::hash() { V3Hash& result = user(); if (!result.value()) { V3Hash hash{selfHash()}; // Variables are defined by themselves, so there is no need to hash them further // (especially the sources). This enables sound hashing of graphs circular only through // variables, which we rely on. if (!is()) { hash += m_type; if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep(), UnpackArrayDType)) { hash += adtypep->elementsConst(); // TODO: maybe include sub-dtype, but not hugely important at the moment } else { hash += width(); } const auto pair = sourceEdges(); const DfgEdge* const edgesp = pair.first; const size_t arity = pair.second; // Sources must always be connected in well-formed graphs for (size_t i = 0; i < arity; ++i) hash += edgesp[i].m_sourcep->hash(); } result = hash; } return result; } uint32_t DfgVertex::fanout() const { uint32_t result = 0; forEachSinkEdge([&](const DfgEdge&) { ++result; }); return result; } DfgVertexVar* DfgVertex::getResultVar() { // It's easy if the vertex is already a variable ... if (DfgVertexVar* const varp = this->cast()) return varp; // Inspect existing variables written by this vertex, and choose one DfgVertexVar* resp = nullptr; // cppcheck-has-bug-suppress constParameter this->forEachSink([&resp](DfgVertex& sink) { DfgVertexVar* const varp = sink.cast(); if (!varp) return; // First variable found if (!resp) { resp = varp; return; } // Prefer those variables that must be kept anyway if (resp->hasExtRefs() != varp->hasExtRefs()) { if (!resp->hasExtRefs()) resp = varp; return; } if (resp->hasModWrRefs() != varp->hasModWrRefs()) { if (!resp->hasModWrRefs()) resp = varp; return; } if (resp->hasDfgRefs() != varp->hasDfgRefs()) { if (!resp->hasDfgRefs()) resp = varp; return; } // Prefer those that already have module references if (resp->hasModRdRefs() != varp->hasModRdRefs()) { if (!resp->hasModRdRefs()) resp = varp; return; } // Prefer the earlier one in source order const FileLine& oldFlp = *(resp->fileline()); const FileLine& newFlp = *(varp->fileline()); if (const int cmp = oldFlp.operatorCompare(newFlp)) { if (cmp > 0) resp = varp; return; } // Prefer the one with the lexically smaller name if (const int cmp = resp->nodep()->name().compare(varp->nodep()->name())) { if (cmp > 0) resp = varp; return; } // 'resp' and 'varp' are all the same, keep using the existing 'resp' }); return resp; } AstScope* DfgVertex::scopep(ScopeCache& cache, bool tryResultVar) VL_MT_DISABLED { // If this is a variable, we are done if (DfgVertexVar* const varp = this->cast()) return varp->varScopep()->scopep(); // Try the result var first if instructed (usully only in the recursive case) if (tryResultVar) { if (DfgVertexVar* const varp = this->getResultVar()) return varp->varScopep()->scopep(); } // Note: the recursive invocation can cause a re-hash but that will not invalidate references AstScope*& resultr = cache[this]; if (!resultr) { // Mark to prevent infinite recursion on circular graphs - should never be called on such resultr = reinterpret_cast(1); // Find scope based on sources, falling back on the root scope AstScope* const rootp = v3Global.rootp()->topScopep()->scopep(); AstScope* foundp = rootp; const auto edges = sourceEdges(); for (size_t i = 0; i < edges.second; ++i) { DfgEdge& edge = edges.first[i]; foundp = edge.sourcep()->scopep(cache, true); if (foundp != rootp) break; } resultr = foundp; } // Die on a graph circular through operation vertices UASSERT_OBJ(resultr != reinterpret_cast(1), this, "DfgVertex::scopep called on graph with circular operations"); // Done return resultr; } void DfgVertex::unlinkDelete(DfgGraph& dfg) { // Unlink source edges forEachSourceEdge([](DfgEdge& edge, size_t) { edge.unlinkSource(); }); // Unlink sink edges forEachSinkEdge([](DfgEdge& edge) { edge.unlinkSource(); }); // Remove from graph dfg.removeVertex(*this); // Delete delete this; } void DfgVertex::replaceWith(DfgVertex* newSorucep) { while (m_sinksp) m_sinksp->relinkSource(newSorucep); } //------------------------------------------------------------------------------ // Vertex classes //------------------------------------------------------------------------------ // DfgConst ---------- bool DfgConst::selfEquals(const DfgVertex& that) const { return num().isCaseEq(that.as()->num()); } V3Hash DfgConst::selfHash() const { return num().toHash(); } // DfgSel ---------- bool DfgSel::selfEquals(const DfgVertex& that) const { return lsb() == that.as()->lsb(); } V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; } // DfgSpliceArray ---------- bool DfgSpliceArray::selfEquals(const DfgVertex& that) const { const DfgSpliceArray* const thatp = that.as(); const size_t arity = this->arity(); for (size_t i = 0; i < arity; ++i) { if (driverIndex(i) != thatp->driverIndex(i)) return false; } return true; } V3Hash DfgSpliceArray::selfHash() const { V3Hash hash; const size_t arity = this->arity(); for (size_t i = 0; i < arity; ++i) hash += driverIndex(i); return hash; } // DfgSplicePacked ---------- bool DfgSplicePacked::selfEquals(const DfgVertex& that) const { const DfgSplicePacked* const thatp = that.as(); const size_t arity = this->arity(); for (size_t i = 0; i < arity; ++i) { if (driverLsb(i) != thatp->driverLsb(i)) return false; } return true; } V3Hash DfgSplicePacked::selfHash() const { V3Hash hash; const size_t arity = this->arity(); for (size_t i = 0; i < arity; ++i) hash += driverLsb(i); return hash; } // DfgVertexVar ---------- bool DfgVertexVar::selfEquals(const DfgVertex& that) const { UASSERT_OBJ(nodep()->type() == that.as()->nodep()->type(), this, "Both DfgVertexVar should be scoped or unscoped"); UASSERT_OBJ(nodep() != that.as()->nodep(), this, "There should only be one DfgVertexVar for a given AstVar or AstVarScope"); return false; } V3Hash DfgVertexVar::selfHash() const { V3Hash hash; hash += nodep()->name(); hash += varp()->varType(); return hash; } //------------------------------------------------------------------------------ // DfgVisitor //------------------------------------------------------------------------------ #include "V3Dfg__gen_visitor_defns.h" // From ./astgen