// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add temporaries, such as for premit nodes
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2020 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
//
//*************************************************************************
// V3Premit's Transformations:
//
// Each module:
//      For each wide OP, make a a temporary variable with the wide value
//      For each deep expression, assign expression to temporary.
//
// Each display (independent transformation; here as Premit is a good point)
//      If autoflush, insert a flush
//
//*************************************************************************

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

#include "V3Global.h"
#include "V3Premit.h"
#include "V3Ast.h"

#include <algorithm>

//######################################################################
// Structure for global state

class PremitAssignVisitor final : public AstNVisitor {
private:
    // NODE STATE
    //  AstVar::user4()         // bool; occurs on LHS of current assignment
    AstUser4InUse m_inuser4;

    // STATE
    bool m_noopt = false;  // Disable optimization of variables in this block

    // METHODS
    VL_DEBUG_FUNC;  // Declare debug()

    // VISITORS
    virtual void visit(AstNodeAssign* nodep) override {
        // AstNode::user4ClearTree();  // Implied by AstUser4InUse
        // LHS first as fewer varrefs
        iterateAndNextNull(nodep->lhsp());
        // Now find vars marked as lhs
        iterateAndNextNull(nodep->rhsp());
    }
    virtual void visit(AstVarRef* nodep) override {
        // it's LHS var is used so need a deep temporary
        if (nodep->access().isWriteOrRW()) {
            nodep->varp()->user4(true);
        } else {
            if (nodep->varp()->user4()) {
                if (!m_noopt) UINFO(4, "Block has LHS+RHS var: " << nodep << endl);
                m_noopt = true;
            }
        }
    }
    virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }

public:
    // CONSTRUCTORS
    explicit PremitAssignVisitor(AstNodeAssign* nodep) {
        UINFO(4, "  PremitAssignVisitor on " << nodep << endl);
        iterate(nodep);
    }
    virtual ~PremitAssignVisitor() override = default;
    bool noOpt() const { return m_noopt; }
};

//######################################################################
// Premit state, as a visitor of each AstNode

class PremitVisitor final : public AstNVisitor {
private:
    // NODE STATE
    //  AstNodeMath::user()     -> bool.  True if iterated already
    //  AstShiftL::user2()      -> bool.  True if converted to conditional
    //  AstShiftR::user2()      -> bool.  True if converted to conditional
    //  *::user4()              -> See PremitAssignVisitor
    AstUser1InUse m_inuser1;
    AstUser2InUse m_inuser2;

    // STATE
    AstNodeModule* m_modp = nullptr;  // Current module
    AstCFunc* m_cfuncp = nullptr;  // Current block
    AstNode* m_stmtp = nullptr;  // Current statement
    AstWhile* m_inWhilep = nullptr;  // Inside while loop, special statement additions
    AstTraceInc* m_inTracep = nullptr;  // Inside while loop, special statement additions
    bool m_assignLhs = false;  // Inside assignment lhs, don't breakup extracts

    // METHODS
    VL_DEBUG_FUNC;  // Declare debug()

