// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Resolve 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
//
//*************************************************************************
// LinkResolve TRANSFORMATIONS:
//      Top-down traversal
//          Extracts:
//              Add SUB so that we subtract off the "base 0-start" of the array
//          SelBit: Convert to ArraySel
//              Add SUB so that we subtract off the "base 0-start" of the array
//          File operations
//              Convert normal var to FILE* type
//          SenItems: Convert pos/negedge of non-simple signals to temporaries
//*************************************************************************

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

#include "V3Global.h"
#include "V3String.h"
#include "V3LinkResolve.h"
#include "V3Ast.h"

#include <algorithm>
#include <map>

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

class LinkResolveVisitor final : public AstNVisitor {
private:
    // NODE STATE
    //  Entire netlist:
    //   AstCaseItem::user2()   // bool           Moved default caseitems
    AstUser2InUse m_inuser2;

    // STATE
    // Below state needs to be preserved between each module call.
    AstNodeModule* m_modp = nullptr;  // Current module
    AstClass* m_classp = nullptr;  // Class we're inside
    AstNodeFTask* m_ftaskp = nullptr;  // Function or task we're inside
    AstNodeCoverOrAssert* m_assertp = nullptr;  // Current assertion
    int m_senitemCvtNum = 0;  // Temporary signal counter

    // METHODS
    VL_DEBUG_FUNC;  // Declare debug()

    // VISITs
    // TODO: Most of these visitors are here for historical reasons.
    // TODO: ExpectDecriptor can move to data type resolution, and the rest
    // TODO: could move to V3LinkParse to get them out of the way of elaboration
    virtual void visit(AstNodeModule* nodep) override {
        // Module: Create sim table for entire module and iterate
        UINFO(8, "MODULE " << nodep << endl);
        if (nodep->dead()) return;
        VL_RESTORER(m_modp);
        VL_RESTORER(m_senitemCvtNum);
        {
            m_modp = nodep;
            m_senitemCvtNum = 0;
            iterateChildren(nodep);
        }
    }
    virtual void visit(AstClass* nodep) override {
        VL_RESTORER(m_classp);
        {
            m_classp = nodep;
            iterateChildren(nodep);
        }
    }
    virtual void visit(AstInitial* nodep) override {
        iterateChildren(nodep);
        // Initial assignments under function/tasks can just be simple
        // assignments without the initial
        if (m_ftaskp) {
            VL_DO_DANGLING(nodep->replaceWith(nodep->bodysp()->unlinkFrBackWithNext()), nodep);
        }
    }
    virtual void visit(AstNodeCoverOrAssert* nodep) override {
        if (m_assertp) nodep->v3error("Assert not allowed under another assert");
        m_assertp = nodep;
        iterateChildren(nodep);
        m_assertp = nullptr;
    }
    virtual void visit(AstVar* nodep) override {
        iterateChildren(nodep);
        if (m_classp && !nodep->isParam()) nodep->varType(AstVarType::MEMBER);
        if (m_classp && nodep->isParam())
            nodep->v3warn(E_UNSUPPORTED, "Unsupported: class parameter");
        if (m_ftaskp) nodep->funcLocal(true);
        if (nodep->isSigModPublic()) {
            nodep->sigModPublic(false);  // We're done with this attribute
            m_modp->modPublic(true);  // Avoid flattening if signals are exposed
        }
    }

    virtual void visit(AstNodeVarRef* nodep) override {
        // VarRef: Resolve its reference
        if (nodep->varp()) { nodep->varp()->usedParam(true); }
        iterateChildren(nodep);
    }

    virtual void visit(AstNodeFTask* nodep) override {
        // NodeTask: Remember its name for later resolution
        // Remember the existing symbol table scope
        if (m_classp) nodep->classMethod(true);
        // V3LinkDot moved the isExternDef into the class, the extern proto was
        // checked to exist, and now isn't needed
        nodep->isExternDef(false);
        if (nodep->isExternProto()) {
            VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
            return;
        }
        {
            m_ftaskp = nodep;
            iterateChildren(nodep);
        }
        m_ftaskp = nullptr;
        if (nodep->dpiExport()) { nodep->scopeNamep(new AstScopeName(nodep->fileline())); }
    }
    virtual void visit(AstNodeFTaskRef* nodep) override {
        iterateChildren(nodep);
        if (nodep->taskp() && (nodep->taskp()->dpiContext() || nodep->taskp()->dpiExport())) {
            nodep->scopeNamep(new AstScopeName(nodep->fileline()));
        }
    }

