// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Emit C++ for tree
//
// 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
//
//*************************************************************************

#include "V3PchAstMT.h"

#include "V3EmitC.h"
#include "V3EmitCConstInit.h"
#include "V3File.h"
#include "V3UniqueNames.h"

#include <algorithm>
#include <cstdint>
#include <set>
#include <string>
#include <vector>

VL_DEFINE_DEBUG_FUNCTIONS;

//######################################################################
// Internal EmitC implementation

class EmitCHeader final : public EmitCConstInit {
    V3UniqueNames m_names;
    // METHODS

    class CoverCountVisitor final : public VNVisitorConst {
        int m_bins = 0;

        void visit(AstNodeCoverDecl* nodep) override {
            // Each module class owns the counters for declarations it emits;
            // duplicate no-inline instances need separate arrays for
            // forcePerInstance reporting.
            if (!nodep->dataDeclNullp()) m_bins += nodep->size();
        }
        void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }

    public:
        explicit CoverCountVisitor(const AstNodeModule* modp) {
            iterateConst(const_cast<AstNodeModule*>(modp));
        }
        int bins() const { return m_bins; }
    };

    void decorateFirst(bool& first, const string& str) {
        if (first) {
            putsDecoration(nullptr, str);
            first = false;
        }
    }
    void emitCellDecls(const AstNodeModule* modp) {
        bool first = true;
        for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
            if (const AstCell* const cellp = VN_CAST(nodep, Cell)) {
                decorateFirst(first, "// CELLS\n");
                putns(cellp, EmitCUtil::prefixNameProtect(cellp->modp()) + "* "
                                 + cellp->nameProtect() + ";\n");
            }
        }
    }
    void emitDesignVarDecls(const AstNodeModule* modp) {
        bool first = true;
        std::vector<const AstVar*> varList;
        bool lastAnon = false;  // initial value is not important, but is used

        const auto emitCurrentList = [this, &first, &varList, &lastAnon]() {
            if (varList.empty()) return;

            decorateFirst(first, "\n// DESIGN SPECIFIC STATE\n");

            if (lastAnon) {  // Output as anons
                const int anonMembers = varList.size();
                const int lim = v3Global.opt.compLimitMembers();
                int anonL3s = 1;
                int anonL2s = 1;
                int anonL1s = 1;
                if (anonMembers > (lim * lim * lim)) {
                    anonL3s = (anonMembers + (lim * lim * lim) - 1) / (lim * lim * lim);
                    anonL2s = lim;
                    anonL1s = lim;
                } else if (anonMembers > (lim * lim)) {
                    anonL2s = (anonMembers + (lim * lim) - 1) / (lim * lim);
                    anonL1s = lim;
                } else if (anonMembers > lim) {
                    anonL1s = (anonMembers + lim - 1) / lim;
                }
                if (anonL1s != 1)
                    puts("// Anonymous structures to workaround compiler member-count bugs\n");
                auto it = varList.cbegin();
                for (int l3 = 0; l3 < anonL3s && it != varList.cend(); ++l3) {
                    if (anonL3s != 1) puts("struct {\n");
                    for (int l2 = 0; l2 < anonL2s && it != varList.cend(); ++l2) {
                        if (anonL2s != 1) puts("struct {\n");
                        for (int l1 = 0; l1 < anonL1s && it != varList.cend(); ++l1) {
                            if (anonL1s != 1) puts("struct {\n");
                            for (int l0 = 0; l0 < lim && it != varList.cend(); ++l0) {
                                emitVarDecl(*it);
                                ++it;
                            }
                            if (anonL1s != 1) puts("};\n");
                        }
                        if (anonL2s != 1) puts("};\n");
                    }
                    if (anonL3s != 1) puts("};\n");
                }
                // Leftovers, just in case off by one error somewhere above
                for (; it != varList.cend(); ++it) emitVarDecl(*it);
            } else {  // Output as nonanons
                for (const auto& pair : varList) emitVarDecl(pair);
            }

            varList.clear();
        };

        // Emit variables in consecutive anon and non-anon batches
        for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
            if (const AstVar* const varp = VN_CAST(nodep, Var)) {
                if (varp->isIO() || varp->isSignal() || varp->isClassMember() || varp->isTemp()
                    || varp->isGenVar()) {
                    const bool anon = EmitCUtil::isAnonOk(varp);
                    if (anon != lastAnon) emitCurrentList();
                    lastAnon = anon;
                    varList.emplace_back(varp);
                }
            }
        }

        // Emit final batch
        emitCurrentList();
    }
    void emitInternalVarDecls(const AstNodeModule* modp) {
        if (const AstClass* const classp = VN_CAST(modp, Class)) {
            if (classp->needRNG()) {
                putsDecoration(nullptr, "\n// INTERNAL VARIABLES\n");
                puts("VlRNG __Vm_rng;\n");
            }
        } else {  // not class
            putsDecoration(nullptr, "\n// INTERNAL VARIABLES\n");
            puts(EmitCUtil::symClassName() + "* vlSymsp;\n");
            puts("const char* vlNamep;\n");
            // Allocate object-local coverage counters only on modules that
            // contain emitted coverage declarations.
            const int coverBins = CoverCountVisitor{modp}.bins();
            if (coverBins) {
                puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
                puts(" __Vcoverage[");
                puts(std::to_string(coverBins));
                puts("]{};\n");
            }
        }
    }
    void emitParamDecls(const AstNodeModule* modp) {
        bool first = true;
        for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
            if (const AstVar* const varp = VN_CAST(nodep, Var)) {
                if (varp->isParam()) {
                    decorateFirst(first, "\n// PARAMETERS\n");
                    UASSERT_OBJ(varp->valuep(), nodep, "No init for a param?");
                    // Only C++ LiteralTypes can be constexpr
                    const bool canBeConstexpr = varp->dtypep()->isLiteralType();
                    putns(varp, "static ");
                    puts(canBeConstexpr ? "constexpr " : "const ");
                    puts(varp->dtypep()->cType(varp->nameProtect(), false, false));
                    if (canBeConstexpr) {
                        puts(" = ");
                        iterateConst(varp->valuep());
                    }
                    puts(";\n");
                }
            }
        }
    }
    void emitCtorDtorDecls(const AstNodeModule* modp) {
        // Classes use CFuncs with isConstructor/isDestructor
        if (VN_IS(modp, Class)) return;

        // The root module needs a proper constuctor/destructor, everything
        // else uses a 'ctor'/'dtor' function in order to be able to split up
        // construction/destruction code
        const std::string name = EmitCUtil::prefixNameProtect(modp);
        putsDecoration(nullptr, "\n// CONSTRUCTORS\n");
        const std::string ctorArgs = EmitCUtil::symClassName() + "* symsp, const char* namep";
        if (modp->isTop()) {
            putns(modp, name + "(" + ctorArgs + ");\n");
            putns(modp, "~" + name + "();\n");
        } else {
            putns(modp, name + "();\n");
            putns(modp, "~" + name + "();\n");
            putns(modp, "void ctor(" + ctorArgs + ");\n");
            putns(modp, "void dtor();\n");
        }
        putns(modp, "VL_UNCOPYABLE(" + name + ");\n");
    }
    void emitInternalMethodDecls(const AstNodeModule* modp) {
        bool first = true;
        const string section = "\n// INTERNAL METHODS\n";

        if (!VN_IS(modp, Class)) {
            decorateFirst(first, section);
            puts("void " + protect("__Vconfigure") + "(bool first);\n");
        } else {
            decorateFirst(first, section);
            const std::string name = V3OutFormatter::quoteNameControls(
                VIdProtect::protectWordsIf(modp->prettyName(), v3Global.opt.protectIds()));
            // "override", but don't want clang to start checking them:
            puts("virtual const char* typeName() const { return \""s + name + "\"; }\n");
        }

        if (v3Global.opt.coverage() && !VN_IS(modp, Class)) {
            decorateFirst(first, section);
            puts("void __vlCoverInsert(");
            puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
            puts("* countp, bool enable, bool localCounter, const char* filenamep, int lineno, "
                 "int column,\n");
            puts("const char* hierp, const char* pagep, const char* commentp, const char* "
                 "linescovp,\n");
            puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* "
                 "fsmTagp);\n");
        }

        if (v3Global.opt.coverageToggle() && !VN_IS(modp, Class)) {
            decorateFirst(first, section);
            puts("void __vlCoverToggleInsert(int begin, int end, bool ranged, ");
            puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
            puts("* countp, bool enable, bool localCounter, const char* filenamep, int lineno, "
                 "int column,\n");
            puts("const char* hierp, const char* pagep, const char* commentp);\n");
        }

        if (v3Global.opt.savable()) {
            decorateFirst(first, section);
            puts("void " + protect("__Vserialize") + "(VerilatedSerialize& os);\n");
            puts("void " + protect("__Vdeserialize") + "(VerilatedDeserialize& os);\n");
        }

        // Polymorphic clone for concrete (non-abstract, non-interface) classes
        if (const AstClass* const classp = VN_CAST(modp, Class)) {
            if (!classp->isInterfaceClass() && !classp->isVirtual()) {
                decorateFirst(first, section);
                putns(classp, "VlClass* clone() const { return new "
                                  + EmitCUtil::prefixNameProtect(classp) + "(*this); }\n");
            }
        }
    }
    void emitEnums(const AstNodeModule* modp) {
        bool first = true;
        for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
            const AstTypedef* const tdefp = VN_CAST(nodep, Typedef);
            if (!tdefp) continue;
            if (!tdefp->attrPublic()) continue;
            const AstEnumDType* const edtypep
                = VN_CAST(tdefp->dtypep()->skipRefToEnump(), EnumDType);
            if (!edtypep) continue;
            decorateFirst(first, "\n// ENUMS (that were declared public)\n");
            if (edtypep->width() > 64) {
                putsDecoration(tdefp,
                               "// enum " + tdefp->nameProtect() + " ignored: Too wide for C++\n");
            } else {
                putns(tdefp, "enum " + tdefp->name() + " {\n");
                for (const AstEnumItem* itemp = edtypep->itemsp(); itemp;
                     itemp = VN_AS(itemp->nextp(), EnumItem)) {
                    if (const AstConst* const constp = VN_CAST(itemp->valuep(), Const)) {
                        if (constp->num().isFourState()) {
                            putns(itemp, "// " + itemp->nameProtect() + " is four-state\n");
                            continue;
                        }
                    }
                    putns(itemp, itemp->nameProtect());
                    puts(" = ");
                    iterateConst(itemp->valuep());
                    if (VN_IS(itemp->nextp(), EnumItem)) puts(",");
                    puts("\n");
                }
                puts("};\n");
            }
        }
    }
    void emitStructDecl(const AstNodeModule* modp, AstNodeUOrStructDType* sdtypep,
                        std::set<AstNodeUOrStructDType*>& emitted) {
        if (emitted.count(sdtypep) > 0) return;
        emitted.insert(sdtypep);
        for (AstMemberDType* itemp = sdtypep->membersp(); itemp;
             itemp = VN_AS(itemp->nextp(), MemberDType)) {
            AstNodeUOrStructDType* const subp = itemp->getChildStructp();
            if (subp && (!subp->packed() || sdtypep->packed())) {
                // Recurse if it belongs to the current module
                if (subp->classOrPackagep() == modp) {
                    emitStructDecl(modp, subp, emitted);
                    puts("\n");
                }
            }
        }
        if (sdtypep->packed()) {
            emitPackedUOrSBody(sdtypep);
        } else {
            emitUnpackedUOrSBody(sdtypep);
        }
    }
    enum class AttributeType : uint8_t { WIDTH, DIMENSION };
    // Get member attribute based on type
    int getNodeAttribute(const AstMemberDType* itemp, AttributeType type) {
        const bool isArrayType = itemp->dtypep()->isNonPackedArray();
        switch (type) {
        case AttributeType::WIDTH: {
            if (isArrayType) {
                // For arrays, get innermost element width
                const AstNodeDType* dtype = itemp->dtypep();
                while (dtype->subDTypep()) dtype = dtype->subDTypep();
                return dtype->width();
            }
            return itemp->width();
        }
        case AttributeType::DIMENSION: {
            // Return array dimension or 0 for non-arrays
            return isArrayType ? itemp->dtypep()->dimensions(true).second : 0;
        }
        default: {
            return 0;
        }
        }
    }
    template <AttributeType T>
    void emitMemberVector(const AstNodeUOrStructDType* sdtypep) {
        puts("return {");
        bool needComma = false;
        for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
             itemp = VN_AS(itemp->nextp(), MemberDType)) {
            if (!itemp->isConstrainedRand()) continue;
            // Comma handling: add before element except first
            if (needComma) puts(",\n");
            putns(itemp, std::to_string(getNodeAttribute(itemp, T)));
            needComma = true;
        }
        puts("};\n}\n");
    }
    void emitUnpackedUOrSBody(AstNodeUOrStructDType* sdtypep) {
        putns(sdtypep, sdtypep->verilogKwd());  // "struct"/"union"
        puts(" " + EmitCUtil::prefixNameProtect(sdtypep) + " {\n");
        for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
             itemp = VN_AS(itemp->nextp(), MemberDType)) {
            putns(itemp, itemp->dtypep()->cType(itemp->nameProtect(), false, false));
            puts(";\n");
        }

        // Three helper functions for struct constrained randomization:
        // - memberNames: Get member names
        // - getMembers: Access member references
        // - memberIndices: Retrieve member indices
        // - memberWidth: Retrieve member width
        // - memberDimension: Retrieve member dimension
        if (sdtypep->isConstrainedRand()) {
            bool needComma = false;
            putns(sdtypep, "\nstd::vector<std::string> memberNames(void) const {\n");
            puts("return {");
            for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
                 itemp = VN_AS(itemp->nextp(), MemberDType)) {
                if (!itemp->isConstrainedRand()) continue;
                if (needComma) puts(",\n");
                putns(itemp, "\"" + itemp->shortName() + "\"");
                needComma = true;
            }
            puts("};\n}\n");

            putns(sdtypep, "\nstd::vector<int> memberWidth(void) const {\n");
            emitMemberVector<AttributeType::WIDTH>(sdtypep);

            putns(sdtypep, "\nstd::vector<int> memberDimension(void) const {\n");
            emitMemberVector<AttributeType::DIMENSION>(sdtypep);

            needComma = false;
            putns(sdtypep, "\nauto memberIndices(void) const {\n");
            puts("return std::index_sequence_for<");
            for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
                 itemp = VN_AS(itemp->nextp(), MemberDType)) {
                if (!itemp->isConstrainedRand()) continue;
                if (needComma) puts(",\n");
                putns(itemp, itemp->dtypep()->cType("", false, false));
                needComma = true;
            }
            puts(">{};\n}\n");

            needComma = false;
            putns(sdtypep, "\ntemplate <typename T>");
            putns(sdtypep, "\nauto getMembers(T& obj) {\n");
            puts("return std::tie(");
            for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
                 itemp = VN_AS(itemp->nextp(), MemberDType)) {
                if (!itemp->isConstrainedRand()) continue;
                if (needComma) puts(",\n");
                putns(itemp, "obj." + itemp->nameProtect());
                needComma = true;
            }
            puts(");\n}\n");
        }
        putns(sdtypep, "\nbool operator==(const " + EmitCUtil::prefixNameProtect(sdtypep)
                           + "& rhs) const {\n");
        puts("return ");
        for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
             itemp = VN_AS(itemp->nextp(), MemberDType)) {
            if (itemp != sdtypep->membersp()) puts("\n    && ");
            putns(itemp, itemp->nameProtect() + " == " + "rhs." + itemp->nameProtect());
        }
        puts(";\n");
        puts("}\n");
        putns(sdtypep, "bool operator!=(const " + EmitCUtil::prefixNameProtect(sdtypep)
                           + "& rhs) const {\n");
        puts("return !(*this == rhs);\n}\n");
        putns(sdtypep, "\nbool operator<(const " + EmitCUtil::prefixNameProtect(sdtypep)
                           + "& rhs) const {\n");
        for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
             itemp = VN_AS(itemp->nextp(), MemberDType)) {
            putns(itemp, "if (" + itemp->nameProtect() + " < rhs." + itemp->nameProtect()
                             + ") return true;\n");
            putns(itemp, "if (rhs." + itemp->nameProtect() + " < " + itemp->nameProtect()
                             + ") return false;\n");
        }
        puts("return false;\n");
        puts("}\n");
        puts("};\n");
        puts("template <>\n");
        putns(sdtypep, "struct VlIsCustomStruct<" + EmitCUtil::prefixNameProtect(sdtypep)
                           + "> : public std::true_type {};\n");
    }

    // getfunc: VL_ASSIGNSEL_XX(rbits, obits, off, lhsdata, rhsdata);
    // !getfunc: VL_SELASSIGN_XX(rbits, obits, lhsdata, rhsdata, off);
    void emitVlAssign(const AstNodeDType* const lhstype, const AstNodeDType* rhstype,
                      const std::string& off, const std::string& lhsdata,
                      const std::string& rhsdata, bool getfunc) {
        puts(getfunc ? "VL_ASSIGNSEL_" : "VL_SELASSIGN_");
        puts(lhstype->charIQWN());
        puts(rhstype->charIQWN());
        puts("(" + std::to_string(lhstype->width()) + ", ");  // LHS width
        if (getfunc) {
            puts(std::to_string(rhstype->width()) + ", ");  // Number of copy bits
            puts(off + ", ");  // LHS offset
        } else {
            // Number of copy bits. Use widthTototalBytes to
            // make VL_SELASSIGN_XX clear upper unused bits for us.
            // puts(std::to_string(lhstype->width()) + ", ");
            puts(std::to_string(lhstype->widthTotalBytes() * 8) + ", ");
        }
        puts(lhsdata + ", ");  // LHS data
        puts(rhsdata);  // RHS data
        if (!getfunc) {
            puts(", " + off);  // RHS offset
        }
        puts(");\n");
    }

    // `retOrArg` should be prefixed by `&` or suffixed by `.data()` depending on its type
    void emitPackedMember(const AstNodeDType* parentDtypep, const AstNodeDType* dtypep,
                          const std::string& fieldname, const std::string& offset, bool getfunc,
                          const std::string& retOrArg) {
        dtypep = dtypep->skipRefp();
        if (const auto* adtypep = VN_CAST(dtypep, PackArrayDType)) {
            const std::string index = m_names.get("__Vi");
            puts("for (int " + index + " = 0; " + index + " < "
                 + std::to_string(adtypep->elementsConst()) + "; ++" + index + ") {\n");

            const std::string offsetInLoop
                = offset + " + " + index + " * " + std::to_string(adtypep->subDTypep()->width());
            const std::string newName = fieldname + "[" + index + "]";
            emitPackedMember(parentDtypep, adtypep->subDTypep(), newName, offsetInLoop, getfunc,
                             retOrArg);
            puts("}\n");
        } else if (VN_IS(dtypep, NodeUOrStructDType)) {
            const std::string tmp = m_names.get("__Vtmp");
            if (getfunc) {  // Emit `get` func;
                // auto __tmp = field.get();
                puts("auto " + tmp + " = " + fieldname + ".get();\n");
                // VL_ASSIGNSEL_XX(rbits, obits, lsb, lhsdata, rhsdata);
                emitVlAssign(parentDtypep, dtypep, offset, retOrArg, tmp, getfunc);
            } else {  // Emit `set` func
                const std::string tmptype = AstCDType::typeToHold(dtypep->width());
                // type tmp;
                puts(tmptype + " " + tmp + ";\n");
                // VL_SELASSIGN_XX(rbits, obits, lhsdata, rhsdata, roffset);
                emitVlAssign(dtypep, parentDtypep, offset, tmp, retOrArg, getfunc);
                // field.set(__tmp);
                puts(fieldname + ".set(" + tmp + ");\n");
            }
        } else {
            UASSERT_OBJ(VN_IS(dtypep, EnumDType) || VN_IS(dtypep, BasicDType), dtypep,
                        "Unsupported type in packed struct or union");
            if (getfunc) {  // Emit `get` func;
                // VL_ASSIGNSEL_XX(rbits, obits, lsb, lhsdata, rhsdata);
                emitVlAssign(parentDtypep, dtypep, offset, retOrArg, fieldname, getfunc);
            } else {  // Emit `set` func
                // VL_SELASSIGN_XX(rbits, obits, lhsdata, rhsdata, roffset);
                emitVlAssign(dtypep, parentDtypep, offset, fieldname, retOrArg, getfunc);
            }
        }
    }
    void emitPackedUOrSBody(AstNodeUOrStructDType* sdtypep) {
        putns(sdtypep, sdtypep->verilogKwd());  // "struct"/"union"
        puts(" " + EmitCUtil::prefixNameProtect(sdtypep) + " {\n");

        AstMemberDType* itemp;
        AstMemberDType* lastItemp;
        const AstMemberDType* witemp = nullptr;
        // LSB is first field in C, so loop backwards
        for (lastItemp = sdtypep->membersp(); lastItemp && lastItemp->nextp();
             lastItemp = VN_AS(lastItemp->nextp(), MemberDType)) {
            if (lastItemp->width() == sdtypep->width()) witemp = lastItemp;
        }
        for (itemp = lastItemp; itemp; itemp = VN_CAST(itemp->backp(), MemberDType)) {
            putns(itemp, itemp->dtypep()->cType(itemp->nameProtect(), false, false, true));
            puts(";\n");
        }

        const std::string retArgName = m_names.get("__v");
        const std::string retArgType = AstCDType::typeToHold(sdtypep->width());

        // Emit `get` member function
        puts(retArgType + " get() const {\n");
        puts(retArgType + " " + retArgName + ";\n");
        if (VN_IS(sdtypep, StructDType)) {
            for (itemp = lastItemp; itemp; itemp = VN_CAST(itemp->backp(), MemberDType)) {
                emitPackedMember(sdtypep, itemp->dtypep(), itemp->nameProtect(),
                                 std::to_string(itemp->lsb()), /*getfunc=*/true, retArgName);
            }
        } else {
            // We only need to fill the widest field of union
            emitPackedMember(sdtypep, witemp->dtypep(), witemp->nameProtect(),
                             std::to_string(witemp->lsb()), /*getfunc=*/true, retArgName);
        }
        puts("return " + retArgName + ";\n");
        puts("}\n");

        // Emit `set` member function
        puts("void set(const " + retArgType + "& " + retArgName + ") {\n");
        if (VN_IS(sdtypep, StructDType)) {
            for (itemp = lastItemp; itemp; itemp = VN_CAST(itemp->backp(), MemberDType)) {
                emitPackedMember(sdtypep, itemp->dtypep(), itemp->nameProtect(),
                                 std::to_string(itemp->lsb()), /*getfunc=*/false, retArgName);
            }
        } else {
            // We only need to fill the widest field of union
            emitPackedMember(sdtypep, witemp->dtypep(), witemp->nameProtect(),
                             std::to_string(witemp->lsb()), /*getfunc=*/false, retArgName);
        }

        puts("}\n");

        puts("};\n");
        m_names.reset();
    }
    void emitStructs(const AstNodeModule* modp) {
        // Track structs that've been emitted already
        std::set<AstNodeUOrStructDType*> emitted;
        for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
            const AstTypedef* const tdefp = VN_CAST(nodep, Typedef);
            if (!tdefp) continue;
            AstNodeUOrStructDType* const sdtypep
                = VN_CAST(tdefp->dtypep()->skipRefToEnump(), NodeUOrStructDType);
            if (!sdtypep) continue;
            emitStructDecl(modp, sdtypep, emitted);
        }
    }
    void emitFuncDecls(const AstNodeModule* modp, bool inClassBody) {
        std::vector<const AstCFunc*> funcsp;

        for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
            if (const AstCFunc* const funcp = VN_CAST(nodep, CFunc)) {
                if (funcp->dpiImportPrototype())  // Declared in __Dpi.h
                    continue;
                if (funcp->dpiExportDispatcher())  // Declared in __Dpi.h
                    continue;
                if (funcp->isMethod() != inClassBody)  // Only methods go inside class
                    continue;
                if (funcp->isMethod() && funcp->isLoose())  // Loose methods are declared lazily
                    continue;
                funcsp.push_back(funcp);
            }
        }

        std::stable_sort(funcsp.begin(), funcsp.end(), [](const AstNode* ap, const AstNode* bp) {
            return ap->name() < bp->name();
        });

        for (const AstCFunc* const funcp : funcsp) {
            if (inClassBody) ofp()->putsPrivate(funcp->declPrivate());
            emitCFuncDecl(funcp, modp);
        }
    }
    void emitAll(const AstNodeModule* modp) {
        // Include files required by this AstNodeModule
        if (const AstClass* const classp = VN_CAST(modp, Class)) {
            for (const AstClassExtends* extp = classp->extendsp(); extp;
                 extp = VN_AS(extp->nextp(), ClassExtends)) {
                putns(extp, "#include \""
                                + EmitCUtil::prefixNameProtect(extp->classp()->classOrPackagep())
                                + ".h\"\n");
            }
        }

        // Forward declarations required by this AstNodeModule
        puts("\nclass " + EmitCUtil::symClassName() + ";\n");

        // From `systemc_header
        emitSystemCSection(modp, VSystemCSectionType::HDR);

        emitStructs(modp);

        // Open class body {{{
        puts("\n");
        putns(modp, "class ");
        if (!VN_IS(modp, Class)) puts("alignas(VL_CACHE_LINE_BYTES) ");
        puts(EmitCUtil::prefixNameProtect(modp));
        if (const AstClass* const classp = VN_CAST(modp, Class)) {
            puts(" : ");
            if (classp->extendsp()) {
                bool needComma = false;
                for (const AstClassExtends* extp = classp->extendsp(); extp;
                     extp = VN_AS(extp->nextp(), ClassExtends)) {
                    if (needComma) puts(", ");
                    // Use virtual only for interfaces for class inheritance
                    // (extends)
                    puts(extp->classp()->useVirtualPublic() ? "virtual public " : "public ");
                    putns(extp, EmitCUtil::prefixNameProtect(extp->classp()));
                    needComma = true;
                }
            } else {
                puts("public virtual VlClass");
            }
        } else {
            puts(" final");
        }
        puts(" {\n");
        ofp()->resetPrivate();
        ofp()->putsPrivate(false);  // public:

        // Emit all class body contents
        emitCellDecls(modp);
        emitEnums(modp);
        emitDesignVarDecls(modp);
        emitInternalVarDecls(modp);
        emitParamDecls(modp);
        emitCtorDtorDecls(modp);
        emitInternalMethodDecls(modp);
        emitFuncDecls(modp, /* inClassBody: */ true);

        // From `systemc_interface
        emitSystemCSection(modp, VSystemCSectionType::INT);

        // Close class body
        puts("};\n");
        // }}}

        // Emit out of class function declarations
        puts("\n");
        emitFuncDecls(modp, /* inClassBody: */ false);
        emitSystemCSection(modp, VSystemCSectionType::HDR_POST);
    }

    explicit EmitCHeader(const AstNodeModule* modp) {
        UINFO(5, "  Emitting header for " << EmitCUtil::prefixNameProtect(modp));

        // Open output file
        openNewOutputHeaderFile(EmitCUtil::prefixNameProtect(modp), "Design internal header");
        puts("// See " + EmitCUtil::topClassName() + ".h for the primary calling header\n");

        ofp()->putsGuard();

        // Include files
        puts("\n");
        ofp()->putsIntTopInclude();
        puts("#include \"verilated.h\"\n");
        if (modp->isTop() && optSystemC()) puts("#include \"verilated_sc.h\"\n");
        if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n");
        if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n");
        if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
        if (v3Global.opt.coverage() || v3Global.useCovergroup())
            puts("#include \"verilated_covergroup.h\"\n");
        if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n");
        if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n");
        if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n");

        std::set<string> cuse_set;
        auto add_to_cuse_set = [&](string s) { cuse_set.insert(s); };

        forModCUse(modp, VUseType::INT_FWD_CLASS | VUseType::INT_INCLUDE, add_to_cuse_set);
        if (const AstClassPackage* const packagep = VN_CAST(modp, ClassPackage)) {
            forModCUse(packagep->classp(), VUseType::INT_INCLUDE | VUseType::INT_FWD_CLASS,
                       add_to_cuse_set);
        }

        for (const string& s : cuse_set) puts(s);
        puts("\n");

        emitAll(modp);

        if (const AstClassPackage* const packagep = VN_CAST(modp, ClassPackage)) {
            // Put the non-static class implementation in same h file for speed
            emitAll(packagep->classp());
        }

        ofp()->putsEndGuard();

        // Close output file
        closeOutputFile();
    }
    ~EmitCHeader() override = default;

public:
    static void main(const AstNodeModule* modp) { const EmitCHeader emitCHeader{modp}; }
};

//######################################################################
// EmitC class functions

void V3EmitC::emitcHeaders() {
    UINFO(2, __FUNCTION__ << ":");

    // Process each module in turn
    for (const AstNode* nodep = v3Global.rootp()->modulesp(); nodep; nodep = nodep->nextp()) {
        if (VN_IS(nodep, Class)) continue;  // Declared with the ClassPackage
        EmitCHeader::main(VN_AS(nodep, NodeModule));
    }
}
