// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Break case statements up and add Unknown assigns
//
// 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
//
//*************************************************************************
// V3Case's Transformations:
//
// Each module:
//      TBD: Eliminate tristates by adding __in, __inen, __en wires in parallel
//          Need __en in changed list if a signal is on the LHS of a assign
//      Cases:
//          CASE(v) CASEITEM(items,body) ->  IF (OR (EQ (AND v mask)
//                                                      (AND item1 mask))
//                                                  (other items))
//                                              body
//              Or, converts to a if/else tree.
//      FUTURES:
//          Large 16+ bit tables with constants and no masking (address muxes)
//              Enter all into std::multimap, sort by value and use a tree of < and == compares.
//          "Diagonal" find of {rightmost,leftmost} bit {set,clear}
//              Ignoring mask, check each value is unique (using std::multimap as above?)
//              Each branch is then mask-and-compare operation (IE
//              <000000001_000000000 at midpoint.)
//
//*************************************************************************

#include "V3PchAstNoMT.h"  // VL_MT_DISABLED_CODE_UNIT

#include "V3Case.h"

#include "V3Stats.h"

VL_DEFINE_DEBUG_FUNCTIONS;

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

class CaseLintVisitor final : public VNVisitorConst {
    // Under a CASE value node, if so the relevant case statement
    const AstNode* m_casep = nullptr;

    // METHODS
    template <typename CaseItem>
    static void detectMultipleDefaults(CaseItem* itemsp) {
        bool hitDefault = false;
        for (CaseItem* itemp = itemsp; itemp; itemp = AstNode::as<CaseItem>(itemp->nextp())) {
            if (!itemp->isDefault()) continue;
            if (hitDefault) itemp->v3error("Multiple default statements in case statement.");
            hitDefault = true;
        }
    }

    template <typename CaseItem>
    void checkXZinNonCaseX(AstNode* casep, AstNodeExpr* exprp, CaseItem* itemsp) {
        VL_RESTORER(m_casep);
        m_casep = casep;
        iterateConst(exprp);
        for (CaseItem* itemp = itemsp; itemp; itemp = AstNode::as<CaseItem>(itemp->nextp())) {
            iterateAndNextConstNull(itemp->condsp());
        }
    }

    // VISITORS
    void visit(AstGenCase* nodep) override {
        // Detect multiple defaults
        detectMultipleDefaults(nodep->itemsp());
        // Check for X/Z in non-casex statements
        checkXZinNonCaseX(nodep, nodep->exprp(), nodep->itemsp());
    }

    void visit(AstCase* nodep) override {
        if (nodep->casex()) {
            nodep->v3warn(CASEX, "Suggest casez (with ?'s) in place of casex (with X's)");
        }
        // Detect multiple defaults
        detectMultipleDefaults(nodep->itemsp());
        // Check for X/Z in non-casex statements
        checkXZinNonCaseX(nodep, nodep->exprp(), nodep->itemsp());
    }
    void visit(AstConst* nodep) override {
        if (!nodep->num().isFourState()) return;

        // Error if generate case
        if (VN_IS(m_casep, GenCase)) {
            nodep->v3error("Use of x/? constant in generate case statement, "
                           "(no such thing as 'generate casez')");
            return;
        }

        // Otherwise must be a case statement
        const AstCase* const casep = VN_AS(m_casep, Case);

        // Don't sweat it, we already complained about casex in general
        if (casep->casex()) return;

        if (casep->casez() || casep->caseInside()) {
            if (nodep->num().isAnyX()) {
                nodep->v3warn(CASEWITHX, "Use of x constant in casez statement, "
                                         "(perhaps intended ?/z in constant)");
            }
            return;
        }

        nodep->v3warn(CASEWITHX, "Use of x/? constant in case statement, "
                                 "(perhaps intended casex/casez)");
    }
    void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }

    // CONSTRUCTORS
    explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); }
    explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); }
    ~CaseLintVisitor() override = default;

public:
    static void apply(AstCase* nodep) { CaseLintVisitor{nodep}; }
    static void apply(AstGenCase* nodep) { CaseLintVisitor{nodep}; }
};

//######################################################################
// Case state, as a visitor of each AstNode

class CaseVisitor final : public VNVisitor {
    // Maximum width for detailed analysis
    constexpr static int CASE_DETAILS_MAX_WIDTH = 16;
    // Levels of priority to be ORed together in top IF tree
    constexpr static int CASE_ENCODER_GROUP_DEPTH = 8;
    // Maximum size for tiny lookup tables - materialized in code
    constexpr static size_t CASE_TABLE_TINY_BITS = 32;  // Up to 2 instructions to materialize
    // Maximum size for normal lookup tables - stored in constant pool
    constexpr static size_t CASE_TABLE_MAX_BITS = 1ULL << 16;  // 64Kbits / 8KBytes
    // Minimum number of the branches a table must replace to be worth a load
    constexpr static size_t CASE_TABLE_MIN_BRANCHES = 3;

    // TYPES
    // Record for each case value
    struct CaseRecord final {
        AstCaseItem* itemp;  // Case item that covers value
        AstConst* constp;  // Expression within 'itemp' that covers value (nullptr for default)
        AstNode* stmtsp;  // Statements of 'itemp' (might be nullptr if case is empty)
    };

    // Record for each LHS of a decoder pattern
    struct LhsRecord final {
        AstNodeExpr* lhsp = nullptr;  // LHS of the assignment
        AstNodeAssign* preDefaultp = nullptr;  // Default assignment *before the case statement*
        size_t nCaseAssigns = 0;  // Number of AstAssigns to this LHS in case clauses
        size_t nCaseAssignDlys = 0;  // Number of AstAssignDlys to this LHS in case clauses
        size_t offset = 0;  // Offset in the table for this LHS

        static size_t s_nextId;  // Static unique Id counter
        size_t id = ++s_nextId;  // Unique Id for sorting
    };

    // NODE STATE:
    // AstVarScope::user1() -> bool: true if written to, only in parts of analysis phase

    // STATE
    // Statistics tracking, as a struct so can be passed to 'const' methods
    struct Stats final {
        VDouble0 caseDecoder;  // Cases using decoder method
        VDouble0 caseTableNormal;  // Cases using table method with normal table
        VDouble0 caseTableTiny;  // Cases using table method with tiny table
        VDouble0 caseFast;  // Cases using fast bit tree method
        VDouble0 caseGeneric;  // Cases using generic if/else tree method
        VDouble0 provenAssertions;  // Assertions proven to hold
    } m_stats;
    const AstNode* m_alwaysp = nullptr;  // Always in which case is located
    size_t m_nTmps = 0;  // Sequence numbers for temporary variables
    AstScope* m_scopep = nullptr;  // Current scope

    // STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise
    bool m_caseOpaque = false;  // Case statement is opaque (non-packed, or non-const conditions)
    bool m_caseHasDefault = false;  // Indicates the case statement has a default case
    size_t m_caseNCaseItems = 0;  // Number of AstCaseItems in the case statement
    size_t m_caseNConditions = 0;  // Number of conditions in the case statement
    // Map from LHSs of decoder pattern to corresponding LhsRecord.
    std::unordered_map<VNRef<AstNodeExpr>, LhsRecord> m_caseLhsRecords;
    // Values of 'm_caseLhsRecords' in sorted order, if case statement is a decoder pattern
    std::vector<LhsRecord> m_caseDecoderRecords;
    size_t m_caseDecoderEntryWidth = 0;  // Width of each entry in the decoder table
    size_t m_caseTableWidth = 0;  // Total width of the case table - 0 means can't optimize
    bool m_caseDetailsValid = false;  // Indicates m_caseDetails is valid
    struct final {
        bool exhaustive = false;  // Proven exhaustive
        bool exhaustiveOverEnumOnly = false;  // Exhaustive over enum values only
        bool noOverlaps = false;  // Proven no overlaps between cases
        // Map from value (index) to the CaseRecord that covers this value
        std::array<CaseRecord, 1U << CASE_DETAILS_MAX_WIDTH> records;
    } m_caseDetails;

    // METHODS

