//*************************************************************************
// DESCRIPTION: Verilator: Resolve module/signal name references
//
// Code available from: http://www.veripool.org/verilator
//
// AUTHORS: Wilson Snyder with Paul Wasson, Duane Gabli
//
//*************************************************************************
//
// Copyright 2003-2009 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.
//
// Verilator is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
//*************************************************************************
// Link TRANSFORMATIONS:
//	Top-down traversal
//	    Cells:
//		Connect pins
//	    VarRef's:
//		Link to var they reference
//*************************************************************************

#include "config_build.h"
#include "verilatedos.h"
#include <cstdio>
#include <cstdarg>
#include <cctype>
#include <unistd.h>
#include <map>
#include <algorithm>
#include <vector>

#include "V3Global.h"
#include "V3Link.h"
#include "V3SymTable.h"
#include "V3Ast.h"

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

class LinkVisitor : public AstNVisitor {
private:
    // NODE STATE
    //  Entire netlist:
    //   AstVar/Module/Task::user1() // AstPackage*    Set if inside a package
    //   AstVar::user2p()	// bool		  True if port set for this variable
    //   AstVar/Module::user3p() // V3SymTable*    Table used to create this variable
    //   AstNodeModule::user4p() // V3SymTable*    Module's Symbol table
    //   AstNodeFTask::user4p()	// V3SymTable*    Local Symbol table
    //   AstBegin::user4p()	// V3SymTable*    Local Symbol table
    AstUser2InUse	m_inuser2;
    AstUser3InUse	m_inuser3;
    AstUser4InUse	m_inuser4;

    // ENUMS
    enum IdState {		// Which loop through the tree
	ID_FIND,		// Find all identifiers and add to lists
	ID_PARAM,		// Move parameters to cell definitions
	ID_RESOLVE};		// Resolve all identifiers and report errors

    // STATE
    // Below state needs to be preserved between each module call.
    AstPackage*		m_packagep;	// Current package
    AstNodeModule*	m_modp;		// Current module
    AstNodeFTask* m_ftaskp;	// Current function/task
    IdState	m_idState;	// Id linking mode (find or resolve)
    int		m_paramNum;	// Parameter number, for position based connection
    V3SymTable* m_curVarsp;	// Symbol table of variables and tasks under table we're inserting into
    V3SymTable* m_cellVarsp;	// Symbol table of variables under cell's module
    int		m_beginNum;	// Begin block number, 0=none seen
    int		m_modBeginNum;	// Begin block number in module, 0=none seen
    bool	m_inGenerate;	// Inside a generate
    vector<V3SymTable*> m_delSymps;	// Symbol tables to delete

    static int debug() {
	static int level = -1;
	if (VL_UNLIKELY(level < 0)) level = v3Global.opt.debugSrcLevel(__FILE__);
	return level;
    }

    // METHODS
    V3SymTable* symsFindNew(AstNode* nodep, V3SymTable* upperVarsp) {
	// Find or create symbol table for this node
	if (V3SymTable* symsp = nodep->user4p()->castSymTable()) {
	    return symsp;
	} else {
	    V3SymTable* symsp = new V3SymTable(nodep, upperVarsp);
	    m_delSymps.push_back(symsp);
	    nodep->user4p(symsp);
	    return symsp;
	}
    }
    V3SymTable* symsFind(AstNode* nodep) {
	// Find or create symbol table for this node
	if (V3SymTable* symsp = nodep->user4p()->castSymTable()) {
	    return symsp;
	} else {
	    nodep->v3fatalSrc("Symbol table not found looking up symbol");
	    return NULL;
	}
    }

    void symsInsert(const string& name, AstNode* nodep) {
	// Insert into symbol table, and remember what table the node is in
	m_curVarsp->insert(name, nodep);
	nodep->user3p(m_curVarsp);
	nodep->user1p(m_packagep);
    }

    AstPackage* packageFor(AstNode* nodep) {
	if (nodep) return nodep->user1p()->castNode()->castPackage();  // Loaded by symsInsert
	else return NULL;
    }

    void linkVarName (AstVarRef* nodep) {
	if (!nodep->varp()) {
	    AstVar* varp = m_curVarsp->findIdUpward(nodep->name())->castVar();
	    nodep->varp(varp);
	    nodep->packagep(packageFor(varp));
	}
    }

