Internals: Optimize temporary memory allocations (#6517)

This patch gets rid of over 80% of temporary dynamic memory allocations
(when a malloced node is immediately freed with no other malloc in
between). It also gets rid of over 20% of all calls to malloc.

It's worth ~3% average verilation speed up with tcmalloc, and more
without tcmalloc.
This commit is contained in:
Geza Lore 2025-10-01 16:01:30 +02:00 committed by GitHub
parent 435e1149d5
commit e9c48cd1ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 218 additions and 216 deletions

View File

@ -1287,30 +1287,24 @@ void AstNode::foreachImpl(ConstCorrectAstNode<T_Arg>* nodep, const T_Callable& f
using T_Arg_NonConst = typename std::remove_const<T_Arg>::type;
using Node = ConstCorrectAstNode<T_Arg>;
// Traversal stack
std::vector<Node*> stack; // Kept as a vector for easy resizing
Node** basep = nullptr; // Pointer to base of stack
Node** topp = nullptr; // Pointer to top of stack
Node** limp = nullptr; // Pointer to stack limit (when need growing)
// We prefetch this far into the stack
constexpr int prefetchDistance = 2;
constexpr int PrefetchDistance = 2;
// We push max 5 items per iteration
constexpr size_t MaxPushesPerIteration = 5;
// Initial stack sie
constexpr size_t InitialStackSize = 32;
// Grow stack to given size
const auto grow = [&](size_t size) {
const ptrdiff_t occupancy = topp - basep;
stack.resize(size);
basep = stack.data() + prefetchDistance;
topp = basep + occupancy;
limp = basep + size - 5; // We push max 5 items per iteration
};
// Initial stack size
grow(32);
// Traversal stack. Because we often iterate over small trees, use an in-line initial stack
size_t stackSize = InitialStackSize;
Node* inLineStack[InitialStackSize]; // In-line initial stack storage
std::unique_ptr<Node*[]> outOfLineStackp{nullptr}; // In case we need more, we will allocate
Node** basep = inLineStack + PrefetchDistance; // Pointer to base of stack
Node** topp = basep; // Pointer to top of stack
Node** limp = inLineStack + stackSize - MaxPushesPerIteration; // Pointer to grow limit
// We want some non-null pointers at the beginning. These will be prefetched, but not
// visited, so the root node will suffice. This eliminates needing branches in the loop.
for (int i = -prefetchDistance; i; ++i) basep[i] = nodep;
for (int i = -PrefetchDistance; i; ++i) basep[i] = nodep;
// Visit given node, enqueue children for traversal
const auto visit = [&](Node* currp) {
@ -1343,10 +1337,23 @@ void AstNode::foreachImpl(ConstCorrectAstNode<T_Arg>* nodep, const T_Callable& f
Node* const headp = *--topp;
// Prefetch in case we are ascending the tree
ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]);
ASTNODE_PREFETCH_NON_NULL(topp[-PrefetchDistance]);
// Ensure we have stack space for nextp and the 4 children
if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2);
if (VL_UNLIKELY(topp >= limp)) {
const ptrdiff_t occupancy = topp - basep;
{
const size_t newStackSize = stackSize * 2;
Node** const newStackp = new Node*[newStackSize];
std::memcpy(newStackp, basep - PrefetchDistance,
sizeof(Node**) * (occupancy + PrefetchDistance));
stackSize = newStackSize;
outOfLineStackp.reset(newStackp);
}
basep = outOfLineStackp.get() + PrefetchDistance;
topp = basep + occupancy;
limp = outOfLineStackp.get() + stackSize - MaxPushesPerIteration;
}
// Enqueue the next node
if (headp->nextp()) *topp++ = headp->nextp();
@ -1363,30 +1370,24 @@ bool AstNode::predicateImpl(ConstCorrectAstNode<T_Arg>* nodep, const T_Callable&
using T_Arg_NonConst = typename std::remove_const<T_Arg>::type;
using Node = ConstCorrectAstNode<T_Arg>;
// Traversal stack
std::vector<Node*> stack; // Kept as a vector for easy resizing
Node** basep = nullptr; // Pointer to base of stack
Node** topp = nullptr; // Pointer to top of stack
Node** limp = nullptr; // Pointer to stack limit (when need growing)
// We prefetch this far into the stack
constexpr int prefetchDistance = 2;
constexpr int PrefetchDistance = 2;
// We push max 5 items per iteration
constexpr size_t MaxPushesPerIteration = 5;
// Initial stack sie
constexpr size_t InitialStackSize = 32;
// Grow stack to given size
const auto grow = [&](size_t size) {
const ptrdiff_t occupancy = topp - basep;
stack.resize(size);
basep = stack.data() + prefetchDistance;
topp = basep + occupancy;
limp = basep + size - 5; // We push max 5 items per iteration
};
// Initial stack size
grow(32);
// Traversal stack. Because we often iterate over small trees, use an in-line initial stack
size_t stackSize = InitialStackSize;
Node* inLineStack[InitialStackSize]; // In-line initial stack storage
std::unique_ptr<Node*[]> outOfLineStackp{nullptr}; // In case we need more, we will allocate
Node** basep = inLineStack + PrefetchDistance; // Pointer to base of stack
Node** topp = basep; // Pointer to top of stack
Node** limp = inLineStack + stackSize - MaxPushesPerIteration; // Pointer to grow limit
// We want some non-null pointers at the beginning. These will be prefetched, but not
// visited, so the root node will suffice. This eliminates needing branches in the loop.
for (int i = -prefetchDistance; i; ++i) basep[i] = nodep;
for (int i = -PrefetchDistance; i; ++i) basep[i] = nodep;
// Visit given node, enqueue children for traversal, return true if result determined.
const auto visit = [&](Node* currp) {
@ -1418,10 +1419,23 @@ bool AstNode::predicateImpl(ConstCorrectAstNode<T_Arg>* nodep, const T_Callable&
Node* const headp = *--topp;
// Prefetch in case we are ascending the tree
ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]);
ASTNODE_PREFETCH_NON_NULL(topp[-PrefetchDistance]);
// Ensure we have stack space for nextp and the 4 children
if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2);
if (VL_UNLIKELY(topp >= limp)) {
const ptrdiff_t occupancy = topp - basep;
{
const size_t newStackSize = stackSize * 2;
Node** const newStackp = new Node*[newStackSize];
std::memcpy(newStackp, basep - PrefetchDistance,
sizeof(Node**) * (occupancy + PrefetchDistance));
stackSize = newStackSize;
outOfLineStackp.reset(newStackp);
}
basep = outOfLineStackp.get() + PrefetchDistance;
topp = basep + occupancy;
limp = outOfLineStackp.get() + stackSize - MaxPushesPerIteration;
}
// Enqueue the next node
if (headp->nextp()) *topp++ = headp->nextp();

View File

@ -170,6 +170,11 @@ AstCExpr::AstCExpr(FileLine* fl, const string& textStmt, int setwidth, bool clea
if (setwidth) dtypeSetLogicSized(setwidth, VSigning::UNSIGNED);
}
bool AstVar::sameNode(const AstNode* samep) const {
const AstVar* const asamep = VN_DBG_AS(samep, Var);
return m_name == asamep->m_name && varType() == asamep->varType();
}
AstVarRef::AstVarRef(FileLine* fl, AstVar* varp, const VAccess& access)
: ASTGEN_SUPER_VarRef(fl, varp, access) {}
AstVarRef::AstVarRef(FileLine* fl, AstNodeModule* pkgp, AstVar* varp, const VAccess& access)
@ -190,7 +195,7 @@ bool AstVarRef::sameNode(const AstVarRef* samep) const {
} else {
return (selfPointer() == samep->selfPointer()
&& classOrPackagep() == samep->classOrPackagep() && access() == samep->access()
&& (varp() && samep->varp() && varp()->name() == samep->varp()->name()));
&& (varp() && samep->varp() && varp()->sameNode(samep->varp())));
}
}
bool AstVarRef::sameNoLvalue(const AstVarRef* samep) const {
@ -200,7 +205,7 @@ bool AstVarRef::sameNoLvalue(const AstVarRef* samep) const {
return (selfPointer() == samep->selfPointer()
&& classOrPackagep() == samep->classOrPackagep()
&& (!selfPointer().isEmpty() || !samep->selfPointer().isEmpty())
&& varp()->name() == samep->varp()->name());
&& varp()->sameNode(samep->varp()));
}
}