    // Xs in case or casez are impossible due to two state simulations.
    // Returns true if the item is never possible
    static bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) {
        const AstConst* const constp = VN_CAST(itemExprp, Const);
        if (!constp) return false;
        if (casep->casex() || casep->caseInside()) return false;
        if (casep->casez()) return constp->num().isAnyX();
        return constp->num().isFourState();
    }

    // Returns the matching mask and bits for a case item constant condition
    // TODO: with 'neverItem' checked, this is alway the same for all types of cases
    //       4-state will have to fix this up
    static std::pair<V3Number, V3Number> matchPattern(const AstCase* /*casep*/,
                                                      const AstConst* condp) {
        // As one to encourage NRVO/move
        std::pair<V3Number, V3Number> pairMaskBits{
            std::piecewise_construct,  //
            std::forward_as_tuple(condp->fileline(), condp->width(), 0),
            std::forward_as_tuple(condp->fileline(), condp->width(), 0)};
        pairMaskBits.first.opBitsNonXZ(condp->num());
        pairMaskBits.second.opBitsOne(condp->num());
        return pairMaskBits;
    }

    // If the given statement is an assignment that fits the decoder pattern,
    // return it, otherwise return nullptr
    static AstNodeAssign* checkDecoderAssign(AstNode* stmtp) {
        // Only Assign and AssignDly are supported
        if (!VN_IS(stmtp, Assign) && !VN_IS(stmtp, AssignDly)) return nullptr;
        AstNodeAssign* const assp = VN_AS(stmtp, NodeAssign);
        // Only if no timing control
        if (assp->timingControlp()) return nullptr;
        // Only if assigning a constant
        if (!VN_IS(assp->rhsp(), Const)) return nullptr;
        // Only if it's a packed value
        AstNodeDType* const dtypep = assp->rhsp()->dtypep();
        if (dtypep->isString() || dtypep->isDouble()) return nullptr;
        // Only if the LHS has no reads (can be relaxed, but need to prove there is no r/w hazard)
        if (assp->lhsp()->exists([](AstVarRef* refp) { return refp->access().isReadOrRW(); })) {
            return nullptr;
        }
        // This is an assignment that fits the decoder pattern
        return assp;
    }

    // Analyze if the given case item fits the decoder pattern, return true iff so.
    // Updates 'm_caseLhsRecords'.
    bool analyzeDecoderCaseItem(AstCaseItem* cip) {
        // AstVarScope::user1() -> bool: true if written to
        const VNUser1InUse user1InUse;
        for (AstNode* stmtp = cip->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
            // Must be an assignment that fits the decoder pattern
            AstNodeAssign* const assp = checkDecoderAssign(stmtp);
            if (!assp) return false;
            // Must assign each LHS exactly once - RHS is Const
            const bool multipleAssignments = assp->lhsp()->exists([](AstVarRef* refp) {  //
                return refp->varScopep()->user1SetOnce();
            });
            if (multipleAssignments) return false;
            // Update LhsRecord
            LhsRecord& lhsRecord = m_caseLhsRecords[*assp->lhsp()];
            if (!lhsRecord.lhsp) lhsRecord.lhsp = assp->lhsp();
            lhsRecord.nCaseAssigns += VN_IS(assp, Assign);
            lhsRecord.nCaseAssignDlys += VN_IS(assp, AssignDly);
        }
        return true;
    }

    // Determine whether we should check case items are complete
    // Returns enum's dtype if should check, nullptr if shouldn't
    static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) {
        if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr;
        const AstEnumDType* const enumDtp
            = VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType);
        if (!enumDtp) return nullptr;  // Case isn't enum
        const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp();
        if (!basicp) return nullptr;  // Not simple type (perhaps IEEE illegal)
        return enumDtp;
    }

    // Check and warn if case items are not complete over the given enum type.
    // Returns true iff the case items cover all enum values/patterns.
    bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) {
        const uint32_t numCases = 1UL << nodep->exprp()->width();
        bool fullyCovered = true;
        string missingItems;
        for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) {
            const auto& match = matchPattern(nodep, VN_AS(eip->valuep(), Const));
            const uint32_t mask = match.first.toUInt();
            const uint32_t bits = match.second.toUInt();

            // Check all cases to see if they cover this enum value/pattern
            for (uint32_t i = 0; i < numCases; ++i) {
                if ((i & mask) != bits) continue;  // This case is not for this enum value
                if (m_caseDetails.records[i].itemp) continue;  // Covered case
                if (!missingItems.empty()) missingItems += ", ";
                missingItems += eip->prettyNameQ();
                fullyCovered = false;
                break;
            }
        }
        if (!missingItems.empty() && !nodep->unique0Pragma()) {
            nodep->v3warn(CASEINCOMPLETE, "Enum item(s) not covered by case: " << missingItems);
        }
        return fullyCovered;  // enum is fully covered
    }

    // Check and warn if case items are not complete over all possible values.
    // Returns true iff the case items cover all values of the case expression.
    bool checkExhaustivePacked(AstCase* nodep) {
        const uint32_t numCases = 1UL << nodep->exprp()->width();
        for (uint32_t i = 0; i < numCases; ++i) {
            if (m_caseDetails.records[i].itemp) continue;  // Covered case
            if (!nodep->unique0Pragma()) {
                nodep->v3warn(CASEINCOMPLETE,
                              "Case values incompletely covered (example pattern 0x" << std::hex
                                                                                     << i << ")");
            }
            // TODO: warn for more than one uncovered case, not just the first
            return false;
        }

        // It's an exhaustive case statement
        return true;
    }

    // Analyze each value in the case statement. Updates 'm_caseDetails' and issues warnings.
    void analyzeCaseDetails(AstCase* nodep) {
        const uint32_t numValues = 1UL << nodep->exprp()->width();
        // Clear case records
        for (uint32_t i = 0; i < numValues; ++i) {
            m_caseDetails.records[i].itemp = nullptr;
            m_caseDetails.records[i].constp = nullptr;
            m_caseDetails.records[i].stmtsp = nullptr;
        }
        // Now pick up the values for each assignment
        // We can cheat and use uint32_t's because we only support narrow case's
        bool reportedOverlap = false;
        bool hasDefault = false;
        m_caseDetails.noOverlaps = true;
        for (AstCaseItem* itemp = nodep->itemsp(); itemp;
             itemp = VN_AS(itemp->nextp(), CaseItem)) {

            // Default case
            if (itemp->isDefault()) {
                // Default was moved to be the last item by V3LinkDot. Fill remaining cases
                for (uint32_t i = 0; i < numValues; ++i) {
                    CaseRecord& caseRecord = m_caseDetails.records[i];
                    if (!caseRecord.itemp) {
                        caseRecord.itemp = itemp;
                        caseRecord.stmtsp = itemp->stmtsp();
                    }
                }
                hasDefault = true;
                continue;
            }

            for (AstConst* iconstp = VN_AS(itemp->condsp(), Const); iconstp;
                 iconstp = VN_AS(iconstp->nextp(), Const)) {
                // Some items can never match due to 2-state simulation
                if (neverItem(nodep, iconstp)) continue;

                const auto& match = matchPattern(nodep, iconstp);
                const uint32_t mask = match.first.toUInt();
                const uint32_t bits = match.second.toUInt();

                bool foundNewCase = false;
                const AstConst* firstOverlapConstp = nullptr;
                uint32_t firstOverlapValue = 0;
                for (uint32_t i = 0; i < numValues; ++i) {
                    if ((i & mask) != bits) continue;

                    CaseRecord& caseRecord = m_caseDetails.records[i];

                    // If this is the first case that covers this value, record it
                    if (!caseRecord.itemp) {
                        caseRecord.itemp = itemp;
                        caseRecord.constp = iconstp;
                        caseRecord.stmtsp = itemp->stmtsp();
                        foundNewCase = true;
                        continue;
                    }

                    // There is an overlap. If it's within the same CaseItem, it is legal
                    if (caseRecord.itemp == itemp) continue;

                    // Otherwise record the first overlapping case
                    if (!firstOverlapConstp) {
                        firstOverlapConstp = caseRecord.constp;
                        firstOverlapValue = i;
                        m_caseDetails.noOverlaps = false;
                    }
                }

                // Only report first overlap
                if (reportedOverlap || !firstOverlapConstp) continue;

                // Report first overlap
                if (nodep->priorityPragma()) {
                    // If this is a priority case, we only want to complain if every possible
                    // value for this item is already hit by some other item. This is true if
                    // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only
                    // covering item is this item itself, which is legal overlap within one
                    // item.
                    if (!foundNewCase) {
                        iconstp->v3warn(CASEOVERLAP,
                                        "Case item ignored: every matching value is covered "
                                        "by an earlier condition\n"
                                            << iconstp->warnContextPrimary() << '\n'
                                            << firstOverlapConstp->warnOther()
                                            << "... Location of previous condition\n"
                                            << firstOverlapConstp->warnContextPrimary());
                        reportedOverlap = true;
                    }
                } else {
                    // If this case statement doesn't have the priority keyword,
                    // we want to warn on any overlap.
                    std::ostringstream examplePattern;
                    if (iconstp->num().isAnyXZ()) {
                        examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue
                                       << ")";
                    }
                    iconstp->v3warn(CASEOVERLAP,
                                    "Case conditions overlap"
                                        << examplePattern.str() << "\n"
                                        << iconstp->warnContextPrimary() << '\n'
                                        << firstOverlapConstp->warnOther()
                                        << "... Location of overlapping condition\n"
                                        << firstOverlapConstp->warnContextSecondary());
                    reportedOverlap = true;
                }
            }
        }

        // If there was no default, check exhaustiveness
        m_caseDetails.exhaustiveOverEnumOnly = false;
        m_caseDetails.exhaustive = hasDefault;
        if (!hasDefault) {
            if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) {
                // Only checks enum values are covered, not all bit patterns of the case expression
                const bool exhaustiveOverEnum = checkExhaustiveEnum(nodep, enump);
                m_caseDetails.exhaustiveOverEnumOnly = exhaustiveOverEnum;
                m_caseDetails.exhaustive = exhaustiveOverEnum;
            } else {
                m_caseDetails.exhaustive = checkExhaustivePacked(nodep);
            }
        }

        // Records now valid
        m_caseDetailsValid = true;
    }

    void analyzeDecoderPattern(AstCase* nodep) {
        // Check each LHS record
        for (auto it = m_caseLhsRecords.cbegin(); it != m_caseLhsRecords.cend();) {
            const LhsRecord& lhsRecord = it->second;

            // Delete records that have no assignments in any case item (only pre-defaults)
            if (!lhsRecord.nCaseAssigns && !lhsRecord.nCaseAssignDlys) {
                it = m_caseLhsRecords.erase(it);
                continue;
            }
            ++it;

            // If mixed assignments, it's not a decoder pattern
            if (lhsRecord.nCaseAssigns && lhsRecord.nCaseAssignDlys) return;

            // If assigned in all branches, it's good - but only if every table entry will be
            // covered, i.e. the case has a default, or is exhaustive over all bit patterns.
            // Enum-only exhaustiveness is not enough: out-of-enum values leave entries
            // uncovered.
            if (m_caseHasDefault
                || (m_caseDetailsValid && m_caseDetails.exhaustive
                    && !m_caseDetails.exhaustiveOverEnumOnly)) {
                if (lhsRecord.nCaseAssigns == m_caseNCaseItems) continue;
                if (lhsRecord.nCaseAssignDlys == m_caseNCaseItems) continue;
            }

            // Otherwise it needs to have a pre-default assignment
            AstNode* const preDefaultp = lhsRecord.preDefaultp;
            if (!preDefaultp) return;
            // And the pre-default needs to be the same type
            if (lhsRecord.nCaseAssigns && !VN_IS(preDefaultp, Assign)) return;
            if (lhsRecord.nCaseAssignDlys && !VN_IS(preDefaultp, AssignDly)) return;
        }
        // All cases check out, can optimize if there are some entries left
        if (m_caseLhsRecords.empty()) return;

        // Gather all the LhsRecords and sort them - there is a copy here, it's ok, won't be many
        m_caseDecoderRecords.reserve(m_caseLhsRecords.size());
        for (const auto& item : m_caseLhsRecords) m_caseDecoderRecords.emplace_back(item.second);
        std::sort(m_caseDecoderRecords.begin(), m_caseDecoderRecords.end(),
                  [](const LhsRecord& a, const LhsRecord& b) {
                      // Sort by width, then id
                      const int aWidth = a.lhsp->width();
                      const int bWidth = b.lhsp->width();
                      if (aWidth != bWidth) return aWidth < bWidth;
                      return a.id < b.id;
                  });

        // We can either create a single lookup table for all LHSs, or one for each LHS.
        // With a single table, we need to select out of the lookup via a temporary variable.
        // With one table per LHS, we need to do multiple loads. The table is likely to incur a
        // D-cache miss on large designs, so we choose single table.

        const int caseWidth = nodep->exprp()->width();

        // Safely check if table with 'entryWidth' entries would fit within 'maxWidth' bits
        const auto fitsLimit = [&](size_t entryWidth, size_t maxWidth) -> bool {
            size_t totalWidth = entryWidth;
            // Multiply cases - iterative to avoid overflow
            for (int i = 0; i < caseWidth; ++i) {
                totalWidth <<= 1;
                if (totalWidth > maxWidth) return false;
            }
            return true;
        };

        // Check if the whole table would fit in a tiny table packed tightly
        m_caseDecoderEntryWidth = 0;
        for (LhsRecord& lhsRecord : m_caseDecoderRecords) {
            lhsRecord.offset = m_caseDecoderEntryWidth;
            m_caseDecoderEntryWidth += lhsRecord.lhsp->width();
        }
        // If it fits, we will pack it tightly
        if (fitsLimit(m_caseDecoderEntryWidth, CASE_TABLE_TINY_BITS)) {
            m_caseTableWidth = m_caseDecoderEntryWidth << caseWidth;  // Can optimize
            return;
        }

        // Tabel will be bigish. To avoid expensive bit swizzling, align each entry to a
        // word boundary if it would cross a word boundary.
        m_caseDecoderEntryWidth = 0;
        for (LhsRecord& lhsRecord : m_caseDecoderRecords) {
            const size_t width = lhsRecord.lhsp->width();
            const size_t lsbWord = VL_BITWORD_E(m_caseDecoderEntryWidth);
            const size_t msbWord = VL_BITWORD_E(m_caseDecoderEntryWidth + width - 1);
            if (lsbWord != msbWord) {
                m_caseDecoderEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE;
            }
            lhsRecord.offset = m_caseDecoderEntryWidth;
            m_caseDecoderEntryWidth += width;
        }
        // Check the table fits max size
        const size_t alignedEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE;
        if (fitsLimit(alignedEntryWidth, CASE_TABLE_MAX_BITS)) {
            m_caseTableWidth = alignedEntryWidth << caseWidth;  // Can optimize
            return;
        }

        // Can optimize as AstMatchMasked, no other info needed
    }

    // Analyze case statement. Updates 'm_case*' members. Reports warnings.
    void analyzeCase(AstCase* nodep) {
        // Reset all analysis results
        m_caseOpaque = false;
        m_caseHasDefault = false;
        m_caseNCaseItems = 0;
        m_caseNConditions = 0;
        m_caseDecoderRecords.clear();
        m_caseDecoderEntryWidth = 0;
        m_caseTableWidth = 0;
        m_caseLhsRecords.clear();
        m_caseDetailsValid = false;

        AstNode* const caseExprp = nodep->exprp();

        // Mark opaque if not a packed value - TODO: can this be a class?
        if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true;

        // Gather pre-default assignments of decoder pattern
        {
            // AstVarScope::user1() -> bool: true if written to
            const VNUser1InUse user1InUse;
            for (AstNode* prevp = nodep->prevp(); prevp; prevp = prevp->prevp()) {
                AstNodeAssign* const assp = checkDecoderAssign(prevp);
                if (!assp) break;  // Stop if not a decoder assignment
                // Stop if multiple assignments
                const bool multipleAssignments = assp->lhsp()->exists([&](AstVarRef* refp) {  //
                    return refp->varScopep()->user1SetOnce();
                });
                if (multipleAssignments) break;
                // Store pre-default assignment
                LhsRecord& lhsRecord = m_caseLhsRecords[*assp->lhsp()];
                lhsRecord.lhsp = assp->lhsp();
                lhsRecord.preDefaultp = assp;
            }
        }

        // Check each case item
        bool canBeDecoder = true;
        for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) {
            // Check conditions
            for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) {
                // Count conditions that can actually match.
                if (!neverItem(nodep, VN_AS(condp, NodeExpr))) ++m_caseNConditions;
                // Mark opaque if non-constant condition
                if (!VN_IS(condp, Const)) {
                    m_caseOpaque = true;
                    canBeDecoder = false;  // Can't be a decoder if opaque
                }
            }
            // Check if it has a default case
            if (cip->isDefault()) m_caseHasDefault = true;
            // Count case items
            ++m_caseNCaseItems;
            // Check if it fits the decoder pattern, if still possible
            if (canBeDecoder) canBeDecoder = analyzeDecoderCaseItem(cip);
        }

        // Nothing else to do if not a packed type, or non-const conditions
        if (m_caseOpaque) return;

        // If small enough, analyse details
        if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep);

        // Check if it actually fits a full decoder pattern
        if (canBeDecoder) analyzeDecoderPattern(nodep);
    }

    AstNodeStmt* connectDecoderOutputs(AstCase* nodep, AstNodeExpr* exprp,
                                       const char* tmpPrefixp) {
        FileLine* const flp = nodep->fileline();

        // If there is only one LHS, just use the result
        if (m_caseDecoderRecords.size() == 1) {
            const LhsRecord& lhsRecord = m_caseDecoderRecords[0];
            const int width = lhsRecord.lhsp->width();
            AstNodeExpr* const rhsp
                = exprp->width() == width ? exprp : new AstSel{flp, exprp, 0, width};
            AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false);
            if (lhsRecord.nCaseAssigns) {
                return new AstAssign{flp, lhsp, rhsp};
            } else if (lhsRecord.nCaseAssignDlys) {
                return new AstAssignDly{flp, lhsp, rhsp};
            } else {
                nodep->v3fatalSrc("Unknown assignment type");
            }
        }

        // There are multiple LHSs, store the lookup result in a temporary
        const std::string name = tmpPrefixp + std::to_string(m_nTmps++);
        AstVarScope* const tempVscp = m_scopep->createTemp(name, m_caseDecoderEntryWidth);
        AstNodeExpr* const tempWritep = new AstVarRef{flp, tempVscp, VAccess::WRITE};
        AstNodeStmt* const resultp = new AstAssign{flp, tempWritep, exprp};

        // For each LHS, select out the result
        for (const LhsRecord& lhsRecord : m_caseDecoderRecords) {
            const int width = lhsRecord.lhsp->width();
            const int lsb = lhsRecord.offset;
            AstNodeExpr* const tempReadp = new AstVarRef{flp, tempVscp, VAccess::READ};
            AstNodeExpr* const rhsp = new AstSel{flp, tempReadp, lsb, width};
            AstNodeExpr* const lhsp = lhsRecord.lhsp->cloneTreePure(false);
            if (lhsRecord.nCaseAssigns) {
                resultp->addNext(new AstAssign{flp, lhsp, rhsp});
            } else if (lhsRecord.nCaseAssignDlys) {
                resultp->addNext(new AstAssignDly{flp, lhsp, rhsp});
            } else {
                nodep->v3fatalSrc("Unknown assignment type");
            }
        }
        return resultp;
    }

    AstNodeStmt* convertCaseTable(AstCase* nodep) {
        // Create the table constant
        FileLine* const flp = nodep->fileline();
        AstConst* const tablep
            = new AstConst{flp, AstConst::WidthedValue{}, static_cast<int>(m_caseTableWidth), 0};
        const uint32_t tableEntries = 1U << nodep->exprp()->width();

        const bool isTinyTable = m_caseTableWidth <= CASE_TABLE_TINY_BITS;
        if (isTinyTable) {
            ++m_stats.caseTableTiny;
        } else {
            ++m_stats.caseTableNormal;
        }

        // Populate the table. Align entries to a word boundary to avoid bit swizzling at runtime.
        const uint32_t entryWidth = isTinyTable
                                        ? m_caseDecoderEntryWidth
                                        : VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE;
        for (const LhsRecord& lhsRecord : m_caseDecoderRecords) {
            const int lhsWidth = lhsRecord.lhsp->width();
            const int lhsOffset = lhsRecord.offset;

            // Broadcast the pre-default assignment
            if (lhsRecord.preDefaultp) {
                AstConst* const rhsp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const);
                for (uint32_t index = 0; index < tableEntries; ++index) {
                    const uint32_t tableOffset = index * entryWidth + lhsOffset;
                    tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth);
                }
            }

            // Populate table based on each case item. In reverse order so earlier items win
            for (AstCaseItem* cip = VN_AS(nodep->itemsp()->lastp(), CaseItem); cip;
                 cip = VN_AS(cip->prevp(), CaseItem)) {
                // Find the RHS in this case
                AstConst* const rhsp = [&]() -> AstConst* {
                    for (AstNode* stmtp = cip->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
                        AstNodeAssign* const ap = VN_AS(stmtp, NodeAssign);
                        if (lhsRecord.lhsp->sameTree(ap->lhsp())) return VN_AS(ap->rhsp(), Const);
                    }
                    // Not assigned in this case, use the pre-assigned default
                    return VN_AS(lhsRecord.preDefaultp->rhsp(), Const);
                }();

                // If default, broadcast it
                if (cip->isDefault()) {
                    for (uint32_t index = 0; index < tableEntries; ++index) {
                        const uint32_t tableOffset = index * entryWidth + lhsOffset;
                        tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth);
                    }
                    continue;
                }

                // Iterate case conditions in reverse order
                for (AstConst* condp = VN_AS(cip->condsp()->lastp(), Const); condp;
                     condp = VN_AS(condp->prevp(), Const)) {
                    if (neverItem(nodep, condp)) continue;  // If item never matches, ignore it
                    const auto& match = matchPattern(nodep, condp);
                    const uint32_t matchMask = match.first.toUInt();
                    const uint32_t matchBits = match.second.toUInt();
                    const uint32_t inverseMask = ~matchMask & ((1U << condp->width()) - 1);
                    // This iterates through all integers that are a subset of the inverse mask,
                    // i.e.: all don't care values masked out
                    for (uint32_t i = inverseMask; true; i = (i - 1) & inverseMask) {
                        const uint32_t index = i | matchBits;
                        const uint32_t tableOffset = index * entryWidth + lhsOffset;
                        tablep->num().opSelInto(rhsp->num(), tableOffset, lhsWidth);
                        if (!i) break;
                    }
                }
            }
        }

        // Create the table in the constant pool, unless using an inline table
        AstVarScope* const tableVscp = [&]() -> AstVarScope* {
            if (isTinyTable) return nullptr;
            AstVarScope* vscp = v3Global.rootp()->constPoolp()->findConst(tablep, true);
            VL_DO_DANGLING(tablep->deleteTree(), tablep);  // findConst clones
            return vscp;
        }();

        // Create the lookup table reference and index
        AstNodeExpr* const tableRefp
            = tableVscp ? static_cast<AstNodeExpr*>(new AstVarRef{flp, tableVscp, VAccess::READ})
                        : static_cast<AstNodeExpr*>(tablep);
        AstNodeExpr* const caseExprp
            = new AstExtend{flp, nodep->exprp()->cloneTreePure(false), 32};
        AstNodeExpr* const scalep = new AstConst{flp, entryWidth};
        AstNodeExpr* const tableLsbp = new AstMul{flp, scalep, caseExprp};
        AstNodeExpr* const tableSelp
            = new AstSel{flp, tableRefp, tableLsbp, static_cast<int>(m_caseDecoderEntryWidth)};

        // Connect outputs
        return connectDecoderOutputs(nodep, tableSelp, "__VcaseTableOut");
    }

    AstNodeStmt* convertCaseDecoder(AstCase* nodep) {
        ++m_stats.caseDecoder;

        FileLine* const flp = nodep->fileline();

        // Gather all the case conditions, paird with their statements. A 'nullptr' condition
        // matches anything (the default case, or the catch-all added below).
        std::vector<std::pair<AstConst*, AstNode*>> clauses;  // (condition, item statements)
        clauses.reserve(m_caseNConditions + 1);
        for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) {
            if (cip->isDefault()) {
                clauses.emplace_back(nullptr, cip->stmtsp());
                continue;
            }
            for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) {
                AstConst* const iconstp = VN_AS(condp, Const);
                // Skip items that can never match in 2-state simulation (e.g. X in casez)
                if (neverItem(nodep, iconstp)) continue;
                clauses.emplace_back(iconstp, cip->stmtsp());
            }
        }
        // If the case has no default item and is not provably exhaustive, unmatched selector
        // values fall back to the pre-defaults. Represent that with a catch-all clause (null
        // condition and no statements, so every LHS uses its pre-default). 'analyzeDecoderPattern'
        // guarantees every LHS has a pre-default in this case.
        const bool provenExhaustive = m_caseDetailsValid && m_caseDetails.exhaustive
                                      && !m_caseDetails.exhaustiveOverEnumOnly;
        if (clauses.back().first && !provenExhaustive) clauses.emplace_back(nullptr, nullptr);

        // Number of entries in decoder table
        const int decoderEnries = clauses.size();

        // Build the match table: a {matchBits, matchMask} packed pair per clause, shared by all
        // LHSs. Each field is rounded up to a whole EDATA word boundary to avoid bit swizzling
        // at runtime. We use a packed value so the runtime function can take a VlWide pointer
        // without templating on the array size.
        const int condWidth = nodep->exprp()->width();
        const int matchWidth = 2 * VL_WORDS_I(condWidth) * VL_EDATASIZE;
        AstConst* const matchp
            = new AstConst{flp, AstConst::WidthedValue{}, decoderEnries * matchWidth, 0};
        for (int i = 0; i < decoderEnries; ++i) {
            const int entryLsb = i * matchWidth;

            // If the entry has a condition, use it's match bits and mask
            if (AstConst* const condp = clauses[i].first) {
                const auto& match = matchPattern(nodep, condp);
                matchp->num().opSelInto(match.first, entryLsb, condWidth);
                matchp->num().opSelInto(match.second, entryLsb + matchWidth / 2, condWidth);
                continue;
            }

            // Otherwise use zero for mask and bits, which matches anything
            V3Number numZero{flp, condWidth, 0};
            matchp->num().opSelInto(numZero, entryLsb, condWidth);
            matchp->num().opSelInto(numZero, entryLsb + matchWidth / 2, condWidth);
        }

        // Create the table initializer
        AstRange* const rangep = new AstRange{flp, decoderEnries - 1, 0};
        AstNodeDType* const entryDtypep = nodep->findBitDType(
            m_caseDecoderEntryWidth, m_caseDecoderEntryWidth, VSigning::UNSIGNED);
        AstNodeDType* const tableDtypep = new AstUnpackArrayDType{flp, entryDtypep, rangep};
        v3Global.rootp()->typeTablep()->addTypesp(tableDtypep);
        AstInitArray* const tablep = new AstInitArray{flp, tableDtypep, nullptr};

        // Build a single value table for all LHSs: one entry per clause, packing each LHS's value
        // at its offset. The entry width is the table packing computed by 'analyzeDecoderPattern'
        // Rounded up to a whole EDATA word boundary to avoid bit swizzling at runtime.
        for (int i = 0; i < decoderEnries; ++i) {
            AstNode* const stmtsp = clauses[i].second;
            AstConst* const entryp = new AstConst{flp, AstConst::WidthedValue{},
                                                  static_cast<int>(m_caseDecoderEntryWidth), 0};
            for (const LhsRecord& lhsRecord : m_caseDecoderRecords) {
                AstNodeExpr* const lhsp = lhsRecord.lhsp;
                // Find the value assigned to this LHS in the clause's statements
                AstConst* valConstp = nullptr;
                for (AstNode* stmtp = stmtsp; stmtp; stmtp = stmtp->nextp()) {
                    AstNodeAssign* const assignp = VN_AS(stmtp, NodeAssign);
                    if (!lhsp->sameTree(assignp->lhsp())) continue;
                    valConstp = VN_AS(assignp->rhsp(), Const);
                    break;
                }
                // Not assigned in this clause, so use the pre-assigned default
                if (!valConstp) {
                    UASSERT_OBJ(lhsRecord.preDefaultp, nodep,
                                "Decoder LHS unassigned in case item without a pre-default");
                    valConstp = VN_AS(lhsRecord.preDefaultp->rhsp(), Const);
                }
                entryp->num().opSelInto(valConstp->num(), lhsRecord.offset, lhsp->width());
            }
            tablep->addIndexValuep(i, entryp);
        }

        // Create the tables
        AstVarScope* const matchVscp = v3Global.rootp()->constPoolp()->findConst(matchp, true);
        AstVarScope* const tableVscp = v3Global.rootp()->constPoolp()->findTable(tablep);
        VL_DO_DANGLING(matchp->deleteTree(), matchp);
        VL_DO_DANGLING(tablep->deleteTree(), tablep);

        // AstMatchMasked produces the index of the matching entry
        AstNodeExpr* const tableRefp = new AstVarRef{flp, tableVscp, VAccess::READ};
        AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false);
        AstMatchMasked* const indexp = new AstMatchMasked{flp, caseExprp, matchVscp};
        AstNodeExpr* const entryp = new AstArraySel{flp, tableRefp, indexp};

        return connectDecoderOutputs(nodep, entryp, "__VcaseDecoderOut");
    }

    // TODO: should return AstNodeStmt after #6280
    AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const {
        // Base case: If reached the last bit, upperValue equals an exact value, just return
        // the statements from that CaseItem. Note: Not cloning here as the caller will do
        // an identity check.
        if (msb < 0) return m_caseDetails.records[upperValue].stmtsp;

        // Recursive case:
        // Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively
        const uint32_t upperValue0 = upperValue;
        const uint32_t upperValue1 = upperValue | (1UL << msb);
        AstNode* tree0p = convertCaseFastRecurse(cexprp, msb - 1, upperValue0);
        AstNode* tree1p = convertCaseFastRecurse(cexprp, msb - 1, upperValue1);

        // If same logic on both sides, we can just return one of them
        if (tree0p == tree1p) return tree0p;

        // We could have a "checkerboard" with A B A B, we can use the same IF on both edges
        {
            bool same = true;
            for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) {
                if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) {
                    same = false;
                    break;
                }
            }
            if (same) {
                VL_DO_DANGLING(tree1p->deleteTree(), tree1p);
                return tree0p;
            }
        }

        // Must have differing logic. Test the bit and convert to an If.

        // Clone if needed
        if (tree0p && tree0p->backp()) tree0p = tree0p->cloneTree(true);
        if (tree1p && tree1p->backp()) tree1p = tree1p->cloneTree(true);
        // Create the If statement
        FileLine* const flp = cexprp->fileline();
        AstNodeExpr* const condp = new AstSel{flp, cexprp->cloneTreePure(false), msb, 1};
        AstIf* const ifp = new AstIf{flp, condp, tree1p, tree0p};
        return ifp;
    }

    // CASEx(cexpr,....
    // ->  tree of IF(msb,  IF(msb-1, 11, 10)
    //                      IF(msb-1, 01, 00))
    // TODO: should return AstNodeStmt after #6280
    AstNode* convertCaseFast(AstCase* nodep) {
        ++m_stats.caseFast;
        const int caseWidth = nodep->exprp()->width();
        AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL);
        return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp;
    }

    // Convet case statement using generic if/else tree
    // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3))
    // ->  IF((cexpr==icond1),istmts1,
    //                       IF((EQ (AND MASK cexpr) (AND MASK icond1)
    //                              ,istmts2, istmts3
    // TODO: should return AstNodeStmt after #6280
    AstNode* convertCaseGeneric(AstCase* nodep) {
        ++m_stats.caseGeneric;
        // We'll do this in two stages.
        // First stage, convert the conditions to the appropriate IF AND terms.
        bool hasDefault = false;
        for (AstCaseItem* itemp = nodep->itemsp(); itemp;
             itemp = VN_AS(itemp->nextp(), CaseItem)) {

            FileLine* const flp = itemp->fileline();

            // Default clause. Just make true, we'll optimize it away later
            if (itemp->isDefault()) {
                itemp->addCondsp(new AstConst{flp, AstConst::BitTrue{}});
                hasDefault = true;
                continue;
            }

            // Regular clause. Construct the condition expression.
            AstNodeExpr* newCondp = nullptr;
            for (AstNodeExpr *itemExprp = itemp->condsp(), *nextp; itemExprp; itemExprp = nextp) {
                nextp = VN_AS(itemExprp->nextp(), NodeExpr);
                itemExprp->unlinkFrBack();

                // If case never matches, ignore it
                if (neverItem(nodep, itemExprp)) {
                    VL_DO_DANGLING(itemExprp->deleteTree(), itemExprp);
                    continue;
                }

                // Compute the term to add to the condition expression
                AstNodeExpr* const termp = [&]() -> AstNodeExpr* {
                    // Will need a copy of the caseExpr regardless
                    AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false);

                    // InsideRange: Similar logic in V3Width::visit(AstInside)
                    if (AstInsideRange* const itemRangep = VN_CAST(itemExprp, InsideRange)) {
                        AstNodeExpr* const resultp = itemRangep->newAndFromInside(  //
                            caseExprp,  //
                            itemRangep->lhsp()->unlinkFrBack(),
                            itemRangep->rhsp()->unlinkFrBack());
                        VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemRangep);
                        return resultp;
                    }

                    // Check if we need to perform a wildcard match, this needs masking
                    if (AstConst* const itemConstp = VN_CAST(itemExprp, Const)) {
                        // TODO: 4-state will need to fix this
                        if (itemConstp->num().isFourState()
                            && (nodep->casex() || nodep->casez() || nodep->caseInside())) {
                            // Wildcard match, make 'caseExpr' & 'mask' == 'itemExpr' & 'mask'
                            const auto& match = matchPattern(nodep, itemConstp);
                            const V3Number& matchMask = match.first;
                            const V3Number& matchBits = match.second;
                            VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp);
                            return AstEq::newTyped(
                                flp,  //
                                new AstConst{flp, matchBits},
                                new AstAnd{flp, caseExprp, new AstConst{flp, matchMask}});
                        }
                    }

                    // Regular case, use simple equality comparison
                    return AstEq::newTyped(flp, caseExprp, itemExprp);
                }();

                // 'Or' new term with previous terms
                newCondp = newCondp ? new AstLogOr{flp, newCondp, termp} : termp;
            }
            // Replace expression in tree. Needs to be non-null, so add a constant false if
            // needed
            if (!newCondp) newCondp = new AstConst{flp, AstConst::BitFalse{}};
            itemp->addCondsp(newCondp);
        }

        // If there was no default, add a empty one, this greatly simplifies below code
        // and constant propagation will just eliminate it for us later.
        if (!hasDefault) {
            nodep->addItemsp(new AstCaseItem{
                nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr});
        }

        // Now build the IF statement tree
        // The tree can be quite huge.  Pull every group of 8 out, and make a OR tree.
        // This reduces the depth for the bottom elements, at the cost of
        // some of the top elements.  If we ever have profiling data, we
        // should pull out the most common item from here and instead make
        // it the first IF branch.
        int depth = 0;
        AstIf* grouprootp = nullptr;
        AstIf* groupnextp = nullptr;
        AstIf* itemnextp = nullptr;
        for (AstCaseItem* itemp = nodep->itemsp(); itemp;
             itemp = VN_AS(itemp->nextp(), CaseItem)) {

            // Grab the statements from this item. May be empty.
            AstNode* const istmtsp = itemp->stmtsp();
            if (istmtsp) istmtsp->unlinkFrBackWithNext();

            // Expressioned clause
            AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack();
            {  // Prepare for next group
                if (++depth > CASE_ENCODER_GROUP_DEPTH) depth = 1;
                if (depth == 1) {  // First group or starting new group
                    itemnextp = nullptr;
                    AstIf* const newp = new AstIf{itemp->fileline(), ifexprp->cloneTreePure(true)};
                    if (groupnextp) {
                        groupnextp->addElsesp(newp);
                    } else {
                        grouprootp = newp;
                    }
                    groupnextp = newp;
                } else {  // Continue group, modify if condition to OR in this new condition
                    AstNodeExpr* const condp = groupnextp->condp()->unlinkFrBack();
                    groupnextp->condp(
                        new AstOr{ifexprp->fileline(), condp, ifexprp->cloneTreePure(true)});
                }
            }
            {  // Make the new lower IF and attach in the tree
                AstNodeExpr* itemexprp = ifexprp;
                VL_DANGLING(ifexprp);
                if (depth == CASE_ENCODER_GROUP_DEPTH) {  // End of group - can skip the condition
                    VL_DO_DANGLING(itemexprp->deleteTree(), itemexprp);
                    itemexprp = new AstConst{itemp->fileline(), AstConst::BitTrue{}};
                }
                AstIf* const newp = new AstIf{itemp->fileline(), itemexprp, istmtsp};
                if (itemnextp) {
                    itemnextp->addElsesp(newp);
                } else {
                    groupnextp->addThensp(newp);  // First in a new group
                }
                itemnextp = newp;
            }
        }
        return grouprootp;
    }

    // Convert the given case statement to a representation not using AstCase
    // TODO: should return AstNodeStmt after #6280
    AstNode* convertCase(AstCase* nodep) {
        // Determine if we should use the lookup table method
        const bool useTable = [&]() {
            // Not if disabled
            if (!v3Global.opt.fCaseTable()) return false;
            // Not if analysis tells us we can't
            if (!m_caseTableWidth) return false;
            // Always if tiny - it is materialized inline, so there is no load to amortize
            if (m_caseTableWidth <= CASE_TABLE_TINY_BITS) return true;
            // For a normal (constant-pool) table, weigh the indexed load against the branch
            // lowering it would replace. That lowering's depth is bounded by the selector
            // width (a balanced bit tree tests ~one bit per level) and by the number of
            // distinct values (a generic if/else does ~one compare per value). A few compares
            // are cheaper than a load that is likely to be a cache miss, so only table once that
            // depth is exceeded.
            const size_t branches = std::min<size_t>(nodep->exprp()->width(), m_caseNConditions);
            if (branches < CASE_TABLE_MIN_BRANCHES) return false;
            return true;
        }();
        if (useTable) return convertCaseTable(nodep);

        // Determine if we should use the decoder method.
        const bool useDecoder = [&]() {
            // Not if disabled
            if (!v3Global.opt.fCaseDecoder()) return false;
            // Not if not a decoder pattern
            if (m_caseDecoderRecords.empty()) return false;
            // Only worth it once the branch lowering it would replace is deep enough (see
            // useTable)
            const size_t branches = std::min<size_t>(nodep->exprp()->width(), m_caseNConditions);
            if (branches < CASE_TABLE_MIN_BRANCHES) return false;
            return true;
        }();
        if (useDecoder) return convertCaseDecoder(nodep);

        // Determine if we should use the fast bitwise branching tree method
        const bool useFastBitTree = [&]() {
            // Not if disabled
            if (!v3Global.opt.fCaseTree()) return false;
            // Can't do it without the detailed analysis
            if (!m_caseDetailsValid) return false;
            // Can't do it if not exhaustive
            if (!m_caseDetails.exhaustive) return false;
            // Not worth doing if there are few conditions
            if (m_caseNConditions <= 3) return false;
            // Avoid e.g. priority expanders from going crazy in expansion
            const size_t caseWidth = nodep->exprp()->width();
            if (caseWidth >= 8 && m_caseNConditions <= (caseWidth + 1)) return false;
            // Otherwise use the bit tree
            return true;
        }();
        if (useFastBitTree) return convertCaseFast(nodep);

        // Convert using the generic if/else tree method
        // If a case statement is exhaustive, presume signals involved aren't forming a latch
        // TODO: this is broken, but it is as was before
        if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) {
            m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true);
        }
        return convertCaseGeneric(nodep);
    }

    // VISITORS
    void visit(AstCase* nodep) override {
        UASSERT_OBJ(nodep->exprp()->isPure(), nodep,
                    "Impure case expression should have been removed by V3LiftExpr");

        CaseLintVisitor::apply(nodep);

        // Convert any children first
        iterateChildren(nodep);

        // Analyze this case statement
        analyzeCase(nodep);

        // Take the 'notParallelp' statements under the case statement created by V3Assert.
        // If the statement was proven to have no overlaps and all cases covered,
        // it can be removed. Otherwise insert the assertion after the case statement.
        if (AstNode* const stmtp = nodep->notParallelp()) {
            stmtp->unlinkFrBackWithNext();
            if (m_caseDetailsValid && m_caseDetails.exhaustive && m_caseDetails.noOverlaps) {
                ++m_stats.provenAssertions;
                VL_DO_DANGLING(stmtp->deleteTree(), stmtp);
            } else {
                nodep->addNextHere(stmtp);
            }
        }

        // Convert the case statement and replace the original
        if (AstNode* const replacementp = convertCase(nodep)) {
            nodep->replaceWith(replacementp);
        } else {
            nodep->unlinkFrBack();
        }
        VL_DO_DANGLING(nodep->deleteTree(), nodep);
    }

    void visit(AstScope* nodep) override {
        VL_RESTORER(m_scopep);
        m_scopep = nodep;
        iterateChildren(nodep);
    }

    void visit(AstAlways* nodep) override {
        VL_RESTORER(m_alwaysp);
        m_alwaysp = nodep;
        iterateChildren(nodep);
    }
    void visit(AstNode* nodep) override { iterateChildren(nodep); }

public:
    // CONSTRUCTORS
    explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); }
    ~CaseVisitor() override {
        V3Stats::addStat("Optimizations, Cases decoder", m_stats.caseDecoder);
        V3Stats::addStat("Optimizations, Cases table normal", m_stats.caseTableNormal);
        V3Stats::addStat("Optimizations, Cases table tiny", m_stats.caseTableTiny);
        V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast);
        V3Stats::addStat("Optimizations, Cases complex", m_stats.caseGeneric);
        V3Stats::addStat("Optimizations, Cases proven assertions", m_stats.provenAssertions);
    }
};

size_t CaseVisitor::LhsRecord::s_nextId = 0;

//######################################################################
// Case class functions

void V3Case::caseAll(AstNetlist* nodep) {
    UINFO(2, __FUNCTION__ << ":");
    { CaseVisitor{nodep}; }  // Destruct before checking
    V3Global::dumpCheckGlobalTree("case", 0, dumpTreeEitherLevel() >= 3);
}
void V3Case::caseLint(AstGenCase* nodep) {
    UINFO(4, __FUNCTION__ << ": ");
    CaseLintVisitor::apply(nodep);
}
