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 T_Arg_NonConst = typename std::remove_const<T_Arg>::type;
using Node = ConstCorrectAstNode<T_Arg>; 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 // 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 // Traversal stack. Because we often iterate over small trees, use an in-line initial stack
const auto grow = [&](size_t size) { size_t stackSize = InitialStackSize;
const ptrdiff_t occupancy = topp - basep; Node* inLineStack[InitialStackSize]; // In-line initial stack storage
stack.resize(size); std::unique_ptr<Node*[]> outOfLineStackp{nullptr}; // In case we need more, we will allocate
basep = stack.data() + prefetchDistance; Node** basep = inLineStack + PrefetchDistance; // Pointer to base of stack
topp = basep + occupancy; Node** topp = basep; // Pointer to top of stack
limp = basep + size - 5; // We push max 5 items per iteration Node** limp = inLineStack + stackSize - MaxPushesPerIteration; // Pointer to grow limit
};
// Initial stack size
grow(32);
// We want some non-null pointers at the beginning. These will be prefetched, but not // 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. // 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 // Visit given node, enqueue children for traversal
const auto visit = [&](Node* currp) { const auto visit = [&](Node* currp) {
@ -1343,10 +1337,23 @@ void AstNode::foreachImpl(ConstCorrectAstNode<T_Arg>* nodep, const T_Callable& f
Node* const headp = *--topp; Node* const headp = *--topp;
// Prefetch in case we are ascending the tree // 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 // 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 // Enqueue the next node
if (headp->nextp()) *topp++ = headp->nextp(); 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 T_Arg_NonConst = typename std::remove_const<T_Arg>::type;
using Node = ConstCorrectAstNode<T_Arg>; 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 // 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 // Traversal stack. Because we often iterate over small trees, use an in-line initial stack
const auto grow = [&](size_t size) { size_t stackSize = InitialStackSize;
const ptrdiff_t occupancy = topp - basep; Node* inLineStack[InitialStackSize]; // In-line initial stack storage
stack.resize(size); std::unique_ptr<Node*[]> outOfLineStackp{nullptr}; // In case we need more, we will allocate
basep = stack.data() + prefetchDistance; Node** basep = inLineStack + PrefetchDistance; // Pointer to base of stack
topp = basep + occupancy; Node** topp = basep; // Pointer to top of stack
limp = basep + size - 5; // We push max 5 items per iteration Node** limp = inLineStack + stackSize - MaxPushesPerIteration; // Pointer to grow limit
};
// Initial stack size
grow(32);
// We want some non-null pointers at the beginning. These will be prefetched, but not // 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. // 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. // Visit given node, enqueue children for traversal, return true if result determined.
const auto visit = [&](Node* currp) { const auto visit = [&](Node* currp) {
@ -1418,10 +1419,23 @@ bool AstNode::predicateImpl(ConstCorrectAstNode<T_Arg>* nodep, const T_Callable&
Node* const headp = *--topp; Node* const headp = *--topp;
// Prefetch in case we are ascending the tree // 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 // 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 // Enqueue the next node
if (headp->nextp()) *topp++ = headp->nextp(); 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); 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) AstVarRef::AstVarRef(FileLine* fl, AstVar* varp, const VAccess& access)
: ASTGEN_SUPER_VarRef(fl, varp, access) {} : ASTGEN_SUPER_VarRef(fl, varp, access) {}
AstVarRef::AstVarRef(FileLine* fl, AstNodeModule* pkgp, AstVar* varp, const VAccess& access) AstVarRef::AstVarRef(FileLine* fl, AstNodeModule* pkgp, AstVar* varp, const VAccess& access)
@ -190,7 +195,7 @@ bool AstVarRef::sameNode(const AstVarRef* samep) const {
} else { } else {
return (selfPointer() == samep->selfPointer() return (selfPointer() == samep->selfPointer()
&& classOrPackagep() == samep->classOrPackagep() && access() == samep->access() && 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 { bool AstVarRef::sameNoLvalue(const AstVarRef* samep) const {
@ -200,7 +205,7 @@ bool AstVarRef::sameNoLvalue(const AstVarRef* samep) const {
return (selfPointer() == samep->selfPointer() return (selfPointer() == samep->selfPointer()
&& classOrPackagep() == samep->classOrPackagep() && classOrPackagep() == samep->classOrPackagep()
&& (!selfPointer().isEmpty() || !samep->selfPointer().isEmpty()) && (!selfPointer().isEmpty() || !samep->selfPointer().isEmpty())
&& varp()->name() == samep->varp()->name()); && varp()->sameNode(samep->varp()));
} }
} }

View File

@ -1704,6 +1704,7 @@ public:
VSigning numeric); VSigning numeric);
AstBasicDType* findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd, const VNumRange& range, AstBasicDType* findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd, const VNumRange& range,
int widthMin, VSigning numeric); int widthMin, VSigning numeric);
AstBasicDType* findCreateSameDType(AstBasicDType& node);
AstBasicDType* findInsertSameDType(AstBasicDType* nodep); AstBasicDType* findInsertSameDType(AstBasicDType* nodep);
AstConstraintRefDType* findConstraintRefDType(FileLine* fl); AstConstraintRefDType* findConstraintRefDType(FileLine* fl);
AstEmptyQueueDType* findEmptyQueueDType(FileLine* fl); AstEmptyQueueDType* findEmptyQueueDType(FileLine* fl);
@ -2003,7 +2004,7 @@ public:
ASTGEN_MEMBERS_AstVar; ASTGEN_MEMBERS_AstVar;
void dump(std::ostream& str) const override; void dump(std::ostream& str) const override;
void dumpJson(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 string name() const override VL_MT_STABLE { return m_name; } // * = Var name
bool hasDType() const override VL_MT_SAFE { return true; } bool hasDType() const override VL_MT_SAFE { return true; }
bool maybePointedTo() 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) { AstBasicDType* AstTypeTable::findBasicDType(FileLine* fl, VBasicDTypeKwd kwd) {
if (m_basicps[kwd]) return m_basicps[kwd]; // 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
AstBasicDType* const new1p = new AstBasicDType{fl, kwd}; if (!m_basicps[kwd]) {
// Because the detailed map doesn't update this map, AstBasicDType basic{fl, kwd};
// check the detailed map for this same node m_basicps[kwd] = findCreateSameDType(basic);
// 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);
} }
// return m_basicps[kwd];
m_basicps[kwd] = newp;
return newp;
} }
AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd, int width, AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd, int width,
int widthMin, VSigning numeric) { int widthMin, VSigning numeric) {
AstBasicDType* const new1p = new AstBasicDType{fl, kwd, numeric, width, widthMin}; AstBasicDType basic{fl, kwd, numeric, width, widthMin};
AstBasicDType* const newp = findInsertSameDType(new1p); return findCreateSameDType(basic);
if (newp != new1p) {
VL_DO_DANGLING(new1p->deleteTree(), new1p);
} else {
addTypesp(newp);
}
return newp;
} }
AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd, AstBasicDType* AstTypeTable::findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd,
const VNumRange& range, int widthMin, const VNumRange& range, int widthMin,
VSigning numeric) { VSigning numeric) {
AstBasicDType* const new1p = new AstBasicDType{fl, kwd, numeric, range, widthMin}; AstBasicDType basic{fl, kwd, numeric, range, widthMin};
AstBasicDType* const newp = findInsertSameDType(new1p); return findCreateSameDType(basic);
if (newp != new1p) { }
VL_DO_DANGLING(new1p->deleteTree(), new1p);
} else { AstBasicDType* AstTypeTable::findCreateSameDType(AstBasicDType& node) {
addTypesp(newp); 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 // cppcheck-suppress duplInheritedMember
@ -2785,10 +2777,6 @@ void AstVar::dumpJson(std::ostream& str) const {
dumpJsonBoolFunc(str, ignoreSchedWrite); dumpJsonBoolFunc(str, ignoreSchedWrite);
dumpJsonGen(str); 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 { void AstScope::dump(std::ostream& str) const {
this->AstNode::dump(str); this->AstNode::dump(str);
str << " [abovep=" << nodeAddr(aboveScopep()) << "]"; 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 // 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, inline DfgSel*& getEntry(CacheSel& cache, const DfgDataType& dtype, DfgVertex* src0p,
uint32_t lsb) { uint32_t lsb) {
return cache const KeySel key{src0p, lsb, dtype.size()};
.emplace(std::piecewise_construct, // return cache[key];
std::forward_as_tuple(src0p, lsb, dtype.size()), //
std::forward_as_tuple(nullptr))
.first->second;
} }
inline DfgVertexUnary*& getEntry(CacheUnary& cache, const DfgDataType&, DfgVertex* src0p) { inline DfgVertexUnary*& getEntry(CacheUnary& cache, const DfgDataType&, DfgVertex* src0p) {
return cache const KeyUnary key{src0p};
.emplace(std::piecewise_construct, // return cache[key];
std::forward_as_tuple(src0p), //
std::forward_as_tuple(nullptr))
.first->second;
} }
inline DfgVertexBinary*& getEntry(CacheBinary& cache, const DfgDataType&, DfgVertex* src0p, inline DfgVertexBinary*& getEntry(CacheBinary& cache, const DfgDataType&, DfgVertex* src0p,
DfgVertex* src1p) { DfgVertex* src1p) {
return cache const KeyBinary key{src0p, src1p};
.emplace(std::piecewise_construct, // return cache[key];
std::forward_as_tuple(src0p, src1p), //
std::forward_as_tuple(nullptr))
.first->second;
} }
inline DfgVertexTernary*& getEntry(CacheTernary& cache, const DfgDataType&, DfgVertex* src0p, inline DfgVertexTernary*& getEntry(CacheTernary& cache, const DfgDataType&, DfgVertex* src0p,
DfgVertex* src1p, DfgVertex* src2p) { DfgVertex* src1p, DfgVertex* src2p) {
return cache const KeyTernary key{src0p, src1p, src2p};
.emplace(std::piecewise_construct, // return cache[key];
std::forward_as_tuple(src0p, src1p, src2p), //
std::forward_as_tuple(nullptr))
.first->second;
} }
// These return a reference to the mapped entry, inserting a nullptr if not yet exists // 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) { 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) { 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) { 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, inline CacheTernary::iterator find(CacheTernary& cache, DfgVertex* src0p, DfgVertex* src1p,
DfgVertex* src2p) { 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 // These set the operands of a new vertex

View File

@ -137,18 +137,18 @@ public:
// Returns a Packed type of the given width // Returns a Packed type of the given width
static const DfgDataType& packed(uint32_t width) { static const DfgDataType& packed(uint32_t width) {
// Find or create the right sized packed type // Find or create the right sized packed type
const auto pair = s_packedTypes.emplace(width, nullptr); const DfgDataType*& entryr = s_packedTypes[width];
if (pair.second) pair.first->second = new DfgDataType{width}; if (!entryr) entryr = new DfgDataType{width};
return *pair.first->second; return *entryr;
} }
// Returns an Array type of the given size with the given elements // Returns an Array type of the given size with the given elements
static const DfgDataType& array(const DfgDataType& elemType, uint32_t size) { static const DfgDataType& array(const DfgDataType& elemType, uint32_t size) {
UASSERT(elemType.isPacked(), "Cannot create multi-dimensional arrays yet"); UASSERT(elemType.isPacked(), "Cannot create multi-dimensional arrays yet");
// Find or create the right sized array type with this as elements // Find or create the right sized array type with this as elements
const auto pair = elemType.m_arrayTypes.emplace(size, nullptr); const DfgDataType*& entryr = elemType.m_arrayTypes[size];
if (pair.second) pair.first->second = new DfgDataType{size, elemType}; if (!entryr) entryr = new DfgDataType{size, elemType};
return *pair.first->second; return *entryr;
} }
// Returns the singleton Null type // 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. // Indent the specified number of spaces.
if (num <= 0) return std::string{}; return s_indentSpaces[std::max(0, std::min(num, MAXSPACE))];
return std::string(std::min<size_t>(num, MAXSPACE), ' ');
} }
bool V3OutFormatter::tokenMatch(const char* cp, const char* cmp) { bool V3OutFormatter::tokenMatch(const char* cp, const char* cmp) {

View File

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

View File

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

View File

@ -465,22 +465,14 @@ void V3Graph::subtreeLoops(V3EdgeFuncP edgeFuncp, V3GraphVertex* vertexp, V3Grap
//###################################################################### //######################################################################
// Algorithms - sorting // 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() { void V3Graph::sortVertices() {
// Sort list of vertices by rank, then fanout // Sort list of vertices by rank, then fanout
std::vector<V3GraphVertex*> vertexps; std::vector<V3GraphVertex*> vertexps;
for (V3GraphVertex& vertex : m_vertices) vertexps.push_back(&vertex); 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 // Re-insert in sorted order
for (V3GraphVertex* const vertexp : vertexps) { for (V3GraphVertex* const vertexp : vertexps) {
m_vertices.unlink(vertexp); m_vertices.unlink(vertexp);
@ -495,7 +487,10 @@ void V3Graph::sortEdges() {
// Make a vector // Make a vector
for (V3GraphEdge& edge : vertex.outEdges()) edges.push_back(&edge); for (V3GraphEdge& edge : vertex.outEdges()) edges.push_back(&edge);
// Sort // 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 // Relink edges in specified order
for (V3GraphEdge* const edgep : edges) edgep->relinkFromp(&vertex); for (V3GraphEdge* const edgep : edges) edgep->relinkFromp(&vertex);
// Prep for next // Prep for next

View File

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

View File

@ -206,7 +206,7 @@ class LocalizeVisitor final : public VNVisitor {
AstVarScope* const varScopep = nodep->varScopep(); AstVarScope* const varScopep = nodep->varScopep();
// Remember this function accesses this VarScope (we always need this as we might optimize // 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') // 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) // Remember the reference so we can fix it up later (we always need this as well)
m_references(m_cfuncp).emplace(varScopep, nodep); m_references(m_cfuncp).emplace(varScopep, nodep);

View File

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

View File

@ -169,11 +169,6 @@ public:
//###################################################################### //######################################################################
// Top Stats class // 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) { void V3Stats::statsStageAll(AstNetlist* nodep, const std::string& stage, bool fastOnly) {
StatsVisitor{nodep, stage, fastOnly}; StatsVisitor{nodep, stage, fastOnly};
} }

View File

@ -90,7 +90,7 @@ public:
} }
// CONSTRUCTORS // CONSTRUCTORS
V3Statistic(const string& stage, const string& name, double value, unsigned precision, 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_name{name}
, m_value{value} , m_value{value}
, m_precision{precision} , m_precision{precision}
@ -121,13 +121,22 @@ public:
static void addStat(const V3Statistic&); static void addStat(const V3Statistic&);
static void addStat(const string& stage, const string& name, double value, static void addStat(const string& stage, const string& name, double value,
unsigned precision = 0) { 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) { 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_ // 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) { static void addStatPerf(const string& name, double value) {
addStat(V3Statistic{"*", name, value, 6, true, true}); addStat(V3Statistic{"*", name, value, 6, true, true});
} }

View File

@ -160,12 +160,7 @@ class StatsReport final {
public: public:
// METHODS // METHODS
static void addStat(const V3Statistic& stat) { static void addStat(const V3Statistic& stat) { s_allStats.push_back(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 double getStatSum(const string& name) { static double getStatSum(const string& name) {
// O(n^2) if called a lot; present assumption is only a small call count // 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; 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) { bool VString::endsWith(const string& str, const string& suffix) {
if (str.length() < suffix.length()) return false; if (str.length() < suffix.length()) return false;
return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0; 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" // 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); static string replaceWord(const string& str, const string& from, const string& to);
// Predicate to check if 'str' starts with 'prefix' // 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' // Predicate to check if 'str' ends with 'suffix'
static bool endsWith(const string& str, const string& 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 // Return proper article (a/an) for a word. May be inaccurate for some special words