// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Data flow graph (DFG) representation of logic
//
// 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
//
//*************************************************************************

#include "V3PchAstNoMT.h"  // VL_MT_DISABLED_CODE_UNIT

#include "V3Dfg.h"

#include "V3Ast.h"
#include "V3EmitV.h"
#include "V3File.h"

VL_DEFINE_DEBUG_FUNCTIONS;

//------------------------------------------------------------------------------
// DfgGraph

DfgGraph::DfgGraph(const string& name)
    : m_name{name} {}

DfgGraph::~DfgGraph() {
    forEachVertex([&](DfgVertex& vtx) { vtx.unlinkDelete(*this); });
}

std::unique_ptr<DfgGraph> DfgGraph::clone() const {
    // Create the new graph
    DfgGraph* const clonep = new DfgGraph{name()};

    // Map from original vertex to clone
    std::unordered_map<const DfgVertex*, DfgVertex*> 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>();
        DfgVertexVar* cp = nullptr;

        switch (vtx.type()) {
        case VDfgType::VarArray: {
            cp = new DfgVarArray{*clonep, vp->vscp()};
            vtxp2clonep.emplace(&vtx, cp);
            break;
        }
        case VDfgType::VarPacked: {
            cp = new DfgVarPacked{*clonep, vp->vscp()};
            vtxp2clonep.emplace(&vtx, cp);
            break;
        }
        default: {
            vtx.v3fatalSrc("Unhandled variable vertex type: " + vtx.typeName());
            VL_UNREACHABLE;
            break;
        }
        }

        if (AstVarScope* const tmpForp = vp->tmpForp()) cp->tmpForp(tmpForp);
    }
    // Clone ast reference vertices
    for (const DfgVertexAst& vtx : m_astVertices) {  // LCOV_EXCL_START
        switch (vtx.type()) {
        case VDfgType::AstRd: {
            const DfgAstRd* const vp = vtx.as<DfgAstRd>();
            DfgAstRd* const cp = new DfgAstRd{*clonep, vp->exprp(), vp->inSenItem(), vp->inLoop()};
            vtxp2clonep.emplace(&vtx, cp);
            break;
        }
        default: {
            vtx.v3fatalSrc("Unhandled ast reference vertex type: " + vtx.typeName());
            VL_UNREACHABLE;
            break;
        }
        }
    }  // LCOV_EXCL_STOP
    // Clone operation vertices
    for (const DfgVertex& vtx : m_opVertices) {
        switch (vtx.type()) {
#include "V3Dfg__gen_clone_cases.h"  // From ./astgen
        case VDfgType::Sel: {
            DfgSel* const cp = new DfgSel{*clonep, vtx.fileline(), vtx.dtype()};
            cp->lsb(vtx.as<DfgSel>()->lsb());
            vtxp2clonep.emplace(&vtx, cp);
            break;
        }
        case VDfgType::Rep: {
            DfgRep* const cp = new DfgRep{*clonep, vtx.fileline(), vtx.dtype()};
            vtxp2clonep.emplace(&vtx, cp);
            break;
        }
        case VDfgType::UnitArray: {
            DfgUnitArray* const cp = new DfgUnitArray{*clonep, vtx.fileline(), vtx.dtype()};
            vtxp2clonep.emplace(&vtx, cp);
            break;
        }
        case VDfgType::Mux: {
            DfgMux* const cp = new DfgMux{*clonep, vtx.fileline(), vtx.dtype()};
            vtxp2clonep.emplace(&vtx, cp);
            break;
        }
        case VDfgType::SpliceArray: {
            DfgSpliceArray* const cp = new DfgSpliceArray{*clonep, vtx.fileline(), vtx.dtype()};
            vtxp2clonep.emplace(&vtx, cp);
            break;
        }
        case VDfgType::SplicePacked: {
            DfgSplicePacked* const cp = new DfgSplicePacked{*clonep, vtx.fileline(), vtx.dtype()};
            vtxp2clonep.emplace(&vtx, cp);
            break;
        }
        case VDfgType::Logic: {
            vtx.v3fatalSrc("DfgLogic cannot be cloned");
            VL_UNREACHABLE;
            break;
        }
        case VDfgType::Unresolved: {
            vtx.v3fatalSrc("DfgUnresolved cannot be cloned");
            VL_UNREACHABLE;
            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) {
        DfgVertexVar* const cp = vtxp2clonep.at(&vtx)->as<DfgVertexVar>();
        if (const DfgVertex* const srcp = vtx.srcp()) cp->srcp(vtxp2clonep.at(srcp));
        if (const DfgVertex* const defp = vtx.defaultp()) cp->defaultp(vtxp2clonep.at(defp));
    }
    // Hook up inputs of cloned ast references
    for (const DfgVertexAst& vtx : m_astVertices) {  // LCOV_EXCL_START
        switch (vtx.type()) {
        case VDfgType::AstRd: {
            const DfgAstRd* const vp = vtx.as<DfgAstRd>();
            DfgAstRd* const cp = vtxp2clonep.at(&vtx)->as<DfgAstRd>();
            if (const DfgVertex* const srcp = vp->srcp()) cp->srcp(vtxp2clonep.at(srcp));
            break;
        }
        default: {
            vtx.v3fatalSrc("Unhandled DfgVertexAst sub type: " + vtx.typeName());
            VL_UNREACHABLE;
            break;
        }
        }
    }  // LCOV_EXCL_STOP
    // Hook up inputs of cloned operation vertices
    for (const DfgVertex& vtx : m_opVertices) {
        if (vtx.is<DfgVertexVariadic>()) {
            switch (vtx.type()) {
            case VDfgType::SpliceArray:
            case VDfgType::SplicePacked: {
                const DfgVertexSplice* const vp = vtx.as<DfgVertexSplice>();
                DfgVertexSplice* const cp = vtxp2clonep.at(vp)->as<DfgVertexSplice>();
                vp->foreachDriver([&](const DfgVertex& src, uint32_t lo, FileLine* flp) {
                    cp->addDriver(vtxp2clonep.at(&src), lo, flp);
                    return false;
                });
                break;
            }
            default: {
                vtx.v3fatalSrc("Unhandled DfgVertexVariadic sub type: " + vtx.typeName());
                VL_UNREACHABLE;
                break;
            }
            }
        } else {
            DfgVertex* const cp = vtxp2clonep.at(&vtx);
            for (size_t i = 0; i < vtx.nInputs(); ++i) {
                cp->inputp(i, vtxp2clonep.at(vtx.inputp(i)));
            }
        }
    }

    return std::unique_ptr<DfgGraph>{clonep};
}

void DfgGraph::mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& otherps) {
    if (otherps.empty()) return;

    // NODE STATE
    // 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.vscp()->user2p(&vtx);

    // Merge in each of the other graphs
    for (const std::unique_ptr<DfgGraph>& 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->vscp()->user2u().to<DfgVertexVar*>()) {
                DfgVertex* const srcp = vtxp->srcp();
                DfgVertex* const defaultp = vtxp->defaultp();
                UASSERT_OBJ(!(srcp || defaultp) || (!altp->srcp() && !altp->defaultp()), vtxp,
                            "At most one alias should be driven");
                vtxp->replaceWith(altp);
                if (srcp) altp->srcp(srcp);
                if (defaultp) altp->defaultp(defaultp);
                VL_DO_DANGLING(vtxp->unlinkDelete(*otherp), vtxp);
                continue;
            }
            // Otherwise they will be moved
            vtxp->vscp()->user2p(vtxp);
            vtxp->m_userGeneration = 0;
#ifdef VL_DEBUG
            vtxp->m_dfgp = this;
#endif
        }
        m_varVertices.splice(m_varVertices.end(), otherp->m_varVertices);
        // Process Ast references
        for (DfgVertexAst* const vtxp : otherp->m_astVertices.unlinkable()) {
            vtxp->m_userGeneration = 0;
#ifdef VL_DEBUG
            vtxp->m_dfgp = this;
#endif
        }
        m_astVertices.splice(m_astVertices.end(), otherp->m_astVertices);
        // Process constants
        for (DfgConst& vtx : otherp->m_constVertices) {
            vtx.m_userGeneration = 0;
#ifdef VL_DEBUG
            vtx.m_dfgp = this;
#endif
        }
        m_constVertices.splice(m_constVertices.end(), otherp->m_constVertices);
        // Process operations
        for (DfgVertex& vtx : otherp->m_opVertices) {
            vtx.m_userGeneration = 0;
#ifdef VL_DEBUG
            vtx.m_dfgp = this;
#endif
        }
        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 hash = 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<std::string, uint32_t> s_multiplicity;
        m_tmpNameStub += '_' + hash + '_' + std::to_string(s_multiplicity[hash]++) + '_';
    }
    // Assemble the globally unique name
    return "__Vdfg" + prefix + m_tmpNameStub + std::to_string(n);
}

DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& name,
                                   const DfgDataType& dtype, AstScope* scopep) {
    // Create AstVar
    AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtype.astDtypep()};
    // 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 (dtype.isArray()) return new DfgVarArray{*this, vscp};
    return new DfgVarPacked{*this, vscp};
}

static const std::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 DfgVertexVar* const varVtxp = vtx.cast<DfgVertexVar>()) {
        const AstVarScope* const vscp = varVtxp->vscp();
        os << toDotId(vtx);
        // Begin attributes
        os << " [";
        // Begin 'label'
        os << "label=\"";
        // Name
        os << vscp->prettyName();
        // Address
        os << '\n' << cvtToHex(varVtxp);
        // Original variable, if any
        if (const AstVarScope* const tmpForp = varVtxp->tmpForp()) {
            if (tmpForp != vscp) os << "\ntemporary for: " << tmpForp->prettyName();
        }
        // Type and fanout
        os << '\n';
        varVtxp->dtype().astDtypep()->dumpSmall(os);
        os << " / F" << varVtxp->fanout();
        // Reference flags
        os << " / ";
        static const char* const rwmn[2][2] = {{"_", "W"}, {"R", "M"}};
        os << rwmn[varVtxp->hasExtRdRefs()][varVtxp->hasExtWrRefs()];
        os << rwmn[false][varVtxp->hasModWrRefs()];
        os << (varVtxp->hasDfgRefs() ? "D" : "_");
        // End 'label'
        os << '"';
        // Shape
        if (varVtxp->is<DfgVarPacked>()) {
            os << ", shape=box";
        } else if (varVtxp->is<DfgVarArray>()) {
            os << ", shape=box3d";
        } else {
            varVtxp->v3fatalSrc("Unhandled DfgVertexVar sub-type");
        }
        // Color
        const char* const colorp = varVtxp->hasExtRefs()     ? "firebrick2"  // Red
                                   : varVtxp->hasModWrRefs() ? "darkorange1"  // Orange
                                   : varVtxp->hasDfgRefs()   ? "gold2"  // Yellow
                                   : varVtxp->tmpForp()      ? "gray95"  // Gray
                                                             : "white";
        os << ", style=filled";
        os << ", fillcolor=\"" << colorp << "\"";
        // End attributes
        os << "]\n";
        return;
    }

    if (const DfgConst* const constVtxp = vtx.cast<DfgConst>()) {
        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 << '\n';
        } else {
            os << num.ascii() << '\n';
        }
        os << cvtToHex(constVtxp) << '\n';
        os << '"';
        os << ", shape=plain";
        os << "]\n";
        return;
    }

    if (const DfgSel* const selVtxp = vtx.cast<DfgSel>()) {
        const uint32_t lsb = selVtxp->lsb();
        const uint32_t msb = lsb + selVtxp->width() - 1;
        os << toDotId(vtx);
        os << " [label=\"SEL _[" << msb << ":" << lsb << "]\n";
        os << cvtToHex(selVtxp) << '\n';
        vtx.dtype().astDtypep()->dumpSmall(os);
        os << " / F" << vtx.fanout() << '"';
        if (vtx.hasMultipleSinks()) {
            os << ", shape=doublecircle";
        } else {
            os << ", shape=circle";
        }
        os << "]\n";
        return;
    }

    if (vtx.is<DfgVertexSplice>() || vtx.is<DfgUnitArray>() || vtx.is<DfgUnresolved>()) {
        os << toDotId(vtx);
        os << " [label=\"" << vtx.typeName() << '\n';
        os << cvtToHex(&vtx) << '\n';
        vtx.dtype().astDtypep()->dumpSmall(os);
        os << " / F" << vtx.fanout() << '"';
        if (vtx.hasMultipleSinks()) {
            os << ", shape=doubleoctagon";
        } else {
            os << ", shape=octagon";
        }
        os << "]\n";
        return;
    }

    if (const DfgLogic* const logicp = vtx.cast<DfgLogic>()) {
        os << toDotId(vtx);
        std::stringstream ss;
        V3EmitV::debugVerilogForTree(logicp->nodep(), ss);
        std::string str = "AstNode: " + cvtToHex(logicp->nodep()) + "\n" + ss.str();
        str = VString::quoteBackslash(str);
        str = VString::quoteAny(str, '"', '\\');
        str = VString::replaceSubstr(str, "\n", "\\l");
        const char* const colorp = !logicp->selectedForSynthesis() ? "#d0d0ff"  // Pale Blue
                                   : logicp->nonSynthesizable()    ? "#ffd0d0"  // Pale Red
                                   : logicp->reverted()            ? "#faffd0"  // Pale Yellow
                                                                   : "#d0ffd0";  // Pale Green

        os << " [label=\"";
        os << str;
        os << '\n' << vtx.typeName() << '\n' << cvtToHex(&vtx);
        os << "\"\n";
        os << ", shape=box, style=\"rounded,filled\", nojustify=true";
        os << ", fillcolor=\"" << colorp << "\"";
        os << "]\n";
        return;
    }

    if (const DfgVertexAst* const astVtxp = vtx.cast<DfgVertexAst>()) {
        os << toDotId(vtx);
        std::stringstream ss;
        V3EmitV::debugVerilogForTree(astVtxp->exprp(), ss);
        std::string str = "AstNode: " + cvtToHex(astVtxp->exprp()) + "\n" + ss.str() + "\n";
        str = VString::quoteBackslash(str);
        str = VString::quoteAny(str, '"', '\\');
        str = VString::replaceSubstr(str, "\n", "\\l");
        const DfgAstRd* const astRdVtxp = astVtxp->cast<DfgAstRd>();
        const char* const colorp = astRdVtxp ? "#80ff80"  // Green
                                             : "#ffff80";  // Yellow
        os << " [label=\"";
        os << str;
        os << '\n' << vtx.typeName() << '\n' << cvtToHex(&vtx) << '\n';
        vtx.dtype().astDtypep()->dumpSmall(os);
        os << " / ";
        os << "_S"[astRdVtxp->inSenItem()];
        os << "_L"[astRdVtxp->inLoop()];
        os << "\"\n";
        os << ", shape=box, style=\"rounded,filled\", nojustify=true";
        os << ", fillcolor=\"" << colorp << "\"";
        os << "]\n";
        return;
    }

    os << toDotId(vtx);
    os << " [label=\"" << vtx.typeName() << '\n';
    os << cvtToHex(&vtx) << '\n';
    vtx.dtype().astDtypep()->dumpSmall(os);
    os << " / F" << vtx.fanout() << '"';
    if (vtx.hasMultipleSinks()) {
        os << ", shape=doublecircle";
    } else {
        os << ", shape=circle";
    }
    os << "]\n";
}

