Optimize more wide operation temporaries with substitution (#6972)

A temporary introduced by V3Premit could not be eliminated in V3Subst if
it was involved in an expression that did a write back to a
non-temporary. To enables removing these, we need to track all variables
in V3Subst, not just the ones we would consider for elimination. Note
the new implementation is marginally faster than the old one even though
it does more work. It can eliminate ~5% more of wide temporaries on some
designs. Algorithm is largely the same.
This commit is contained in:
Geza Lore 2026-02-01 05:07:13 +00:00 committed by GitHub
parent 07ce0ac2ea
commit cea4c88e12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 319 additions and 288 deletions

View File

@ -29,380 +29,411 @@
#include "V3Const.h"
#include "V3Stats.h"
#include <algorithm>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Class for each word of a multi-word variable
class SubstVarWord final {
protected:
// MEMBERS
AstNodeAssign* m_assignp; // Last assignment to each word of this var
int m_step; // Step number of last assignment
bool m_use; // True if each word was consumed
bool m_complex; // True if each word is complex
friend class SubstVarEntry;
// METHODS
void clear() {
m_assignp = nullptr;
m_step = 0;
m_use = false;
m_complex = false;
}
};
//######################################################################
// Class for every variable we may process
// 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
AstVar* const m_varp; // Variable this tracks
bool m_wordAssign = false; // True if any word assignments
bool m_wordUse = false; // True if any individual word usage
SubstVarWord m_whole; // Data for whole vector used at once
std::vector<SubstVarWord> m_words; // Data for every word, if multi word variable
// 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<Record> m_wordRecords{static_cast<size_t>(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} { // Construction for when a var is used
m_words.resize(varp->widthWords());
m_whole.clear();
for (int i = 0; i < varp->widthWords(); i++) m_words[i].clear();
}
: m_varp{varp} {}
~SubstVarEntry() = default;
private:
// METHODS
bool wordNumOk(int word) const { return word < m_varp->widthWords(); }
AstNodeAssign* getWordAssignp(int word) const {
if (!wordNumOk(word)) {
return nullptr;
} else {
return m_words[word].m_assignp;
}
// 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;
}
public:
void assignWhole(int step, AstNodeAssign* assp) {
if (m_whole.m_assignp) m_whole.m_complex = true;
m_whole.m_assignp = assp;
m_whole.m_step = step;
// 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;
}
void assignWord(int step, int word, AstNodeAssign* assp) {
if (!wordNumOk(word) || getWordAssignp(word) || m_words[word].m_complex) {
m_whole.m_complex = true;
}
m_wordAssign = true;
if (wordNumOk(word)) {
m_words[word].m_assignp = assp;
m_words[word].m_step = step;
}
}
void assignWordComplex(int word) {
if (!wordNumOk(word) || getWordAssignp(word) || m_words[word].m_complex) {
m_whole.m_complex = true;
}
m_words[word].m_complex = true;
}
void assignComplex() { m_whole.m_complex = true; }
void consumeWhole() { // ==consumeComplex as we don't know the difference
m_whole.m_use = true;
}
void consumeWord(int word) {
m_words[word].m_use = true;
m_wordUse = true;
}
// ACCESSORS
AstNodeExpr* substWhole(AstNode* errp) {
if (m_varp->isWide()) return nullptr;
if (m_whole.m_complex) return nullptr;
if (!m_whole.m_assignp) return nullptr;
if (m_wordAssign) return nullptr;
const AstNodeAssign* const assp = m_whole.m_assignp;
UASSERT_OBJ(assp, errp, "Reading whole that was never assigned");
AstNodeExpr* const rhsp = assp->rhsp();
// Mark the whole variable as used (value consumed)
void usedWhole() {
m_wholeRecord.m_used = true;
for (Record& wordRecord : m_wordRecords) wordRecord.m_used = true;
}
// AstCvtPackedToArray can't be anywhere else than on the RHS of assignment
if (VN_IS(rhsp, CvtPackedToArray)) return nullptr;
// Check if only substitute if constant
if (m_varp->substConstOnly() && !VN_IS(rhsp, Const)) return nullptr;
// Substitute it
return rhsp;
// Mark the specific word as used (value consumed)
void usedWord(uint32_t word) {
m_wholeRecord.m_used = true;
m_wordRecords[word].m_used = true;
}
// Return what to substitute given word number for
AstNodeExpr* substWord(AstNode* errp, int word) {
if (!m_whole.m_complex && !m_whole.m_assignp && !m_words[word].m_complex) {
const AstNodeAssign* const assp = getWordAssignp(word);
UASSERT_OBJ(assp, errp, "Reading a word that was never assigned, or bad word #");
return assp->rhsp();
} else {
return nullptr;
}
}
int getWholeStep() const { return m_whole.m_step; }
int getWordStep(int word) const {
if (!wordNumOk(word)) {
return 0;
} else {
return m_words[word].m_step;
}
}
void deleteAssign(AstNodeAssign* nodep) {
UINFO(5, "Delete " << nodep);
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
void deleteUnusedAssign() {
// If there are unused assignments in this var, kill them
if (!m_whole.m_use && !m_wordUse && m_whole.m_assignp) {
VL_DO_CLEAR(deleteAssign(m_whole.m_assignp), m_whole.m_assignp = nullptr);
}
for (unsigned i = 0; i < m_words.size(); i++) {
if (!m_whole.m_use && !m_words[i].m_use && m_words[i].m_assignp
&& !m_words[i].m_complex) {
VL_DO_CLEAR(deleteAssign(m_words[i].m_assignp), m_words[i].m_assignp = nullptr);
}
}
// 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 have changed value since we determined subst value,
// as a visitor of each AstNode
// See if any variables in the expression we are considering to
// substitute have changed value since we recorded the assignment.
class SubstUseVisitor final : public VNVisitorConst {
class SubstValidVisitor final : public VNVisitorConst {
// NODE STATE
// See SubstVisitor
// STATE - across all visitors
const int m_origStep; // Step number where subst was recorded
bool m_ok = true; // No misassignments found
// 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* findEntryp(AstVarRef* nodep) {
return reinterpret_cast<SubstVarEntry*>(nodep->varp()->user1p()); // Might be nullptr
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<SubstVarEntry*>();
}
// VISITORS
void visit(AstVarRef* nodep) override {
const SubstVarEntry* const entryp = findEntryp(nodep);
if (entryp) {
// Don't sweat it. We assign a new temp variable for every new assignment,
// so there's no way we'd ever replace a old value.
} else {
// A simple variable; needs checking.
if (m_origStep < nodep->varp()->user2()) {
if (m_ok) { UINFO(9, " RHS variable changed since subst recorded: " << nodep); }
m_ok = false;
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;
}
}
}
void visit(AstConst*) override {} // Accelerate
void visit(AstNode* nodep) override {
if (!nodep->isPure()) m_ok = false;
iterateChildrenConst(nodep);
}
public:
// CONSTRUCTORS
SubstUseVisitor(AstNode* nodep, int origStep)
: m_origStep{origStep} {
UINFO(9, " SubstUseVisitor " << origStep << " " << nodep);
iterateConst(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;
}
~SubstUseVisitor() override = default;
// METHODS
bool ok() const { return m_ok; }
};
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;
}
//######################################################################
// Subst state, as a visitor of each AstNode
// Substitution visitor
class SubstVisitor final : public VNVisitor {
// NODE STATE
// Passed to SubstUseVisitor
// AstVar::user1p -> SubstVar* for usage var, 0=not set yet. Only under CFunc.
// AstVar::user2 -> int step number for last assignment, 0=not set yet
const VNUser2InUse m_inuser2;
// NODE STATE - only Under AstCFunc
// AstVar::user1p -> SubstVarEntry* for assignment tracking. Also used by SubstValidVisitor
// STATE
std::deque<SubstVarEntry> m_entries; // Nodes to delete when we are finished
int m_ops = 0; // Number of operators on assign rhs
int m_assignStep = 0; // Assignment number to determine var lifetime
std::deque<SubstVarEntry> 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
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
enum {
SUBST_MAX_OPS_SUBST = 30, // Maximum number of ops to substitute in
SUBST_MAX_OPS_NA = 9999
}; // Not allowed to substitute
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* getEntryp(AstVarRef* nodep) {
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<SubstVarEntry*>();
return *varp->user1u().to<SubstVarEntry*>();
}
bool isSubstVar(AstVar* nodep) { return nodep->isStatementTemp() && !nodep->noSubst(); }
// VISITORS
void visit(AstNodeAssign* nodep) override {
if (!m_funcp) return;
VL_RESTORER(m_ops);
m_ops = 0;
m_assignStep++;
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;
iterateAndNextNull(nodep->rhsp());
if (nSubstBefore != m_nSubst) V3Const::constifyEditCpp(nodep->rhsp());
if (VN_IS(nodep->rhsp(), Const)) m_ops = 0;
bool hit = false;
if (AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef)) {
if (isSubstVar(varrefp->varp())) {
SubstVarEntry* const entryp = getEntryp(varrefp);
hit = true;
if (m_ops > SUBST_MAX_OPS_SUBST) {
UINFO(8, " ASSIGNtooDeep " << varrefp);
entryp->assignComplex();
} else {
UINFO(8, " ASSIGNwhole " << varrefp);
entryp->assignWhole(m_assignStep, nodep);
}
}
} else if (const AstWordSel* const wordp = VN_CAST(nodep->lhsp(), WordSel)) {
if (AstVarRef* const varrefp = VN_CAST(wordp->fromp(), VarRef)) {
if (VN_IS(wordp->bitp(), Const) && isSubstVar(varrefp->varp())) {
const int word = VN_AS(wordp->bitp(), Const)->toUInt();
SubstVarEntry* const entryp = getEntryp(varrefp);
hit = true;
if (m_ops > SUBST_MAX_OPS_SUBST) {
UINFO(8, " ASSIGNtooDeep " << varrefp);
entryp->assignWordComplex(word);
} else {
UINFO(8, " ASSIGNword" << word << " " << varrefp);
entryp->assignWord(m_assignStep, word, nodep);
}
}
}
}
if (!hit) iterate(nodep->lhsp());
exprp = VN_AS(iterateSubtreeReturnEdits(exprp), NodeExpr);
if (nSubstBefore != m_nSubst) V3Const::constifyEditCpp(exprp);
}
void replaceSubstEtc(AstNode* nodep, AstNodeExpr* substp) {
UINFOTREE(6, nodep, "", "substw_old");
// 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->cloneTreePure(true);
if (!nodep->isQuad() && newp->isQuad()) {
newp = new AstCCast{newp->fileline(), newp, nodep};
}
UINFOTREE(6, newp, "", "w_new");
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
++m_nSubst;
}
void visit(AstWordSel* nodep) override {
if (!m_funcp) return;
const size_t nSubstBefore = m_nSubst;
iterate(nodep->bitp());
// Simplify in case it was substituted and became constant
if (nSubstBefore != m_nSubst) V3Const::constifyEditCpp(nodep->bitp());
AstVarRef* const varrefp = VN_CAST(nodep->fromp(), VarRef);
const AstConst* const constp = VN_CAST(nodep->bitp(), Const);
if (varrefp && isSubstVar(varrefp->varp()) && varrefp->access().isReadOnly() && constp) {
// Nicely formed lvalues handled in NodeAssign
// Other lvalues handled as unknown mess in AstVarRef
const int word = constp->toUInt();
UINFO(8, " USEword" << word << " " << varrefp);
SubstVarEntry* const entryp = getEntryp(varrefp);
if (AstNodeExpr* const substp = entryp->substWord(nodep, word)) {
// Check that the RHS hasn't changed value since we recorded it.
const SubstUseVisitor visitor{substp, entryp->getWordStep(word)};
if (visitor.ok()) {
VL_DO_DANGLING(replaceSubstEtc(nodep, substp), nodep);
} else {
entryp->consumeWord(word);
}
} else {
entryp->consumeWord(word);
}
} else {
iterate(nodep->fromp());
}
}
void visit(AstVarRef* nodep) override {
if (!m_funcp) return;
// Any variable
if (nodep->access().isWriteOrRW()) {
m_assignStep++;
nodep->varp()->user2(m_assignStep);
UINFO(9, " ASSIGNstep u2=" << nodep->varp()->user2() << " " << nodep);
}
if (isSubstVar(nodep->varp())) {
SubstVarEntry* const entryp = getEntryp(nodep);
if (nodep->access().isWriteOrRW()) {
UINFO(8, " ASSIGNcpx " << nodep);
entryp->assignComplex();
} else if (AstNodeExpr* const substp = entryp->substWhole(nodep)) {
// Check that the RHS hasn't changed value since we recorded it.
const SubstUseVisitor visitor{substp, entryp->getWholeStep()};
if (visitor.ok()) {
UINFO(8, " USEwhole " << nodep);
VL_DO_DANGLING(replaceSubstEtc(nodep, substp), nodep);
} else {
UINFO(8, " USEwholeButChg " << nodep);
entryp->consumeWhole();
}
} else { // Consumed w/o substitute
UINFO(8, " USEwtf " << nodep);
entryp->consumeWhole();
}
}
}
void visit(AstVar*) override {}
void visit(AstConst*) override {}
// VISITORS
void visit(AstCFunc* nodep) override {
UASSERT_OBJ(!m_funcp, nodep, "Should not nest");
UASSERT_OBJ(m_entries.empty(), nodep, "References outside functions");
VL_RESTORER(m_funcp);
m_funcp = nodep;
UASSERT_OBJ(m_entries.empty(), nodep, "Should not visit outside functions");
const VNUser1InUse m_inuser1;
iterateChildren(nodep);
for (SubstVarEntry& ip : m_entries) ip.deleteUnusedAssign();
m_entries.clear();
// 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()) {
AstNode* const editedp = V3Const::constifyEditCpp(nodep);
UASSERT_OBJ(editedp == nodep, editedp, "Should not have replaced CFunc");
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());
}
// Do not optimzie across user $c input
void visit(AstCExprUser* nodep) override {
m_ops = SUBST_MAX_OPS_NA;
iterateChildren(nodep);
}
void visit(AstCStmtUser* nodep) override {
m_ops = SUBST_MAX_OPS_NA;
iterateChildren(nodep);
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, substitute it if possible
UASSERT_OBJ(refp->access().isReadOnly(), nodep, "Invalid access");
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(AstNode* nodep) override {
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(AstNode* nodep) { iterate(nodep); }
~SubstVisitor() override {
V3Stats::addStat("Optimizations, Substituted temps", m_nSubst);
UASSERT(m_entries.empty(), "References outside functions");
V3Stats::addStat("Optimizations, Whole variable assignments deleted",
m_nWholeAssignDeleted);
V3Stats::addStat("Optimizations, Whole variables substituted", m_nWholeSubstituted);
V3Stats::addStat("Optimizations, Word assignments deleted", m_nWordAssignDeleted);
V3Stats::addStat("Optimizations, Words substituted", m_nWordSubstituted);
UASSERT(m_entries.empty(), "Should not visit outside functions");
}
};