// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Substitute constants and expressions in expr temp's // // Code available from: https://verilator.org // //************************************************************************* // // 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-FileCopyrightText: 2004-2026 Wilson Snyder // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // V3Subst's Transformations: // // Each module: // Search all ASSIGN(WORDSEL(...)) and build what it's assigned to // Later usages of that word may then be replaced as long as // the RHS hasn't changed value. // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Subst.h" #include "V3Const.h" #include "V3Stats.h" #include VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### // Data strored for every variable that tracks assignments and usage class SubstVarEntry final { friend class SubstValidVisitor; // SubstVarEntry contains a Record for each word, and one extra for the whole variable struct Record final { // MEMBERS AstNodeAssign* m_assignp; // Last assignment to this part uint32_t m_step; // Step number of last assignment. 0 means invalid entry. bool m_used; // True if consumed. Can be set even if entry is invalid. // CONSTRUCTOR Record() { invalidate(); } // METHODS void invalidate() { m_assignp = nullptr; m_step = 0; m_used = false; } }; // MEMBERS // Variable this SubstVarEntry tracks AstVar* const m_varp; // The recrod for whole variable tracking Record m_wholeRecord{}; // A record for each word in the variable std::vector m_wordRecords{static_cast(m_varp->widthWords()), Record{}}; // METHDOS void deleteAssignmentIfUnused(Record& record, size_t& nAssignDeleted) { if (!record.m_assignp) return; if (record.m_used) return; ++nAssignDeleted; VL_DO_DANGLING(record.m_assignp->unlinkFrBack()->deleteTree(), record.m_assignp); } AstNodeExpr* substRecord(Record& record); public: // CONSTRUCTORS explicit SubstVarEntry(AstVar* varp) : m_varp{varp} {} ~SubstVarEntry() = default; // Record assignment of whole variable. The given 'assp' can be null, which means // the variable is known to be assigned, but to an unknown value. void assignWhole(AstNodeAssign* assp, uint32_t step) { // Invalidate all word records for (Record& wordRecord : m_wordRecords) wordRecord.invalidate(); // Set whole record m_wholeRecord.m_assignp = assp; m_wholeRecord.m_step = step; m_wholeRecord.m_used = false; } // Like assignWhole word, but records assignment to a specific word of the variable. void assignWord(AstNodeAssign* assp, uint32_t step, uint32_t word) { // Invalidate whole record m_wholeRecord.invalidate(); // Set word record Record& wordRecord = m_wordRecords[word]; wordRecord.m_assignp = assp; wordRecord.m_step = step; wordRecord.m_used = false; } // Mark the whole variable as used (value consumed) void usedWhole() { m_wholeRecord.m_used = true; for (Record& wordRecord : m_wordRecords) wordRecord.m_used = true; } // Mark the specific word as used (value consumed) void usedWord(uint32_t word) { m_wholeRecord.m_used = true; m_wordRecords[word].m_used = true; } // Returns substitution of whole word, or nullptr if not known/stale AstNodeExpr* substWhole() { return substRecord(m_wholeRecord); } // Returns substitution of whole word, or nullptr if not known/stale AstNodeExpr* substWord(uint32_t word) { return substRecord(m_wordRecords[word]); } void deleteUnusedAssignments(size_t& nWordAssignDeleted, size_t& nWholeAssignDeleted) { // Delete assignments to temporaries if they are not used if (!m_varp->isStatementTemp()) return; for (Record& wordRecord : m_wordRecords) deleteAssignmentIfUnused(wordRecord, nWordAssignDeleted); deleteAssignmentIfUnused(m_wholeRecord, nWholeAssignDeleted); } }; //###################################################################### // See if any variables in the expression we are considering to // substitute have changed value since we recorded the assignment. class SubstValidVisitor final : public VNVisitorConst { // NODE STATE // See SubstVisitor // STATE const uint32_t m_step; // Step number where assignment was recorded bool m_valid = true; // Is the expression we are considering to substitute valid // METHODS SubstVarEntry& getEntry(AstVarRef* nodep) { // This vistor is always invoked on the RHS of an assignment we are considering to // substitute. Variable references must have all been recorded when visiting the // assignments RHS, so the SubstVarEntry must exist for each referenced variable. return *nodep->varp()->user1u().to(); } // VISITORS void visit(AstWordSel* nodep) override { if (!m_valid) return; if (AstVarRef* const refp = VN_CAST(nodep->fromp(), VarRef)) { if (AstConst* const idxp = VN_CAST(nodep->bitp(), Const)) { SubstVarEntry& entry = getEntry(refp); // If either the whole variable, or the indexed word was written to // after the original assignment was recorded, the value is invalid. if (m_step < entry.m_wholeRecord.m_step) m_valid = false; if (m_step < entry.m_wordRecords[idxp->toUInt()].m_step) m_valid = false; return; } } iterateChildrenConst(nodep); } void visit(AstVarRef* nodep) override { if (!m_valid) return; // If either the whole variable, or any of the words were written to // after the original assignment was recorded, the value is invalid. SubstVarEntry& entry = getEntry(nodep); if (m_step < entry.m_wholeRecord.m_step) { m_valid = false; return; } for (SubstVarEntry::Record& wordRecord : entry.m_wordRecords) { if (m_step < wordRecord.m_step) { m_valid = false; return; } } } void visit(AstConst*) override {} // Accelerate void visit(AstNodeExpr* nodep) override { if (!m_valid) return; iterateChildrenConst(nodep); } void visit(AstNode* nodep) override { nodep->v3fatalSrc("Non AstNodeExpr under AstNodeExpr"); } // CONSTRUCTORS SubstValidVisitor(SubstVarEntry::Record& record) : m_step{record.m_step} { iterateConst(record.m_assignp->rhsp()); } ~SubstValidVisitor() override = default; public: static bool valid(SubstVarEntry::Record& record) { if (!record.m_assignp) return false; return SubstValidVisitor{record}.m_valid; } }; AstNodeExpr* SubstVarEntry::substRecord(SubstVarEntry::Record& record) { if (!SubstValidVisitor::valid(record)) return nullptr; AstNodeExpr* const rhsp = record.m_assignp->rhsp(); UDEBUGONLY(UASSERT_OBJ(rhsp->isPure(), record.m_assignp, "Substituting impure expression");); return rhsp; } //###################################################################### // Substitution visitor class SubstVisitor final : public VNVisitor { // NODE STATE - only Under AstCFunc // AstVar::user1p -> SubstVarEntry* for assignment tracking. Also used by SubstValidVisitor // AstVar::user2 -> bool. Is a constant pool variable const VNUser2InUse m_user2InUse; // STATE std::deque m_entries; // Storage for SubstVarEntry instances uint32_t m_ops = 0; // Number of nodes on the RHS of an assignment uint32_t m_assignStep = 0; // Assignment number to determine variable lifetimes const AstCFunc* m_funcp = nullptr; // Current function we are under size_t m_nSubst = 0; // Number of substitutions performed - for avoiding constant folding // Statistics size_t m_nWordSubstituted = 0; // Number of words substituted size_t m_nWholeSubstituted = 0; // Number of whole variables substituted size_t m_nWordAssignDeleted = 0; // Number of word assignments deleted size_t m_nWholeAssignDeleted = 0; // Number of whole variable assignments deleted size_t m_nConstWordsReinlined = 0; // Number of constant words substituted static constexpr uint32_t SUBST_MAX_OPS_SUBST = 30; // Maximum number of ops to substitute in static constexpr uint32_t SUBST_MAX_OPS_NA = 9999; // Not allowed to substitute // METHODS SubstVarEntry& getEntry(AstVarRef* nodep) { AstVar* const varp = nodep->varp(); if (!varp->user1p()) { m_entries.emplace_back(varp); varp->user1p(&m_entries.back()); } return *varp->user1u().to(); } void simplify(AstNodeExpr* exprp) { // Often constant, so short circuit if (VN_IS(exprp, Const)) return; // Iterate expression, then constant fold it if anything changed const size_t nSubstBefore = m_nSubst; exprp = VN_AS(iterateSubtreeReturnEdits(exprp), NodeExpr); if (nSubstBefore != m_nSubst) V3Const::constifyEditCpp(exprp); } // The way the analysis algorithm works based on statement numbering only works // for variables that are written in the same basic block (ignoring AstCond) as // where they are consumed. The temporaries introduced in V3Premit are such, so // only substitute those for now. bool isSubstitutable(AstVar* nodep) { return nodep->isStatementTemp() && !nodep->noSubst(); } void substitute(AstNode* nodep, AstNodeExpr* substp) { AstNodeExpr* newp = substp->backp() ? substp->cloneTreePure(true) : substp; if (!nodep->isQuad() && newp->isQuad()) { newp = new AstCCast{newp->fileline(), newp, nodep}; } nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); ++m_nSubst; } // VISITORS void visit(AstNetlist* nodep) override { // Mark constant pool variables for (AstNode* np = nodep->constPoolp()->modp()->stmtsp(); np; np = np->nextp()) { if (VN_IS(np, Var)) np->user2(true); } iterateAndNextNull(nodep->modulesp()); } void visit(AstCFunc* nodep) override { UASSERT_OBJ(!m_funcp, nodep, "Should not nest"); UASSERT_OBJ(m_entries.empty(), nodep, "Should not visit outside functions"); // Process the function body { VL_RESTORER(m_funcp); m_funcp = nodep; const VNUser1InUse m_inuser1; iterateChildren(nodep); // Deletes unused assignments and clear entries for (SubstVarEntry& entry : m_entries) { entry.deleteUnusedAssignments(m_nWordAssignDeleted, m_nWholeAssignDeleted); } m_entries.clear(); } // Constant fold here, as Ast size can likely be reduced if (v3Global.opt.fConstEager()) V3Const::constifyEditCpp(nodep); } void visit(AstNodeAssign* nodep) override { if (!m_funcp) return; const uint32_t ops = [&]() { VL_RESTORER(m_ops); m_ops = 0; // Simplify the RHS simplify(nodep->rhsp()); // If the became constant, we can continue substituting it return VN_IS(nodep->rhsp(), Const) ? 0 : m_ops; }(); // Assignment that defines the value, which is either this 'nodep', // or nullptr, if the value should not be substituted. const auto getAssignp = [&](AstVarRef* refp) -> AstNodeAssign* { // If too complex, don't substitute if (ops > SUBST_MAX_OPS_SUBST) return nullptr; // AstCvtPackedToArray can't be anywhere else than on the RHS of assignment if (VN_IS(nodep->rhsp(), CvtPackedToArray)) return nullptr; // If non const but want const subtitutions only if (refp->varp()->substConstOnly() && !VN_IS(nodep->rhsp(), Const)) return nullptr; // Otherwise can substitute based on the assignment return nodep; }; // If LHS is a whole variable reference, track the whole variable if (AstVarRef* const refp = VN_CAST(nodep->lhsp(), VarRef)) { getEntry(refp).assignWhole(getAssignp(refp), ++m_assignStep); return; } // If LHS is a known word reference, track the word if (const AstWordSel* const selp = VN_CAST(nodep->lhsp(), WordSel)) { if (AstVarRef* const refp = VN_CAST(selp->fromp(), VarRef)) { // Simplify the index simplify(selp->bitp()); if (const AstConst* const idxp = VN_CAST(selp->bitp(), Const)) { getEntry(refp).assignWord(getAssignp(refp), ++m_assignStep, idxp->toUInt()); return; } } } // Not tracked, iterate LHS to simplify/reset tracking iterate(nodep->lhsp()); } void visit(AstWordSel* nodep) override { if (!m_funcp) return; // Simplify the index simplify(nodep->bitp()); // If this is a known word reference, track/substitute it if (AstVarRef* const refp = VN_CAST(nodep->fromp(), VarRef)) { if (const AstConst* const idxp = VN_CAST(nodep->bitp(), Const)) { SubstVarEntry& entry = getEntry(refp); const uint32_t word = idxp->toUInt(); // If it's a write, reset tracking as we don't know the assigned value, // otherwise we would have picked it up in visit(AstNodeAssign*) if (refp->access().isWriteOrRW()) { entry.assignWord(nullptr, ++m_assignStep, word); return; } // Otherwise it's a read, UASSERT_OBJ(refp->access().isReadOnly(), nodep, "Invalid access"); // If it's a constant pool variable, substiute with the constant word AstVar* const varp = refp->varp(); if (varp->user2()) { AstConst* const constp = VN_AS(varp->valuep(), Const); const uint32_t value = constp->num().edataWord(word); FileLine* const flp = nodep->fileline(); ++m_nConstWordsReinlined; substitute(nodep, new AstConst{flp, AstConst::SizedEData{}, value}); return; } // Substitute other variables if possible if (isSubstitutable(refp->varp())) { if (AstNodeExpr* const substp = entry.substWord(word)) { ++m_nWordSubstituted; substitute(nodep, substp); return; } } // If not substituted, mark the assignment setting this word as used entry.usedWord(word); return; } } // If not a known word reference, iterate fromp to simplify/reset tracking iterate(nodep->fromp()); } void visit(AstVarRef* nodep) override { if (!m_funcp) return; SubstVarEntry& entry = getEntry(nodep); // If it's a write, reset tracking as we don't know the assigned value, // otherwise we would have picked it up in visit(AstNodeAssign*) if (nodep->access().isWriteOrRW()) { entry.assignWhole(nullptr, ++m_assignStep); return; } // Otherwise it's a read, substitute it if possible UASSERT_OBJ(nodep->access().isReadOnly(), nodep, "Invalid access"); if (isSubstitutable(nodep->varp())) { if (AstNodeExpr* const substp = entry.substWhole()) { // Do not substitute a compound wide expression. // The whole point of adding temporaries is to eliminate them. if (!nodep->isWide() || VN_IS(substp, VarRef)) { ++m_nWholeSubstituted; substitute(nodep, substp); return; } } } // If not substituted, mark the assignment setting this variable as used entry.usedWhole(); } void visit(AstNodeExpr* nodep) override { if (!m_funcp) return; // Count nodes as we descend to track complexity ++m_ops; // First iterate children, this can cache child purity iterateChildren(nodep); // Do not substitute impure expressions if (!nodep->isPure()) m_ops = SUBST_MAX_OPS_NA; } void visit(AstVar*) override {} // Accelerate void visit(AstConst*) override {} // Accelerate void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS explicit SubstVisitor(AstNetlist* nodep) { iterate(nodep); } ~SubstVisitor() override { V3Stats::addStat("Optimizations, Subst, Substituted temps", m_nSubst); V3Stats::addStat("Optimizations, Subst, Constant words reinlined", m_nConstWordsReinlined); V3Stats::addStat("Optimizations, Subst, Whole variable assignments deleted", m_nWholeAssignDeleted); V3Stats::addStat("Optimizations, Subst, Whole variables substituted", m_nWholeSubstituted); V3Stats::addStat("Optimizations, Subst, Word assignments deleted", m_nWordAssignDeleted); V3Stats::addStat("Optimizations, Subst, Words substituted", m_nWordSubstituted); UASSERT(m_entries.empty(), "Should not visit outside functions"); } }; //###################################################################### // Subst class functions void V3Subst::substituteAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); { SubstVisitor{nodep}; } // Destruct before checking V3Global::dumpCheckGlobalTree("subst", 0, dumpTreeEitherLevel() >= 3); }