2012-04-13 03:08:20 +02:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
2006-08-26 13:35:28 +02:00
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: Lifelicate variable assignment elimination
|
|
|
|
|
//
|
2019-11-08 04:33:59 +01:00
|
|
|
// Code available from: https://verilator.org
|
2006-08-26 13:35:28 +02:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2023-01-01 16:18:39 +01:00
|
|
|
// Copyright 2003-2023 by Wilson Snyder. This program is free software; you
|
2020-03-21 16:24:24 +01:00
|
|
|
// can redistribute it and/or modify it under the terms of either the GNU
|
2009-05-04 23:07:57 +02:00
|
|
|
// Lesser General Public License Version 3 or the Perl Artistic License
|
|
|
|
|
// Version 2.0.
|
2020-03-21 16:24:24 +01:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
2006-08-26 13:35:28 +02:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// LIFE TRANSFORMATIONS:
|
2019-05-19 22:13:13 +02:00
|
|
|
// Build control-flow graph with assignments and var usages
|
|
|
|
|
// All modules:
|
|
|
|
|
// ASSIGN(x,...), ASSIGN(x,...) => delete first one
|
|
|
|
|
// We also track across if statements:
|
|
|
|
|
// ASSIGN(X,...) IF( ..., ASSIGN(X,...), ASSIGN(X,...)) => deletes first
|
|
|
|
|
// We don't do the opposite yet though (remove assigns in if followed by outside if)
|
2008-06-10 03:25:10 +02:00
|
|
|
//
|
2006-08-26 13:35:28 +02:00
|
|
|
//*************************************************************************
|
2019-10-05 02:17:11 +02:00
|
|
|
|
2023-10-18 12:37:46 +02:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
#include "V3Life.h"
|
2022-08-05 11:56:57 +02:00
|
|
|
|
2006-09-26 17:05:35 +02:00
|
|
|
#include "V3Const.h"
|
2022-08-05 11:56:57 +02:00
|
|
|
#include "V3Stats.h"
|
2006-08-26 13:35:28 +02:00
|
|
|
|
2018-10-14 19:43:24 +02:00
|
|
|
#include <vector>
|
|
|
|
|
|
2022-09-18 21:53:42 +02:00
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
//######################################################################
|
2006-09-26 17:05:35 +02:00
|
|
|
// Structure for global state
|
|
|
|
|
|
2020-11-19 03:32:16 +01:00
|
|
|
class LifeState final {
|
2008-11-21 21:50:33 +01:00
|
|
|
// NODE STATE
|
|
|
|
|
// See below
|
2022-01-02 19:56:40 +01:00
|
|
|
const VNUser1InUse m_inuser1;
|
2008-11-21 21:50:33 +01:00
|
|
|
|
2006-09-26 17:05:35 +02:00
|
|
|
// STATE
|
|
|
|
|
public:
|
2019-10-05 13:54:14 +02:00
|
|
|
VDouble0 m_statAssnDel; // Statistic tracking
|
|
|
|
|
VDouble0 m_statAssnCon; // Statistic tracking
|
2018-02-02 03:24:41 +01:00
|
|
|
std::vector<AstNode*> m_unlinkps;
|
2006-09-26 17:05:35 +02:00
|
|
|
|
2009-10-14 14:26:30 +02:00
|
|
|
// CONSTRUCTORS
|
2020-11-17 01:56:16 +01:00
|
|
|
LifeState() = default;
|
2006-09-26 17:05:35 +02:00
|
|
|
~LifeState() {
|
2019-05-19 22:13:13 +02:00
|
|
|
V3Stats::addStatSum("Optimizations, Lifetime assign deletions", m_statAssnDel);
|
|
|
|
|
V3Stats::addStatSum("Optimizations, Lifetime constant prop", m_statAssnCon);
|
2020-08-16 17:43:49 +02:00
|
|
|
for (AstNode* ip : m_unlinkps) {
|
|
|
|
|
ip->unlinkFrBack();
|
|
|
|
|
ip->deleteTree();
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-09-26 17:05:35 +02:00
|
|
|
}
|
2009-10-14 14:26:30 +02:00
|
|
|
// METHODS
|
|
|
|
|
void pushUnlinkDeletep(AstNode* nodep) { m_unlinkps.push_back(nodep); }
|
2006-09-26 17:05:35 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//######################################################################
|
|
|
|
|
// Structure for each variable encountered
|
2006-08-26 13:35:28 +02:00
|
|
|
|
2020-11-19 03:32:16 +01:00
|
|
|
class LifeVarEntry final {
|
2020-11-11 03:40:14 +01:00
|
|
|
// Last assignment to this varscope, nullptr if no longer relevant
|
|
|
|
|
AstNodeAssign* m_assignp = nullptr;
|
|
|
|
|
AstConst* m_constp = nullptr; // Known constant value
|
2020-04-15 13:58:34 +02:00
|
|
|
// First access was a set (and thus block above may have a set that can be deleted
|
|
|
|
|
bool m_setBeforeUse;
|
|
|
|
|
// Was ever assigned (and thus above block may not preserve constant propagation)
|
2020-11-11 03:40:14 +01:00
|
|
|
bool m_everSet = false;
|
2020-04-15 13:58:34 +02:00
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
public:
|
2006-09-26 17:05:35 +02:00
|
|
|
class SIMPLEASSIGN {};
|
|
|
|
|
class COMPLEXASSIGN {};
|
|
|
|
|
class CONSUMED {};
|
|
|
|
|
|
2020-11-11 03:40:14 +01:00
|
|
|
LifeVarEntry(SIMPLEASSIGN, AstNodeAssign* assp)
|
|
|
|
|
: m_setBeforeUse{true} {
|
2020-04-15 13:58:34 +02:00
|
|
|
simpleAssign(assp);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2020-11-11 03:40:14 +01:00
|
|
|
explicit LifeVarEntry(COMPLEXASSIGN)
|
|
|
|
|
: m_setBeforeUse{false} {
|
2020-04-15 13:58:34 +02:00
|
|
|
complexAssign();
|
2006-09-26 17:05:35 +02:00
|
|
|
}
|
2020-11-11 03:40:14 +01:00
|
|
|
explicit LifeVarEntry(CONSUMED)
|
|
|
|
|
: m_setBeforeUse{false} {
|
2020-04-15 13:58:34 +02:00
|
|
|
consumed();
|
2006-09-26 17:05:35 +02:00
|
|
|
}
|
2020-11-17 01:56:16 +01:00
|
|
|
~LifeVarEntry() = default;
|
2022-09-16 14:17:38 +02:00
|
|
|
void simpleAssign(AstNodeAssign* assp) { // New simple A=.... assignment
|
2019-05-19 22:13:13 +02:00
|
|
|
m_assignp = assp;
|
2020-08-15 16:12:55 +02:00
|
|
|
m_constp = nullptr;
|
2019-05-19 22:13:13 +02:00
|
|
|
m_everSet = true;
|
2021-10-22 14:56:48 +02:00
|
|
|
if (VN_IS(assp->rhsp(), Const)) m_constp = VN_AS(assp->rhsp(), Const);
|
2006-09-26 17:05:35 +02:00
|
|
|
}
|
2022-09-16 14:17:38 +02:00
|
|
|
void complexAssign() { // A[x]=... or some complicated assignment
|
2020-08-15 16:12:55 +02:00
|
|
|
m_assignp = nullptr;
|
|
|
|
|
m_constp = nullptr;
|
2019-05-19 22:13:13 +02:00
|
|
|
m_everSet = true;
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 14:17:38 +02:00
|
|
|
void consumed() { // Rvalue read of A
|
2020-08-15 16:12:55 +02:00
|
|
|
m_assignp = nullptr;
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2006-09-26 17:05:35 +02:00
|
|
|
AstNodeAssign* assignp() const { return m_assignp; }
|
|
|
|
|
AstConst* constNodep() const { return m_constp; }
|
|
|
|
|
bool setBeforeUse() const { return m_setBeforeUse; }
|
|
|
|
|
bool everSet() const { return m_everSet; }
|
2006-08-26 13:35:28 +02:00
|
|
|
};
|
|
|
|
|
|
2006-09-26 17:05:35 +02:00
|
|
|
//######################################################################
|
|
|
|
|
// Structure for all variables under a given meta-basic block
|
2006-08-26 13:35:28 +02:00
|
|
|
|
2020-11-19 03:32:16 +01:00
|
|
|
class LifeBlock final {
|
2006-09-26 17:05:35 +02:00
|
|
|
// NODE STATE
|
|
|
|
|
// Cleared each AstIf:
|
2019-05-19 22:13:13 +02:00
|
|
|
// AstVarScope::user1() -> int. Used in combining to detect duplicates
|
2006-08-26 13:35:28 +02:00
|
|
|
|
|
|
|
|
// LIFE MAP
|
|
|
|
|
// For each basic block, we'll make a new map of what variables that if/else is changing
|
2021-03-13 00:10:45 +01:00
|
|
|
using LifeMap = std::unordered_map<AstVarScope*, LifeVarEntry>;
|
2020-04-15 13:58:34 +02:00
|
|
|
LifeMap m_map; // Current active lifetime map for current scope
|
2022-01-01 17:46:49 +01:00
|
|
|
LifeBlock* const m_aboveLifep; // Upper life, or nullptr
|
|
|
|
|
LifeState* const m_statep; // Current global state
|
2022-09-24 21:44:00 +02:00
|
|
|
bool m_replacedVref = false; // Replaced a variable reference since last clearing
|
2006-08-26 13:35:28 +02:00
|
|
|
|
2006-09-26 17:05:35 +02:00
|
|
|
public:
|
2022-01-01 17:46:49 +01:00
|
|
|
LifeBlock(LifeBlock* aboveLifep, LifeState* statep)
|
|
|
|
|
: m_aboveLifep{aboveLifep} // Null if top
|
|
|
|
|
, m_statep{statep} {}
|
2020-11-17 01:56:16 +01:00
|
|
|
~LifeBlock() = default;
|
2006-08-26 13:35:28 +02:00
|
|
|
// METHODS
|
2006-09-26 17:05:35 +02:00
|
|
|
void checkRemoveAssign(const LifeMap::iterator& it) {
|
2021-11-13 19:50:44 +01:00
|
|
|
const AstVar* const varp = it->first->varp();
|
|
|
|
|
LifeVarEntry* const entp = &(it->second);
|
2022-10-20 12:31:00 +02:00
|
|
|
if (!varp->isSigPublic() && !varp->isUsedVirtIface()) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Rather than track what sigs AstUCFunc/AstUCStmt may change,
|
|
|
|
|
// we just don't optimize any public sigs
|
|
|
|
|
// Check the var entry, and remove if appropriate
|
2021-11-13 19:50:44 +01:00
|
|
|
if (AstNode* const oldassp = entp->assignp()) {
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(7, " PREV: " << oldassp << endl);
|
2019-05-19 22:13:13 +02:00
|
|
|
// Redundant assignment, in same level block
|
|
|
|
|
// Don't delete it now as it will confuse iteration since it maybe WAY
|
|
|
|
|
// above our current iteration point.
|
2022-11-27 14:31:22 +01:00
|
|
|
if (debug() > 4) oldassp->dumpTree("- REMOVE/SAMEBLK: ");
|
2019-05-19 22:13:13 +02:00
|
|
|
entp->complexAssign();
|
2020-01-17 02:17:11 +01:00
|
|
|
VL_DO_DANGLING(m_statep->pushUnlinkDeletep(oldassp), oldassp);
|
2019-05-19 22:13:13 +02:00
|
|
|
++m_statep->m_statAssnDel;
|
|
|
|
|
}
|
|
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2006-09-26 17:05:35 +02:00
|
|
|
void simpleAssign(AstVarScope* nodep, AstNodeAssign* assp) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Do we have a old assignment we can nuke?
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, " ASSIGNof: " << nodep << endl);
|
|
|
|
|
UINFO(7, " new: " << assp << endl);
|
2020-08-16 17:43:49 +02:00
|
|
|
const auto it = m_map.find(nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
if (it != m_map.end()) {
|
|
|
|
|
checkRemoveAssign(it);
|
|
|
|
|
it->second.simpleAssign(assp);
|
|
|
|
|
} else {
|
2022-09-16 01:58:01 +02:00
|
|
|
m_map.emplace(nodep, LifeVarEntry{LifeVarEntry::SIMPLEASSIGN{}, assp});
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2020-04-15 13:58:34 +02:00
|
|
|
// lifeDump();
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2006-09-26 17:05:35 +02:00
|
|
|
void complexAssign(AstVarScope* nodep) {
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, " clearof: " << nodep << endl);
|
2020-08-16 17:43:49 +02:00
|
|
|
const auto it = m_map.find(nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
if (it != m_map.end()) {
|
|
|
|
|
it->second.complexAssign();
|
|
|
|
|
} else {
|
2022-09-16 01:58:01 +02:00
|
|
|
m_map.emplace(nodep, LifeVarEntry{LifeVarEntry::COMPLEXASSIGN{}});
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-24 21:44:00 +02:00
|
|
|
void clearReplaced() { m_replacedVref = false; }
|
|
|
|
|
bool replaced() const { return m_replacedVref; }
|
2018-08-25 15:52:45 +02:00
|
|
|
void varUsageReplace(AstVarScope* nodep, AstVarRef* varrefp) {
|
2022-03-31 02:17:59 +02:00
|
|
|
// Variable rvalue. If it references a constant, we can replace it
|
2020-08-16 17:43:49 +02:00
|
|
|
const auto it = m_map.find(nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
if (it != m_map.end()) {
|
2021-11-13 19:50:44 +01:00
|
|
|
if (AstConst* const constp = it->second.constNodep()) {
|
2022-10-20 12:31:00 +02:00
|
|
|
if (!varrefp->varp()->isSigPublic() && !varrefp->varp()->isUsedVirtIface()) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Aha, variable is constant; substitute in.
|
|
|
|
|
// We'll later constant propagate
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, " replaceconst: " << varrefp << endl);
|
2019-05-19 22:13:13 +02:00
|
|
|
varrefp->replaceWith(constp->cloneTree(false));
|
2022-09-24 21:44:00 +02:00
|
|
|
m_replacedVref = true;
|
2020-01-17 02:17:11 +01:00
|
|
|
VL_DO_DANGLING(varrefp->deleteTree(), varrefp);
|
2019-05-19 22:13:13 +02:00
|
|
|
++m_statep->m_statAssnCon;
|
|
|
|
|
return; // **DONE, no longer a var reference**
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, " usage: " << nodep << endl);
|
2019-05-19 22:13:13 +02:00
|
|
|
it->second.consumed();
|
|
|
|
|
} else {
|
2022-09-16 01:58:01 +02:00
|
|
|
m_map.emplace(nodep, LifeVarEntry{LifeVarEntry::CONSUMED{}});
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2006-09-26 17:05:35 +02:00
|
|
|
void complexAssignFind(AstVarScope* nodep) {
|
2020-08-16 17:43:49 +02:00
|
|
|
const auto it = m_map.find(nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
if (it != m_map.end()) {
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, " casfind: " << it->first << endl);
|
2019-05-19 22:13:13 +02:00
|
|
|
it->second.complexAssign();
|
|
|
|
|
} else {
|
2022-09-16 01:58:01 +02:00
|
|
|
m_map.emplace(nodep, LifeVarEntry{LifeVarEntry::COMPLEXASSIGN{}});
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-09-26 17:05:35 +02:00
|
|
|
}
|
|
|
|
|
void consumedFind(AstVarScope* nodep) {
|
2020-08-16 17:43:49 +02:00
|
|
|
const auto it = m_map.find(nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
if (it != m_map.end()) {
|
|
|
|
|
it->second.consumed();
|
|
|
|
|
} else {
|
2022-09-16 01:58:01 +02:00
|
|
|
m_map.emplace(nodep, LifeVarEntry{LifeVarEntry::CONSUMED{}});
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-09-26 17:05:35 +02:00
|
|
|
}
|
|
|
|
|
void lifeToAbove() {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Any varrefs under a if/else branch affect statements outside and after the if/else
|
|
|
|
|
if (!m_aboveLifep) v3fatalSrc("Pushing life when already at the top level");
|
2022-06-16 00:49:32 +02:00
|
|
|
for (auto& itr : m_map) {
|
|
|
|
|
AstVarScope* const nodep = itr.first;
|
2019-05-19 22:13:13 +02:00
|
|
|
m_aboveLifep->complexAssignFind(nodep);
|
2022-06-16 00:49:32 +02:00
|
|
|
if (itr.second.everSet()) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Record there may be an assignment, so we don't constant propagate across the if.
|
|
|
|
|
complexAssignFind(nodep);
|
|
|
|
|
} else {
|
|
|
|
|
// Record consumption, so we don't eliminate earlier assignments
|
|
|
|
|
consumedFind(nodep);
|
|
|
|
|
}
|
|
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2018-08-25 15:52:45 +02:00
|
|
|
void dualBranch(LifeBlock* life1p, LifeBlock* life2p) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Find any common sets on both branches of IF and propagate upwards
|
2020-04-15 13:58:34 +02:00
|
|
|
// life1p->lifeDump();
|
|
|
|
|
// life2p->lifeDump();
|
2019-05-19 22:13:13 +02:00
|
|
|
AstNode::user1ClearTree(); // user1p() used on entire tree
|
2022-06-16 00:49:32 +02:00
|
|
|
for (auto& itr : life1p->m_map) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// When the if branch sets a var before it's used, mark that variable
|
2022-06-16 00:49:32 +02:00
|
|
|
if (itr.second.setBeforeUse()) itr.first->user1(1);
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2022-06-16 00:49:32 +02:00
|
|
|
for (auto& itr : life2p->m_map) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// When the else branch sets a var before it's used
|
2022-06-16 00:49:32 +02:00
|
|
|
AstVarScope* const nodep = itr.first;
|
|
|
|
|
if (itr.second.setBeforeUse() && nodep->user1()) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Both branches set the var, we can remove the assignment before the IF.
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, "DUALBRANCH " << nodep << endl);
|
2020-08-16 17:43:49 +02:00
|
|
|
const auto itab = m_map.find(nodep);
|
2020-04-15 13:58:34 +02:00
|
|
|
if (itab != m_map.end()) checkRemoveAssign(itab);
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-04-15 13:58:34 +02:00
|
|
|
// this->lifeDump();
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-06-16 00:11:03 +02:00
|
|
|
void clear() { m_map.clear(); }
|
2006-09-26 17:05:35 +02:00
|
|
|
// DEBUG
|
|
|
|
|
void lifeDump() {
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(5, " LifeMap:" << endl);
|
2022-06-16 00:49:32 +02:00
|
|
|
for (const auto& itr : m_map) {
|
|
|
|
|
UINFO(5, " Ent: " << (itr.second.setBeforeUse() ? "[F] " : " ") << itr.first
|
2020-04-15 13:58:34 +02:00
|
|
|
<< endl);
|
2022-06-16 00:49:32 +02:00
|
|
|
if (itr.second.assignp()) { //
|
|
|
|
|
UINFO(5, " Ass: " << itr.second.assignp() << endl);
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2006-09-26 17:05:35 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//######################################################################
|
|
|
|
|
// Life state, as a visitor of each AstNode
|
2006-08-26 13:35:28 +02:00
|
|
|
|
2022-01-02 19:56:40 +01:00
|
|
|
class LifeVisitor final : public VNVisitor {
|
2006-09-26 17:05:35 +02:00
|
|
|
private:
|
|
|
|
|
// STATE
|
2022-01-01 17:46:49 +01:00
|
|
|
LifeState* const m_statep; // Current state
|
2020-08-15 19:11:27 +02:00
|
|
|
bool m_sideEffect = false; // Side effects discovered in assign RHS
|
|
|
|
|
bool m_noopt = false; // Disable optimization of variables in this block
|
|
|
|
|
bool m_tracingCall = false; // Iterating into a CCall to a CFunc
|
2006-09-26 17:05:35 +02:00
|
|
|
|
|
|
|
|
// LIFE MAP
|
|
|
|
|
// For each basic block, we'll make a new map of what variables that if/else is changing
|
2021-03-13 00:10:45 +01:00
|
|
|
using LifeMap = std::unordered_map<AstVarScope*, LifeVarEntry>;
|
2011-08-05 03:58:45 +02:00
|
|
|
// cppcheck-suppress memleak // cppcheck bug - it is deleted
|
2020-04-15 13:58:34 +02:00
|
|
|
LifeBlock* m_lifep; // Current active lifetime map for current scope
|
2006-09-26 17:05:35 +02:00
|
|
|
|
|
|
|
|
// METHODS
|
2022-06-16 00:11:03 +02:00
|
|
|
void setNoopt() {
|
|
|
|
|
m_noopt = true;
|
|
|
|
|
m_lifep->clear();
|
|
|
|
|
}
|
2009-01-21 22:56:50 +01:00
|
|
|
|
2006-09-26 17:05:35 +02:00
|
|
|
// VISITORS
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstVarRef* nodep) override {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Consumption/generation of a variable,
|
|
|
|
|
// it's used so can't elim assignment before this use.
|
2020-08-15 16:12:55 +02:00
|
|
|
UASSERT_OBJ(nodep->varScopep(), nodep, "nullptr");
|
2019-05-19 22:13:13 +02:00
|
|
|
//
|
2021-11-13 19:50:44 +01:00
|
|
|
AstVarScope* const vscp = nodep->varScopep();
|
2019-07-06 18:57:50 +02:00
|
|
|
UASSERT_OBJ(vscp, nodep, "Scope not assigned");
|
2020-11-07 16:37:55 +01:00
|
|
|
if (nodep->access().isWriteOrRW()) {
|
2019-07-06 02:09:56 +02:00
|
|
|
m_sideEffect = true; // $sscanf etc may have RHS vars that are lvalues
|
2019-05-19 22:13:13 +02:00
|
|
|
m_lifep->complexAssign(vscp);
|
|
|
|
|
} else {
|
2020-01-17 02:17:11 +01:00
|
|
|
VL_DO_DANGLING(m_lifep->varUsageReplace(vscp, nodep), nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstNodeAssign* nodep) override {
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
if (nodep->isTimingControl()) {
|
|
|
|
|
// V3Life doesn't understand time sense - don't optimize
|
|
|
|
|
setNoopt();
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
// Collect any used variables first, as lhs may also be on rhs
|
|
|
|
|
// Similar code in V3Dead
|
|
|
|
|
m_sideEffect = false;
|
2022-09-24 21:44:00 +02:00
|
|
|
m_lifep->clearReplaced();
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateAndNextNull(nodep->rhsp());
|
2022-09-24 21:44:00 +02:00
|
|
|
if (m_lifep->replaced()) {
|
2019-05-19 22:13:13 +02:00
|
|
|
// We changed something, try to constant propagate, but don't delete the
|
|
|
|
|
// assignment as we still need nodep to remain.
|
|
|
|
|
V3Const::constifyEdit(nodep->rhsp()); // rhsp may change
|
|
|
|
|
}
|
|
|
|
|
// Has to be direct assignment without any EXTRACTing.
|
2018-02-02 03:32:58 +01:00
|
|
|
if (VN_IS(nodep->lhsp(), VarRef) && !m_sideEffect && !m_noopt) {
|
2021-11-13 19:50:44 +01:00
|
|
|
AstVarScope* const vscp = VN_AS(nodep->lhsp(), VarRef)->varScopep();
|
2019-07-06 18:57:50 +02:00
|
|
|
UASSERT_OBJ(vscp, nodep, "Scope lost on variable");
|
2019-05-19 22:13:13 +02:00
|
|
|
m_lifep->simpleAssign(vscp, nodep);
|
|
|
|
|
} else {
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateAndNextNull(nodep->lhsp());
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstAssignDly* nodep) override {
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
// V3Life doesn't understand time sense
|
|
|
|
|
if (nodep->isTimingControl()) {
|
|
|
|
|
// Don't optimize
|
|
|
|
|
setNoopt();
|
|
|
|
|
}
|
|
|
|
|
// Don't treat as normal assign
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateChildren(nodep);
|
2006-09-26 17:05:35 +02:00
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
|
2008-06-10 03:25:10 +02:00
|
|
|
//---- Track control flow changes
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstNodeIf* nodep) override {
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, " IF " << nodep << endl);
|
2019-05-19 22:13:13 +02:00
|
|
|
// Condition is part of PREVIOUS block
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateAndNextNull(nodep->condp());
|
2021-11-13 19:50:44 +01:00
|
|
|
LifeBlock* const prevLifep = m_lifep;
|
2022-11-20 19:11:01 +01:00
|
|
|
LifeBlock* const ifLifep = new LifeBlock{prevLifep, m_statep};
|
|
|
|
|
LifeBlock* const elseLifep = new LifeBlock{prevLifep, m_statep};
|
2019-05-19 22:13:13 +02:00
|
|
|
{
|
|
|
|
|
m_lifep = ifLifep;
|
2022-09-15 20:43:56 +02:00
|
|
|
iterateAndNextNull(nodep->thensp());
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
m_lifep = elseLifep;
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateAndNextNull(nodep->elsesp());
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2019-10-25 03:48:45 +02:00
|
|
|
m_lifep = prevLifep;
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, " join " << endl);
|
2019-05-19 22:13:13 +02:00
|
|
|
// Find sets on both flows
|
2018-08-25 15:52:45 +02:00
|
|
|
m_lifep->dualBranch(ifLifep, elseLifep);
|
2019-05-19 22:13:13 +02:00
|
|
|
// For the next assignments, clear any variables that were read or written in the block
|
|
|
|
|
ifLifep->lifeToAbove();
|
|
|
|
|
elseLifep->lifeToAbove();
|
2020-01-17 02:17:11 +01:00
|
|
|
VL_DO_DANGLING(delete ifLifep, ifLifep);
|
|
|
|
|
VL_DO_DANGLING(delete elseLifep, elseLifep);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstWhile* nodep) override {
|
2019-05-19 22:13:13 +02:00
|
|
|
// While's are a problem, as we don't allow loops in the graph. We
|
|
|
|
|
// may go around the cond/body multiple times. Thus a
|
|
|
|
|
// lifelication just in the body is ok, but we can't delete an
|
|
|
|
|
// assignment in the body that's used in the cond. (And otherwise
|
|
|
|
|
// would because it only appears used after-the-fact. So, we model
|
|
|
|
|
// it as a IF statement, and just don't allow elimination of
|
|
|
|
|
// variables across the body.
|
2021-11-13 19:50:44 +01:00
|
|
|
LifeBlock* const prevLifep = m_lifep;
|
2022-11-20 19:11:01 +01:00
|
|
|
LifeBlock* const condLifep = new LifeBlock{prevLifep, m_statep};
|
|
|
|
|
LifeBlock* const bodyLifep = new LifeBlock{prevLifep, m_statep};
|
2019-05-19 22:13:13 +02:00
|
|
|
{
|
|
|
|
|
m_lifep = condLifep;
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateAndNextNull(nodep->precondsp());
|
|
|
|
|
iterateAndNextNull(nodep->condp());
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
m_lifep = bodyLifep;
|
2022-09-15 20:43:56 +02:00
|
|
|
iterateAndNextNull(nodep->stmtsp());
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateAndNextNull(nodep->incsp());
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2019-10-25 04:19:46 +02:00
|
|
|
m_lifep = prevLifep;
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, " joinfor" << endl);
|
2019-05-19 22:13:13 +02:00
|
|
|
// For the next assignments, clear any variables that were read or written in the block
|
|
|
|
|
condLifep->lifeToAbove();
|
|
|
|
|
bodyLifep->lifeToAbove();
|
2020-01-17 02:17:11 +01:00
|
|
|
VL_DO_DANGLING(delete condLifep, condLifep);
|
|
|
|
|
VL_DO_DANGLING(delete bodyLifep, bodyLifep);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstJumpBlock* nodep) override {
|
2019-05-19 22:13:13 +02:00
|
|
|
// As with While's we can't predict if a JumpGo will kill us or not
|
|
|
|
|
// It's worse though as an IF(..., JUMPGO) may change the control flow.
|
|
|
|
|
// Just don't optimize blocks with labels; they're rare - so far.
|
2021-11-13 19:50:44 +01:00
|
|
|
LifeBlock* const prevLifep = m_lifep;
|
2022-11-20 19:11:01 +01:00
|
|
|
LifeBlock* const bodyLifep = new LifeBlock{prevLifep, m_statep};
|
2019-05-19 22:13:13 +02:00
|
|
|
{
|
2022-06-16 00:49:32 +02:00
|
|
|
VL_RESTORER(m_noopt);
|
2019-05-19 22:13:13 +02:00
|
|
|
m_lifep = bodyLifep;
|
2022-06-16 00:11:03 +02:00
|
|
|
setNoopt();
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateAndNextNull(nodep->stmtsp());
|
2019-05-19 22:13:13 +02:00
|
|
|
m_lifep = prevLifep;
|
|
|
|
|
}
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, " joinjump" << endl);
|
2019-05-19 22:13:13 +02:00
|
|
|
// For the next assignments, clear any variables that were read or written in the block
|
|
|
|
|
bodyLifep->lifeToAbove();
|
2020-01-17 02:17:11 +01:00
|
|
|
VL_DO_DANGLING(delete bodyLifep, bodyLifep);
|
2010-02-14 16:01:21 +01:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstNodeCCall* nodep) override {
|
2020-04-15 13:58:34 +02:00
|
|
|
// UINFO(4, " CCALL " << nodep << endl);
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateChildren(nodep);
|
2019-05-19 22:13:13 +02:00
|
|
|
// Enter the function and trace it
|
2020-04-15 13:58:34 +02:00
|
|
|
// else is non-inline or public function we optimize separately
|
|
|
|
|
if (!nodep->funcp()->entryPoint()) {
|
2017-11-29 00:38:19 +01:00
|
|
|
m_tracingCall = true;
|
2018-05-11 02:55:37 +02:00
|
|
|
iterate(nodep->funcp());
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstCFunc* nodep) override {
|
2020-04-15 13:58:34 +02:00
|
|
|
// UINFO(4, " CFUNC " << nodep << endl);
|
2017-11-29 00:38:19 +01:00
|
|
|
if (!m_tracingCall && !nodep->entryPoint()) return;
|
|
|
|
|
m_tracingCall = false;
|
2023-09-08 08:51:19 +02:00
|
|
|
if (nodep->dpiImportPrototype() && !nodep->dpiPure()) {
|
2019-05-19 22:13:13 +02:00
|
|
|
m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment
|
|
|
|
|
}
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateChildren(nodep);
|
2009-12-03 12:55:29 +01:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstUCFunc* nodep) override {
|
2019-05-19 22:13:13 +02:00
|
|
|
m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateChildren(nodep);
|
2006-09-26 19:05:08 +02:00
|
|
|
}
|
2022-10-12 11:19:21 +02:00
|
|
|
void visit(AstCExpr* nodep) override {
|
2019-05-19 22:13:13 +02:00
|
|
|
m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment
|
2018-05-11 02:55:37 +02:00
|
|
|
iterateChildren(nodep);
|
2009-12-03 12:55:29 +01:00
|
|
|
}
|
2006-09-26 17:05:35 +02:00
|
|
|
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstVar*) override {} // Don't want varrefs under it
|
2022-09-16 17:15:10 +02:00
|
|
|
void visit(AstNode* nodep) override {
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
if (nodep->isTimingControl()) {
|
|
|
|
|
// V3Life doesn't understand time sense - don't optimize
|
|
|
|
|
setNoopt();
|
|
|
|
|
}
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
// CONSTRUCTORS
|
2022-01-01 17:46:49 +01:00
|
|
|
LifeVisitor(AstNode* nodep, LifeState* statep)
|
|
|
|
|
: m_statep{statep} {
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(4, " LifeVisitor on " << nodep << endl);
|
2019-05-19 22:13:13 +02:00
|
|
|
{
|
2022-11-20 19:11:01 +01:00
|
|
|
m_lifep = new LifeBlock{nullptr, m_statep};
|
2018-05-11 02:55:37 +02:00
|
|
|
iterate(nodep);
|
2020-08-15 16:12:55 +02:00
|
|
|
if (m_lifep) VL_DO_CLEAR(delete m_lifep, m_lifep = nullptr);
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
~LifeVisitor() override {
|
2020-08-15 16:12:55 +02:00
|
|
|
if (m_lifep) VL_DO_CLEAR(delete m_lifep, m_lifep = nullptr);
|
2015-10-04 04:33:06 +02:00
|
|
|
}
|
2020-01-09 01:33:47 +01:00
|
|
|
VL_UNCOPYABLE(LifeVisitor);
|
2006-09-26 17:05:35 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//######################################################################
|
|
|
|
|
|
2022-01-02 19:56:40 +01:00
|
|
|
class LifeTopVisitor final : public VNVisitor {
|
2006-09-26 17:05:35 +02:00
|
|
|
// Visit all top nodes searching for functions that are entry points we want to start
|
|
|
|
|
// finding code within.
|
|
|
|
|
private:
|
|
|
|
|
// STATE
|
2021-11-13 19:50:44 +01:00
|
|
|
LifeState* const m_statep; // Current state
|
2006-09-26 17:05:35 +02:00
|
|
|
|
|
|
|
|
// VISITORS
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstCFunc* nodep) override {
|
2019-05-19 22:13:13 +02:00
|
|
|
if (nodep->entryPoint()) {
|
|
|
|
|
// Usage model 1: Simulate all C code, doing lifetime analysis
|
2021-11-26 16:52:36 +01:00
|
|
|
LifeVisitor{nodep, m_statep};
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2006-09-26 17:05:35 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstNodeProcedure* nodep) override {
|
2019-05-19 22:13:13 +02:00
|
|
|
// Usage model 2: Cleanup basic blocks
|
2021-11-26 16:52:36 +01:00
|
|
|
LifeVisitor{nodep, m_statep};
|
2006-09-26 19:05:08 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstVar*) override {} // Accelerate
|
|
|
|
|
void visit(AstNodeStmt*) override {} // Accelerate
|
2022-10-12 11:19:21 +02:00
|
|
|
void visit(AstNodeExpr*) override {} // Accelerate
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
2020-04-04 14:31:14 +02:00
|
|
|
|
2006-09-26 17:05:35 +02:00
|
|
|
public:
|
|
|
|
|
// CONSTRUCTORS
|
2020-08-15 19:11:27 +02:00
|
|
|
LifeTopVisitor(AstNetlist* nodep, LifeState* statep)
|
2020-08-16 15:55:36 +02:00
|
|
|
: m_statep{statep} {
|
2018-05-11 02:55:37 +02:00
|
|
|
iterate(nodep);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
~LifeTopVisitor() override = default;
|
2006-08-26 13:35:28 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//######################################################################
|
|
|
|
|
// Life class functions
|
|
|
|
|
|
|
|
|
|
void V3Life::lifeAll(AstNetlist* nodep) {
|
2020-04-15 13:58:34 +02:00
|
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
2018-03-10 18:57:50 +01:00
|
|
|
{
|
|
|
|
|
LifeState state;
|
2021-11-26 16:52:36 +01:00
|
|
|
LifeTopVisitor{nodep, &state};
|
2018-03-10 18:57:50 +01:00
|
|
|
} // Destruct before checking
|
2023-10-09 11:50:31 +02:00
|
|
|
VIsCached::clearCacheTree(); // Removing assignments may affect isPure
|
2023-05-04 00:04:10 +02:00
|
|
|
V3Global::dumpCheckGlobalTree("life", 0, dumpTreeLevel() >= 3);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|