// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Parse module/signal name references
//
// 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
//
//*************************************************************************
// Slice TRANSFORMATIONS:
//      Top-down traversal (SliceVisitor):
//        NODEASSIGN
//          ARRAYSEL
//            Compare the dimensions to the Var to check for implicit slices.
//            Using ->length() calculate the number of clones needed.
//          VARREF
//            Check the dimensions of the Var for an implicit slice.
//            Replace with ArraySel nodes if needed.
//          SEL, EXTEND
//            We might be assigning a 1-D packed array to a 2-D packed array,
//            this is unsupported.
//          SliceCloneVisitor (called if this node is a slice):
//            NODEASSIGN
//              Clone and iterate the clone:
//                ARRAYSEL
//                  Modify bitp() for the new value and set ->length(1)
//
// TODO: This code was written before SLICESEL was a type it might be
// simplified to look primarily for SLICESELs.
//*************************************************************************

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

#include "V3Global.h"
#include "V3Slice.h"
#include "V3Ast.h"

//*************************************************************************

class SliceVisitor : public AstNVisitor {
    // NODE STATE
    // Cleared on netlist
    //  AstNodeAssign::user1()      -> bool.  True if find is complete
    //  AstNodeUniop::user1()       -> bool.  True if find is complete
    //  AstArraySel::user1p()       -> AstVarRef. The VarRef that the final ArraySel points to
    AstUser1InUse m_inuser1;

    // STATE
    AstNode* m_assignp;  // Assignment we are under
    bool m_assignError;  // True if the current assign already has an error

    // METHODS
    VL_DEBUG_FUNC;  // Declare debug()

    AstNode* cloneAndSel(AstNode* nodep, int elements, int offset) {
        // Insert an ArraySel, except for a few special cases
        AstUnpackArrayDType* arrayp = VN_CAST(nodep->dtypep()->skipRefp(), UnpackArrayDType);
        if (!arrayp) {  // V3Width should have complained, but...
            if (!m_assignError) {
                nodep->v3error(
                    nodep->prettyTypeName()
                    << " is not an unpacked array, but is in an unpacked array context");
            }
            m_assignError = true;
            return nodep->cloneTree(false);  // Likely will cause downstream errors
        }
        if (arrayp->rangep()->elementsConst() != elements) {
            if (!m_assignError) {
                nodep->v3error(
                    "Slices of arrays in assignments have different unpacked dimensions, "
                    << elements << " versus " << arrayp->rangep()->elementsConst());
            }
            m_assignError = true;
            elements = 1;
            offset = 0;
        }
        AstNode* newp;
        if (AstInitArray* initp = VN_CAST(nodep, InitArray)) {
            UINFO(9, "  cloneInitArray(" << elements << "," << offset << ") " << nodep << endl);
            int leOffset = !arrayp->rangep()->littleEndian()
                               ? arrayp->rangep()->elementsConst() - 1 - offset
                               : offset;
            AstNode* itemp = initp->getIndexDefaultedValuep(leOffset);
            if (!itemp) {
                nodep->v3error("Array initialization has too few elements, need element "
                               << offset);
                itemp = initp->initsp();
            }
            newp = itemp->cloneTree(false);
        } else if (AstNodeCond* snodep = VN_CAST(nodep, NodeCond)) {
            UINFO(9, "  cloneCond(" << elements << "," << offset << ") " << nodep << endl);
            return snodep->cloneType(snodep->condp()->cloneTree(false),
                                     cloneAndSel(snodep->expr1p(), elements, offset),
                                     cloneAndSel(snodep->expr2p(), elements, offset));
        } else if (AstSliceSel* snodep = VN_CAST(nodep, SliceSel)) {
            UINFO(9, "  cloneSliceSel(" << elements << "," << offset << ") " << nodep << endl);
            int leOffset = (snodep->declRange().lo()
                            + (!snodep->declRange().littleEndian()
                                   ? snodep->declRange().elements() - 1 - offset
                                   : offset));
            newp = new AstArraySel(nodep->fileline(), snodep->fromp()->cloneTree(false), leOffset);
        } else if (VN_IS(nodep, ArraySel) || VN_IS(nodep, NodeVarRef) || VN_IS(nodep, NodeSel)) {
            UINFO(9, "  cloneSel(" << elements << "," << offset << ") " << nodep << endl);
            int leOffset = !arrayp->rangep()->littleEndian()
                               ? arrayp->rangep()->elementsConst() - 1 - offset
                               : offset;
            newp = new AstArraySel(nodep->fileline(), nodep->cloneTree(false), leOffset);
        } else {
            if (!m_assignError) {
                nodep->v3error(nodep->prettyTypeName()
                               << " unexpected in assignment to unpacked array");
            }
            m_assignError = true;
            newp = nodep->cloneTree(false);  // Likely will cause downstream errors
        }
        return newp;
    }

