// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Ast node structure
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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-FileCopyrightText: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************

#ifndef VERILATOR_V3AST_H_
#define VERILATOR_V3AST_H_

#include "config_build.h"
#include "verilatedos.h"

#include "V3Broken.h"
#include "V3Error.h"
#include "V3FileLine.h"
#include "V3FunctionTraits.h"
#include "V3Global.h"
#include "V3Number.h"
#include "V3StdFuture.h"

#include "V3Ast__gen_forward_class_decls.h"  // From ./astgen

#include <cmath>
#include <cstdint>
#include <functional>
#include <map>
#include <set>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <vector>

// clang-format off
#include "V3AstAttr.h"
// clang-format on

// Forward declarations
class V3Graph;
class ExecMTask;

//######################################################################

// For broken() function, return error string if have a match
#define BROKEN_RTN(test) \
    do { \
        if (VL_UNCOVERABLE(test)) return "'" #test "' @ " __FILE__ ":" VL_STRINGIFY(__LINE__); \
    } while (false)
// For broken() function, return error string if a base of this class has a match
#define BROKEN_BASE_RTN(test) \
    do { \
        const char* const reasonp = (test); \
        if (VL_UNCOVERABLE(reasonp)) return reasonp; \
    } while (false)

// (V)erilator (N)ode is: Returns true if and only if AstNode is of the given AstNode subtype,
// and not nullptr.
#define VN_IS(nodep, nodetypename) (AstNode::is<Ast##nodetypename>(nodep))

// (V)erilator (N)ode cast: Similar to dynamic_cast, but more efficient, use this instead.
// Cast to given type if node is of such type, otherwise returns nullptr. If 'nodep' is nullptr,
// return nullptr. Pointer constness is preserved, i.e.: given a 'const AstNode*',
// a 'const Ast<nodetypename>*' is returned.
#define VN_CAST(nodep, nodetypename) (AstNode::cast<Ast##nodetypename>(nodep))

// (V)erilator (N)ode as: Assert node is of given type then cast to that type. Use this to
// downcast instead of VN_CAST when you know the true type of the node. If 'nodep' is nullptr,
// return nullptr. Pointer constness is preserved, i.e.: given a 'const AstNode*', a 'const
// Ast<nodetypename>*' is returned.
#define VN_AS(nodep, nodetypename) (AstNode::as<Ast##nodetypename>(nodep))

// Same as VN_AS, but only checks the type in debug builds. Type checking is less critical in node
// child getters (the strongly-typed functions that wrap op*p pointers). This is because the op*p
// pointers are usually populated by code that already asserts the correct type. Having fewer type
// assertions yields better performance in release builds.
#if defined(VL_DEBUG) || defined(VL_ASAN)
#define VN_DBG_AS(nodep, nodetypename) VN_AS(nodep, nodetypename)
#else
#define VN_DBG_AS(nodep, nodetypename) (AstNode::unsafePrivateAs<Ast##nodetypename>(nodep))
#endif

// (V)erilator (N)ode deleted: Pointer to deleted AstNode (for assertions only)
#define VN_DELETED(nodep) VL_UNLIKELY(reinterpret_cast<uint64_t>(nodep) == 0x1)

//######################################################################

struct VNTypeInfo final {
    const char* m_namep;
    enum OpEn : uint8_t {
        OP_UNUSED,
        OP_USED,
        OP_LIST,
        OP_OPTIONAL,
    } m_opType[4];
    const char* m_opNamep[4];
    size_t m_sizeof;
};

class VNType final {
    static const VNTypeInfo s_typeInfoTable[];

public:
#include "V3Ast__gen_type_enum.h"  // From ./astgen
    const en m_e;
    VNType() = delete;

    // VNType is interconvetible with VNType::en
    // cppcheck-suppress noExplicitConstructor
    constexpr VNType(en _e) VL_MT_SAFE : m_e{_e} {}
    constexpr operator en() const VL_MT_SAFE { return m_e; }

    // Retrieve VNTypeInfo for the given type
    static const VNTypeInfo& typeInfo(VNType t) VL_MT_SAFE { return s_typeInfoTable[t.m_e]; }
};
constexpr bool operator==(const VNType& lhs, const VNType& rhs) VL_PURE {
    return lhs.m_e == rhs.m_e;
}
constexpr bool operator==(const VNType& lhs, VNType::en rhs) VL_PURE { return lhs.m_e == rhs; }
constexpr bool operator==(VNType::en lhs, const VNType& rhs) VL_PURE { return lhs == rhs.m_e; }
inline std::ostream& operator<<(std::ostream& os, const VNType& rhs) { return os << rhs.ascii(); }

//######################################################################
// AstNUser - Generic base class for AST User nodes.
//          - Also used to allow parameter passing up/down iterate calls

class WidthVP;
class V3GraphVertex;
class VSymEnt;

class VNUser final {
    union {
        void* up;
        int ui;
    } m_u;

public:
    VNUser() = default;
    // non-explicit:
    // cppcheck-suppress noExplicitConstructor
    VNUser(int i) {
        m_u.up = nullptr;
        m_u.ui = i;
    }
    explicit VNUser(void* p) { m_u.up = p; }
    ~VNUser() = default;
    // Casters
    template <typename T>
    typename std::enable_if<std::is_pointer<T>::value, T>::type to() const VL_MT_SAFE {
        return reinterpret_cast<T>(m_u.up);
    }
    VSymEnt* toSymEnt() const { return to<VSymEnt*>(); }
    AstNode* toNodep() const VL_MT_SAFE { return to<AstNode*>(); }
    V3GraphVertex* toGraphVertex() const { return to<V3GraphVertex*>(); }
    int toInt() const { return m_u.ui; }
};

//######################################################################
// AstUserResource - Generic pointer base class for tracking usage of user()
//
//  Where AstNode->user2() is going to be used, for example, you write:
//
//      VNUser2InUse  m_userres;
//
//  This will clear the tree, and prevent another visitor from clobbering
//  user2.  When the member goes out of scope it will be automagically
//  freed up.

class VNUserInUseBase VL_NOT_FINAL {
protected:
    static void allocate(int id, uint32_t& cntGblRef, bool& userBusyRef) {
        // Perhaps there's still a AstUserInUse in scope for this?
        UASSERT_STATIC(!userBusyRef, "Conflicting user use; AstUser" + cvtToStr(id)
                                         + "InUse request when under another AstUserInUse");
        userBusyRef = true;
        clearcnt(id, cntGblRef, userBusyRef);
    }
    static void free(int id, uint32_t& cntGblRef, bool& userBusyRef) {
        UASSERT_STATIC(userBusyRef, "Free of User" + cvtToStr(id) + "() not under AstUserInUse");
        clearcnt(id, cntGblRef, userBusyRef);  // Includes a checkUse for us
        userBusyRef = false;
    }
    static void clearcnt(int id, uint32_t& cntGblRef, const bool& userBusyRef) {
        UASSERT_STATIC(userBusyRef, "Clear of User" + cvtToStr(id) + "() not under AstUserInUse");
        // If this really fires and is real (after 2^32 edits???)
        // we could just walk the tree and clear manually
        ++cntGblRef;
        UASSERT_STATIC(cntGblRef, "User*() overflowed!");
    }
    static void checkcnt(int id, uint32_t&, const bool& userBusyRef) {
        UASSERT_STATIC(userBusyRef,
                       "Check of User" + cvtToStr(id) + "() failed, not under AstUserInUse");
    }
};