    virtual void visit(AstSenItem* nodep) override {
        // Remove bit selects, and bark if it's not a simple variable
        iterateChildren(nodep);
        if (nodep->isClocked()) {
            // If it's not a simple variable wrap in a temporary
            // This is a bit unfortunate as we haven't done width resolution
            // and any width errors will look a bit odd, but it works.
            AstNode* sensp = nodep->sensp();
            if (sensp && !VN_IS(sensp, NodeVarRef) && !VN_IS(sensp, Const)) {
                // Make a new temp wire
                string newvarname = "__Vsenitemexpr" + cvtToStr(++m_senitemCvtNum);
                AstVar* newvarp = new AstVar(sensp->fileline(), AstVarType::MODULETEMP, newvarname,
                                             VFlagLogicPacked(), 1);
                // We can't just add under the module, because we may be
                // inside a generate, begin, etc.
                // We know a SenItem should be under a SenTree/Always etc,
                // we we'll just hunt upwards
                AstNode* addwherep = nodep;  // Add to this element's next
                while (VN_IS(addwherep, SenItem) || VN_IS(addwherep, SenTree)) {
                    addwherep = addwherep->backp();
                }
                if (!VN_IS(addwherep, Always)) {  // Assertion perhaps?
                    sensp->v3warn(E_UNSUPPORTED,
                                  "Unsupported: Non-single-bit pos/negedge clock statement under "
                                  "some complicated block");
                    addwherep = m_modp;
                }
                addwherep->addNext(newvarp);

                sensp->replaceWith(new AstVarRef(sensp->fileline(), newvarp, VAccess::READ));
                AstAssignW* assignp = new AstAssignW(
                    sensp->fileline(), new AstVarRef(sensp->fileline(), newvarp, VAccess::WRITE),
                    sensp);
                addwherep->addNext(assignp);
            }
        } else {  // Old V1995 sensitivity list; we'll probably mostly ignore
            bool did = true;
            while (did) {
                did = false;
                if (AstNodeSel* selp = VN_CAST(nodep->sensp(), NodeSel)) {
                    AstNode* fromp = selp->fromp()->unlinkFrBack();
                    selp->replaceWith(fromp);
                    VL_DO_DANGLING(selp->deleteTree(), selp);
                    did = true;
                }
                // NodeSel doesn't include AstSel....
                if (AstSel* selp = VN_CAST(nodep->sensp(), Sel)) {
                    AstNode* fromp = selp->fromp()->unlinkFrBack();
                    selp->replaceWith(fromp);
                    VL_DO_DANGLING(selp->deleteTree(), selp);
                    did = true;
                }
                if (AstNodePreSel* selp = VN_CAST(nodep->sensp(), NodePreSel)) {
                    AstNode* fromp = selp->lhsp()->unlinkFrBack();
                    selp->replaceWith(fromp);
                    VL_DO_DANGLING(selp->deleteTree(), selp);
                    did = true;
                }
            }
        }
        if (!VN_IS(nodep->sensp(), NodeVarRef)
            && !VN_IS(nodep->sensp(), EnumItemRef)  // V3Const will cleanup
            && !nodep->isIllegal()) {
            if (debug()) nodep->dumpTree(cout, "-tree: ");
            nodep->v3warn(E_UNSUPPORTED, "Unsupported: Complex statement in sensitivity list");
        }
    }

