// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Block code ordering
//
// 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
//
//*************************************************************************
//
//  Class used to construct AstCFuncs from a sequence of OrderLogicVertex's
//
//*************************************************************************

#ifndef VERILATOR_V3ORDERCFUNCEMITTER_H_
#define VERILATOR_V3ORDERCFUNCEMITTER_H_

#include "config_build.h"
#include "verilatedos.h"

#include "V3Ast.h"
#include "V3Graph.h"
#include "V3OrderGraph.h"
#include "V3Sched.h"

#include <limits>
#include <map>
#include <vector>

class V3OrderCFuncEmitter final {
    // Name component to add to function - must be unique
    const std::string m_tag;
    // True if creating slow functions
    const bool m_slow;
    // Whether to split functions
    const bool m_split = v3Global.opt.outputSplitCFuncs();
    // Size of code emitted so in the current function - for splitting
    size_t m_size = 0;
    // Maximum size of code per function
    const size_t m_splitSize = []() -> size_t {
        // Don't want to split with procCFuncs
        if (v3Global.opt.profCFuncs()) return std::numeric_limits<size_t>::max();
        // Otherwise split at the specified size (- 1, so we can do >= comparison)
        if (const size_t limit = v3Global.opt.outputSplitCFuncs()) return limit - 1;
        return std::numeric_limits<size_t>::max();
    }();
    // Current function being populated
    AstCFunc* m_funcp = nullptr;
    // Function ordinals to ensure unique names
    std::map<std::pair<AstNodeModule*, std::string>, unsigned> m_funcNums;
    // The resulting ordered CFuncs with the trigger conditions needed to call them
    std::vector<std::pair<AstCFunc*, AstSenTree*>> m_result;

    // Create a unique name for a new function
    std::string cfuncName(FileLine* flp, AstScope* scopep, AstNodeModule* modp,
                          AstSenTree* domainp) {
        std::string name = "_" + m_tag;
        name += domainp->isMulti() ? "_comb" : "_sequent";
        name += "__" + scopep->nameDotless();
        name += "__" + std::to_string(m_funcNums[{modp, name}]++);
        if (v3Global.opt.profCFuncs()) name += "__PROF__" + flp->profileFuncname();
        return name;
    }

public:
    // CONSTRUCTOR
    V3OrderCFuncEmitter(const std::string& tag, bool slow)
        : m_tag{tag}
        , m_slow{slow} {}
    VL_UNCOPYABLE(V3OrderCFuncEmitter);
    VL_UNMOVABLE(V3OrderCFuncEmitter);

    // Force the creation of a new function
    void forceNewFunction() {
        m_size = 0;
        m_funcp = nullptr;
    }

    // Retrieve list of statements which when executed will call the constructed functions
    AstNodeStmt* getStmts() {
        // The resulting list of statements we are constructing here
        AstNodeStmt* stmtsp = nullptr;
        // Current AstIf
        AstIf* ifp = nullptr;
        // Trigger conditoin of 'ifp'
        AstSenTree* pervSenTreep = nullptr;
        // Call each function under an AstIf that checks for the trigger condition
        for (const auto& pair : m_result) {
            AstCFunc* const cfuncp = pair.first;
            AstSenTree* senTreep = pair.second;
            // Create a new AstIf if the trigger is different
            if (senTreep != pervSenTreep) {
                pervSenTreep = senTreep;
                ifp = V3Sched::util::createIfFromSenTree(senTreep);
                stmtsp = AstNode::addNext(stmtsp, ifp);
            }
            // Call function when triggered
            AstCCall* const callp = new AstCCall{cfuncp->fileline(), cfuncp};
            callp->dtypeSetVoid();
            ifp->addThensp(callp->makeStmt());
        }
        // Result is now spent, reset the emitter state
        m_result.clear();
        forceNewFunction();
        // Return the list of statement
        return stmtsp;
    }

    // Emit one logic vertex
    void emitLogic(const OrderLogicVertex* lVtxp) {
        // Sensitivity domain of logic we are emitting
        AstSenTree* const domainp = lVtxp->domainp();
        // We are move the logic into a CFunc
        AstNode* const logicp = lVtxp->nodep();
        // If the logic is a procedure, we need to do a few special things
        AstNodeProcedure* const procp = VN_CAST(logicp, NodeProcedure);

        // Some properties to consider
        const bool suspendable = procp && procp->isSuspendable();
        const bool needProcess = procp && procp->needProcess();
        // TODO: This is a bit muddy: 'initial forever @(posedge clk) begin ... end' is a fancy
        //       way of saying always @(posedge clk), so it might be quite hot...
        //       Also, if m_funcp is slow, but this one isn't we should force a new function
        const bool slow = m_slow && !(suspendable && VN_IS(procp, Always));

        // Put suspendable processes into individual functions on their own
        if (suspendable) forceNewFunction();
        // When profCFuncs, create a new function for each logic vertex
        if (v3Global.opt.profCFuncs()) forceNewFunction();
        // If the new domain is different, force a new function as it needs to be called separately
        if (!m_result.empty() && m_result.back().second != domainp) forceNewFunction();

        // Process procedures per statement, so we can split CFuncs within procedures.
        // Everything else is handled as a unit.
        AstNode* const headp = [&]() -> AstNode* {
            if (!procp) return logicp->unlinkFrBack();  // Not a procedure, handle as a unit
            AstNode* const stmtsp = procp->stmtsp();
            UASSERT_OBJ(stmtsp, procp, "Empty process should have been deleted earlier");
            stmtsp->unlinkFrBackWithNext();
            // 'procp' is no longer needed but the OrderGraph still references
            // it (for debug dump). It will get deleted when deleting the
            // AstActives in V3Order.
            return stmtsp;
        }();
        // Process each statement in the list starting at headp
        for (AstNode *currp = headp, *nextp; currp; currp = nextp) {
            nextp = currp->nextp();
            // Unlink the current statement from the next statement (if any)
            if (nextp) nextp->unlinkFrBackWithNext();
            // Split the function if too large, but don't split suspendable processes
            if (!suspendable && m_size >= m_splitSize) forceNewFunction();
            // Create a new function if we don't have a current one
            if (!m_funcp) {
                UASSERT_OBJ(!m_size, currp, "Should have used forceNewFunction");
                FileLine* const flp = currp->fileline();
                AstScope* const scopep = lVtxp->scopep();
                AstNodeModule* const modp = scopep->modp();
                const std::string name = cfuncName(flp, scopep, modp, domainp);
                m_funcp = new AstCFunc{flp, name, scopep, suspendable ? "VlCoroutine" : ""};
                if (needProcess) m_funcp->setNeedProcess();
                m_funcp->isStatic(false);
                m_funcp->isLoose(true);
                m_funcp->slow(slow);
                scopep->addBlocksp(m_funcp);
                // Record function and sensitivity to call it with
                m_result.emplace_back(m_funcp, domainp);
            }
            // Add the code to the current function
            m_funcp->addStmtsp(currp);
            // If splitting, add in the size of the code we just added
            if (m_split) m_size += currp->nodeCount();
        }
        // Put suspendable processes into individual functions on their own
        if (suspendable) forceNewFunction();
    }
};

#endif  // Guard