View File

@ -1704,6 +1704,7 @@ public:
VSigning numeric);
AstBasicDType* findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd, const VNumRange& range,
int widthMin, VSigning numeric);
AstBasicDType* findCreateSameDType(AstBasicDType& node);
AstBasicDType* findInsertSameDType(AstBasicDType* nodep);
AstConstraintRefDType* findConstraintRefDType(FileLine* fl);
AstEmptyQueueDType* findEmptyQueueDType(FileLine* fl);
@ -2003,7 +2004,7 @@ public:
ASTGEN_MEMBERS_AstVar;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool sameNode(const AstNode* samep) const override;
inline bool sameNode(const AstNode* samep) const override;
string name() const override VL_MT_STABLE { return m_name; } // * = Var name
bool hasDType() const override VL_MT_SAFE { return true; }
bool maybePointedTo() const override VL_MT_SAFE { return true; }

View File

@ -1355,46 +1355,38 @@ AstVoidDType* AstTypeTable::findVoidDType(FileLine* fl) {
}
AstBasicDType* AstTypeTable::findBasicDType(FileLine* fl, VBasicDTypeKwd kwd) {
if (m_basicps[kwd]) return m_basicps[kwd];
//
AstBasicDType* const new1p = new AstBasicDType{fl, kwd};
// Because the detailed map doesn't update this map,
// check the detailed map for this same node
// Also adds this new node to the detailed map
AstBasicDType* const newp = findInsertSameDType(new1p);
if (newp != new1p) {
VL_DO_DANGLING(new1p->deleteTree(), new1p);
} else {
addTypesp(newp);
// Because the detailed map doesn't update m_basicps, check the detailed
// map for this same node. Also adds this new node to the detailed map
if (!m_basicps[kwd]) {
AstBasicDType basic{fl, kwd};
m_basicps[kwd] = findCreateSameDType(basic);
}
//
m_basicps[kwd] = newp;
return newp;
return m_basicps[kwd];
}
AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd, int width,
int widthMin, VSigning numeric) {
AstBasicDType* const new1p = new AstBasicDType{fl, kwd, numeric, width, widthMin};
AstBasicDType* const newp = findInsertSameDType(new1p);
if (newp != new1p) {
VL_DO_DANGLING(new1p->deleteTree(), new1p);
} else {
addTypesp(newp);
}
return newp;
AstBasicDType basic{fl, kwd, numeric, width, widthMin};
return findCreateSameDType(basic);
}
AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd,
const VNumRange& range, int widthMin,
VSigning numeric) {
AstBasicDType* const new1p = new AstBasicDType{fl, kwd, numeric, range, widthMin};
AstBasicDType* const newp = findInsertSameDType(new1p);
if (newp != new1p) {
VL_DO_DANGLING(new1p->deleteTree(), new1p);
} else {
addTypesp(newp);
AstBasicDType basic{fl, kwd, numeric, range, widthMin};
return findCreateSameDType(basic);
}
AstBasicDType* AstTypeTable::findCreateSameDType(AstBasicDType& node) {
const VBasicTypeKey key{node.width(), node.widthMin(), node.numeric(), node.keyword(),
node.nrange()};
AstBasicDType*& entryr = m_detailedMap[key];
if (!entryr) {
entryr = node.cloneTree(false);
entryr->generic(true);
addTypesp(entryr);
}
return newp;
return entryr;
}
// cppcheck-suppress duplInheritedMember
@ -2785,10 +2777,6 @@ void AstVar::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, ignoreSchedWrite);
dumpJsonGen(str);
}
bool AstVar::sameNode(const AstNode* samep) const {
const AstVar* const asamep = VN_DBG_AS(samep, Var);
return name() == asamep->name() && varType() == asamep->varType();
}
void AstScope::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [abovep=" << nodeAddr(aboveScopep()) << "]";