    string nodeTextType(AstNode* nodep) {
	if (nodep->castVar()) return "variable";
	else if (nodep->castCell()) return "cell";
	else if (nodep->castTask()) return "task";
	else if (nodep->castFunc()) return "function";
	else if (nodep->castBegin()) return "block";
	else return nodep->prettyTypeName();
    }

    string ucfirst(const string& text) {
	string out = text;
	out[0] = toupper(out[0]);
	return out;
    }

    void findAndInsertAndCheck(AstNode* nodep, const string& name) {
	// Lookup the given name under current symbol table
	// Insert if not found
	// Report error if there's a duplicate
	//
	// Note we only check for conflicts at the same level; it's ok if one block hides another
	// We also wouldn't want to not insert it even though it's lower down
	AstNode* foundp = m_curVarsp->findIdFlat(name);
	if (!foundp) {
	    symsInsert(nodep->name(), nodep);
	    foundp = nodep;
	} else if (nodep==foundp) {  // Already inserted.
	    // Good.
	} else if ((nodep->castBegin() || foundp->castBegin())
		   && m_inGenerate) {
	    // Begin: ... blocks often replicate under genif/genfor, so simply suppress duplicate checks
	    // See t_gen_forif.v for an example.
	} else if (nodep->type() == foundp->type()) {
	    nodep->v3error("Duplicate declaration of "<<nodeTextType(foundp)<<": "<<nodep->prettyName());
	    foundp->v3error("... Location of original declaration");
	} else {
	    nodep->v3error("Unsupported in C: "<<ucfirst(nodeTextType(nodep))<<" has the same name as "
			   <<nodeTextType(foundp)<<": "<<nodep->prettyName());
	    foundp->v3error("... Location of original declaration");
	}
    }

    void createImplicitVar (AstVarRef* forrefp, bool noWarn) {
	// Create implicit after warning
	linkVarName(forrefp);
	if (!forrefp->varp()) {
	    if (!noWarn) forrefp->v3warn(IMPLICIT,"Signal definition not found, creating implicitly: "<<forrefp->prettyName());
	    AstVar* newp = new AstVar (forrefp->fileline(), AstVarType::WIRE,
				       forrefp->name(), AstLogicPacked(), 1);

	    newp->trace(m_modp->modTrace());
	    m_modp->addStmtp(newp);
	    // Link it to signal list
	    IdState old_id = m_idState;
	    m_idState = ID_FIND;
	    newp->accept(*this);
	    m_idState = old_id;
	}
    }

    // VISITs
    virtual void visit(AstNetlist* nodep, AstNUser*) {
	// Top scope
	m_curVarsp = symsFindNew(nodep, NULL);
	// And recurse...
	// Recurse...
	m_idState = ID_FIND;
	nodep->iterateChildren(*this);
	if (debug()==9) m_curVarsp->dump(cout,"-curvars: ",true/*user4p_is_table*/);
	m_idState = ID_PARAM;
	nodep->iterateChildren(*this);
	m_idState = ID_RESOLVE;
	nodep->iterateChildren(*this);
	nodep->checkTree();
    }

    virtual void visit(AstNodeModule* nodep, AstNUser*) {
	// Module: Create sim table for entire module and iterate
	UINFO(2,"Link Module: "<<nodep<<endl);
	V3SymTable* upperVarsp = m_curVarsp;
	{
	    m_modp = nodep;
	    if (!m_curVarsp) nodep->v3fatalSrc("NULL");
	    if (nodep->castPackage()) m_packagep = nodep->castPackage();
	    if (m_packagep && m_packagep->isDollarUnit()) {  // $unit goes on "top"
		nodep->user4p(m_curVarsp);
		// Don't insert dunit itself, or symtable->dump will loop-recurse
	    } else {
		findAndInsertAndCheck(nodep, nodep->name());
		m_curVarsp = symsFindNew(nodep, upperVarsp);
		UINFO(9, "New module scope "<<m_curVarsp<<endl);
	    }
	    // This state must be save/restored in the cell visitor function
	    m_cellVarsp = NULL;
	    m_paramNum = 0;
	    m_beginNum = 0;
	    m_modBeginNum = 0;
	    nodep->iterateChildren(*this);
	    // Prep for next
	    m_modp = NULL;
	    m_packagep = NULL;
	}
	m_curVarsp = upperVarsp;
    }

