Merge branch 'master' into develop-v5
This commit is contained in:
commit
ba66fa7200
357
src/V3Ast.h
357
src/V3Ast.h
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
422
src/V3Gate.cpp
422
src/V3Gate.cpp
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue