mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 18:13:50 +02:00
The loops modelling the SystemVerilog scheduling regions are no longer generated. They now live in 'VerilatedEvalLoop' in the runtime library. The generated model holds one as a member, passing itself to it, and exposes each evaluation entry point to it as a pure virtual method on VerilatedModel. The model's 'eval' and 'eval_step' remain the top level entry points, and are backward compatible. V3Sched no longer emits '_eval' or '_eval_settle', etc.. Instead every evaluation entry point called from the runtime is enumerated by 'VEval', Scheduling creates all entry points, for all scheduling regions, even if they are empty, and the runtime eval loop calls everything unconditionally. If regions are empty, this is simply a call to an empty function. This will hurt performance on very small models, but should not be noticeable on anything meaningful, so it is likely best to keep to reduce complexity. A scheduling entry points evaluate a single iteration and returns whether it did any work, they are effectively the previous `_eval_phase_*` functions.
498 lines
21 KiB
C++
498 lines
21 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Inline small CFuncs into their callers
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// 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-FileCopyrightText: 2003-2026 Wilson Snyder
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
// V3InlineCFuncs's Transformations:
|
|
//
|
|
// Build a bipartite call graph containing function and call site vertices,
|
|
// then iterate functions leaf to root, inlining if size heuristics are met.
|
|
// Finally, remove unused functions.
|
|
//
|
|
// Two tunables control inlining:
|
|
// --inline-cfuncs <n> : Inline if size <= n
|
|
// --inline-cfuncs-product <n> : Also inline if size * call_count <= n
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
#include "V3InlineCFuncs.h"
|
|
|
|
#include "V3AstUserAllocator.h"
|
|
#include "V3ExecGraph.h"
|
|
#include "V3Graph.h"
|
|
#include "V3Stats.h"
|
|
|
|
#include <vector>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
//######################################################################
|
|
// Bipartite call graph containing function and call site vertices
|
|
|
|
class InlineCFuncsFunctionVertex;
|
|
class InlineCFuncsCallSiteVertex;
|
|
|
|
class InlineCFuncsCallGraph final : public V3Graph {
|
|
public:
|
|
InlineCFuncsCallGraph()
|
|
: V3Graph{} {}
|
|
~InlineCFuncsCallGraph() override = default;
|
|
|
|
void addEdge(InlineCFuncsFunctionVertex& from, InlineCFuncsCallSiteVertex& top);
|
|
void addEdge(InlineCFuncsCallSiteVertex& from, InlineCFuncsFunctionVertex& top);
|
|
};
|
|
|
|
class EitherVertex VL_NOT_FINAL : public V3GraphVertex {
|
|
VL_RTTI_IMPL(EitherVertex, V3GraphVertex)
|
|
protected:
|
|
explicit EitherVertex(InlineCFuncsCallGraph& graph)
|
|
: V3GraphVertex{&graph} {}
|
|
};
|
|
|
|
class InlineCFuncsFunctionVertex final : public EitherVertex {
|
|
VL_RTTI_IMPL(InlineCFuncsFunctionVertex, EitherVertex)
|
|
AstCFunc* const m_cfuncp; // The function
|
|
const char* m_noInlineWyp = nullptr; // First reason the function should not be inlined
|
|
const char* m_keepWyp = nullptr; // Why the function should not be removed
|
|
size_t m_size = 0; // The size of the function
|
|
|
|
public:
|
|
InlineCFuncsFunctionVertex(InlineCFuncsCallGraph& graph, AstCFunc* cfuncp)
|
|
: EitherVertex{graph}
|
|
, m_cfuncp{cfuncp} {}
|
|
~InlineCFuncsFunctionVertex() override = default;
|
|
|
|
// ACCESSORS
|
|
AstCFunc* cfuncp() const { return m_cfuncp; }
|
|
size_t size() const { return m_size; }
|
|
void sizeInc(size_t value = 1) { m_size += value; }
|
|
bool noInline() const { return m_noInlineWyp; }
|
|
void setNoInline(const char* whyp) {
|
|
if (!m_noInlineWyp) m_noInlineWyp = whyp;
|
|
}
|
|
bool keep() const { return m_keepWyp; }
|
|
void setKeep(const char* whyp) {
|
|
if (!m_keepWyp) m_keepWyp = whyp;
|
|
}
|
|
std::string dotColor() const override {
|
|
return m_noInlineWyp ? "red" : m_keepWyp ? "orange" : "black";
|
|
}
|
|
|
|
// debug
|
|
FileLine* fileline() const override { return m_cfuncp->fileline(); }
|
|
std::string dotShape() const override { return "box"; }
|
|
std::string name() const override VL_MT_STABLE {
|
|
std::string str = cvtToHex(m_cfuncp);
|
|
str += "\n" + m_cfuncp->name();
|
|
str += "\nsize: " + std::to_string(m_size);
|
|
if (m_noInlineWyp) str += "\nNoInline: "s + m_noInlineWyp;
|
|
if (m_keepWyp) str += "\nKeep: "s + m_keepWyp;
|
|
return str;
|
|
}
|
|
};
|
|
|
|
class InlineCFuncsCallSiteVertex final : public EitherVertex {
|
|
VL_RTTI_IMPL(InlineCFuncsCallSiteVertex, EitherVertex)
|
|
AstCCall* const m_callp; // The call site
|
|
const char* m_noInlineWyp = nullptr; // First reason the function should not be inlined
|
|
|
|
public:
|
|
InlineCFuncsCallSiteVertex(InlineCFuncsCallGraph& graph, AstCCall* callp)
|
|
: EitherVertex{graph}
|
|
, m_callp{callp} {}
|
|
~InlineCFuncsCallSiteVertex() override = default;
|
|
|
|
// ACCESSORS
|
|
AstCCall* callp() const { return m_callp; }
|
|
bool noInline() const { return m_noInlineWyp; }
|
|
void setNoInline(const char* whyp) {
|
|
if (!m_noInlineWyp) m_noInlineWyp = whyp;
|
|
}
|
|
|
|
// debug
|
|
FileLine* fileline() const override { return m_callp->fileline(); }
|
|
std::string dotColor() const override { return m_noInlineWyp ? "red" : "black"; }
|
|
std::string dotShape() const override { return "ellipse"; }
|
|
std::string name() const override VL_MT_STABLE {
|
|
std::string str = cvtToHex(m_callp);
|
|
if (m_noInlineWyp) str += "\nNoInline: "s + m_noInlineWyp;
|
|
return str;
|
|
}
|
|
};
|
|
|
|
void InlineCFuncsCallGraph::addEdge(InlineCFuncsFunctionVertex& caller,
|
|
InlineCFuncsCallSiteVertex& callsite) {
|
|
UASSERT_OBJ(callsite.inEmpty(), &callsite, "Call site should have at most one incoming edge");
|
|
new V3GraphEdge{this, &caller, &callsite, 1, true}; // Can cut caller -> callsite
|
|
}
|
|
void InlineCFuncsCallGraph::addEdge(InlineCFuncsCallSiteVertex& callsite,
|
|
InlineCFuncsFunctionVertex& callee) {
|
|
UASSERT_OBJ(callsite.outEmpty(), &callsite, "Call site should have at most one outgoing edge");
|
|
new V3GraphEdge{this, &callsite, &callee, 1, false};
|
|
}
|
|
|
|
//######################################################################
|
|
|
|
class InlineCFuncsVisitor final : public VNVisitor {
|
|
// NODE STATE
|
|
// AstCFunc::user1p() -> InlineCFuncsFunctionVertex*, the function vertex
|
|
// AstCCall::user1p() -> InlineCFuncsCallSiteVertex*, the call site vertex
|
|
// AstVar::user2p() -> AstVar*, the cloned inlined local variable
|
|
const VNUser1InUse m_user1InUse;
|
|
|
|
// STATE
|
|
InlineCFuncsCallGraph m_graph; // The call graph
|
|
VDouble0 m_statCallsInlined; // Number of calls inlined
|
|
VDouble0 m_statFuncsInlined; // Number of functions inlined at least once
|
|
VDouble0 m_statFuncsRemoved; // Number of fully-inlined functions removed
|
|
// Size threshold: always inline if size <= this
|
|
const size_t m_sizeThreshold = v3Global.opt.inlineCFuncs();
|
|
// Product threshold: inline if size * calls <= this
|
|
const size_t m_prodThreshold = v3Global.opt.inlineCFuncsProduct();
|
|
// Maximum size of caller to consider inlining into
|
|
const size_t m_maxSizeCFunc = []() -> size_t {
|
|
int maxCFunc = v3Global.opt.outputSplitCFuncs();
|
|
int maxFile = v3Global.opt.outputSplit();
|
|
if (maxCFunc <= 0) maxCFunc = std::numeric_limits<int>::max();
|
|
if (maxFile <= 0) maxFile = std::numeric_limits<int>::max();
|
|
return std::min(maxCFunc, maxFile);
|
|
}();
|
|
const size_t m_maxSizeTrace = []() -> size_t {
|
|
int maxTrace = v3Global.opt.outputSplitCTrace();
|
|
int maxFile = v3Global.opt.outputSplit();
|
|
if (maxTrace <= 0) maxTrace = std::numeric_limits<int>::max();
|
|
if (maxFile <= 0) maxFile = std::numeric_limits<int>::max();
|
|
return std::min(maxTrace, maxFile);
|
|
}();
|
|
InlineCFuncsFunctionVertex* m_cfuncVtxp = nullptr; // Vertex of currently iterated function
|
|
bool m_inExecGraph = false; // True while inside an AstExecGraph subtree
|
|
|
|
// METHODS
|
|
InlineCFuncsFunctionVertex* getInlineCFuncsFunctionVertexp(AstCFunc* cfuncp) {
|
|
if (!cfuncp->user1p()) cfuncp->user1p(new InlineCFuncsFunctionVertex{m_graph, cfuncp});
|
|
return cfuncp->user1u().to<InlineCFuncsFunctionVertex*>();
|
|
}
|
|
|
|
InlineCFuncsCallSiteVertex* getInlineCFuncsCallSiteVertexp(AstCCall* callp) {
|
|
if (!callp->user1p()) callp->user1p(new InlineCFuncsCallSiteVertex{m_graph, callp});
|
|
return callp->user1u().to<InlineCFuncsCallSiteVertex*>();
|
|
}
|
|
|
|
AstCLocalScope* inlineCall(AstCFunc* const callerp, //
|
|
AstCCall* const callp, //
|
|
AstCFunc* const calleep, //
|
|
const size_t seqNum) {
|
|
UINFO(6, "Inlining CFunc " << calleep->name() << " into " << callerp->name()
|
|
<< " at call site " << callp);
|
|
|
|
AstNodeStmt* const callSitep = VN_AS(callp->backp(), StmtExpr);
|
|
++m_statCallsInlined;
|
|
|
|
// Callee might be empty, just delete the call
|
|
if (!calleep->stmtsp()) {
|
|
VL_DO_DANGLING(pushDeletep(callSitep->unlinkFrBack()), callSitep);
|
|
return nullptr;
|
|
}
|
|
|
|
// Replace call site with a local scope
|
|
FileLine* const flp = callSitep->fileline();
|
|
AstCLocalScope* const lscopep = new AstCLocalScope{flp, nullptr};
|
|
callSitep->replaceWith(lscopep);
|
|
VL_DO_DANGLING(pushDeletep(callSitep), callSitep);
|
|
lscopep->addStmtsp(new AstComment{flp, "Inlined CFunc: " + calleep->name()});
|
|
|
|
// Although it's in a local scope, we still make names of cloned locals unique
|
|
const std::string varPrefix
|
|
= "__Vinline_" + std::to_string(seqNum) + "_" + calleep->name() + "_";
|
|
|
|
// AstVar::user2p() -> AstVar*, the cloned inlined local variable
|
|
const VNUser2InUse user2InUse;
|
|
|
|
// Clone local variables, add them to the local scope
|
|
for (AstVar* varp = calleep->varsp(); varp; varp = VN_AS(varp->nextp(), Var)) {
|
|
AstVar* const newVarp = varp->cloneTree(false);
|
|
newVarp->name(varPrefix + varp->name());
|
|
lscopep->addStmtsp(newVarp);
|
|
varp->user2p(newVarp);
|
|
}
|
|
|
|
// Clone the function body
|
|
AstNode* const bodyp = calleep->stmtsp()->cloneTree(true);
|
|
lscopep->addStmtsp(bodyp);
|
|
|
|
// Retarget local variable references to the cloned locals
|
|
// Rename locals defined in the body, TODO: there should be none after #6280
|
|
// Reset vertex pointers on calls
|
|
bodyp->foreachAndNext([&](AstNode* nodep) {
|
|
if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
|
|
if (AstVar* const varp = VN_AS(refp->varp()->user2p(), Var)) refp->varp(varp);
|
|
} else if (AstVar* const varp = VN_CAST(nodep, Var)) {
|
|
varp->name(varPrefix + varp->name());
|
|
} else if (AstCCall* const callp = VN_CAST(nodep, CCall)) {
|
|
callp->user1p(nullptr);
|
|
}
|
|
});
|
|
|
|
// Return the local scope
|
|
return lscopep;
|
|
}
|
|
|
|
void doInlining() {
|
|
// Need to gather vertices as we are changing the graph
|
|
std::vector<InlineCFuncsFunctionVertex*> m_fVtxps;
|
|
for (V3GraphVertex& vtx : m_graph.vertices()) {
|
|
if (InlineCFuncsFunctionVertex* const fVtxp = vtx.cast<InlineCFuncsFunctionVertex>()) {
|
|
m_fVtxps.emplace_back(fVtxp);
|
|
}
|
|
}
|
|
|
|
// Iterate functions leaf to root
|
|
for (InlineCFuncsFunctionVertex* const calleeVtxp : vlstd::reverse_view(m_fVtxps)) {
|
|
// Should we inline this function?
|
|
if (calleeVtxp->noInline()) continue; // Told not to
|
|
|
|
// Check size heuristics
|
|
const bool doIt = [&]() {
|
|
// Inline if small enough
|
|
if (calleeVtxp->size() <= m_sizeThreshold) return true;
|
|
// Inline if not too much bloat
|
|
const size_t nCalls = calleeVtxp->inEdges().size();
|
|
if (nCalls * calleeVtxp->size() <= m_prodThreshold) return true;
|
|
// Otherwise don't inline
|
|
return false;
|
|
}();
|
|
if (!doIt) continue;
|
|
|
|
// Ok, attempt to inline call sites
|
|
size_t nInlined = 0;
|
|
for (const V3GraphEdge* const edgep : calleeVtxp->inEdges().unlinkable()) {
|
|
InlineCFuncsCallSiteVertex* const callVtxp
|
|
= edgep->fromp()->as<InlineCFuncsCallSiteVertex>();
|
|
|
|
AstCFunc* const calleep = calleeVtxp->cfuncp();
|
|
AstCCall* const callp = callVtxp->callp();
|
|
UINFO(6, "Consider inlining " << calleep->name() << " at call site " << callp);
|
|
// Should we inline this call site?
|
|
if (callVtxp->noInline()) continue; // Told not to
|
|
if (callVtxp->inEmpty()) continue; // Don't know where it's called from
|
|
|
|
// Pick up the caller
|
|
UASSERT_OBJ(callVtxp->inSize1(), callVtxp->callp(),
|
|
"Expected exactly one input edge for call site");
|
|
InlineCFuncsFunctionVertex* const callerVtxp
|
|
= callVtxp->inEdges().frontp()->fromp()->as<InlineCFuncsFunctionVertex>();
|
|
AstCFunc* const callerp = callerVtxp->cfuncp();
|
|
|
|
// Don't make a function bigger than the limit
|
|
const size_t limit = callerp->isTrace() ? m_maxSizeTrace : m_maxSizeCFunc;
|
|
if (callerVtxp->size() + calleeVtxp->size() > limit) continue;
|
|
|
|
// Can't do it if it's in a different scope, self pointers differ
|
|
if (callerp->scopep() != calleep->scopep()) continue;
|
|
|
|
// Inline it
|
|
if (!nInlined) ++m_statFuncsInlined;
|
|
AstNode* const inlinedp = inlineCall(callerp, callp, calleep, nInlined++);
|
|
|
|
// Need to adjust the graph:
|
|
// 1. Delete inlined call site
|
|
VL_DO_DANGLING(callVtxp->unlinkDelete(&m_graph), callVtxp);
|
|
// 2. Add new inlined call sites - also increments size of caller
|
|
VL_RESTORER(m_cfuncVtxp);
|
|
m_cfuncVtxp = callerVtxp;
|
|
if (inlinedp) iterateChildrenConst(inlinedp);
|
|
}
|
|
}
|
|
}
|
|
|
|
void removeUnusedFuncs() {
|
|
// Iterate root to leaves
|
|
for (V3GraphVertex* const vtxp : m_graph.vertices().unlinkable()) {
|
|
InlineCFuncsFunctionVertex* const fVtxp = vtxp->cast<InlineCFuncsFunctionVertex>();
|
|
if (!fVtxp) continue;
|
|
// Keep if still called
|
|
if (!fVtxp->inEmpty()) continue;
|
|
// Keep for other reasons
|
|
if (fVtxp->keep()) continue;
|
|
|
|
AstCFunc* const funcp = fVtxp->cfuncp();
|
|
UINFO(6, "Removing unused CFunc " << funcp);
|
|
++m_statFuncsRemoved;
|
|
|
|
// Unlink all call sites
|
|
for (const V3GraphEdge* const edgep : vtxp->outEdges().unlinkable()) {
|
|
edgep->top()->unlinkEdges(&m_graph);
|
|
}
|
|
// Delete function vertex
|
|
vtxp->unlinkDelete(&m_graph);
|
|
// Delete the function
|
|
VL_DO_DANGLING(pushDeletep(funcp->unlinkFrBack()), funcp);
|
|
}
|
|
|
|
// Delete inlined/deleted call site vertices (for debugging only)
|
|
for (V3GraphVertex* const vtxp : m_graph.vertices().unlinkable()) {
|
|
InlineCFuncsCallSiteVertex* const cVtxp = vtxp->cast<InlineCFuncsCallSiteVertex>();
|
|
if (!cVtxp) continue;
|
|
if (!cVtxp->inEmpty()) continue;
|
|
if (!cVtxp->outEmpty()) continue;
|
|
vtxp->unlinkDelete(&m_graph);
|
|
}
|
|
}
|
|
|
|
// VISITORS
|
|
void visit(AstCFunc* nodep) override {
|
|
// Create the function vertex
|
|
InlineCFuncsFunctionVertex* const vtxp = getInlineCFuncsFunctionVertexp(nodep);
|
|
|
|
// Check if the function itself is not inlineable
|
|
if (nodep->rtnTypeVoid() != "void") vtxp->setNoInline("Not void");
|
|
if (nodep->dpiImportPrototype()) vtxp->setNoInline("DPI import prototype");
|
|
if (nodep->recursive()) vtxp->setNoInline("Recursive");
|
|
if (nodep->argsp()) vtxp->setNoInline("Has arguments");
|
|
if (nodep->isVirtual()) vtxp->setNoInline("Virtual method");
|
|
|
|
// Check if the function should not be removed
|
|
if (nodep->entryPoint()) vtxp->setKeep("Entry point");
|
|
if (nodep->dpiImportPrototype()) vtxp->setKeep("DPI import prototype");
|
|
if (nodep->dpiExportDispatcher()) vtxp->setKeep("DPI export implementation");
|
|
if (nodep->isVirtual()) vtxp->setKeep("Virtual method");
|
|
|
|
// Iterate children
|
|
VL_RESTORER(m_cfuncVtxp);
|
|
m_cfuncVtxp = vtxp;
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
|
|
// Inlineable calls
|
|
void visit(AstCCall* nodep) override {
|
|
if (m_cfuncVtxp) m_cfuncVtxp->sizeInc();
|
|
AstCFunc* const calleep = nodep->funcp();
|
|
|
|
// Create the call site vertex
|
|
InlineCFuncsCallSiteVertex* const vtxp = getInlineCFuncsCallSiteVertexp(nodep);
|
|
|
|
// Check if the call site is not inlineable
|
|
if (!VN_IS(nodep->backp(), StmtExpr)) vtxp->setNoInline("Not in statement position");
|
|
if (m_inExecGraph) vtxp->setNoInline("In ExecGraph");
|
|
if (calleep->isVirtual()) vtxp->setNoInline("Virtual method");
|
|
|
|
// Add caller/callee edges
|
|
if (m_cfuncVtxp) m_graph.addEdge(*m_cfuncVtxp, *vtxp);
|
|
m_graph.addEdge(*vtxp, *getInlineCFuncsFunctionVertexp(calleep));
|
|
|
|
// Iterate children
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
|
|
void visit(AstNodeCCall* nodep) override {
|
|
if (m_cfuncVtxp) m_cfuncVtxp->sizeInc();
|
|
getInlineCFuncsFunctionVertexp(nodep->funcp())->setKeep("Called elsewhere");
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
|
|
void visit(AstAddrOfCFunc* nodep) override {
|
|
if (m_cfuncVtxp) m_cfuncVtxp->sizeInc();
|
|
getInlineCFuncsFunctionVertexp(nodep->funcp())->setKeep("Referenced by AddressOfCFunc");
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
|
|
// Nodes preventing inlining
|
|
void visit(AstTraceDecl* nodep) override {
|
|
// Referenced by TraceInc
|
|
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains TraceDecl");
|
|
|
|
if (AstCCall* const callp = nodep->dtypeCallp()) {
|
|
getInlineCFuncsCallSiteVertexp(callp)->setNoInline("Referenced by TraceDecl");
|
|
}
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
void visit(AstExecGraph* nodep) override {
|
|
// AstExecGraph is not cloneable, so can't inline the containing function
|
|
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains ExecGraph");
|
|
// Also mark functions referenced in the dependency graph
|
|
for (const V3GraphVertex& vtx : nodep->depGraphp()->vertices()) {
|
|
getInlineCFuncsFunctionVertexp(vtx.as<ExecMTask>()->funcp())
|
|
->setKeep("MTask function");
|
|
}
|
|
VL_RESTORER(m_inExecGraph);
|
|
m_inExecGraph = true;
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
void visit(AstCStmt* nodep) override {
|
|
// Can reference anything in text
|
|
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CStmt");
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
void visit(AstCExpr* nodep) override {
|
|
// Can reference anything in text
|
|
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CExpr");
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
void visit(AstCStmtUser* nodep) override {
|
|
// Can reference anything in text
|
|
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CStmtUser");
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
void visit(AstCExprUser* nodep) override {
|
|
// Can reference anything in text
|
|
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CExprUser");
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
void visit(AstCReturn* nodep) override {
|
|
if (m_cfuncVtxp) m_cfuncVtxp->setNoInline("Contains CReturn");
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
|
|
// Base node
|
|
void visit(AstNode* nodep) override {
|
|
if (m_cfuncVtxp) m_cfuncVtxp->sizeInc();
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit InlineCFuncsVisitor(AstNetlist* nodep) {
|
|
// Phase 1: Build call graph
|
|
iterateConst(nodep);
|
|
// Make acyclic in case there is recursion
|
|
m_graph.acyclic(V3GraphEdge::followAlwaysTrue);
|
|
// Order vertices (any topological order is fine)
|
|
m_graph.order();
|
|
if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-graph");
|
|
// Phase 2: Inline calls
|
|
doInlining();
|
|
if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-inlined");
|
|
// Phase 3: Remove unused functions
|
|
removeUnusedFuncs();
|
|
if (dumpGraphLevel() >= 6) m_graph.dumpDotFilePrefixed("inlinecfuncs-kept");
|
|
}
|
|
~InlineCFuncsVisitor() override {
|
|
V3Stats::addStat("Optimizations, Inline CFuncs, calls inlined", m_statCallsInlined);
|
|
V3Stats::addStat("Optimizations, Inline CFuncs, functions inlined", m_statFuncsInlined);
|
|
V3Stats::addStat("Optimizations, Inline CFuncs, functions removed", m_statFuncsRemoved);
|
|
}
|
|
};
|
|
|
|
//######################################################################
|
|
// InlineCFuncs class functions
|
|
|
|
void V3InlineCFuncs::inlineAll(AstNetlist* nodep) {
|
|
UINFO(2, __FUNCTION__ << ":");
|
|
// Don't inline when profiling per-function (it would lose granularity)
|
|
if (v3Global.opt.profCFuncs()) return;
|
|
{ InlineCFuncsVisitor{nodep}; } // Destruct before checking
|
|
V3Global::dumpCheckGlobalTree("inlinecfuncs", 0, dumpTreeEitherLevel() >= 6);
|
|
}
|