// For each user() declare the in use structure
// We let AstNode peek into here, because when under low optimization even
// an accessor would be way too slow.
// clang-format off
class VNUser1InUse final : VNUserInUseBase {
protected:
    friend class AstNode;
    static uint32_t s_userCntGbl;  // Count of which usage of userp() this is
    static bool s_userBusy;  // Count is in use
public:
    VNUser1InUse()      { allocate(1, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    ~VNUser1InUse()     { free    (1, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    static void clear() { clearcnt(1, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    static void check() { checkcnt(1, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
};
class VNUser2InUse final : VNUserInUseBase {
protected:
    friend class AstNode;
    static uint32_t s_userCntGbl;  // Count of which usage of userp() this is
    static bool s_userBusy;  // Count is in use
public:
    VNUser2InUse()      { allocate(2, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    ~VNUser2InUse()     { free    (2, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    static void clear() { clearcnt(2, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    static void check() { checkcnt(2, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
};
class VNUser3InUse final : VNUserInUseBase {
protected:
    friend class AstNode;
    static uint32_t s_userCntGbl;  // Count of which usage of userp() this is
    static bool s_userBusy;  // Count is in use
public:
    VNUser3InUse()      { allocate(3, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    ~VNUser3InUse()     { free    (3, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    static void clear() { clearcnt(3, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    static void check() { checkcnt(3, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
};
class VNUser4InUse final : VNUserInUseBase {
protected:
    friend class AstNode;
    static uint32_t s_userCntGbl;  // Count of which usage of userp() this is
    static bool s_userBusy;  // Count is in use
public:
    VNUser4InUse()      { allocate(4, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    ~VNUser4InUse()     { free    (4, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    static void clear() { clearcnt(4, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
    static void check() { checkcnt(4, s_userCntGbl/*ref*/, s_userBusy/*ref*/); }
};
// clang-format on

//######################################################################
// Node deleter, deletes all enqueued AstNode* on destruction, or when
// explicitly told to do so. This is useful when the deletion of removed
// nodes needs to be deferred to a later time, because pointers to the
// removed nodes might still exist.

class VNDeleter VL_NOT_FINAL {
    // MEMBERS
    std::vector<AstNode*> m_deleteps;  // Nodes to delete

public:
    // METHODS

    // Enqueue node for deletion on next 'doDelete' (or destruction)
    void pushDeletep(AstNode* nodep) {
        UASSERT_STATIC(nodep, "Cannot delete nullptr node");
        m_deleteps.push_back(nodep);
    }

    // Delete all previously pushed nodes (by calling deleteTree)
    void doDeletes();

    // Do the deletions on destruction
    ~VNDeleter() { doDeletes(); }
};

//######################################################################
// VNVisitorConst -- Allows new functions to be called on each node
// type without changing the base classes.  See "Modern C++ Design".
// This only has the constant fuctions for non-modifying visitors.
// For more typical usage see VNVisitor

class VNVisitorConst VL_NOT_FINAL {
    friend class AstNode;

public:
    /// Call visit()s on nodep
    inline void iterateConst(AstNode* nodep);
    /// Call visit()s on nodep
    inline void iterateConstNull(AstNode* nodep);
    /// Call visit()s on const nodep's children
    inline void iterateChildrenConst(AstNode* nodep);
    /// Call visit()s on nodep's children in backp() order
    inline void iterateChildrenBackwardsConst(AstNode* nodep);
    /// Call visit()s on const nodep (maybe nullptr) and nodep's nextp() list
    inline void iterateAndNextConstNull(AstNode* nodep);

    virtual void visit(AstNode* nodep) = 0;
    virtual ~VNVisitorConst() {}
#include "V3Ast__gen_visitor_decls.h"  // From ./astgen
};

//######################################################################
// VNVisitor -- Allows new functions to be called on each node
// type without changing the base classes.  See "Modern C++ Design".

class VNVisitor VL_NOT_FINAL : public VNVisitorConst {
    VNDeleter m_deleter;  // Used to delay deletion of nodes
public:
    /// Call visit()s on nodep
    inline void iterate(AstNode* nodep);
    /// Call visit()s on nodep
    inline void iterateNull(AstNode* nodep);
    /// Call visit()s on nodep's children
    inline void iterateChildren(AstNode* nodep);
    /// Call visit()s on nodep (maybe nullptr) and nodep's nextp() list
    inline void iterateAndNextNull(AstNode* nodep);
    /// Return edited nodep; see comments in V3Ast.cpp
    inline AstNode* iterateSubtreeReturnEdits(AstNode* nodep);

    VNDeleter& deleter() { return m_deleter; }
    void pushDeletep(AstNode* nodep) { deleter().pushDeletep(nodep); }
    void doDeletes() { deleter().doDeletes(); }
};

//######################################################################
// VNRelinker -- Holds the state of a unlink so a new node can be
// added at the same point.

class VNRelinker final {
protected:
    friend class AstNode;
    enum RelinkWhatEn : uint8_t {
        RELINK_BAD,
        RELINK_NEXT,
        RELINK_OP1,
        RELINK_OP2,
        RELINK_OP3,
        RELINK_OP4
    };
    AstNode* m_oldp = nullptr;  // The old node that was linked to this point in the tree
    AstNode* m_backp = nullptr;
    AstNode** m_iterpp = nullptr;
    RelinkWhatEn m_chg = RELINK_BAD;

public:
    VNRelinker() = default;
    ~VNRelinker() {
        // Relink is needed so m_iterpp's get restored, e.g. can't have:
        // ->unlinkFrBack(relinker);
        // if (only_sometimes) relinker.relink(newp);
        UDEBUGONLY(
            UASSERT_STATIC(!m_backp, "Active linker must be relink()ed before destruction"););
    }
    inline void relink(AstNode* newp);
    void dump(std::ostream& str = std::cout) const;
};
inline std::ostream& operator<<(std::ostream& os, const VNRelinker& rhs) {
    rhs.dump(os);
    return os;
}

// ######################################################################
//  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

// 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) ASTNODE_PREFETCH_NON_NULL(nodep); \
    } while (false)

class AstNode VL_NOT_FINAL {
    // v ASTNODE_PREFETCH depends on below ordering of members
    AstNode* m_nextp = nullptr;  // Next peer in the parent's list
    AstNode* m_backp = nullptr;  // Node that points to this one (via next/op1/op2/...)
    AstNode* m_op1p = nullptr;  // Generic pointer 1
    AstNode* m_op2p = nullptr;  // Generic pointer 2
    AstNode* m_op3p = nullptr;  // Generic pointer 3
    AstNode* m_op4p = nullptr;  // Generic pointer 4
    AstNode** m_iterpp
        = nullptr;  // Pointer to node iterating on, change it if we replace this node.
    const VNType m_type;  // Node sub-type identifier
    // ^ ASTNODE_PREFETCH depends on above ordering of members

    // VNType is 2 bytes, so we can stick another 6 bytes after it to utilize what would
    // otherwise be padding (on a 64-bit system). We stick the attribute flags, broken state,
    // and the clone count here.

    struct {
        bool didWidth : 1;  // Did V3Width computation
        bool doingWidth : 1;  // Inside V3Width
        bool protect : 1;  // Protect name if protection is on
        // Space for more flags here (there must be 8 bits in total)
        uint8_t unused : 5;
    } m_flags;  // Attribute flags

    // State variable used by V3Broken for consistency checking. The top bit of this is byte is a
    // flag, representing V3Broken is currently proceeding under this node. The bottom 7 bits are
    // a generation number. This is hot when --debug-checks so we access as a whole to avoid bit
    // field masking resulting in unnecessary read-modify-write ops.
    uint8_t m_brokenState = 0;

    int m_cloneCnt = 0;  // Sequence number for when last clone was made

#if defined(__x86_64__) && defined(__gnu_linux__)
    // Only assert this on known platforms, as it only affects performance, not correctness
    static_assert(sizeof(m_type) + sizeof(m_flags) + sizeof(m_brokenState) + sizeof(m_cloneCnt)
                      <= sizeof(void*),
                  "packing requires padding");
#endif

    AstNodeDType* m_dtypep = nullptr;  // Data type of output or assignment (etc)
    AstNode* m_headtailp;  // When at begin/end of list, the opposite end of the list, else nullptr
    FileLine* m_fileline;  // Where it was declared
#ifdef VL_DEBUG
    // Only keep track of the edit count in the node in the debug build.
    // In the release build we will take the space saving instead.
    uint64_t m_editCount;  // When it was last edited
#endif
    static uint64_t s_editCntGbl;  // Global edit counter
    static uint64_t s_editCntLast;  // Last committed value of global edit counter

    AstNode* m_clonep = nullptr;  // Pointer to clone/source of node (only for *LAST* cloneTree())
    static int s_cloneCntGbl;  // Count of which userp is set

    // 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
    uint32_t m_user2Cnt = 0;  // Mark of when userp was set
    VNUser m_user2u{0};  // Contains any information the user iteration routine wants
    VNUser m_user3u{0};  // Contains any information the user iteration routine wants
    uint32_t m_user3Cnt = 0;  // Mark of when userp was set
    uint32_t m_user4Cnt = 0;  // Mark of when userp was set
    VNUser m_user4u{0};  // Contains any information the user iteration routine wants

    // METHODS
    void op1p(AstNode* nodep) {
        m_op1p = nodep;
        if (nodep) nodep->m_backp = this;
    }
    void op2p(AstNode* nodep) {
        m_op2p = nodep;
        if (nodep) nodep->m_backp = this;
    }
    void op3p(AstNode* nodep) {
        m_op3p = nodep;
        if (nodep) nodep->m_backp = this;
    }
    void op4p(AstNode* nodep) {
        m_op4p = nodep;
        if (nodep) nodep->m_backp = this;
    }

private:
    AstNode* cloneTreeIter(bool needPure);
    AstNode* cloneTreeIterList(bool needPure);
    void checkTreeIter(const AstNode* prevBackp) const VL_MT_STABLE;
    bool gateTreeIter() const;
    static bool sameTreeIter(const AstNode* node1p, const AstNode* node2p, bool ignNext,
                             bool gateOnly);
    void deleteTreeIter();
    void deleteNode();
    string instanceStr() const;

public:
    void purityCheck();
    static void relinkOneLink(AstNode*& pointpr, AstNode* newp);
    // cppcheck-suppress functionConst
    static void debugTreeChange(const AstNode* nodep, const char* prefix, int lineno, bool next);

protected:
    // CONSTRUCTORS
    AstNode(VNType t, FileLine* fl);
    virtual ~AstNode() = default;  // Use 'deleteTree' instead
    virtual AstNode* clone() = 0;  // Generally, cloneTree is what you want instead
    virtual void cloneRelink() { cloneRelinkGen(); }
    virtual void cloneRelinkGen() {};  // Overrides generated by 'astgen'
    void cloneRelinkTree();

    // METHODS
    void setOp1p(AstNode* newp);  // Set non-list-type op1 to non-list element
    void setOp2p(AstNode* newp);  // Set non-list-type op2 to non-list element
    void setOp3p(AstNode* newp);  // Set non-list-type op3 to non-list element
    void setOp4p(AstNode* newp);  // Set non-list-type op4 to non-list element

    void addOp1p(AstNode* newp);  // Append newp to end of op1
    void addOp2p(AstNode* newp);  // Append newp to end of op2
    void addOp3p(AstNode* newp);  // Append newp to end of op3
    void addOp4p(AstNode* newp);  // Append newp to end of op4

    // clang-format off
    void setNOp1p(AstNode* newp) { if (newp) setOp1p(newp); }
    void setNOp2p(AstNode* newp) { if (newp) setOp2p(newp); }
    void setNOp3p(AstNode* newp) { if (newp) setOp3p(newp); }
    void setNOp4p(AstNode* newp) { if (newp) setOp4p(newp); }

    void addNOp1p(AstNode* newp) { if (newp) addOp1p(newp); }
    void addNOp2p(AstNode* newp) { if (newp) addOp2p(newp); }
    void addNOp3p(AstNode* newp) { if (newp) addOp3p(newp); }
    void addNOp4p(AstNode* newp) { if (newp) addOp4p(newp); }
    // clang-format on

    void clonep(AstNode* nodep) {
        m_clonep = nodep;
        m_cloneCnt = s_cloneCntGbl;
    }
    static void cloneClearTree() {
        s_cloneCntGbl++;
        UASSERT_STATIC(s_cloneCntGbl, "Rollover");
    }

    // Use instead isSame(), this is for each Ast* class, and assumes node is of same type
    virtual bool sameNode(const AstNode*) const { return true; }
    // Generated by 'astgen'. If do an oldp->replaceNode(newp), would cause a broken()
    virtual bool wouldBreakGen(const AstNode* const oldp,
                               const AstNode* const newp) const
        = 0;  // Generated by 'astgen'

public:
    // ACCESSORS
    VNType type() const VL_MT_SAFE { return m_type; }
    const char* typeName() const VL_MT_SAFE { return type().ascii(); }  // See also prettyTypeName
    AstNode* nextp() const VL_MT_STABLE { return m_nextp; }
    AstNode* backp() const VL_MT_STABLE { return m_backp; }
    AstNode* abovep() const;  // Get parent node above, only for list head and tail
    AstNode* aboveLoopp() const;  // Get parent node above, may have performance issues as loops
    AstNode* lastp() const {  // Get last node in list, only for list head
        UASSERT_OBJ(m_backp->m_nextp != this, this, "lastp() only allowed on head of list");
        return m_headtailp;
    }
    AstNode* prevp() const {  // Previous node in list, nullptr for head of list
        if (m_backp->m_nextp != this) return nullptr;
        return m_backp;
    }
    AstNode* op1p() const VL_MT_STABLE { return m_op1p; }
    AstNode* op2p() const VL_MT_STABLE { return m_op2p; }
    AstNode* op3p() const VL_MT_STABLE { return m_op3p; }
    AstNode* op4p() const VL_MT_STABLE { return m_op4p; }
    AstNodeDType* dtypep() const VL_MT_STABLE { return m_dtypep; }
    AstNode* clonep() const { return ((m_cloneCnt == s_cloneCntGbl) ? m_clonep : nullptr); }
    AstNode* firstAbovep() const {  // Returns nullptr when second or later in list
        return ((backp() && backp()->nextp() != this) ? backp() : nullptr);
    }
    uint8_t brokenState() const VL_MT_SAFE { return m_brokenState; }
    void brokenState(uint8_t value) { m_brokenState = value; }

    // Used by AstNode::broken()
    bool brokeExists() const { return V3Broken::isLinkable(this); }
    bool brokeExistsAbove() const { return brokeExists() && (m_brokenState >> 7); }

    // Perform a function on every link in a node
    virtual void foreachLink(std::function<void(AstNode** linkpp, const char* namep)> f) = 0;

#if defined(VL_LEAK_CHECKS) || defined(VL_ALLOC_RANDOM_CHECKS)
    static void* operator new(size_t size);
    static void operator delete(void* obj, size_t size);
#endif

    // CONSTANTS
    // The following are relative dynamic costs (~ execution cycle count) of various operations.
    // They are used by V3InstCount to estimate the relative execution time of code fragments.
    static constexpr int INSTR_COUNT_BRANCH = 4;  // Branch
    static constexpr int INSTR_COUNT_CALL = INSTR_COUNT_BRANCH + 10;  // Subroutine call
    static constexpr int INSTR_COUNT_LD = 2;  // Load memory
    static constexpr int INSTR_COUNT_INT_MUL = 3;  // Integer multiply
    static constexpr int INSTR_COUNT_INT_DIV = 10;  // Integer divide
    static constexpr int INSTR_COUNT_DBL = 8;  // Convert or do float ops
    static constexpr int INSTR_COUNT_DBL_DIV = 40;  // Double divide
    static constexpr int INSTR_COUNT_DBL_TRIG = 200;  // Double trigonometric ops
    static constexpr int INSTR_COUNT_STR = 100;  // String ops
    static constexpr int INSTR_COUNT_TIME = INSTR_COUNT_CALL + 5;  // Determine simulation time
    static constexpr int INSTR_COUNT_PLI = 20;  // PLI routines
    static constexpr int INSTR_COUNT_SYM = 5;  // Syms ctor/dtor statements

    // ACCESSORS
    virtual string name() const VL_MT_STABLE { return ""; }
    virtual string origName() const { return ""; }
    string prettyDehashOrigOrName() const {
        return prettyName(VName::dehash(origName().empty() ? name() : origName()));
    }
    virtual void name(const string& name) {
        this->v3fatalSrc("name() called on object without name() method");
    }
    virtual void tag(const string& text) {}
    virtual string tag() const { return ""; }
    virtual uint32_t declTokenNum() const { return 0; }
    virtual void declTokenNumSetMin(uint32_t tokenNum) {}
    virtual string verilogKwd() const { return ""; }
    string nameProtect() const VL_MT_STABLE;  // Name with --protect-id applied
    string origNameProtect() const;  // origName with --protect-id applied
    string shortName() const;  // Name with __PVT__ removed for concatenating scopes
    static string dedotName(const string& namein);  // Name with dots removed
    static string prettyName(const string& namein) VL_PURE;  // Name for printing out to the user
    static string vpiName(const string& namein);  // Name for vpi access
    static string prettyNameQ(const string& namein) {  // Quoted pretty name (for errors)
        return "'"s + prettyName(namein) + "'";
    }
    // Encode user name into internal C representation
    static string encodeName(const string& namein);
    static string encodeNumber(int64_t num);  // Encode number into internal C representation
    static string vcdName(const string& namein);  // Name for printing out to vcd files
    string prettyName() const { return prettyName(name()); }
    string prettyNameQ() const { return prettyNameQ(name()); }
    string verilogName() const { return vpiName(origName()); }  // Decoded original Verilog name
    // "VARREF" for error messages (NOT dtype's pretty name)
    string prettyTypeName() const;
    virtual string prettyOperatorName() const { return "operator " + prettyTypeName(); }
    FileLine* fileline() const VL_MT_SAFE { return m_fileline; }
    void fileline(FileLine* fl) { m_fileline = fl; }
    inline bool width1() const;
    inline int widthInstrs() const;
    bool didWidth() const { return m_flags.didWidth; }
    void didWidth(bool flag) { m_flags.didWidth = flag; }
    bool didWidthAndSet() {
        if (didWidth()) return true;
        didWidth(true);
        return false;
    }
    bool doingWidth() const { return m_flags.doingWidth; }
    void doingWidth(bool flag) { m_flags.doingWidth = flag; }
    bool protect() const VL_MT_SAFE { return m_flags.protect; }
    void protect(bool flag) { m_flags.protect = flag; }

    // TODO stomp these width functions out, and call via dtypep() instead
    inline int width() const VL_MT_STABLE;
    inline int widthMin() const VL_MT_STABLE;
    int widthMinV() const {
        return v3Global.widthMinUsage() == VWidthMinUsage::VERILOG_WIDTH ? widthMin() : width();
    }
    int widthWords() const { return VL_WORDS_I(width()); }
    bool isQuad() const VL_MT_STABLE { return (width() > VL_IDATASIZE && width() <= VL_QUADSIZE); }
    bool isWide() const VL_MT_STABLE { return (width() > VL_QUADSIZE); }
    inline bool isCHandle() const VL_MT_STABLE;
    inline bool isDouble() const VL_MT_STABLE;
    inline bool isSigned() const VL_MT_STABLE;
    inline bool isString() const VL_MT_STABLE;
    inline bool isEvent() const VL_MT_STABLE;

    // clang-format off
    VNUser user1u() const VL_MT_STABLE {
        // Slows things down measurably, so disabled by default
        //UASSERT_STATIC(VNUser1InUse::s_userBusy, "user1p used without AstUserInUse");
        return ((m_user1Cnt == VNUser1InUse::s_userCntGbl) ? m_user1u : VNUser{0});
    }
    AstNode* user1p() const VL_MT_STABLE { return user1u().toNodep(); }
    void user1u(const VNUser& user) { m_user1u = user; m_user1Cnt = VNUser1InUse::s_userCntGbl; }
    void user1p(void* userp) { user1u(VNUser{userp}); }
    void user1(int val) { user1u(VNUser{val}); }
    int user1() const { return user1u().toInt(); }
    int user1Inc(int val = 1) { const int v = user1(); user1(v + val); return v; }
    int user1Or(int val) { const int v = user1(); user1(v | val); return v; }
    int user1SetOnce() { const int v = user1(); if (!v) user1(1); return v; }  // Better for cache than user1Inc()
    static void user1ClearTree() { VNUser1InUse::clear(); }  // Clear userp()'s across the entire tree

    VNUser user2u() const VL_MT_STABLE {
        // Slows things down measurably, so disabled by default
        //UASSERT_STATIC(VNUser2InUse::s_userBusy, "user2p used without AstUserInUse");
        return ((m_user2Cnt == VNUser2InUse::s_userCntGbl) ? m_user2u : VNUser{0});
    }
    AstNode* user2p() const VL_MT_STABLE { return user2u().toNodep(); }
    void user2u(const VNUser& user) { m_user2u = user; m_user2Cnt = VNUser2InUse::s_userCntGbl; }
    void user2p(void* userp) { user2u(VNUser{userp}); }
    void user2(int val) { user2u(VNUser{val}); }
    int user2() const { return user2u().toInt(); }
    int user2Inc(int val = 1) { const int v = user2(); user2(v + val); return v; }
    int user2Or(int val) { const int v = user2(); user2(v | val); return v; }
    int user2SetOnce() { const int v = user2(); if (!v) user2(1); return v; }  // Better for cache than user2Inc()
    static void user2ClearTree() { VNUser2InUse::clear(); }  // Clear userp()'s across the entire tree

    VNUser user3u() const VL_MT_STABLE {
        // Slows things down measurably, so disabled by default
        //UASSERT_STATIC(VNUser3InUse::s_userBusy, "user3p used without AstUserInUse");
        return ((m_user3Cnt == VNUser3InUse::s_userCntGbl) ? m_user3u : VNUser{0});
    }
    AstNode* user3p() const VL_MT_STABLE { return user3u().toNodep(); }
    void user3u(const VNUser& user) { m_user3u = user; m_user3Cnt = VNUser3InUse::s_userCntGbl; }
    void user3p(void* userp) { user3u(VNUser{userp}); }
    void user3(int val) { user3u(VNUser{val}); }
    int user3() const { return user3u().toInt(); }
    int user3Inc(int val = 1) { const int v = user3(); user3(v + val); return v; }
    int user3Or(int val) { const int v = user3(); user3(v | val); return v; }
    int user3SetOnce() { const int v = user3(); if (!v) user3(1); return v; }  // Better for cache than user3Inc()
    static void user3ClearTree() { VNUser3InUse::clear(); }  // Clear userp()'s across the entire tree

    VNUser user4u() const VL_MT_STABLE {
        // Slows things down measurably, so disabled by default
        //UASSERT_STATIC(VNUser4InUse::s_userBusy, "user4p used without AstUserInUse");
        return ((m_user4Cnt == VNUser4InUse::s_userCntGbl) ? m_user4u : VNUser{0});
    }
    AstNode* user4p() const VL_MT_STABLE { return user4u().toNodep(); }
    void user4u(const VNUser& user) { m_user4u = user; m_user4Cnt = VNUser4InUse::s_userCntGbl; }
    void user4p(void* userp) { user4u(VNUser{userp}); }
    void user4(int val) { user4u(VNUser{val}); }
    int user4() const { return user4u().toInt(); }
    int user4Or(int val) { const int v = user4(); user4(v | val); return v; }
    int user4Inc(int val = 1) { const int v = user4(); user4(v + val); return v; }
    int user4SetOnce() { const int v = user4(); if (!v) user4(1); return v; }  // Better for cache than user4Inc()
    static void user4ClearTree() { VNUser4InUse::clear(); }  // Clear userp()'s across the entire tree
    // clang-format on

#ifdef VL_DEBUG
    uint64_t editCount() const { return m_editCount; }
    void editCountInc() {
        m_editCount = ++s_editCntGbl;  // Preincrement, so can "watch AstNode::s_editCntGbl=##"
        VIsCached::clearCacheTree();  // Any edit clears all caching
    }
#else
    void editCountInc() { ++s_editCntGbl; }
#endif
    static uint64_t editCountLast() VL_MT_SAFE { return s_editCntLast; }
    static uint64_t editCountGbl() VL_MT_SAFE { return s_editCntGbl; }
    static void editCountSetLast() { s_editCntLast = editCountGbl(); }

    // ACCESSORS for specific types
    // Alas these can't be virtual or they break when passed a nullptr
    inline bool isClassHandleValue() const;
    inline bool isNull() const;
    inline bool isZero() const;
    inline bool isOne() const;
    inline bool isNeqZero() const;
    inline bool isAllOnes() const;
    inline bool isAllOnesV() const;  // Verilog width rules apply

    // METHODS - data type changes especially for initial creation
    void dtypep(AstNodeDType* nodep) {
        if (m_dtypep != nodep) {
            m_dtypep = nodep;
            editCountInc();
        }
    }
    void dtypeFrom(const AstNode* fromp) {
        if (fromp) dtypep(fromp->dtypep());
    }
    void dtypeChgSigned(bool flag = true);
    void dtypeChgWidth(int width, int widthMin);
    void dtypeChgWidthSigned(int width, int widthMin, VSigning numeric);
    void dtypeSetBitUnsized(int width, int widthMin, VSigning numeric) {
        dtypep(findBitDType(width, widthMin, numeric));
    }
    void dtypeSetBitSized(int width, VSigning numeric) {
        dtypep(findBitDType(width, width, numeric));  // Since sized, widthMin is width
    }
    void dtypeSetLogicUnsized(int width, int widthMin, VSigning numeric) {
        dtypep(findLogicDType(width, widthMin, numeric));
    }
    void dtypeSetLogicSized(int width, VSigning numeric) {
        dtypep(findLogicDType(width, width, numeric));  // Since sized, widthMin is width
    }
    void dtypeSetBit() { dtypep(findBitDType()); }
    void dtypeSetLogic() { dtypep(findLogicDType()); }
    void dtypeSetDouble() { dtypep(findDoubleDType()); }
    void dtypeSetInt() { dtypep(findIntDType()); }
    void dtypeSetInteger() { dtypep(findIntegerDType()); }
    // TODO: add a type to handle 4-state integers with 2-state domain
    void dtypeSetInteger2State() { dtypep(findIntegerDType()); }
    void dtypeSetString() { dtypep(findStringDType()); }
    void dtypeSetUInt32() { dtypep(findUInt32DType()); }  // Twostate
    void dtypeSetUInt64() { dtypep(findUInt64DType()); }  // Twostate
    void dtypeSetEmptyQueue() { dtypep(findEmptyQueueDType()); }
    void dtypeSetStream() { dtypep(findStreamDType()); }
    void dtypeSetVoid() { dtypep(findVoidDType()); }

    // Data type locators
    AstNodeDType* findBitDType() const { return findBasicDType(VBasicDTypeKwd::BIT); }
    AstNodeDType* findLogicDType() const { return findBasicDType(VBasicDTypeKwd::LOGIC); }
    AstNodeDType* findDoubleDType() const { return findBasicDType(VBasicDTypeKwd::DOUBLE); }
    AstNodeDType* findIntDType() const { return findBasicDType(VBasicDTypeKwd::INT); }
    AstNodeDType* findIntegerDType() const { return findBasicDType(VBasicDTypeKwd::INTEGER); }
    AstNodeDType* findStringDType() const { return findBasicDType(VBasicDTypeKwd::STRING); }
    AstNodeDType* findSigned8DType() const { return findBasicDType(VBasicDTypeKwd::BYTE); }
    AstNodeDType* findUInt32DType() const { return findBasicDType(VBasicDTypeKwd::UINT32); }
    AstNodeDType* findUInt64DType() const { return findBasicDType(VBasicDTypeKwd::UINT64); }
    AstNodeDType* findCHandleDType() const { return findBasicDType(VBasicDTypeKwd::CHANDLE); }
    AstNodeDType* findConstraintRefDType() const;
    AstNodeDType* findEmptyQueueDType() const;
    AstNodeDType* findQueueIndexDType() const;
    AstNodeDType* findStreamDType() const;
    AstNodeDType* findVoidDType() const;
    AstNodeDType* findBitDType(int width, int widthMin, VSigning numeric) const;
    AstNodeDType* findLogicDType(int width, int widthMin, VSigning numeric) const;
    AstNodeDType* findBitOrLogicDType(int width, int widthMin, VSigning numeric,
                                      bool isFourstate) const {
        return isFourstate ? findLogicDType(width, widthMin, numeric)
                           : findBitDType(width, widthMin, numeric);
    }
    AstNodeDType* findLogicRangeDType(const VNumRange& range, int widthMin,
                                      VSigning numeric) const VL_MT_STABLE;
    AstNodeDType* findBitRangeDType(const VNumRange& range, int widthMin,
                                    VSigning numeric) const VL_MT_STABLE;
    AstNodeDType* findBasicDType(VBasicDTypeKwd kwd) const;
    static AstBasicDType* findInsertSameDType(AstBasicDType* nodep);

    static VCastable computeCastable(const AstNodeDType* toDtp, const AstNodeDType* fromDtp,
                                     const AstNode* fromConstp);
    static AstNodeDType* getCommonClassTypep(AstNode* nodep1, AstNode* nodep2);

    // METHODS - dump and error
    void v3errorEnd(const std::ostringstream& str) const VL_RELEASE(V3Error::s().m_mutex);
    void v3errorEndFatal(const std::ostringstream& str) const VL_ATTR_NORETURN
        VL_RELEASE(V3Error::s().m_mutex);
    string warnContextPrimary() const VL_REQUIRES(V3Error::s().m_mutex) {
        return fileline()->warnContextPrimary();
    }
    string warnContextSecondary() const { return fileline()->warnContextSecondary(); }
    string warnMore() const VL_REQUIRES(V3Error::s().m_mutex) { return fileline()->warnMore(); }
    string warnOther() const VL_REQUIRES(V3Error::s().m_mutex) { return fileline()->warnOther(); }

    virtual void dump(std::ostream& str = std::cout) const;
    static char* dumpTreeJsonGdb(const AstNode* nodep);  // For GDB only, free()d by caller
    static char* dumpTreeJsonGdb(const char* str);  // For GDB only, free()d by caller
    static char* dumpTreeJsonGdb(intptr_t nodep);  // For GDB only, free()d by caller
    static void dumpGdb(const AstNode* nodep);  // For GDB only
    void dumpGdbHeader() const;

    // METHODS - Tree modifications
    // Returns nodep. If newp is not nullptr, then add it to end of nodep's list.
    template <typename T_NodeResult, typename T_NodeNext>
    static T_NodeResult* addNext(T_NodeResult* nodep, T_NodeNext* newp) {
        static_assert(std::is_base_of<AstNode, T_NodeResult>::value,
                      "'T_NodeResult' must be a subtype of AstNode");
        static_assert(std::is_base_of<T_NodeResult, T_NodeNext>::value,
                      "'T_NodeNext' must be a subtype of 'T_NodeResult'");
        if (!newp) return nodep;
        return static_cast<T_NodeResult*>(addNext<AstNode, AstNode>(nodep, newp));
    }
    template <typename T_NodeResult, typename T_NodeNext>
    static T_NodeResult* addNextNull(T_NodeResult* nodep, T_NodeNext* newp) {
        static_assert(std::is_base_of<AstNode, T_NodeResult>::value,
                      "'T_NodeResult' must be a subtype of AstNode");
        static_assert(std::is_base_of<T_NodeResult, T_NodeNext>::value,
                      "'T_NodeNext' must be a subtype of 'T_NodeResult'");
        if (!newp) return nodep;
        if (!nodep) return newp;
        return static_cast<T_NodeResult*>(addNext<AstNode, AstNode>(nodep, newp));
    }
    inline AstNode* addNext(AstNode* newp);
    void addNextHere(AstNode* newp);  // Insert newp at this->nextp
    void addHereThisAsNext(AstNode* newp);  // Adds at old place of this, this becomes next
    void replaceWith(AstNode* newp);  // Replace current node in tree with new node
    void replaceWithKeepDType(AstNode* newp);  // Replace current node in tree, keep old dtype
    // Unlink this from whoever points to it.
    AstNode* unlinkFrBack(VNRelinker* linkerp = nullptr);
    // Unlink this from whoever points to it, keep entire next list with unlinked node
    AstNode* unlinkFrBackWithNext(VNRelinker* linkerp = nullptr);
    void relink(VNRelinker* linkerp);  // Generally use linker->relink() instead
    void cloneRelinkNode() { cloneRelink(); }

    // METHODS - Iterate on a tree
    AstNode* cloneTree(bool cloneNextLink,
                       bool needPure = false);  // Not const, as sets clonep() on original nodep
    AstNode* cloneTreePure(bool cloneNextLink) { return cloneTree(cloneNextLink, true); }
    bool gateTree() { return gateTreeIter(); }  // Is tree isGateOptimizable?
    inline bool sameTree(const AstNode* node2p) const;  // Does tree of this == node2p?
    // Does tree of this == node2p?, not allowing non-isGateOptimizable
    inline bool sameGateTree(const AstNode* node2p) const;
    void deleteTree();  // Always deletes the next link
    void checkTree() const VL_MT_STABLE {
        if (v3Global.opt.debugCheck()) checkTreeIter(backp());
    }
    void checkIter() const;
    void dumpPtrs(std::ostream& os = std::cout) const;
    void dumpTree(std::ostream& os = std::cout, const string& indent = "    ",
                  int maxDepth = 0) const;
    void dumpTree(const string& indent, int maxDepth = 0) const {
        dumpTree(std::cout, indent, maxDepth);
    }
    static void dumpTreeGdb(const AstNode* nodep);  // For GDB only
    void dumpTreeAndNext(std::ostream& os = std::cout, const string& indent = "    ",
                         int maxDepth = 0) const;
    void dumpTreeFile(const string& filename, bool doDump = true);
    static void dumpTreeFileGdb(const AstNode* nodep, const char* filenamep = nullptr);
    void dumpTreeDot(std::ostream& os = std::cout) const;
    void dumpTreeDotFile(const string& filename, bool doDump = true);
    virtual void dumpJson(std::ostream& os) const { dumpJsonGen(os); };  // node-specific fields
    // Generated by 'astgen'. Dumps node-specific pointers and calls 'dumpJson()' of parent class
    // Note that we don't make it virtual as it would result in infinite recursion
    void dumpJsonGen(std::ostream& os) const {};
    virtual void dumpTreeJsonOpGen(std::ostream& os, const string& indent) const {};
    void dumpTreeJson(std::ostream& os, const string& indent = "") const;
    void dumpTreeJsonFile(const string& filename, bool doDump = true);
    static void dumpJsonMetaFileGdb(const char* filename);
    static void dumpJsonMetaFile(const string& filename);

    // Render node address for dumps. By default this is just the address
    // printed as hex, but with --dump-tree-addrids we map addresses to short
    // strings with a bijection to aid human readability. Observe that this might
    // not actually be a unique identifier as the address can get reused after a
    // node has been freed.
    static std::string nodeAddr(const AstNode* nodep) {
        return v3Global.opt.dumpTreeAddrids() ? v3Global.ptrToId(nodep) : cvtToHex(nodep);
    }

    // METHODS - static advancement
    static AstNode* afterCommentp(AstNode* nodep) {
        // Skip over comments starting at provided nodep,
        // such as to determine if a AstIf is empty.
        // nodep may be null, if so return nullptr.
        while (nodep && VN_IS(nodep, Comment)) nodep = nodep->nextp();
        return nodep;
    }

    // METHODS - queries
    // Changes control flow, disable some optimizations
    virtual bool isBrancher() const { return false; }
    // Else a AstTime etc that can't be pushed out
    virtual bool isGateOptimizable() const { return !isTimingControl(); }
    // GateDedupable is a slightly larger superset of GateOptimzable (eg, AstNodeIf)
    virtual bool isGateDedupable() const { return isGateOptimizable(); }
    // Whether the node can be used in expression coverage
    virtual bool isExprCoverageEligible() const { return isGateDedupable(); }
    // Else creates output or exits, etc, not unconsumed
    virtual bool isOutputter() { return false; }
    // Else a AstTime etc which output can't be predicted from input
    virtual bool isPredictOptimizable() const { return !isTimingControl(); }
    // Else a $display, etc, that must be ordered with other displays
    virtual bool isPure() { return true; }
    // Iff isPure on current node and any nextp()
    bool isPureAndNext() { return isPure() && (!nextp() || nextp()->isPure()); }
    // An event control, delay, wait, etc.
    virtual bool isTimingControl() const { return false; }
    // isUnlikely handles $stop or similar statement which means an above IF
    // statement is unlikely to be taken
    virtual bool isUnlikely() const { return false; }
    // Is an IEEE system function (versus internally-generated)
    virtual bool isSystemFunc() const { return false; }
    virtual int instrCount() const { return 0; }
    // Iff node is identical to another node
    virtual bool isSame(const AstNode* samep) const {
        return type() == samep->type() && sameNode(samep);
    }
    // Iff has a data type; dtype() must be non-null
    virtual bool hasDType() const VL_MT_SAFE { return false; }
    // Iff has a non-null childDTypep(), as generic node function
    virtual AstNodeDType* getChildDTypep() const { return nullptr; }
    // Iff has a non-null child2DTypep(), as generic node function
    virtual AstNodeDType* getChild2DTypep() const { return nullptr; }
    // Another AstNode* may have a pointer into this node, other then normal front/back/etc.
    virtual bool maybePointedTo() const VL_MT_SAFE { return false; }
    // Don't reclaim this node in V3Dead
    virtual bool undead() const { return false; }
    // Check if node is consistent, return nullptr if ok, else reason string
    virtual const char* broken() const { return nullptr; }
    // Generated by 'astgen'. Calls 'broken()', which can be used to add extra checks
    virtual const char* brokenGen() const = 0;  // Generated by 'astgen'
    // If do a this->replaceNode(newp), would cause a broken()
    bool wouldBreak(const AstNode* const newp) const { return backp()->wouldBreakGen(this, newp); }

    // INVOKERS
    virtual void accept(VNVisitorConst& v) = 0;

protected:
    // All VNVisitor related functions are called as methods off the visitor
    friend class VNVisitor;
    friend class VNVisitorConst;
    // Use instead VNVisitor::iterateChildren
    void iterateChildren(VNVisitor& v);
    // Use instead VNVisitor::iterateChildrenBackwardsConst
    void iterateChildrenBackwardsConst(VNVisitorConst& v);
    // Use instead VNVisitor::iterateChildrenConst
    void iterateChildrenConst(VNVisitorConst& v);
    // Use instead VNVisitor::iterateAndNextNull
    void iterateAndNext(VNVisitor& v);
    // Use instead VNVisitor::iterateAndNextConstNull
    void iterateAndNextConst(VNVisitorConst& v);
    // Use instead VNVisitor::iterateSubtreeReturnEdits
    AstNode* iterateSubtreeReturnEdits(VNVisitor& v);

    static void dumpJsonNum(std::ostream& os, const std::string& name, int64_t val);
    static void dumpJsonBool(std::ostream& os, const std::string& name, bool val);
    static void dumpJsonBoolIf(std::ostream& os, const std::string& name, bool val);
    static void dumpJsonStr(std::ostream& os, const std::string& name, const std::string& val);
    static void dumpJsonPtr(std::ostream& os, const std::string& name, const AstNode* const valp);

protected:
    void iterateListBackwardsConst(VNVisitorConst& v);

    // For internal use only.
    // Note: specializations for particular node types are provided by 'astgen'
    template <typename T>
    inline static bool privateTypeTest(const AstNode* nodep);

    // For internal use only.
    template <typename T_TargetType, typename T_DeclType>
    constexpr static bool uselessCast() VL_PURE {  // LCOV_EXCL_START
        using NonRef = typename std::remove_reference<T_DeclType>::type;
        using NonPtr = typename std::remove_pointer<NonRef>::type;
        using NonCV = typename std::remove_cv<NonPtr>::type;
        return std::is_base_of<T_TargetType, NonCV>::value;
    }  // LCOV_EXCL_STOP

    // For internal use only.
    template <typename T_TargetType, typename T_DeclType>
    constexpr static bool impossibleCast() VL_PURE {  // LCOV_EXCL_START
        using NonRef = typename std::remove_reference<T_DeclType>::type;
        using NonPtr = typename std::remove_pointer<NonRef>::type;
        using NonCV = typename std::remove_cv<NonPtr>::type;
        return !std::is_base_of<NonCV, T_TargetType>::value;
    }  // LCOV_EXCL_STOP

public:
    // For use via the VN_IS macro only, or in templated code
    template <typename T_TargetType, typename T_Node>
    static bool is(const T_Node* nodep) VL_MT_SAFE {
        static_assert(std::is_base_of<AstNode, T_Node>::value, "VN_IS called on non-AstNode");
        static_assert(!uselessCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_IS, node known to have target type.");
        static_assert(!impossibleCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_IS, node cannot be this type.");
        return nodep && privateTypeTest<T_TargetType>(nodep);
    }

    // For use via the VN_CAST macro only, or in templated code
    template <typename T_TargetType, typename T_Node>
    static T_TargetType* cast(T_Node* nodep) VL_MT_SAFE {
        static_assert(std::is_base_of<AstNode, T_Node>::value, "VN_CAST called on non-AstNode");
        static_assert(!uselessCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_CAST, node known to have target type.");
        static_assert(!impossibleCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_CAST, node cannot be this type.");
        return nodep && privateTypeTest<T_TargetType>(nodep)
                   ? reinterpret_cast<T_TargetType*>(nodep)
                   : nullptr;
    }
    template <typename T_TargetType, typename T_Node>
    static const T_TargetType* cast(const T_Node* nodep) VL_MT_SAFE {
        static_assert(std::is_base_of<AstNode, T_Node>::value, "VN_CAST called on non-AstNode");
        static_assert(!uselessCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_CAST, node known to have target type.");
        static_assert(!impossibleCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_CAST, node cannot be this type.");
        return nodep && privateTypeTest<T_TargetType>(nodep)
                   ? reinterpret_cast<const T_TargetType*>(nodep)
                   : nullptr;
    }

    // For use via the VN_AS macro only, or in templated code
    template <typename T_TargetType, typename T_Node>
    static T_TargetType* as(T_Node* nodep) VL_PURE {
        static_assert(std::is_base_of<AstNode, T_Node>::value, "VN_AS called on non-AstNode");
        static_assert(!uselessCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_AS, node known to have target type.");
        static_assert(!impossibleCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_AS, node cannot be this type.");
        UASSERT_OBJ(!nodep || privateTypeTest<T_TargetType>(nodep), nodep,
                    "AstNode is not of expected type, but instead has type '" << nodep->typeName()
                                                                              << "'");
        return unsafePrivateAs<T_TargetType, T_Node>(nodep);
    }
    template <typename T_TargetType, typename T_Node>
    static const T_TargetType* as(const T_Node* nodep) VL_PURE {
        static_assert(std::is_base_of<AstNode, T_Node>::value, "VN_AS called on non-AstNode");
        static_assert(!uselessCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_AS, node known to have target type.");
        static_assert(!impossibleCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_AS, node cannot be this type.");
        UASSERT_OBJ(!nodep || privateTypeTest<T_TargetType>(nodep), nodep,
                    "AstNode is not of expected type, but instead has type '" << nodep->typeName()
                                                                              << "'");
        return unsafePrivateAs<T_TargetType, T_Node>(nodep);
    }

    // For use via privateAs or the VN_DBG_AS macro only
    template <typename T_TargetType, typename T_Node>
    static T_TargetType* unsafePrivateAs(T_Node* nodep) VL_PURE {
        static_assert(std::is_base_of<AstNode, T_Node>::value, "VN_DBG_AS called on non-AstNode");
        static_assert(!uselessCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_DBG_AS, node known to have target type.");
        static_assert(!impossibleCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_DBG_AS, node cannot be this type.");
        return reinterpret_cast<T_TargetType*>(nodep);
    }
    template <typename T_TargetType, typename T_Node>
    static const T_TargetType* unsafePrivateAs(const T_Node* nodep) VL_PURE {
        static_assert(std::is_base_of<AstNode, T_Node>::value, "VN_DBG_AS called on non-AstNode");
        static_assert(!uselessCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_DBG_AS, node known to have target type.");
        static_assert(!impossibleCast<T_TargetType, T_Node>(),
                      "Unnecessary VN_DBG_AS, node cannot be this type.");
        return reinterpret_cast<const T_TargetType*>(nodep);
    }

    // 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.
    // 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 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:
    // 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;

    template <typename T_Arg, typename T_Callable>
    inline static void foreachImpl(ConstCorrectAstNode<T_Arg>* nodep, const T_Callable& f,
                                   bool visitNext);

    template <typename T_Arg, bool N_Default, typename T_Callable>
    inline static bool predicateImpl(ConstCorrectAstNode<T_Arg>* nodep, const T_Callable& p,
                                     bool visitNext);

public:
    // Given a callable 'f' that takes a single argument of some AstNode subtype 'T_Node', traverse
    // the tree rooted at this node, and call 'f' in pre-order on each node that is of type
    // 'T_Node'. The node passed to the callable '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 callable in 'foreach' should be completely predictable by branch target
    // caches in modern CPUs, while it is basically unpredictable for VNVisitor.
    template <typename T_Callable>
    void foreach(T_Callable&& f) {
        using T_Node = typename FunctionArgNoPointerNoCV<T_Callable, 0>::type;
        static_assert(vlstd::is_invocable<T_Callable, T_Node*>::value
                          && std::is_base_of<AstNode, T_Node>::value,
                      "T_Callable 'f' must have a signature compatible with 'void(T_Node*)', "
                      "with 'T_Node' being a subtype of 'AstNode'");
        foreachImpl<T_Node>(this, f, /* visitNext: */ false);
    }
    // Same as above, but for 'const' nodes
    template <typename T_Callable>
    void foreach(T_Callable&& f) const {
        using T_Node = typename FunctionArgNoPointerNoCV<T_Callable, 0>::type;
        static_assert(
            vlstd::is_invocable<T_Callable, const T_Node*>::value
                && std::is_base_of<AstNode, T_Node>::value,
            "T_Callable 'f' must have a signature compatible with 'void(const T_Node*)', "
            "with 'T_Node' being a subtype of 'AstNode'");
        foreachImpl<const T_Node>(this, f, /* visitNext: */ false);
    }
    // Same as 'foreach' but also traverses 'this->nextp()' transitively
    template <typename T_Callable>
    void foreachAndNext(T_Callable&& f) {
        using T_Node = typename FunctionArgNoPointerNoCV<T_Callable, 0>::type;
        static_assert(vlstd::is_invocable<T_Callable, T_Node*>::value
                          && std::is_base_of<AstNode, T_Node>::value,
                      "T_Callable 'f' must have a signature compatible with 'void(T_Node*)', "
                      "with 'T_Node' being a subtype of 'AstNode'");
        foreachImpl<T_Node>(this, f, /* visitNext: */ true);
    }
    // Same as above, but for 'const' nodes
    template <typename T_Callable>
    void foreachAndNext(T_Callable&& f) const {
        using T_Node = typename FunctionArgNoPointerNoCV<T_Callable, 0>::type;
        static_assert(
            vlstd::is_invocable<T_Callable, const T_Node*>::value
                && std::is_base_of<AstNode, T_Node>::value,
            "T_Callable 'f' must have a signature compatible with 'void(const T_Node*)', "
            "with 'T_Node' being a subtype of 'AstNode'");
        foreachImpl<const T_Node>(this, f, /* visitNext: */ true);
    }

    // Given a predicate 'p' that takes a single argument of some AstNode subtype 'T_Node', return
    // true if and only if there exists a node of type 'T_Node' in the tree rooted at this node,
    // that satisfies the predicate 'p'. Returns false if no node of type 'T_Node' is present.
    // Traversal is performed in some arbitrary order and is terminated as soon as the result can
    // be determined.
    template <typename T_Callable>
    bool exists(T_Callable&& p) {
        using T_Node = typename FunctionArgNoPointerNoCV<T_Callable, 0>::type;
        static_assert(vlstd::is_invocable_r<bool, T_Callable, T_Node*>::value
                          && std::is_base_of<AstNode, T_Node>::value,
                      "Predicate 'p' must have a signature compatible with 'bool(T_Node*)', "
                      "with 'T_Node' being a subtype of 'AstNode'");
        return predicateImpl<T_Node, /* N_Default: */ false>(this, p, /* visitNext: */ false);
    }
    // Same as above, but for 'const' nodes
    template <typename T_Callable>
    bool exists(T_Callable&& p) const {
        using T_Node = typename FunctionArgNoPointerNoCV<T_Callable, 0>::type;
        static_assert(vlstd::is_invocable_r<bool, T_Callable, const T_Node*>::value
                          && std::is_base_of<AstNode, T_Node>::value,
                      "Predicate 'p' must have a signature compatible with 'bool(const T_Node*)', "
                      "with 'T_Node' being a subtype of 'AstNode'");
        return predicateImpl<const T_Node, /* N_Default: */ false>(this, p,
                                                                   /* visitNext: */ false);
    }
    // Same as 'exists' but also traverses 'this->nextp()' transitively
    template <typename T_Callable>
    bool existsAndNext(T_Callable&& p) {
        using T_Node = typename FunctionArgNoPointerNoCV<T_Callable, 0>::type;
        static_assert(vlstd::is_invocable_r<bool, T_Callable, const T_Node*>::value
                          && std::is_base_of<AstNode, T_Node>::value,
                      "Predicate 'p' must have a signature compatible with 'bool(const T_Node*)', "
                      "with 'T_Node' being a subtype of 'AstNode'");
        return predicateImpl<T_Node, /* N_Default: */ false>(this, p, /* visitNext: */ true);
    }
    // Same as above, but for 'const' nodes
    template <typename T_Callable>
    bool existsAndNext(T_Callable&& p) const {
        using T_Node = typename FunctionArgNoPointerNoCV<T_Callable, 0>::type;
        static_assert(vlstd::is_invocable_r<bool, T_Callable, const T_Node*>::value
                          && std::is_base_of<AstNode, T_Node>::value,
                      "Predicate 'p' must have a signature compatible with 'bool(const T_Node*)', "
                      "with 'T_Node' being a subtype of 'AstNode'");
        return predicateImpl<const T_Node, /* N_Default: */ false>(this, p, /* visitNext: */ true);
    }

    // Given a predicate 'p' that takes a single argument of some AstNode subtype 'T_Node', return
    // true if and only if all nodes of type 'T_Node' in the tree rooted at this node satisfy the
    // predicate 'p'. Returns true if no node of type 'T_Node' is present. Traversal is performed
    // in some arbitrary order and is terminated as soon as the result can be determined.
    template <typename T_Callable>
    bool forall(T_Callable&& p) {
        using T_Node = typename FunctionArgNoPointerNoCV<T_Callable, 0>::type;
        static_assert(vlstd::is_invocable_r<bool, T_Callable, T_Node*>::value
                          && std::is_base_of<AstNode, T_Node>::value,
                      "Predicate 'p' must have a signature compatible with 'bool(T_Node*)', "
                      "with 'T_Node' being a subtype of 'AstNode'");
        return predicateImpl<T_Node, /* N_Default: */ true>(this, p, /* visitNext: */ false);
    }
    // Same as above, but for 'const' nodes
    template <typename T_Callable>
    bool forall(T_Callable&& p) const {
        using T_Node = typename FunctionArgNoPointerNoCV<T_Callable, 0>::type;
        static_assert(vlstd::is_invocable_r<bool, T_Callable, const T_Node*>::value
                          && std::is_base_of<AstNode, T_Node>::value,
                      "Predicate 'p' must have a signature compatible with 'bool(const T_Node*)', "
                      "with 'T_Node' being a subtype of 'AstNode'");
        return predicateImpl<const T_Node, /* N_Default: */ true>(this, p, /* visitNext: */ false);
    }

    int nodeCount() const {
        // TODO: this should really return size_t, but need to fix use sites
        int count = 0;
        this->foreach([&count](const AstNode*) { ++count; });
        return count;
    }
};

// Forward declarations of specializations defined in V3Ast.cpp
template <>
AstNode* AstNode::addNext<AstNode, AstNode>(AstNode* nodep, AstNode* newp);

// Inline method implementations
AstNode* AstNode::addNext(AstNode* newp) { return addNext(this, newp); }

// Specializations of privateTypeTest
#include "V3Ast__gen_type_tests.h"  // From ./astgen

// Specializations of AstNode::mayBeUnder
template <>
inline bool AstNode::mayBeUnder<AstCell>(const AstNode* nodep) {
    return !VN_IS(nodep, NodeStmt) && !VN_IS(nodep, NodeExpr);
}
template <>
inline bool AstNode::mayBeUnder<AstNodeAssign>(const AstNode* nodep) {
    return !VN_IS(nodep, NodeExpr);
}
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, NodeExpr)) return false;
    return true;
}
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::mayBeUnder<AstActive>(const AstNode* nodep) {
    return !VN_IS(nodep, Active);  // AstActives do not nest
}
template <>
inline bool AstNode::mayBeUnder<AstScope>(const AstNode* nodep) {
    return !VN_IS(nodep, Scope);  // AstScopes do not nest
}
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, typename T_Callable>
void AstNode::foreachImpl(ConstCorrectAstNode<T_Arg>* nodep, const T_Callable& f, bool visitNext) {
    // 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>;

    // We prefetch this far into the stack
    constexpr int PrefetchDistance = 2;
    // We push max 5 items per iteration
    constexpr size_t MaxPushesPerIteration = 5;
    // Initial stack sie
    constexpr size_t InitialStackSize = 32;

    // Traversal stack. Because we often iterate over small trees, use an in-line initial stack
    size_t stackSize = InitialStackSize;
    Node* inLineStack[InitialStackSize];  // In-line initial stack storage
    std::unique_ptr<Node*[]> outOfLineStackp{nullptr};  // In case we need more, we will allocate
    Node** basep = inLineStack + PrefetchDistance;  // Pointer to base of stack
    Node** topp = basep;  // Pointer to top of stack
    Node** limp = inLineStack + stackSize - MaxPushesPerIteration;  // Pointer to grow limit

    // 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) {
        // 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)) {
            const ptrdiff_t occupancy = topp - basep;
            {
                const size_t newStackSize = stackSize * 2;
                Node** const newStackp = new Node*[newStackSize];
                std::memcpy(newStackp, basep - PrefetchDistance,
                            sizeof(Node**) * (occupancy + PrefetchDistance));
                stackSize = newStackSize;
                outOfLineStackp.reset(newStackp);
            }
            basep = outOfLineStackp.get() + PrefetchDistance;
            topp = basep + occupancy;
            limp = outOfLineStackp.get() + stackSize - MaxPushesPerIteration;
        }

        // Enqueue the next node
        if (headp->nextp()) *topp++ = headp->nextp();

        // Visit the head node
        visit(headp);
    }
}

// predicate implementation
template <typename T_Arg, bool N_Default, typename T_Callable>
bool AstNode::predicateImpl(ConstCorrectAstNode<T_Arg>* nodep, const T_Callable& p,
                            bool visitNext) {
    // Implementation similar to foreach, but abort traversal as soon as result is determined
    using T_Arg_NonConst = typename std::remove_const<T_Arg>::type;
    using Node = ConstCorrectAstNode<T_Arg>;

    // We prefetch this far into the stack
    constexpr int PrefetchDistance = 2;
    // We push max 5 items per iteration
    constexpr size_t MaxPushesPerIteration = 5;
    // Initial stack sie
    constexpr size_t InitialStackSize = 32;

    // Traversal stack. Because we often iterate over small trees, use an in-line initial stack
    size_t stackSize = InitialStackSize;
    Node* inLineStack[InitialStackSize];  // In-line initial stack storage
    std::unique_ptr<Node*[]> outOfLineStackp{nullptr};  // In case we need more, we will allocate
    Node** basep = inLineStack + PrefetchDistance;  // Pointer to base of stack
    Node** topp = basep;  // Pointer to top of stack
    Node** limp = inLineStack + stackSize - MaxPushesPerIteration;  // Pointer to grow limit

    // 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) {
        // Type test this node
        if (AstNode::privateTypeTest<T_Arg_NonConst>(currp)) {
            // Call the client function
            if (p(static_cast<T_Arg*>(currp)) != N_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;
    };

    // Enqueue the next of the root node, if required
    if (visitNext && nodep->nextp()) *topp++ = nodep->nextp();

    // Visit the root node
    if (visit(nodep)) return !N_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)) {
            const ptrdiff_t occupancy = topp - basep;
            {
                const size_t newStackSize = stackSize * 2;
                Node** const newStackp = new Node*[newStackSize];
                std::memcpy(newStackp, basep - PrefetchDistance,
                            sizeof(Node**) * (occupancy + PrefetchDistance));
                stackSize = newStackSize;
                outOfLineStackp.reset(newStackp);
            }
            basep = outOfLineStackp.get() + PrefetchDistance;
            topp = basep + occupancy;
            limp = outOfLineStackp.get() + stackSize - MaxPushesPerIteration;
        }

        // Enqueue the next node
        if (headp->nextp()) *topp++ = headp->nextp();

        // Visit the head node
        if (visit(headp)) return !N_Default;
    }

    return N_Default;
}

inline std::ostream& operator<<(std::ostream& os, const AstNode* rhs) {
    if (!rhs) {  // LCOV_EXCL_LINE
        os << "nullptr";  // LCOV_EXCL_LINE
    } else if (VN_DELETED(rhs)) {  // LCOV_EXCL_LINE
        os << "%E-0x1/deleted!";  // LCOV_EXCL_LINE
    } else {
        rhs->dump(os);
    }
    return os;
}
void VNRelinker::relink(AstNode* newp) { newp->AstNode::relink(this); }

//######################################################################

// VNRef is std::reference_wrapper that can only hold AstNode subtypes
template <typename T_Node>
class VNRef final : public std::reference_wrapper<T_Node> {
    static_assert(std::is_base_of<AstNode, T_Node>::value,
                  "Type parameter 'T_Node' must be a subtype of AstNode");

public:
    template <typename U>
    // cppcheck-suppress noExplicitConstructor
    VNRef(U&& x)
        : std::reference_wrapper<T_Node>{x} {}
    // cppcheck-suppress noExplicitConstructor
    VNRef(const std::reference_wrapper<T_Node>& other)
        : std::reference_wrapper<T_Node>{other} {}
};

static_assert(sizeof(VNRef<AstNode>) == sizeof(std::reference_wrapper<AstNode>),
              "VNRef should not contain extra members");

// Specializations of std::hash and std::equal_to for VNRef. This in turn
// enables us to use for example std::unordered_set<VNRef<AstNode>> for
// sets using equality (AstNode::sameTree) rather than identity comparisons,
// without having to copy nodes into the collections.

// Forward declaration to avoid including V3Hasher.h which needs V3Ast.h (this file).
size_t V3HasherUncachedHash(const AstNode&);

// Specialization of std::hash for VNRef
template <typename T_Node>
struct std::hash<VNRef<T_Node>> final {
    size_t operator()(VNRef<T_Node> r) const { return V3HasherUncachedHash(r); }
};

// Specialization of std::equal_to for VNRef
template <typename T_Node>
struct std::equal_to<VNRef<T_Node>> final {
    size_t operator()(VNRef<T_Node> ra, VNRef<T_Node> rb) const {
        return ra.get().sameTree(&(rb.get()));
    }
};

//######################################################################
// Inline VNVisitor METHODS

void VNVisitorConst::iterateConst(AstNode* nodep) { nodep->accept(*this); }
void VNVisitorConst::iterateConstNull(AstNode* nodep) {
    if (VL_LIKELY(nodep)) nodep->accept(*this);
}
void VNVisitorConst::iterateChildrenConst(AstNode* nodep) { nodep->iterateChildrenConst(*this); }
void VNVisitorConst::iterateChildrenBackwardsConst(AstNode* nodep) {
    nodep->iterateChildrenBackwardsConst(*this);
}
void VNVisitorConst::iterateAndNextConstNull(AstNode* nodep) {
    if (VL_LIKELY(nodep)) nodep->iterateAndNextConst(*this);
}

void VNVisitor::iterate(AstNode* nodep) { nodep->accept(*this); }
void VNVisitor::iterateNull(AstNode* nodep) {
    if (VL_LIKELY(nodep)) nodep->accept(*this);
}
void VNVisitor::iterateChildren(AstNode* nodep) { nodep->iterateChildren(*this); }
void VNVisitor::iterateAndNextNull(AstNode* nodep) {
    if (VL_LIKELY(nodep)) nodep->iterateAndNext(*this);
}
AstNode* VNVisitor::iterateSubtreeReturnEdits(AstNode* nodep) {
    return nodep->iterateSubtreeReturnEdits(*this);
}

// Include macros generated by 'astgen'. These include ASTGEN_MEMBERS_Ast<Node>
// for each AstNode sub-type, and ASTGEN_SUPER_<Node> for concrete final
// AstNode sub-types. The generated members include boilerplate methods related
// to cloning, visitor dispatch, and other functionality. ASTGEN_SUPER_<Node>
// is the necessary constructor invocation for concrete AstNode sub-types
// that passes the generated type-id numbers all the way back to AstNode.
// For precise details please read the generated macros.
#include "V3Ast__gen_macros.h"

// AstNode subclasses - dependency order matters, so fmt off
// clang-format off
#include "V3AstNodeDType.h"
#include "V3AstNodeOther.h"
#include "V3AstNodeExpr.h"
#include "V3AstNodeStmt.h"
// clang-format on

// Inline function definitions need to go last
#include "V3AstInlines.h"
void dumpNodeListJson(std::ostream& os, const AstNode* nodep, const std::string& listName,
                      const string& indent);

#endif  // Guard
