// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add temporaries, such as for expand nodes
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2004-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
//
//*************************************************************************
// V3Expand's Transformations:
//
// Each module:
//      Expand verilated.h macros into internal micro optimizations (RTL)
//      this will enable later optimizations.
//      Wide operands become assignments to each word of the vector, (WORDSELs)
//          Note in this case that the widthMin is not correct for the MSW of
//          the vector.  This must be accounted for if doing later constant
//          propagation across signals.
//
//*************************************************************************

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

#include "V3Global.h"
#include "V3Expand.h"
#include "V3Ast.h"

#include <algorithm>

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

class ExpandVisitor : public AstNVisitor {
private:
    // NODE STATE
    //  AstNode::user1()        -> bool.  Processed
    AstUser1InUse m_inuser1;

    // STATE
    AstNode* m_stmtp;  // Current statement

    // METHODS
    VL_DEBUG_FUNC;  // Declare debug()

    int longOrQuadWidth(AstNode* nodep) {
        return (nodep->width() + (VL_EDATASIZE - 1)) & ~(VL_EDATASIZE - 1);
    }
    V3Number notWideMask(AstNode* nodep) {
        return V3Number(nodep, VL_EDATASIZE, ~VL_MASK_E(nodep->widthMin()));
    }
    V3Number wordMask(AstNode* nodep) {
        if (nodep->isWide()) {
            return V3Number(nodep, VL_EDATASIZE, VL_MASK_E(nodep->widthMin()));
        } else {
            V3Number mask(nodep, longOrQuadWidth(nodep));
            mask.setMask(nodep->widthMin());
            return mask;
        }
    }

    void insertBefore(AstNode* placep, AstNode* newp) {
        newp->user1(1);  // Already processed, don't need to re-iterate
        AstNRelinker linker;
        placep->unlinkFrBack(&linker);
        newp->addNext(placep);
        linker.relink(newp);
    }
    void replaceWithDelete(AstNode* nodep, AstNode* newp) {
        newp->user1(1);  // Already processed, don't need to re-iterate
        nodep->replaceWith(newp);
        VL_DO_DANGLING(nodep->deleteTree(), nodep);
    }
    AstNode* newWordAssign(AstNodeAssign* placep, int word, AstNode* lhsp, AstNode* rhsp) {
        AstAssign* newp = new AstAssign(placep->fileline(),
                                        new AstWordSel(placep->fileline(), lhsp->cloneTree(true),
                                                       new AstConst(placep->fileline(), word)),
                                        rhsp);
        return newp;
    }
    void addWordAssign(AstNodeAssign* placep, int word, AstNode* lhsp, AstNode* rhsp) {
        insertBefore(placep, newWordAssign(placep, word, lhsp, rhsp));
    }
    void addWordAssign(AstNodeAssign* placep, int word, AstNode* rhsp) {
        addWordAssign(placep, word, placep->lhsp(), rhsp);
    }

    void fixCloneLvalue(AstNode* nodep) {
        // In AstSel transforms, we call clone() on VarRefs that were lvalues,
        // but are now being used on the RHS of the assignment
        if (VN_IS(nodep, VarRef)) VN_CAST(nodep, VarRef)->lvalue(false);
        // Iterate
        if (nodep->op1p()) fixCloneLvalue(nodep->op1p());
        if (nodep->op2p()) fixCloneLvalue(nodep->op2p());
        if (nodep->op3p()) fixCloneLvalue(nodep->op3p());
        if (nodep->op4p()) fixCloneLvalue(nodep->op4p());
    }

    AstNode* newAstWordSelClone(AstNode* nodep, int word) {
        // Get the specified word number from a wide array
        // Or, if it's a long/quad, do appropriate conversion to wide
        // Concat may pass negative word numbers, that means it wants a zero
        if (nodep->isWide() && word >= 0 && word < nodep->widthWords()) {
            return new AstWordSel(nodep->fileline(), nodep->cloneTree(true),
                                  new AstConst(nodep->fileline(), word));
        } else if (nodep->isQuad() && word == 0) {
            AstNode* quadfromp = nodep->cloneTree(true);
            quadfromp->dtypeSetBitUnsized(VL_QUADSIZE, quadfromp->widthMin(), VSigning::UNSIGNED);
            return new AstCCast(nodep->fileline(), quadfromp, VL_EDATASIZE);
        } else if (nodep->isQuad() && word == 1) {
            AstNode* quadfromp = nodep->cloneTree(true);
            quadfromp->dtypeSetBitUnsized(VL_QUADSIZE, quadfromp->widthMin(), VSigning::UNSIGNED);
            return new AstCCast(nodep->fileline(),
                                new AstShiftR(nodep->fileline(), quadfromp,
                                              new AstConst(nodep->fileline(), VL_EDATASIZE),
                                              VL_EDATASIZE),
                                VL_EDATASIZE);
        } else if (!nodep->isWide() && !nodep->isQuad() && word == 0) {
            return nodep->cloneTree(true);
        } else {  // Out of bounds
            return new AstConst(nodep->fileline(), 0);
        }
    }