View File

@ -167,55 +167,47 @@ using CacheTernary = Cache<KeyTernary, DfgVertexTernary*>;
// These return a reference to the mapped entry, inserting a nullptr if not yet exists
inline DfgSel*& getEntry(CacheSel& cache, const DfgDataType& dtype, DfgVertex* src0p,
uint32_t lsb) {
return cache
.emplace(std::piecewise_construct, //
std::forward_as_tuple(src0p, lsb, dtype.size()), //
std::forward_as_tuple(nullptr))
.first->second;
const KeySel key{src0p, lsb, dtype.size()};
return cache[key];
}
inline DfgVertexUnary*& getEntry(CacheUnary& cache, const DfgDataType&, DfgVertex* src0p) {
return cache
.emplace(std::piecewise_construct, //
std::forward_as_tuple(src0p), //
std::forward_as_tuple(nullptr))
.first->second;
const KeyUnary key{src0p};
return cache[key];
}
inline DfgVertexBinary*& getEntry(CacheBinary& cache, const DfgDataType&, DfgVertex* src0p,
DfgVertex* src1p) {
return cache
.emplace(std::piecewise_construct, //
std::forward_as_tuple(src0p, src1p), //
std::forward_as_tuple(nullptr))
.first->second;
const KeyBinary key{src0p, src1p};
return cache[key];
}
inline DfgVertexTernary*& getEntry(CacheTernary& cache, const DfgDataType&, DfgVertex* src0p,
DfgVertex* src1p, DfgVertex* src2p) {
return cache
.emplace(std::piecewise_construct, //
std::forward_as_tuple(src0p, src1p, src2p), //
std::forward_as_tuple(nullptr))
.first->second;
const KeyTernary key{src0p, src1p, src2p};
return cache[key];
}
// These return a reference to the mapped entry, inserting a nullptr if not yet exists
inline CacheSel::iterator find(CacheSel& cache, DfgVertex* src0p, uint32_t lsb, uint32_t size) {
return cache.find({src0p, lsb, size});
const KeySel key{src0p, lsb, size};
return cache.find(key);
}
inline CacheUnary::iterator find(CacheUnary& cache, DfgVertex* src0p) {
return cache.find({src0p});
const KeyUnary key{src0p};
return cache.find(key);
}
inline CacheBinary::iterator find(CacheBinary& cache, DfgVertex* src0p, DfgVertex* src1p) {
return cache.find({src0p, src1p});
const KeyBinary key{src0p, src1p};
return cache.find(key);
}
inline CacheTernary::iterator find(CacheTernary& cache, DfgVertex* src0p, DfgVertex* src1p,
DfgVertex* src2p) {
return cache.find({src0p, src1p, src2p});
const KeyTernary key{src0p, src1p, src2p};
return cache.find(key);
}
// These set the operands of a new vertex