    bool assignNoTemp(AstNodeAssign* nodep) {
        return (VN_IS(nodep->lhsp(), VarRef) && !AstVar::scVarRecurse(nodep->lhsp())
                && VN_IS(nodep->rhsp(), Const));
    }
    void checkNode(AstNode* nodep) {
        // Consider adding a temp for this expression.
        // We need to avoid adding temps to the following:
        //   ASSIGN(x, *here*)
        //   ASSIGN(CONST*here*, VARREF(!sc))
        //   ARRAYSEL(*here*, ...)   (No wides can be in any argument but first,
        //                            so we don't check which arg is wide)
        //   ASSIGN(x, SEL*HERE*(ARRAYSEL()...)   (m_assignLhs==true handles this.)
        // UINFO(9, "   Check: " << nodep << endl);
        // UINFO(9, "     Detail stmtp=" << (m_stmtp?"Y":"N") << " U=" << (nodep->user1()?"Y":"N")
        // << " IW=" << (nodep->isWide()?"Y":"N") << endl);
        if (m_stmtp && !nodep->user1()) {  // Not already done
            if (nodep->isWide()) {
                if (m_assignLhs) {
                } else if (nodep->firstAbovep() && VN_IS(nodep->firstAbovep(), NodeAssign)
                           && assignNoTemp(VN_CAST(nodep->firstAbovep(), NodeAssign))) {
                    // Not much point if it's just a direct assignment to a constant
                } else if (VN_IS(nodep->backp(), Sel)
                           && VN_CAST(nodep->backp(), Sel)->widthp() == nodep) {
                    // AstSel::width must remain a constant
                } else if ((nodep->firstAbovep() && VN_IS(nodep->firstAbovep(), ArraySel))
                           || ((VN_IS(m_stmtp, CCall) || VN_IS(m_stmtp, CStmt))
                               && VN_IS(nodep, ArraySel))) {
                    // ArraySel's are pointer refs, ignore
                } else {
                    UINFO(4, "Cre Temp: " << nodep << endl);
                    createDeepTemp(nodep, false);
                }
            }
        }
    }

    AstVar* getBlockTemp(AstNode* nodep) {
        string newvarname = (string("__Vtemp") + cvtToStr(m_modp->varNumGetInc()));
        AstVar* varp
            = new AstVar(nodep->fileline(), AstVarType::STMTTEMP, newvarname, nodep->dtypep());
        m_cfuncp->addInitsp(varp);
        return varp;
    }

    void insertBeforeStmt(AstNode* newp) {
        // Insert newp before m_stmtp
        if (m_inWhilep) {
            // Statements that are needed for the 'condition' in a while
            // actually have to be put before & after the loop, since we
            // can't do any statements in a while's (cond).
            m_inWhilep->addPrecondsp(newp);
        } else if (m_inTracep) {
            m_inTracep->addPrecondsp(newp);
        } else if (m_stmtp) {
            AstNRelinker linker;
            m_stmtp->unlinkFrBack(&linker);
            newp->addNext(m_stmtp);
            linker.relink(newp);
        } else {
            newp->v3fatalSrc("No statement insertion point.");
        }
    }

    void createDeepTemp(AstNode* nodep, bool noSubst) {
        if (debug() > 8) nodep->dumpTree(cout, "deepin:");

        AstNRelinker linker;
        nodep->unlinkFrBack(&linker);

        AstVar* varp = getBlockTemp(nodep);
        if (noSubst) varp->noSubst(true);  // Do not remove varrefs to this in V3Const
        // Replace node tree with reference to var
        AstVarRef* newp = new AstVarRef(nodep->fileline(), varp, VAccess::READ);
        linker.relink(newp);
        // Put assignment before the referencing statement
        AstAssign* assp = new AstAssign(
            nodep->fileline(), new AstVarRef(nodep->fileline(), varp, VAccess::WRITE), nodep);
        insertBeforeStmt(assp);
        if (debug() > 8) assp->dumpTree(cout, "deepou:");
        nodep->user1(true);  // Don't add another assignment
    }

