diff --git a/Changes b/Changes index a975d2e6e..2495e471f 100644 --- a/Changes +++ b/Changes @@ -9,6 +9,8 @@ indicates the contributor was also the author of the fix; Thanks! *** Allow overriding Perl, Flex and Bison versions. [by Robert Farrell] +*** Optimize variables set to constants within basic blocks. + **** Default make no longer makes the docs; if you edit the documentation sources, run "make info" to get them. diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index ff3213dc5..792a55a6d 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -2776,6 +2776,7 @@ private: bool m_funcPublic:1; // From user public task/function bool m_isStatic:1; // Function is declared static (no this) bool m_symProlog:1; // Setup symbol table for later instructions + bool m_entryPoint:1; // User may call into this top level function public: AstCFunc(FileLine* fl, const string& name, AstScope* scopep, const string& rtnType="") : AstNode(fl) { @@ -2791,6 +2792,7 @@ public: m_funcPublic = false; m_isStatic = true; // Note defaults to static, later we see where thisp is needed m_symProlog = false; + m_entryPoint = false; } virtual ~AstCFunc() {} virtual AstType type() const { return AstType::CFUNC;} @@ -2826,6 +2828,8 @@ public: void isStatic(bool flag) { m_isStatic = flag; } bool symProlog() const { return m_symProlog; } void symProlog(bool flag) { m_symProlog = flag; } + bool entryPoint() const { return m_entryPoint; } + void entryPoint(bool flag) { m_entryPoint = flag; } // // If adding node accessors, see below AstNode* argsp() const { return op1p()->castNode(); } diff --git a/src/V3Clock.cpp b/src/V3Clock.cpp index 7636727f2..49a65b880 100644 --- a/src/V3Clock.cpp +++ b/src/V3Clock.cpp @@ -192,6 +192,7 @@ private: funcp->dontCombine(true); funcp->symProlog(true); funcp->isStatic(true); + funcp->entryPoint(true); m_scopep->addActivep(funcp); m_evalFuncp = funcp; } @@ -202,6 +203,7 @@ private: funcp->slow(true); funcp->symProlog(true); funcp->isStatic(true); + funcp->entryPoint(true); m_scopep->addActivep(funcp); m_initFuncp = funcp; } @@ -211,6 +213,7 @@ private: funcp->dontCombine(true); funcp->slow(true); funcp->isStatic(false); + funcp->entryPoint(true); funcp->addInitsp( new AstCStmt(nodep->fileline(), " "+EmitCBaseVisitor::symClassVar()+" = this->__VlSymsp;\n")); @@ -225,6 +228,7 @@ private: funcp->slow(true); funcp->isStatic(true); funcp->symProlog(true); + funcp->entryPoint(true); m_scopep->addActivep(funcp); m_settleFuncp = funcp; } diff --git a/src/V3Life.cpp b/src/V3Life.cpp index b14a6c220..d94df75fa 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -38,152 +38,242 @@ #include "V3Life.h" #include "V3Stats.h" #include "V3Ast.h" +#include "V3Const.h" //###################################################################### -// Life state, as a visitor of each AstNode +// Structure for global state -class LifeVarEntry { - AstNode* m_assignp; // Last assignment to this varscope - bool m_firstSet; // True if creation was a set (and thus block above may have a set that can be deleted +class LifeState { + // STATE public: - LifeVarEntry() { // Construction for when a var is used - clearAssign(); - m_firstSet = false; + V3Double0 m_statAssnDel; // Statistic tracking + V3Double0 m_statAssnCon; // Statistic tracking + +public: + LifeState() {} + ~LifeState() { + V3Stats::addStat("Optimizations, Lifetime assign deletions", m_statAssnDel); + V3Stats::addStat("Optimizations, Lifetime constant prop", m_statAssnCon); } - LifeVarEntry(AstNode* assp) { // Construction for when a var is set - m_assignp = assp; - m_firstSet = true; - } - LifeVarEntry(bool) { // Construction for when a var is set, but ass isn't - m_assignp = NULL; - m_firstSet = true; - } - AstNode* assignp() const { return m_assignp; } - bool firstSet() const { return m_firstSet; } - void clearAssign() { m_assignp = NULL; } - void newerAssign(AstNode* assp) { m_assignp = assp; } }; -class LifeVisitor : public AstNVisitor { -private: - // NODE STATE - // AstVarScope::user() -> int. Used in combining to detect duplicates +//###################################################################### +// Structure for each variable encountered - // STATE - //static int debug() { return 9; } - int m_ifCount; // If counter - V3Double0 m_statAssnDel; // Statistic tracking +class LifeVarEntry { + AstNodeAssign* m_assignp; // Last assignment to this varscope, NULL if no longer relevant + AstConst* m_constp; // Known constant value + bool m_setBeforeUse; // First access was a set (and thus block above may have a set that can be deleted + bool m_everSet; // Was ever assigned (and thus above block may not preserve constant propagation) + + inline void init (bool setBeforeUse) { + m_assignp = NULL; + m_constp = NULL; + m_setBeforeUse = setBeforeUse; + m_everSet = false; + } +public: + class SIMPLEASSIGN {}; + class COMPLEXASSIGN {}; + class CONSUMED {}; + + LifeVarEntry(SIMPLEASSIGN, AstNodeAssign* assp) { + init(true); simpleAssign(assp); + } + LifeVarEntry(COMPLEXASSIGN) { + init(false); complexAssign(); + } + LifeVarEntry(CONSUMED) { + init(false); consumed(); + } + ~LifeVarEntry() {} + inline void simpleAssign(AstNodeAssign* assp) { // New simple A=.... assignment + m_assignp = assp; + m_constp = NULL; + m_everSet = true; + if (assp->rhsp()->castConst()) m_constp = assp->rhsp()->castConst(); + } + inline void complexAssign() { // A[x]=... or some complicated assignment + m_assignp = NULL; + m_constp = NULL; + m_everSet = true; + } + inline void consumed() { // Rvalue read of A + m_assignp = NULL; + } + AstNodeAssign* assignp() const { return m_assignp; } + AstConst* constNodep() const { return m_constp; } + bool setBeforeUse() const { return m_setBeforeUse; } + bool everSet() const { return m_everSet; } +}; + +//###################################################################### +// Structure for all variables under a given meta-basic block + +class LifeBlock { + // NODE STATE + // Cleared each AstIf: + // AstVarScope::user() -> int. Used in combining to detect duplicates // LIFE MAP // For each basic block, we'll make a new map of what variables that if/else is changing typedef std::map LifeMap; - LifeMap *m_lifep; // Current active lifetime map for current scope + LifeMap m_map; // Current active lifetime map for current scope + LifeBlock* m_aboveLifep; // Upper life, or NULL + LifeState* m_statep; // Current global state +public: + LifeBlock(LifeBlock* aboveLifep, LifeState* statep) { + m_aboveLifep = aboveLifep; // Null if top + m_statep = statep; + } + ~LifeBlock() {} // METHODS - void checkRemoveAssign(LifeVarEntry* entp) { - // Check the var entry, and remove if appropriate - if (AstNode* oldassp = entp->assignp()) { - UINFO(7," PREV: "<4) oldassp->dumpTree(cout, " REMOVE/SAMEBLK "); - entp->clearAssign(); - oldassp->unlinkFrBack(); - pushDeletep(oldassp); - m_statAssnDel++; + void checkRemoveAssign(const LifeMap::iterator& it) { + AstVar* varp = it->first->varp(); + LifeVarEntry* entp = &(it->second); + if (!varp->isSigPublic()) { + // Rather then track what sigs AstUCFunc/AstUCStmt may change, + // we just don't optimize any public sigs + // Check the var entry, and remove if appropriate + if (AstNode* oldassp = entp->assignp()) { + UINFO(7," PREV: "<4) oldassp->dumpTree(cout, " REMOVE/SAMEBLK "); + entp->complexAssign(); + oldassp->unlinkFrBack()->deleteTree(); oldassp=NULL; + m_statep->m_statAssnDel++; + } } } - void setLast(AstVarScope* nodep, AstNode* assp) { + void simpleAssign(AstVarScope* nodep, AstNodeAssign* assp) { // Do we have a old assignment we can nuke? UINFO(4," ASSIGNof: "<find(nodep); - if (iter != m_lifep->end()) { - checkRemoveAssign(&(iter->second)); - iter->second.newerAssign(assp); + LifeMap::iterator it = m_map.find(nodep); + if (it != m_map.end()) { + checkRemoveAssign(it); + it->second.simpleAssign(assp); } else { - m_lifep->insert(make_pair(nodep,LifeVarEntry(assp))); + m_map.insert(make_pair(nodep,LifeVarEntry(LifeVarEntry::SIMPLEASSIGN(), assp))); } //lifeDump(); } - void clearLastAssign(AstVarScope* nodep) { + void complexAssign(AstVarScope* nodep) { UINFO(4," clearof: "<find(nodep); - if (iter != m_lifep->end()) { - iter->second.clearAssign(); + LifeMap::iterator it = m_map.find(nodep); + if (it != m_map.end()) { + it->second.complexAssign(); } else { - m_lifep->insert(make_pair(nodep,LifeVarEntry())); + m_map.insert(make_pair(nodep,LifeVarEntry(LifeVarEntry::COMPLEXASSIGN()))); } } - void lifeDump(LifeMap* lifep) { + void varUsageReplace (AstVarScope* nodep, AstVarRef* varrefp) { + // Variable rvalue. If it references a constant, we can simply replace it + LifeMap::iterator it = m_map.find(nodep); + if (it != m_map.end()) { + if (AstConst* constp = it->second.constNodep()) { + if (!varrefp->varp()->isSigPublic()) { + // Aha, variable is constant; substitute in. + // We'll later constant propagate + UINFO(4," replaceconst: "<replaceWith(constp->cloneTree(false)); + varrefp->deleteTree(); varrefp=NULL; + m_statep->m_statAssnCon++; + return; // **DONE, no longer a var reference** + } + } + UINFO(4," usage: "<second.consumed(); + } else { + m_map.insert(make_pair(nodep,LifeVarEntry(LifeVarEntry::CONSUMED()))); + } + } + void complexAssignFind(AstVarScope* nodep) { + LifeMap::iterator it = m_map.find(nodep); + if (it != m_map.end()) { + UINFO(4," casfind: "<first<second.complexAssign(); + } else { + m_map.insert(make_pair(nodep,LifeVarEntry(LifeVarEntry::COMPLEXASSIGN()))); + } + } + void consumedFind(AstVarScope* nodep) { + LifeMap::iterator it = m_map.find(nodep); + if (it != m_map.end()) { + it->second.consumed(); + } else { + m_map.insert(make_pair(nodep,LifeVarEntry(LifeVarEntry::CONSUMED()))); + } + } + void lifeToAbove() { + // Any varrefs under a if/else branch affect statements outside and after the if/else + if (!m_aboveLifep) v3fatalSrc("Pushing life when already at the top level"); + for (LifeMap::iterator it = m_map.begin(); it!=m_map.end(); ++it) { + AstVarScope* nodep = it->first; + m_aboveLifep->complexAssignFind(nodep); + if (it->second.everSet()) { + // Record there may be an assignment, so we don't constant propagate across the if. + complexAssignFind(nodep); + } else { + // Record consumption, so we don't eliminate earlier assignments + consumedFind(nodep); + } + } + } + void dualBranch (LifeBlock* life1p, LifeBlock* life2p) { + // Find any common sets on both branches of IF and propagate upwards + //life1p->lifeDump(); + //life2p->lifeDump(); + AstNode::userClearTree(); // userp() used on entire tree + for (LifeMap::iterator it = life1p->m_map.begin(); it!=life1p->m_map.end(); ++it) { + // When the if branch sets a var before it's used, mark that variable + if (it->second.setBeforeUse()) it->first->user(1); + } + for (LifeMap::iterator it = life2p->m_map.begin(); it!=life2p->m_map.end(); ++it) { + // When the else branch sets a var before it's used + AstVarScope* nodep = it->first; + if (it->second.setBeforeUse() && nodep->user()) { + // Both branches set the var, we can remove the assignment before the IF. + UINFO(4,"DUALBRANCH "<lifeDump(); + } + // DEBUG + void lifeDump() { UINFO(5, " LifeMap:"<begin(); it!=lifep->end(); ++it) { + for (LifeMap::iterator it = m_map.begin(); it!=m_map.end(); ++it) { UINFO(5, " Ent: " - <<(it->second.firstSet()?"[F] ":" ") + <<(it->second.setBeforeUse()?"[F] ":" ") <first<second.assignp()) { UINFO(5, " Ass: "<second.assignp()<begin(); it!=sublifep->end(); ++it) { - clearLastAssign(it->first); - } - } - void dualBranch (LifeMap* life1p, LifeMap* life2p) { - // Find any common sets on both branches of IF and propagate upwards - //lifeDump(life1p); - //lifeDump(life2p); - m_ifCount++; - for (LifeMap::iterator it = life1p->begin(); it!=life1p->end(); ++it) { - // When the if branch sets a var before it's used, mark that variable - if (it->second.firstSet()) it->first->user(m_ifCount); - } - for (LifeMap::iterator it = life2p->begin(); it!=life2p->end(); ++it) { - // When the else branch sets a var before it's used - AstVarScope* nodep = it->first; - if (it->second.firstSet() - && nodep->user()==m_ifCount) { // And the if hit the same var - UINFO(4,"DUALBRANCH "<find(nodep); - if (iter != m_lifep->end()) { - checkRemoveAssign(&(iter->second)); - iter->second.clearAssign(); - } else { - m_lifep->insert(make_pair(nodep,LifeVarEntry(true))); - } - } - } - //lifeDump(m_lifep); - } +}; +//###################################################################### +// Life state, as a visitor of each AstNode + +class LifeVisitor : public AstNVisitor { +private: + // STATE + LifeState* m_statep; // Current state + //static int debug() { return 9; } + + // LIFE MAP + // For each basic block, we'll make a new map of what variables that if/else is changing + typedef std::map LifeMap; + LifeBlock* m_lifep; // Current active lifetime map for current scope + + // METHODS // VISITORS - virtual void visit(AstModule* nodep, AstNUser*) { - // Only track the top scopes, not lower level functions - if (nodep->isTop()) nodep->iterateChildren(*this); - } - virtual void visit(AstTopScope* nodep, AstNUser*) { - AstNode::userClearTree(); // userp() used on entire tree - AstNode::user2ClearTree(); // userp() used on entire tree - AstNode::user3ClearTree(); // userp() used on entire tree - AstNode::user4ClearTree(); // userp() used on entire tree - - AstScope* scopep = nodep->scopep(); - if (!scopep) nodep->v3fatalSrc("TopScope has no scope\n"); - AstCFunc* evalp = NULL; - for (AstNode* searchp = scopep->blocksp(); searchp; searchp=searchp->nextp()) { - if (AstCFunc* funcp = searchp->castCFunc()) { - if (funcp->name() == "_eval") { - evalp = funcp; - break; - } - } - } - if (!evalp) nodep->v3fatalSrc("Top _eval entry function not found\n"); - evalp->iterateChildren(*this); - } - virtual void visit(AstVarRef* nodep, AstNUser*) { // Consumption/generation of a variable, // it's used so can't elim assignment before this use. @@ -191,44 +281,59 @@ private: // AstVarScope* vscp = nodep->varScopep(); if (!vscp) nodep->v3fatalSrc("Scope not assigned"); - clearLastAssign(vscp); + if (nodep->lvalue()) { + m_lifep->complexAssign(vscp); + } else { + m_lifep->varUsageReplace(vscp, nodep); nodep=NULL; + } } virtual void visit(AstNodeAssign* nodep, AstNUser*) { // Collect any used variables first, as lhs may also be on rhs + vluint64_t lastEdit = AstNode::editCountGbl(); // When it was last edited nodep->rhsp()->iterateAndNext(*this); + if (lastEdit != AstNode::editCountGbl()) { + // We changed something, try to constant propagate, but don't delete the + // assignment as we still need nodep to remain. + V3Const::constifyTree(nodep->rhsp()); + } // Has to be direct assignment without any EXTRACTing. if (nodep->lhsp()->castVarRef()) { AstVarScope* vscp = nodep->lhsp()->castVarRef()->varScopep(); if (!vscp) vscp->v3fatalSrc("Scope lost on variable"); - setLast(vscp, nodep); + m_lifep->simpleAssign(vscp, nodep); } else { nodep->lhsp()->iterateAndNext(*this); } } + virtual void visit(AstAssignDly* nodep, AstNUser*) { + // Don't treat as normal assign; V3Life doesn't understand time sense + nodep->iterateChildren(*this); + } //---- Track control flow changes virtual void visit(AstNodeIf* nodep, AstNUser*) { UINFO(4," IF "<condp()->iterateAndNext(*this); - LifeMap* prevLifeMapp = m_lifep; - LifeMap* ifLifep = new LifeMap; - LifeMap* elseLifep = new LifeMap; + LifeBlock* prevLifep = m_lifep; + LifeBlock* ifLifep = new LifeBlock (prevLifep, m_statep); + LifeBlock* elseLifep = new LifeBlock (prevLifep, m_statep); { m_lifep = ifLifep; nodep->ifsp()->iterateAndNext(*this); + m_lifep = prevLifep; } { m_lifep = elseLifep; nodep->elsesp()->iterateAndNext(*this); + m_lifep = prevLifep; } UINFO(4," join "<dualBranch (ifLifep, elseLifep); // For the next assignments, clear any variables that were read or written in the block - clearLifeFromSublife(ifLifep); - clearLifeFromSublife(elseLifep); + ifLifep->lifeToAbove(); + elseLifep->lifeToAbove(); delete ifLifep; delete elseLifep; } @@ -240,24 +345,25 @@ private: // assignment in the body that's used in the cond. (And otherwise // would because it only appears used after-the-fact. So, we model // it as a IF statement, and just don't allow elimination of - // variables in the body. - LifeMap* prevLifeMapp = m_lifep; - LifeMap* condLifep = new LifeMap; - LifeMap* bodyLifep = new LifeMap; + // variables across the body. + LifeBlock* prevLifep = m_lifep; + LifeBlock* condLifep = new LifeBlock (prevLifep, m_statep); + LifeBlock* bodyLifep = new LifeBlock (prevLifep, m_statep); { m_lifep = condLifep; nodep->precondsp()->iterateAndNext(*this); nodep->condp()->iterateAndNext(*this); + m_lifep = prevLifep; } { m_lifep = bodyLifep; nodep->bodysp()->iterateAndNext(*this); + m_lifep = prevLifep; } UINFO(4," joinfor"<lifeToAbove(); + bodyLifep->lifeToAbove(); delete condLifep; delete bodyLifep; } @@ -267,6 +373,7 @@ private: // Enter the function and trace it nodep->funcp()->accept(*this); } + virtual void visit(AstVar*, AstNUser*) {} // Don't want varrefs under it virtual void visit(AstNode* nodep, AstNUser*) { nodep->iterateChildren(*this); @@ -274,15 +381,47 @@ private: public: // CONSTRUCTORS - LifeVisitor(AstNetlist* nodep) { - m_ifCount = 1; - m_lifep = new LifeMap; + LifeVisitor(AstCFunc* nodep, LifeState* statep) { + UINFO(4," LifeVisitor on "<accept(*this); + delete m_lifep; m_lifep=NULL; + } + } + virtual ~LifeVisitor() {} +}; + +//###################################################################### + +class LifeTopVisitor : public AstNVisitor { + // Visit all top nodes searching for functions that are entry points we want to start + // finding code within. +private: + // STATE + LifeState* m_statep; // Current state + + // VISITORS + virtual void visit(AstCFunc* nodep, AstNUser*) { + if (nodep->entryPoint()) { + // Usage model 1: Simulate all C code, doing lifetime analysis + LifeVisitor visitor (nodep, m_statep); + } + } + virtual void visit(AstVar*, AstNUser*) {} // Accelerate + virtual void visit(AstNodeStmt*, AstNUser*) {} // Accelerate + virtual void visit(AstNodeMath*, AstNUser*) {} // Accelerate + virtual void visit(AstNode* nodep, AstNUser*) { + nodep->iterateChildren(*this); + } +public: + // CONSTRUCTORS + LifeTopVisitor(AstNetlist* nodep, LifeState* statep) { + m_statep = statep; nodep->accept(*this); - delete m_lifep; - } - virtual ~LifeVisitor() { - V3Stats::addStat("Optimizations, Lifetime assign deletions", m_statAssnDel); } + virtual ~LifeTopVisitor() {} }; //###################################################################### @@ -290,6 +429,6 @@ public: void V3Life::lifeAll(AstNetlist* nodep) { UINFO(2,__FUNCTION__<<": "<addArgsp(rtnvarp); funcp->dontCombine(true); funcp->funcPublic(true); + funcp->entryPoint(true); funcp->isStatic(false); // We need to get a pointer to all of our variables (may have eval'ed something else earlier) diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 4b4a93f32..e132efd92 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -327,6 +327,7 @@ void process () { v3Global.rootp()->dumpTreeFile(v3Global.debugFilename("clock.tree")); // Cleanup any dly vars or other temps that are simple assignments + // Life must be done before Subst, as it assumes each CFunc under _eval is called only once. if (v3Global.opt.oLife()) { V3Life::lifeAll(v3Global.rootp()); } diff --git a/test_regress/t/t_func.v b/test_regress/t/t_func.v index 60e4e01be..26c707e52 100644 --- a/test_regress/t/t_func.v +++ b/test_regress/t/t_func.v @@ -23,10 +23,9 @@ module t; nop(32'h11); global = 32'h00000001; - flipbit(global,5'd8); - flipbit(global,5'd16); - flipbit(global,5'd24); - if (global !== 32'h01010101) $stop; + flipupperbit(global,4'd4); + flipupperbit(global,4'd12); + if (global !== 32'h10100001) $stop; $write("*-* All Finished *-*\n"); $finish; @@ -76,10 +75,14 @@ module t; end endtask - task flipbit; + task flipupperbit; inout [31:0] vector; - input [4:0] bitnum; - vector[bitnum] = vector[bitnum] ^ 1'b1; + input [3:0] bitnum; + reg [4:0] bitnum2; + begin + bitnum2 = {1'b1, bitnum}; // A little math to test constant propagation + vector[bitnum2] = vector[bitnum2] ^ 1'b1; + end endtask endmodule diff --git a/test_regress/t/t_var_life.pl b/test_regress/t/t_var_life.pl index 7d5b675b5..b16833df9 100755 --- a/test_regress/t/t_var_life.pl +++ b/test_regress/t/t_var_life.pl @@ -12,7 +12,8 @@ compile ( ); if ($Last_Self->{v3}) { - file_grep ($Last_Self->{stats}, qr/Optimizations, Lifetime assign deletions\s+3/i); + file_grep ($Last_Self->{stats}, qr/Optimizations, Lifetime assign deletions\s+4/i); + file_grep ($Last_Self->{stats}, qr/Optimizations, Lifetime constant prop\s+2/i); } execute (