// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Convert BLOCKTEMPs to local variables
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2021 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
//
//*************************************************************************
// LOCALIZE TRANSFORMATIONS:
//      All modules:
//          VAR(BLOCKTEMP...
//             if only referenced in a CFUNC, make it local to that CFUNC
//          VAR(others
//             if non-public, set before used, and in single CFUNC, make it local
//
//*************************************************************************

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

#include "V3Global.h"
#include "V3Localize.h"
#include "V3Stats.h"
#include "V3Ast.h"

#include <vector>

//######################################################################
// Localize base class

class LocalizeBaseVisitor VL_NOT_FINAL : public AstNVisitor {
protected:
    // NODE STATE
    // Cleared on entire tree
    //  AstVar::user1p()        -> CFunc which references the variable
    //  AstVar::user2()         -> VarFlags.  Flag state
    //  AstVar::user4()         -> AstVarRef*.  First place signal set; must be first assignment

    // METHODS
    VL_DEBUG_FUNC;  // Declare debug()

    // TYPES
    union VarFlags {
        // Per-variable flags
        // Used in user()'s so initializes to all zeros
        struct {
            int m_notOpt : 1;  // NOT optimizable
            int m_notStd : 1;  // NOT optimizable if a non-blocktemp signal
            int m_stdFuncAsn : 1;  // Found simple assignment
            int m_done : 1;  // Removed
        };
        // cppcheck-suppress unusedStructMember
        uint32_t m_flags;
        explicit VarFlags(AstNode* nodep) { m_flags = nodep->user2(); }
        void setNodeFlags(AstNode* nodep) { nodep->user2(m_flags); }
    };
};

//######################################################################
// Localize class functions

class LocalizeDehierVisitor final : public LocalizeBaseVisitor {
private:
    // NODE STATE/TYPES
    // See above

    // METHODS
    virtual void visit(AstVarRef* nodep) override {
        // cppcheck-suppress unreadVariable  // cppcheck 1.90 bug
        VarFlags flags(nodep->varp());
        if (flags.m_done) {
            nodep->hiernameToProt("");  // Remove this->
            nodep->hiernameToUnprot("");  // Remove this->
            nodep->hierThis(true);
        }
    }
    virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }

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

//######################################################################
// Localize class functions

class LocalizeVisitor final : public LocalizeBaseVisitor {
private:
    // NODE STATE/TYPES
    // See above
    AstUser1InUse m_inuser1;
    AstUser2InUse m_inuser2;
    AstUser4InUse m_inuser4;

    // STATE
    VDouble0 m_statLocVars;  // Statistic tracking
    AstCFunc* m_cfuncp = nullptr;  // Current active function
    std::vector<AstVar*> m_varps;  // List of variables to consider for deletion

    // METHODS
    void clearOptimizable(AstVar* nodep, const char* reason) {
        UINFO(4, "       NoOpt " << reason << " " << nodep << endl);
        VarFlags flags(nodep);
        flags.m_notOpt = true;
        flags.setNodeFlags(nodep);
    }
    void clearStdOptimizable(AstVar* nodep, const char* reason) {
        UINFO(4, "       NoStd " << reason << " " << nodep << endl);
        VarFlags flags(nodep);
        flags.m_notStd = true;
        flags.setNodeFlags(nodep);
    }
    void moveVars() {
        for (AstVar* nodep : m_varps) {
            if (nodep->valuep()) clearOptimizable(nodep, "HasInitValue");
            if (!VarFlags(nodep).m_stdFuncAsn) clearStdOptimizable(nodep, "NoStdAssign");
            VarFlags flags(nodep);

            if ((nodep->isMovableToBlock()  // Blocktemp
                 || !flags.m_notStd)  // Or used only in block
                && !flags.m_notOpt  // Optimizable
                && !nodep->isClassMember() && nodep->user1p()) {  // Single cfunc
                // We don't need to test for tracing; it would be in the tracefunc if it was needed
                UINFO(4, "  ModVar->BlkVar " << nodep << endl);
                ++m_statLocVars;
                AstCFunc* newfuncp = VN_CAST(nodep->user1p(), CFunc);
                nodep->unlinkFrBack();
                newfuncp->addInitsp(nodep);
                // Done
                flags.m_done = true;
                flags.setNodeFlags(nodep);
            } else {
                clearOptimizable(nodep, "NotDone");
            }
        }
        m_varps.clear();
    }