    // VISITORS
    virtual void visit(AstNodeModule* nodep) override {
        UINFO(4, " MOD   " << nodep << endl);
        VL_RESTORER(m_modp);
        {
            m_modp = nodep;
            m_cfuncp = nullptr;
            iterateChildren(nodep);
        }
    }
    virtual void visit(AstCFunc* nodep) override {
        VL_RESTORER(m_cfuncp);
        {
            m_cfuncp = nodep;
            iterateChildren(nodep);
        }
    }
    void startStatement(AstNode* nodep) {
        m_assignLhs = false;
        if (m_cfuncp) m_stmtp = nodep;
    }
    virtual void visit(AstWhile* nodep) override {
        UINFO(4, "  WHILE  " << nodep << endl);
        startStatement(nodep);
        iterateAndNextNull(nodep->precondsp());
        startStatement(nodep);
        m_inWhilep = nodep;
        iterateAndNextNull(nodep->condp());
        m_inWhilep = nullptr;
        startStatement(nodep);
        iterateAndNextNull(nodep->bodysp());
        iterateAndNextNull(nodep->incsp());
        m_stmtp = nullptr;
    }
    virtual void visit(AstNodeAssign* nodep) override {
        startStatement(nodep);
        {
            bool noopt = PremitAssignVisitor(nodep).noOpt();
            if (noopt && !nodep->user1()) {
                // Need to do this even if not wide, as e.g. a select may be on a wide operator
                UINFO(4, "Deep temp for LHS/RHS\n");
                createDeepTemp(nodep->rhsp(), false);
            }
        }
        iterateAndNextNull(nodep->rhsp());
        m_assignLhs = true;
        iterateAndNextNull(nodep->lhsp());
        m_assignLhs = false;
        m_stmtp = nullptr;
    }
    virtual void visit(AstNodeStmt* nodep) override {
        if (!nodep->isStatement()) {
            iterateChildren(nodep);
            return;
        }
        UINFO(4, "  STMT  " << nodep << endl);
        startStatement(nodep);
        iterateChildren(nodep);
        m_stmtp = nullptr;
    }
    virtual void visit(AstTraceInc* nodep) override {
        startStatement(nodep);
        m_inTracep = nodep;
        iterateChildren(nodep);
        m_inTracep = nullptr;
        m_stmtp = nullptr;
    }
    void visitShift(AstNodeBiop* nodep) {
        // Shifts of > 32/64 bits in C++ will wrap-around and generate non-0s
        if (!nodep->user2SetOnce()) {
            UINFO(4, "  ShiftFix  " << nodep << endl);
            const AstConst* shiftp = VN_CAST(nodep->rhsp(), Const);
            if (shiftp && shiftp->num().mostSetBitP1() > 32) {
                shiftp->v3error(
                    "Unsupported: Shifting of by over 32-bit number isn't supported."
                    << " (This isn't a shift of 32 bits, but a shift of 2^32, or 4 billion!)\n");
            }
            if (nodep->widthMin() <= 64  // Else we'll use large operators which work right
                                         // C operator's width must be < maximum shift which is
                                         // based on Verilog width
                && nodep->width() < (1LL << nodep->rhsp()->widthMin())) {
                AstNRelinker replaceHandle;
                nodep->unlinkFrBack(&replaceHandle);
                AstNode* constzerop;
                int m1value
                    = nodep->widthMin() - 1;  // Constant of width-1; not changing dtype width
                if (nodep->signedFlavor()) {
                    // Then over shifting gives the sign bit, not all zeros
                    // Note *NOT* clean output -- just like normal shift!
                    // Create equivalent of VL_SIGNONES_(node_width)
                    constzerop = new AstNegate(
                        nodep->fileline(),
                        new AstShiftR(nodep->fileline(), nodep->lhsp()->cloneTree(false),
                                      new AstConst(nodep->fileline(), m1value), nodep->width()));
                } else {
                    constzerop = new AstConst(nodep->fileline(), AstConst::WidthedValue(),
                                              nodep->width(), 0);
                }
                constzerop->dtypeFrom(nodep);  // unsigned

                AstNode* constwidthp = new AstConst(nodep->fileline(), AstConst::WidthedValue(),
                                                    nodep->rhsp()->widthMin(), m1value);
                constwidthp->dtypeFrom(nodep->rhsp());  // unsigned
                AstCond* newp = new AstCond(
                    nodep->fileline(),
                    new AstGte(nodep->fileline(), constwidthp, nodep->rhsp()->cloneTree(false)),
                    nodep, constzerop);
                replaceHandle.relink(newp);
            }
        }
        iterateChildren(nodep);
        checkNode(nodep);
    }
    virtual void visit(AstShiftL* nodep) override { visitShift(nodep); }
    virtual void visit(AstShiftR* nodep) override { visitShift(nodep); }
    virtual void visit(AstShiftRS* nodep) override { visitShift(nodep); }
    // Operators
    virtual void visit(AstNodeTermop* nodep) override {
        iterateChildren(nodep);
        checkNode(nodep);
    }
    virtual void visit(AstNodeUniop* nodep) override {
        iterateChildren(nodep);
        checkNode(nodep);
    }
    virtual void visit(AstNodeBiop* nodep) override {
        iterateChildren(nodep);
        checkNode(nodep);
    }
    virtual void visit(AstUCFunc* nodep) override {
        iterateChildren(nodep);
        checkNode(nodep);
    }
    virtual void visit(AstSel* nodep) override {
        iterateAndNextNull(nodep->fromp());
        {  // Only the 'from' is part of the assignment LHS
            VL_RESTORER(m_assignLhs);
            m_assignLhs = false;
            iterateAndNextNull(nodep->lsbp());
            iterateAndNextNull(nodep->widthp());
        }
        checkNode(nodep);
    }
    virtual void visit(AstArraySel* nodep) override {
        iterateAndNextNull(nodep->fromp());
        {  // Only the 'from' is part of the assignment LHS
            VL_RESTORER(m_assignLhs);
            m_assignLhs = false;
            iterateAndNextNull(nodep->bitp());
        }
        checkNode(nodep);
    }
    virtual void visit(AstAssocSel* nodep) override {
        iterateAndNextNull(nodep->fromp());
        {  // Only the 'from' is part of the assignment LHS
            VL_RESTORER(m_assignLhs);
            m_assignLhs = false;
            iterateAndNextNull(nodep->bitp());
        }
        checkNode(nodep);
    }
    virtual void visit(AstConst* nodep) override {
        iterateChildren(nodep);
        checkNode(nodep);
    }
    virtual void visit(AstNodeCond* nodep) override {
        iterateChildren(nodep);
        if (nodep->expr1p()->isWide() && !VN_IS(nodep->condp(), Const)
            && !VN_IS(nodep->condp(), VarRef)) {
            // We're going to need the expression several times in the expanded code,
            // so might as well make it a common expression
            createDeepTemp(nodep->condp(), false);
        }
        checkNode(nodep);
    }

