// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Replace increments/decrements with new variables
//
// 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
//
//*************************************************************************
// V3LinkInc's Transformations:
//
//      prepost_stmt_visit
//        PREADD/PRESUB
//          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.
//        POSTADD/POSTSUB
//          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_non_stmt_visit
//        PREADD/PRESUB/POSTADD/POSTSUB
//          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.
//
//*************************************************************************

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

#include "V3Global.h"
#include "V3LinkInc.h"
#include "V3Ast.h"

#include <algorithm>

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

class LinkIncVisitor final : public AstNVisitor {
private:
    // TYPES
    enum InsertMode : uint8_t {
        IM_BEFORE,  // Pointing at statement ref is in, insert before this
        IM_AFTER,  // Pointing at last inserted stmt, insert after
        IM_WHILE_PRECOND  // Pointing to for loop, add to body end
    };

    // STATE
    int m_modIncrementsNum = 0;  // Var name counter
    InsertMode m_insMode = IM_BEFORE;  // How to insert
    AstNode* m_insStmtp = nullptr;  // Where to insert statement
    bool m_unsupportedHere = false;  // Used to detect where it's not supported yet

private:
    void insertBeforeStmt(AstNode* nodep, AstNode* newp) {
        // Return node that must be visited, if any
        // See also AstNode::addBeforeStmt; this predates that function
        if (debug() >= 9) newp->dumpTree(cout, "-newstmt:");
        UASSERT_OBJ(m_insStmtp, nodep, "Function not underneath a statement");
        if (m_insMode == IM_BEFORE) {
            // Add the whole thing before insertAt
            if (debug() >= 9) newp->dumpTree(cout, "-newfunc:");
            m_insStmtp->addHereThisAsNext(newp);
        } else if (m_insMode == IM_AFTER) {
            m_insStmtp->addNextHere(newp);
        } else if (m_insMode == IM_WHILE_PRECOND) {
            AstWhile* whilep = VN_CAST(m_insStmtp, While);
            UASSERT_OBJ(whilep, nodep, "Insert should be under WHILE");
            whilep->addPrecondsp(newp);
        } else {
            nodep->v3fatalSrc("Unknown InsertMode");
        }
        m_insMode = IM_AFTER;
        m_insStmtp = newp;
    }