void DfgGraph::dumpDot(std::ostream& os, const std::string& label,
                       std::function<bool(const DfgVertex&)> p) const {
    // This generates a graphviz dump, https://www.graphviz.org

    // Header
    os << "digraph dfg {\n";
    os << "rankdir=LR\n";

    // If predicate not given, dump everything
    if (!p) p = [](const DfgVertex&) { return true; };

    std::unordered_set<const DfgVertex*> emitted;
    // Emit all vertices associated with a DfgLogic
    forEachVertex([&](const DfgVertex& vtx) {
        const DfgLogic* const logicp = vtx.cast<DfgLogic>();
        if (!logicp) return;
        if (logicp->synth().empty()) return;
        if (!p(vtx)) return;
        os << "subgraph cluster_" << cvtToHex(logicp) << " {\n";
        dumpDotVertex(os, *logicp);
        emitted.insert(logicp);
        for (DfgVertex* const vtxp : logicp->synth()) {
            if (!p(*vtxp)) continue;
            dumpDotVertex(os, *vtxp);
            emitted.insert(vtxp);
        }
        os << "}\n";
    });
    // Emit all remaining vertices
    forEachVertex([&](const DfgVertex& vtx) {
        if (emitted.count(&vtx)) return;
        if (!p(vtx)) return;
        dumpDotVertex(os, vtx);
    });
    // Emit all edges
    forEachVertex([&](const DfgVertex& vtx) {
        if (!p(vtx)) return;
        for (size_t i = 0; i < vtx.nInputs(); ++i) {
            DfgVertex* const srcp = vtx.inputp(i);
            if (!srcp) continue;
            if (!p(*srcp)) continue;
            os << toDotId(*srcp) << " -> " << toDotId(vtx);
            os << " [headlabel=\"" << vtx.srcName(i) << "\"]";
            os << '\n';
        }
    });

    // Footer
    os << "label=\"" << name() + (label.empty() ? "" : "-" + label) << "\"\n";
    os << "labelloc=t\n";
    os << "labeljust=l\n";
    os << "}\n";
}