    // Autoflush
    virtual void visit(AstDisplay* nodep) override {
        startStatement(nodep);
        iterateChildren(nodep);
        m_stmtp = nullptr;
        if (v3Global.opt.autoflush()) {
            AstNode* searchp = nodep->nextp();
            while (searchp && VN_IS(searchp, Comment)) searchp = searchp->nextp();
            if (searchp && VN_IS(searchp, Display)
                && nodep->filep()->sameGateTree(VN_CAST(searchp, Display)->filep())) {
                // There's another display next; we can just wait to flush
            } else {
                UINFO(4, "Autoflush " << nodep << endl);
                nodep->addNextHere(new AstFFlush(nodep->fileline(),
                                                 AstNode::cloneTreeNull(nodep->filep(), true)));
            }
        }
    }
    virtual void visit(AstSFormatF* nodep) override {
        iterateChildren(nodep);
        // Any strings sent to a display must be var of string data type,
        // to avoid passing a pointer to a temporary.
        for (AstNode* expp = nodep->exprsp(); expp; expp = expp->nextp()) {
            if (expp->dtypep()->basicp() && expp->dtypep()->basicp()->isString()
                && !VN_IS(expp, VarRef)) {
                createDeepTemp(expp, true);
            }
        }
    }

    //--------------------
    // Default: Just iterate
    virtual void visit(AstVar*) override {}  // Don't hit varrefs under vars
    virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }

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

//----------------------------------------------------------------------
// Top loop

//######################################################################
// Premit class functions

void V3Premit::premitAll(AstNetlist* nodep) {
    UINFO(2, __FUNCTION__ << ": " << endl);
    { PremitVisitor visitor(nodep); }  // Destruct before checking
    V3Global::dumpCheckGlobalTree("premit", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
}