    // VISITORS
    virtual void visit(AstNetlist* nodep) override {
        iterateChildren(nodep);
        moveVars();
    }
    virtual void visit(AstCFunc* nodep) override {
        UINFO(4, "  CFUNC " << nodep << endl);
        VL_RESTORER(m_cfuncp);
        {
            m_cfuncp = nodep;
            searchFuncStmts(nodep->argsp());
            searchFuncStmts(nodep->initsp());
            searchFuncStmts(nodep->stmtsp());
            searchFuncStmts(nodep->finalsp());
            iterateChildren(nodep);
        }
    }
    void searchFuncStmts(AstNode* nodep) {
        // Search for basic assignments to allow moving non-blocktemps
        // For now we only find simple assignments not under any other statement.
        // This could be more complicated; allow always-set under both branches of a IF.
        // If so, check for ArrayRef's and such, as they aren't acceptable.
        for (; nodep; nodep = nodep->nextp()) {
            if (VN_IS(nodep, NodeAssign)) {
                if (AstVarRef* varrefp = VN_CAST(VN_CAST(nodep, NodeAssign)->lhsp(), VarRef)) {
                    UASSERT_OBJ(varrefp->access().isWriteOrRW(), varrefp,
                                "LHS assignment not lvalue");
                    if (!varrefp->varp()->user4p()) {
                        UINFO(4, "      FuncAsn " << varrefp << endl);
                        varrefp->varp()->user4p(varrefp);
                        VarFlags flags(varrefp->varp());
                        flags.m_stdFuncAsn = true;
                        flags.setNodeFlags(varrefp->varp());
                    }
                }
            }
        }
    }

    virtual void visit(AstVar* nodep) override {
        if (!nodep->isSigPublic() && !nodep->isPrimaryIO()
            && !m_cfuncp) {  // Not already inside a function
            UINFO(4, "    BLKVAR " << nodep << endl);
            m_varps.push_back(nodep);
        }
        // No iterate; Don't want varrefs under it
    }
    virtual void visit(AstVarRef* nodep) override {
        if (!VarFlags(nodep->varp()).m_notOpt) {
            if (!m_cfuncp) {  // Not in function, can't optimize
                // Perhaps impossible, but better safe
                clearOptimizable(nodep->varp(), "BVnofunc");  // LCOV_EXCL_LINE
            } else {
                // If we're scoping down to it, it isn't really in the same block
                if (!nodep->hierThis()) clearOptimizable(nodep->varp(), "HierRef");
                // Allow a variable to appear in only a single function
                AstNode* oldfunc = nodep->varp()->user1p();
                if (!oldfunc) {
                    UINFO(4, "      BVnewref " << nodep << endl);
                    nodep->varp()->user1p(m_cfuncp);  // Remember where it was used
                } else if (m_cfuncp == oldfunc) {
                    // Same usage
                } else {
                    // Used in multiple functions
                    clearOptimizable(nodep->varp(), "BVmultiF");
                }
                // First varref in function must be assignment found earlier
                AstVarRef* firstasn = static_cast<AstVarRef*>(nodep->varp()->user4p());
                if (firstasn && nodep != firstasn) {
                    clearStdOptimizable(nodep->varp(), "notFirstAsn");
                    nodep->varp()->user4p(nullptr);
                }
            }
        }
        // No iterate; Don't want varrefs under it
    }
    virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }

public:
    // CONSTRUCTORS
    explicit LocalizeVisitor(AstNetlist* nodep) { iterate(nodep); }
    virtual ~LocalizeVisitor() override {
        V3Stats::addStat("Optimizations, Vars localized", m_statLocVars);
    }
};

//######################################################################
// Localize class functions

void V3Localize::localizeAll(AstNetlist* nodep) {
    UINFO(2, __FUNCTION__ << ": " << endl);
    {
        LocalizeVisitor visitor(nodep);
        // Fix up hiernames
        LocalizeDehierVisitor dvisitor(nodep);
    }  // Destruct before checking
    V3Global::dumpCheckGlobalTree("localize", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 6);
}