View File

@ -137,18 +137,18 @@ public:
// Returns a Packed type of the given width
static const DfgDataType& packed(uint32_t width) {
// Find or create the right sized packed type
const auto pair = s_packedTypes.emplace(width, nullptr);
if (pair.second) pair.first->second = new DfgDataType{width};
return *pair.first->second;
const DfgDataType*& entryr = s_packedTypes[width];
if (!entryr) entryr = new DfgDataType{width};
return *entryr;
}
// Returns an Array type of the given size with the given elements
static const DfgDataType& array(const DfgDataType& elemType, uint32_t size) {
UASSERT(elemType.isPacked(), "Cannot create multi-dimensional arrays yet");
// Find or create the right sized array type with this as elements
const auto pair = elemType.m_arrayTypes.emplace(size, nullptr);
if (pair.second) pair.first->second = new DfgDataType{size, elemType};
return *pair.first->second;
const DfgDataType*& entryr = elemType.m_arrayTypes[size];
if (!entryr) entryr = new DfgDataType{size, elemType};
return *entryr;
}
// Returns the singleton Null type

View File

@ -651,10 +651,18 @@ V3OutFormatter::V3OutFormatter(V3OutFormatter::Language lang)
//----------------------------------------------------------------------
string V3OutFormatter::indentSpaces(int num) {
static constexpr int MAXSPACE = 80; // After this indent, stop indenting more
// Table of spaces, so we don't have to alloc/free them all the time
static const std::array<std::string, MAXSPACE + 1> s_indentSpaces = []() {
std::array<std::string, MAXSPACE + 1> table;
for (int i = 0; i <= MAXSPACE; ++i) table[i] = std::string(static_cast<size_t>(i), ' ');
return table;
}();
const std::string& V3OutFormatter::indentSpaces(int num) {
// Indent the specified number of spaces.
if (num <= 0) return std::string{};
return std::string(std::min<size_t>(num, MAXSPACE), ' ');
return s_indentSpaces[std::max(0, std::min(num, MAXSPACE))];
}
bool V3OutFormatter::tokenMatch(const char* cp, const char* cmp) {

View File

@ -106,7 +106,6 @@ public:
class V3OutFormatter VL_NOT_FINAL {
// TYPES
static constexpr int MAXSPACE = 80; // After this indent, stop indenting more
public:
enum AlignClass : uint8_t { AL_AUTO = 0, AL_STATIC = 1 };
enum Language : uint8_t { LA_C, LA_JSON, LA_MK, LA_VERILOG, LA_XML };
@ -173,7 +172,7 @@ public:
if (!m_nobreak) puts("\n");
}
// STATIC METHODS
static string indentSpaces(int num);
static const std::string& indentSpaces(int num);
// Add escaped characters to strings
static string quoteNameControls(const string& namein, Language lang = LA_C) VL_PURE;
static bool tokenMatch(const char* cp, const char* cmp);

View File

@ -66,7 +66,9 @@ class FileLineSingleton final {
~FileLineSingleton() = default;
fileNameIdx_t nameToNumber(const string& filename);
string numberToName(fileNameIdx_t filenameno) const VL_MT_SAFE { return m_names[filenameno]; }
const std::string& numberToName(fileNameIdx_t filenameno) const VL_MT_SAFE {
return m_names[filenameno];
}
V3LangCode numberToLang(fileNameIdx_t filenameno) const { return m_languages[filenameno]; }
void numberToLang(fileNameIdx_t filenameno, const V3LangCode& l) {
m_languages[filenameno] = l;
@ -291,7 +293,9 @@ public:
string ascii() const VL_MT_SAFE;
string asciiLineCol() const;
int filenameno() const VL_MT_SAFE { return m_filenameno; }
string filename() const VL_MT_SAFE { return singleton().numberToName(filenameno()); }
const std::string& filename() const VL_MT_SAFE {
return singleton().numberToName(filenameno());
}
// Filename with C string escapes
string filenameEsc() const VL_MT_SAFE { return VString::quoteBackslash(filename()); }
bool filenameIsGlobal() const VL_MT_SAFE {

View File

@ -465,22 +465,14 @@ void V3Graph::subtreeLoops(V3EdgeFuncP edgeFuncp, V3GraphVertex* vertexp, V3Grap
//######################################################################
// Algorithms - sorting
struct GraphSortVertexCmp final {
bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const {
return lhsp->sortCmp(rhsp) < 0;
}
};
struct GraphSortEdgeCmp final {
bool operator()(const V3GraphEdge* lhsp, const V3GraphEdge* rhsp) const {
return lhsp->sortCmp(rhsp) < 0;
}
};
void V3Graph::sortVertices() {
// Sort list of vertices by rank, then fanout
std::vector<V3GraphVertex*> vertexps;
for (V3GraphVertex& vertex : m_vertices) vertexps.push_back(&vertex);
std::stable_sort(vertexps.begin(), vertexps.end(), GraphSortVertexCmp());
std::stable_sort(vertexps.begin(), vertexps.end(),
[](const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) { //
return lhsp->sortCmp(rhsp) < 0;
});
// Re-insert in sorted order
for (V3GraphVertex* const vertexp : vertexps) {
m_vertices.unlink(vertexp);
@ -495,7 +487,10 @@ void V3Graph::sortEdges() {
// Make a vector
for (V3GraphEdge& edge : vertex.outEdges()) edges.push_back(&edge);
// Sort
std::stable_sort(edges.begin(), edges.end(), GraphSortEdgeCmp());
std::stable_sort(edges.begin(), edges.end(),
[](const V3GraphEdge* lhsp, const V3GraphEdge* rhsp) { //
return lhsp->sortCmp(rhsp) < 0;
});
// Relink edges in specified order
for (V3GraphEdge* const edgep : edges) edgep->relinkFromp(&vertex);
// Prep for next

View File

@ -64,12 +64,16 @@ public:
V3Hash operator+(T that) const {
return V3Hash{combine(m_value, V3Hash{that}.m_value)};
}
V3Hash operator+(const std::string& that) const {
return V3Hash{combine(m_value, V3Hash{that}.m_value)};
}
// '+=' combines in place
template <typename T>
V3Hash& operator+=(T that) {
return *this = *this + that;
}
V3Hash& operator+=(const std::string& that) { return *this = *this + that; }
};
std::ostream& operator<<(std::ostream& os, const V3Hash& rhs) VL_MT_SAFE;

View File

@ -64,55 +64,48 @@ class LifeVarEntry final {
// Last assignment to this varscope, nullptr if no longer relevant
AstNodeStmt* m_assignp = nullptr;
AstConst* m_constp = nullptr; // Known constant value
bool m_isNew = true; // Is just created
// First access was a set (and thus block above may have a set that can be deleted
bool m_setBeforeUse;
bool m_setBeforeUse = false;
// Was ever assigned (and thus above block may not preserve constant propagation)
bool m_everSet = false;
public:
class CRESET {};
class SIMPLEASSIGN {};
class COMPLEXASSIGN {};
class CONSUMED {};
LifeVarEntry(CRESET, AstCReset* nodep)
: m_setBeforeUse{true} {
resetStatement(nodep);
}
LifeVarEntry(SIMPLEASSIGN, AstNodeAssign* nodep)
: m_setBeforeUse{true} {
simpleAssign(nodep);
}
explicit LifeVarEntry(COMPLEXASSIGN)
: m_setBeforeUse{false} {
complexAssign();
}
explicit LifeVarEntry(CONSUMED)
: m_setBeforeUse{false} {
consumed();
}
LifeVarEntry() = default;
~LifeVarEntry() = default;
void init(bool setBeforeUse) {
UASSERT(m_isNew, "Not a new entry");
m_isNew = false;
m_setBeforeUse = setBeforeUse;
}
void simpleAssign(AstNodeAssign* nodep) { // New simple A=.... assignment
UASSERT_OBJ(!m_isNew, nodep, "Uninitialzized new entry");
m_assignp = nodep;
m_constp = nullptr;
m_everSet = true;
if (VN_IS(nodep->rhsp(), Const)) m_constp = VN_AS(nodep->rhsp(), Const);
}
void resetStatement(AstCReset* nodep) { // New CReset(A) assignment
UASSERT_OBJ(!m_isNew, nodep, "Uninitialzized new entry");
m_assignp = nodep;
m_constp = nullptr;
m_everSet = true;
}
void complexAssign() { // A[x]=... or some complicated assignment
UASSERT(!m_isNew, "Uninitialzized new entry");
m_assignp = nullptr;
m_constp = nullptr;
m_everSet = true;
}
void consumed() { // Rvalue read of A
UASSERT(!m_isNew, "Uninitialzized new entry");
m_assignp = nullptr;
}
AstNodeStmt* assignp() const { return m_assignp; }
AstConst* constNodep() const { return m_constp; }
bool isNew() const { return m_isNew; }
bool setBeforeUse() const { return m_setBeforeUse; }
bool everSet() const { return m_everSet; }
};
@ -126,9 +119,9 @@ class LifeBlock final {
// AstVarScope::user1() -> 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
using LifeMap = std::unordered_map<AstVarScope*, LifeVarEntry>;
LifeMap m_map; // Current active lifetime map for current scope
// For each basic block, we'll make a new map of what variables that if/else is changing
// Current active lifetime map for current scope
std::unordered_map<AstVarScope*, LifeVarEntry> m_map;
LifeBlock* const m_aboveLifep; // Upper life, or nullptr
LifeState* const m_statep; // Current global state
bool m_replacedVref = false; // Replaced a variable reference since last clearing
@ -140,20 +133,19 @@ public:
, m_statep{statep} {}
~LifeBlock() = default;
// METHODS
void checkRemoveAssign(const LifeMap::iterator& it) {
const AstVar* const varp = it->first->varp();
LifeVarEntry* const entp = &(it->second);
void checkRemoveAssign(AstVarScope* vscp, LifeVarEntry& entr) {
const AstVar* const varp = vscp->varp();
if (!varp->isSigPublic() && !varp->sensIfacep()) {
// Rather than track what sigs AstUCFunc/AstUCStmt may change,
// we just don't optimize any public sigs
// Check the var entry, and remove if appropriate
if (AstNodeStmt* const oldassp = entp->assignp()) {
if (AstNodeStmt* const oldassp = entr.assignp()) {
UINFO(7, " PREV: " << oldassp);
// Redundant assignment, in same level block
// Don't delete it now as it will confuse iteration since it maybe WAY
// above our current iteration point.
UINFOTREE(7, oldassp, "", "REMOVE/SAMEBLK");
entp->complexAssign();
entr.complexAssign();
oldassp->unlinkFrBack();
if (VN_IS(oldassp, CReset)) {
++m_statep->m_statCResetDel;
@ -168,40 +160,43 @@ public:
// Do we have a old assignment we can nuke?
UINFO(4, " CRESETof: " << nodep);
UINFO(7, " new: " << rstp);
const auto pair = m_map.emplace(std::piecewise_construct, //
std::forward_as_tuple(nodep),
std::forward_as_tuple(LifeVarEntry::CRESET{}, rstp));
if (!pair.second) {
checkRemoveAssign(pair.first);
pair.first->second.resetStatement(rstp);
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) {
entr.init(true);
} else {
checkRemoveAssign(nodep, entr);
}
entr.resetStatement(rstp);
// lifeDump();
}
void simpleAssign(AstVarScope* nodep, AstNodeAssign* assp) {
// Do we have a old assignment we can nuke?
UINFO(4, " ASSIGNof: " << nodep);
UINFO(7, " new: " << assp);
const auto pair = m_map.emplace(std::piecewise_construct, //
std::forward_as_tuple(nodep),
std::forward_as_tuple(LifeVarEntry::SIMPLEASSIGN{}, assp));
if (!pair.second) {
checkRemoveAssign(pair.first);
pair.first->second.simpleAssign(assp);
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) {
entr.init(true);
} else {
checkRemoveAssign(nodep, entr);
}
entr.simpleAssign(assp);
// lifeDump();
}
void complexAssign(AstVarScope* nodep) {
UINFO(4, " clearof: " << nodep);
const auto pair = m_map.emplace(nodep, LifeVarEntry::COMPLEXASSIGN{});
if (!pair.second) pair.first->second.complexAssign();
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) entr.init(false);
entr.complexAssign();
}
void clearReplaced() { m_replacedVref = false; }
bool replaced() const { return m_replacedVref; }
void varUsageReplace(AstVarScope* nodep, AstVarRef* varrefp) {
// Variable rvalue. If it references a constant, we can replace it
const auto pair = m_map.emplace(nodep, LifeVarEntry::CONSUMED{});
if (!pair.second) {
if (AstConst* const constp = pair.first->second.constNodep()) {
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) {
entr.init(false);
} else {
if (AstConst* const constp = entr.constNodep()) {
if (!varrefp->varp()->isSigPublic() && !varrefp->varp()->sensIfacep()) {
// Aha, variable is constant; substitute in.
// We'll later constant propagate
@ -214,19 +209,19 @@ public:
}
}
UINFO(4, " usage: " << nodep);
pair.first->second.consumed();
}
entr.consumed();
}
void complexAssignFind(AstVarScope* nodep) {
const auto pair = m_map.emplace(nodep, LifeVarEntry::COMPLEXASSIGN{});
if (!pair.second) {
UINFO(4, " casfind: " << pair.first->first);
pair.first->second.complexAssign();
}
UINFO(4, " casfind: " << nodep);
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) entr.init(false);
entr.complexAssign();
}
void consumedFind(AstVarScope* nodep) {
const auto pair = m_map.emplace(nodep, LifeVarEntry::CONSUMED{});
if (!pair.second) pair.first->second.consumed();
LifeVarEntry& entr = m_map[nodep];
if (entr.isNew()) entr.init(false);
entr.consumed();
}
void lifeToAbove() {
// Any varrefs under a if/else branch affect statements outside and after the if/else
@ -259,7 +254,7 @@ public:
// Both branches set the var, we can remove the assignment before the IF.
UINFO(4, "DUALBRANCH " << nodep);
const auto itab = m_map.find(nodep);
if (itab != m_map.end()) checkRemoveAssign(itab);
if (itab != m_map.end()) checkRemoveAssign(nodep, itab->second);
}
}
// this->lifeDump();
@ -287,12 +282,7 @@ class LifeVisitor final : public VNVisitor {
bool m_sideEffect = false; // Side effects discovered in assign RHS
bool m_noopt = false; // Disable optimization of variables in this block
bool m_tracingCall = false; // Iterating into a CCall to a CFunc
// LIFE MAP
// For each basic block, we'll make a new map of what variables that if/else is changing
using LifeMap = std::unordered_map<AstVarScope*, LifeVarEntry>;
// cppcheck-suppress memleak // cppcheck bug - it is deleted
LifeBlock* m_lifep; // Current active lifetime map for current scope
LifeBlock* m_lifep = nullptr; // Current active lifetime map for current scope
// METHODS
void setNoopt() {

View File

@ -206,7 +206,7 @@ class LocalizeVisitor final : public VNVisitor {
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);
m_accessors(varScopep).insert(m_cfuncp); // emplace performs a temporary malloc
// Remember the reference so we can fix it up later (we always need this as well)
m_references(m_cfuncp).emplace(varScopep, nodep);

View File

@ -195,6 +195,8 @@ public:
// For getline()
string m_lineChars; ///< Characters left for next line
string m_tokenBuf; // Token buffer, to avoid repeated alloc/free
void v3errorEnd(std::ostringstream& str) VL_RELEASE(V3Error::s().m_mutex) {
fileline()->v3errorEnd(str);
}
@ -1687,7 +1689,7 @@ int V3PreProcImp::getFinalToken(string& buf) {
if (!m_finAhead) {
m_finAhead = true;
m_finToken = getStateToken();
m_finBuf = string{yyourtext(), yyourleng()};
m_finBuf.assign(yyourtext(), yyourleng());
}
const int tok = m_finToken;
buf = m_finBuf;
@ -1748,10 +1750,10 @@ string V3PreProcImp::getline() {
const char* rtnp;
bool gotEof = false;
while (nullptr == (rtnp = std::strchr(m_lineChars.c_str(), '\n')) && !gotEof) {
string buf;
const int tok = getFinalToken(buf /*ref*/);
m_tokenBuf.clear();
const int tok = getFinalToken(m_tokenBuf /*ref*/);
if (debug() >= 5) {
const string bufcln = V3PreLex::cleanDbgStrg(buf);
const string bufcln = V3PreLex::cleanDbgStrg(m_tokenBuf);
const string flcol = m_lexp->m_tokFilelinep->asciiLineCol();
UINFO(0, flcol << ": GETFETC: " << tokenName(tok) << ": " << bufcln);
}
@ -1762,7 +1764,7 @@ string V3PreProcImp::getline() {
}
gotEof = true;
} else {
m_lineChars.append(buf);
m_lineChars.append(m_tokenBuf);
}
}

View File

@ -169,11 +169,6 @@ public:
//######################################################################
// Top Stats class
void V3Stats::addStatSum(const string& name, double count) VL_MT_SAFE_EXCLUDES(s_mutex) {
V3LockGuard lock{s_mutex};
addStat(V3Statistic{"*", name, count, 0, true});
}
void V3Stats::statsStageAll(AstNetlist* nodep, const std::string& stage, bool fastOnly) {
StatsVisitor{nodep, stage, fastOnly};
}

View File

@ -90,7 +90,7 @@ public:
}
// CONSTRUCTORS
V3Statistic(const string& stage, const string& name, double value, unsigned precision,
bool sumit = false, bool perf = false)
bool sumit, bool perf)
: m_name{name}
, m_value{value}
, m_precision{precision}
@ -121,13 +121,22 @@ public:
static void addStat(const V3Statistic&);
static void addStat(const string& stage, const string& name, double value,
unsigned precision = 0) {
addStat(V3Statistic{stage, name, value, precision});
addStat(V3Statistic{stage, name, value, precision, false, false});
}
static void addStat(const string& name, double value, unsigned precision = 0) {
addStat(V3Statistic{"*", name, value, precision});
addStat(V3Statistic{"*", name, value, precision, false, false});
}
// Add summary statistic - Threadsafe _unlike most other functions here_
static void addStatSum(const string& name, double count) VL_MT_SAFE_EXCLUDES(s_mutex);
static void addStatSum(const char* name, double count) VL_MT_SAFE_EXCLUDES(s_mutex) {
// Avoid memory blow-up when called frequently with zero adds,
// e.g. from V3Const invoked on individual expressions.
if (count == 0.0) return;
V3LockGuard lock{s_mutex};
addStat(V3Statistic{"*", name, count, 0, true, false});
}
static void addStatSum(const std::string& name, double count) {
addStatSum(name.c_str(), count);
}
static void addStatPerf(const string& name, double value) {
addStat(V3Statistic{"*", name, value, 6, true, true});
}

View File

@ -160,12 +160,7 @@ class StatsReport final {
public:
// METHODS
static void addStat(const V3Statistic& stat) {
// Avoid memory blow-up when called frequently with zero adds,
// e.g. from V3Const invoked on individual expressions.
if (stat.sumit() && stat.value() == 0.0) return;
s_allStats.push_back(stat);
}
static void addStat(const V3Statistic& stat) { s_allStats.push_back(stat); }
static double getStatSum(const string& name) {
// O(n^2) if called a lot; present assumption is only a small call count

View File

@ -327,10 +327,6 @@ string VString::replaceWord(const string& str, const string& from, const string&
return result;
}
bool VString::startsWith(const string& str, const string& prefix) {
return str.rfind(prefix, 0) == 0; // Faster than .find(_) == 0
}
bool VString::endsWith(const string& str, const string& suffix) {
if (str.length() < suffix.length()) return false;
return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0;

View File

@ -139,7 +139,12 @@ public:
// e.g.: replaceWords("one apple bad_apple", "apple", "banana") -> "one banana bad_apple"
static string replaceWord(const string& str, const string& from, const string& to);
// Predicate to check if 'str' starts with 'prefix'
static bool startsWith(const string& str, const string& prefix);
static bool startsWith(const string& str, const char* prefixp) {
return !str.rfind(prefixp, 0); // Faster than .find(_) == 0
}
static bool startsWith(const string& str, const string& prefix) {
return !str.rfind(prefix, 0); // Faster than .find(_) == 0
}
// Predicate to check if 'str' ends with 'suffix'
static bool endsWith(const string& str, const string& suffix);
// Return proper article (a/an) for a word. May be inaccurate for some special words