std::string DfgGraph::dumpDotString(const std::string& label,
                                    std::function<bool(const DfgVertex&)> p) const {
    std::stringstream ss;
    dumpDot(ss, label, p);
    return ss.str();
}

void DfgGraph::dumpDotFile(const std::string& filename, const std::string& label,
                           std::function<bool(const DfgVertex&)> p) const {
    const std::unique_ptr<std::ofstream> os{V3File::new_ofstream(filename)};
    if (os->fail()) v3fatal("Can't write file: " << filename);
    dumpDot(*os.get(), label, p);
    os->close();
}

void DfgGraph::dumpDotFilePrefixed(const std::string& label,
                                   std::function<bool(const DfgVertex&)> p) const {
    std::string filename = name();
    if (!label.empty()) filename += "-" + label;
    dumpDotFile(v3Global.debugFilename(filename) + ".dot", label, p);
}

template <bool T_SinksNotSources>
static std::unique_ptr<std::unordered_set<const DfgVertex*>>
dfgGraphCollectCone(const std::vector<const DfgVertex*>& vtxps) {
    // Work queue for traversal starting from all the seed vertices
    std::vector<const DfgVertex*> queue = vtxps;
    // Set of already visited vertices
    std::unique_ptr<std::unordered_set<const DfgVertex*>> resp{
        new std::unordered_set<const DfgVertex*>{}};
    // Depth first traversal
    while (!queue.empty()) {
        // Pop next work item
        const DfgVertex* const vtxp = queue.back();
        queue.pop_back();
        // Mark vertex as visited, move on if already visited
        if (!resp->insert(vtxp).second) continue;
        // Enqueue all siblings of this vertex.
        if VL_CONSTEXPR_CXX17 (T_SinksNotSources) {
            vtxp->foreachSink([&](const DfgVertex& sink) {
                queue.push_back(&sink);
                return false;
            });
        } else {
            vtxp->foreachSource([&](const DfgVertex& src) {
                queue.push_back(&src);
                return false;
            });
        }
    }
    // Done
    return resp;
}

