// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: DfgGraph common sub-expression elimination (CSE)
//
// 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 "V3DfgPasses.h"

VL_DEFINE_DEBUG_FUNCTIONS;

class V3DfgCse final {
    // TYPES
    using VertexPair = std::pair<const DfgVertex*, const DfgVertex*>;
    struct VertexPairHash final {
        size_t operator()(const VertexPair& pair) const {
            V3Hash hash;
            hash += pair.first;
            hash += pair.second;
            return hash.value();
        }
    };

    // STATE
    // The graph being processed
    DfgGraph& m_dfg;
    // Cache for vertex hashes
    DfgUserMap<V3Hash> m_hashCache = m_dfg.makeUserMap<V3Hash>();
    // Cache for vertex equality
    std::unordered_map<VertexPair, uint8_t, VertexPairHash> m_equivalentCache;

    // METHODS
    // Returns hash of vertex dependent on information internal to the vertex
    static V3Hash vertexSelfHash(const DfgVertex& vtx) {
        switch (vtx.type()) {
        // Unhandled vertices
        case VDfgType::Logic:  // LCOV_EXCL_START
        case VDfgType::Unresolved:  // LCOV_EXCL_STOP
            vtx.v3fatalSrc("Should not have reached CSE");

        // Special vertices
        case VDfgType::Const:  // LCOV_EXCL_START
        case VDfgType::CReset:
        case VDfgType::VarArray:
        case VDfgType::VarPacked:
        case VDfgType::AstRd:  // LCOV_EXCL_STOP
            vtx.v3fatalSrc("Hash should have been pre-computed");

        // Vertices with internal information
        case VDfgType::Sel: return V3Hash{vtx.as<DfgSel>()->lsb()};

        case VDfgType::SpliceArray:
        case VDfgType::SplicePacked: {
            V3Hash hash;
            vtx.as<DfgVertexSplice>()->foreachDriver([&](const DfgVertex&, uint32_t lo) {
                hash += lo;
                return false;
            });
            return hash;
        }

        // Vertices with no internal information
        case VDfgType::MatchMasked:
        case VDfgType::Mux:
        case VDfgType::UnitArray: return V3Hash{};

        // Generated classes - none of them have internal information
        case VDfgType::Add:
        case VDfgType::And:
        case VDfgType::ArraySel:
        case VDfgType::Concat:
        case VDfgType::Cond:
        case VDfgType::CountOnes:
        case VDfgType::Div:
        case VDfgType::DivS:
        case VDfgType::Eq:
        case VDfgType::EqCase:
        case VDfgType::EqWild:
        case VDfgType::Extend:
        case VDfgType::ExtendS:
        case VDfgType::Gt:
        case VDfgType::GtS:
        case VDfgType::Gte:
        case VDfgType::GteS:
        case VDfgType::LogAnd:
        case VDfgType::LogEq:
        case VDfgType::LogIf:
        case VDfgType::LogNot:
        case VDfgType::LogOr:
        case VDfgType::Lt:
        case VDfgType::LtS:
        case VDfgType::Lte:
        case VDfgType::LteS:
        case VDfgType::ModDiv:
        case VDfgType::ModDivS:
        case VDfgType::Mul:
        case VDfgType::MulS:
        case VDfgType::Negate:
        case VDfgType::Neq:
        case VDfgType::NeqCase:
        case VDfgType::NeqWild:
        case VDfgType::Not:
        case VDfgType::OneHot:
        case VDfgType::OneHot0:
        case VDfgType::Or:
        case VDfgType::Pow:
        case VDfgType::PowSS:
        case VDfgType::PowSU:
        case VDfgType::PowUS:
        case VDfgType::RedAnd:
        case VDfgType::RedOr:
        case VDfgType::RedXor:
        case VDfgType::Rep:
        case VDfgType::ShiftL:
        case VDfgType::ShiftR:
        case VDfgType::ShiftRS:
        case VDfgType::StreamL:
        case VDfgType::StreamR:
        case VDfgType::Sub:
        case VDfgType::Xor: return V3Hash{};
        }
        VL_UNREACHABLE;
    }