    virtual void visit(AstNodePreSel* nodep) override {
        if (!nodep->attrp()) {
            iterateChildren(nodep);
            // Constification may change the fromp() to a constant, which will lose the
            // variable we're extracting from (to determine MSB/LSB/endianness/etc.)
            // So we replicate it in another node
            // Note that V3Param knows not to replace AstVarRef's under AstAttrOf's
            AstNode* basefromp = AstArraySel::baseFromp(nodep);
            if (AstNodeVarRef* varrefp
                = VN_CAST(basefromp, NodeVarRef)) {  // Maybe varxref - so need to clone
                nodep->attrp(new AstAttrOf(nodep->fileline(), AstAttrType::VAR_BASE,
                                           varrefp->cloneTree(false)));
            } else if (AstUnlinkedRef* uvxrp
                       = VN_CAST(basefromp, UnlinkedRef)) {  // Maybe unlinked - so need to clone
                nodep->attrp(new AstAttrOf(nodep->fileline(), AstAttrType::VAR_BASE,
                                           uvxrp->cloneTree(false)));
            } else if (AstMemberSel* fromp = VN_CAST(basefromp, MemberSel)) {
                nodep->attrp(new AstAttrOf(nodep->fileline(), AstAttrType::MEMBER_BASE,
                                           fromp->cloneTree(false)));
            } else if (AstEnumItemRef* fromp = VN_CAST(basefromp, EnumItemRef)) {
                nodep->attrp(new AstAttrOf(nodep->fileline(), AstAttrType::ENUM_BASE,
                                           fromp->cloneTree(false)));
            } else if (VN_IS(basefromp, Replicate)) {
                // From {...}[...] syntax in IEEE 2017
                if (basefromp) { UINFO(1, "    Related node: " << basefromp << endl); }
                nodep->v3warn(E_UNSUPPORTED, "Unsupported: Select of concatenation");
                nodep = nullptr;
            } else {
                if (basefromp) { UINFO(1, "    Related node: " << basefromp << endl); }
                nodep->v3fatalSrc("Illegal bit select; no signal/member being extracted from");
            }
        }
    }

    virtual void visit(AstCaseItem* nodep) override {
        // Move default caseItems to the bottom of the list
        // That saves us from having to search each case list twice, for non-defaults and defaults
        iterateChildren(nodep);
        if (!nodep->user2() && nodep->isDefault() && nodep->nextp()) {
            nodep->user2(true);
            AstNode* nextp = nodep->nextp();
            nodep->unlinkFrBack();
            nextp->addNext(nodep);
        }
    }

    virtual void visit(AstPragma* nodep) override {
        if (nodep->pragType() == AstPragmaType::HIER_BLOCK) {
            UASSERT_OBJ(m_modp, nodep, "HIER_BLOCK not under a module");
            // If this is hierarchical mode which is to create protect-lib,
            // sub modules do not have hier_block meta comment in the source code.
            // But .vlt files may still mark a module which is actually a protect-lib wrapper
            // hier_block. AstNodeModule::hierBlock() can be true only when --hierarchical is
            // specified.
            m_modp->hierBlock(v3Global.opt.hierarchical());
            nodep->unlinkFrBack();
            VL_DO_DANGLING(pushDeletep(nodep), nodep);
        } else if (nodep->pragType() == AstPragmaType::PUBLIC_MODULE) {
            UASSERT_OBJ(m_modp, nodep, "PUBLIC_MODULE not under a module");
            m_modp->modPublic(true);
            nodep->unlinkFrBack();
            VL_DO_DANGLING(pushDeletep(nodep), nodep);
        } else if (nodep->pragType() == AstPragmaType::PUBLIC_TASK) {
            UASSERT_OBJ(m_ftaskp, nodep, "PUBLIC_TASK not under a task");
            m_ftaskp->taskPublic(true);
            m_modp->modPublic(true);  // Need to get to the task...
            nodep->unlinkFrBack();
            VL_DO_DANGLING(pushDeletep(nodep), nodep);
        } else if (nodep->pragType() == AstPragmaType::COVERAGE_BLOCK_OFF) {
            if (!v3Global.opt.coverageLine()) {  // No need for block statements; may optimize
                                                 // better without
                nodep->unlinkFrBack();
                VL_DO_DANGLING(pushDeletep(nodep), nodep);
            }
        } else {
            iterateChildren(nodep);
        }
    }

