verilator/src/V3Localize.cpp
2025-01-01 08:30:25 -05:00

229 lines
9.6 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Convert BLOCKTEMPs to local variables
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 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.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// LOCALIZE TRANSFORMATIONS:
// All modules:
// VARSCOPE(BLOCKTEMP...
// if only referenced in one CFUNC, make it local
// VARSCOPE
// if non-public, always written before used, make it local
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Localize.h"
#include "V3AstUserAllocator.h"
#include "V3Stats.h"
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// LocalizeVisitor
class LocalizeVisitor final : public VNVisitor {
// NODE STATE
// AstVarScope::user1() -> Bool indicating VarScope is not optimizable.
// AstCFunc::user1() -> Bool indicating CFunc is not a leaf function.
// AstVarScope::user2() -> Bool indicating VarScope was fully assigned in the current
// function.
// AstVarScope::user3p() -> Set of CFuncs referencing this VarScope. (via m_accessors)
// AstCFunc::user4p() -> Multimap of 'VarScope -> VarRefs that reference that VarScope'
// in this function. (via m_references)
const VNUser1InUse m_user1InUse;
const VNUser3InUse m_user3InUse;
const VNUser4InUse m_user4InUse;
AstUser3Allocator<AstVarScope, std::unordered_set<AstCFunc*>> m_accessors;
AstUser4Allocator<AstCFunc, std::unordered_multimap<const AstVarScope*, AstVarRef*>>
m_references;
// STATE - across all visitors
std::vector<AstVarScope*> m_varScopeps; // List of variables to consider for localization
VDouble0 m_statLocVars; // Statistic tracking
// STATE - for current visit position (use VL_RESTORER)
AstCFunc* m_cfuncp = nullptr; // Current active function
uint32_t m_nodeDepth = 0; // Node depth under m_cfuncp
// METHODS
bool isOptimizable(AstVarScope* nodep) {
// Don't want to malloc/free the backing store all the time
if (VN_IS(nodep->dtypep(), NBACommitQueueDType)) return false;
return ((!nodep->user1() // Not marked as not optimizable, or ...
// .. a block temp used in a single CFunc
|| (nodep->varp()->varType() == VVarType::BLOCKTEMP
&& m_accessors(nodep).size() == 1))
// and under size limit
&& nodep->varp()->dtypep()->widthTotalBytes() <= v3Global.opt.localizeMaxSize());
}
static bool existsNonLeaf(const std::unordered_set<AstCFunc*>& funcps) {
for (const AstCFunc* const funcp : funcps) {
if (funcp->user1()) return true;
}
return false;
}
void moveVarScopes() {
for (AstVarScope* const nodep : m_varScopeps) {
if (!isOptimizable(nodep)) continue; // Not optimizable
const std::unordered_set<AstCFunc*>& funcps = m_accessors(nodep);
if (funcps.empty()) continue; // No referencing functions at all
// If more than one referencing function, but not all are leaf
// functions, then don't localize, as one of the referencing
// functions might be calling another, which the current analysis
// cannot cope with. This should be rare (introduced by V3Depth).
if (funcps.size() > 1 && existsNonLeaf(funcps)) continue;
UINFO(4, "Localizing " << nodep << endl);
++m_statLocVars;
// Yank the VarScope from it's parent and schedule them for deletion. Leave the Var
// for now, as not all VarScopes referencing this Var might be localized.
pushDeletep(nodep->unlinkFrBack());
// In each referencing function, create a replacement local variable
AstVar* const oldVarp = nodep->varp();
for (AstCFunc* const funcp : funcps) {
// Create the new local variable.
const string newName
= nodep->scopep() == funcp->scopep()
? oldVarp->name()
: nodep->scopep()->nameDotless() + "__DOT__" + oldVarp->name();
AstVar* const newVarp
= new AstVar{oldVarp->fileline(), oldVarp->varType(), newName, oldVarp};
newVarp->funcLocal(true);
newVarp->noReset(oldVarp->noReset());
funcp->addInitsp(newVarp);
// Fix up all the references within this function
const auto er = m_references(funcp).equal_range(nodep);
for (auto it = er.first; it != er.second; ++it) {
AstVarRef* const refp = it->second;
refp->varScopep(nullptr);
refp->varp(newVarp);
}
}
}
m_varScopeps.clear();
}
// VISITORS
void visit(AstNetlist* nodep) override {
iterateChildrenConst(nodep);
moveVarScopes();
}
void visit(AstCFunc* nodep) override {
UINFO(4, " CFUNC " << nodep << endl);
VL_RESTORER(m_cfuncp);
VL_RESTORER(m_nodeDepth);
m_cfuncp = nodep;
m_nodeDepth = 0;
const VNUser2InUse user2InUse;
iterateChildrenConst(nodep);
}
void visit(AstCCall* nodep) override {
m_cfuncp->user1(true); // Mark caller as not a leaf function
iterateChildrenConst(nodep);
}
void visit(AstNodeAssign* nodep) override {
// Analyze RHS first so "a = a + 1" is detected as a read before write
iterate(nodep->rhsp());
// For now we only consider an assignment that is directly under the function, (in
// particular: not under an AstIf, or other kind of branch). This could be improved with
// proper data flow analysis.
if (m_nodeDepth == 0) {
// Check if simple "VARREF = ..." assignment, i.e.: this assignment sets the whole
// variable (and in particular, it is not assigned only in part).
if (const AstVarRef* const refp = VN_CAST(nodep->lhsp(), VarRef)) {
// Mark this VarScope as assigned in this function
refp->varScopep()->user2(1);
}
}
// Analyze LHS (in case it's not the above simple case
iterate(nodep->lhsp());
}
void visit(AstVarScope* nodep) override {
if (!nodep->varp()->isPrimaryIO() // Not an IO the user wants to interact with
&& !nodep->varp()->isSigPublic() // Not something the user wants to interact with
&& !nodep->varp()->isFuncLocal() // Not already a function local (e.g.: argument)
&& !nodep->varp()->isStatic() // Not a static variable
&& !nodep->varp()->isClassMember() // Statically exists in design hierarchy
&& !nodep->varp()->sensIfacep() // Not sensitive to an interface
&& !nodep->varp()->valuep() // Does not have an initializer
) {
UINFO(4, "Consider for localization: " << nodep << endl);
m_varScopeps.push_back(nodep);
}
// No iterate; Don't want varrefs under it (e.g.: in child dtype?)
}
void visit(AstVarRef* nodep) override {
UASSERT_OBJ(m_cfuncp, nodep, "AstVarRef not under function");
AstVarScope* const varScopep = nodep->varScopep();
// Remember this function accesses this VarScope (we always need this as we might optimize
// this VarScope into a local, even if it's not assigned. See 'isOptimizable')
m_accessors(varScopep).emplace(m_cfuncp);
// Remember the reference so we can fix it up later (we always need this as well)
m_references(m_cfuncp).emplace(varScopep, nodep);
// Check if already marked as not optimizable
if (!varScopep->user1()) {
// Note: we only check read variables, as it's ok to localize (and in fact discard)
// any variables that are only written but never read.
if (nodep->access().isReadOrRW() && !varScopep->user2()) {
// Variable is read, but is not known to have been assigned in this function. Mark
// as not optimizable.
UINFO(4, "Not optimizable (not written): " << nodep << endl);
varScopep->user1(1);
}
}
// No iterate; Don't want varrefs under it (e.g.: in child dtype?)
}
void visit(AstNode* nodep) override {
VL_RESTORER(m_nodeDepth);
++m_nodeDepth;
iterateChildrenConst(nodep);
}
public:
// CONSTRUCTORS
explicit LocalizeVisitor(AstNetlist* nodep) { iterate(nodep); }
~LocalizeVisitor() override {
V3Stats::addStat("Optimizations, Vars localized", m_statLocVars);
}
};
//######################################################################
// Localize class functions
void V3Localize::localizeAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ": " << endl);
{ LocalizeVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("localize", 0, dumpTreeEitherLevel() >= 6);
}