Merge branch 'master' into develop-v5

This commit is contained in:
Geza Lore 2022-08-02 11:13:04 +01:00
commit ba66fa7200
3 changed files with 416 additions and 371 deletions

View File

@ -1345,25 +1345,27 @@ inline std::ostream& operator<<(std::ostream& os, const VNRelinker& rhs) {
return os;
}
//######################################################################
// Callback base class to determine if node matches some formula
// ######################################################################
// Callback base class to determine if node matches some formula
class VNodeMatcher VL_NOT_FINAL {
public:
virtual bool nodeMatch(const AstNode* nodep) const { return true; }
};
//######################################################################
// AstNode -- Base type of all Ast types
// ######################################################################
// AstNode -- Base type of all Ast types
// Prefetch a node.
#define ASTNODE_PREFETCH_NON_NULL(nodep) \
do { \
VL_PREFETCH_RD(&((nodep)->m_nextp)); \
VL_PREFETCH_RD(&((nodep)->m_type)); \
} while (false)
// The if() makes it faster, even though prefetch won't fault on null pointers
#define ASTNODE_PREFETCH(nodep) \
do { \
if (nodep) { \
VL_PREFETCH_RD(&((nodep)->m_nextp)); \
VL_PREFETCH_RD(&((nodep)->m_type)); \
} \
if (nodep) ASTNODE_PREFETCH_NON_NULL(nodep); \
} while (false)
class AstNode VL_NOT_FINAL {
@ -1870,12 +1872,6 @@ private:
// Note: specializations for particular node types are provided by 'astgen'
template <typename T> inline static bool privateTypeTest(const AstNode* nodep);
// For internal use only.
// Note: specializations for particular node types are provided below
template <typename T_Node> inline static bool privateMayBeUnder(const AstNode* nodep) {
return true;
}
// For internal use only.
template <typename TargetType, typename DeclType> constexpr static bool uselessCast() {
using NonRef = typename std::remove_reference<DeclType>::type;
@ -1934,102 +1930,39 @@ public:
// Predicate that returns true if the given 'nodep' might have a descendant of type 'T_Node'.
// This is conservative and is used to speed up traversals.
template <typename T_Node> inline static bool mayBeUnder(const AstNode* nodep) {
// Note: specializations for particular node types are provided below
template <typename T_Node> static bool mayBeUnder(const AstNode* nodep) {
static_assert(!std::is_const<T_Node>::value,
"Type parameter 'T_Node' should not be const qualified");
static_assert(std::is_base_of<AstNode, T_Node>::value,
"Type parameter 'T_Node' must be a subtype of AstNode");
return privateMayBeUnder<T_Node>(nodep);
return true;
}
// Predicate that is true for node subtypes 'T_Node' that do not have any children
// This is conservative and is used to speed up traversals.
// Note: specializations for particular node types are provided below
template <typename T_Node> static constexpr bool isLeaf() {
static_assert(!std::is_const<T_Node>::value,
"Type parameter 'T_Node' should not be const qualified");
static_assert(std::is_base_of<AstNode, T_Node>::value,
"Type parameter 'T_Node' must be a subtype of AstNode");
return false;
}
private:
template <typename T_Arg, bool VisitNext>
static void foreachImpl(
// Using std::conditional for const correctness in the public 'foreach' functions
typename std::conditional<std::is_const<T_Arg>::value, const AstNode*, AstNode*>::type
nodep,
std::function<void(T_Arg*)> f) {
// Using std::conditional for const correctness in the public 'foreach' functions
template <typename T_Arg>
using ConstCorrectAstNode =
typename std::conditional<std::is_const<T_Arg>::value, const AstNode, AstNode>::type;
// Note: Using a loop to iterate the nextp() chain, instead of tail recursion, because
// debug builds don't eliminate tail calls, causing stack overflow on long lists of nodes.
do {
// Prefetch children and next
ASTNODE_PREFETCH(nodep->op1p());
ASTNODE_PREFETCH(nodep->op2p());
ASTNODE_PREFETCH(nodep->op3p());
ASTNODE_PREFETCH(nodep->op4p());
if VL_CONSTEXPR_CXX17 (VisitNext) ASTNODE_PREFETCH(nodep->nextp());
template <typename T_Arg>
inline static void foreachImpl(ConstCorrectAstNode<T_Arg>* nodep,
const std::function<void(T_Arg*)>& f, bool visitNext);
// Apply function in pre-order
if (privateTypeTest<typename std::remove_const<T_Arg>::type>(nodep)) {
f(static_cast<T_Arg*>(nodep));
}
// Traverse children (including their 'nextp()' chains), unless futile
if (mayBeUnder<typename std::remove_const<T_Arg>::type>(nodep)) {
if (AstNode* const op1p = nodep->op1p()) foreachImpl<T_Arg, true>(op1p, f);
if (AstNode* const op2p = nodep->op2p()) foreachImpl<T_Arg, true>(op2p, f);
if (AstNode* const op3p = nodep->op3p()) foreachImpl<T_Arg, true>(op3p, f);
if (AstNode* const op4p = nodep->op4p()) foreachImpl<T_Arg, true>(op4p, f);
}
// Traverse 'nextp()' chain if requested
if VL_CONSTEXPR_CXX17 (VisitNext) {
nodep = nodep->nextp();
} else {
break;
}
} while (nodep);
}
template <typename T_Arg, bool Default, bool VisitNext>
static bool predicateImpl(
// Using std::conditional for const correctness in the public 'foreach' functions
typename std::conditional<std::is_const<T_Arg>::value, const AstNode*, AstNode*>::type
nodep,
std::function<bool(T_Arg*)> p) {
// Note: Using a loop to iterate the nextp() chain, instead of tail recursion, because
// debug builds don't eliminate tail calls, causing stack overflow on long lists of nodes.
do {
// Prefetch children and next
ASTNODE_PREFETCH(nodep->op1p());
ASTNODE_PREFETCH(nodep->op2p());
ASTNODE_PREFETCH(nodep->op3p());
ASTNODE_PREFETCH(nodep->op4p());
if VL_CONSTEXPR_CXX17 (VisitNext) ASTNODE_PREFETCH(nodep->nextp());
// Apply function in pre-order
if (privateTypeTest<typename std::remove_const<T_Arg>::type>(nodep)) {
if (p(static_cast<T_Arg*>(nodep)) != Default) return !Default;
}
// Traverse children (including their 'nextp()' chains), unless futile
if (mayBeUnder<typename std::remove_const<T_Arg>::type>(nodep)) {
if (AstNode* const op1p = nodep->op1p()) {
if (predicateImpl<T_Arg, Default, true>(op1p, p) != Default) return !Default;
}
if (AstNode* const op2p = nodep->op2p()) {
if (predicateImpl<T_Arg, Default, true>(op2p, p) != Default) return !Default;
}
if (AstNode* const op3p = nodep->op3p()) {
if (predicateImpl<T_Arg, Default, true>(op3p, p) != Default) return !Default;
}
if (AstNode* const op4p = nodep->op4p()) {
if (predicateImpl<T_Arg, Default, true>(op4p, p) != Default) return !Default;
}
}
// Traverse 'nextp()' chain if requested
if VL_CONSTEXPR_CXX17 (VisitNext) {
nodep = nodep->nextp();
} else {
break;
}
} while (nodep);
return Default;
}
template <typename T_Arg, bool Default>
inline static bool predicateImpl(ConstCorrectAstNode<T_Arg>* nodep,
const std::function<bool(T_Arg*)>& p);
template <typename T_Node> constexpr static bool checkTypeParameter() {
static_assert(!std::is_const<T_Node>::value,
@ -2041,31 +1974,32 @@ private:
public:
// Traverse subtree and call given function 'f' in pre-order on each node that has type
// 'T_Node'. Prefer 'foreach' over simple VNVisitor that only needs to handle a single (or a
// few) node types, as it's easier to write, but more importantly, the dispatch to the
// operation function in 'foreach' should be completely predictable by branch target caches in
// modern CPUs, while it is basically unpredictable for VNVisitor.
// 'T_Node'. The node passd to the function 'f' can be removed or replaced, but other editing
// of the iterated tree is not safe. Prefer 'foreach' over simple VNVisitor that only needs to
// handle a single (or a few) node types, as it's easier to write, but more importantly, the
// dispatch to the operation function in 'foreach' should be completely predictable by branch
// target caches in modern CPUs, while it is basically unpredictable for VNVisitor.
template <typename T_Node> void foreach (std::function<void(T_Node*)> f) {
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
foreachImpl<T_Node, /* VisitNext: */ false>(this, f);
foreachImpl<T_Node>(this, f, /* visitNext: */ false);
}
// Same as above, but for 'const' nodes
template <typename T_Node> void foreach (std::function<void(const T_Node*)> f) const {
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
foreachImpl<const T_Node, /* VisitNext: */ false>(this, f);
foreachImpl<const T_Node>(this, f, /* visitNext: */ false);
}
// Same as 'foreach' but also follows 'this->nextp()'
template <typename T_Node> void foreachAndNext(std::function<void(T_Node*)> f) {
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
foreachImpl<T_Node, /* VisitNext: */ true>(this, f);
foreachImpl<T_Node>(this, f, /* visitNext: */ true);
}
// Same as 'foreach' but also follows 'this->nextp()'
template <typename T_Node> void foreachAndNext(std::function<void(const T_Node*)> f) const {
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
foreachImpl<const T_Node, /* VisitNext: */ true>(this, f);
foreachImpl<const T_Node>(this, f, /* visitNext: */ true);
}
// Given a predicate function 'p' return true if and only if there exists a node of type
@ -2074,13 +2008,13 @@ public:
// result can be determined.
template <typename T_Node> bool exists(std::function<bool(T_Node*)> p) {
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
return predicateImpl<T_Node, /* Default: */ false, /* VisitNext: */ false>(this, p);
return predicateImpl<T_Node, /* Default: */ false>(this, p);
}
// Same as above, but for 'const' nodes
template <typename T_Node> void exists(std::function<bool(const T_Node*)> p) const {
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
return predicateImpl<const T_Node, /* Default: */ false, /* VisitNext: */ false>(this, p);
return predicateImpl<const T_Node, /* Default: */ false>(this, p);
}
// Given a predicate function 'p' return true if and only if all nodes of type
@ -2089,13 +2023,13 @@ public:
// result can be determined.
template <typename T_Node> bool forall(std::function<bool(T_Node*)> p) {
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
return predicateImpl<T_Node, /* Default: */ true, /* VisitNext: */ false>(this, p);
return predicateImpl<T_Node, /* Default: */ true>(this, p);
}
// Same as above, but for 'const' nodes
template <typename T_Node> void forall(std::function<bool(const T_Node*)> p) const {
static_assert(checkTypeParameter<T_Node>(), "Invalid type parameter 'T_Node'");
return predicateImpl<const T_Node, /* Default: */ true, /* VisitNext: */ false>(this, p);
return predicateImpl<const T_Node, /* Default: */ true>(this, p);
}
int nodeCount() const {
@ -2109,31 +2043,210 @@ public:
// Specialisations of privateTypeTest
#include "V3Ast__gen_impl.h" // From ./astgen
// Specializations of privateMayBeUnder
template <> inline bool AstNode::privateMayBeUnder<AstCell>(const AstNode* nodep) {
// Specializations of AstNode::mayBeUnder
template <> inline bool AstNode::mayBeUnder<AstCell>(const AstNode* nodep) {
return !VN_IS(nodep, NodeStmt) && !VN_IS(nodep, NodeMath);
}
template <> inline bool AstNode::privateMayBeUnder<AstNodeAssign>(const AstNode* nodep) {
template <> inline bool AstNode::mayBeUnder<AstNodeAssign>(const AstNode* nodep) {
return !VN_IS(nodep, NodeMath);
}
template <> inline bool AstNode::privateMayBeUnder<AstVarScope>(const AstNode* nodep) {
return !VN_IS(nodep, NodeStmt) && !VN_IS(nodep, NodeMath);
template <> inline bool AstNode::mayBeUnder<AstVarScope>(const AstNode* nodep) {
if (VN_IS(nodep, VarScope)) return false; // Should not nest
if (VN_IS(nodep, Var)) return false;
if (VN_IS(nodep, Active)) return false;
if (VN_IS(nodep, NodeStmt)) return false;
if (VN_IS(nodep, NodeMath)) return false;
return true;
}
template <> inline bool AstNode::privateMayBeUnder<AstExecGraph>(const AstNode* nodep) {
template <> inline bool AstNode::mayBeUnder<AstExecGraph>(const AstNode* nodep) {
if (VN_IS(nodep, ExecGraph)) return false; // Should not nest
if (VN_IS(nodep, NodeStmt)) return false; // Should be directly under CFunc
return true;
}
template <> inline bool AstNode::privateMayBeUnder<AstActive>(const AstNode* nodep) {
template <> inline bool AstNode::mayBeUnder<AstActive>(const AstNode* nodep) {
return !VN_IS(nodep, Active); // AstActives do not nest
}
template <> inline bool AstNode::privateMayBeUnder<AstScope>(const AstNode* nodep) {
template <> inline bool AstNode::mayBeUnder<AstScope>(const AstNode* nodep) {
return !VN_IS(nodep, Scope); // AstScopes do not nest
}
template <> inline bool AstNode::privateMayBeUnder<AstSenTree>(const AstNode* nodep) {
template <> inline bool AstNode::mayBeUnder<AstSenTree>(const AstNode* nodep) {
return !VN_IS(nodep, SenTree); // AstSenTree do not nest
}
// Specializations of AstNode::isLeaf
template <> constexpr bool AstNode::isLeaf<AstNodeVarRef>() { return true; }
template <> constexpr bool AstNode::isLeaf<AstVarRef>() { return true; }
template <> constexpr bool AstNode::isLeaf<AstVarXRef>() { return true; }
// foreach implementation
template <typename T_Arg>
void AstNode::foreachImpl(ConstCorrectAstNode<T_Arg>* nodep, const std::function<void(T_Arg*)>& f,
bool visitNext) {
// Checking the function is bound up front eliminates this check from the loop at invocation
if (!f) {
nodep->v3fatal("AstNode::foreach called with unbound function"); // LCOV_EXCL_LINE
} else {
// Pre-order traversal implemented directly (without recursion) for speed reasons. The very
// first iteration (the one that operates on the input nodep) is special, as we might or
// might not need to enqueue nodep->nextp() depending on VisitNext, while in all other
// iterations, we do want to enqueue nodep->nextp(). Duplicating code (via
// 'foreachImplVisit') for the initial iteration here to avoid an extra branch in the loop
using T_Arg_NonConst = typename std::remove_const<T_Arg>::type;
using Node = ConstCorrectAstNode<T_Arg>;
// Traversal stack
std::vector<Node*> stack; // Kept as a vector for easy resizing
Node** basep = nullptr; // Pointer to base of stack
Node** topp = nullptr; // Pointer to top of stack
Node** limp = nullptr; // Pointer to stack limit (when need growing)
// We prefetch this far into the stack
constexpr int prefetchDistance = 2;
// Grow stack to given size
const auto grow = [&](size_t size) VL_ATTR_ALWINLINE {
const ptrdiff_t occupancy = topp - basep;
stack.resize(size);
basep = stack.data() + prefetchDistance;
topp = basep + occupancy;
limp = basep + size - 5; // We push max 5 items per iteration
};
// Initial stack size
grow(32);
// We want some non-null pointers at the beginning. These will be prefetched, but not
// visited, so the root node will suffice. This eliminates needing branches in the loop.
for (int i = -prefetchDistance; i; ++i) basep[i] = nodep;
// Visit given node, enqueue children for traversal
const auto visit = [&](Node* currp) VL_ATTR_ALWINLINE {
// Type test this node
if (AstNode::privateTypeTest<T_Arg_NonConst>(currp)) {
// Call the client function
f(static_cast<T_Arg*>(currp));
// Short circuit if iterating leaf nodes
if VL_CONSTEXPR_CXX17 (isLeaf<T_Arg_NonConst>()) return;
}
// Enqueue children for traversal, unless futile
if (mayBeUnder<T_Arg_NonConst>(currp)) {
if (AstNode* const op4p = currp->op4p()) *topp++ = op4p;
if (AstNode* const op3p = currp->op3p()) *topp++ = op3p;
if (AstNode* const op2p = currp->op2p()) *topp++ = op2p;
if (AstNode* const op1p = currp->op1p()) *topp++ = op1p;
}
};
// Enqueue the next of the root node, if required
if (visitNext && nodep->nextp()) *topp++ = nodep->nextp();
// Visit the root node
visit(nodep);
// Visit the rest of the tree
while (VL_LIKELY(topp > basep)) {
// Pop next node in the traversal
Node* const headp = *--topp;
// Prefetch in case we are ascending the tree
ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]);
// Ensure we have stack space for nextp and the 4 children
if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2);
// Enqueue the next node
if (headp->nextp()) *topp++ = headp->nextp();
// Visit the head node
visit(headp);
}
}
}
// predicate implementation
template <typename T_Arg, bool Default>
bool AstNode::predicateImpl(ConstCorrectAstNode<T_Arg>* nodep,
const std::function<bool(T_Arg*)>& p) {
// Implementation similar to foreach, but abort traversal as soon as result is determined.
if (!p) {
nodep->v3fatal("AstNode::foreach called with unbound function"); // LCOV_EXCL_LINE
} else {
using T_Arg_NonConst = typename std::remove_const<T_Arg>::type;
using Node = ConstCorrectAstNode<T_Arg>;
// Traversal stack
std::vector<Node*> stack; // Kept as a vector for easy resizing
Node** basep = nullptr; // Pointer to base of stack
Node** topp = nullptr; // Pointer to top of stack
Node** limp = nullptr; // Pointer to stack limit (when need growing)
// We prefetch this far into the stack
constexpr int prefetchDistance = 2;
// Grow stack to given size
const auto grow = [&](size_t size) VL_ATTR_ALWINLINE {
const ptrdiff_t occupancy = topp - basep;
stack.resize(size);
basep = stack.data() + prefetchDistance;
topp = basep + occupancy;
limp = basep + size - 5; // We push max 5 items per iteration
};
// Initial stack size
grow(32);
// We want some non-null pointers at the beginning. These will be prefetched, but not
// visited, so the root node will suffice. This eliminates needing branches in the loop.
for (int i = -prefetchDistance; i; ++i) basep[i] = nodep;
// Visit given node, enqueue children for traversal, return true if result determined.
const auto visit = [&](Node* currp) VL_ATTR_ALWINLINE {
// Type test this node
if (AstNode::privateTypeTest<T_Arg_NonConst>(currp)) {
// Call the client function
if (p(static_cast<T_Arg*>(currp)) != Default) return true;
// Short circuit if iterating leaf nodes
if VL_CONSTEXPR_CXX17 (isLeaf<T_Arg_NonConst>()) return false;
}
// Enqueue children for traversal, unless futile
if (mayBeUnder<T_Arg_NonConst>(currp)) {
if (AstNode* const op4p = currp->op4p()) *topp++ = op4p;
if (AstNode* const op3p = currp->op3p()) *topp++ = op3p;
if (AstNode* const op2p = currp->op2p()) *topp++ = op2p;
if (AstNode* const op1p = currp->op1p()) *topp++ = op1p;
}
return false;
};
// Visit the root node
if (visit(nodep)) return !Default;
// Visit the rest of the tree
while (VL_LIKELY(topp > basep)) {
// Pop next node in the traversal
Node* const headp = *--topp;
// Prefetch in case we are ascending the tree
ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]);
// Ensure we have stack space for nextp and the 4 children
if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2);
// Enqueue the next node
if (headp->nextp()) *topp++ = headp->nextp();
// Visit the head node
if (visit(headp)) return !Default;
}
return Default;
}
}
inline std::ostream& operator<<(std::ostream& os, const AstNode* rhs) {
if (!rhs) {
os << "nullptr";

View File

@ -151,14 +151,14 @@ class ForceConvertVisitor final : public VNVisitor {
// referenced AstVarScope with the given function.
void transformWritenVarScopes(AstNode* nodep, std::function<AstVarScope*(AstVarScope*)> f) {
UASSERT_OBJ(nodep->backp(), nodep, "Must have backp, otherwise will be lost if replaced");
nodep->foreach<AstNodeVarRef>([this, &f](AstNodeVarRef* refp) {
nodep->foreach<AstNodeVarRef>([&f](AstNodeVarRef* refp) {
if (refp->access() != VAccess::WRITE) return;
// TODO: this is not strictly speaking safe for some complicated lvalues, eg.:
// 'force foo[a(cnt)] = 1;', where 'cnt' is an out parameter, but it will
// do for now...
refp->replaceWith(
new AstVarRef{refp->fileline(), f(refp->varScopep()), VAccess::WRITE});
pushDeletep(refp);
VL_DO_DANGLING(refp->deleteTree(), refp);
});
}
@ -238,7 +238,7 @@ class ForceConvertVisitor final : public VNVisitor {
flp->warnOff(V3ErrorCode::BLKANDNBLK, true);
AstVarRef* const newpRefp = new AstVarRef{flp, newVscp, VAccess::WRITE};
refp->replaceWith(newpRefp);
pushDeletep(refp);
VL_DO_DANGLING(refp->deleteTree(), refp);
});
// Replace write refs on RHS
resetRdp->rhsp()->foreach<AstNodeVarRef>([this](AstNodeVarRef* refp) {
@ -249,7 +249,7 @@ class ForceConvertVisitor final : public VNVisitor {
AstVarRef* const newpRefp = new AstVarRef{refp->fileline(), newVscp, VAccess::READ};
newpRefp->user2(1); // Don't replace this read ref with the read signal
refp->replaceWith(newpRefp);
pushDeletep(refp);
VL_DO_DANGLING(refp->deleteTree(), refp);
});
resetEnp->addNext(resetRdp);

View File

@ -27,6 +27,7 @@
#include "V3Global.h"
#include "V3Gate.h"
#include "V3Ast.h"
#include "V3AstUserAllocator.h"
#include "V3Graph.h"
#include "V3Const.h"
#include "V3Stats.h"
@ -34,9 +35,11 @@
#include <algorithm>
#include <list>
#include <map>
#include <unordered_map>
#include <unordered_set>
class GateDedupeVarVisitor;
using GateVarRefList = std::list<AstNodeVarRef*>;
constexpr int GATE_DEDUP_MAX_DEPTH = 20;
@ -297,21 +300,31 @@ public:
const GateVarRefList& rhsVarRefs() const { return m_rhsVarRefs; }
};
//######################################################################
// Gate class functions
// ######################################################################
// Replace refs to 'varscp' with 'substp' in 'consumerp'
static void eliminate(AstNode* logicp,
const std::unordered_map<AstVarScope*, AstNode*>& substitutions,
GateDedupeVarVisitor* varVisp);
// ######################################################################
// Gate class functions
class GateVisitor final : public GateBaseVisitor {
private:
// NODE STATE
// Entire netlist:
// AstVarScope::user1p -> GateVarVertex* for usage var, 0=not set yet
// {statement}Node::user1p -> GateLogicVertex* for this statement
// {logic}Node::user1 -> map of substitutions, via m_substitutions
// AstVarScope::user2 -> bool: Signal used in SenItem in *this* always statement
// AstVar::user2 -> bool: Warned about SYNCASYNCNET
// AstNodeVarRef::user2 -> bool: ConcatOffset visited
const VNUser1InUse m_inuser1;
const VNUser2InUse m_inuser2;
// Variable substitutions to apply to a given logic block
AstUser1Allocator<AstNode, std::unordered_map<AstVarScope*, AstNode*>> m_substitutions;
// STATE
V3Graph m_graph; // Scoreboard of var usages/dependencies
GateLogicVertex* m_logicVertexp = nullptr; // Current statement being tracked, nullptr=ignored
@ -321,6 +334,8 @@ private:
bool m_activeReducible = true; // Is activation block reducible?
bool m_inSenItem = false; // Underneath AstSenItem; any varrefs are clocks
bool m_inSlow = false; // Inside a slow structure
std::vector<AstNode*> m_optimized; // Logic blocks optimized
VDouble0 m_statSigs; // Statistic tracking
VDouble0 m_statRefs; // Statistic tracking
VDouble0 m_statDedupLogic; // Statistic tracking
@ -368,9 +383,26 @@ private:
return vertexp;
}
void optimizeElimVar(AstVarScope* varscp, AstNode* substp, AstNode* consumerp) {
if (debug() >= 5) consumerp->dumpTree(cout, " elimUsePre: ");
if (!m_substitutions.tryGet(consumerp)) m_optimized.push_back(consumerp);
m_substitutions(consumerp).emplace(varscp, substp->cloneTree(false));
}
void commitElimVar(AstNode* logicp) {
if (auto* const substitutionsp = m_substitutions.tryGet(logicp)) {
if (!substitutionsp->empty()) {
eliminate(logicp, *substitutionsp, nullptr);
AstNode* const foldedp = V3Const::constifyEdit(logicp);
UASSERT_OBJ(foldedp == logicp, foldedp, "Should not remove whole logic");
for (const auto& pair : *substitutionsp) pair.second->deleteTree();
substitutionsp->clear();
}
}
}
void optimizeSignals(bool allowMultiIn);
bool elimLogicOkOutputs(GateLogicVertex* consumeVertexp, const GateOkVisitor& okVisitor);
void optimizeElimVar(AstVarScope* varscp, AstNode* substp, AstNode* consumerp);
void warnSignals();
void consumedMark();
void consumedMarkRecurse(GateEitherVertex* vertexp);
@ -396,6 +428,8 @@ private:
optimizeSignals(false);
// Then propagate more complicated equations
optimizeSignals(true);
// Commit substitutions on the optimized logic
for (AstNode* const logicp : m_optimized) commitElimVar(logicp);
// Remove redundant logic
if (v3Global.opt.fDedupe()) {
dedupe();
@ -537,141 +571,99 @@ public:
void GateVisitor::optimizeSignals(bool allowMultiIn) {
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
if (GateVarVertex* const vvertexp = dynamic_cast<GateVarVertex*>(itp)) {
if (vvertexp->inEmpty()) {
vvertexp->clearReducibleAndDedupable("inEmpty"); // Can't deal with no sources
if (!vvertexp->isTop() // Ok if top inputs are driverless
&& !vvertexp->varScp()->varp()->valuep()
&& !vvertexp->varScp()->varp()->isSigPublic()) {
UINFO(4, "No drivers " << vvertexp->varScp() << endl);
if (false) {
// If we warned here after constant propagation, what the user considered
// reasonable logic may have disappeared. Issuing a warning would
// thus be confusing. V3Undriven now handles this.
vvertexp->varScp()->varp()->v3warn(
UNDRIVEN, "Signal has no drivers: '"
<< vvertexp->scopep()->prettyName() << "."
<< vvertexp->varScp()->varp()->prettyName() << "'");
}
}
} else if (!vvertexp->inSize1()) {
// Can't deal with more than one src
vvertexp->clearReducibleAndDedupable("size!1");
GateVarVertex* const vvertexp = dynamic_cast<GateVarVertex*>(itp);
// Consider "inlining" variables
if (!vvertexp) continue;
if (vvertexp->inEmpty()) { // Can't deal with no sources
vvertexp->clearReducibleAndDedupable("inEmpty");
} else if (!vvertexp->inSize1()) { // Can't deal with more than one src
vvertexp->clearReducibleAndDedupable("size!1");
}
// Reduce it?
if (!vvertexp->reducible()) continue;
// Grab the driving logic
GateLogicVertex* const logicVertexp
= static_cast<GateLogicVertex*>(vvertexp->inBeginp()->fromp());
if (!logicVertexp->reducible()) continue;
AstNode* const logicp = logicVertexp->nodep();
// Commit pendingg optimizations to driving logic, as we will re-analyse
commitElimVar(logicp);
// Can we eliminate?
const GateOkVisitor okVisitor{logicp, vvertexp->isClock(), false};
// Was it ok?
if (!okVisitor.isSimple()) continue;
// Does it read multiple source variables?
if (okVisitor.rhsVarRefs().size() > 1) {
if (!allowMultiIn) continue;
// Do it if not used, or used only once, ignoring traces
int n = 0;
for (V3GraphEdge* edgep = vvertexp->outBeginp(); edgep; edgep = edgep->outNextp()) {
const GateLogicVertex* const consumeVertexp
= static_cast<GateLogicVertex*>(edgep->top());
// Ignore tracing or other slow path junk, or if the destination is not used
if (!consumeVertexp->slow() && consumeVertexp->outBeginp()) n += edgep->weight();
if (n > 1) break;
}
// Reduce it?
if (!vvertexp->reducible()) {
UINFO(8, "SigNotRed " << vvertexp->name() << endl);
if (n > 1) continue;
}
// Process it
AstNode* const substp = okVisitor.substTree();
if (debug() >= 5) logicp->dumpTree(cout, " elimVar: ");
if (debug() >= 5) substp->dumpTree(cout, " subst: ");
++m_statSigs;
bool removedAllUsages = true;
for (V3GraphEdge* edgep = vvertexp->outBeginp(); edgep;) {
GateLogicVertex* const consumeVertexp = static_cast<GateLogicVertex*>(edgep->top());
AstNode* const consumerp = consumeVertexp->nodep();
if (!elimLogicOkOutputs(consumeVertexp, okVisitor /*ref*/)) {
// Cannot optimize this replacement
removedAllUsages = false;
edgep = edgep->outNextp();
} else {
UINFO(8, "Sig " << vvertexp->name() << endl);
GateLogicVertex* const logicVertexp
= dynamic_cast<GateLogicVertex*>(vvertexp->inBeginp()->fromp());
UINFO(8, " From " << logicVertexp->name() << endl);
AstNode* logicp = logicVertexp->nodep();
if (logicVertexp->reducible()) {
// Can we eliminate?
const GateOkVisitor okVisitor{logicp, vvertexp->isClock(), false};
const bool multiInputs = okVisitor.rhsVarRefs().size() > 1;
// Was it ok?
bool doit = okVisitor.isSimple();
if (doit && multiInputs) {
if (!allowMultiIn) doit = false;
// Doit if one input, or not used, or used only once, ignoring traces
int n = 0;
for (V3GraphEdge* edgep = vvertexp->outBeginp(); edgep;
edgep = edgep->outNextp()) {
const GateLogicVertex* const consumeVertexp
= dynamic_cast<GateLogicVertex*>(edgep->top());
if (!consumeVertexp->slow()) { // Not tracing or other slow path junk
if (edgep->top()->outBeginp()) { // Destination is itself used
n += edgep->weight();
}
}
if (n > 1) {
doit = false;
break;
}
}
}
// Process it
if (!doit) {
if (allowMultiIn && (debug() >= 9)) {
UINFO(9, "Not ok simp" << okVisitor.isSimple() << " mi" << multiInputs
<< " ob" << vvertexp->outBeginp() << " on"
<< (vvertexp->outBeginp()
? vvertexp->outBeginp()->outNextp()
: nullptr)
<< " " << vvertexp->name() << endl);
for (V3GraphEdge* edgep = vvertexp->outBeginp(); edgep;
edgep = edgep->outNextp()) {
const GateLogicVertex* const consumeVertexp
= dynamic_cast<GateLogicVertex*>(edgep->top());
UINFO(9, " edge " << edgep << " to: " << consumeVertexp->nodep()
<< endl);
}
for (V3GraphEdge* edgep = vvertexp->inBeginp(); edgep;
edgep = edgep->inNextp()) {
const GateLogicVertex* const consumeVertexp
= dynamic_cast<GateLogicVertex*>(edgep->fromp());
UINFO(9, " edge " << edgep << " from: "
<< consumeVertexp->nodep() << endl);
}
}
} else {
AstNode* const substp = okVisitor.substTree();
if (debug() >= 5) logicp->dumpTree(cout, " elimVar: ");
if (debug() >= 5) substp->dumpTree(cout, " subst: ");
++m_statSigs;
bool removedAllUsages = true;
for (V3GraphEdge* edgep = vvertexp->outBeginp(); edgep;) {
GateLogicVertex* const consumeVertexp
= dynamic_cast<GateLogicVertex*>(edgep->top());
AstNode* const consumerp = consumeVertexp->nodep();
if (!elimLogicOkOutputs(consumeVertexp, okVisitor /*ref*/)) {
// Cannot optimize this replacement
removedAllUsages = false;
edgep = edgep->outNextp();
} else {
optimizeElimVar(vvertexp->varScp(), substp, consumerp);
// If the new replacement referred to a signal,
// Correct the graph to point to this new generating variable
const GateVarRefList& rhsVarRefs = okVisitor.rhsVarRefs();
for (GateVarRefList::const_iterator it = rhsVarRefs.begin();
it != rhsVarRefs.end(); ++it) {
AstVarScope* const newvarscp = (*it)->varScopep();
UINFO(9, " Point-to-new vertex " << newvarscp << endl);
GateVarVertex* const varvertexp = makeVarVertex(newvarscp);
new V3GraphEdge(&m_graph, varvertexp, consumeVertexp, 1);
// Propagate clock attribute onto generating node
varvertexp->propagateAttrClocksFrom(vvertexp);
}
// Remove the edge
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
++m_statRefs;
edgep = vvertexp->outBeginp();
}
}
if (removedAllUsages) {
// Remove input links
while (V3GraphEdge* const edgep = vvertexp->inBeginp()) {
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
}
// Clone tree so we remember it for tracing, and keep the pointer
// to the "ALWAYS" part of the tree as part of this statement
// That way if a later signal optimization that
// retained a pointer to the always can
// optimize it further
logicp->unlinkFrBack();
vvertexp->varScp()->valuep(logicp);
logicp = nullptr;
// Mark the vertex so we don't mark it as being
// unconsumed in the next step
vvertexp->user(true);
logicVertexp->user(true);
}
}
optimizeElimVar(vvertexp->varScp(), substp, consumerp);
// If the new replacement referred to a signal,
// Correct the graph to point to this new generating variable
const GateVarRefList& rhsVarRefs = okVisitor.rhsVarRefs();
for (AstNodeVarRef* const refp : rhsVarRefs) {
AstVarScope* const newvarscp = refp->varScopep();
GateVarVertex* const varvertexp = makeVarVertex(newvarscp);
new V3GraphEdge(&m_graph, varvertexp, consumeVertexp, 1);
// Propagate clock attribute onto generating node
varvertexp->propagateAttrClocksFrom(vvertexp);
}
// Remove the edge
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
++m_statRefs;
edgep = vvertexp->outBeginp();
}
}
if (removedAllUsages) {
// Remove input links
while (V3GraphEdge* const edgep = vvertexp->inBeginp()) {
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
}
// Clone tree so we remember it for tracing, and keep the pointer
// to the "ALWAYS" part of the tree as part of this statement
// That way if a later signal optimization that
// retained a pointer to the always can
// optimize it further
VL_DO_DANGLING(vvertexp->varScp()->valuep(logicp->unlinkFrBack()), logicp);
// Mark the vertex so we don't mark it as being
// unconsumed in the next step
vvertexp->user(true);
logicVertexp->user(true);
}
}
}
@ -817,83 +809,6 @@ void GateVisitor::warnSignals() {
}
}
//######################################################################
// Push constant into expressions and reevaluate
class GateDedupeVarVisitor;
class GateElimVisitor final : public GateBaseVisitor {
private:
// NODE STATE
// STATE
const AstVarScope* const m_elimVarScp; // Variable being eliminated
AstNode* const m_replaceTreep; // What to replace the variable with
bool m_didReplace = false; // Did we do any replacements
GateDedupeVarVisitor* const m_varVisp; // Callback to keep hash up to date
// METHODS
void hashReplace(AstNode* oldp, AstNode* newp);
// VISITORS
virtual void visit(AstNodeVarRef* nodep) override {
if (nodep->varScopep() == m_elimVarScp) {
// Substitute in the new tree
// It's possible we substitute into something that will be reduced more later,
// however, as we never delete the top Always/initial statement, all should be well.
m_didReplace = true;
UASSERT_OBJ(nodep->access().isReadOnly(), nodep,
"Can't replace lvalue assignments with const var");
AstNode* const substp = m_replaceTreep->cloneTree(false);
UASSERT_OBJ(!(VN_IS(substp, NodeVarRef) && nodep->same(substp)),
// Prevent an infinite loop...
substp, "Replacing node with itself; perhaps circular logic?");
// Which fileline() to use?
// If replacing with logic, an error/warning is likely to want to point to the logic
// IE what we're replacing with.
// However a VARREF should point to the original as it's otherwise confusing
// to throw warnings that point to a PIN rather than where the pin us used.
if (VN_IS(substp, VarRef)) substp->fileline(nodep->fileline());
// Make the substp an rvalue like nodep. This facilitates the hashing in dedupe.
if (AstNodeVarRef* const varrefp = VN_CAST(substp, NodeVarRef))
varrefp->access(VAccess::READ);
hashReplace(nodep, substp);
nodep->replaceWith(substp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
}
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
virtual ~GateElimVisitor() override = default;
GateElimVisitor(AstNode* nodep, AstVarScope* varscp, AstNode* replaceTreep,
GateDedupeVarVisitor* varVisp)
: m_elimVarScp{varscp}
, m_replaceTreep{replaceTreep}
, m_varVisp{varVisp} {
UINFO(9, " elimvisitor " << nodep << endl);
UINFO(9, " elim varscp " << varscp << endl);
UINFO(9, " elim repce " << replaceTreep << endl);
iterate(nodep);
}
bool didReplace() const { return m_didReplace; }
};
void GateVisitor::optimizeElimVar(AstVarScope* varscp, AstNode* substp, AstNode* consumerp) {
if (debug() >= 5) consumerp->dumpTree(cout, " elimUsePre: ");
const GateElimVisitor elimVisitor{consumerp, varscp, substp, nullptr};
if (elimVisitor.didReplace()) {
if (debug() >= 9) consumerp->dumpTree(cout, " elimUseCns: ");
// Caution: Can't let V3Const change our handle to consumerp, such as by
// optimizing away this assignment, etc.
consumerp = V3Const::constifyEdit(consumerp);
if (debug() >= 5) consumerp->dumpTree(cout, " elimUseDne: ");
// Some previous input edges may have disappeared, perhaps all of them.
// If we remove the edges we can further optimize
// See e.g t_var_overzero.v.
}
}
//######################################################################
// Auxiliary hash class for GateDedupeVarVisitor
@ -1111,15 +1026,50 @@ public:
void hashReplace(AstNode* oldp, AstNode* newp) { m_ghash.hashReplace(oldp, newp); }
};
//######################################################################
// ######################################################################
void GateElimVisitor::hashReplace(AstNode* oldp, AstNode* newp) {
UINFO(9, "hashReplace " << (void*)oldp << " -> " << (void*)newp << endl);
if (m_varVisp) m_varVisp->hashReplace(oldp, newp);
static void eliminate(AstNode* logicp,
const std::unordered_map<AstVarScope*, AstNode*>& substitutions,
GateDedupeVarVisitor* varVisp) {
const std::function<void(AstNodeVarRef*)> visit
= [&substitutions, &visit, varVisp](AstNodeVarRef* nodep) -> void {
// See if this variable has a substitution
const auto& it = substitutions.find(nodep->varScopep());
if (it == substitutions.end()) return;
AstNode* const substp = it->second;
// Substitute in the new tree
UASSERT_OBJ(nodep->access().isReadOnly(), nodep,
"Can't replace lvalue assignments with const var");
UASSERT_OBJ(!(VN_IS(substp, NodeVarRef) && nodep->same(substp)),
// Prevent an infinite loop...
substp, "Replacing node with itself; perhaps circular logic?");
// The replacement
AstNode* const newp = substp->cloneTree(false);
// Which fileline() to use? If replacing with logic, an error/warning is likely to want
// to point to the logic IE what we're replacing with. However, a VARREF should point
// to the original as it's otherwise confusing to throw warnings that point to a PIN
// rather than where the pin us used.
if (VN_IS(newp, VarRef)) newp->fileline(nodep->fileline());
// Make the newp an rvalue like nodep. This facilitates the hashing in dedupe.
if (AstNodeVarRef* const varrefp = VN_CAST(newp, NodeVarRef)) {
varrefp->access(VAccess::READ);
}
// Update hash?
if (varVisp) varVisp->hashReplace(nodep, newp);
// Replace the node
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
// Recursively substitute the new tree
newp->foreach<AstNodeVarRef>(visit);
};
logicp->foreach<AstNodeVarRef>(visit);
}
//######################################################################
// Recurse through the graph, looking for duplicate expressions on the rhs of an assign
// ######################################################################
// Recurse through the graph, looking for duplicate expressions on the rhs of an assign
class GateDedupeGraphVisitor final : public GateGraphBaseVisitor {
private:
@ -1170,8 +1120,8 @@ private:
if (lvertexp == consumeVertexp) {
UINFO(9, "skipping as self-recirculates\n");
} else {
const GateElimVisitor elimVisitor(consumerp, vvertexp->varScp(),
dupVarRefp, &m_varVisitor);
eliminate(consumerp, {std::make_pair(vvertexp->varScp(), dupVarRefp)},
&m_varVisitor);
}
outedgep = outedgep->relinkFromp(dupVvertexp);
}
@ -1571,12 +1521,13 @@ void GateVisitor::decomposeClkVectors() {
}
//######################################################################
// Convert VARSCOPE(ASSIGN(default, VARREF)) to just VARSCOPE(default)
// Gate class functions
class GateDeassignVisitor final : public GateBaseVisitor {
private:
// VISITORS
virtual void visit(AstVarScope* nodep) override {
void V3Gate::gateAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ": " << endl);
{ const GateVisitor visitor{nodep}; } // Destruct before checking
nodep->foreach<AstVarScope>([](AstVarScope* nodep) {
if (AstNodeAssign* const assp = VN_CAST(nodep->valuep(), NodeAssign)) {
UINFO(5, " Removeassign " << assp << endl);
AstNode* const valuep = assp->rhsp();
@ -1584,26 +1535,7 @@ private:
assp->replaceWith(valuep);
VL_DO_DANGLING(assp->deleteTree(), assp);
}
}
// Speedups
virtual void visit(AstVar*) override {} // Accelerate
virtual void visit(AstActive*) override {} // Accelerate
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
});
public:
// CONSTRUCTORS
explicit GateDeassignVisitor(AstNode* nodep) { iterate(nodep); }
virtual ~GateDeassignVisitor() override = default;
};
//######################################################################
// Gate class functions
void V3Gate::gateAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ": " << endl);
{
const GateVisitor visitor{nodep};
GateDeassignVisitor{nodep};
} // Destruct before checking
V3Global::dumpCheckGlobalTree("gate", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
}