    string expectFormat(AstNode* nodep, const string& format, AstNode* argp, bool isScan) {
        // Check display arguments, return new format string
        string newFormat;
        bool inPct = false;
        bool inIgnore = false;
        string fmt;
        for (const char ch : format) {
            if (!inPct && ch == '%') {
                inPct = true;
                inIgnore = false;
                fmt = ch;
            } else if (inPct && (isdigit(ch) || ch == '.' || ch == '-')) {
                fmt += ch;
            } else if (inPct) {
                inPct = false;
                fmt += ch;
                switch (tolower(ch)) {
                case '%':  // %% - just output a %
                    break;
                case '*':
                    inPct = true;
                    inIgnore = true;
                    break;
                case 'm':  // %m - auto insert "name"
                    if (isScan) {
                        nodep->v3warn(E_UNSUPPORTED, "Unsupported: %m in $fscanf");
                        fmt = "";
                    }
                    break;
                case 'l':  // %l - auto insert "library"
                    if (isScan) {
                        nodep->v3warn(E_UNSUPPORTED, "Unsupported: %l in $fscanf");
                        fmt = "";
                    }
                    if (m_modp) fmt = VString::quotePercent(m_modp->prettyName());
                    break;
                default:  // Most operators, just move to next argument
                    if (!V3Number::displayedFmtLegal(ch, isScan)) {
                        nodep->v3error("Unknown $display-like format code: '%" << ch << "'");
                    } else if (!inIgnore) {
                        if (!argp) {
                            nodep->v3error("Missing arguments for $display-like format");
                        } else {
                            argp = argp->nextp();
                        }
                    }
                    break;
                }  // switch
                newFormat += fmt;
            } else {
                newFormat += ch;
            }
        }

        if (argp && !isScan) {
            int skipCount = 0;  // number of args consume by any additional format strings
            while (argp) {
                if (skipCount) {
                    argp = argp->nextp();
                    skipCount--;
                    continue;
                }
                AstConst* constp = VN_CAST(argp, Const);
                bool isFromString = (constp) ? constp->num().isFromString() : false;
                if (isFromString) {
                    int numchars = argp->dtypep()->width() / 8;
                    string str(numchars, ' ');
                    // now scan for % operators
                    bool inpercent = false;
                    for (int i = 0; i < numchars; i++) {
                        int ii = numchars - i - 1;
                        char c = constp->num().dataByte(ii);
                        str[i] = c;
                        if (!inpercent && c == '%') {
                            inpercent = true;
                        } else if (inpercent) {
                            inpercent = false;
                            switch (c) {
                            case '0':  // FALLTHRU
                            case '1':  // FALLTHRU
                            case '2':  // FALLTHRU
                            case '3':  // FALLTHRU
                            case '4':  // FALLTHRU
                            case '5':  // FALLTHRU
                            case '6':  // FALLTHRU
                            case '7':  // FALLTHRU
                            case '8':  // FALLTHRU
                            case '9':  // FALLTHRU
                            case '.': inpercent = true; break;
                            case '%': break;
                            default:
                                if (V3Number::displayedFmtLegal(c, isScan)) ++skipCount;
                            }
                        }
                    }
                    newFormat.append(str);
                    AstNode* nextp = argp->nextp();
                    argp->unlinkFrBack();
                    VL_DO_DANGLING(pushDeletep(argp), argp);
                    argp = nextp;
                } else {
                    newFormat.append("%?");  // V3Width to figure it out
                    argp = argp->nextp();
                }
            }
        }
        return newFormat;
    }

    static void expectDescriptor(AstNode* nodep, AstNodeVarRef* filep) {
        if (!filep) {
            nodep->v3warn(E_UNSUPPORTED,
                          "Unsupported: $fopen/$fclose/$f* descriptor must be a simple variable");
        }
        if (filep && filep->varp()) filep->varp()->attrFileDescr(true);
    }

    virtual void visit(AstFOpen* nodep) override {
        iterateChildren(nodep);
        expectDescriptor(nodep, VN_CAST(nodep->filep(), NodeVarRef));
    }
    virtual void visit(AstFOpenMcd* nodep) override {
        iterateChildren(nodep);
        expectDescriptor(nodep, VN_CAST(nodep->filep(), NodeVarRef));
    }
    virtual void visit(AstFClose* nodep) override {
        iterateChildren(nodep);
        expectDescriptor(nodep, VN_CAST(nodep->filep(), NodeVarRef));
    }
    virtual void visit(AstFError* nodep) override {
        iterateChildren(nodep);
        expectDescriptor(nodep, VN_CAST(nodep->filep(), NodeVarRef));
    }
    virtual void visit(AstFEof* nodep) override {
        iterateChildren(nodep);
        expectDescriptor(nodep, VN_CAST(nodep->filep(), NodeVarRef));
    }
    virtual void visit(AstFRead* nodep) override {
        iterateChildren(nodep);
        expectDescriptor(nodep, VN_CAST(nodep->filep(), NodeVarRef));
    }
    virtual void visit(AstFScanF* nodep) override {
        iterateChildren(nodep);
        expectFormat(nodep, nodep->text(), nodep->exprsp(), true);
    }
    virtual void visit(AstSScanF* nodep) override {
        iterateChildren(nodep);
        expectFormat(nodep, nodep->text(), nodep->exprsp(), true);
    }
    virtual void visit(AstSFormatF* nodep) override {
        iterateChildren(nodep);
        // Cleanup old-school displays without format arguments
        if (!nodep->hasFormat()) {
            UASSERT_OBJ(nodep->text() == "", nodep,
                        "Non-format $sformatf should have \"\" format");
            if (VN_IS(nodep->exprsp(), Const)
                && VN_CAST(nodep->exprsp(), Const)->num().isFromString()) {
                AstConst* fmtp = VN_CAST(nodep->exprsp()->unlinkFrBack(), Const);
                nodep->text(fmtp->num().toString());
                VL_DO_DANGLING(pushDeletep(fmtp), fmtp);
            }
            nodep->hasFormat(true);
        }
        string newFormat = expectFormat(nodep, nodep->text(), nodep->exprsp(), false);
        nodep->text(newFormat);
        if ((VN_IS(nodep->backp(), Display)
             && VN_CAST(nodep->backp(), Display)->displayType().needScopeTracking())
            || nodep->formatScopeTracking()) {
            nodep->scopeNamep(new AstScopeName(nodep->fileline()));
        }
    }

    virtual void visit(AstUdpTable* nodep) override {
        UINFO(5, "UDPTABLE  " << nodep << endl);
        if (!v3Global.opt.bboxUnsup()) {
            // We don't warn until V3Inst, so that UDPs that are in libraries and
            // never used won't result in any warnings.
        } else {
            // Massive hack, just tie off all outputs so our analysis can proceed
            AstVar* varoutp = nullptr;
            for (AstNode* stmtp = m_modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
                if (AstVar* varp = VN_CAST(stmtp, Var)) {
                    if (varp->isReadOnly()) {
                    } else if (varp->isWritable()) {
                        if (varoutp) {
                            varp->v3error("Multiple outputs not allowed in udp modules");
                        }
                        varoutp = varp;
                        // Tie off
                        m_modp->addStmtp(new AstAssignW(
                            varp->fileline(),
                            new AstVarRef(varp->fileline(), varp, VAccess::WRITE),
                            new AstConst(varp->fileline(), AstConst::LogicFalse())));
                    } else {
                        varp->v3error("Only inputs and outputs are allowed in udp modules");
                    }
                }
            }
            nodep->unlinkFrBack();
            VL_DO_DANGLING(pushDeletep(nodep), nodep);
        }
    }

    virtual void visit(AstScCtor* nodep) override {
        // Constructor info means the module must remain public
        m_modp->modPublic(true);
        iterateChildren(nodep);
    }
    virtual void visit(AstScDtor* nodep) override {
        // Destructor info means the module must remain public
        m_modp->modPublic(true);
        iterateChildren(nodep);
    }
    virtual void visit(AstScInt* nodep) override {
        // Special class info means the module must remain public
        m_modp->modPublic(true);
        iterateChildren(nodep);
    }

    virtual void visit(AstIfaceRefDType* nodep) override {
        // LinkDot checked modports, now remove references to them
        // Keeping them later caused problems with InstDeArray,
        // as it needed to make new modport arrays and such
        nodep->modportp(nullptr);
        iterateChildren(nodep);
    }
    // virtual void visit(AstModport* nodep) override { ... }
    // We keep Modport's themselves around for XML dump purposes

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

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

//######################################################################
// LinkBotupVisitor
//      Recurses cells backwards, so we can pick up those things that propagate
//      from child cells up to the top module.

class LinkBotupVisitor final : public AstNVisitor {
private:
    // STATE
    AstNodeModule* m_modp = nullptr;  // Current module

    // METHODS
    VL_DEBUG_FUNC;  // Declare debug()

    // VISITs
    virtual void visit(AstNetlist* nodep) override {
        // Iterate modules backwards, in bottom-up order.
        iterateChildrenBackwards(nodep);
    }
    virtual void visit(AstNodeModule* nodep) override {
        VL_RESTORER(m_modp);
        {
            m_modp = nodep;
            iterateChildren(nodep);
        }
    }
    virtual void visit(AstCell* nodep) override {
        // Parent module inherits child's publicity
        if (nodep->modp()->modPublic()) m_modp->modPublic(true);
        //** No iteration for speed
    }
    virtual void visit(AstNodeMath*) override {}  // Accelerate
    virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }

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

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

void V3LinkResolve::linkResolve(AstNetlist* rootp) {
    UINFO(4, __FUNCTION__ << ": " << endl);
    {
        LinkResolveVisitor visitor(rootp);
        LinkBotupVisitor visitorb(rootp);
    }  // Destruct before checking
    V3Global::dumpCheckGlobalTree("linkresolve", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 6);
}
