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:
parent
435e1149d5
commit
e9c48cd1ce
98
src/V3Ast.h
98
src/V3Ast.h
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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()) << "]";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
114
src/V3Life.cpp
114
src/V3Life.cpp
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue