mirror of
https://github.com/verilator/verilator.git
synced 2026-09-02 10:48:27 +02:00
Add predicted stack overflow warning (#4799).
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: Estimate stack size to run the AST subtree.
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// Copyright 2003-2024 by Wilson Snyder. This program is free software; you
|
||||
// can redistribute it and/or modify it under the terms of either the GNU
|
||||
// Lesser General Public License Version 3 or the Perl Artistic License
|
||||
// Version 2.0.
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
||||
|
||||
#include "V3StackCount.h"
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
/// Estimate the stack byte size for executing all logic within and below a
|
||||
/// given AST node. This is very rough, only for warning when stack is too
|
||||
/// small.
|
||||
|
||||
class StackCountVisitor final : public VNVisitorConst {
|
||||
// NODE STATE
|
||||
// AstNode::user2() -> int. Path cost + 1,
|
||||
const VNUser2InUse m_inuser2;
|
||||
|
||||
// MEMBERS
|
||||
uint64_t m_stackSize = 0; // Running count of instructions
|
||||
bool m_tracingCall = false; // Iterating into a CCall to a CFunc
|
||||
bool m_ignoreRemaining = false; // Ignore remaining statements in the block
|
||||
bool m_inCFunc = false; // Inside function
|
||||
|
||||
// TYPES
|
||||
// Little class to cleanly call startVisitBase/endVisitBase
|
||||
class VisitBase final {
|
||||
// MEMBERS
|
||||
uint32_t m_savedCount;
|
||||
AstNode* const m_nodep;
|
||||
StackCountVisitor* const m_visitor;
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
VisitBase(StackCountVisitor* visitor, AstNode* nodep)
|
||||
: m_nodep{nodep}
|
||||
, m_visitor{visitor} {
|
||||
m_savedCount = m_visitor->startVisitBase(nodep);
|
||||
}
|
||||
~VisitBase() { m_visitor->endVisitBase(m_savedCount, m_nodep); }
|
||||
|
||||
private:
|
||||
VL_UNCOPYABLE(VisitBase);
|
||||
};
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
StackCountVisitor(AstNode* nodep) { iterateConstNull(nodep); }
|
||||
~StackCountVisitor() override = default;
|
||||
|
||||
// METHODS
|
||||
uint32_t stackSize() const { return m_stackSize; }
|
||||
|
||||
private:
|
||||
void reset() {
|
||||
m_stackSize = 0;
|
||||
m_ignoreRemaining = false;
|
||||
}
|
||||
uint32_t startVisitBase(AstNode* nodep) {
|
||||
UASSERT_OBJ(!m_ignoreRemaining, nodep, "Should not reach here if ignoring");
|
||||
|
||||
// Save the count, and add it back in during ~VisitBase This allows
|
||||
// debug prints to show local cost of each subtree, so we can see a
|
||||
// hierarchical view of the cost when in debug mode.
|
||||
const uint32_t savedCount = m_stackSize;
|
||||
m_stackSize = 0;
|
||||
return savedCount;
|
||||
}
|
||||
void endVisitBase(uint32_t savedCount, AstNode* nodep) {
|
||||
UINFO(8, "cost " << std::setw(6) << std::left << m_stackSize << " " << nodep << endl);
|
||||
if (!m_ignoreRemaining) m_stackSize += savedCount;
|
||||
}
|
||||
|
||||
// VISITORS
|
||||
void visit(AstNodeIf* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
const VisitBase vb{this, nodep};
|
||||
iterateAndNextConstNull(nodep->condp());
|
||||
const uint32_t savedCount = m_stackSize;
|
||||
|
||||
UINFO(8, "thensp:\n");
|
||||
reset();
|
||||
iterateAndNextConstNull(nodep->thensp());
|
||||
uint32_t ifCount = m_stackSize;
|
||||
if (nodep->branchPred().unlikely()) ifCount = 0;
|
||||
|
||||
UINFO(8, "elsesp:\n");
|
||||
reset();
|
||||
iterateAndNextConstNull(nodep->elsesp());
|
||||
uint32_t elseCount = m_stackSize;
|
||||
if (nodep->branchPred().likely()) elseCount = 0;
|
||||
|
||||
reset();
|
||||
if (ifCount >= elseCount) {
|
||||
m_stackSize = savedCount + ifCount;
|
||||
if (nodep->elsesp()) nodep->elsesp()->user2(0); // Don't dump it
|
||||
} else {
|
||||
m_stackSize = savedCount + elseCount;
|
||||
if (nodep->thensp()) nodep->thensp()->user2(0); // Don't dump it
|
||||
}
|
||||
}
|
||||
void visit(AstNodeCond* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
// Just like if/else above, the ternary operator only evaluates
|
||||
// one of the two expressions, so only count the max.
|
||||
const VisitBase vb{this, nodep};
|
||||
iterateAndNextConstNull(nodep->condp());
|
||||
const uint32_t savedCount = m_stackSize;
|
||||
|
||||
UINFO(8, "?\n");
|
||||
reset();
|
||||
iterateAndNextConstNull(nodep->thenp());
|
||||
const uint32_t ifCount = m_stackSize;
|
||||
|
||||
UINFO(8, ":\n");
|
||||
reset();
|
||||
iterateAndNextConstNull(nodep->elsep());
|
||||
const uint32_t elseCount = m_stackSize;
|
||||
|
||||
reset();
|
||||
if (ifCount >= elseCount) {
|
||||
m_stackSize = savedCount + ifCount;
|
||||
if (nodep->elsep()) nodep->elsep()->user2(0); // Don't dump it
|
||||
} else {
|
||||
m_stackSize = savedCount + elseCount;
|
||||
if (nodep->thenp()) nodep->thenp()->user2(0); // Don't dump it
|
||||
}
|
||||
}
|
||||
void visit(AstFork* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
const VisitBase vb{this, nodep};
|
||||
uint32_t totalCount = m_stackSize;
|
||||
VL_RESTORER(m_ignoreRemaining);
|
||||
// Sum counts in each statement
|
||||
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
reset();
|
||||
iterateConst(stmtp);
|
||||
totalCount += m_stackSize;
|
||||
}
|
||||
m_stackSize = totalCount;
|
||||
}
|
||||
void visit(AstNodeCCall* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
const VisitBase vb{this, nodep};
|
||||
iterateChildrenConst(nodep);
|
||||
m_tracingCall = true;
|
||||
iterateConst(nodep->funcp());
|
||||
UASSERT_OBJ(!m_tracingCall, nodep, "visit(AstCFunc) should have cleared m_tracingCall.");
|
||||
}
|
||||
void visit(AstCFunc* nodep) override {
|
||||
// Don't count a CFunc other than by tracing a call or counting it
|
||||
// from the root
|
||||
if (!m_tracingCall && !nodep->entryPoint()) return;
|
||||
m_tracingCall = false;
|
||||
if (nodep->recursive()) return;
|
||||
if (!nodep->user2()) { // Short circuit
|
||||
VL_RESTORER(m_ignoreRemaining);
|
||||
VL_RESTORER(m_stackSize);
|
||||
VL_RESTORER(m_inCFunc);
|
||||
m_tracingCall = false;
|
||||
m_stackSize = 0;
|
||||
m_inCFunc = true;
|
||||
const VisitBase vb{this, nodep};
|
||||
iterateChildrenConst(nodep);
|
||||
nodep->user2(m_stackSize + 1);
|
||||
}
|
||||
m_stackSize += nodep->user2() - 1;
|
||||
m_tracingCall = false;
|
||||
}
|
||||
void visit(AstVar* nodep) override {
|
||||
if (!m_inCFunc) return;
|
||||
m_stackSize += nodep->isRef() ? sizeof(void*) : nodep->dtypep()->widthTotalBytes();
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
|
||||
void visit(AstNodeExpr* nodep) override {} // Short-circuit
|
||||
void visit(AstNode* nodep) override {
|
||||
if (m_ignoreRemaining) return;
|
||||
const VisitBase vb{this, nodep};
|
||||
iterateChildrenConst(nodep);
|
||||
}
|
||||
|
||||
VL_UNCOPYABLE(StackCountVisitor);
|
||||
};
|
||||
|
||||
uint64_t V3StackCount::count(AstNode* nodep) {
|
||||
const StackCountVisitor visitor{nodep};
|
||||
return visitor.stackSize();
|
||||
}
|
||||
Reference in New Issue
Block a user