    // Returns hash of vertex dependent on and all its input
    V3Hash vertexHash(DfgVertex& vtx) {
        V3Hash& result = m_hashCache[vtx];
        if (!result.value()) {
            V3Hash hash{vertexSelfHash(vtx)};
            // 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 (!vtx.is<DfgVertexVar>()) {
                hash += vtx.type();
                hash += vtx.size();
                vtx.foreachSource([&](DfgVertex& src) {
                    hash += vertexHash(src);
                    return false;
                });
            }
            result = hash;
        }
        return result;
    }

    // Compare 'a' and 'b' for equivalence based on their internal information only
    bool vertexSelfEquivalent(const DfgVertex& a, const DfgVertex& b) {
        // Note: 'a' and 'b' are of the same Vertex type, data type, and have
        // the same number of inputs with matching types. This is established
        // by 'vertexEquivalent'.
        switch (a.type()) {
        // Unhandled vertices
        case VDfgType::Logic:  // LCOV_EXCL_START
        case VDfgType::Unresolved:  // LCOV_EXCL_STOP
            a.v3fatalSrc("Should not have reached CSE");

        // Not reachable via operation vertices
        case VDfgType::AstRd:  // LCOV_EXCL_LINE
            a.v3fatalSrc("Should not be reachable via operation vertices");

        // Special vertices
        case VDfgType::Const: return a.as<DfgConst>()->num().isCaseEq(b.as<DfgConst>()->num());
        case VDfgType::CReset: return false;

        case VDfgType::VarArray:
        case VDfgType::VarPacked:  // CSE does not combine variables
            return false;

        // Vertices with internal information
        case VDfgType::Sel: return a.as<DfgSel>()->lsb() == b.as<DfgSel>()->lsb();

        case VDfgType::SpliceArray:
        case VDfgType::SplicePacked: {
            const DfgVertexSplice* const ap = a.as<DfgVertexSplice>();
            // Gather indices of drivers of 'a'
            std::vector<uint32_t> aLo;
            aLo.reserve(ap->nInputs());
            ap->foreachDriver([&](const DfgVertex&, uint32_t lo) {
                aLo.push_back(lo);
                return false;
            });
            // Compare indices of drivers of 'b'
            uint32_t* aLop = aLo.data();
            return !b.as<DfgVertexSplice>()->foreachDriver(
                [&](const DfgVertex&, uint32_t lo) { return *aLop++ != lo; });
        }

        // Vertices with no internal information
        case VDfgType::MatchMasked:
        case VDfgType::Mux:
        case VDfgType::UnitArray: return true;

        // Generated classes - none of them have internal information
        case VDfgType::Add:
        case VDfgType::And:
        case VDfgType::ArraySel:
        case VDfgType::Concat:
        case VDfgType::Cond:
        case VDfgType::CountOnes:
        case VDfgType::Div:
        case VDfgType::DivS:
        case VDfgType::Eq:
        case VDfgType::EqCase:
        case VDfgType::EqWild:
        case VDfgType::Extend:
        case VDfgType::ExtendS:
        case VDfgType::Gt:
        case VDfgType::GtS:
        case VDfgType::Gte:
        case VDfgType::GteS:
        case VDfgType::LogAnd:
        case VDfgType::LogEq:
        case VDfgType::LogIf:
        case VDfgType::LogNot:
        case VDfgType::LogOr:
        case VDfgType::Lt:
        case VDfgType::LtS:
        case VDfgType::Lte:
        case VDfgType::LteS:
        case VDfgType::ModDiv:
        case VDfgType::ModDivS:
        case VDfgType::Mul:
        case VDfgType::MulS:
        case VDfgType::Negate:
        case VDfgType::Neq:
        case VDfgType::NeqCase:
        case VDfgType::NeqWild:
        case VDfgType::Not:
        case VDfgType::OneHot:
        case VDfgType::OneHot0:
        case VDfgType::Or:
        case VDfgType::Pow:
        case VDfgType::PowSS:
        case VDfgType::PowSU:
        case VDfgType::PowUS:
        case VDfgType::RedAnd:
        case VDfgType::RedOr:
        case VDfgType::RedXor:
        case VDfgType::Rep:
        case VDfgType::ShiftL:
        case VDfgType::ShiftR:
        case VDfgType::ShiftRS:
        case VDfgType::StreamL:
        case VDfgType::StreamR:
        case VDfgType::Sub:
        case VDfgType::Xor: return true;
        }
        VL_UNREACHABLE;
    }

    // Compares 'a' and 'b' for equivalence
    bool vertexEquivalent(const DfgVertex& a, const DfgVertex& b) {
        // If same vertex, then equal
        if (&a == &b) return true;

        // If different type, then not equal
        if (a.type() != b.type()) return false;

        // If different data type, then not equal
        if (a.dtype() != b.dtype()) return false;

        // If different number of inputs, then not equal
        if (a.nInputs() != b.nInputs()) return false;

        // Check vertex specifics
        if (!vertexSelfEquivalent(a, b)) return false;

        // Check sources
        const VertexPair key = (&a < &b) ? std::make_pair(&a, &b) : std::make_pair(&b, &a);
        // The recursive invocation can cause a re-hash but that will not invalidate references
        uint8_t& result = m_equivalentCache[key];
        if (!result) {
            const bool equal = [&]() {
                for (size_t i = 0; i < a.nInputs(); ++i) {
                    const DfgVertex* const ap = a.inputp(i);
                    const DfgVertex* const bp = b.inputp(i);
                    if (!ap && !bp) continue;
                    if (!ap || !bp) return false;
                    if (!vertexEquivalent(*ap, *bp)) return false;
                }
                return true;
            }();
            result = (static_cast<uint8_t>(equal) << 1) | 1;
        }
        return result >> 1;
    }

    V3DfgCse(DfgGraph& dfg, V3DfgCseContext& ctx)
        : m_dfg{dfg} {
        std::unordered_map<V3Hash, std::vector<DfgVertex*>> verticesWithEqualHashes;
        verticesWithEqualHashes.reserve(dfg.size());

        // Pre-hash variables, these are all unique, so just set their hash to a unique value
        uint32_t varHash = 0;
        for (const DfgVertexVar& vtx : dfg.varVertices()) m_hashCache[vtx] = V3Hash{++varHash};
        // Pre-hash Ast references, these are all unique like variables
        for (const DfgVertexAst& vtx : dfg.astVertices()) m_hashCache[vtx] = V3Hash{++varHash};
        // Pre-hash CReset vertices, these are all unique
        for (const DfgVertex& vtx : dfg.opVertices()) {
            if (vtx.is<DfgCReset>()) m_hashCache[vtx] = V3Hash{++varHash};
        }

        // Similarly pre-hash constants for speed. While we don't combine constants, we do want
        // expressions using the same constants to be combined, so we do need to hash equal
        // constants to equal values.
        for (DfgConst* const vtxp : dfg.constVertices().unlinkable()) {
            // Delete unused constants while we are at it.
            if (!vtxp->hasSinks()) {
                VL_DO_DANGLING(vtxp->unlinkDelete(dfg), vtxp);
                continue;
            }
            m_hashCache[vtxp] = vtxp->num().toHash() + varHash;
        }

        // Combine operation vertices
        for (DfgVertex* const vtxp : dfg.opVertices().unlinkable()) {
            // Delete unused nodes while we are at it.
            if (!vtxp->hasSinks()) {
                vtxp->unlinkDelete(dfg);
                continue;
            }
            std::vector<DfgVertex*>& vec = verticesWithEqualHashes[vertexHash(*vtxp)];
            bool replaced = false;
            for (DfgVertex* const candidatep : vec) {
                if (vertexEquivalent(*candidatep, *vtxp)) {
                    ++ctx.m_eliminated;
                    vtxp->replaceWith(candidatep);
                    VL_DO_DANGLING(vtxp->unlinkDelete(dfg), vtxp);
                    replaced = true;
                    break;
                }
            }
            if (replaced) continue;
            vec.push_back(vtxp);
        }
    }

public:
    static void apply(DfgGraph& dfg, V3DfgCseContext& ctx) {
        { V3DfgCse{dfg, ctx}; }
        // Prune unused nodes
        V3DfgPasses::removeUnused(dfg);
    }
};

void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) { V3DfgCse::apply(dfg, ctx); }
