247 lines
8.9 KiB
C++
247 lines
8.9 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Control flow graph (CFG) builder
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Copyright 2003-2025 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
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Control flow graph (CFG) builder
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "config_build.h"
|
|
#include "verilatedos.h"
|
|
|
|
#include "V3Ast.h"
|
|
#include "V3Cfg.h"
|
|
#include "V3EmitV.h"
|
|
|
|
#include <deque>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
class CfgBuilder final : public VNVisitorConst {
|
|
// STATE
|
|
// The graph being built, or nullptr if failed to build one
|
|
std::unique_ptr<CfgGraph> m_cfgp{new CfgGraph};
|
|
// Current basic block to add statements to
|
|
CfgBlock* m_currBBp = nullptr;
|
|
// Continuation block for given JumpBlock
|
|
std::unordered_map<AstJumpBlock*, CfgBlock*> m_jumpBlockContp;
|
|
// Continuation block for given Loop
|
|
std::unordered_map<AstLoop*, CfgBlock*> m_loopContp;
|
|
|
|
// METHODS
|
|
|
|
// Add the given statement to the current CfgBlock
|
|
void addStmt(AstNodeStmt* nodep) { m_currBBp->m_stmtps.emplace_back(nodep); }
|
|
|
|
// Used to handle statements not representable in the CFG
|
|
void nonRepresentable(AstNodeStmt*) {
|
|
if (!m_cfgp) return;
|
|
m_cfgp.reset();
|
|
}
|
|
|
|
// Used to handle simple (non-branching) statements in the CFG
|
|
void simpleStatement(AstNodeStmt* nodep, bool representable = true) {
|
|
if (!m_cfgp) return;
|
|
// If non-representable, reset graph
|
|
if (!representable) {
|
|
m_cfgp.reset();
|
|
return;
|
|
}
|
|
// Just add to current block
|
|
addStmt(nodep);
|
|
}
|
|
|
|
// VISITORS
|
|
|
|
// Eventually we should handle all procedural statements, however, what
|
|
// is a procedural statemen is a bit unclear (#6280), so in the first
|
|
// instance we will only handle select statemetns that cover the requied
|
|
// use cases, and in the base case we conservatively assume the statement
|
|
// is non-representable. More visits can be added case by case if needed.
|
|
void visit(AstNode* nodep) override {
|
|
if (!m_cfgp) return;
|
|
UINFO(9, "Unhandled AstNode type " << nodep->typeName());
|
|
m_cfgp.reset();
|
|
}
|
|
|
|
// Non-representable statements
|
|
void visit(AstAssignDly* nodep) override { nonRepresentable(nodep); }
|
|
void visit(AstCase* nodep) override { nonRepresentable(nodep); } // V3Case will eliminate
|
|
void visit(AstCReset* nodep) override { nonRepresentable(nodep); }
|
|
void visit(AstDelay* nodep) override { nonRepresentable(nodep); }
|
|
|
|
// Representable non control-flow statements
|
|
void visit(AstAssign* nodep) override { simpleStatement(nodep, !nodep->timingControlp()); }
|
|
void visit(AstComment*) override {} // ignore entirely
|
|
void visit(AstDisplay* nodep) override { simpleStatement(nodep); }
|
|
void visit(AstFinish* nodep) override { simpleStatement(nodep); }
|
|
void visit(AstStmtExpr* nodep) override { simpleStatement(nodep); }
|
|
void visit(AstStop* nodep) override { simpleStatement(nodep); }
|
|
|
|
// Representable control flow statements
|
|
void visit(AstIf* nodep) override {
|
|
if (!m_cfgp) return;
|
|
|
|
// Add terminator statement to current block - semantically the condition check only ...
|
|
addStmt(nodep);
|
|
|
|
// Create then/else/continuation blocks
|
|
CfgBlock* const thenBBp = m_cfgp->addBlock();
|
|
CfgBlock* const elseBBp = m_cfgp->addBlock();
|
|
CfgBlock* const contBBp = m_cfgp->addBlock();
|
|
m_cfgp->addTakenEdge(m_currBBp, thenBBp);
|
|
m_cfgp->addUntknEdge(m_currBBp, elseBBp);
|
|
|
|
// Build then branch
|
|
m_currBBp = thenBBp;
|
|
iterateAndNextConstNull(nodep->thensp());
|
|
if (!m_cfgp) return;
|
|
if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, contBBp);
|
|
|
|
// Build else branch
|
|
m_currBBp = elseBBp;
|
|
iterateAndNextConstNull(nodep->elsesp());
|
|
if (!m_cfgp) return;
|
|
if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, contBBp);
|
|
|
|
// Set continuation
|
|
m_currBBp = contBBp;
|
|
}
|
|
void visit(AstLoop* nodep) override {
|
|
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
|
|
if (!m_cfgp) return;
|
|
|
|
// Don't acutally need to add this 'nodep' to any block
|
|
|
|
// Create continuation block
|
|
CfgBlock* const contBBp = m_cfgp->addBlock();
|
|
const bool newEntry = m_loopContp.emplace(nodep, contBBp).second;
|
|
UASSERT_OBJ(newEntry, nodep, "AstLoop visited twice");
|
|
|
|
// Create the body block
|
|
CfgBlock* const bodyBBp = m_cfgp->addBlock();
|
|
m_cfgp->addTakenEdge(m_currBBp, bodyBBp);
|
|
|
|
// Build the body
|
|
m_currBBp = bodyBBp;
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
|
if (!m_cfgp) return;
|
|
if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, bodyBBp);
|
|
|
|
// Set continuation
|
|
m_currBBp = contBBp;
|
|
}
|
|
void visit(AstLoopTest* nodep) override {
|
|
if (!m_cfgp) return;
|
|
|
|
// Add terminator statement to current block - semantically the condition check only ...
|
|
addStmt(nodep);
|
|
|
|
// Create continuation blocks
|
|
CfgBlock* const contBBp = m_cfgp->addBlock();
|
|
m_cfgp->addTakenEdge(m_currBBp, contBBp);
|
|
m_cfgp->addUntknEdge(m_currBBp, m_loopContp.at(nodep->loopp()));
|
|
|
|
// Set continuation
|
|
m_currBBp = contBBp;
|
|
}
|
|
void visit(AstJumpBlock* nodep) override {
|
|
if (!m_cfgp) return;
|
|
|
|
// Don't acutally need to add this 'nodep' to any block
|
|
|
|
// Create continuation block
|
|
CfgBlock* const contBBp = m_cfgp->addBlock();
|
|
const bool newEntry = m_jumpBlockContp.emplace(nodep, contBBp).second;
|
|
UASSERT_OBJ(newEntry, nodep, "AstJumpBlock visited twice");
|
|
|
|
// Build the body
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
|
if (!m_cfgp) return;
|
|
if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, contBBp);
|
|
|
|
// Set continuation
|
|
m_currBBp = contBBp;
|
|
}
|
|
void visit(AstJumpGo* nodep) override {
|
|
if (!m_cfgp) return;
|
|
|
|
// Non-representable if not last in statement list (V3Const will fix this later)
|
|
if (nodep->nextp()) {
|
|
m_cfgp.reset();
|
|
return;
|
|
}
|
|
|
|
// Don't acutally need to add this 'nodep' to any block
|
|
|
|
// Make current block go to the continuation of the JumpBlock
|
|
m_cfgp->addTakenEdge(m_currBBp, m_jumpBlockContp.at(nodep->blockp()));
|
|
|
|
// There should be no statements after a JumpGo!
|
|
m_currBBp = nullptr;
|
|
}
|
|
|
|
// CONSTRUCTOR
|
|
explicit CfgBuilder(AstNode* stmtsp) {
|
|
// Build the graph, starting from the entry block
|
|
m_currBBp = m_cfgp->addBlock();
|
|
m_cfgp->m_enterp = m_currBBp;
|
|
// Visit each statement to build the control flow graph
|
|
iterateAndNextConstNull(stmtsp);
|
|
// If failed, stop now
|
|
if (!m_cfgp) return;
|
|
// The final block is the exit block
|
|
m_cfgp->m_exitp = m_currBBp;
|
|
// Some blocks might not have predecessors if they are unreachable, remove them
|
|
{
|
|
std::vector<V3GraphVertex*> unreachableps;
|
|
for (V3GraphVertex* const vtxp : m_cfgp->vertices().unlinkable()) {
|
|
if (vtxp == m_cfgp->m_enterp) continue;
|
|
if (vtxp == m_cfgp->m_exitp) continue;
|
|
UASSERT_OBJ(!vtxp->outEmpty(), vtxp, "Block with no successor other than exit");
|
|
if (vtxp->inEmpty()) unreachableps.emplace_back(vtxp);
|
|
}
|
|
while (!unreachableps.empty()) {
|
|
V3GraphVertex* const vtxp = unreachableps.back();
|
|
unreachableps.pop_back();
|
|
for (const V3GraphEdge& edge : vtxp->outEdges()) {
|
|
--m_cfgp->m_nEdges;
|
|
if (edge.top()->inSize1()) unreachableps.emplace_back(edge.top());
|
|
}
|
|
--m_cfgp->m_nBlocks;
|
|
VL_DO_DANGLING(vtxp->unlinkDelete(m_cfgp.get()), vtxp);
|
|
}
|
|
}
|
|
// Dump the initial graph
|
|
if (dumpGraphLevel() >= 9) {
|
|
m_cfgp->rpoBlocks();
|
|
m_cfgp->dumpDotFilePrefixed("cfg-builder-initial");
|
|
}
|
|
// Minimize it
|
|
m_cfgp->minimize();
|
|
// Dump the final graph
|
|
if (dumpGraphLevel() >= 8) m_cfgp->dumpDotFilePrefixed("cfg-builder");
|
|
}
|
|
|
|
public:
|
|
static std::unique_ptr<CfgGraph> apply(AstNode* stmtsp) {
|
|
return std::move(CfgBuilder{stmtsp}.m_cfgp);
|
|
}
|
|
};
|
|
|
|
std::unique_ptr<CfgGraph> CfgGraph::build(AstNode* stmtsp) { return CfgBuilder::apply(stmtsp); }
|