    AstNode* newWordGrabShift(FileLine* fl, int word, AstNode* lhsp, int shift) {
        // Extract the expression to grab the value for the specified word, if it's the shift
        // of shift bits from lhsp
        AstNode* newp;
        // Negative word numbers requested for lhs when it's "before" what we want.
        // We get a 0 then.
        int othword = word - shift / VL_EDATASIZE;
        AstNode* llowp = newAstWordSelClone(lhsp, othword);
        if (int loffset = VL_BITBIT_E(shift)) {
            AstNode* lhip = newAstWordSelClone(lhsp, othword - 1);
            int nbitsonright = VL_EDATASIZE - loffset;  // bits that end up in lword
            newp = new AstOr(
                fl,
                new AstAnd(fl, new AstConst(fl, AstConst::SizedEData(), VL_MASK_E(loffset)),
                           new AstShiftR(fl, lhip, new AstConst(fl, nbitsonright), VL_EDATASIZE)),
                new AstAnd(fl, new AstConst(fl, AstConst::SizedEData(), ~VL_MASK_E(loffset)),
                           new AstShiftL(fl, llowp, new AstConst(fl, loffset), VL_EDATASIZE)));
        } else {
            newp = llowp;
        }
        return newp;
    }

    AstNode* newWordSel(FileLine* fl, AstNode* fromp, AstNode* lsbp, int wordAdder) {
        // Return equation to get the VL_BITWORD of a constant or non-constant
        UASSERT_OBJ(fromp->isWide(), fromp, "Only need AstWordSel on wide from's");
        if (wordAdder >= fromp->widthWords()) {
            // e.g. "logic [95:0] var[0]; logic [0] sel; out = var[sel];"
            // Squash before C++ to avoid getting a C++ compiler warning
            // (even though code would be unreachable as presumably a
            // AstCondBound is protecting above this node.
            return new AstConst(fl, AstConst::SizedEData(), 0);
        } else {
            AstNode* wordp;
            if (VN_IS(lsbp, Const)) {
                wordp = new AstConst(lsbp->fileline(),
                                     wordAdder + VL_BITWORD_E(VN_CAST(lsbp, Const)->toUInt()));
            } else {
                wordp = new AstShiftR(lsbp->fileline(), lsbp->cloneTree(true),
                                      new AstConst(lsbp->fileline(), VL_EDATASIZE_LOG2),
                                      VL_EDATASIZE);
                if (wordAdder != 0) {
                    wordp = new AstAdd(lsbp->fileline(),
                                       // This is indexing a arraysel, so a 32 bit constant is fine
                                       new AstConst(lsbp->fileline(), wordAdder), wordp);
                }
            }
            return new AstWordSel(fl, fromp, wordp);
        }
    }

    AstNode* dropCondBound(AstNode* nodep) {
        // Experimental only...
        //  If there's a CONDBOUND safety to keep arrays in bounds,
        //  we're going to AND it to a value that always fits inside a
        //  word, so we don't need it.
        // if (VN_IS(nodep, CondBound) && VN_IS(VN_CAST(nodep, CondBound)->lhsp(), Lte)) {
        //    nodep = VN_CAST(nodep, CondBound)->rhsp();
        //}
        return nodep;
    }

    AstNode* newSelBitBit(AstNode* lsbp) {
        // Return equation to get the VL_BITBIT of a constant or non-constant
        if (VN_IS(lsbp, Const)) {
            return new AstConst(lsbp->fileline(), VL_BITBIT_E(VN_CAST(lsbp, Const)->toUInt()));
        } else {
            return new AstAnd(lsbp->fileline(), new AstConst(lsbp->fileline(), VL_EDATASIZE - 1),
                              dropCondBound(lsbp)->cloneTree(true));
        }
    }

    //====================