    virtual void visit(AstGenerate* nodep, AstNUser*) {
	// Begin: ... blocks often replicate under genif/genfor, so simply suppress duplicate checks
	// See t_gen_forif.v for an example.
	bool lastInGen = m_inGenerate;
	{
	    m_inGenerate = true;
	    nodep->iterateChildren(*this);
	}
	m_inGenerate = lastInGen;
    }

    virtual void visit(AstVar* nodep, AstNUser*) {
	// Var: Remember its name for later resolution
	if (!m_curVarsp) nodep->v3fatalSrc("Var not under module??\n");
	nodep->iterateChildren(*this);
	if (m_idState==ID_FIND) {
	    // We used modTrace before leveling, and we may now
	    // want to turn it off now that we know the levelizations
	    if (v3Global.opt.traceDepth()
		&& (m_modp->level()-1) > v3Global.opt.traceDepth()) {
		m_modp->modTrace(false);
		nodep->trace(false);
	    }
	    // Find under either a task or the module's vars
	    AstNode* foundp = m_curVarsp->findIdUpward(nodep->name());
	    AstVar* findvarp = foundp->castVar();
	    bool ins=false;
	    if (!foundp) {
		ins=true;
	    } else if (!findvarp && m_curVarsp->findIdFlat(nodep->name())) {
		nodep->v3error("Unsupported in C: Variable has same name as "
			       <<nodeTextType(foundp)<<": "<<nodep->prettyName());
	    } else if (findvarp != nodep) {
		UINFO(4,"DupVar: "<<nodep<<" ;; "<<foundp<<endl);
		if (findvarp && findvarp->user3p() == m_curVarsp) {  // Only when on same level
		    if ((findvarp->isIO() && nodep->isSignal())
			|| (findvarp->isSignal() && nodep->isIO())) {
			findvarp->combineType(nodep);
			findvarp->fileline()->warnStateInherit(nodep->fileline());
			nodep->unlinkFrBack()->deleteTree(); nodep=NULL;
		    } else {
			nodep->v3error("Duplicate declaration of signal: "<<nodep->prettyName());
			findvarp->v3error("... Location of original declaration");
		    }
		} else {
		    // User can disable the message at either point
		    if (!nodep->fileline()->warnIsOff(V3ErrorCode::VARHIDDEN)
			&& !foundp->fileline()->warnIsOff(V3ErrorCode::VARHIDDEN)) {
			nodep->v3warn(VARHIDDEN,"Declaration of signal hides declaration in upper scope: "<<nodep->name());
			foundp->v3warn(VARHIDDEN,"... Location of original declaration");
		    }
		    ins = true;
		}
	    }
	    if (ins) {
		symsInsert(nodep->name(), nodep);
		if (nodep->isGParam()) {
		    m_paramNum++;
		    symsInsert("__paramNumber"+cvtToStr(m_paramNum), nodep);
		}
	    }
	}
	if (m_idState==ID_RESOLVE) {
	    if (nodep->isIO() && !m_ftaskp && !nodep->user2()) {
		nodep->v3error("Input/output/inout does not appear in port list: "<<nodep->prettyName());
	    }
	}
    }
    virtual void visit(AstVarRef* nodep, AstNUser*) {
	// VarRef: Resolve its reference
	if (m_idState==ID_RESOLVE && !nodep->varp()) {
	    linkVarName(nodep);
	    if (!nodep->varp()) {
		nodep->v3error("Can't find definition of signal: "<<nodep->prettyName());
		createImplicitVar (nodep, true);  // So only complain once
	    }
	}
	nodep->iterateChildren(*this);
    }
    //virtual void visit(AstVarXRef* nodep, AstNUser*) {
    // See LinkDotVisitor
    //}

