mirror of
https://github.com/verilator/verilator.git
synced 2026-08-31 18:14:33 +02:00
Relax the live variable analysis performed by Dfg to bail on fewer cases. This analysis was already conservative (meaning it might think variables are live when they are not), which is good enough for Dfg use. This change in particular enables synthesizing more complex logic involving arrays, e.g. those introduce by V3Table creating lookup tables.
207 lines
8.1 KiB
C++
207 lines
8.1 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: CFG liveness analysis
|
|
//
|
|
// 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
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Classical data flow analysis computing live variables input to a CFG
|
|
// https://en.wikipedia.org/wiki/Live-variable_analysis
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "config_build.h"
|
|
#include "verilatedos.h"
|
|
|
|
#include "V3Ast.h"
|
|
#include "V3Cfg.h"
|
|
|
|
#include <memory>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
class CfgLiveVariables final : VNVisitorConst {
|
|
// State associted with each basic block
|
|
struct BlockState final {
|
|
// Variables used in block, before a complete assignment in the same block
|
|
std::unordered_set<AstVarScope*> m_gen;
|
|
// Variables that are assigned a complete value in the basic block
|
|
std::unordered_set<AstVarScope*> m_kill;
|
|
std::unordered_set<AstVarScope*> m_liveIn; // Variables live on entry to the block
|
|
std::unordered_set<AstVarScope*> m_liveOut; // Variables live on exit from the block
|
|
bool m_isOnWorkList = false; // Block is on work list
|
|
bool m_wasProcessed = false; // Already processed at least once
|
|
};
|
|
|
|
// STATE
|
|
const CfgGraph& m_cfg; // The CFG beign analysed
|
|
// State for each block
|
|
CfgBlockMap<BlockState> m_blockState{m_cfg.makeBlockMap<BlockState>()};
|
|
BlockState* m_currp = nullptr; // State of current block being analysed
|
|
bool m_abort = false; // Abort analysis - unhandled construct
|
|
|
|
// METHODS
|
|
|
|
static bool isPacked(const AstVarScope* vscp) {
|
|
AstNodeDType* const dtypep = vscp->dtypep()->skipRefp();
|
|
if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) {
|
|
return typep->keyword().isIntNumeric();
|
|
}
|
|
if (VN_IS(dtypep, PackArrayDType)) return true;
|
|
if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) {
|
|
return typep->packed();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void updateGen(AstNode* nodep) {
|
|
UASSERT_OBJ(!m_abort, nodep, "Doing useless work");
|
|
m_abort = nodep->exists([&](const AstVarRef* refp) {
|
|
// Only care about reads
|
|
if (refp->access().isWriteOnly()) return false;
|
|
// Grab referenced variable
|
|
AstVarScope* const vscp = refp->varScopep();
|
|
// Add to gen set, if not killed - assume whole variable read
|
|
if (m_currp->m_kill.count(vscp)) return false;
|
|
m_currp->m_gen.emplace(vscp);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
void updateKill(AstNode* nodep) {
|
|
UASSERT_OBJ(!m_abort, nodep, "Doing useless work");
|
|
m_abort = nodep->exists([&](const AstVarRef* refp) {
|
|
// Only care about writes
|
|
if (refp->access().isReadOnly()) return false;
|
|
// Grab referenced variable
|
|
AstVarScope* const vscp = refp->varScopep();
|
|
// Safety case: bail on variables with unusual semantics
|
|
if (vscp->varp()->ignoreSchedWrite()) return true;
|
|
// If not a packed variable, assume it's not a whole update, don't add to kill set
|
|
if (!isPacked(vscp)) return false;
|
|
// If packed, and updated partially, don't add to kill set
|
|
if (refp->backp()->nextp() != refp && VN_IS(refp->abovep(), Sel)) return false;
|
|
// Whole variable is writen, add to kill set
|
|
m_currp->m_kill.emplace(vscp);
|
|
return false;
|
|
});
|
|
}
|
|
|
|
void single(AstNode* nodep) {
|
|
// Assume all reads hapen before any writes
|
|
if (m_abort) return;
|
|
updateGen(nodep);
|
|
if (m_abort) return;
|
|
updateKill(nodep);
|
|
}
|
|
|
|
// Apply transfer function of block, return true if changed
|
|
bool transfer(const CfgBlock& bb) {
|
|
BlockState& state = m_blockState[bb];
|
|
|
|
// liveIn = gen union (liveOut - kill)
|
|
std::unordered_set<AstVarScope*> liveIn = state.m_gen;
|
|
for (AstVarScope* const varp : state.m_liveOut) {
|
|
if (state.m_kill.count(varp)) continue;
|
|
liveIn.insert(varp);
|
|
}
|
|
if (liveIn == state.m_liveIn) return false;
|
|
std::swap(liveIn, state.m_liveIn);
|
|
return true;
|
|
}
|
|
|
|
// VISIT
|
|
void visit(AstNode* nodep) override { //
|
|
UASSERT_OBJ(!m_abort, nodep, "Repeat traversal after abort");
|
|
m_abort = true;
|
|
UINFO(9, "Unhandled AstNode type " << nodep->typeName());
|
|
}
|
|
|
|
void visit(AstAssign* nodep) override { single(nodep); }
|
|
void visit(AstAssignW* nodep) override { single(nodep); }
|
|
void visit(AstDisplay* nodep) override { single(nodep); }
|
|
void visit(AstFinish* nodep) override { single(nodep); }
|
|
void visit(AstFinishFork* nodep) override { single(nodep); }
|
|
void visit(AstStmtExpr* nodep) override { single(nodep); }
|
|
void visit(AstStop* nodep) override { single(nodep); }
|
|
|
|
// Only the condition check belongs to the terminated basic block
|
|
void visit(AstIf* nodep) override { single(nodep->condp()); }
|
|
void visit(AstLoopTest* nodep) override { single(nodep->condp()); }
|
|
|
|
// CONSTRUCTOR
|
|
explicit CfgLiveVariables(const CfgGraph& cfg)
|
|
: m_cfg{cfg} {
|
|
// For each basic block, compute the gen and kill set via visit
|
|
for (const V3GraphVertex& vtx : cfg.vertices()) {
|
|
const CfgBlock& bb = static_cast<const CfgBlock&>(vtx);
|
|
if (m_abort) return;
|
|
VL_RESTORER(m_currp);
|
|
m_currp = &m_blockState[bb];
|
|
for (AstNode* const stmtp : bb.stmtps()) iterateConst(stmtp);
|
|
}
|
|
if (m_abort) return;
|
|
|
|
// Perform the flow analysis
|
|
std::deque<const CfgBlock*> workList;
|
|
const auto enqueue = [&](const CfgBlock& bb) {
|
|
BlockState& state = m_blockState[bb];
|
|
if (state.m_isOnWorkList) return;
|
|
state.m_isOnWorkList = true;
|
|
workList.emplace_back(&bb);
|
|
};
|
|
|
|
enqueue(cfg.exit());
|
|
|
|
while (!workList.empty()) {
|
|
const CfgBlock* const currp = workList.front();
|
|
workList.pop_front();
|
|
BlockState& state = m_blockState[*currp];
|
|
state.m_isOnWorkList = false;
|
|
|
|
// Compute meet (liveOut = union liveIn of successors)
|
|
currp->forEachSuccessor([&](const CfgBlock& bb) {
|
|
auto& liveIn = m_blockState[bb].m_liveIn;
|
|
state.m_liveOut.insert(liveIn.begin(), liveIn.end());
|
|
});
|
|
|
|
// Apply transfer function of block
|
|
const bool changed = transfer(*currp);
|
|
// Enqueue predecessors
|
|
if (changed || !state.m_wasProcessed) currp->forEachPredecessor(enqueue);
|
|
// Mark as done with first visit
|
|
state.m_wasProcessed = true;
|
|
}
|
|
}
|
|
|
|
public:
|
|
static std::unique_ptr<std::vector<AstVarScope*>> apply(const CfgGraph& cfg) {
|
|
CfgLiveVariables analysis{cfg};
|
|
// If failed, return nullptr
|
|
if (analysis.m_abort) return nullptr;
|
|
// Gather variables live in to the entry block
|
|
const std::unordered_set<AstVarScope*>& lin = analysis.m_blockState[cfg.enter()].m_liveIn;
|
|
std::vector<AstVarScope*>* const resultp
|
|
= new std::vector<AstVarScope*>{lin.begin(), lin.end()};
|
|
// Sort for stability
|
|
std::stable_sort(resultp->begin(), resultp->end(),
|
|
[](AstVarScope* ap, AstVarScope* bp) { //
|
|
return ap->name() < bp->name();
|
|
});
|
|
return std::unique_ptr<std::vector<AstVarScope*>>{resultp};
|
|
}
|
|
};
|
|
|
|
std::unique_ptr<std::vector<AstVarScope*>> V3Cfg::liveVarScopes(const CfgGraph& cfg) {
|
|
return CfgLiveVariables::apply(cfg);
|
|
}
|