diff --git a/src/V3Ast.h b/src/V3Ast.h index d2bb7068f4..b727e4260f 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -1569,6 +1569,7 @@ class VNUserInUseBase VL_NOT_FINAL { // clang-format off class VNUser1InUse final : VNUserInUseBase { protected: + friend class VNUser1Accessor; friend class AstNode; static uint32_t s_userCntGbl; // Count of which usage of userp() this is static bool s_userBusy; // Count is in use @@ -1580,6 +1581,7 @@ class VNUser1InUse final : VNUserInUseBase { }; class VNUser2InUse final : VNUserInUseBase { protected: + friend class VNUser2Accessor; friend class AstNode; static uint32_t s_userCntGbl; // Count of which usage of userp() this is static bool s_userBusy; // Count is in use @@ -1591,6 +1593,7 @@ class VNUser2InUse final : VNUserInUseBase { }; class VNUser3InUse final : VNUserInUseBase { protected: + friend class VNUser3Accessor; friend class AstNode; static uint32_t s_userCntGbl; // Count of which usage of userp() this is static bool s_userBusy; // Count is in use @@ -1602,6 +1605,7 @@ class VNUser3InUse final : VNUserInUseBase { }; class VNUser4InUse final : VNUserInUseBase { protected: + friend class VNUser4Accessor; friend class AstNode; static uint32_t s_userCntGbl; // Count of which usage of userp() this is static bool s_userBusy; // Count is in use @@ -1805,6 +1809,11 @@ class AstNode VL_NOT_FINAL { AstNode* m_clonep = nullptr; // Pointer to clone/source of node (only for *LAST* cloneTree()) static int s_cloneCntGbl; // Count of which userp is set + friend class VNUser1Accessor; + friend class VNUser2Accessor; + friend class VNUser3Accessor; + friend class VNUser4Accessor; + // This member ordering both allows 64 bit alignment and puts associated data together VNUser m_user1u{0}; // Contains any information the user iteration routine wants uint32_t m_user1Cnt = 0; // Mark of when userp was set diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index da769536ea..91303167b6 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -54,6 +54,7 @@ #include "V3AstUserAllocator.h" #include "V3Stats.h" +#include "V3VNUserHandle.h" #include @@ -66,17 +67,13 @@ class DelayedVisitor final : public VNVisitor { private: // NODE STATE // Cleared each module: - // AstVarScope::user1p() -> aux // AstVar::user2() -> bool. Set true if already made warning - // AstVarRef::user1() -> bool. Set true if was blocking reference // AstVarRef::user2() -> bool. Set true if already processed - // AstAlwaysPost::user1() -> AstIf*. Last IF (__Vdlyvset__) created under this AlwaysPost // AstAlwaysPost::user2() -> ActActive*. Points to activity block of signal // (valid when AstAlwaysPost::user4p is valid) // Cleared each scope/active: // AstAssignDly::user3() -> AstVarScope*. __Vdlyvset__ created for this assign // AstAlwaysPost::user3() -> AstVarScope*. __Vdlyvset__ last referenced in IF - const VNUser1InUse m_inuser1; const VNUser2InUse m_inuser2; const VNUser3InUse m_inuser3; @@ -92,7 +89,13 @@ class DelayedVisitor final : public VNVisitor { // Last blocking or non-blocking reference AstNodeVarRef* lastRefp = nullptr; }; - AstUser1Allocator m_vscpAux; + + VNUser1Handle< // + VNAux, // See above + VNAux, // Set true if was blocking reference + VNAux // Last IF (__Vdlyvset__) created under this AlwaysPost + > + m_user1; // STATE - across all visitors std::unordered_map m_scopeVecMap; // Next var number for each scope @@ -123,14 +126,14 @@ class DelayedVisitor final : public VNVisitor { // Ignore if warning is disabled on this reference (used by V3Force). if (nodep->fileline()->warnIsOff(V3ErrorCode::BLKANDNBLK)) return; if (m_ignoreBlkAndNBlk) return; - if (blocking) nodep->user1(true); + if (blocking) m_user1(nodep) = true; AstVarScope* const vscp = nodep->varScopep(); // UINFO(4, " MVU " << blocking << " " << nodep << endl); - const AstNode* const lastrefp = m_vscpAux(vscp).lastRefp; + AstNodeVarRef* const lastrefp = m_user1(vscp).lastRefp; if (!lastrefp) { - m_vscpAux(vscp).lastRefp = nodep; + m_user1(vscp).lastRefp = nodep; } else { - const bool last_was_blocking = lastrefp->user1(); + const bool last_was_blocking = m_user1(lastrefp); if (last_was_blocking != blocking) { const AstNode* nonblockingp = blocking ? nodep : lastrefp; if (const AstNode* np = containingAssignment(nonblockingp)) nonblockingp = np; @@ -358,7 +361,7 @@ class DelayedVisitor final : public VNVisitor { UINFO(9, " & " << varrefp << endl); AstAlwaysPost* finalp = nullptr; if (m_inSuspendableOrFork) { - finalp = m_vscpAux(varrefp->varScopep()).suspFinalp; + finalp = m_user1(varrefp->varScopep()).suspFinalp; if (!finalp) { FileLine* const flp = nodep->fileline(); finalp = new AstAlwaysPost{flp, nullptr, nullptr}; @@ -366,13 +369,13 @@ class DelayedVisitor final : public VNVisitor { if (!m_procp->user3p()) { AstActive* const newactp = createActive(varrefp); m_procp->user3p(newactp); - m_vscpAux(varrefp->varScopep()).suspFinalp = finalp; + m_user1(varrefp->varScopep()).suspFinalp = finalp; } AstActive* const actp = VN_AS(m_procp->user3p(), Active); actp->addStmtsp(finalp); } } else { - finalp = m_vscpAux(varrefp->varScopep()).finalp; + finalp = m_user1(varrefp->varScopep()).finalp; if (finalp) { AstActive* const oldactivep = VN_AS(finalp->user2p(), Active); checkActivePost(varrefp, oldactivep); @@ -382,7 +385,7 @@ class DelayedVisitor final : public VNVisitor { UINFO(9, " Created " << finalp << endl); AstActive* const newactp = createActive(varrefp); newactp->addStmtsp(finalp); - m_vscpAux(varrefp->varScopep()).finalp = finalp; + m_user1(varrefp->varScopep()).finalp = finalp; finalp->user2p(newactp); if (setinitp) newactp->addStmtsp(setinitp); } @@ -391,7 +394,7 @@ class DelayedVisitor final : public VNVisitor { if (finalp->user3p() == setvscp) { // Optimize as above; if sharing Vdlyvset *ON SAME VARIABLE*, // we can share the IF statement too - postLogicp = VN_AS(finalp->user1p(), If); + postLogicp = m_user1(finalp); UASSERT_OBJ(postLogicp, nodep, "Delayed assignment misoptimized; prev var found w/o associated IF"); } else { @@ -400,7 +403,7 @@ class DelayedVisitor final : public VNVisitor { UINFO(9, " Created " << postLogicp << endl); finalp->addStmtsp(postLogicp); finalp->user3p(setvscp); // Remember IF's vset variable - finalp->user1p(postLogicp); // and the associated IF, as we may be able to reuse it + m_user1(finalp) = postLogicp; // and the associated IF, as we may be able to reuse it } postLogicp->addThensp(new AstAssign{nodep->fileline(), selectsp, valreadp}); if (m_inSuspendableOrFork) { @@ -572,9 +575,9 @@ class DelayedVisitor final : public VNVisitor { } AstVarScope* const oldvscp = nodep->varScopep(); UASSERT_OBJ(oldvscp, nodep, "Var didn't get varscoped in V3Scope.cpp"); - AstVarScope* dlyvscp = m_vscpAux(oldvscp).delayVscp; + AstVarScope* dlyvscp = m_user1(oldvscp).delayVscp; if (dlyvscp) { // Multiple use of delayed variable - AstActive* const oldactivep = m_vscpAux(dlyvscp).activep; + AstActive* const oldactivep = m_user1(dlyvscp).activep; checkActivePost(nodep, oldactivep); } else { // First use of this delayed variable const string newvarname = string{"__Vdly__"} + nodep->varp()->shortName(); @@ -588,10 +591,10 @@ class DelayedVisitor final : public VNVisitor { new AstVarRef{nodep->fileline(), oldvscp, VAccess::WRITE}, new AstVarRef{nodep->fileline(), dlyvscp, VAccess::READ}}; postp->lhsp()->user2(true); // Don't detect this assignment - m_vscpAux(oldvscp).delayVscp = dlyvscp; // So we can find it later + m_user1(oldvscp).delayVscp = dlyvscp; // So we can find it later // Make new ACTIVE with identical sensitivity tree AstActive* const newactp = createActive(nodep); - m_vscpAux(dlyvscp).activep = newactp; + m_user1(dlyvscp).activep = newactp; newactp->addStmtsp(prep); // Add to FRONT of statements newactp->addStmtsp(postp); } diff --git a/src/V3VNUserHandle.h b/src/V3VNUserHandle.h new file mode 100644 index 0000000000..f94782c53e --- /dev/null +++ b/src/V3VNUserHandle.h @@ -0,0 +1,204 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Utility to hang advanced data structures of +// AstNode::user*p() pointers with automatic memory management. +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2023 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3VNUSERHANDLE_H_ +#define VERILATOR_V3VNUSERHANDLE_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" + +#include +#include +#include + +//============================================================================ +// VNAux is juts a type pair, to be passed to VNUser*Handle as type parameter +//============================================================================ + +template +struct VNAux final { + static_assert(std::is_base_of::value, "T_Node must be an AstNode type"); + using Node = T_Node; + using Data = T_Data; +}; + +//============================================================================ +// Simple accessors for the 4 user fields +//============================================================================ + +class VNUser1Accessor final { +public: + static VNUser& user(AstNode* nodep) { return nodep->m_user1u; } + static VNUser user(const AstNode* nodep) { return nodep->m_user1u; } + static uint32_t& nodeGeneration(AstNode* nodep) { return nodep->m_user1Cnt; } + static uint32_t nodeGeneration(const AstNode* nodep) { return nodep->m_user1Cnt; } + static uint32_t currentGeneration() { return VNUser1InUse::s_userCntGbl; } +}; + +class VNUser2Accessor final { +public: + static VNUser& user(AstNode* nodep) { return nodep->m_user2u; } + static VNUser user(const AstNode* nodep) { return nodep->m_user2u; } + static uint32_t& nodeGeneration(AstNode* nodep) { return nodep->m_user2Cnt; } + static uint32_t nodeGeneration(const AstNode* nodep) { return nodep->m_user2Cnt; } + static uint32_t currentGeneration() { return VNUser2InUse::s_userCntGbl; } +}; + +class VNUser3Accessor final { +public: + static VNUser& user(AstNode* nodep) { return nodep->m_user3u; } + static VNUser user(const AstNode* nodep) { return nodep->m_user3u; } + static uint32_t& nodeGeneration(AstNode* nodep) { return nodep->m_user3Cnt; } + static uint32_t nodeGeneration(const AstNode* nodep) { return nodep->m_user3Cnt; } + static uint32_t currentGeneration() { return VNUser3InUse::s_userCntGbl; } +}; + +class VNUser4Accessor final { +public: + static VNUser& user(AstNode* nodep) { return nodep->m_user4u; } + static VNUser user(const AstNode* nodep) { return nodep->m_user4u; } + static uint32_t& nodeGeneration(AstNode* nodep) { return nodep->m_user4Cnt; } + static uint32_t nodeGeneration(const AstNode* nodep) { return nodep->m_user4Cnt; } + static uint32_t currentGeneration() { return VNUser4InUse::s_userCntGbl; } +}; + +//============================================================================ +// Implementation of access to the user data for one node type (Aux::Node) +//============================================================================ + +// There are 2 implementation flavours: Data held directly vs indirectly +template +class VNUserHandleImpl; + +// Implementation for Data held directly +template +class VNUserHandleImpl VL_NOT_FINAL { + using Node = typename Aux::Node; + using Data = typename Aux::Data; + static_assert(sizeof(Data) <= sizeof(VNUser), "'Data' does not fit directly"); + +protected: + VNUserHandleImpl() = default; + ~VNUserHandleImpl() = default; + VL_UNCOPYABLE(VNUserHandleImpl); + +public: + // Get a reference to the user data. If not current, construct it with given arguments. + template + Data& operator()(Node* nodep, Args&&... args) { + Data* const storagep = reinterpret_cast(&Accessor::user(nodep)); + if (Accessor::nodeGeneration(nodep) != Accessor::currentGeneration()) { + Accessor::nodeGeneration(nodep) = Accessor::currentGeneration(); + new (storagep) Data{std::forward(args)...}; + } + return *storagep; + } + + // Get a reference to the user data. The node is const, so it must be current. + Data& operator()(const Node* nodep) { + UASSERT_OBJ(Accessor::nodeGeneration(nodep) == Accessor::currentGeneration(), nodep, + "User data is stale on const AstNode"); + return *Accessor::user(nodep).template to(); + } +}; + +// Implementation for Data held indirectly +template +class VNUserHandleImpl VL_NOT_FINAL { + using Node = typename Aux::Node; + using Data = typename Aux::Data; + static_assert(sizeof(Data) > sizeof(VNUser), "'Data' should fit directly"); + + std::deque m_allocated; + +protected: + VNUserHandleImpl() = default; + ~VNUserHandleImpl() = default; + VL_UNCOPYABLE(VNUserHandleImpl); + +public: + // Get a reference to the user data. If not current, construct it with given arguments. + template + Data& operator()(Node* nodep, Args&&... args) { + if (Accessor::nodeGeneration(nodep) != Accessor::currentGeneration()) { + Accessor::nodeGeneration(nodep) = Accessor::currentGeneration(); + m_allocated.emplace_back(std::forward(args)...); + Accessor::user(nodep) = VNUser{&m_allocated.back()}; + } + return *Accessor::user(nodep).template to(); + } + + // Get a reference to the user data. The node is const, so it must be current. + Data& operator()(const Node* nodep) { + UASSERT_OBJ(Accessor::nodeGeneration(nodep) == Accessor::currentGeneration(), nodep, + "User data is stale on const AstNode"); + return *Accessor::user(nodep).template to(); + } +}; + +//============================================================================ +// This is a variadic 'unroller' template that eventually derives from +// VNUserHandleImpl for each VNAux parameter passed to it +//============================================================================ + +// Generic template +template +class VNUserHandleTemplate; + +// Specialization for two or more Aux arguments +template +class VNUserHandleTemplate VL_NOT_FINAL + : public VNUserHandleTemplate, // Single argument version + public VNUserHandleTemplate // Generic version +{ +public: + using VNUserHandleTemplate::operator(); + using VNUserHandleTemplate::operator(); +}; + +// Specialization for one Aux argument - just derives from the implementation +template +class VNUserHandleTemplate VL_NOT_FINAL + : public VNUserHandleImpl sizeof(VNUser))> { + static_assert(std::is_same>::value, + "Don't invent your own 'VNAux'!"); +}; + +//============================================================================ +// The actual AstNode 'user' data handlers, to be used in client code +//============================================================================ + +template +class VNUser1Handle final : public VNUserHandleTemplate { + const VNUser1InUse m_user1InUse; +}; +template +class VNUser2Handle final : public VNUserHandleTemplate { + const VNUser2InUse m_user2InUse; +}; +template +class VNUser3Handle final : public VNUserHandleTemplate { + const VNUser3InUse m_user3InUse; +}; +template +class VNUser4Handle final : public VNUserHandleTemplate { + const VNUser4InUse m_user4InUse; +}; + +#endif // Guard