    virtual void visit(AstNodeFTask* nodep, AstNUser*) {
	// NodeTask: Remember its name for later resolution
	if (!m_curVarsp) nodep->v3fatalSrc("Function/Task not under module??\n");
	// Remember the existing symbol table scope
	V3SymTable* upperVarsp = m_curVarsp;
	{
	    // Create symbol table for the task's vars
	    m_curVarsp = symsFindNew(nodep, upperVarsp);

	    // Convert the func's range to the output variable
	    // This should probably be done in the Parser instead, as then we could
	    // just attact normal signal attributes to it.
	    if (nodep->fvarp()
		&& !nodep->fvarp()->castVar()) {
		AstNodeDType* dtypep = nodep->fvarp()->castNodeDType();
		// If unspecified, function returns one bit; however when we support NEW() it could
		// also return the class reference.
		if (dtypep) dtypep->unlinkFrBack();
		else dtypep = new AstBasicDType(nodep->fileline(), AstBasicDTypeKwd::LOGIC);
		AstVar* newvarp = new AstVar(nodep->fileline(), AstVarType::OUTPUT, nodep->name(), dtypep);
		newvarp->isSigned(nodep->isSigned());
		newvarp->funcReturn(true);
		newvarp->trace(false);  // Not user visible
		newvarp->attrIsolateAssign(nodep->attrIsolateAssign());
		nodep->addFvarp(newvarp);
		// Explicit insert required, as the var name shadows the upper level's task name
		symsInsert(newvarp->name(), newvarp);
	    }
	    m_ftaskp = nodep;
	    nodep->iterateChildren(*this);
	    m_ftaskp = NULL;
	}
	m_curVarsp = upperVarsp;
	if (m_idState==ID_FIND) {
	    findAndInsertAndCheck(nodep, nodep->name());
	}
    }
    virtual void visit(AstBegin* nodep, AstNUser*) {
	// Link variables underneath blocks
	// Remember the existing symbol table scope
	V3SymTable* upperVarsp = m_curVarsp;
	// Rename "genblk"s to include a number
	// All blocks are numbered in the standard, IE we start with "genblk1" even if only one.
	UINFO(8,"   "<<nodep<<endl);
	if (m_idState==ID_FIND && nodep->name() == "genblk") {
	    ++m_beginNum;
	    nodep->name(nodep->name()+cvtToStr(m_beginNum));
	}
	if (m_idState==ID_FIND && nodep->name()=="" && nodep->unnamed()) {
	    // Unnamed blocks are only important when they contain var
	    // decls, so search for them. (Otherwise adding all the
	    // unnamed#'s would just confuse tracing variables in
	    // places such as tasks, where "task ...; begin ... end"
	    // are common.
	    for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp=stmtp->nextp()) {
		if (stmtp->castVar()) {
		    ++m_modBeginNum;
		    nodep->name("unnamedblk"+cvtToStr(m_modBeginNum));
		    break;
		}
	    }
	}