std::unique_ptr<std::unordered_set<const DfgVertex*>>
DfgGraph::sourceCone(const std::vector<const DfgVertex*>& vtxps) const {
    return dfgGraphCollectCone<false>(vtxps);
}

std::unique_ptr<std::unordered_set<const DfgVertex*>>
DfgGraph::sinkCone(const std::vector<const DfgVertex*>& vtxps) const {
    return dfgGraphCollectCone<true>(vtxps);
}

std::unique_ptr<std::unordered_set<const DfgVertex*>>
DfgGraph::neighborhood(const std::vector<const DfgVertex*>& vtxps, size_t n) const {
    // Neighborhood
    std::vector<const DfgVertex*> vec = vtxps;
    // Set of already visited vertices
    std::unordered_set<const DfgVertex*> res{vec.begin(), vec.end()};
    // Expand neihborhood by 'n' hops
    size_t begin = 0;
    size_t end = vec.size();
    for (size_t hops = 1; hops <= n; ++hops) {
        for (size_t i = begin; i < end; ++i) {
            const DfgVertex* const vtxp = vec[i];
            vtxp->foreachSink([&](const DfgVertex& dst) {
                if (res.insert(&dst).second) vec.push_back(&dst);
                return false;
            });
            vtxp->foreachSource([&](const DfgVertex& src) {
                if (res.insert(&src).second) vec.push_back(&src);
                return false;
            });
        }
        begin = end;
        end = vec.size();
    }
    // Move out the results
    return std::make_unique<std::unordered_set<const DfgVertex*>>(std::move(res));
}

//------------------------------------------------------------------------------
// DfgVertex

DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, const DfgDataType& dt)
    : m_filelinep{flp}
    , m_dtype{dt}
    , m_type{type} {
    dfg.addVertex(*this);
}

