292 lines
11 KiB
C++
292 lines
11 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 : VNVisitorConst {
|
||
|
|
// STATE
|
||
|
|
// The graph being built, or nullptr if failed to build one
|
||
|
|
std::unique_ptr<ControlFlowGraph> m_cfgp{new ControlFlowGraph};
|
||
|
|
// Current basic block to add statements to
|
||
|
|
BasicBlock* m_currBBp = nullptr;
|
||
|
|
// Continuation block for given JumpBlock
|
||
|
|
std::unordered_map<AstJumpBlock*, BasicBlock*> m_jumpBlockContp;
|
||
|
|
|
||
|
|
// METHODS
|
||
|
|
|
||
|
|
// Create new Basicblock block in the CFG
|
||
|
|
BasicBlock& addBasicBlock() { return *new BasicBlock{m_cfgp.get()}; }
|
||
|
|
|
||
|
|
// Create new taken (or unconditional) ControlFlowEdge in the CFG
|
||
|
|
void addTakenEdge(BasicBlock& src, BasicBlock& dst) {
|
||
|
|
UASSERT_OBJ(src.outEmpty(), &src, "Taken edge should be added first");
|
||
|
|
new ControlFlowEdge{m_cfgp.get(), &src, &dst};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create new untaken ControlFlowEdge in the CFG
|
||
|
|
void addUntknEdge(BasicBlock& src, BasicBlock& dst) {
|
||
|
|
UASSERT_OBJ(src.outSize1(), &src, "Untaken edge shold be added second");
|
||
|
|
UASSERT_OBJ(src.outEdges().frontp()->top() != &dst, &src,
|
||
|
|
"Untaken branch targets the same block as the taken branch");
|
||
|
|
new ControlFlowEdge{m_cfgp.get(), &src, &dst};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add the given statement to the current BasicBlock
|
||
|
|
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
|
||
|
|
BasicBlock& thenBB = addBasicBlock();
|
||
|
|
BasicBlock& elseBB = addBasicBlock();
|
||
|
|
BasicBlock& contBB = addBasicBlock();
|
||
|
|
addTakenEdge(*m_currBBp, thenBB);
|
||
|
|
addUntknEdge(*m_currBBp, elseBB);
|
||
|
|
|
||
|
|
// Build then branch
|
||
|
|
m_currBBp = &thenBB;
|
||
|
|
iterateAndNextConstNull(nodep->thensp());
|
||
|
|
if (!m_cfgp) return;
|
||
|
|
if (m_currBBp) addTakenEdge(*m_currBBp, contBB);
|
||
|
|
|
||
|
|
// Build else branch
|
||
|
|
m_currBBp = &elseBB;
|
||
|
|
iterateAndNextConstNull(nodep->elsesp());
|
||
|
|
if (!m_cfgp) return;
|
||
|
|
if (m_currBBp) addTakenEdge(*m_currBBp, contBB);
|
||
|
|
|
||
|
|
// Set continuation
|
||
|
|
m_currBBp = &contBB;
|
||
|
|
}
|
||
|
|
void visit(AstWhile* nodep) override {
|
||
|
|
if (!m_cfgp) return;
|
||
|
|
|
||
|
|
// Create the header block
|
||
|
|
BasicBlock& headBB = addBasicBlock();
|
||
|
|
addTakenEdge(*m_currBBp, headBB);
|
||
|
|
|
||
|
|
// The While goes in the header block - semantically the condition check only ...
|
||
|
|
m_currBBp = &headBB;
|
||
|
|
addStmt(nodep);
|
||
|
|
|
||
|
|
// Create the body/continuation blocks
|
||
|
|
BasicBlock& bodyBB = addBasicBlock();
|
||
|
|
BasicBlock& contBB = addBasicBlock();
|
||
|
|
addTakenEdge(headBB, bodyBB);
|
||
|
|
addUntknEdge(headBB, contBB);
|
||
|
|
|
||
|
|
// Build the body
|
||
|
|
m_currBBp = &bodyBB;
|
||
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
||
|
|
iterateAndNextConstNull(nodep->incsp());
|
||
|
|
if (!m_cfgp) return;
|
||
|
|
if (m_currBBp) addTakenEdge(*m_currBBp, headBB);
|
||
|
|
|
||
|
|
// Set continuation
|
||
|
|
m_currBBp = &contBB;
|
||
|
|
}
|
||
|
|
void visit(AstJumpBlock* nodep) override {
|
||
|
|
if (!m_cfgp) return;
|
||
|
|
|
||
|
|
// Don't acutally need to add this 'nodep' to any block - but we could later if needed
|
||
|
|
|
||
|
|
// Create continuation block
|
||
|
|
BasicBlock& contBB = addBasicBlock();
|
||
|
|
const bool newEntry = m_jumpBlockContp.emplace(nodep, &contBB).second;
|
||
|
|
UASSERT_OBJ(newEntry, nodep, "AstJumpBlock visited twice");
|
||
|
|
|
||
|
|
// Build the body
|
||
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
||
|
|
if (!m_cfgp) return;
|
||
|
|
if (m_currBBp) addTakenEdge(*m_currBBp, contBB);
|
||
|
|
|
||
|
|
// Set continuation
|
||
|
|
m_currBBp = &contBB;
|
||
|
|
}
|
||
|
|
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 - but we could later if needed
|
||
|
|
|
||
|
|
// Make current block go to the continuation of the JumpBlock
|
||
|
|
addTakenEdge(*m_currBBp, *m_jumpBlockContp.at(nodep->blockp()));
|
||
|
|
|
||
|
|
// There should be no statements after a JumpGo!
|
||
|
|
m_currBBp = nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
// CONSTRUCTOR
|
||
|
|
explicit CfgBuilder(const AstNodeProcedure* nodep) {
|
||
|
|
// Build the graph, starting from the entry block
|
||
|
|
m_currBBp = &addBasicBlock();
|
||
|
|
m_cfgp->m_enterp = m_currBBp;
|
||
|
|
// Visit each statement to build the control flow graph
|
||
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
||
|
|
if (!m_cfgp) return;
|
||
|
|
// The final block is the exit block
|
||
|
|
m_cfgp->m_exitp = m_currBBp;
|
||
|
|
|
||
|
|
// Remove empty blocks - except enter/exit
|
||
|
|
for (V3GraphVertex* const vtxp : m_cfgp->vertices().unlinkable()) {
|
||
|
|
if (vtxp == &m_cfgp->enter()) continue;
|
||
|
|
if (vtxp == &m_cfgp->exit()) continue;
|
||
|
|
BasicBlock* const bbp = vtxp->as<BasicBlock>();
|
||
|
|
if (!bbp->stmtps().empty()) continue;
|
||
|
|
UASSERT(bbp->outSize1(), "Empty block should have a single successor");
|
||
|
|
BasicBlock* const succp = const_cast<BasicBlock*>(bbp->takenSuccessorp());
|
||
|
|
for (V3GraphEdge* const edgep : bbp->inEdges().unlinkable()) edgep->relinkTop(succp);
|
||
|
|
vtxp->unlinkDelete(m_cfgp.get());
|
||
|
|
}
|
||
|
|
// Remove redundant entry block
|
||
|
|
while (m_cfgp->enter().stmtps().empty() && m_cfgp->enter().outSize1()) {
|
||
|
|
BasicBlock* const succp = m_cfgp->enter().outEdges().frontp()->top()->as<BasicBlock>();
|
||
|
|
if (!succp->inSize1()) break;
|
||
|
|
m_cfgp->m_enterp->unlinkDelete(m_cfgp.get());
|
||
|
|
m_cfgp->m_enterp = succp;
|
||
|
|
}
|
||
|
|
// Remove redundant exit block
|
||
|
|
while (m_cfgp->exit().stmtps().empty() && m_cfgp->exit().inSize1()) {
|
||
|
|
BasicBlock* const prep = m_cfgp->exit().inEdges().frontp()->fromp()->as<BasicBlock>();
|
||
|
|
if (!prep->outSize1()) break;
|
||
|
|
m_cfgp->m_exitp->unlinkDelete(m_cfgp.get());
|
||
|
|
m_cfgp->m_exitp = prep;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Compute reverse post-order enumeration and sort blocks, assign IDs
|
||
|
|
{
|
||
|
|
// Simple recursive algorith will do ...
|
||
|
|
std::vector<BasicBlock*> postOrderEnumeration;
|
||
|
|
std::unordered_set<BasicBlock*> visited;
|
||
|
|
const std::function<void(BasicBlock*)> visitBasicBlock = [&](BasicBlock* bbp) {
|
||
|
|
// Mark and skip if already visited
|
||
|
|
if (!visited.emplace(bbp).second) return;
|
||
|
|
// Visit successors
|
||
|
|
for (const V3GraphEdge& e : bbp->outEdges()) {
|
||
|
|
visitBasicBlock(static_cast<BasicBlock*>(e.top()));
|
||
|
|
}
|
||
|
|
// Add to post order enumeration
|
||
|
|
postOrderEnumeration.emplace_back(bbp);
|
||
|
|
};
|
||
|
|
visitBasicBlock(m_cfgp->m_enterp);
|
||
|
|
const uint32_t n = postOrderEnumeration.size();
|
||
|
|
UASSERT_OBJ(n == m_cfgp->vertices().size(), nodep, "Inconsistent enumeration size");
|
||
|
|
|
||
|
|
// Set size in graph
|
||
|
|
m_cfgp->m_nBasicBlocks = n;
|
||
|
|
|
||
|
|
// Assign ids equal to the reverse post order number and sort vertices
|
||
|
|
for (uint32_t i = 0; i < postOrderEnumeration.size(); ++i) {
|
||
|
|
BasicBlock* const bbp = postOrderEnumeration[n - 1 - i];
|
||
|
|
bbp->user(i);
|
||
|
|
m_cfgp->vertices().unlink(bbp);
|
||
|
|
m_cfgp->vertices().linkBack(bbp);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Assign IDs to edges
|
||
|
|
{
|
||
|
|
size_t nEdges = 0;
|
||
|
|
for (V3GraphVertex& v : m_cfgp->vertices()) {
|
||
|
|
for (V3GraphEdge& e : v.outEdges()) e.user(nEdges++);
|
||
|
|
}
|
||
|
|
// Set size in graph
|
||
|
|
m_cfgp->m_nEdges = nEdges;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (dumpGraphLevel() >= 9) m_cfgp->dumpDotFilePrefixed("cfgbuilder");
|
||
|
|
}
|
||
|
|
|
||
|
|
public:
|
||
|
|
static std::unique_ptr<ControlFlowGraph> apply(const AstNodeProcedure* nodep) {
|
||
|
|
return std::move(CfgBuilder{nodep}.m_cfgp);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
std::unique_ptr<const ControlFlowGraph> V3Cfg::build(const AstNodeProcedure* nodep) {
|
||
|
|
return CfgBuilder::apply(nodep);
|
||
|
|
}
|