	// Check naming (we don't really care, but some tools do, so better to warn)
	if (m_idState==ID_FIND && nodep->name()!="") {
	    findAndInsertAndCheck(nodep, nodep->name());
	}
	// Recurse
	int oldNum = m_beginNum;
	m_beginNum = 0;
	{
	    // Create symbol table for the task's vars
	    m_curVarsp = symsFindNew(nodep, upperVarsp);
	    nodep->iterateChildren(*this);
	}
	m_curVarsp = upperVarsp;
	m_beginNum = oldNum;
    }
    virtual void visit(AstNodeFTaskRef* nodep, AstNUser*) {
	// NodeFTaskRef: Resolve its reference
	if (m_idState==ID_RESOLVE && !nodep->taskp()) {
	    if (nodep->dotted() == "") {
		AstNodeFTask* taskp;
		if (nodep->packagep()) {
		    taskp = symsFind(nodep->packagep())->findIdUpward(nodep->name())->castNodeFTask();
		} else {
		    taskp = m_curVarsp->findIdUpward(nodep->name())->castNodeFTask();
		}
		if (!taskp) { nodep->v3error("Can't find definition of task/function: "<<nodep->prettyName()); }
		nodep->taskp(taskp);
		nodep->packagep(packageFor(taskp));
	    }
	}
	nodep->iterateChildren(*this);
    }

    virtual void visit(AstTypedef* nodep, AstNUser*) {
	// Remember its name for later resolution
	if (!m_curVarsp) nodep->v3fatalSrc("Typedef not under module??\n");
	nodep->iterateChildren(*this);
	if (m_idState==ID_FIND) {
	    findAndInsertAndCheck(nodep, nodep->name());
	}
    }
    virtual void visit(AstTypedefFwd* nodep, AstNUser*) {
	// We only needed the forward declaration in order to parse correctly.
	// We won't even check it was ever really defined, as it might have been in a header
	// file referring to a module we never needed
	nodep->unlinkFrBack()->deleteTree();
    }
    virtual void visit(AstRefDType* nodep, AstNUser*) {
	// Resolve its reference
	if (m_idState==ID_RESOLVE && !nodep->defp()) {
	    AstTypedef* defp;
	    if (nodep->packagep()) {
		defp = symsFind(nodep->packagep())->findIdFlat(nodep->name())->castTypedef();
	    } else {
		defp = m_curVarsp->findIdUpward(nodep->name())->castTypedef();
	    }
	    if (!defp) { nodep->v3error("Can't find typedef: "<<nodep->prettyName()); }
	    nodep->defp(defp);
	    nodep->packagep(packageFor(defp));
	}
	nodep->iterateChildren(*this);
    }
    virtual void visit(AstBasicDType* nodep, AstNUser*) {
	if (m_idState==ID_RESOLVE && nodep->keyword()==AstBasicDTypeKwd::STRING) {
	    if (!(m_ftaskp && m_ftaskp->dpiImport() && 0/*UNIMP*/)) {
		nodep->v3error("Unsupported: SystemVerilog 'string' anywhere but DPI import __format");
	    }
	}
	nodep->iterateChildren(*this);
    }

    virtual void visit(AstCell* nodep, AstNUser*) {
	// Cell: Resolve its filename.  If necessary, parse it.
	if (m_idState==ID_FIND) {
	    // Add to list of all cells, for error checking and defparam's
	    findAndInsertAndCheck(nodep, nodep->name());
	}
	if (!nodep->modp()) {
	    nodep->v3fatalSrc("Cell has unlinked module"); // V3LinkCell should have errored out
	}
	else {
	    // Need to pass the module info to this cell, so we can link up the pin names
	    if (m_idState==ID_RESOLVE) {
		m_cellVarsp = nodep->modp()->user4p()->castSymTable();
		UINFO(4,"(Backto) Link Cell: "<<nodep<<endl);
		//if (debug()) { nodep->dumpTree(cout,"linkcell:"); }
		//if (debug()) { nodep->modp()->dumpTree(cout,"linkcemd:"); }
		nodep->iterateChildren(*this);
		m_cellVarsp = NULL;
	    }
	}
	// Parent module inherits child's publicity
	// This is done bottom up in the LinkBotupVisitor stage
    }

    virtual void visit(AstPort* nodep, AstNUser*) {
	// Port: Stash the pin number
	if (!m_curVarsp) nodep->v3fatalSrc("Port not under module??\n");
	if (m_idState==ID_PARAM) {
	    // Need to set pin numbers after varnames are created
	    // But before we do the final resolution based on names
	    AstVar* refp = m_curVarsp->findIdFlat(nodep->name())->castVar();
	    if (!refp) {
		nodep->v3error("Input/output/inout declaration not found for port: "<<nodep->prettyName());
	    } else if (!refp->isIO()) {
		nodep->v3error("Pin is not an in/out/inout: "<<nodep->prettyName());
	    } else {
		symsInsert("__pinNumber"+cvtToStr(nodep->pinNum()), refp);
		refp->user2(true);
	    }
	    // Ports not needed any more
	    nodep->unlinkFrBack()->deleteTree();  nodep=NULL;
	}
    }

    void pinImplicitExprRecurse(AstNode* nodep) {
	// Under a pin, Check interconnect expression for a pin reference or a concat.
	// Create implicit variable as needed
	if (AstVarRef* varrefp = nodep->castVarRef()) {
	    // To prevent user errors, we should only do single bit
	    // implicit vars, however some netlists (MIPS) expect single
	    // bit implicit wires to get created with range 0:0 etc.
	    //if (!nodep->modVarp()->rangep()) ...
	    createImplicitVar(varrefp, false);
	}
	// These are perhaps a little too generous, as a SELect of siga[sigb]
	// perhaps shouldn't create an implicit variable.  But, we've warned...
	else {
	    if (nodep->op1p()) pinImplicitExprRecurse(nodep->op1p());
	    if (nodep->op2p()) pinImplicitExprRecurse(nodep->op2p());
	    if (nodep->op3p()) pinImplicitExprRecurse(nodep->op3p());
	    if (nodep->op4p()) pinImplicitExprRecurse(nodep->op4p());
	}
    }

    virtual void visit(AstPin* nodep, AstNUser*) {
	// Pin: Link to submodule's pin
	// ONLY CALLED by AstCell during ID_RESOLVE state
	if (!m_cellVarsp) nodep->v3fatalSrc("Pin not under cell?\n");
	if (m_idState==ID_RESOLVE && !nodep->modVarp()) {
	    AstVar* refp = m_cellVarsp->findIdFlat(nodep->name())->castVar();
	    if (!refp) {
		nodep->v3error("Pin not found: "<<nodep->prettyName());
	    } else if (!refp->isIO() && !refp->isParam()) {
		nodep->v3error("Pin is not an in/out/inout/param: "<<nodep->prettyName());
	    } else {
		nodep->modVarp(refp);
	    }
	}
	// Deal with implicit definitions
	if (m_idState==ID_RESOLVE && nodep->modVarp()
	    && !nodep->svImplicit()) {   // SV 19.11.3: .name pins don't allow implicit decls
	    pinImplicitExprRecurse(nodep->exprp());
	}
	nodep->iterateChildren(*this);
    }

    virtual void visit(AstAssignW* nodep, AstNUser*) {
	// Deal with implicit definitions
	// We used to nodep->allowImplicit() here, but it turns out
	// normal "assigns" can also make implicit wires.  Yuk.
	if (AstVarRef* forrefp = nodep->lhsp()->castVarRef()) {
	    createImplicitVar(forrefp, false);
	}
	nodep->iterateChildren(*this);
    }

    virtual void visit(AstDefParam* nodep, AstNUser*) {
	nodep->iterateChildren(*this);
	if (m_idState==ID_PARAM) {
	    AstNode* foundp = m_curVarsp->findIdUpward(nodep->path());
	    AstCell* cellp = foundp->castCell();
	    if (!cellp) {
		nodep->v3error("In defparam, cell "<<nodep->path()<<" never declared");
	    } else {
		AstNode* exprp = nodep->rhsp()->unlinkFrBack();
		UINFO(9,"Defparam cell "<<nodep->path()<<"."<<nodep->name()
		      <<" <= "<<exprp<<endl);
		// Don't need to check the name of the defparam exists.  V3Param does.
		AstPin* pinp = new AstPin (nodep->fileline(),
					   -1, // Pin# not relevant
					   nodep->name(),
					   exprp);
		cellp->addParamsp(pinp);
		nodep->unlinkFrBack()->deleteTree(); nodep=NULL;
	    }
	}
    }

    virtual void visit(AstPackageImport* nodep, AstNUser*) {
	UINFO(2,"  Link: "<<nodep<<endl);
	V3SymTable* srcp = symsFind(nodep->packagep());
	if (nodep->name()!="*") {
	    AstNode* impp = srcp->findIdFlat(nodep->name());
	    if (!impp) {
		nodep->v3error("Import object not found: "<<nodep->packagep()->prettyName()<<"::"<<nodep->prettyName());
	    }
	}
	m_curVarsp->import(srcp, nodep->name());
	// No longer needed
	nodep->unlinkFrBack()->deleteTree(); nodep=NULL;
    }

    virtual void visit(AstNode* nodep, AstNUser*) {
	// Default: Just iterate
	nodep->iterateChildren(*this);
    }

public:
    // CONSTUCTORS
    LinkVisitor(AstNetlist* rootp) {
	m_curVarsp = NULL;
	m_cellVarsp = NULL;
	m_modp = NULL;
	m_ftaskp = NULL;
	m_packagep = NULL;
	m_paramNum = 0;
	m_beginNum = 0;
	m_modBeginNum = 0;
	m_inGenerate = false;
	//
	rootp->accept(*this);
    }
    virtual ~LinkVisitor() {
	for (vector<V3SymTable*>::iterator it = m_delSymps.begin(); it != m_delSymps.end(); ++it) {
	    delete (*it);
	}
    }
};

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

void V3Link::link(AstNetlist* rootp) {
    UINFO(4,__FUNCTION__<<": "<<endl);
    LinkVisitor visitor(rootp);
}