void DfgVertex::typeCheck(const DfgGraph& dfg) const {

#define CHECK(cond, msg) \
    UASSERT_OBJ(cond, this, \
                "Dfg type error for vertex " << typeName() << " in " << dfg.name() << ": " \
                                             << msg);
    switch (type()) {
    case VDfgType::Const: {
        CHECK(isPacked(), "Should be Packed type");
        return;
    }
    case VDfgType::AstRd: {
        const DfgAstRd& v = *as<DfgAstRd>();
        CHECK(v.isPacked() || v.isArray(), "Should be Packed or Array type");
        return;
    }
    case VDfgType::VarArray:
    case VDfgType::VarPacked: {
        const DfgVertexVar& v = *as<DfgVertexVar>();
        CHECK(!v.defaultp() || v.defaultp()->dtype() == v.dtype(), "'defaultp' should match");
        CHECK(!v.srcp() || v.srcp()->dtype() == v.dtype(), "'srcp' should match");
        return;
    }
    case VDfgType::SpliceArray:
    case VDfgType::SplicePacked: {
        const DfgVertexSplice& v = *as<DfgVertexSplice>();
        v.foreachDriver([&](const DfgVertex& src, uint32_t lo) {
            CHECK(src.dtype() == DfgDataType::select(v.dtype(), lo, src.size()), "driver");
            return false;
        });
        return;
    }
    case VDfgType::Logic: {
        CHECK(dtype().isNull(), "Should be Null type");
        return;
    }
    case VDfgType::Unresolved: {
        CHECK(!dtype().isNull(), "Should not be Null type");
        return;
    }
    case VDfgType::UnitArray: {
        const DfgUnitArray& v = *as<DfgUnitArray>();
        CHECK(v.isArray(), "Should be Array type");
        CHECK(v.size() == 1, "Should be one element");
        CHECK(v.srcp()->dtype() == v.dtype().elemDtype(), "Input should be the element type");
        return;
    }
    case VDfgType::Sel: {
        const DfgSel& v = *as<DfgSel>();
        CHECK(v.isPacked(), "Should be Packed type");
        CHECK(v.dtype() == DfgDataType::select(v.srcp()->dtype(), v.lsb(), v.size()), "sel");
        return;
    }
    case VDfgType::Mux: {
        const DfgMux& v = *as<DfgMux>();
        CHECK(v.isPacked(), "Should be Packed type");
        CHECK(v.fromp()->isPacked(), "Source operand should be Packed type");
        CHECK(v.fromp()->size() >= v.size(), "Source operand should not be narrower");
        CHECK(v.lsbp()->isPacked(), "Index should be Packed type");
        return;
    }
    case VDfgType::ArraySel: {
        const DfgArraySel& v = *as<DfgArraySel>();
        CHECK(v.dtype() == v.fromp()->dtype().elemDtype(), "Element type should match");
        CHECK(v.bitp()->isPacked(), "Index should be Packed type");
        return;
    }

    case VDfgType::Add:
    case VDfgType::And:
    case VDfgType::Div:
    case VDfgType::DivS:
    case VDfgType::ModDiv:
    case VDfgType::ModDivS:
    case VDfgType::Mul:
    case VDfgType::MulS:
    case VDfgType::Or:
    case VDfgType::Sub:
    case VDfgType::Xor: {
        CHECK(isPacked(), "Should be Packed type");
        CHECK(inputp(0)->dtype() == dtype(), "LHS should be same type");
        CHECK(inputp(1)->dtype() == dtype(), "RHS should be same type");
        return;
    }

    case VDfgType::Negate:
    case VDfgType::Not: {
        CHECK(isPacked(), "Should be Packed type");
        CHECK(inputp(0)->dtype() == dtype(), "Input should be same type");
        return;
    }

    case VDfgType::ShiftL:
    case VDfgType::ShiftR:
    case VDfgType::ShiftRS: {
        CHECK(isPacked(), "Should be Packed type");
        CHECK(inputp(0)->dtype() == dtype(), "LHS should be same type");
        CHECK(inputp(1)->isPacked(), "RHS should be Packed type");
        return;
    }

    case VDfgType::Concat: {
        const DfgConcat& v = *as<DfgConcat>();
        CHECK(v.isPacked(), "Should be Packed type");
        CHECK(v.lhsp()->isPacked(), "LHS should be Packed type");
        CHECK(v.rhsp()->isPacked(), "RHS should be Packed type");
        CHECK(v.size() == v.rhsp()->size() + v.lhsp()->size(), "Concat result mismatch");
        return;
    }

    case VDfgType::Rep: {
        const DfgRep& v = *as<DfgRep>();
        CHECK(v.isPacked(), "Should be Packed type");
        CHECK(v.srcp()->isPacked(), "'srcp' should be same type");
        CHECK(v.size() % v.srcp()->size() == 0, "Not a replicate");
        return;
    }

    case VDfgType::StreamL:
    case VDfgType::StreamR: {
        // TODO: model these without an explicit slice size which is always constant (?)
        CHECK(isPacked(), "Should be Packed type");
        CHECK(inputp(0)->dtype() == dtype(), "LHS should be same type");
        CHECK(inputp(1)->isPacked(), "Slice size should be Packed type");
        return;
    }

    case VDfgType::Eq:
    case VDfgType::EqCase:
    case VDfgType::EqWild:
    case VDfgType::Neq:
    case VDfgType::NeqCase:
    case VDfgType::NeqWild:
    case VDfgType::Gt:
    case VDfgType::GtS:
    case VDfgType::Gte:
    case VDfgType::GteS:
    case VDfgType::Lt:
    case VDfgType::LtS:
    case VDfgType::Lte:
    case VDfgType::LteS: {
        CHECK(dtype() == DfgDataType::packed(1), "Should be 1-bit");
        CHECK(inputp(0)->dtype() == inputp(1)->dtype(), "Sides should match");
        return;
    }

    case VDfgType::Extend:
    case VDfgType::ExtendS: {
        CHECK(isPacked(), "Should be Packed type");
        CHECK(inputp(0)->isPacked(), "Operand should be same type");
        CHECK(inputp(0)->size() < size(), "Operand should be narrower");
        return;
    }

    case VDfgType::LogAnd:
    case VDfgType::LogEq:
    case VDfgType::LogIf:
    case VDfgType::LogOr: {
        CHECK(dtype() == DfgDataType::packed(1), "Should be 1-bit");
        CHECK(inputp(0)->isPacked(), "LHS should be Packed type");
        CHECK(inputp(1)->isPacked(), "RHS should be Packed type");
        return;
    }

    case VDfgType::LogNot:
    case VDfgType::RedAnd:
    case VDfgType::RedOr:
    case VDfgType::RedXor: {
        CHECK(dtype() == DfgDataType::packed(1), "Should be 1-bit");
        CHECK(inputp(0)->isPacked(), "Operand should be Packed type");
        return;
    }

    case VDfgType::Cond: {
        const DfgCond& v = *as<DfgCond>();
        CHECK(v.isPacked(), "Should be Packed type");
        CHECK(v.condp()->isPacked(), "Condition should be Packed type");
        CHECK(v.thenp()->dtype() == v.dtype(), "Then should be same type");
        CHECK(v.elsep()->dtype() == v.dtype(), "Else should be same type");
        return;
    }

    case VDfgType::CountOnes: {
        const DfgCountOnes& v = *as<DfgCountOnes>();
        CHECK(v.isPacked(), "Should be Packed type");
        CHECK(v.srcp()->isPacked(), "Source should be Packed type");
        CHECK(v.size() == 32U, "Should yield a 32-bit result");
        return;
    }

    case VDfgType::Pow:
    case VDfgType::PowSS:
    case VDfgType::PowSU:
    case VDfgType::PowUS: {
        CHECK(isPacked(), "Should be Packed type");
        CHECK(inputp(0)->dtype() == dtype(), "LHS should be same type");
        CHECK(inputp(1)->isPacked(), "RHS should be Packed type");
        return;
    }
    }
#undef CHECK
}

