// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Combine common code into functions // // Code available from: http://www.veripool.org/verilator // //************************************************************************* // // Copyright 2003-2017 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. // //************************************************************************* // V3Combine's Transformations: // // For every function that we spit out // Examine code to find largest common blocks // Hash each node depth first // Hash includes varp name and operator type, and constants // Form lookup table based on hash of each statement w/ nodep and next nodep // GO through table // Lookup in hash, while next of each statement match, grow that common block // Foreach common block // If common block large enough (> 20 statements) & used 2x or more // Make new function // Move common block to function // Replace each common block ref with funccall // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include #include #include #include #include #include #include "V3Global.h" #include "V3Combine.h" #include "V3Hashed.h" #include "V3Stats.h" #include "V3Ast.h" //###################################################################### #define COMBINE_MIN_STATEMENTS 50 // Min # of statements to be worth making a function //###################################################################### class CombBaseVisitor : public AstNVisitor { protected: // STATE // METHODS static int debug() { static int level = -1; if (VL_UNLIKELY(level < 0)) level = v3Global.opt.debugSrcLevel(__FILE__); return level; } virtual ~CombBaseVisitor() {} //***** optimization levels static bool emptyFunctionDeletion() { return true; } static bool duplicateFunctionCombine() { return true; } // Note this is disabled, it still needed work // Also repair it for DPI functions; when make __common need to insure proper // flags get inherited from the old to new AstCFunc, and that AstText doesn't // get split between functions causing the text to have a danginling reference. bool statementCombine() { return false && duplicateFunctionCombine(); } }; //###################################################################### // Combine replacement function class CombCallVisitor : CombBaseVisitor { // Find all CCALLS of each CFUNC, so that we can later rename them private: // NODE STATE bool m_find; // Find mode vs. delete mode typedef multimap CallMmap; CallMmap m_callMmap; // Associative array of {function}{call} // METHODS public: void replaceFunc (AstCFunc* oldfuncp, AstCFunc* newfuncp) { if (oldfuncp==newfuncp) return; if (newfuncp) { UINFO(4, " Replace "< "< eqrange = m_callMmap.equal_range(oldfuncp); for (CallMmap::iterator nextit = eqrange.first; nextit != eqrange.second;) { CallMmap::iterator eqit = nextit++; AstCCall* callp = eqit->second; if (!callp->user3()) { // !already done UINFO(4, " Called "<funcp() != oldfuncp) callp->v3fatalSrc("Call list broken, points to call w/different func"); if (newfuncp) { AstCCall* newp = new AstCCall(callp, newfuncp); // Special new AstCCall form above transfers children of callp to newfuncp callp->replaceWith(newp); addCall(newp); // Fix the table } else { // Just deleting empty function callp->unlinkFrBack(); } callp->user3(true); // Dead now pushDeletep(callp); VL_DANGLING(callp); m_callMmap.erase(eqit); // Fix the table } } } // METHODS void addCall(AstCCall* nodep) { m_callMmap.insert(make_pair(nodep->funcp(), nodep)); } void deleteCall(AstCCall* nodep) { pair eqrange = m_callMmap.equal_range(nodep->funcp()); for (CallMmap::iterator nextit = eqrange.first; nextit != eqrange.second;) { CallMmap::iterator eqit = nextit++; AstCCall* callp = eqit->second; if (callp==nodep) { m_callMmap.erase(eqit); return; } } nodep->v3fatalSrc("deleteCall node not found in table"); } private: // VISITORS virtual void visit(AstCCall* nodep) { addCall(nodep); } // Speed things up virtual void visit(AstNodeAssign* nodep) {} virtual void visit(AstNodeMath* nodep) {} virtual void visit(AstNode* nodep) { nodep->iterateChildren(*this); } public: // CONSTRUCTORS CombCallVisitor() { m_find = false; } virtual ~CombCallVisitor() {} void main(AstNetlist* nodep) { nodep->accept(*this); } }; //###################################################################### // Combine marking function class CombMarkVisitor : CombBaseVisitor { // Mark all nodes under specified one. private: // OUTPUT: // AstNode::user3() -> bool. True to indicate duplicated // VISITORS virtual void visit(AstNode* nodep) { nodep->user3(true); nodep->iterateChildren(*this); } public: // CONSTRUCTORS explicit CombMarkVisitor(AstNode* nodep) { nodep->accept(*this); } virtual ~CombMarkVisitor() {} }; //###################################################################### // Combine state, as a visitor of each AstNode class CombineVisitor : CombBaseVisitor { private: // NODE STATE // Entire netlist: // AstNodeStmt::user() -> bool. True if iterated already // AstCFunc::user3p() -> AstCFunc*, If set, replace ccalls to this func with new func // AstNodeStmt::user3() -> AstNode*. True if to ignore this cell // AstNodeStmt::user4() -> V3Hashed::V3Hash. Hash value of this node (hash of 0 is illegal) AstUser1InUse m_inuser1; AstUser3InUse m_inuser3; //AstUser4InUse part of V3Hashed // STATE typedef enum {STATE_IDLE, STATE_HASH, STATE_DUP} CombineState; V3Double0 m_statCombs; // Statistic tracking CombineState m_state; // Major state AstNodeModule* m_modp; // Current module AstCFunc* m_funcp; // Current function V3Hash m_lowerHash; // Hash of the statement we're building CombCallVisitor m_call; // Tracking of function call users int m_modNFuncs; // Number of functions made AstNode* m_walkLast1p; // Final node that is the same in duplicate list AstNode* m_walkLast2p; // Final node that is the same in duplicate list V3Hashed m_hashed; // Hash for every node in module // METHODS void hashStatement(AstNode* nodep) { // Compute hash on entire tree of this statement m_hashed.hashAndInsert(nodep); //UINFO(9," stmthash "<user4()<<" "<accept(*this); } m_state = oldState; } void walkEmptyFuncs() { for (V3Hashed::iterator it = m_hashed.begin(); it != m_hashed.end(); ++it) { AstNode* node1p = it->second; AstCFunc* oldfuncp = node1p->castCFunc(); if (oldfuncp && oldfuncp->emptyBody() && !oldfuncp->dontCombine()) { UINFO(5," EmptyFunc "<user4p())<<" "<unlinkFrBack(); pushDeletep(oldfuncp); VL_DANGLING(oldfuncp); } } } void walkDupFuncs() { for (V3Hashed::iterator it = m_hashed.begin(); it != m_hashed.end(); ++it) { V3Hash hashval = it->first; AstNode* node1p = it->second; if (!node1p->castCFunc()) continue; if (hashval.isIllegal()) node1p->v3fatalSrc("Illegal (unhashed) nodes"); for (V3Hashed::iterator eqit = it; eqit != m_hashed.end(); ++eqit) { AstNode* node2p = eqit->second; if (!(eqit->first == hashval)) break; if (node1p==node2p) continue; // Identical iterator if (node1p->user3p() || node2p->user3p()) continue; // Already merged if (node1p->sameTree(node2p)) { // walk of tree has same comparison // Replace AstCCall's that point here replaceFuncWFunc(node2p->castCFunc(), node1p->castCFunc()); // Replacement may promote a slow routine to fast path if (!node2p->castCFunc()->slow()) node1p->castCFunc()->slow(false); } } } } void replaceFuncWFunc(AstCFunc* oldfuncp, AstCFunc* newfuncp) { UINFO(5," DupFunc "<user4p())<<" "<user4p())<<" "<unlinkFrBack(); pushDeletep(oldfuncp); VL_DANGLING(oldfuncp); } void replaceOnlyCallFunc(AstCCall* nodep) { if (AstCFunc* oldfuncp = nodep->backp()->castCFunc()) { //oldfuncp->dumpTree(cout,"MAYDEL: "); if (nodep->nextp()==NULL && oldfuncp->initsp()==NULL && oldfuncp->stmtsp()==nodep && oldfuncp->finalsp()==NULL) { UINFO(9," Function only has call "<funcp()); VL_DANGLING(nodep); } } } void walkDupCodeStart(AstNode* node1p) { V3Hash hashval (node1p->user4p()); //UINFO(4," STMT "< eqrange = m_hashed.mmap().equal_range(hashval); for (V3Hashed::iterator eqit = eqrange.first; eqit != eqrange.second; ++eqit) { AstNode* node2p = eqit->second; if (node1p==node2p) continue; // // We need to mark iteration to prevent matching code inside code (abab matching in ababab) AstNode::user1ClearTree(); // user1p() used on entire tree m_walkLast1p = NULL; m_walkLast2p = NULL; int depth = walkDupCodeNext(node1p, node2p, 1); if (depth>COMBINE_MIN_STATEMENTS && depth>bestDepth) { bestDepth = depth; bestNode2p = node2p; bestLast1p = m_walkLast1p; bestLast2p = m_walkLast2p; } } if (bestDepth) { // Found a replacement UINFO(5," Duplicate of depth "<user1p() || node2p->user1p()) return 0; // Already iterated if (node1p->user3p() || node2p->user3p()) return 0; // Already merged if (!m_hashed.sameNodes(node1p,node2p)) return 0; // walk of tree has same comparison V3Hash hashval(node1p->user4p()); //UINFO(9," wdup1 "<user4p())<<" "<user4p())<<" "<user1(true); node2p->user1(true); if (node1p->nextp() && node2p->nextp()) { return hashval.depth()+walkDupCodeNext(node1p->nextp(), node2p->nextp(), level+1); } return hashval.depth(); } void walkReplace(AstNode* node1p, AstNode* node2p, AstNode* last1p, AstNode* last2p) { // Final node in linked list, maybe null if all statements to be grabbed // Make new function string oldname = m_funcp->name(); string::size_type pos; if ((pos=oldname.find("_common")) != string::npos) { oldname.erase(pos); } if ((pos=oldname.find("__")) != string::npos) { oldname.erase(pos); } AstCFunc* newfuncp = new AstCFunc(node1p->fileline(), oldname+"_common"+cvtToStr(++m_modNFuncs), NULL); m_modp->addStmtp(newfuncp); // Create calls AstCCall* call1p = new AstCCall(node1p->fileline(), newfuncp); AstCCall* call2p = new AstCCall(node2p->fileline(), newfuncp); // Grab statement bodies AstNRelinker relink1Handle; AstNRelinker relink2Handle; for (AstNode* nextp, *walkp = node1p; 1; walkp = nextp) { nextp = walkp->nextp(); if (walkp==node1p) walkp->unlinkFrBack(&relink1Handle); else { walkp->unlinkFrBack(); node1p->addNext(walkp); } if (walkp==last1p) break; } for (AstNode* nextp, *walkp = node2p; 1; walkp = nextp) { nextp = walkp->nextp(); if (walkp==node2p) walkp->unlinkFrBack(&relink2Handle); else { walkp->unlinkFrBack(); node2p->addNext(walkp); } if (walkp==last2p) break; } // Move node1 statements to new function newfuncp->addStmtsp(node1p); //newfuncp->dumpTree(cout," newfunctree: "); // Mark node2 statements as dead CombMarkVisitor visitor(node2p); pushDeletep(node2p); // Delete later // Link in new function relink1Handle.relink(call1p); relink2Handle.relink(call2p); // Hash the new function hashFunctions(newfuncp); m_call.addCall(call1p); m_call.addCall(call2p); // If either new statement makes a func with only a single call, replace // the above callers to call it directly replaceOnlyCallFunc(call1p); VL_DANGLING(call1p); replaceOnlyCallFunc(call2p); VL_DANGLING(call2p); } // VISITORS virtual void visit(AstNetlist* nodep) { // Track all callers of each function m_call.main(nodep); // //In V3Hashed AstNode::user4ClearTree(); // user4p() used on entire tree // Iterate modules backwards, in bottom-up order. // Required so that a module instantiating another can benefit from collapsing. nodep->iterateChildrenBackwards(*this); } virtual void visit(AstNodeModule* nodep) { UINFO(4," MOD "<iterateChildren(*this); m_state = STATE_IDLE; if (debug()>=9) { m_hashed.dumpFilePrefixed("combine"); } // Walk the hashes removing empty functions if (emptyFunctionDeletion()) { walkEmptyFuncs(); } // Walk the hashes looking for duplicate functions if (duplicateFunctionCombine()) { walkDupFuncs(); } // Walk the statements looking for large replicated code sections if (statementCombine()) { m_state = STATE_DUP; nodep->iterateChildren(*this); m_state = STATE_IDLE; } m_modp = NULL; } virtual void visit(AstCFunc* nodep) { m_funcp = nodep; if (!nodep->dontCombine()) { if (m_state == STATE_HASH) { hashStatement(nodep); // Hash the entire function - it might be identical } else if (m_state == STATE_DUP) { nodep->iterateChildren(*this); } } m_funcp = NULL; } virtual void visit(AstNodeStmt* nodep) { if (m_state == STATE_HASH && m_funcp) { hashStatement(nodep); } else if (m_state == STATE_DUP && m_funcp) { walkDupCodeStart(nodep); } } //-------------------- // Default: Just iterate virtual void visit(AstVar*) {} virtual void visit(AstTraceDecl*) {} virtual void visit(AstTraceInc*) {} virtual void visit(AstNode* nodep) { nodep->iterateChildren(*this); } public: // CONSTUCTORS explicit CombineVisitor(AstNetlist* nodep) { m_modp=NULL; m_funcp = NULL; m_state = STATE_IDLE; nodep->accept(*this); } virtual ~CombineVisitor() { V3Stats::addStat("Optimizations, Combined CFuncs", m_statCombs); } }; //###################################################################### // Combine class functions void V3Combine::combineAll(AstNetlist* nodep) { UINFO(2,__FUNCTION__<<": "<= 3); }