Skip to content

Commit

Permalink
A type safe, generic accessor for AstNode user data
Browse files Browse the repository at this point in the history
  • Loading branch information
gezalore committed Oct 30, 2023
1 parent c1c8b30 commit 1183d6d
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 19 deletions.
9 changes: 9 additions & 0 deletions src/V3Ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
41 changes: 22 additions & 19 deletions src/V3Delayed.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

#include "V3AstUserAllocator.h"
#include "V3Stats.h"
#include "V3VNUserHandle.h"

#include <deque>

Expand All @@ -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;

Expand All @@ -92,7 +89,13 @@ class DelayedVisitor final : public VNVisitor {
// Last blocking or non-blocking reference
AstNodeVarRef* lastRefp = nullptr;
};
AstUser1Allocator<AstVarScope, AuxAstVarScope> m_vscpAux;

VNUser1Handle< //
VNAux<AstVarScope, AuxAstVarScope>, // See above
VNAux<AstNodeVarRef, bool>, // Set true if was blocking reference
VNAux<AstAlwaysPost, AstIf*> // Last IF (__Vdlyvset__) created under this AlwaysPost
>
m_user1;

// STATE - across all visitors
std::unordered_map<const AstVarScope*, int> m_scopeVecMap; // Next var number for each scope
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -358,21 +361,21 @@ 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};
UINFO(9, " Created " << finalp << endl);
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);
Expand All @@ -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);
}
Expand All @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
Expand All @@ -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);
}
Expand Down
204 changes: 204 additions & 0 deletions src/V3VNUserHandle.h
Original file line number Diff line number Diff line change
@@ -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 <type_traits>
#include <utility>
#include <vector>

//============================================================================
// VNAux is juts a type pair, to be passed to VNUser*Handle as type parameter
//============================================================================

template <typename T_Node, typename T_Data>
struct VNAux final {
static_assert(std::is_base_of<AstNode, T_Node>::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 <typename Accessor, typename Aux, bool Indirect>
class VNUserHandleImpl;

// Implementation for Data held directly
template <typename Accessor, typename Aux>
class VNUserHandleImpl<Accessor, Aux, /* Indirect: */ false> 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 <typename... Args>
Data& operator()(Node* nodep, Args&&... args) {
Data* const storagep = reinterpret_cast<Data*>(&Accessor::user(nodep));
if (Accessor::nodeGeneration(nodep) != Accessor::currentGeneration()) {
Accessor::nodeGeneration(nodep) = Accessor::currentGeneration();
new (storagep) Data{std::forward<Args>(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<Data*>();
}
};

// Implementation for Data held indirectly
template <typename Accessor, typename Aux>
class VNUserHandleImpl<Accessor, Aux, /* Indirect: */ true> 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<Data> 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 <typename... Args>
Data& operator()(Node* nodep, Args&&... args) {
if (Accessor::nodeGeneration(nodep) != Accessor::currentGeneration()) {
Accessor::nodeGeneration(nodep) = Accessor::currentGeneration();
m_allocated.emplace_back(std::forward<Args>(args)...);
Accessor::user(nodep) = VNUser{&m_allocated.back()};
}
return *Accessor::user(nodep).template to<Data*>();
}

// 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<Data*>();
}
};

//============================================================================
// This is a variadic 'unroller' template that eventually derives from
// VNUserHandleImpl for each VNAux parameter passed to it
//============================================================================

// Generic template
template <typename Accessor, typename AuxFirst, typename... AuxRest>
class VNUserHandleTemplate;

// Specialization for two or more Aux arguments
template <typename Accessor, typename Aux1, typename Aux2, typename... AuxRest>
class VNUserHandleTemplate<Accessor, Aux1, Aux2, AuxRest...> VL_NOT_FINAL
: public VNUserHandleTemplate<Accessor, Aux1>, // Single argument version
public VNUserHandleTemplate<Accessor, Aux2, AuxRest...> // Generic version
{
public:
using VNUserHandleTemplate<Accessor, Aux1>::operator();
using VNUserHandleTemplate<Accessor, Aux2, AuxRest...>::operator();
};

// Specialization for one Aux argument - just derives from the implementation
template <typename Accessor, typename Aux>
class VNUserHandleTemplate<Accessor, Aux> VL_NOT_FINAL
: public VNUserHandleImpl<Accessor, Aux, (sizeof(typename Aux::Data) > sizeof(VNUser))> {
static_assert(std::is_same<Aux, VNAux<typename Aux::Node, typename Aux::Data>>::value,
"Don't invent your own 'VNAux'!");
};

//============================================================================
// The actual AstNode 'user' data handlers, to be used in client code
//============================================================================

template <typename... T_VNAuxs>
class VNUser1Handle final : public VNUserHandleTemplate<VNUser1Accessor, T_VNAuxs...> {
const VNUser1InUse m_user1InUse;
};
template <typename... T_VNAuxs>
class VNUser2Handle final : public VNUserHandleTemplate<VNUser2Accessor, T_VNAuxs...> {
const VNUser2InUse m_user2InUse;
};
template <typename... T_VNAuxs>
class VNUser3Handle final : public VNUserHandleTemplate<VNUser3Accessor, T_VNAuxs...> {
const VNUser3InUse m_user3InUse;
};
template <typename... T_VNAuxs>
class VNUser4Handle final : public VNUserHandleTemplate<VNUser4Accessor, T_VNAuxs...> {
const VNUser4InUse m_user4InUse;
};

#endif // Guard

0 comments on commit 1183d6d

Please sign in to comment.