uint32_t DfgVertex::fanout() const {
    uint32_t result = 0;
    foreachSink([&](const DfgVertex&) {
        ++result;
        return false;
    });
    return result;
}

DfgVertexVar* DfgVertex::getResultVar() {
    // It's easy if the vertex is already a variable ...
    if (DfgVertexVar* const varp = this->cast<DfgVertexVar>()) 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<DfgVertexVar>();
        if (!varp) return false;

        // Do not use it if value might differ from its drivers
        if (varp->isVolatile()) return false;

        // First variable found
        if (!resp) {
            resp = varp;
            return false;
        }

        // Prefer those variables that must be kept anyway
        if (resp->hasExtRdRefs() != varp->hasExtRdRefs()) {
            if (!resp->hasExtRdRefs()) resp = varp;
            return false;
        }
        if (resp->hasDfgRefs() != varp->hasDfgRefs()) {
            if (!resp->hasDfgRefs()) resp = varp;
            return false;
        }

        // Prefer real variables over temporaries
        const bool resIsTemp = resp->vscp()->varp()->isTemp();
        const bool varIsTemp = varp->vscp()->varp()->isTemp();
        if (resIsTemp != varIsTemp) {
            if (resIsTemp) resp = varp;
            return false;
        }
        if (!resp->tmpForp() != !varp->tmpForp()) {
            if (resp->tmpForp()) resp = varp;
            return false;
        }

        // 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 false;
        }
        // Prefer the one with the lexically smaller name
        if (const int cmp = resp->vscp()->name().compare(varp->vscp()->name())) {
            if (cmp > 0) resp = varp;
            return false;
        }
        // 'resp' and 'varp' are all the same, keep using the existing 'resp'
        return false;
    });
    return resp;
}