    virtual void visit(AstNodeAssign* nodep) VL_OVERRIDE {
        // Called recursively on newly created assignments
        if (!nodep->user1() && !VN_IS(nodep, AssignAlias)) {
            nodep->user1(true);
            m_assignError = false;
            if (debug() >= 9) nodep->dumpTree(cout, " Deslice-In: ");
            AstNodeDType* dtp = nodep->lhsp()->dtypep()->skipRefp();
            if (AstUnpackArrayDType* arrayp = VN_CAST(dtp, UnpackArrayDType)) {
                // Left and right could have different msb/lsbs/endianness, but #elements is common
                // and all variables are realigned to start at zero
                // Assign of a little endian'ed slice to a big endian one must reverse the elements
                AstNode* newlistp = NULL;
                int elements = arrayp->rangep()->elementsConst();
                for (int offset = 0; offset < elements; ++offset) {
                    AstNode* newp = nodep->cloneType  // AstNodeAssign
                                    (cloneAndSel(nodep->lhsp(), elements, offset),
                                     cloneAndSel(nodep->rhsp(), elements, offset));
                    if (debug() >= 9) { newp->dumpTree(cout, "-new "); }
                    newlistp = AstNode::addNextNull(newlistp, newp);
                }
                if (debug() >= 9) nodep->dumpTree(cout, " Deslice-Dn: ");
                nodep->replaceWith(newlistp);
                VL_DO_DANGLING(nodep->deleteTree(), nodep);
                // Normal edit iterator will now iterate on all of the expansion assignments
                // This will potentially call this function again to resolve next level of slicing
                return;
            }
            m_assignp = nodep;
            iterateChildren(nodep);
            m_assignp = NULL;
        }
    }

    virtual void visit(AstInitArray* nodep) VL_OVERRIDE {
        UASSERT_OBJ(!m_assignp, nodep, "Array initialization should have been removed earlier");
    }

    void expandBiOp(AstNodeBiop* nodep) {
        if (!nodep->user1()) {
            nodep->user1(true);
            // If it's an unpacked array, blow it up into comparing each element
            AstNodeDType* fromDtp = nodep->lhsp()->dtypep()->skipRefp();
            UINFO(9, "  Bi-Eq/Neq expansion " << nodep << endl);
            if (AstUnpackArrayDType* adtypep = VN_CAST(fromDtp, UnpackArrayDType)) {
                AstNodeBiop* logp = NULL;
                if (!VN_IS(nodep->lhsp()->dtypep()->skipRefp(), NodeArrayDType)) {
                    nodep->lhsp()->v3error(
                        "Slice operator "
                        << nodep->lhsp()->prettyTypeName()
                        << " on non-slicable (e.g. non-vector) left-hand-side operand");
                } else if (!VN_IS(nodep->rhsp()->dtypep()->skipRefp(), NodeArrayDType)) {
                    nodep->rhsp()->v3error(
                        "Slice operator "
                        << nodep->rhsp()->prettyTypeName()
                        << " on non-slicable (e.g. non-vector) right-hand-side operand");
                } else {
                    for (int index = 0; index < adtypep->rangep()->elementsConst(); ++index) {
                        // EQ(a,b) -> LOGAND(EQ(ARRAYSEL(a,0), ARRAYSEL(b,0)), ...[1])
                        AstNodeBiop* clonep
                            = VN_CAST(nodep->cloneType(
                                          new AstArraySel(nodep->fileline(),
                                                          nodep->lhsp()->cloneTree(false), index),
                                          new AstArraySel(nodep->fileline(),
                                                          nodep->rhsp()->cloneTree(false), index)),
                                      NodeBiop);
                        if (!logp)
                            logp = clonep;
                        else {
                            switch (nodep->type()) {
                            case AstType::atEq:  // FALLTHRU
                            case AstType::atEqCase:
                                logp = new AstLogAnd(nodep->fileline(), logp, clonep);
                                break;
                            case AstType::atNeq:  // FALLTHRU
                            case AstType::atNeqCase:
                                logp = new AstLogOr(nodep->fileline(), logp, clonep);
                                break;
                            default:
                                nodep->v3fatalSrc("Unknown node type processing array slice");
                                break;
                            }
                        }
                    }
                    UASSERT_OBJ(logp, nodep, "Unpacked array with empty indices range");
                    nodep->replaceWith(logp);
                    VL_DO_DANGLING(pushDeletep(nodep), nodep);
                    nodep = logp;
                }
            }
            iterateChildren(nodep);
        }
    }
    virtual void visit(AstEq* nodep) VL_OVERRIDE { expandBiOp(nodep); }
    virtual void visit(AstNeq* nodep) VL_OVERRIDE { expandBiOp(nodep); }
    virtual void visit(AstEqCase* nodep) VL_OVERRIDE { expandBiOp(nodep); }
    virtual void visit(AstNeqCase* nodep) VL_OVERRIDE { expandBiOp(nodep); }

    virtual void visit(AstNode* nodep) VL_OVERRIDE { iterateChildren(nodep); }

public:
    // CONSTRUCTORS
    explicit SliceVisitor(AstNetlist* nodep) {
        m_assignp = NULL;
        m_assignError = false;
        iterate(nodep);
    }
    virtual ~SliceVisitor() {}
};

//######################################################################
// Link class functions

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