475 lines
18 KiB
C++
475 lines
18 KiB
C++
// -*- 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 <vector>
|
|
|
|
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<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} {}
|
|
~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<SubstVarEntry*>();
|
|
}
|
|
|
|
// 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<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 - 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<SubstVarEntry*>();
|
|
}
|
|
|
|
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);
|
|
}
|