AstScope* DfgVertex::scopep(ScopeCache& cache, bool tryResultVar) VL_MT_DISABLED {
    // If this is a variable, we are done
    if (const DfgVertexVar* const varp = this->cast<DfgVertexVar>()) {
        return varp->vscp()->scopep();
    }

    // Try the result var first if instructed (usully only in the recursive case)
    if (tryResultVar) {
        if (const DfgVertexVar* const varp = this->getResultVar()) {
            return varp->vscp()->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<AstScope*>(1);
        // Find scope based on sources, falling back on the root scope
        AstScope* const rootp = v3Global.rootp()->topScopep()->scopep();
        AstScope* foundp = nullptr;
        foreachSource([&](DfgVertex& src) {
            AstScope* const scp = src.scopep(cache, true);
            if (scp != rootp) {
                foundp = scp;
                return true;
            }
            return false;
        });
        resultr = foundp ? foundp : rootp;
    }

    // Die on a graph circular through operation vertices
    UASSERT_OBJ(resultr != reinterpret_cast<AstScope*>(1), this,
                "DfgVertex::scopep called on graph with circular operations");

    // Done
    return resultr;
}

void DfgVertex::unlinkDelete(DfgGraph& dfg) {
    // Unlink sink edges
    while (!m_sinks.empty()) m_sinks.frontp()->unlinkSrcp();
    // Remove from graph
    dfg.removeVertex(*this);
    // Delete - this will unlink sources
    delete this;
}

class DfgPatternString final {
    std::ostream& m_os;

    std::map<std::string, std::string> m_internedConsts;  // Interned constants
    std::map<uint32_t, std::string> m_internedSelLsbs;  // Interned lsb value for selects
    std::map<uint32_t, std::string> m_internedWordWidths;  // Interned widths
    std::map<uint32_t, std::string> m_internedWideWidths;  // Interned widths
    std::map<const DfgVertex*, std::string> m_internedVertices;  // Interned vertices
    // Multiplicity and depth of vertices
    std::map<const DfgVertex*, std::pair<uint32_t, uint32_t>> m_multiplicityAndDepth;

    static std::string toLetters(size_t value, bool lowerCase = false) {
        const char base = lowerCase ? 'a' : 'A';
        std::string s;
        do { s += static_cast<char>(base + value % 26); } while (value /= 26);
        return s;
    }

    const std::string& internConst(const DfgConst& vtx) {
        const auto pair = m_internedConsts.emplace(vtx.num().ascii(false), "");
        if (pair.second) pair.first->second += toLetters(m_internedConsts.size() - 1);
        return pair.first->second;
    }

    const std::string& internSelLsb(uint32_t value) {
        const auto pair = m_internedSelLsbs.emplace(value, "");
        if (pair.second) pair.first->second += toLetters(m_internedSelLsbs.size() - 1);
        return pair.first->second;
    }

    const std::string& internWordWidth(uint32_t value) {
        const auto pair = m_internedWordWidths.emplace(value, "");
        if (pair.second) pair.first->second += toLetters(m_internedWordWidths.size() - 1, true);
        return pair.first->second;
    }

    const std::string& internWideWidth(uint32_t value) {
        const auto pair = m_internedWideWidths.emplace(value, "");
        if (pair.second) pair.first->second += toLetters(m_internedWideWidths.size() - 1);
        return pair.first->second;
    }

    const std::string& internVertex(const DfgVertex& vtx) {
        const auto pair = m_internedVertices.emplace(&vtx, "");
        if (pair.second) pair.first->second += toLetters(m_internedVertices.size() - 1);
        return pair.first->second;
    }

    void recordMultiplicityAndDepth(const DfgVertex& vtx, uint32_t depth) {
        std::pair<uint32_t, uint32_t>& value = m_multiplicityAndDepth
                                                   .emplace(std::piecewise_construct,  //
                                                            std::forward_as_tuple(&vtx),  //
                                                            std::forward_as_tuple(0, depth))
                                                   .first->second;
        value.first += 1;
        value.second = std::max(value.second, depth);
        if (!depth) return;
        vtx.foreachSource([&](const DfgVertex& src) {
            recordMultiplicityAndDepth(src, depth - 1);
            return false;
        });
    }

    // Render the vertx into ss, and return true if the recursion reached the given depth,
    // meaning an S-expression with that nesting level has been rendered.
    void render(const DfgVertex& vtx, uint32_t depth, bool isRoot = true) {
        if (const DfgConst* const constp = vtx.cast<DfgConst>()) {
            // Base case 1: constant
            if (constp->isZero()) {
                m_os << "(CONST ZERO)";
            } else if (constp->isOnes()) {
                m_os << "(CONST ONES)";
            } else {
                m_os << "(CONST #" << internConst(*constp) << ')';
            }
        } else if (!isRoot && m_multiplicityAndDepth.at(&vtx).first > 1) {
            // Base case 2: vertex appearing multiple times
            m_os << internVertex(vtx);
        } else if (!vtx.foreachSource([&](const DfgVertex&) { return true; })) {
            // Base case 3: vertex with no inputs (input variable)
            m_os << '(' << vtx.typeName() << ')';
        } else if (depth == 0) {
            // Base case 4: deep vertex (apperaing only once)
            m_os << "_";
        } else {
            // Recursively print an S-expression for the vertex
            m_os << '(';
            // Name
            m_os << vtx.typeName();
            // Specials
            if (const DfgSel* const selp = vtx.cast<DfgSel>()) {
                m_os << '@';
                if (selp->lsb() == 0) {
                    m_os << '0';
                } else {
                    m_os << internSelLsb(selp->lsb());
                }
            }
            // Operands
            vtx.foreachSource([&](const DfgVertex& src) {
                m_os << ' ';
                render(src, depth - 1, false);
                return false;
            });
            // S-expression end
            m_os << ')';
        }

        // Annotate type
        m_os << ':';
        if (!vtx.dtype().isPacked()) {
            vtx.dtype().astDtypep()->dumpSmall(m_os);
        } else {
            const uint32_t width = vtx.size();
            if (width == 1) {
                m_os << '1';
            } else if (width <= VL_QUADSIZE) {
                m_os << internWordWidth(width);
            } else {
                m_os << internWideWidth(width);
            }
        }

        // Mark it if it has multiple sinks
        if (vtx.hasMultipleSinks()) m_os << '*';
    }

public:
    DfgPatternString(std::ostream& os, const DfgVertex& vtx, uint32_t depth)
        : m_os{os} {
        recordMultiplicityAndDepth(vtx, depth);
        render(vtx, depth, false);
        using Pair = std::pair<std::string, const DfgVertex*>;
        std::vector<Pair> vertices;
        for (const auto& pair : m_multiplicityAndDepth) {
            if (pair.second.first == 1) continue;
            vertices.emplace_back(internVertex(*pair.first), pair.first);
        }
        std::sort(vertices.begin(), vertices.end(), [](const Pair& a, const Pair& b) {  //
            return a.first < b.first;
        });
        for (const Pair& pair : vertices) {
            m_os << " | " << pair.first << " is ";
            render(*pair.second, m_multiplicityAndDepth.at(pair.second).second);
        }
    }
};

std::string DfgVertex::patternString(uint32_t depth) const {
    std::ostringstream oss;
    DfgPatternString{oss, *this, depth};
    return oss.str();
}

//------------------------------------------------------------------------------
// DfgVisitor

#include "V3Dfg__gen_visitor_defns.h"  // From ./astgen
