// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Replace increments/decrements with new variables
//
// 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
//
//*************************************************************************
// V3LinkInc's Transformations:
//
//      prepost_expr_visit
//        PREINC/PREDEC
//          Create a temporary __VIncrementX variable, assign the value of
//          the current variable value to it, substitute the current
//          variable with the temporary one in the statement.
//          Increment/decrement the original variable with by the given
//          value.
//        POSTINC/POSTDEC
//          Increment/decrement the current variable by the given value.
//          Create a temporary __VIncrementX variable, assign the value of
//          of the current variable (after the operation) to it. Substitute
//          The original variable with the temporary one in the statement.
//
//      prepost_stmt_visit
//        PREINC/PREDEC/POSTINC/POSTDEC
//          Increment/decrement the current variable by the given value.
//          The order (pre/post) doesn't matter outside statements thus
//          the pre/post operations are treated equally and there is no
//          need for a temporary variable.
//
//      prepost_stmt_sel_visit
//        For e.g. 'array[something_with_side_eff]++', common in UVM etc
//        PREADD/PRESUB/POSTADD/POSTSUB
//          Create temporary with array index.
//          Increment/decrement using index of the temporary.
//
//*************************************************************************

#include "V3PchAstNoMT.h"  // VL_MT_DISABLED_CODE_UNIT

#include "V3LinkInc.h"

#include "V3LinkLValue.h"

VL_DEFINE_DEBUG_FUNCTIONS;

//######################################################################

class LinkIncVisitor final : public VNVisitor {
    // NODE STATE
    //  AstLogAnd/AstLogOr::user1()  -> bool.  True if already lowered
    const VNUser1InUse m_inuser1;

    // STATE
    AstNodeFTask* m_ftaskp = nullptr;  // Function or task we're inside
    AstNodeModule* m_modp = nullptr;  // Module we're inside
    int m_modCompoundAssignmentsNum = 0;  // Var name counter
    AstNode* m_insStmtp = nullptr;  // Where to insert statement
    bool m_condEvalContext = false;  // ++/-- is in a conditionally-evaluated position
    AstNodeExpr* m_incCondp = nullptr;  // Gating condition for ++/-- in short-circuit context

    // METHODS
    void insertOnTop(AstNode* newp) {
        // Add the thing directly under the current TFunc/Module
        AstNode* stmtsp = nullptr;
        if (m_ftaskp) {
            stmtsp = m_ftaskp->stmtsp();
        } else if (m_modp) {
            stmtsp = m_modp->stmtsp();
        }
        UASSERT_OBJ(stmtsp, newp, "Variable not under FTASK/MODULE");
        newp->addNext(stmtsp->unlinkFrBackWithNext());
        if (m_ftaskp) {
            m_ftaskp->addStmtsp(newp);
        } else if (m_modp) {
            m_modp->addStmtsp(newp);
        }
    }
    void insertBeforeStmt(AstNode* nodep, AstNode* newp) {
        // Return node that must be visited, if any
        UINFOTREE(9, newp, "", "newstmt");
        UASSERT_OBJ(m_insStmtp, nodep, "Expression not underneath a statement");
        m_insStmtp->addHereThisAsNext(newp);
    }