    // VISITORS
    virtual void visit(AstNodeModule* nodep) override {
        // Reset increments count
        m_modIncrementsNum = 0;
        iterateChildren(nodep);
    }
    virtual void visit(AstWhile* nodep) override {
        // Special, as statements need to be put in different places
        // Preconditions insert first just before themselves (the normal
        // rule for other statement types)
        m_insStmtp = nullptr;  // First thing should be new statement
        iterateAndNextNull(nodep->precondsp());
        // Conditions insert first at end of precondsp.
        m_insMode = IM_WHILE_PRECOND;
        m_insStmtp = nodep;
        iterateAndNextNull(nodep->condp());
        // Body insert just before themselves
        m_insStmtp = nullptr;  // First thing should be new statement
        iterateAndNextNull(nodep->bodysp());
        iterateAndNextNull(nodep->incsp());
        // Done the loop
        m_insStmtp = nullptr;  // Next thing should be new statement
    }
    virtual void visit(AstNodeIf* nodep) override {
        m_insStmtp = nodep;
        iterateAndNextNull(nodep->condp());
        m_insStmtp = nullptr;
        iterateAndNextNull(nodep->ifsp());
        iterateAndNextNull(nodep->elsesp());
        m_insStmtp = nullptr;
    }
    virtual void visit(AstNodeFor* nodep) override {  // LCOV_EXCL_LINE
        nodep->v3fatalSrc(
            "For statements should have been converted to while statements in V3Begin.cpp");
    }
    virtual void visit(AstNodeStmt* nodep) override {
        if (!nodep->isStatement()) {
            iterateChildren(nodep);
            return;
        }
        m_insMode = IM_BEFORE;
        m_insStmtp = nodep;
        iterateChildren(nodep);
        m_insStmtp = nullptr;  // Next thing should be new statement
    }
    void unsupported_visit(AstNode* nodep) {
        m_unsupportedHere = true;
        UINFO(9, "Marking unsupported " << nodep << endl);
        iterateChildren(nodep);
        m_unsupportedHere = false;
    }
    virtual void visit(AstLogAnd* nodep) override { unsupported_visit(nodep); }
    virtual void visit(AstLogOr* nodep) override { unsupported_visit(nodep); }
    virtual void visit(AstLogEq* nodep) override { unsupported_visit(nodep); }
    virtual void visit(AstLogIf* nodep) override { unsupported_visit(nodep); }
    virtual void visit(AstNodeCond* nodep) override { unsupported_visit(nodep); }
    void prepost_visit(AstNodeTriop* nodep) {
        // Check if we are underneath a statement
        if (!m_insStmtp) {
            prepost_non_stmt_visit(nodep);
        } else {
            prepost_stmt_visit(nodep);
        }
    }
    void prepost_non_stmt_visit(AstNodeTriop* nodep) {
        iterateChildren(nodep);

        AstConst* constp = VN_CAST(nodep->lhsp(), Const);
        UASSERT_OBJ(nodep, constp, "Expecting CONST");
        AstConst* newconstp = constp->cloneTree(true);

        AstNode* storetop = nodep->thsp();
        AstNode* valuep = nodep->rhsp();

        storetop->unlinkFrBack();
        valuep->unlinkFrBack();

        AstAssign* assignp;
        if (VN_IS(nodep, PreSub) || VN_IS(nodep, PostSub)) {
            assignp = new AstAssign(nodep->fileline(), storetop,
                                    new AstSub(nodep->fileline(), valuep, newconstp));
        } else {
            assignp = new AstAssign(nodep->fileline(), storetop,
                                    new AstAdd(nodep->fileline(), valuep, newconstp));
        }
        nodep->replaceWith(assignp);
        VL_DO_DANGLING(nodep->deleteTree(), nodep);
    }
    void prepost_stmt_visit(AstNodeTriop* nodep) {
        iterateChildren(nodep);

        AstNodeVarRef* varrefp;
        if (m_unsupportedHere || !(varrefp = VN_CAST(nodep->rhsp(), VarRef))) {
            nodep->v3warn(E_UNSUPPORTED, "Unsupported: Incrementation in this context.");
            return;
        }

        AstConst* constp = VN_CAST(nodep->lhsp(), Const);
        UASSERT_OBJ(nodep, constp, "Expecting CONST");
        AstNode* backp = nodep->backp();
        AstConst* newconstp = constp->cloneTree(true);

        // Prepare a temporary variable
        FileLine* fl = backp->fileline();
        string name = string("__Vincrement") + cvtToStr(++m_modIncrementsNum);
        AstVar* varp = new AstVar(fl, AstVarType::BLOCKTEMP, name, VFlagChildDType(),
                                  varrefp->varp()->subDTypep()->cloneTree(true));

        // Declare the variable
        insertBeforeStmt(nodep, varp);

        // Define what operation will we be doing
        AstNode* operp;
        if (VN_IS(nodep, PostSub) || VN_IS(nodep, PreSub)) {
            operp = new AstSub(fl, new AstVarRef(fl, varrefp->varp(), VAccess::READ), newconstp);
        } else {
            operp = new AstAdd(fl, new AstVarRef(fl, varrefp->varp(), VAccess::READ), newconstp);
        }

        if (VN_IS(nodep, PreAdd) || VN_IS(nodep, PreSub)) {
            // PreAdd/PreSub operations
            // Immediately after declaration - increment it by one
            m_insStmtp->addHereThisAsNext(
                new AstAssign(fl, new AstVarRef(fl, varp, VAccess::WRITE), operp));
            // Immediately after incrementing - assign it to the original variable
            m_insStmtp->addHereThisAsNext(
                new AstAssign(fl, new AstVarRef(fl, varrefp->varp(), VAccess::WRITE),
                              new AstVarRef(fl, varp, VAccess::READ)));
        } else {
            // PostAdd/PostSub operations
            // assign the original variable to the temporary one
            m_insStmtp->addHereThisAsNext(
                new AstAssign(fl, new AstVarRef(fl, varp, VAccess::WRITE),
                              new AstVarRef(fl, varrefp->varp(), VAccess::READ)));
            // Increment the original variable by one
            m_insStmtp->addHereThisAsNext(
                new AstAssign(fl, new AstVarRef(fl, varrefp->varp(), VAccess::WRITE), operp));
        }

        // Replace the node with the temporary
        nodep->replaceWith(new AstVarRef(varrefp->fileline(), varp, VAccess::WRITE));
        VL_DO_DANGLING(nodep->deleteTree(), nodep);
    }
    virtual void visit(AstPreAdd* nodep) override { prepost_visit(nodep); }
    virtual void visit(AstPostAdd* nodep) override { prepost_visit(nodep); }
    virtual void visit(AstPreSub* nodep) override { prepost_visit(nodep); }
    virtual void visit(AstPostSub* nodep) override { prepost_visit(nodep); }
    virtual void visit(AstGenFor* nodep) override { iterateChildren(nodep); }
    virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }

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

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

void V3LinkInc::linkIncrements(AstNetlist* nodep) {
    UINFO(2, __FUNCTION__ << ": " << endl);
    { LinkIncVisitor bvisitor(nodep); }  // Destruct before checking
    V3Global::dumpCheckGlobalTree("linkInc", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
}
