verilator/src/V3StackCount.cpp
2024-01-17 19:48:07 -05:00

203 lines
6.9 KiB
C++

// -*- 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
explicit 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();
}