    // VISITORS
    void visit(AstNodeModule* nodep) override {
        if (nodep->dead()) return;
        VL_RESTORER(m_modp);
        VL_RESTORER(m_modCompoundAssignmentsNum);
        m_modp = nodep;
        m_modCompoundAssignmentsNum = 0;
        iterateChildren(nodep);
    }
    void visit(AstNodeFTask* nodep) override {
        VL_RESTORER(m_ftaskp);
        m_ftaskp = nodep;
        iterateChildren(nodep);
    }
    void visit(AstNodeCoverOrAssert* nodep) override {
        VL_RESTORER(m_insStmtp);
        m_insStmtp = nodep;
        iterateAndNextNull(nodep->propp());
        m_insStmtp = nullptr;
        // Note: no iterating over sentreep here as they will be ignored anyway
        if (AstAssert* const assertp = VN_CAST(nodep, Assert)) {
            iterateAndNextNull(assertp->failsp());
        } else if (AstAssertIntrinsic* const intrinsicp = VN_CAST(nodep, AssertIntrinsic)) {
            iterateAndNextNull(intrinsicp->failsp());
        }
        iterateAndNextNull(nodep->passsp());
    }
    void visit(AstLoop* nodep) override {
        UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
        m_insStmtp = nullptr;  // First thing should be new statement
        iterateAndNextNull(nodep->stmtsp());
        m_insStmtp = nullptr;  // Next thing should be new statement
    }
    void visit(AstNodeForeach* nodep) override {
        // Special, as statements need to be put in different places
        // Body insert just before themselves
        m_insStmtp = nullptr;  // First thing should be new statement
        iterateChildren(nodep);
        // Done the loop
        m_insStmtp = nullptr;  // Next thing should be new statement
    }
    void visit(AstJumpBlock* nodep) override {
        // Special, as statements need to be put in different places
        // Body insert just before themselves
        m_insStmtp = nullptr;  // First thing should be new statement
        iterateChildren(nodep);
        // Done the loop
        m_insStmtp = nullptr;  // Next thing should be new statement
    }
    void visit(AstNodeIf* nodep) override {
        m_insStmtp = nodep;
        iterateAndNextNull(nodep->condp());
        m_insStmtp = nullptr;
        iterateAndNextNull(nodep->thensp());
        iterateAndNextNull(nodep->elsesp());
        m_insStmtp = nullptr;
    }
    void visit(AstCaseItem* nodep) override {
        {
            VL_RESTORER(m_condEvalContext);
            m_condEvalContext = true;
            iterateAndNextNull(nodep->condsp());
        }
        m_insStmtp = nullptr;  // Next thing should be new statement
        iterateAndNextNull(nodep->stmtsp());
    }
    void visit(AstDelay* nodep) override {
        m_insStmtp = nodep;
        iterateAndNextNull(nodep->lhsp());
        m_insStmtp = nullptr;
        iterateAndNextNull(nodep->stmtsp());
        m_insStmtp = nullptr;
    }
    void visit(AstEventControl* nodep) override {
        m_insStmtp = nullptr;
        iterateAndNextNull(nodep->stmtsp());
        m_insStmtp = nullptr;
    }
    void visit(AstWait* nodep) override {
        m_insStmtp = nodep;
        iterateAndNextNull(nodep->condp());
        m_insStmtp = nullptr;
        iterateAndNextNull(nodep->stmtsp());
        m_insStmtp = nullptr;
    }
    void visit(AstBegin* nodep) override {
        m_insStmtp = nullptr;  // Pretend not a statement TODO: parse ++/-- as ExprStmt
        iterateChildren(nodep);
        m_insStmtp = nullptr;  // Next thing should be new statement
    }
    void visit(AstStmtExpr* nodep) override {
        AstNodeExpr* const exprp = nodep->exprp();
        if (VN_IS(exprp, PostInc) || VN_IS(exprp, PostDec) || VN_IS(exprp, PreInc)
            || VN_IS(exprp, PreDec)) {
            // Repalce this StmtExpr with the expression, visiting it will turn it into a NodeStmt
            nodep->replaceWith(exprp->unlinkFrBack());
            VL_DO_DANGLING(pushDeletep(nodep), nodep);
            m_insStmtp = nullptr;
            iterate(exprp);
            m_insStmtp = nullptr;
            return;
        }
        visit(static_cast<AstNodeStmt*>(nodep));
    }
    void visit(AstNodeStmt* nodep) override {
        m_insStmtp = nodep;
        iterateChildren(nodep);
        m_insStmtp = nullptr;  // Next thing should be new statement
    }
    void unsupported_visit(AstNode* nodep) {
        VL_RESTORER(m_condEvalContext);
        VL_RESTORER(m_incCondp);
        m_condEvalContext = true;
        // Not rescuable by short-circuit gating; drop the gate so nested ++/-- errors
        m_incCondp = nullptr;
        UINFO(9, "Marking unsupported " << nodep);
        iterateChildren(nodep);
    }
    // Hoist LHS into a BLOCKTEMP so it is evaluated exactly once; otherwise the
    // gated RHS ++/-- could modify a variable the LHS reads.
    AstNodeExpr* captureLogicalLhsToTemp(AstNodeBiop* const nodep) {
        FileLine* const fl = nodep->fileline();
        AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBack();
        const string name = "__VincGate"s + cvtToStr(++m_modCompoundAssignmentsNum);
        AstVar* const varp = new AstVar{
            fl, VVarType::BLOCKTEMP, name, VFlagChildDType{},
            new AstRefDType{fl, AstRefDType::FlagTypeOfExpr{}, lhsp->cloneTree(true)}};
        varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
        if (m_ftaskp) varp->funcLocal(true);
        insertOnTop(varp);
        AstNode* tempInitp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, lhsp};
        // Gate by the enclosing condition so a side-effecting LHS isn't run on short-circuit
        if (m_incCondp) {
            tempInitp = new AstIf{fl, m_incCondp->cloneTreePure(true), tempInitp, nullptr};
        }
        insertBeforeStmt(nodep, tempInitp);
        nodep->lhsp(new AstVarRef{fl, varp, VAccess::READ});
        return new AstVarRef{fl, varp, VAccess::READ};
    }
    void handleShortCircuit(AstNodeBiop* const nodep, bool isOr) {
        // insertBeforeStmt retargets the parent iterator; skip on re-visit
        if (nodep->user1SetOnce()) return;
        VL_RESTORER(m_condEvalContext);
        VL_RESTORER(m_incCondp);
        iterateAndNextNull(nodep->lhsp());
        m_condEvalContext = true;
        AstNodeExpr* ownedCondp = nullptr;
        // Gate the RHS by the LHS condition if it evaluates any ++/-- anywhere within
        if (nodep->rhsp()->exists([](const AstNode* np) {
                return VN_IS(np, PreInc) || VN_IS(np, PreDec) || VN_IS(np, PostInc)
                       || VN_IS(np, PostDec) || VN_IS(np, AssignCompound);
            })) {
            // Const LHS or no statement context: clone-and-evaluate-twice is safe
            AstNodeExpr* lhsRefp = (!m_insStmtp || VN_IS(nodep->lhsp(), Const))
                                       ? nodep->lhsp()->cloneTreePure(true)
                                       : captureLogicalLhsToTemp(nodep);
            // For ||, RHS runs when LHS is false; gate by !LHS
            if (isOr) lhsRefp = new AstLogNot{nodep->fileline(), lhsRefp};
            ownedCondp = m_incCondp ? new AstLogAnd{nodep->fileline(),
                                                    m_incCondp->cloneTreePure(true), lhsRefp}
                                    : lhsRefp;
            m_incCondp = ownedCondp;
        }
        iterateAndNextNull(nodep->rhsp());
        if (ownedCondp) VL_DO_DANGLING(ownedCondp->deleteTree(), ownedCondp);
    }
    void visit(AstLogAnd* nodep) override { handleShortCircuit(nodep, /*isOr=*/false); }
    void visit(AstLogOr* nodep) override { handleShortCircuit(nodep, /*isOr=*/true); }
    void visit(AstLogEq* nodep) override { unsupported_visit(nodep); }
    void visit(AstLogIf* nodep) override { unsupported_visit(nodep); }
    void visit(AstCond* nodep) override { unsupported_visit(nodep); }
    void visit(AstPropSpec* nodep) override { unsupported_visit(nodep); }
    void prepost_visit(AstNodeUniop* const nodep) {
        // Check if we are underneath a statement
        AstSelBit* const selbitp = VN_CAST(nodep->lhsp(), SelBit);
        if (!m_insStmtp && selbitp && VN_IS(selbitp->fromp(), NodeVarRef)
            && !selbitp->bitp()->isPure()) {
            prepost_stmt_sel_visit(nodep);
        } else {
            if (!m_insStmtp) {
                prepost_stmt_visit(nodep);
            } else {
                prepost_expr_visit(nodep);
            }
        }
    }
    AstNodeExpr* getOperationp(AstNode* const nodep, AstNodeExpr* const lhsp,
                               AstNodeExpr* const rhsp) {
        if (VN_IS(nodep, PreDec) || VN_IS(nodep, PostDec)) {
            return new AstSub{nodep->fileline(), lhsp, rhsp};
        }
        if (VN_IS(nodep, PreInc) || VN_IS(nodep, PostInc)) {
            return new AstAdd{nodep->fileline(), lhsp, rhsp};
        }
        if (AstAssignCompound* assignp = VN_CAST(nodep, AssignCompound)) {
            switch (assignp->operation()) {
            case AstAssignCompound::operation::Add:
                return new AstAdd{nodep->fileline(), lhsp, rhsp};
            case AstAssignCompound::operation::And:
                return new AstAnd{nodep->fileline(), lhsp, rhsp};
            case AstAssignCompound::operation::Div:
                return new AstDiv{nodep->fileline(), lhsp, rhsp};
            case AstAssignCompound::operation::ModDiv:
                return new AstModDiv{nodep->fileline(), lhsp, rhsp};
            case AstAssignCompound::operation::Mul:
                return new AstMul{nodep->fileline(), lhsp, rhsp};
            case AstAssignCompound::operation::Or: return new AstOr{nodep->fileline(), lhsp, rhsp};
            case AstAssignCompound::operation::ShiftL:
                return new AstShiftL{nodep->fileline(), lhsp, rhsp};
            case AstAssignCompound::operation::ShiftR:
                return new AstShiftR{nodep->fileline(), lhsp, rhsp};
            case AstAssignCompound::operation::ShiftRS:
                return new AstShiftRS{nodep->fileline(), lhsp, rhsp};
            case AstAssignCompound::operation::Sub:
                return new AstSub{nodep->fileline(), lhsp, rhsp};
            case AstAssignCompound::operation::Xor:
                return new AstXor{nodep->fileline(), lhsp, rhsp};
            default:;  // Error below  // LCOV_EXCL_LINE
            }
        }
        nodep->v3fatalSrc("Unhandled compound assignment operation");
    }
    void prepost_stmt_sel_visit(AstNodeUniop* const nodep) {
        // Special case array[something]++, see comments at file top
        // UINFOTREE(9, nodep, "", "pp-stmt-sel-in");
        iterateChildren(nodep);
        FileLine* fl = nodep->fileline();
        V3Number numOne{fl, 32, 1, false};
        AstNodeExpr* const exprp = new AstConst{nodep->fileline(), numOne};

        prepost_stmt_sel_visit(nodep, nodep->lhsp(), exprp);
    }
    void prepost_stmt_sel_visit(AstAssignCompound* const nodep) {
        // Special case array[something] += expr, see comments at file top
        // UINFOTREE(9, nodep, "", "pp-stmt-sel-in");
        AstNodeExpr* const exprp = nodep->rhsp()->unlinkFrBack();

        prepost_stmt_sel_visit(nodep, nodep->lhsp(), exprp);
    }
    void prepost_stmt_sel_visit(AstNode* const nodep, AstNodeExpr* const lhsp,
                                AstNodeExpr* const exprp) {
        AstSelBit* const rdSelbitp = VN_AS(lhsp, SelBit);
        AstNodeVarRef* const rdFromp = VN_AS(rdSelbitp->fromp()->cloneTreePure(true), NodeVarRef);
        rdFromp->access(VAccess::READ);
        AstNodeExpr* const rdBitp = rdSelbitp->bitp()->unlinkFrBack();
        AstSelBit* const wrSelbitp = VN_CAST(lhsp, SelBit);
        AstNodeExpr* const wrFromp = wrSelbitp->fromp()->unlinkFrBack();
        V3LinkLValue::linkLValueSet(wrFromp);

        // Prepare a temporary variable
        FileLine* const fl = nodep->fileline();
        const string name = "__VtempIndex"s + cvtToStr(++m_modCompoundAssignmentsNum);
        AstVar* const varp = new AstVar{
            fl, VVarType::BLOCKTEMP, name, VFlagChildDType{},
            new AstRefDType{fl, AstRefDType::FlagTypeOfExpr{}, rdBitp->cloneTree(true)}};
        if (m_ftaskp) varp->funcLocal(true);

        // Declare the variable
        insertOnTop(varp);

        // Define what operation will we be doing
        AstAssign* const varAssignp
            = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, rdBitp};
        AstNode* const newp = varAssignp;

        AstNodeExpr* const valuep
            = new AstSelBit{fl, rdFromp, new AstVarRef{fl, varp, VAccess::READ}};
        AstNodeExpr* const storeTop
            = new AstSelBit{fl, wrFromp, new AstVarRef{fl, varp, VAccess::READ}};

        AstAssign* assignp
            = new AstAssign{nodep->fileline(), storeTop, getOperationp(nodep, valuep, exprp)};
        newp->addNext(assignp);

        // if (debug() >= 9) newp->dumpTreeAndNext("-pp-stmt-sel-new: ");
        nodep->replaceWith(newp);
        VL_DO_DANGLING(nodep->deleteTree(), nodep);
    }
    void prepost_stmt_visit(AstNodeUniop* const nodep) {
        iterateChildren(nodep);
        AstNodeExpr* const storeTop = nodep->lhsp()->cloneTreePure(true);
        AstNodeExpr* const valuep = nodep->lhsp()->unlinkFrBack();
        FileLine* const fl = nodep->fileline();
        V3Number numOne{fl, 32, 1, false};
        AstNodeExpr* const exprp = new AstConst{nodep->fileline(), numOne};

        prepost_stmt_visit(nodep, exprp, storeTop, valuep);
    }
    void prepost_stmt_visit(AstAssignCompound* const nodep) {
        AstNodeExpr* const exprp = nodep->rhsp()->unlinkFrBack();
        AstNodeExpr* const storeTop = nodep->lhsp()->cloneTreePure(true);
        AstNodeExpr* const valuep = nodep->lhsp()->unlinkFrBack();

        prepost_stmt_visit(nodep, exprp, storeTop, valuep);
    }
    void prepost_stmt_visit(AstNode* const nodep, AstNodeExpr* const exprp,
                            AstNodeExpr* const storeTop, AstNodeExpr* const valuep) {
        V3LinkLValue::linkLValueSet(valuep, false);
        AstAssign* const assignp
            = new AstAssign{nodep->fileline(), storeTop, getOperationp(nodep, valuep, exprp)};
        nodep->replaceWith(assignp);
        VL_DO_DANGLING(nodep->deleteTree(), nodep);
    }
    void prepost_expr_visit(AstNodeUniop* const nodep) {
        iterateChildren(nodep);
        if (m_condEvalContext && !m_incCondp) {
            nodep->v3warn(E_UNSUPPORTED, "Unsupported: Pre/post increment/decrement operator"
                                         " within a logical expression (&&, ||, ?:, etc.)");
            return;
        }
        const bool needGating = m_condEvalContext && m_incCondp;
        AstNodeExpr* const readp = nodep->lhsp();
        AstNodeExpr* const writep = nodep->lhsp()->cloneTreePure(true);
        V3LinkLValue::linkLValueSet(readp, false);

        FileLine* const fl = nodep->fileline();
        V3Number numOne{fl, 32, 1, false};
        AstNodeExpr* const newconstp = new AstConst{nodep->fileline(), numOne};

        // Prepare a temporary variable
        const string name = "__Vincrement"s + cvtToStr(++m_modCompoundAssignmentsNum);
        AstVar* const varp = new AstVar{
            fl, VVarType::BLOCKTEMP, name, VFlagChildDType{},
            new AstRefDType{fl, AstRefDType::FlagTypeOfExpr{}, readp->cloneTreePure(true)}};
        varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
        if (m_ftaskp) varp->funcLocal(true);

        // Declare the variable
        insertOnTop(varp);

        // Define what operation will we be doing
        AstNodeExpr* const operp = getOperationp(nodep, readp->cloneTreePure(true), newconstp);

        if (needGating) {
            // Short-circuit context: mutate the variable only when the gate is true.
            // The temp holds the old value, plus the new value for pre-inc/dec.
            AstAssign* const assignp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
                                                     readp->cloneTreePure(true)};
            AstNode* const incp = new AstAssign{fl, writep, operp};
            if (VN_IS(nodep, PreInc) || VN_IS(nodep, PreDec)) {
                incp->addNext(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
                                            readp->cloneTreePure(true)});
            }
            assignp->addNextHere(new AstIf{fl, m_incCondp->cloneTreePure(true), incp, nullptr});
            insertBeforeStmt(nodep, assignp);
        } else if (VN_IS(nodep, PreInc) || VN_IS(nodep, PreDec)) {
            // PreInc/PreDec operations
            // Immediately after declaration - increment it by one
            AstAssign* const assignp
                = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, operp};
            // Immediately after incrementing - assign it to the original variable
            assignp->addNext(new AstAssign{fl, writep, new AstVarRef{fl, varp, VAccess::READ}});
            insertBeforeStmt(nodep, assignp);
        } else {
            // PostInc/PostDec operations
            // Assign the original variable to the temporary one
            AstAssign* const assignp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
                                                     readp->cloneTreePure(true)};
            // Increment the original variable by one
            assignp->addNext(new AstAssign{fl, writep, operp});
            insertBeforeStmt(nodep, assignp);
        }

        // Replace the node with the temporary
        nodep->replaceWith(new AstVarRef{readp->fileline(), varp, VAccess::READ});
        VL_DO_DANGLING(nodep->deleteTree(), nodep);
    }
    void visit(AstPreInc* nodep) override { prepost_visit(nodep); }
    void visit(AstPostInc* nodep) override { prepost_visit(nodep); }
    void visit(AstPreDec* nodep) override { prepost_visit(nodep); }
    void visit(AstPostDec* nodep) override { prepost_visit(nodep); }
    void visit(AstAssignCompound* nodep) override {
        visit(static_cast<AstNodeStmt*>(nodep));
        AstSelBit* const selbitp = VN_CAST(nodep->lhsp(), SelBit);
        if (!m_insStmtp && selbitp && VN_IS(selbitp->fromp(), NodeVarRef)
            && !selbitp->bitp()->isPure()) {
            prepost_stmt_sel_visit(nodep);
        } else {
            prepost_stmt_visit(nodep);
        }
    }
    void visit(AstGenFor* nodep) override { iterateChildren(nodep); }
    void visit(AstNode* nodep) override { iterateChildren(nodep); }

public:
    // CONSTRUCTORS
    explicit LinkIncVisitor(AstNetlist* nodep) { iterate(nodep); }
    ~LinkIncVisitor() override = default;
};

//######################################################################
// Task class functions

void V3LinkInc::linkIncrements(AstNetlist* nodep) {
    UINFO(2, __FUNCTION__ << ":");
    { LinkIncVisitor{nodep}; }  // Destruct before checking
    V3Global::dumpCheckGlobalTree("linkinc", 0, dumpTreeEitherLevel() >= 3);
}
