From 3ec1e9eb072472e516b0d4235df3e1a392812c08 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 16 Jun 2021 17:44:06 +0100 Subject: [PATCH] Localize variables from other modules when possible V3Localize can now localize variable references that reference variables located in scopes different from the referencing function. This also means V3Descope has now moved after V3Localize. --- src/V3Ast.h | 3 - src/V3AstNodes.h | 1 - src/V3DepthBlock.cpp | 17 ++-- src/V3Descope.cpp | 11 +-- src/V3Localize.cpp | 193 ++++++++++++++++++++----------------------- src/Verilator.cpp | 11 ++- 6 files changed, 112 insertions(+), 124 deletions(-) diff --git a/src/V3Ast.h b/src/V3Ast.h index c72e62b3a..1fd3940d3 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -2280,7 +2280,6 @@ private: string m_name; // Name of variable string m_selfPointer; // Output code object pointer (e.g.: 'this') string m_classPrefix; // Output class prefix (i.e.: the part before ::) - bool m_hierThis = false; // m_selfPointer points to "this" function protected: AstNodeVarRef(AstType t, FileLine* fl, const string& name, const VAccess& access) @@ -2312,8 +2311,6 @@ public: void varp(AstVar* varp); AstVarScope* varScopep() const { return m_varScopep; } void varScopep(AstVarScope* varscp) { m_varScopep = varscp; } - bool hierThis() const { return m_hierThis; } - void hierThis(bool flag) { m_hierThis = flag; } string selfPointer() const { return m_selfPointer; } void selfPointer(const string& value) { m_selfPointer = value; } string selfPointerProtect(bool useSelfForThis) const; diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index c2b82f85c..94ef71ce3 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -2150,7 +2150,6 @@ public: } bool isClassMember() const { return varType() == AstVarType::MEMBER; } bool isStatementTemp() const { return (varType() == AstVarType::STMTTEMP); } - bool isMovableToBlock() const { return (varType() == AstVarType::BLOCKTEMP || isFuncLocal()); } bool isXTemp() const { return (varType() == AstVarType::XTEMP); } bool isParam() const { return (varType() == AstVarType::LPARAM || varType() == AstVarType::GPARAM); diff --git a/src/V3DepthBlock.cpp b/src/V3DepthBlock.cpp index f94813a06..54a131a75 100644 --- a/src/V3DepthBlock.cpp +++ b/src/V3DepthBlock.cpp @@ -48,25 +48,24 @@ private: AstCFunc* createDeepFunc(AstNode* nodep) { AstNRelinker relinkHandle; nodep->unlinkFrBack(&relinkHandle); - // Create function - string name = m_cfuncp->name() + "__deep" + cvtToStr(++m_deepNum); - AstCFunc* funcp = new AstCFunc(nodep->fileline(), name, nullptr); + // Create sub function + AstScope* const scopep = m_cfuncp->scopep(); + const string name = m_cfuncp->name() + "__deep" + cvtToStr(++m_deepNum); + AstCFunc* const funcp = new AstCFunc(nodep->fileline(), name, scopep); funcp->slow(m_cfuncp->slow()); funcp->isStatic(m_cfuncp->isStatic()); funcp->isLoose(m_cfuncp->isLoose()); funcp->addStmtsp(nodep); - m_modp->addStmtp(funcp); - // Call it at the point where the body was removed from - AstCCall* callp = new AstCCall(nodep->fileline(), funcp); + scopep->addActivep(funcp); + // Call sub function at the point where the body was removed from + AstCCall* const callp = new AstCCall(nodep->fileline(), funcp); if (VN_IS(m_modp, Class)) { funcp->argTypes(EmitCBaseVisitor::symClassVar()); callp->argTypes("vlSymsp"); - } else if (!funcp->isStatic()) { - callp->selfPointer("this"); } UINFO(6, " New " << callp << endl); - // relinkHandle.relink(callp); + // Done return funcp; } diff --git a/src/V3Descope.cpp b/src/V3Descope.cpp index 87b7e9564..2428d61a4 100644 --- a/src/V3Descope.cpp +++ b/src/V3Descope.cpp @@ -219,15 +219,17 @@ private: } virtual void visit(AstNodeVarRef* nodep) override { iterateChildren(nodep); + if (!nodep->varScopep()) { + UASSERT_OBJ(nodep->varp()->isFuncLocal(), nodep, + "unscoped reference can only appear to function locals at this point"); + return; + } // Convert the hierch name UINFO(9, " ref-in " << nodep << endl); UASSERT_OBJ(m_scopep, nodep, "Node not under scope"); const AstVar* const varp = nodep->varScopep()->varp(); const AstScope* const scopep = nodep->varScopep()->scopep(); - if (varp->isFuncLocal()) { - nodep->hierThis(true); - } else { - nodep->hierThis(scopep == m_scopep); + if (!varp->isFuncLocal()) { nodep->selfPointer(descopedSelfPointer(scopep)); nodep->classPrefix(descopedClassPrefix(scopep)); } @@ -282,7 +284,6 @@ public: 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); } diff --git a/src/V3Localize.cpp b/src/V3Localize.cpp index 12e10f41a..9ee00a9f7 100644 --- a/src/V3Localize.cpp +++ b/src/V3Localize.cpp @@ -15,9 +15,9 @@ //************************************************************************* // LOCALIZE TRANSFORMATIONS: // All modules: -// VAR(BLOCKTEMP... +// VARSCOPE(BLOCKTEMP... // if only referenced in a CFUNC, make it local to that CFUNC -// VAR(others +// VARSCOPE(others // if non-public, set before used, and in single CFUNC, make it local // //************************************************************************* @@ -33,18 +33,17 @@ #include //###################################################################### -// Localize base class +// LocalizeVisitor -class LocalizeBaseVisitor VL_NOT_FINAL : public AstNVisitor { -protected: +class LocalizeVisitor final : public AstNVisitor { +private: // NODE STATE - // Cleared on entire tree - // AstVar::user1p() -> CFunc which references the variable + // AstVar::user1p() -> First AstCFunc which references the variable // AstVar::user2() -> VarFlags. Flag state - // AstVar::user4() -> AstVarRef*. First place signal set; must be first assignment - - // METHODS - VL_DEBUG_FUNC; // Declare debug() + // AstVar::user4p() -> AstVarRef that writes whole variable, if first write ref. + AstUser1InUse m_inuser1; + AstUser2InUse m_inuser2; + AstUser4InUse m_inuser4; // TYPES union VarFlags { @@ -54,99 +53,89 @@ protected: int m_notOpt : 1; // NOT optimizable int m_notStd : 1; // NOT optimizable if a non-blocktemp signal int m_stdFuncAsn : 1; // Found simple assignment - int m_done : 1; // Removed }; // cppcheck-suppress unusedStructMember uint32_t m_flags; - explicit VarFlags(AstNode* nodep) { m_flags = nodep->user2(); } - void setNodeFlags(AstNode* nodep) { nodep->user2(m_flags); } + explicit VarFlags(AstVarScope* nodep) { m_flags = nodep->user2(); } + void setNodeFlags(AstVarScope* nodep) { nodep->user2(m_flags); } }; -}; - -//###################################################################### -// Localize class functions - -class LocalizeDehierVisitor final : public LocalizeBaseVisitor { -private: - // NODE STATE/TYPES - // See above - - // METHODS - virtual void visit(AstVarRef* nodep) override { - // cppcheck-suppress unreadVariable // cppcheck 1.90 bug - VarFlags flags(nodep->varp()); - if (flags.m_done) { - nodep->selfPointer(""); // Remove 'this' - nodep->hierThis(true); - } - } - virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - explicit LocalizeDehierVisitor(AstNetlist* nodep) { iterate(nodep); } - virtual ~LocalizeDehierVisitor() override = default; -}; - -//###################################################################### -// Localize class functions - -class LocalizeVisitor final : public LocalizeBaseVisitor { -private: - // NODE STATE/TYPES - // See above - AstUser1InUse m_inuser1; - AstUser2InUse m_inuser2; - AstUser4InUse m_inuser4; // STATE VDouble0 m_statLocVars; // Statistic tracking AstCFunc* m_cfuncp = nullptr; // Current active function - std::vector m_varps; // List of variables to consider for deletion + std::vector m_varScopeps; // List of variables to consider for localization + std::unordered_multimap + m_references; // VarRefs referencing the given VarScope // METHODS - void clearOptimizable(AstVar* nodep, const char* reason) { + VL_DEBUG_FUNC; // Declare debug() + + void clearOptimizable(AstVarScope* nodep, const char* reason) { UINFO(4, " NoOpt " << reason << " " << nodep << endl); VarFlags flags(nodep); flags.m_notOpt = true; flags.setNodeFlags(nodep); } - void clearStdOptimizable(AstVar* nodep, const char* reason) { + void clearStdOptimizable(AstVarScope* nodep, const char* reason) { UINFO(4, " NoStd " << reason << " " << nodep << endl); VarFlags flags(nodep); flags.m_notStd = true; flags.setNodeFlags(nodep); } - void moveVars() { - for (AstVar* nodep : m_varps) { - if (nodep->valuep()) clearOptimizable(nodep, "HasInitValue"); + void moveVarScopes() { + for (AstVarScope* const nodep : m_varScopeps) { + if (nodep->varp()->valuep()) clearOptimizable(nodep, "HasInitValue"); if (!VarFlags(nodep).m_stdFuncAsn) clearStdOptimizable(nodep, "NoStdAssign"); VarFlags flags(nodep); - if ((nodep->isMovableToBlock() // Blocktemp - || !flags.m_notStd) // Or used only in block + if ((nodep->varp()->varType() == AstVarType::BLOCKTEMP + || !flags.m_notStd) // Temporary Or used only in block && !flags.m_notOpt // Optimizable - && !nodep->isClassMember() && nodep->user1p()) { // Single cfunc - // We don't need to test for tracing; it would be in the tracefunc if it was needed - UINFO(4, " ModVar->BlkVar " << nodep << endl); + && !nodep->varp()->isClassMember() && // Statically exists in design hierarchy + nodep->user1p()) // Is under a CFunc + { + UINFO(4, "Localizing " << nodep << endl); ++m_statLocVars; - AstCFunc* newfuncp = VN_CAST(nodep->user1p(), CFunc); - nodep->unlinkFrBack(); - newfuncp->addInitsp(nodep); - // Done - flags.m_done = true; - flags.setNodeFlags(nodep); + AstCFunc* const funcp = VN_CAST(nodep->user1p(), CFunc); + // Yank the Var and VarScope from it's parent and schedule them for deletion + AstVar* const varp = nodep->varp(); + if (varp->backp()) { // Might have already unlinked this via another AstVarScope + pushDeletep(varp->unlinkFrBack()); + } + pushDeletep(nodep->unlinkFrBack()); + + // Create the new local variable. + const string newName + = nodep->scopep() == funcp->scopep() + ? varp->name() + : nodep->scopep()->nameDotless() + "__DOT__" + varp->name(); + + AstVar* const newVarp + = new AstVar(varp->fileline(), varp->varType(), newName, varp); + newVarp->funcLocal(true); + + // Fix up all the references + const auto er = m_references.equal_range(nodep); + for (auto it = er.first; it != er.second; ++it) { + AstVarRef* const refp = it->second; + refp->varScopep(nullptr); + refp->varp(newVarp); + } + + // Add the var to this function, and mark local + funcp->addInitsp(newVarp); } else { clearOptimizable(nodep, "NotDone"); } } - m_varps.clear(); + m_varScopeps.clear(); + m_references.clear(); } // VISITORS virtual void visit(AstNetlist* nodep) override { - iterateChildren(nodep); - moveVars(); + iterateChildrenConst(nodep); + moveVarScopes(); } virtual void visit(AstCFunc* nodep) override { UINFO(4, " CFUNC " << nodep << endl); @@ -157,7 +146,7 @@ private: searchFuncStmts(nodep->initsp()); searchFuncStmts(nodep->stmtsp()); searchFuncStmts(nodep->finalsp()); - iterateChildren(nodep); + iterateChildrenConst(nodep); } } void searchFuncStmts(AstNode* nodep) { @@ -166,60 +155,64 @@ private: // This could be more complicated; allow always-set under both branches of a IF. // If so, check for ArrayRef's and such, as they aren't acceptable. for (; nodep; nodep = nodep->nextp()) { - if (VN_IS(nodep, NodeAssign)) { - if (AstVarRef* varrefp = VN_CAST(VN_CAST(nodep, NodeAssign)->lhsp(), VarRef)) { + if (AstNodeAssign* const assignp = VN_CAST(nodep, NodeAssign)) { + if (AstVarRef* const varrefp = VN_CAST(assignp->lhsp(), VarRef)) { UASSERT_OBJ(varrefp->access().isWriteOrRW(), varrefp, - "LHS assignment not lvalue"); - if (!varrefp->varp()->user4p()) { + "LHS of assignment is not an lvalue"); + AstVarScope* const varScopep = varrefp->varScopep(); + if (!varScopep->user4p()) { UINFO(4, " FuncAsn " << varrefp << endl); - varrefp->varp()->user4p(varrefp); - VarFlags flags(varrefp->varp()); + varScopep->user4p(varrefp); + VarFlags flags(varScopep); flags.m_stdFuncAsn = true; - flags.setNodeFlags(varrefp->varp()); + flags.setNodeFlags(varScopep); } } } } } - virtual void visit(AstVar* nodep) override { - if (!nodep->isSigPublic() && !nodep->isPrimaryIO() - && !m_cfuncp) { // Not already inside a function + virtual void visit(AstVarScope* nodep) override { + if (!nodep->varp()->isPrimaryIO() // Not an IO the user wants to interact with + && !nodep->varp()->isSigPublic() // Not something the user wants to interact with + && !nodep->varp()->isFuncLocal() // Not already a function local (e.g.: argument) + && !nodep->varp()->isStatic() // Not a static variable + ) { UINFO(4, " BLKVAR " << nodep << endl); - m_varps.push_back(nodep); + m_varScopeps.push_back(nodep); } // No iterate; Don't want varrefs under it } virtual void visit(AstVarRef* nodep) override { - if (!VarFlags(nodep->varp()).m_notOpt) { + AstVarScope* const varScopep = nodep->varScopep(); + if (!VarFlags(varScopep).m_notOpt) { + // Remember the reference + m_references.emplace(varScopep, nodep); if (!m_cfuncp) { // Not in function, can't optimize // Perhaps impossible, but better safe - clearOptimizable(nodep->varp(), "BVnofunc"); // LCOV_EXCL_LINE + clearOptimizable(varScopep, "BVnofunc"); // LCOV_EXCL_LINE } else { - // If we're scoping down to it, it isn't really in the same block - if (!nodep->hierThis()) clearOptimizable(nodep->varp(), "HierRef"); // Allow a variable to appear in only a single function - AstNode* oldfunc = nodep->varp()->user1p(); + AstNode* const oldfunc = varScopep->user1p(); if (!oldfunc) { + // First encounter with this variable UINFO(4, " BVnewref " << nodep << endl); - nodep->varp()->user1p(m_cfuncp); // Remember where it was used - } else if (m_cfuncp == oldfunc) { - // Same usage - } else { + varScopep->user1p(m_cfuncp); // Remember where it was used + } else if (m_cfuncp != oldfunc) { // Used in multiple functions - clearOptimizable(nodep->varp(), "BVmultiF"); + clearOptimizable(varScopep, "BVmultiF"); } // First varref in function must be assignment found earlier - AstVarRef* firstasn = static_cast(nodep->varp()->user4p()); + const AstVarRef* const firstasn = VN_CAST(varScopep->user4p(), VarRef); if (firstasn && nodep != firstasn) { - clearStdOptimizable(nodep->varp(), "notFirstAsn"); - nodep->varp()->user4p(nullptr); + clearStdOptimizable(varScopep, "notFirstAsn"); + varScopep->user4p(nullptr); } } } // No iterate; Don't want varrefs under it } - virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + virtual void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } public: // CONSTRUCTORS @@ -234,10 +227,6 @@ public: void V3Localize::localizeAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); - { - LocalizeVisitor visitor(nodep); - // Fix up hiernames - LocalizeDehierVisitor dvisitor(nodep); - } // Destruct before checking + { LocalizeVisitor visitor(nodep); } // Destruct before checking V3Global::dumpCheckGlobalTree("localize", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 6); } diff --git a/src/Verilator.cpp b/src/Verilator.cpp index a73fe58bf..187c01586 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -403,9 +403,6 @@ static void process() { if (v3Global.opt.trace()) V3Trace::traceAll(v3Global.rootp()); if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "Scoped"); - - // Remove scopes; make varrefs/funccalls relative to current module - V3Descope::descopeAll(v3Global.rootp()); } //--MODULE OPTIMIZATIONS-------------- @@ -416,9 +413,15 @@ static void process() { V3DepthBlock::depthBlockAll(v3Global.rootp()); } - // Move BLOCKTEMPS from class to local variables + // Up until this point, all references must be scoped + v3Global.assertScoped(false); + + // Move variables from modules to function local variables where possible if (v3Global.opt.oLocalize()) V3Localize::localizeAll(v3Global.rootp()); + // Remove remaining scopes; make varrefs/funccalls relative to current module + V3Descope::descopeAll(v3Global.rootp()); + // Icache packing; combine common code in each module's functions into subroutines if (v3Global.opt.oCombine()) V3Combine::combineAll(v3Global.rootp()); }