Skip to content

Commit

Permalink
Simplify and fix code stats
Browse files Browse the repository at this point in the history
V3Stats for "fast" code have bit-rotted a little and is causing some
problems with tests that rely on stats outputs. The problem is that not
all code is necessarily reachable from eval() any more (due to the
complexity of some the features added over the past few years), so it
might miss some things, as for measuring the "fast" code, it is trying
to trace the execution paths via calls, starting from eval(). It also
appears the fast code can also contain calls to slow code in some
circumstances.

To avoid all that, removed trying to trace dynamic execution, and simply
report the static node counts, which is enough for testing.

Similarly, the variable counts are somewhat dubious, as they don't
include all data types, or all instances of a module in some stages.
Removing these as they are not widely used nor dependable. More specific
stats can be added if required and can be well defined.
  • Loading branch information
gezalore committed Oct 21, 2023
1 parent 146cdc0 commit a441dc5
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 206 deletions.
288 changes: 83 additions & 205 deletions src/V3Stats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,246 +27,124 @@ VL_DEFINE_DEBUG_FUNCTIONS;
// Stats class functions

class StatsVisitor final : public VNVisitorConst {
private:
// NODE STATE/TYPES

using NameMap = std::map<const std::string, int>; // Number of times a name appears
struct Counters final {
// Nodes of given type
uint64_t m_statTypeCount[VNType::_ENUM_END];
// Nodes of given type with given type immediate child
uint64_t m_statAbove[VNType::_ENUM_END][VNType::_ENUM_END];
// Prediction of given type
uint64_t m_statPred[VBranchPred::_ENUM_END];
};

// STATE
const string m_stage; // Name of the stage we are scanning
/// m_fast = true: Counting only critical branch of fastpath
/// m_fast = false: Counting every node, ignoring structure of program
const bool m_fast;

const AstCFunc* m_cfuncp; // Current CFUNC
bool m_counting; // Currently counting
double m_instrs; // Current instr count (for determining branch direction)
bool m_tracingCall; // Iterating into a CCall to a CFunc

std::vector<VDouble0> m_statTypeCount; // Nodes of given type
VDouble0 m_statAbove[VNType::_ENUM_END][VNType::_ENUM_END]; // Nodes of given type
std::array<VDouble0, VBranchPred::_ENUM_END> m_statPred; // Nodes of given type
VDouble0 m_statInstr; // Instruction count
VDouble0 m_statInstrFast; // Instruction count, non-slow() eval functions only
std::vector<VDouble0> m_statVarWidths; // Variables of given width
std::vector<NameMap> m_statVarWidthNames; // Var names of given width
VDouble0 m_statVarArray; // Statistic tracking
VDouble0 m_statVarBytes; // Statistic tracking
VDouble0 m_statVarClock; // Statistic tracking
VDouble0 m_statVarScpBytes; // Statistic tracking
const bool m_fastOnly; // When true, consider only fast functions
const AstNodeExpr* m_parentExprp = nullptr; // Parent expression
Counters m_counters; // The actual counts we will display
Counters m_dumpster; // Alternate buffer to make discarding parts of the tree easier
Counters* m_accump; // The currently active accumulator
std::vector<uint64_t> m_statVarWidths; // Variables of given width
std::vector<std::map<const std::string, uint32_t>>
m_statVarWidthNames; // Var names of given width

// METHODS

void allNodes(AstNode* nodep) {
m_instrs += nodep->instrCount();
if (m_counting) {
++m_statTypeCount[nodep->type()];
if (nodep->firstAbovep()) { // Grab only those above, not those "back"
++m_statAbove[nodep->firstAbovep()->type()][nodep->type()];
}
m_statInstr += nodep->instrCount();
if (m_cfuncp && !m_cfuncp->slow()) m_statInstrFast += nodep->instrCount();
}
void countThenIterateChildren(AstNode* nodep) {
++m_accump->m_statTypeCount[nodep->type()];
iterateChildrenConst(nodep);
}

// VISITORS
void visit(AstNodeModule* nodep) override {
allNodes(nodep);
if (!m_fast) {
// Count all CFuncs below this module
iterateChildrenConst(nodep);
}
// Else we recursively trace fast CFuncs from the top _eval
// func, see visit(AstNetlist*)
}
void visit(AstVar* nodep) override {
allNodes(nodep);
iterateChildrenConst(nodep);
if (m_counting && nodep->dtypep()) {
if (nodep->isUsedClock()) ++m_statVarClock;
if (VN_IS(nodep->dtypeSkipRefp(), UnpackArrayDType)) {
++m_statVarArray;
} else {
m_statVarBytes += nodep->dtypeSkipRefp()->widthTotalBytes();
}
if (int(m_statVarWidths.size()) <= nodep->width()) {
if (nodep->dtypep()) {
if (m_statVarWidths.size() <= static_cast<size_t>(nodep->width())) {
m_statVarWidths.resize(nodep->width() + 5);
if (v3Global.opt.statsVars()) m_statVarWidthNames.resize(nodep->width() + 5);
if (v3Global.opt.statsVars()) { //
m_statVarWidthNames.resize(nodep->width() + 5);
}
}
++m_statVarWidths.at(nodep->width());
const string pn = nodep->prettyName();
if (v3Global.opt.statsVars()) {
NameMap& nameMapr = m_statVarWidthNames.at(nodep->width());
if (nameMapr.find(pn) != nameMapr.end()) {
nameMapr[pn]++;
} else {
nameMapr[pn] = 1;
}
++m_statVarWidthNames.at(nodep->width())[nodep->prettyName()];
}
}

countThenIterateChildren(nodep);
}
void visit(AstVarScope* nodep) override {
allNodes(nodep);
iterateChildrenConst(nodep);
if (m_counting) {
if (VN_IS(nodep->varp()->dtypeSkipRefp(), BasicDType)) {
m_statVarScpBytes += nodep->varp()->dtypeSkipRefp()->widthTotalBytes();
}
}

void visit(AstNodeExpr* nodep) override {
// Count expression combinations
if (m_parentExprp) ++m_accump->m_statAbove[m_parentExprp->type()][nodep->type()];
VL_RESTORER(m_parentExprp);
m_parentExprp = nodep;
countThenIterateChildren(nodep);
}

void visit(AstNodeIf* nodep) override {
UINFO(4, " IF i=" << m_instrs << " " << nodep << endl);
allNodes(nodep);
// Condition is part of cost allocated to PREVIOUS block
iterateAndNextConstNull(nodep->condp());
// Track prediction
if (m_counting) ++m_statPred[nodep->branchPred()];
if (!m_fast) {
// Count everything
iterateChildrenConst(nodep);
} else {
// See which path we want to take
// Need to do even if !m_counting because maybe determining upstream if/else
double ifInstrs = 0.0;
double elseInstrs = 0.0;
if (!nodep->branchPred().unlikely()) { // Check if
VL_RESTORER(m_instrs);
VL_RESTORER(m_counting);
{
m_counting = false;
m_instrs = 0.0;
iterateAndNextConstNull(nodep->thensp());
ifInstrs = m_instrs;
}
}
if (!nodep->branchPred().likely()) { // Check else
VL_RESTORER(m_instrs);
VL_RESTORER(m_counting);
{
m_counting = false;
m_instrs = 0.0;
iterateAndNextConstNull(nodep->elsesp());
elseInstrs = m_instrs;
}
}
// Now collect the stats
if (m_counting) {
if (ifInstrs >= elseInstrs) {
iterateAndNextConstNull(nodep->thensp());
} else {
iterateAndNextConstNull(nodep->elsesp());
}
}
}
++m_accump->m_statPred[nodep->branchPred()];
countThenIterateChildren(nodep);
}
// While's we assume evaluate once.
// void visit(AstWhile* nodep) override {

void visit(AstNodeCCall* nodep) override {
allNodes(nodep);
iterateChildrenConst(nodep);
if (m_fast && !nodep->funcp()->entryPoint()) {
// Enter the function and trace it
m_tracingCall = true;
iterateConst(nodep->funcp());
}
}
void visit(AstCFunc* nodep) override {
if (m_fast) {
if (!m_tracingCall && !nodep->entryPoint()) return;
m_tracingCall = false;
}
VL_RESTORER(m_cfuncp);
{
m_cfuncp = nodep;
allNodes(nodep);
iterateChildrenConst(nodep);
}
}
void visit(AstNode* nodep) override {
allNodes(nodep);
iterateChildrenConst(nodep);
}
void visit(AstNetlist* nodep) override {
if (m_fast && nodep->evalp()) {
m_instrs = 0;
m_counting = true;
iterateChildrenConst(nodep->evalp());
m_counting = false;
}
allNodes(nodep);
iterateChildrenConst(nodep);
VL_RESTORER(m_accump);
if (m_fastOnly && !nodep->slow()) m_accump = &m_counters;
countThenIterateChildren(nodep);
}

void visit(AstNode* nodep) override { countThenIterateChildren(nodep); }

public:
// CONSTRUCTORS
StatsVisitor(AstNetlist* nodep, const string& stage, bool fast)
: m_stage{stage}
, m_fast{fast} {
UINFO(9, "Starting stats, fast=" << fast << endl);
m_cfuncp = nullptr;
m_counting = !m_fast;
m_instrs = 0;
m_tracingCall = false;
// Initialize arrays
m_statTypeCount.resize(VNType::_ENUM_END);
// Process
StatsVisitor(AstNetlist* nodep, const std::string& stage, bool fastOnly)
: m_fastOnly{fastOnly}
, m_accump{fastOnly ? &m_dumpster : &m_counters} {
UINFO(9, "Starting stats, fastOnly=" << fastOnly << endl);
memset(&m_counters, 0, sizeof(m_counters));
memset(&m_dumpster, 0, sizeof(m_dumpster));

iterateConst(nodep);
}
~StatsVisitor() override {
// Done. Publish statistics
V3Stats::addStat(m_stage, "Instruction count, TOTAL", m_statInstr);
V3Stats::addStat(m_stage, "Instruction count, fast critical", m_statInstrFast);
// Vars
V3Stats::addStat(m_stage, "Vars, unpacked arrayed", m_statVarArray);
V3Stats::addStat(m_stage, "Vars, clock attribute", m_statVarClock);
V3Stats::addStat(m_stage, "Var space, non-arrays, bytes", m_statVarBytes);
if (m_statVarScpBytes != 0.0) {
V3Stats::addStat(m_stage, "Var space, scoped, bytes", m_statVarScpBytes);
}

// Shorthand
const auto addStat = [&](const std::string& name, double count) { //
V3Stats::addStat(stage, name, count);
};

// Variable widths
for (unsigned i = 0; i < m_statVarWidths.size(); i++) {
const double count{m_statVarWidths.at(i)};
if (count != 0.0) {
if (const uint64_t count = m_statVarWidths.at(i)) {
std::stringstream ss;
ss << "Vars, width " << std::setw(5) << std::dec << i;
const std::string prefix = ss.str();
if (v3Global.opt.statsVars()) {
const NameMap& nameMapr = m_statVarWidthNames.at(i);
for (const auto& itr : nameMapr) {
std::ostringstream os;
os << "Vars, width " << std::setw(5) << std::dec << i << " " << itr.first;
V3Stats::addStat(m_stage, os.str(), itr.second);
for (const auto& it : m_statVarWidthNames.at(i)) {
addStat(prefix + " " + it.first, it.second);
}
} else {
std::ostringstream os;
os << "Vars, width " << std::setw(5) << std::dec << i;
V3Stats::addStat(m_stage, os.str(), count);
addStat(prefix, count);
}
}
}

// Node types
for (int type = 0; type < VNType::_ENUM_END; type++) {
const double count{m_statTypeCount.at(type)};
if (count != 0.0) {
V3Stats::addStat(m_stage, std::string{"Node count, "} + VNType{type}.ascii(),
count);
const auto typeName = [](int type) { return std::string{VNType{type}.ascii()}; };
for (int t = 0; t < VNType::_ENUM_END; ++t) {
if (const uint64_t count = m_counters.m_statTypeCount[t]) {
addStat("Node count, " + typeName(t), count);
}
}
for (int type = 0; type < VNType::_ENUM_END; type++) {
for (int type2 = 0; type2 < VNType::_ENUM_END; type2++) {
const double count{m_statAbove[type][type2]};
if (count != 0.0) {
V3Stats::addStat(m_stage,
(std::string{"Node pairs, "} + VNType{type}.ascii() + "_"
+ VNType{type2}.ascii()),
count);

// Expression combinations
for (int t1 = 0; t1 < VNType::_ENUM_END; ++t1) {
for (int t2 = 0; t2 < VNType::_ENUM_END; ++t2) {
if (const uint64_t c = m_counters.m_statAbove[t1][t2]) {
addStat("Expr combination, " + typeName(t1) + " over " + typeName(t2), c);
}
}
}
// Branch pred
for (int type = 0; type < VBranchPred::_ENUM_END; type++) {
const double count{m_statPred[type]};
if (count != 0.0) {
V3Stats::addStat(m_stage,
(std::string{"Branch prediction, "} + VBranchPred{type}.ascii()),
count);

// Branch predictions
for (int t = 0; t < VBranchPred::_ENUM_END; ++t) {
if (const uint64_t c = m_counters.m_statPred[t]) {
addStat(std::string{"Branch prediction, "} + VBranchPred{t}.ascii(), c);
}
}
}
Expand All @@ -275,11 +153,11 @@ class StatsVisitor final : public VNVisitorConst {
//######################################################################
// Top Stats class

void V3Stats::statsStageAll(AstNetlist* nodep, const string& stage, bool fast) {
{ StatsVisitor{nodep, stage, fast}; }
void V3Stats::statsStageAll(AstNetlist* nodep, const std::string& stage, bool fastOnly) {
StatsVisitor{nodep, stage, fastOnly};
}

void V3Stats::statsFinalAll(AstNetlist* nodep) {
statsStageAll(nodep, "Final");
statsStageAll(nodep, "Final_Fast", true);
statsStageAll(nodep, "Final all");
statsStageAll(nodep, "Final fast", true);
}
2 changes: 1 addition & 1 deletion src/V3Stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class V3Stats final {
/// Called each stage
static void statsStage(const string& name);
/// Called by the top level to collect statistics
static void statsStageAll(AstNetlist* nodep, const string& stage, bool fast = false);
static void statsStageAll(AstNetlist* nodep, const string& stage, bool fastOnly = false);
static void statsFinalAll(AstNetlist* nodep);
/// Called by the top level to dump the statistics
static void statsReport();
Expand Down

0 comments on commit a441dc5

Please sign in to comment.