verilator/src/V3Premit.cpp

354 lines
14 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add temporaries, such as for premit nodes
//
// 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
//
//*************************************************************************
// V3Premit's Transformations:
//
// Each module:
// For each wide OP, make a a temporary variable with the wide value
// For each deep expression, assign expression to temporary.
//
// Each display (independent transformation; here as Premit is a good point)
// If autoflush, insert a flush
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Premit.h"
#include "V3Stats.h"
#include "V3UniqueNames.h"
VL_DEFINE_DEBUG_FUNCTIONS;
constexpr int STATIC_CONST_MIN_WIDTH = 256; // Minimum size to extract to static constant
//######################################################################
// Premit state, as a visitor of each AstNode
class PremitVisitor final : public VNVisitor {
// NODE STATE
// AstNodeExpr::user() -> bool. True if iterated already
// *::user3() -> Used when visiting AstNodeAssign
const VNUser1InUse m_inuser1;
// STATE - across all visitors
VDouble0 m_extractedToConstPool; // Statistic tracking
VDouble0 m_temporaryVarsCreated; // Statistic tracking
// STATE - for current visit position (use VL_RESTORER)
AstCFunc* m_cfuncp = nullptr; // Current block
size_t m_tmpVarCnt = 0; // Number of temporary variables created inside a function
AstNode* m_stmtp = nullptr; // Current statement
bool m_assignLhs = false; // Inside assignment lhs, don't breakup extracts
// METHODS
void checkNode(AstNodeExpr* nodep) {
// Consider adding a temp for this expression.
if (!m_stmtp) return; // Not under a statement
if (nodep->user1SetOnce()) return; // Already processed
if (!nodep->isWide()) return; // Not wide
if (m_assignLhs) return; // This is an lvalue!
UASSERT_OBJ(!VN_IS(nodep->firstAbovep(), ArraySel), nodep, "Should have been ignored");
createTemp(nodep);
}
AstVar* createTemp(AstNodeExpr* nodep) {
UASSERT_OBJ(m_stmtp, nodep, "Attempting to create temporary with no insertion point");
UINFO(4, "createTemp: " << nodep);
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
FileLine* const flp = nodep->fileline();
AstConst* const constp = VN_CAST(nodep, Const);
const bool useConstPool = constp // Is a constant
&& (constp->width() >= STATIC_CONST_MIN_WIDTH) // Large enough
&& !constp->num().isFourState() // Not four state
&& !constp->num().isString(); // Not a string
AstVar* varp = nullptr;
if (useConstPool) {
// Extract into constant pool.
const bool merge = v3Global.opt.fMergeConstPool();
varp = v3Global.rootp()->constPoolp()->findConst(constp, merge)->varp();
VL_DO_DANGLING(nodep->deleteTree(), nodep);
++m_extractedToConstPool;
} else {
// Keep as local temporary.
const std::string name = "__Vtemp_" + std::to_string(++m_tmpVarCnt);
varp = new AstVar{flp, VVarType::STMTTEMP, name, nodep->dtypep()};
m_cfuncp->addVarsp(varp);
++m_temporaryVarsCreated;
// Assignment to put before the referencing statement
AstAssign* const assignp
= new AstAssign{flp, new AstVarRef{flp, varp, VAccess::WRITE}, nodep};
// Insert before the statement
m_stmtp->addHereThisAsNext(assignp);
}
// Replace node with VarRef to new Var
relinker.relink(new AstVarRef{flp, varp, VAccess::READ});
// Return the temporary variable
return varp;
}
void visitShift(AstNodeBiop* nodep) {
// Shifts of > 32/64 bits in C++ will wrap-around and generate non-0s
UINFO(4, " ShiftFix " << nodep);
const AstConst* const shiftp = VN_CAST(nodep->rhsp(), Const);
if (shiftp && shiftp->num().mostSetBitP1() > 32) {
shiftp->v3warn(
E_UNSUPPORTED,
"Unsupported: Shifting of by over 32-bit number isn't supported."
<< " (This isn't a shift of 32 bits, but a shift of 2^32, or 4 billion!)\n");
}
if (nodep->widthMin() <= 64 // Else we'll use large operators which work right
// C operator's width must be < maximum shift which is
// based on Verilog width
&& nodep->width() < (1LL << nodep->rhsp()->widthMin())) {
AstNode* newp;
if (VN_IS(nodep, ShiftL)) {
newp = new AstShiftLOvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(),
nodep->rhsp()->unlinkFrBack()};
} else if (VN_IS(nodep, ShiftR)) {
newp = new AstShiftROvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(),
nodep->rhsp()->unlinkFrBack()};
} else {
UASSERT_OBJ(VN_IS(nodep, ShiftRS), nodep, "Bad case");
newp = new AstShiftRSOvr{nodep->fileline(), nodep->lhsp()->unlinkFrBack(),
nodep->rhsp()->unlinkFrBack()};
}
nodep->replaceWithKeepDType(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
iterateChildren(nodep);
checkNode(nodep);
}
static bool rhsReadsLhs(const AstNodeAssign* nodep) {
const VNUser3InUse user3InUse;
nodep->lhsp()->foreach([](const AstVarRef* refp) {
if (refp->access().isWriteOrRW()) refp->varp()->user3(true);
});
return nodep->rhsp()->exists([](const AstVarRef* refp) {
return refp->access().isReadOnly() && refp->varp()->user3();
});
}
// VISITORS
void visit(AstCFunc* nodep) override {
UASSERT_OBJ(!m_cfuncp, nodep, "Should not nest");
VL_RESTORER(m_cfuncp);
VL_RESTORER(m_tmpVarCnt);
m_cfuncp = nodep;
m_tmpVarCnt = 0;
iterateChildren(nodep);
}
// VISITORS - Statements
#define START_STATEMENT_OR_RETURN(stmtp) \
if (!m_cfuncp) return; \
if (stmtp->user1SetOnce()) return; \
VL_RESTORER(m_assignLhs); \
VL_RESTORER(m_stmtp); \
m_assignLhs = false; \
m_stmtp = stmtp;
void visit(AstNodeAssign* nodep) override {
START_STATEMENT_OR_RETURN(nodep);
if (AstCvtArrayToPacked* const packedp = VN_CAST(nodep->lhsp(), CvtArrayToPacked)) {
// AstCvtArrayToPacked is converted to VL_PACK, which returns rvalue,
// so it shouldn't be on the LHS. It is now replaced with unpacking of RHS.
AstNodeExpr* const exprLhsp = packedp->fromp()->unlinkFrBack();
packedp->replaceWith(exprLhsp);
VL_DO_DANGLING(pushDeletep(packedp), packedp);
AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
nodep->rhsp(new AstCvtPackedToArray{rhsp->fileline(), rhsp, exprLhsp->dtypep()});
nodep->dtypeFrom(exprLhsp);
}
// Direct assignment to a simple variable
if (VN_IS(nodep->lhsp(), VarRef) && !AstVar::scVarRecurse(nodep->lhsp())) {
AstNode* const rhsp = nodep->rhsp();
// Rhs is already a var ref, so nothing to so
if (VN_IS(rhsp, VarRef) && !AstVar::scVarRecurse(rhsp)) return;
if (!VN_IS(rhsp, Const)) {
// Don't replace the rhs, it's already a simple assignment
rhsp->user1(true);
} else if (rhsp->width() < STATIC_CONST_MIN_WIDTH) {
// It's a small constant, so nothing to do, otherwise will be put to const pool
return;
}
}
// If the RHS reads the LHS, we need a temporary unless the update is atomic
const bool isAtomic = VN_IS(nodep->lhsp(), VarRef) && !nodep->lhsp()->isWide();
if (!isAtomic && rhsReadsLhs(nodep)) {
createTemp(nodep->rhsp());
} else {
iterateAndNextNull(nodep->rhsp());
}
m_assignLhs = true; // Restored by VL_RESTORER in START_STATEMENT_OR_RETURN
iterateAndNextNull(nodep->lhsp());
}
void visit(AstDisplay* nodep) override {
START_STATEMENT_OR_RETURN(nodep);
iterateChildren(nodep);
if (v3Global.opt.autoflush()) {
const AstNode* searchp = nodep->nextp();
while (searchp && VN_IS(searchp, Comment)) searchp = searchp->nextp();
if (searchp && VN_IS(searchp, Display)
&& nodep->filep()->sameGateTree(VN_AS(searchp, Display)->filep())) {
// There's another display next; we can just wait to flush
} else {
UINFO(4, "Autoflush " << nodep);
nodep->addNextHere(
new AstFFlush{nodep->fileline(),
nodep->filep() ? nodep->filep()->cloneTreePure(true) : nullptr});
}
}
}
void visit(AstNodeStmt* nodep) override {
START_STATEMENT_OR_RETURN(nodep);
iterateChildren(nodep);
}
#undef START_STATEMENT_OR_RETURN
// VISITORS - Expressions
void visit(AstShiftL* nodep) override { visitShift(nodep); }
void visit(AstShiftR* nodep) override { visitShift(nodep); }
void visit(AstShiftRS* nodep) override { visitShift(nodep); }
void visit(AstConst* nodep) override { checkNode(nodep); }
// Operators
void visit(AstNodeTermop* nodep) override { checkNode(nodep); }
void visit(AstNodeUniop* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstNodeBiop* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstRand* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstRandRNG* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstCExprUser* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstCvtArrayToPacked* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstCvtPackedToArray* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
if (!VN_IS(nodep->backp(), NodeAssign)) createTemp(nodep);
}
void visit(AstCvtUnpackedToQueue* nodep) override {
iterateChildren(nodep);
checkNode(nodep);
}
void visit(AstSel* nodep) override {
iterateAndNextNull(nodep->fromp());
{ // Only the 'from' is part of the assignment LHS
VL_RESTORER(m_assignLhs);
m_assignLhs = false;
iterateAndNextNull(nodep->lsbp());
// AstSel::widthp() must remain a constant, so not iterating
}
checkNode(nodep);
}
void visit(AstArraySel* nodep) override {
iterateAndNextNull(nodep->fromp());
{ // Only the 'from' is part of the assignment LHS
VL_RESTORER(m_assignLhs);
m_assignLhs = false;
iterateAndNextNull(nodep->bitp());
}
// ArraySel are just pointer arithmetic and should never be replaced
}
void visit(AstAssocSel* nodep) override {
iterateAndNextNull(nodep->fromp());
{ // Only the 'from' is part of the assignment LHS
VL_RESTORER(m_assignLhs);
m_assignLhs = false;
iterateAndNextNull(nodep->bitp());
}
checkNode(nodep);
}
void visit(AstCond* nodep) override {
iterateChildren(nodep);
if (nodep->thenp()->isWide() && !VN_IS(nodep->condp(), Const)
&& !VN_IS(nodep->condp(), VarRef)) {
// We're going to need the expression several times in the expanded code,
// so might as well make it a common expression
createTemp(nodep->condp());
VIsCached::clearCacheTree();
}
checkNode(nodep);
}
void visit(AstSFormatF* nodep) override {
iterateChildren(nodep);
// Any strings sent to a display must be var of string data type,
// to avoid passing a pointer to a temporary.
for (AstNodeExpr *expp = nodep->exprsp(), *nextp; expp; expp = nextp) {
nextp = VN_AS(expp->nextp(), NodeExpr);
if (expp->isString() && !VN_IS(expp, VarRef)) {
AstVar* const varp = createTemp(expp);
// Do not remove VarRefs to this in V3Const
varp->noSubst(true);
}
}
}
//--------------------
// Default: Just iterate
void visit(AstVar*) override {} // Don't hit varrefs under vars
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit PremitVisitor(AstNetlist* nodep) { iterate(nodep); }
~PremitVisitor() override {
V3Stats::addStat("Optimizations, Prelim extracted value to ConstPool",
m_extractedToConstPool);
V3Stats::addStat("Optimizations, Prelim temporary variables created",
m_temporaryVarsCreated);
}
};
//######################################################################
// Premit class functions
void V3Premit::premitAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ PremitVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("premit", 0, dumpTreeEitherLevel() >= 3);
}