    bool expandWide(AstNodeAssign* nodep, AstConst* rhsp) {
        UINFO(8, "    Wordize ASSIGN(CONST) " << nodep << endl);
        // -> {for each_word{ ASSIGN(WORDSEL(wide,#),WORDSEL(CONST,#))}}
        if (rhsp->num().isFourState()) {
            rhsp->v3warn(E_UNSUPPORTED,  // LCOV_EXCL_LINE  // impossible?
                         "Unsupported: 4-state numbers in this context");
        }
        for (int w = 0; w < nodep->widthWords(); w++) {
            addWordAssign(
                nodep, w,
                new AstConst(nodep->fileline(), AstConst::SizedEData(), rhsp->num().edataWord(w)));
        }
        return true;
    }
    //-------- Uniops
    bool expandWide(AstNodeAssign* nodep, AstVarRef* rhsp) {
        UINFO(8, "    Wordize ASSIGN(VARREF) " << nodep << endl);
        for (int w = 0; w < nodep->widthWords(); w++) {
            addWordAssign(nodep, w, newAstWordSelClone(rhsp, w));
        }
        return true;
    }
    bool expandWide(AstNodeAssign* nodep, AstArraySel* rhsp) {
        UINFO(8, "    Wordize ASSIGN(ARRAYSEL) " << nodep << endl);
        UASSERT_OBJ(!VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType), nodep,
                    "ArraySel with unpacked arrays should have been removed in V3Slice");
        for (int w = 0; w < nodep->widthWords(); w++) {
            addWordAssign(nodep, w, newAstWordSelClone(rhsp, w));
        }
        return true;
    }
    bool expandWide(AstNodeAssign* nodep, AstNot* rhsp) {
        UINFO(8, "    Wordize ASSIGN(NOT) " << nodep << endl);
        // -> {for each_word{ ASSIGN(WORDSEL(wide,#),NOT(WORDSEL(lhs,#))) }}
        for (int w = 0; w < nodep->widthWords(); w++) {
            addWordAssign(nodep, w,
                          new AstNot(rhsp->fileline(), newAstWordSelClone(rhsp->lhsp(), w)));
        }
        return true;
    }
    //-------- Biops
    bool expandWide(AstNodeAssign* nodep, AstAnd* rhsp) {
        UINFO(8, "    Wordize ASSIGN(AND) " << nodep << endl);
        for (int w = 0; w < nodep->widthWords(); w++) {
            addWordAssign(nodep, w,
                          new AstAnd(nodep->fileline(), newAstWordSelClone(rhsp->lhsp(), w),
                                     newAstWordSelClone(rhsp->rhsp(), w)));
        }
        return true;
    }
    bool expandWide(AstNodeAssign* nodep, AstOr* rhsp) {
        UINFO(8, "    Wordize ASSIGN(OR) " << nodep << endl);
        for (int w = 0; w < nodep->widthWords(); w++) {
            addWordAssign(nodep, w,
                          new AstOr(nodep->fileline(), newAstWordSelClone(rhsp->lhsp(), w),
                                    newAstWordSelClone(rhsp->rhsp(), w)));
        }
        return true;
    }
    bool expandWide(AstNodeAssign* nodep, AstXor* rhsp) {
        UINFO(8, "    Wordize ASSIGN(XOR) " << nodep << endl);
        for (int w = 0; w < nodep->widthWords(); w++) {
            addWordAssign(nodep, w,
                          new AstXor(nodep->fileline(), newAstWordSelClone(rhsp->lhsp(), w),
                                     newAstWordSelClone(rhsp->rhsp(), w)));
        }
        return true;
    }
    bool expandWide(AstNodeAssign* nodep, AstXnor* rhsp) {
        UINFO(8, "    Wordize ASSIGN(XNOR) " << nodep << endl);
        for (int w = 0; w < nodep->widthWords(); w++) {
            addWordAssign(nodep, w,
                          new AstXnor(nodep->fileline(), newAstWordSelClone(rhsp->lhsp(), w),
                                      newAstWordSelClone(rhsp->rhsp(), w)));
        }
        return true;
    }
    //-------- Triops
    bool expandWide(AstNodeAssign* nodep, AstNodeCond* rhsp) {
        UINFO(8, "    Wordize ASSIGN(COND) " << nodep << endl);
        for (int w = 0; w < nodep->widthWords(); w++) {
            addWordAssign(nodep, w,
                          new AstCond(nodep->fileline(), rhsp->condp()->cloneTree(true),
                                      newAstWordSelClone(rhsp->expr1p(), w),
                                      newAstWordSelClone(rhsp->expr2p(), w)));
        }
        return true;
    }

    // VISITORS
    virtual void visit(AstExtend* nodep) VL_OVERRIDE {
        if (nodep->user1SetOnce()) return;  // Process once
        iterateChildren(nodep);
        if (nodep->isWide()) {
            // See under ASSIGN(EXTEND)
        } else {
            AstNode* lhsp = nodep->lhsp()->unlinkFrBack();
            AstNode* newp = lhsp;
            if (nodep->isQuad()) {
                if (lhsp->isQuad()) {
                    lhsp->dtypeFrom(nodep);  // Just mark it, else nop
                } else if (lhsp->isWide()) {
                    nodep->v3fatalSrc("extending larger thing into smaller?");
                } else {
                    UINFO(8, "    EXTEND(q<-l) " << nodep << endl);
                    newp = new AstCCast(nodep->fileline(), lhsp, nodep);
                }
            } else {  // Long
                UASSERT_OBJ(!(lhsp->isQuad() || lhsp->isWide()), nodep,
                            "extending larger thing into smaller?");
                lhsp->dtypeFrom(nodep);  // Just mark it, else nop
            }
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        }
    }

    virtual void visit(AstSel* nodep) VL_OVERRIDE {
        if (nodep->user1SetOnce()) return;  // Process once
        iterateChildren(nodep);
        // Remember, Sel's may have non-integer rhs, so need to optimize for that!
        UASSERT_OBJ(nodep->widthMin() == nodep->widthConst(), nodep, "Width mismatch");
        if (VN_IS(nodep->backp(), NodeAssign)
            && nodep == VN_CAST(nodep->backp(), NodeAssign)->lhsp()) {
            // Sel is an LHS assignment select
        } else if (nodep->isWide()) {
            // See under ASSIGN(WIDE)
        } else if (nodep->fromp()->isWide()) {
            UINFO(8, "    SEL(wide) " << nodep << endl);
            // Selection amounts
            // Check for constant shifts & save some constification work later.
            // Grab lowest bit(s)
            AstNode* lowwordp = newWordSel(nodep->fromp()->fileline(),
                                           nodep->fromp()->cloneTree(true), nodep->lsbp(), 0);
            if (nodep->isQuad() && !lowwordp->isQuad()) {
                lowwordp = new AstCCast(nodep->fileline(), lowwordp, nodep);
            }
            AstNode* lowp = new AstShiftR(nodep->fileline(), lowwordp, newSelBitBit(nodep->lsbp()),
                                          nodep->width());
            // If > 1 bit, we might be crossing the word boundary
            AstNode* midp = NULL;
            V3Number zero(nodep, longOrQuadWidth(nodep));
            if (nodep->widthConst() > 1) {
                AstNode* midwordp =  // SEL(from,[1+wordnum])
                    newWordSel(nodep->fromp()->fileline(), nodep->fromp()->cloneTree(true),
                               nodep->lsbp(), 1);
                if (nodep->isQuad() && !midwordp->isQuad()) {
                    midwordp = new AstCCast(nodep->fileline(), midwordp, nodep);
                }
                // If we're selecting bit zero, then all 32 bits in word 1
                // get shifted << by 32 bits
                // else we need to form the lower word, so we << by 31 or less
                // nbitsfromlow <= (lsb==0) ? 64-bitbit(lsb) : 32-bitbit(lsb)
                AstNode* midshiftp
                    = new AstSub(nodep->lsbp()->fileline(),
                                 new AstConst(nodep->lsbp()->fileline(), VL_EDATASIZE),
                                 newSelBitBit(nodep->lsbp()));
                if (nodep->isQuad()) {
                    midshiftp = new AstCond(
                        nodep->fileline(),
                        new AstEq(nodep->fileline(), new AstConst(nodep->fileline(), 0),
                                  newSelBitBit(nodep->lsbp())),
                        new AstConst(nodep->lsbp()->fileline(), VL_EDATASIZE), midshiftp);
                }
                AstNode* midmayp
                    = new AstShiftL(nodep->fileline(), midwordp, midshiftp, nodep->width());
                if (nodep->isQuad()) {
                    midp = midmayp;  // Always grab from two words
                } else {
                    midp = new AstCond(nodep->fileline(),
                                       new AstEq(nodep->fileline(),
                                                 new AstConst(nodep->fileline(), 0),
                                                 newSelBitBit(nodep->lsbp())),
                                       new AstConst(nodep->fileline(), zero), midmayp);
                }
            }
            // If > 32 bits, we might be crossing the second word boundary
            AstNode* hip = NULL;
            if (nodep->widthConst() > VL_EDATASIZE) {
                AstNode* hiwordp =  // SEL(from,[2+wordnum])
                    newWordSel(nodep->fromp()->fileline(), nodep->fromp()->cloneTree(true),
                               nodep->lsbp(), 2);
                if (nodep->isQuad() && !hiwordp->isQuad()) {
                    hiwordp = new AstCCast(nodep->fileline(), hiwordp, nodep);
                }
                AstNode* himayp
                    = new AstShiftL(nodep->fileline(), hiwordp,
                                    // nbitsfromlow_and_mid <= 64-bitbit(lsb)
                                    new AstSub(nodep->lsbp()->fileline(),
                                               new AstConst(nodep->lsbp()->fileline(), 64),
                                               newSelBitBit(nodep->lsbp())),
                                    nodep->width());
                // if (frombit==0) then ignore, else use it
                hip = new AstCond(nodep->fileline(),
                                  new AstEq(nodep->fileline(), new AstConst(nodep->fileline(), 0),
                                            newSelBitBit(nodep->lsbp())),
                                  new AstConst(nodep->fileline(), zero), himayp);
            }

            AstNode* newp = lowp;
            if (midp) newp = new AstOr(nodep->fileline(), midp, newp);
            if (hip) newp = new AstOr(nodep->fileline(), hip, newp);
            newp->dtypeFrom(nodep);
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        } else {  // Long/Quad from Long/Quad
            UINFO(8, "    SEL->SHIFT " << nodep << endl);
            AstNode* fromp = nodep->fromp()->unlinkFrBack();
            AstNode* lsbp = nodep->lsbp()->unlinkFrBack();
            if (nodep->isQuad() && !fromp->isQuad()) {
                fromp = new AstCCast(nodep->fileline(), fromp, nodep);
            }
            AstNode* newp = new AstShiftR(
                nodep->fileline(), fromp, dropCondBound(lsbp),
                fromp->width());  // {large}>>32 requires 64-bit shift operation; then cast
            newp->dtypeFrom(fromp);
            if (!nodep->isQuad() && fromp->isQuad()) {
                newp = new AstCCast(newp->fileline(), newp, nodep);
            }
            newp->dtypeFrom(nodep);
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        }
    }

    bool expandWide(AstNodeAssign* nodep, AstSel* rhsp) {
        UASSERT_OBJ(nodep->widthMin() == rhsp->widthConst(), nodep, "Width mismatch");
        if (VN_IS(rhsp->lsbp(), Const) && VL_BITBIT_E(rhsp->lsbConst()) == 0) {
            int lsb = rhsp->lsbConst();
            UINFO(8, "    Wordize ASSIGN(SEL,align) " << nodep << endl);
            for (int w = 0; w < nodep->widthWords(); w++) {
                addWordAssign(nodep, w, newAstWordSelClone(rhsp->fromp(), w + VL_BITWORD_E(lsb)));
            }
            return true;
        } else {
            UINFO(8, "    Wordize ASSIGN(EXTRACT,misalign) " << nodep << endl);
            for (int w = 0; w < nodep->widthWords(); w++) {
                // Grab lowest bits
                AstNode* lowwordp = newWordSel(rhsp->fileline(), rhsp->fromp()->cloneTree(true),
                                               rhsp->lsbp(), w);
                AstNode* lowp = new AstShiftR(rhsp->fileline(), lowwordp,
                                              newSelBitBit(rhsp->lsbp()), VL_EDATASIZE);
                // Upper bits
                V3Number zero(nodep, VL_EDATASIZE, 0);
                AstNode* midwordp =  // SEL(from,[1+wordnum])
                    newWordSel(rhsp->fromp()->fileline(), rhsp->fromp()->cloneTree(true),
                               rhsp->lsbp(), w + 1);
                AstNode* midshiftp = new AstSub(
                    rhsp->lsbp()->fileline(), new AstConst(rhsp->lsbp()->fileline(), VL_EDATASIZE),
                    newSelBitBit(rhsp->lsbp()));
                AstNode* midmayp
                    = new AstShiftL(rhsp->fileline(), midwordp, midshiftp, VL_EDATASIZE);
                AstNode* midp
                    = new AstCond(rhsp->fileline(),
                                  new AstEq(rhsp->fileline(), new AstConst(rhsp->fileline(), 0),
                                            newSelBitBit(rhsp->lsbp())),
                                  new AstConst(rhsp->fileline(), zero), midmayp);
                AstNode* newp = new AstOr(nodep->fileline(), midp, lowp);
                addWordAssign(nodep, w, newp);
            }
            return true;
        }
    }

    bool expandLhs(AstNodeAssign* nodep, AstSel* lhsp) {
        // Possibilities
        //      destp: wide or narrow
        //      rhsp:  wide (destp must be wide), narrow, or 1 bit wide
        //      rhsp:  may be allones and can remove AND NOT gate
        //      lsbp:  constant or variable
        // Yuk.
        bool destwide = lhsp->fromp()->isWide();
        bool ones = nodep->rhsp()->isAllOnesV();
        if (VN_IS(lhsp->lsbp(), Const)) {
            // The code should work without this constant test, but it won't
            // constify as nicely as we'd like.
            AstNode* rhsp = nodep->rhsp()->unlinkFrBack();
            AstNode* destp = lhsp->fromp()->unlinkFrBack();
            int lsb = lhsp->lsbConst();
            int msb = lhsp->msbConst();
            V3Number maskset(nodep, destp->widthMin());
            for (int bit = lsb; bit < (msb + 1); bit++) maskset.setBit(bit, 1);
            V3Number maskold(nodep, destp->widthMin());
            maskold.opNot(maskset);
            if (destwide) {
                UINFO(8, "    ASSIGNSEL(const,wide) " << nodep << endl);
                for (int w = 0; w < destp->widthWords(); w++) {
                    if (w >= VL_BITWORD_E(lsb) && w <= VL_BITWORD_E(msb)) {
                        // else we would just be setting it to the same exact value
                        AstNode* oldvalp = newAstWordSelClone(destp, w);
                        fixCloneLvalue(oldvalp);
                        if (!ones) {
                            oldvalp
                                = new AstAnd(lhsp->fileline(),
                                             new AstConst(lhsp->fileline(), AstConst::SizedEData(),
                                                          maskold.edataWord(w)),
                                             oldvalp);
                        }
                        addWordAssign(nodep, w, destp,
                                      new AstOr(lhsp->fileline(), oldvalp,
                                                newWordGrabShift(lhsp->fileline(), w, rhsp, lsb)));
                    }
                }
                VL_DO_DANGLING(rhsp->deleteTree(), rhsp);
                VL_DO_DANGLING(destp->deleteTree(), destp);
            } else {
                UINFO(8, "    ASSIGNSEL(const,narrow) " << nodep << endl);
                if (destp->isQuad() && !rhsp->isQuad()) {
                    rhsp = new AstCCast(nodep->fileline(), rhsp, nodep);
                }
                AstNode* oldvalp = destp->cloneTree(true);
                fixCloneLvalue(oldvalp);
                if (!ones) {
                    oldvalp = new AstAnd(lhsp->fileline(), new AstConst(lhsp->fileline(), maskold),
                                         oldvalp);
                }
                AstNode* newp = new AstOr(lhsp->fileline(), oldvalp,
                                          new AstShiftL(lhsp->fileline(), rhsp,
                                                        new AstConst(lhsp->fileline(), lsb),
                                                        destp->width()));
                newp = new AstAssign(nodep->fileline(), destp, newp);
                insertBefore(nodep, newp);
            }
            return true;
        } else {  // non-const RHS
            if (destwide && lhsp->widthConst() == 1) {
                UINFO(8, "    ASSIGNSEL(varlsb,wide,1bit) " << nodep << endl);
                AstNode* rhsp = nodep->rhsp()->unlinkFrBack();
                AstNode* destp = lhsp->fromp()->unlinkFrBack();
                AstNode* oldvalp
                    = newWordSel(lhsp->fileline(), destp->cloneTree(true), lhsp->lsbp(), 0);
                fixCloneLvalue(oldvalp);
                if (!ones) {
                    oldvalp = new AstAnd(
                        lhsp->fileline(),
                        new AstNot(
                            lhsp->fileline(),
                            new AstShiftL(lhsp->fileline(), new AstConst(nodep->fileline(), 1),
                                          // newSelBitBit may exceed the MSB of this variable.
                                          // That's ok as we'd just AND with a larger value,
                                          // but oldval would clip the upper bits to sanity
                                          newSelBitBit(lhsp->lsbp()), VL_EDATASIZE)),
                        oldvalp);
                }
                // Restrict the shift amount to 0-31, see bug804.
                AstNode* shiftp = new AstAnd(nodep->fileline(), lhsp->lsbp()->cloneTree(true),
                                             new AstConst(nodep->fileline(), VL_EDATASIZE - 1));
                AstNode* newp
                    = new AstOr(lhsp->fileline(), oldvalp,
                                new AstShiftL(lhsp->fileline(), rhsp, shiftp, VL_EDATASIZE));
                newp = new AstAssign(nodep->fileline(),
                                     newWordSel(nodep->fileline(), destp, lhsp->lsbp(), 0), newp);
                insertBefore(nodep, newp);
                return true;
            } else if (destwide) {
                UINFO(8, "    ASSIGNSEL(varlsb,wide) -- NoOp -- " << nodep << endl);
                //   For wide destp, we can either form a equation for every destination word,
                // with the appropriate long equation of if it's being written or not.
                //   Or, we can use a LHS variable arraysel with
                //   non-constant index to set the vector.
                // Doing the variable arraysel is better for globals and large arrays,
                // doing every word is better for temporaries and if we're setting most words
                // since it may result in better substitution optimizations later.
                //   This results in so much code, we're better off leaving a function call.
                // Reconsider if we get subexpression elimination.
                return false;
            } else {
                UINFO(8, "    ASSIGNSEL(varlsb,narrow) " << nodep << endl);
                // nodep->dumpTree(cout, "-  old: ");
                AstNode* rhsp = nodep->rhsp()->unlinkFrBack();
                AstNode* destp = lhsp->fromp()->unlinkFrBack();
                AstNode* oldvalp = destp->cloneTree(true);
                fixCloneLvalue(oldvalp);

                V3Number maskwidth(nodep, destp->widthMin());
                for (int bit = 0; bit < lhsp->widthConst(); bit++) maskwidth.setBit(bit, 1);

                if (destp->isQuad() && !rhsp->isQuad()) {
                    rhsp = new AstCCast(nodep->fileline(), rhsp, nodep);
                }
                if (!ones) {
                    oldvalp = new AstAnd(
                        lhsp->fileline(),
                        new AstNot(lhsp->fileline(),
                                   new AstShiftL(lhsp->fileline(),
                                                 new AstConst(nodep->fileline(), maskwidth),
                                                 lhsp->lsbp()->cloneTree(true), destp->width())),
                        oldvalp);
                }
                AstNode* newp
                    = new AstOr(lhsp->fileline(), oldvalp,
                                new AstShiftL(lhsp->fileline(), rhsp,
                                              lhsp->lsbp()->cloneTree(true), destp->width()));
                newp = new AstAssign(nodep->fileline(), destp, newp);
                // newp->dumpTree(cout, "-  new: ");
                insertBefore(nodep, newp);
                return true;
            }
        }
    }

    virtual void visit(AstConcat* nodep) VL_OVERRIDE {
        if (nodep->user1SetOnce()) return;  // Process once
        iterateChildren(nodep);
        if (nodep->isWide()) {
            // See under ASSIGN(WIDE)
        } else {
            UINFO(8, "    CONCAT " << nodep << endl);
            AstNode* lhsp = nodep->lhsp()->unlinkFrBack();
            AstNode* rhsp = nodep->rhsp()->unlinkFrBack();
            int rhsshift = rhsp->widthMin();
            if (nodep->isQuad() && !lhsp->isQuad()) {
                lhsp = new AstCCast(nodep->fileline(), lhsp, nodep);
            }
            if (nodep->isQuad() && !rhsp->isQuad()) {
                rhsp = new AstCCast(nodep->fileline(), rhsp, nodep);
            }
            AstNode* newp = new AstOr(nodep->fileline(),
                                      new AstShiftL(nodep->fileline(), lhsp,
                                                    new AstConst(nodep->fileline(), rhsshift),
                                                    nodep->width()),
                                      rhsp);
            newp->dtypeFrom(nodep);  // Unsigned
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        }
    }
    bool expandWide(AstNodeAssign* nodep, AstConcat* rhsp) {
        UINFO(8, "    Wordize ASSIGN(CONCAT) " << nodep << endl);
        // Lhs or Rhs may be word, long, or quad.
        // newAstWordSelClone nicely abstracts the difference.
        int rhsshift = rhsp->rhsp()->widthMin();
        // Sometimes doing the words backwards is preferable.
        // When we have x={x,foo} backwards is better, when x={foo,x} forward is better
        // However V3Subst tends to rip this up, so not worth optimizing now.
        for (int w = 0; w < rhsp->widthWords(); w++) {
            addWordAssign(nodep, w,
                          new AstOr(rhsp->fileline(),
                                    newWordGrabShift(rhsp->fileline(), w, rhsp->lhsp(), rhsshift),
                                    newAstWordSelClone(rhsp->rhsp(), w)));
        }
        return true;
    }

    virtual void visit(AstReplicate* nodep) VL_OVERRIDE {
        if (nodep->user1SetOnce()) return;  // Process once
        iterateChildren(nodep);
        if (nodep->isWide()) {
            // See under ASSIGN(WIDE)
        } else {
            AstNode* lhsp = nodep->lhsp()->unlinkFrBack();
            AstNode* newp;
            int lhswidth = lhsp->widthMin();
            if (lhswidth == 1) {
                UINFO(8, "    REPLICATE(w1) " << nodep << endl);
                newp = new AstNegate(nodep->fileline(), lhsp);
            } else {
                UINFO(8, "    REPLICATE " << nodep << endl);
                const AstConst* constp = VN_CAST(nodep->rhsp(), Const);
                UASSERT_OBJ(constp, nodep,
                            "Replication value isn't a constant.  Checked earlier!");
                uint32_t times = constp->toUInt();
                if (nodep->isQuad() && !lhsp->isQuad()) {
                    lhsp = new AstCCast(nodep->fileline(), lhsp, nodep);
                }
                newp = lhsp->cloneTree(true);
                for (unsigned repnum = 1; repnum < times; repnum++) {
                    int rhsshift = repnum * lhswidth;
                    newp = new AstOr(nodep->fileline(),
                                     new AstShiftL(nodep->fileline(), lhsp->cloneTree(true),
                                                   new AstConst(nodep->fileline(), rhsshift),
                                                   nodep->width()),
                                     newp);
                    newp->dtypeFrom(nodep);  // Unsigned
                }
                VL_DO_DANGLING(lhsp->deleteTree(), lhsp);  // Never used
            }
            newp->dtypeFrom(nodep);  // Unsigned
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        }
    }
    bool expandWide(AstNodeAssign* nodep, AstReplicate* rhsp) {
        UINFO(8, "    Wordize ASSIGN(REPLICATE) " << nodep << endl);
        AstNode* lhsp = rhsp->lhsp();
        int lhswidth = lhsp->widthMin();
        const AstConst* constp = VN_CAST(rhsp->rhsp(), Const);
        UASSERT_OBJ(constp, rhsp, "Replication value isn't a constant.  Checked earlier!");
        uint32_t times = constp->toUInt();
        for (int w = 0; w < rhsp->widthWords(); w++) {
            AstNode* newp;
            if (lhswidth == 1) {
                newp = new AstNegate(nodep->fileline(), lhsp->cloneTree(true));
                newp->dtypeSetLogicSized(VL_EDATASIZE,
                                         VSigning::UNSIGNED);  // Replicate always unsigned
            } else {
                newp = newAstWordSelClone(lhsp, w);
                for (unsigned repnum = 1; repnum < times; repnum++) {
                    newp = new AstOr(
                        nodep->fileline(),
                        newWordGrabShift(rhsp->fileline(), w, lhsp, lhswidth * repnum), newp);
                }
            }
            addWordAssign(nodep, w, newp);
        }
        return true;
    }

    virtual void visit(AstChangeXor* nodep) VL_OVERRIDE {
        if (nodep->user1SetOnce()) return;  // Process once
        iterateChildren(nodep);
        UINFO(8, "    Wordize ChangeXor " << nodep << endl);
        // -> (0=={or{for each_word{WORDSEL(lhs,#)^WORDSEL(rhs,#)}}}
        AstNode* newp = NULL;
        for (int w = 0; w < nodep->lhsp()->widthWords(); w++) {
            AstNode* eqp = new AstXor(nodep->fileline(), newAstWordSelClone(nodep->lhsp(), w),
                                      newAstWordSelClone(nodep->rhsp(), w));
            newp = (newp == NULL) ? eqp : (new AstOr(nodep->fileline(), newp, eqp));
        }
        VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
    }

    void visitEqNeq(AstNodeBiop* nodep) {
        if (nodep->user1SetOnce()) return;  // Process once
        iterateChildren(nodep);
        if (nodep->lhsp()->isWide()) {
            UINFO(8, "    Wordize EQ/NEQ " << nodep << endl);
            // -> (0=={or{for each_word{WORDSEL(lhs,#)^WORDSEL(rhs,#)}}}
            AstNode* newp = NULL;
            for (int w = 0; w < nodep->lhsp()->widthWords(); w++) {
                AstNode* eqp = new AstXor(nodep->fileline(), newAstWordSelClone(nodep->lhsp(), w),
                                          newAstWordSelClone(nodep->rhsp(), w));
                newp = (newp == NULL) ? eqp : (new AstOr(nodep->fileline(), newp, eqp));
            }
            if (VN_IS(nodep, Neq)) {
                newp
                    = new AstNeq(nodep->fileline(),
                                 new AstConst(nodep->fileline(), AstConst::SizedEData(), 0), newp);
            } else {
                newp = new AstEq(nodep->fileline(),
                                 new AstConst(nodep->fileline(), AstConst::SizedEData(), 0), newp);
            }
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        }
    }
    virtual void visit(AstEq* nodep) VL_OVERRIDE { visitEqNeq(nodep); }
    virtual void visit(AstNeq* nodep) VL_OVERRIDE { visitEqNeq(nodep); }

    virtual void visit(AstRedOr* nodep) VL_OVERRIDE {
        if (nodep->user1SetOnce()) return;  // Process once
        iterateChildren(nodep);
        if (nodep->lhsp()->isWide()) {
            UINFO(8, "    Wordize REDOR " << nodep << endl);
            // -> (0!={or{for each_word{WORDSEL(lhs,#)}}}
            AstNode* newp = NULL;
            for (int w = 0; w < nodep->lhsp()->widthWords(); w++) {
                AstNode* eqp = newAstWordSelClone(nodep->lhsp(), w);
                newp = (newp == NULL) ? eqp : (new AstOr(nodep->fileline(), newp, eqp));
            }
            newp = new AstNeq(nodep->fileline(),
                              new AstConst(nodep->fileline(), AstConst::SizedEData(), 0), newp);
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        } else {
            UINFO(8, "    REDOR->EQ " << nodep << endl);
            AstNode* lhsp = nodep->lhsp()->unlinkFrBack();
            AstNode* newp = new AstNeq(nodep->fileline(),
                                       new AstConst(nodep->fileline(), AstConst::WidthedValue(),
                                                    longOrQuadWidth(nodep), 0),
                                       lhsp);
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        }
    }
    virtual void visit(AstRedAnd* nodep) VL_OVERRIDE {
        if (nodep->user1SetOnce()) return;  // Process once
        iterateChildren(nodep);
        if (nodep->lhsp()->isWide()) {
            UINFO(8, "    Wordize REDAND " << nodep << endl);
            // -> (0!={and{for each_word{WORDSEL(lhs,#)}}}
            AstNode* newp = NULL;
            for (int w = 0; w < nodep->lhsp()->widthWords(); w++) {
                AstNode* eqp = newAstWordSelClone(nodep->lhsp(), w);
                if (w == nodep->lhsp()->widthWords() - 1) {
                    // Rather than doing a (slowish) ==##, we OR in the
                    // bits that aren't part of the mask
                    eqp = new AstOr(nodep->fileline(),
                                    new AstConst(nodep->fileline(), notWideMask(nodep->lhsp())),
                                    // Bug in cppcheck
                                    // cppcheck-suppress memleak
                                    eqp);
                }
                newp = (newp == NULL) ? eqp : (new AstAnd(nodep->fileline(), newp, eqp));
            }
            newp = new AstEq(
                nodep->fileline(),
                new AstConst(nodep->fileline(), AstConst::SizedEData(), VL_MASK_E(VL_EDATASIZE)),
                newp);
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        } else {
            UINFO(8, "    REDAND->EQ " << nodep << endl);
            AstNode* lhsp = nodep->lhsp()->unlinkFrBack();
            AstNode* newp = new AstEq(nodep->fileline(),
                                      new AstConst(nodep->fileline(), wordMask(lhsp)), lhsp);
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        }
    }
    virtual void visit(AstRedXor* nodep) VL_OVERRIDE {
        if (nodep->user1SetOnce()) return;  // Process once
        iterateChildren(nodep);
        if (nodep->lhsp()->isWide()) {
            UINFO(8, "    Wordize REDXOR " << nodep << endl);
            // -> (0!={redxor{for each_word{XOR(WORDSEL(lhs,#))}}}
            AstNode* newp = NULL;
            for (int w = 0; w < nodep->lhsp()->widthWords(); w++) {
                AstNode* eqp = newAstWordSelClone(nodep->lhsp(), w);
                newp = (newp == NULL) ? eqp : (new AstXor(nodep->fileline(), newp, eqp));
            }
            newp = new AstRedXor(nodep->fileline(), newp);
            UINFO(8, "    Wordize REDXORnew " << newp << endl);
            VL_DO_DANGLING(replaceWithDelete(nodep, newp), nodep);
        }
        // We don't reduce non-wide XORs, as its more efficient to use a temp register,
        // which the inlined function does nicely.
    }

    virtual void visit(AstNodeStmt* nodep) VL_OVERRIDE {
        if (nodep->user1SetOnce()) return;  // Process once
        if (!nodep->isStatement()) {
            iterateChildren(nodep);
            return;
        }
        m_stmtp = nodep;
        iterateChildren(nodep);
        m_stmtp = NULL;
    }
    virtual void visit(AstNodeAssign* nodep) VL_OVERRIDE {
        if (nodep->user1SetOnce()) return;  // Process once
        m_stmtp = nodep;
        iterateChildren(nodep);
        bool did = false;
        if (nodep->isWide() && ((VN_IS(nodep->lhsp(), VarRef) || VN_IS(nodep->lhsp(), ArraySel)))
            && !AstVar::scVarRecurse(nodep->lhsp())  // Need special function for SC
            && !AstVar::scVarRecurse(nodep->rhsp())) {
            if (AstConst* rhsp = VN_CAST(nodep->rhsp(), Const)) {
                did = expandWide(nodep, rhsp);
            } else if (AstVarRef* rhsp = VN_CAST(nodep->rhsp(), VarRef)) {
                did = expandWide(nodep, rhsp);
            } else if (AstSel* rhsp = VN_CAST(nodep->rhsp(), Sel)) {
                did = expandWide(nodep, rhsp);
            } else if (AstArraySel* rhsp = VN_CAST(nodep->rhsp(), ArraySel)) {
                did = expandWide(nodep, rhsp);
            } else if (AstConcat* rhsp = VN_CAST(nodep->rhsp(), Concat)) {
                did = expandWide(nodep, rhsp);
            } else if (AstReplicate* rhsp = VN_CAST(nodep->rhsp(), Replicate)) {
                did = expandWide(nodep, rhsp);
            } else if (AstAnd* rhsp = VN_CAST(nodep->rhsp(), And)) {
                did = expandWide(nodep, rhsp);
            } else if (AstOr* rhsp = VN_CAST(nodep->rhsp(), Or)) {
                did = expandWide(nodep, rhsp);
            } else if (AstNot* rhsp = VN_CAST(nodep->rhsp(), Not)) {
                did = expandWide(nodep, rhsp);
            } else if (AstXor* rhsp = VN_CAST(nodep->rhsp(), Xor)) {
                did = expandWide(nodep, rhsp);
            } else if (AstXnor* rhsp = VN_CAST(nodep->rhsp(), Xnor)) {
                did = expandWide(nodep, rhsp);
            } else if (AstNodeCond* rhsp = VN_CAST(nodep->rhsp(), NodeCond)) {
                did = expandWide(nodep, rhsp);
            }
        } else if (AstSel* lhsp = VN_CAST(nodep->lhsp(), Sel)) {
            did = expandLhs(nodep, lhsp);
        }
        // Cleanup common code
        if (did) VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
        m_stmtp = NULL;
    }

    //--------------------
    virtual void visit(AstVar*) VL_OVERRIDE {}  // Don't hit varrefs under vars
    virtual void visit(AstNode* nodep) VL_OVERRIDE { iterateChildren(nodep); }

public:
    // CONSTRUCTORS
    explicit ExpandVisitor(AstNetlist* nodep) {
        m_stmtp = NULL;
        iterate(nodep);
    }
    virtual ~ExpandVisitor() {}
};

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

//######################################################################
// Expand class functions

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