// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Rename scope references to module-local 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 // //************************************************************************* // DESCOPE TRANSFORMATIONS: // All modules: // Each VARREF/FUNCCALL // Change varref name() to be relative to current module // Remove varScopep() // This allows for better V3Combine'ing. // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include "V3Global.h" #include "V3Descope.h" #include "V3Ast.h" #include "V3EmitCBase.h" #include //###################################################################### class DescopeVisitor final : public AstNVisitor { private: // NODE STATE // Cleared entire netlist // AstCFunc::user() // bool. Indicates processing completed AstUser1InUse m_inuser1; // TYPES typedef std::multimap FuncMmap; // STATE AstNodeModule* m_modp = nullptr; // Current module AstScope* m_scopep = nullptr; // Current scope bool m_modSingleton = false; // m_modp is only instanced once bool m_allowThis = false; // Allow function non-static bool m_needThis = false; // Make function non-static FuncMmap m_modFuncs; // Name of public functions added // METHODS VL_DEBUG_FUNC; // Declare debug() static bool modIsSingleton(AstNodeModule* modp) { // True iff there's exactly one instance of this module in the design. int instances = 0; for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { if (VN_IS(stmtp, Scope)) { if (++instances > 1) { return false; } } } return (instances == 1); } // Construct the best prefix to reference an object in 'scopep' // from a CFunc in 'm_scopep'. Result may be relative // ("this->[...]") or absolute ("vlTOPp->[...]"). // // Using relative references allows V3Combine'ing // code across multiple instances of the same module. // // Sets 'hierThisr' true if the object is local to this scope // (and could be made into a function-local later in V3Localize), // false if the object is in another scope. string descopedName(bool& hierThisr, string& hierUnprot, const AstScope* scopep, const AstVar* varp) { UASSERT(scopep, "Var/Func not scoped"); hierThisr = (scopep == m_scopep); // It's possible to disable relative references. This is a concession // to older compilers (gcc < 4.5.x) that don't understand __restrict__ // well and emit extra ld/st to guard against pointer aliasing // when this-> and vlTOPp-> are mixed in the same function. // // "vlTOPp" is declared "restrict" so better compilers understand // that it won't alias with "this". bool relativeRefOk = v3Global.opt.relativeCFuncs(); // // Static functions can't use this if (!m_allowThis) relativeRefOk = false; // // Use absolute refs in top-scoped routines, keep them static. // The DPI callback registration depends on representing top-level // static routines as plain function pointers. That breaks if those // become true OO routines. // // V3Combine wouldn't likely be able to combine top-level // routines anyway, so there's no harm in keeping these static. UASSERT_OBJ(m_modp, scopep, "Scope not under module"); if (m_modp->isTop()) relativeRefOk = false; // // Use absolute refs if this scope is the only instance of the module. // Saves a bit of overhead on passing the 'this' pointer, and there's no // need to be nice to V3Combine when we have only a single instance. // The risk that this prevents combining identical logic from differently- // named but identical modules seems low. if (m_modSingleton) relativeRefOk = false; // // Class methods need relative if (m_modp && VN_IS(m_modp, Class)) relativeRefOk = true; if (varp && varp->isFuncLocal()) { hierThisr = true; return ""; // Relative to function, not in this } else if (relativeRefOk && scopep == m_scopep) { m_needThis = true; return "this->"; } else if (VN_IS(scopep->modp(), Class)) { hierUnprot = v3Global.opt.modPrefix() + "_"; // Prefix before protected part return scopep->modp()->name() + "::"; } else if (relativeRefOk && scopep->aboveScopep() && scopep->aboveScopep() == m_scopep) { // Reference to scope of cell directly under this module, can just "cell->" string name = scopep->name(); string::size_type pos; if ((pos = name.rfind('.')) != string::npos) name.erase(0, pos + 1); m_needThis = true; return name + "->"; } else { // Reference to something elsewhere, or relative references // are disabled. Use global variable UINFO(8, " Descope " << scopep << endl); UINFO(8, " to " << scopep->name() << endl); UINFO(8, " under " << m_scopep->name() << endl); if (!scopep->aboveScopep()) { // Top // We could also return "vlSymsp->TOPp->" here, but GCC would // suspect aliases. return "vlTOPp->"; } else { return scopep->nameVlSym() + "."; } } } void makePublicFuncWrappers() { // We recorded all public functions in m_modFuncs. // If for any given name only one function exists, we can use that function directly. // If multiple functions exist, we need to select the appropriate scope. for (FuncMmap::iterator it = m_modFuncs.begin(); it != m_modFuncs.end(); ++it) { string name = it->first; AstCFunc* topFuncp = it->second; auto nextIt1 = it; ++nextIt1; bool moreOfSame1 = (nextIt1 != m_modFuncs.end() && nextIt1->first == name); if (moreOfSame1) { // Multiple functions under this name, need a wrapper function UINFO(6, " Wrapping " << name << " multifuncs\n"); AstCFunc* newfuncp = topFuncp->cloneTree(false); if (newfuncp->initsp()) newfuncp->initsp()->unlinkFrBackWithNext()->deleteTree(); if (newfuncp->stmtsp()) newfuncp->stmtsp()->unlinkFrBackWithNext()->deleteTree(); if (newfuncp->finalsp()) newfuncp->finalsp()->unlinkFrBackWithNext()->deleteTree(); newfuncp->name(name); newfuncp->isStatic(false); newfuncp->addInitsp( new AstCStmt(newfuncp->fileline(), EmitCBaseVisitor::symClassVar() + " = this->__VlSymsp;\n")); newfuncp->addInitsp( new AstCStmt(newfuncp->fileline(), EmitCBaseVisitor::symTopAssign() + "\n")); topFuncp->addNextHere(newfuncp); // In the body, call each function if it matches the given scope for (FuncMmap::iterator eachIt = it; eachIt != m_modFuncs.end() && eachIt->first == name; ++eachIt) { it = eachIt; AstCFunc* funcp = eachIt->second; auto nextIt2 = eachIt; ++nextIt2; bool moreOfSame = (nextIt2 != m_modFuncs.end() && nextIt2->first == name); UASSERT_OBJ(funcp->scopep(), funcp, "Not scoped"); UINFO(6, " Wrapping " << name << " " << funcp << endl); UINFO(6, " at " << newfuncp->argTypes() << " und " << funcp->argTypes() << endl); funcp->declPrivate(true); AstNode* argsp = nullptr; for (AstNode* stmtp = newfuncp->argsp(); stmtp; stmtp = stmtp->nextp()) { if (AstVar* portp = VN_CAST(stmtp, Var)) { if (portp->isIO() && !portp->isFuncReturn()) { AstNode* newp = new AstVarRef(portp->fileline(), portp, portp->isWritable() ? VAccess::WRITE : VAccess::READ); argsp = argsp ? argsp->addNextNull(newp) : newp; } } } AstNode* returnp = new AstCReturn( funcp->fileline(), new AstCCall(funcp->fileline(), funcp, argsp)); if (moreOfSame) { AstIf* ifp = new AstIf( funcp->fileline(), new AstEq( funcp->fileline(), new AstCMath(funcp->fileline(), "this", 64), new AstCMath(funcp->fileline(), string("&(") + funcp->scopep()->nameVlSym() + ")", 64)), returnp, nullptr); newfuncp->addStmtsp(ifp); } else { newfuncp->addStmtsp(returnp); } } // Not really any way the user could do this, and we'd need // to come up with some return value // newfuncp->addStmtsp(new AstDisplay(newfuncp->fileline(), // AstDisplayType::DT_WARNING, // string("%%Error: ")+name+"() called with bad // scope", nullptr)); // newfuncp->addStmtsp(new AstStop(newfuncp->fileline())); if (debug() >= 9) newfuncp->dumpTree(cout, " newfunc: "); } else { // Only a single function under this name, we can simply rename it UINFO(6, " Wrapping " << name << " just one " << topFuncp << endl); topFuncp->name(name); } } } // VISITORS virtual void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); { m_modp = nodep; m_modFuncs.clear(); m_modSingleton = modIsSingleton(m_modp); iterateChildren(nodep); makePublicFuncWrappers(); } } virtual void visit(AstScope* nodep) override { m_scopep = nodep; iterateChildren(nodep); m_scopep = nullptr; } virtual void visit(AstVarScope* nodep) override { // Delete the varscope when we're finished nodep->unlinkFrBack(); pushDeletep(nodep); } virtual void visit(AstNodeVarRef* nodep) override { iterateChildren(nodep); // Convert the hierch name UINFO(9, " ref-in " << nodep << endl); UASSERT_OBJ(m_scopep, nodep, "Node not under scope"); bool hierThis; string hierUnprot; nodep->hiernameToProt(descopedName(hierThis /*ref*/, hierUnprot /*ref*/, nodep->varScopep()->scopep(), nodep->varScopep()->varp())); nodep->hiernameToUnprot(hierUnprot); nodep->hierThis(hierThis); nodep->varScopep(nullptr); UINFO(9, " refout " << nodep << endl); } virtual void visit(AstNodeCCall* nodep) override { // UINFO(9, " " << nodep << endl); iterateChildren(nodep); // Convert the hierch name UASSERT_OBJ(m_scopep, nodep, "Node not under scope"); UASSERT_OBJ(nodep->funcp()->scopep(), nodep, "CFunc not under scope"); bool hierThis; string hierUnprot; nodep->hiernameToProt( descopedName(hierThis /*ref*/, hierUnprot /*ref*/, nodep->funcp()->scopep(), nullptr)); nodep->hiernameToUnprot(hierUnprot); // Can't do this, as we may have more calls later // nodep->funcp()->scopep(nullptr); } virtual void visit(AstCFunc* nodep) override { VL_RESTORER(m_needThis); VL_RESTORER(m_allowThis); if (!nodep->user1()) { m_needThis = false; m_allowThis = nodep->isStatic().falseUnknown(); // Non-static or unknown if static iterateChildren(nodep); nodep->user1(true); if (m_needThis) nodep->isStatic(false); // If it's under a scope, move it up to the top if (m_scopep) { nodep->unlinkFrBack(); m_modp->addStmtp(nodep); if (nodep->funcPublic()) { // There may be multiple public functions by the same name; // record for later correction or making of shells m_modFuncs.insert(make_pair(nodep->name(), nodep)); nodep->name(m_scopep->nameDotless() + "__" + nodep->name()); } } } } virtual void visit(AstVar*) override {} virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS explicit DescopeVisitor(AstNetlist* nodep) { iterate(nodep); } virtual ~DescopeVisitor() override = default; }; //###################################################################### // Descope class functions void V3Descope::descopeAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); v3Global.assertScoped(false); { DescopeVisitor visitor(nodep); } // Destruct before checking V3Global::dumpCheckGlobalTree("descope", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); }