2012-04-13 03:08:20 +02:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
2006-08-26 13:35:28 +02:00
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: Break case statements up and add Unknown assigns
|
|
|
|
|
//
|
2019-11-08 04:33:59 +01:00
|
|
|
// Code available from: https://verilator.org
|
2006-08-26 13:35:28 +02:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2026-01-27 02:24:34 +01:00
|
|
|
// 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
|
2020-03-21 16:24:24 +01:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
2006-08-26 13:35:28 +02:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// V3Case's Transformations:
|
2008-06-10 03:25:10 +02:00
|
|
|
//
|
2006-08-26 13:35:28 +02:00
|
|
|
// Each module:
|
2019-05-19 22:13:13 +02:00
|
|
|
// 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)
|
2018-02-02 03:24:41 +01:00
|
|
|
// Enter all into std::multimap, sort by value and use a tree of < and == compares.
|
2019-05-19 22:13:13 +02:00
|
|
|
// "Diagonal" find of {rightmost,leftmost} bit {set,clear}
|
2018-02-02 03:24:41 +01:00
|
|
|
// Ignoring mask, check each value is unique (using std::multimap as above?)
|
2019-05-19 22:13:13 +02:00
|
|
|
// Each branch is then mask-and-compare operation (IE
|
|
|
|
|
// <000000001_000000000 at midpoint.)
|
2006-08-26 13:35:28 +02:00
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
2019-10-05 02:17:11 +02:00
|
|
|
|
2023-10-18 12:37:46 +02:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
#include "V3Case.h"
|
2022-08-05 11:56:57 +02:00
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
#include "V3Stats.h"
|
|
|
|
|
|
2022-09-18 21:53:42 +02:00
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
//######################################################################
|
|
|
|
|
|
2023-03-18 17:17:25 +01:00
|
|
|
class CaseLintVisitor final : public VNVisitorConst {
|
2025-09-23 20:49:01 +02:00
|
|
|
// Under a CASE value node, if so the relevant case statement
|
|
|
|
|
const AstNode* m_casep = nullptr;
|
2009-01-21 22:56:50 +01:00
|
|
|
|
2018-05-14 12:50:47 +02:00
|
|
|
// METHODS
|
2025-09-23 20:49:01 +02:00
|
|
|
template <typename CaseItem>
|
|
|
|
|
static void detectMultipleDefaults(CaseItem* itemsp) {
|
2019-05-19 22:13:13 +02:00
|
|
|
bool hitDefault = false;
|
2025-09-23 20:49:01 +02:00
|
|
|
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;
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2025-09-23 20:49:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
|
2025-09-23 20:49:01 +02:00
|
|
|
// VISITORS
|
|
|
|
|
void visit(AstGenCase* nodep) override {
|
|
|
|
|
// Detect multiple defaults
|
|
|
|
|
detectMultipleDefaults(nodep->itemsp());
|
2019-05-19 22:13:13 +02:00
|
|
|
// Check for X/Z in non-casex statements
|
2025-09-23 20:49:01 +02:00
|
|
|
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)");
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2025-09-23 20:49:01 +02:00
|
|
|
// Detect multiple defaults
|
|
|
|
|
detectMultipleDefaults(nodep->itemsp());
|
|
|
|
|
// Check for X/Z in non-casex statements
|
|
|
|
|
checkXZinNonCaseX(nodep, nodep->exprp(), nodep->itemsp());
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstConst* nodep) override {
|
2025-09-23 20:49:01 +02:00
|
|
|
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)");
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2025-09-23 20:49:01 +02:00
|
|
|
return;
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2025-09-23 20:49:01 +02:00
|
|
|
|
|
|
|
|
nodep->v3warn(CASEWITHX, "Use of x/? constant in case statement, "
|
|
|
|
|
"(perhaps intended casex/casez)");
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2023-03-18 17:17:25 +01:00
|
|
|
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
2020-04-04 14:31:14 +02:00
|
|
|
|
2019-09-12 13:22:22 +02:00
|
|
|
// CONSTRUCTORS
|
2025-09-23 20:49:01 +02:00
|
|
|
explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); }
|
|
|
|
|
explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); }
|
2022-09-16 12:22:11 +02:00
|
|
|
~CaseLintVisitor() override = default;
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
static void apply(AstCase* nodep) { CaseLintVisitor{nodep}; }
|
|
|
|
|
static void apply(AstGenCase* nodep) { CaseLintVisitor{nodep}; }
|
2006-08-26 13:35:28 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//######################################################################
|
|
|
|
|
// Case state, as a visitor of each AstNode
|
|
|
|
|
|
2022-01-02 19:56:40 +01:00
|
|
|
class CaseVisitor final : public VNVisitor {
|
2026-06-13 09:45:26 +02:00
|
|
|
// Maximum width for detailed analysis
|
|
|
|
|
constexpr static int CASE_DETAILS_MAX_WIDTH = 16;
|
2026-06-12 15:15:41 +02:00
|
|
|
// Levels of priority to be ORed together in top IF tree
|
|
|
|
|
constexpr static int CASE_ENCODER_GROUP_DEPTH = 8;
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
// 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;
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
};
|
2006-08-26 13:35:28 +02:00
|
|
|
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
// 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
|
|
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
// STATE
|
2026-06-13 09:45:26 +02:00
|
|
|
// Statistics tracking, as a struct so can be passed to 'const' methods
|
|
|
|
|
struct Stats final {
|
2026-06-19 20:46:13 +02:00
|
|
|
VDouble0 caseDecoder; // Cases using decoder method
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
VDouble0 caseTableNormal; // Cases using table method with normal table
|
|
|
|
|
VDouble0 caseTableTiny; // Cases using table method with tiny table
|
2026-06-13 09:45:26 +02:00
|
|
|
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;
|
2021-11-26 23:55:36 +01:00
|
|
|
const AstNode* m_alwaysp = nullptr; // Always in which case is located
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
size_t m_nTmps = 0; // Sequence numbers for temporary variables
|
|
|
|
|
AstScope* m_scopep = nullptr; // Current scope
|
2006-08-26 13:35:28 +02:00
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise
|
|
|
|
|
bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions)
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
bool m_caseHasDefault = false; // Indicates the case statement has a default case
|
|
|
|
|
size_t m_caseNCaseItems = 0; // Number of AstCaseItems in the case statement
|
2026-06-13 09:45:26 +02:00
|
|
|
size_t m_caseNConditions = 0; // Number of conditions in the case statement
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
// 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
|
2026-06-13 09:45:26 +02:00
|
|
|
bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid
|
|
|
|
|
struct final {
|
|
|
|
|
bool exhaustive = false; // Proven exhaustive
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
bool exhaustiveOverEnumOnly = false; // Exhaustive over enum values only
|
2026-06-13 09:45:26 +02:00
|
|
|
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;
|
2006-08-26 13:35:28 +02:00
|
|
|
|
|
|
|
|
// METHODS
|
2026-06-13 09:45:26 +02:00
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 15:55:24 +02:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-12 15:15:41 +02:00
|
|
|
// 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) {
|
2023-09-14 13:22:49 +02:00
|
|
|
if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr;
|
|
|
|
|
const AstEnumDType* const enumDtp
|
2022-12-01 01:42:21 +01:00
|
|
|
= VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType);
|
2023-09-14 13:22:49 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) {
|
2026-06-16 15:55:24 +02:00
|
|
|
const auto& match = matchPattern(nodep, VN_AS(eip->valuep(), Const));
|
|
|
|
|
const uint32_t mask = match.first.toUInt();
|
|
|
|
|
const uint32_t bits = match.second.toUInt();
|
2023-09-14 13:22:49 +02:00
|
|
|
|
2026-06-12 15:15:41 +02:00
|
|
|
// Check all cases to see if they cover this enum value/pattern
|
2023-09-14 13:22:49 +02:00
|
|
|
for (uint32_t i = 0; i < numCases; ++i) {
|
2026-06-16 15:55:24 +02:00
|
|
|
if ((i & mask) != bits) continue; // This case is not for this enum value
|
2026-06-13 09:45:26 +02:00
|
|
|
if (m_caseDetails.records[i].itemp) continue; // Covered case
|
2026-06-12 15:15:41 +02:00
|
|
|
// Warn unless unique0 case which allows no-match
|
|
|
|
|
if (!nodep->unique0Pragma()) {
|
|
|
|
|
nodep->v3warn(CASEINCOMPLETE,
|
|
|
|
|
"Enum item " << eip->prettyNameQ() << " not covered by case");
|
2023-09-14 13:22:49 +02:00
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
// TODO: warn for all uncovered enum values, not just the first
|
|
|
|
|
return false; // enum has uncovered value by case items
|
2023-09-14 13:22:49 +02:00
|
|
|
}
|
2022-12-01 01:42:21 +01:00
|
|
|
}
|
2023-09-14 13:22:49 +02:00
|
|
|
return true; // enum is fully covered
|
2022-12-01 01:42:21 +01:00
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// 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) {
|
2026-06-13 09:45:26 +02:00
|
|
|
if (m_caseDetails.records[i].itemp) continue; // Covered case
|
2026-06-12 15:15:41 +02:00
|
|
|
if (!nodep->unique0Pragma()) {
|
|
|
|
|
nodep->v3warn(CASEINCOMPLETE,
|
|
|
|
|
"Case values incompletely covered (example pattern 0x" << std::hex
|
|
|
|
|
<< i << ")");
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
// TODO: warn for more than one uncovered case, not just the first
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// It's an exhaustive case statement
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// 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;
|
2026-06-12 15:15:41 +02:00
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
// Now pick up the values for each assignment
|
|
|
|
|
// We can cheat and use uint32_t's because we only support narrow case's
|
2021-03-29 01:57:36 +02:00
|
|
|
bool reportedOverlap = false;
|
2026-06-12 15:15:41 +02:00
|
|
|
bool hasDefault = false;
|
2026-06-13 09:45:26 +02:00
|
|
|
m_caseDetails.noOverlaps = true;
|
2020-04-15 13:58:34 +02:00
|
|
|
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
2021-10-22 14:56:48 +02:00
|
|
|
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// Default case
|
|
|
|
|
if (itemp->isDefault()) {
|
|
|
|
|
// Default was moved to be the last item by V3LinkDot. Fill remaining cases
|
2026-06-13 09:45:26 +02:00
|
|
|
for (uint32_t i = 0; i < numValues; ++i) {
|
|
|
|
|
CaseRecord& caseRecord = m_caseDetails.records[i];
|
2026-06-12 15:15:41 +02:00
|
|
|
if (!caseRecord.itemp) {
|
|
|
|
|
caseRecord.itemp = itemp;
|
|
|
|
|
caseRecord.stmtsp = itemp->stmtsp();
|
2021-03-29 01:57:36 +02:00
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
hasDefault = true;
|
|
|
|
|
continue;
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2026-06-16 15:55:24 +02:00
|
|
|
const auto& match = matchPattern(nodep, iconstp);
|
|
|
|
|
const uint32_t mask = match.first.toUInt();
|
|
|
|
|
const uint32_t bits = match.second.toUInt();
|
2026-06-12 15:15:41 +02:00
|
|
|
|
2026-06-12 13:22:18 +02:00
|
|
|
bool foundNewCase = false;
|
|
|
|
|
const AstConst* firstOverlapConstp = nullptr;
|
|
|
|
|
uint32_t firstOverlapValue = 0;
|
2026-06-13 09:45:26 +02:00
|
|
|
for (uint32_t i = 0; i < numValues; ++i) {
|
2026-06-16 15:55:24 +02:00
|
|
|
if ((i & mask) != bits) continue;
|
2026-06-12 15:15:41 +02:00
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
CaseRecord& caseRecord = m_caseDetails.records[i];
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// 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();
|
2026-06-12 13:22:18 +02:00
|
|
|
foundNewCase = true;
|
2026-06-12 15:15:41 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-12 13:22:18 +02:00
|
|
|
// 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;
|
2026-06-13 09:45:26 +02:00
|
|
|
m_caseDetails.noOverlaps = false;
|
2026-06-12 15:15:41 +02:00
|
|
|
}
|
2022-12-01 01:42:21 +01:00
|
|
|
}
|
2026-06-13 09:45:26 +02:00
|
|
|
|
|
|
|
|
// Only report first overlap
|
|
|
|
|
if (reportedOverlap || !firstOverlapConstp) continue;
|
|
|
|
|
|
|
|
|
|
// Report first overlap
|
2026-06-12 13:22:18 +02:00
|
|
|
if (nodep->priorityPragma()) {
|
2026-06-13 09:45:26 +02:00
|
|
|
// 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) {
|
2026-06-12 13:22:18 +02:00
|
|
|
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());
|
2026-06-13 09:45:26 +02:00
|
|
|
reportedOverlap = true;
|
2026-06-12 13:22:18 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// If this case statement doesn't have the priority keyword,
|
|
|
|
|
// we want to warn on any overlap.
|
2026-06-13 09:45:26 +02:00
|
|
|
std::ostringstream examplePattern;
|
|
|
|
|
if (iconstp->num().isAnyXZ()) {
|
|
|
|
|
examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue
|
|
|
|
|
<< ")";
|
2026-06-12 15:15:41 +02:00
|
|
|
}
|
2026-06-13 09:45:26 +02:00
|
|
|
iconstp->v3warn(CASEOVERLAP,
|
|
|
|
|
"Case conditions overlap"
|
|
|
|
|
<< examplePattern.str() << "\n"
|
|
|
|
|
<< iconstp->warnContextPrimary() << '\n'
|
|
|
|
|
<< firstOverlapConstp->warnOther()
|
|
|
|
|
<< "... Location of overlapping condition\n"
|
|
|
|
|
<< firstOverlapConstp->warnContextSecondary());
|
|
|
|
|
reportedOverlap = true;
|
2023-09-14 13:22:49 +02:00
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2022-12-01 01:42:21 +01:00
|
|
|
|
2026-06-12 15:15:41 +02:00
|
|
|
// If there was no default, check exhaustiveness
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// Records now valid
|
|
|
|
|
m_caseDetailsValid = true;
|
|
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
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
|
2026-06-19 20:46:13 +02:00
|
|
|
const size_t alignedEntryWidth = VL_WORDS_I(m_caseDecoderEntryWidth) * VL_EDATASIZE;
|
|
|
|
|
if (fitsLimit(alignedEntryWidth, CASE_TABLE_MAX_BITS)) {
|
|
|
|
|
m_caseTableWidth = alignedEntryWidth << caseWidth; // Can optimize
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-19 20:46:13 +02:00
|
|
|
// Can optimize as AstMatchMasked, no other info needed
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// Analyze case statement. Updates 'm_case*' members. Reports warnings.
|
|
|
|
|
void analyzeCase(AstCase* nodep) {
|
|
|
|
|
// Reset all analysis results
|
|
|
|
|
m_caseOpaque = false;
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
m_caseHasDefault = false;
|
|
|
|
|
m_caseNCaseItems = 0;
|
2026-06-13 09:45:26 +02:00
|
|
|
m_caseNConditions = 0;
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
m_caseDecoderRecords.clear();
|
|
|
|
|
m_caseDecoderEntryWidth = 0;
|
|
|
|
|
m_caseTableWidth = 0;
|
|
|
|
|
m_caseLhsRecords.clear();
|
2026-06-13 09:45:26 +02:00
|
|
|
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;
|
|
|
|
|
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
// 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;
|
2026-06-13 09:45:26 +02:00
|
|
|
for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) {
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
// Check conditions
|
2026-06-13 09:45:26 +02:00
|
|
|
for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) {
|
2026-06-19 20:46:13 +02:00
|
|
|
// Count conditions that can actually match.
|
|
|
|
|
if (!neverItem(nodep, VN_AS(condp, NodeExpr))) ++m_caseNConditions;
|
2026-06-13 09:45:26 +02:00
|
|
|
// Mark opaque if non-constant condition
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
if (!VN_IS(condp, Const)) {
|
|
|
|
|
m_caseOpaque = true;
|
|
|
|
|
canBeDecoder = false; // Can't be a decoder if opaque
|
|
|
|
|
}
|
2026-06-13 09:45:26 +02:00
|
|
|
}
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
// 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);
|
2019-12-23 13:47:57 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// 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);
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
|
|
|
|
|
// Check if it actually fits a full decoder pattern
|
|
|
|
|
if (canBeDecoder) analyzeDecoderPattern(nodep);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-19 20:46:13 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
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();
|
|
|
|
|
|
2026-06-19 20:46:13 +02:00
|
|
|
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;
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
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) {
|
2026-06-19 20:46:13 +02:00
|
|
|
const uint32_t tableOffset = index * entryWidth + lhsOffset;
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
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) {
|
2026-06-19 20:46:13 +02:00
|
|
|
const uint32_t tableOffset = index * entryWidth + lhsOffset;
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
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;
|
2026-06-19 20:46:13 +02:00
|
|
|
const uint32_t tableOffset = index * entryWidth + lhsOffset;
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
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* {
|
2026-06-19 20:46:13 +02:00
|
|
|
if (isTinyTable) return nullptr;
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
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};
|
2026-06-19 20:46:13 +02:00
|
|
|
AstNodeExpr* const scalep = new AstConst{flp, entryWidth};
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
AstNodeExpr* const tableLsbp = new AstMul{flp, scalep, caseExprp};
|
2026-06-19 20:46:13 +02:00
|
|
|
AstNodeExpr* const tableSelp
|
|
|
|
|
= new AstSel{flp, tableRefp, tableLsbp, static_cast<int>(m_caseDecoderEntryWidth)};
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
|
2026-06-19 20:46:13 +02:00
|
|
|
// 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());
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-19 20:46:13 +02:00
|
|
|
// 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;
|
|
|
|
|
}
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
|
2026-06-19 20:46:13 +02:00
|
|
|
// 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);
|
|
|
|
|
}
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
|
2026-06-19 20:46:13 +02:00
|
|
|
// 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());
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
}
|
2026-06-19 20:46:13 +02:00
|
|
|
tablep->addIndexValuep(i, entryp);
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
}
|
2026-06-19 20:46:13 +02:00
|
|
|
|
|
|
|
|
// 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");
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-12 15:15:41 +02:00
|
|
|
// TODO: should return AstNodeStmt after #6280
|
2026-06-13 09:45:26 +02:00
|
|
|
AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const {
|
2026-06-12 15:15:41 +02:00
|
|
|
// 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.
|
2026-06-13 09:45:26 +02:00
|
|
|
if (msb < 0) return m_caseDetails.records[upperValue].stmtsp;
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// 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);
|
2026-06-13 09:45:26 +02:00
|
|
|
AstNode* tree0p = convertCaseFastRecurse(cexprp, msb - 1, upperValue0);
|
|
|
|
|
AstNode* tree1p = convertCaseFastRecurse(cexprp, msb - 1, upperValue1);
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
{
|
2019-05-19 22:13:13 +02:00
|
|
|
bool same = true;
|
2026-06-12 15:15:41 +02:00
|
|
|
for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) {
|
2026-06-13 09:45:26 +02:00
|
|
|
if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) {
|
2020-04-15 13:58:34 +02:00
|
|
|
same = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
|
|
|
|
if (same) {
|
2020-01-17 02:17:11 +01:00
|
|
|
VL_DO_DANGLING(tree1p->deleteTree(), tree1p);
|
2019-05-19 22:13:13 +02:00
|
|
|
return tree0p;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// 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;
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// CASEx(cexpr,....
|
|
|
|
|
// -> tree of IF(msb, IF(msb-1, 11, 10)
|
|
|
|
|
// IF(msb-1, 01, 00))
|
2026-06-12 15:15:41 +02:00
|
|
|
// TODO: should return AstNodeStmt after #6280
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
AstNode* convertCaseFast(AstCase* nodep) {
|
|
|
|
|
++m_stats.caseFast;
|
2026-06-12 15:15:41 +02:00
|
|
|
const int caseWidth = nodep->exprp()->width();
|
2026-06-13 09:45:26 +02:00
|
|
|
AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL);
|
2026-06-12 15:15:41 +02:00
|
|
|
return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp;
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// 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
|
2026-06-12 15:15:41 +02:00
|
|
|
// TODO: should return AstNodeStmt after #6280
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
AstNode* convertCaseGeneric(AstCase* nodep) {
|
|
|
|
|
++m_stats.caseGeneric;
|
2026-06-12 15:15:41 +02:00
|
|
|
// We'll do this in two stages.
|
|
|
|
|
// First stage, convert the conditions to the appropriate IF AND terms.
|
|
|
|
|
bool hasDefault = false;
|
2020-04-15 13:58:34 +02:00
|
|
|
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
2021-10-22 14:56:48 +02:00
|
|
|
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
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;
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// 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'
|
2026-06-16 15:55:24 +02:00
|
|
|
const auto& match = matchPattern(nodep, itemConstp);
|
|
|
|
|
const V3Number& matchMask = match.first;
|
|
|
|
|
const V3Number& matchBits = match.second;
|
2026-06-12 15:15:41 +02:00
|
|
|
VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp);
|
|
|
|
|
return AstEq::newTyped(
|
|
|
|
|
flp, //
|
2026-06-16 15:55:24 +02:00
|
|
|
new AstConst{flp, matchBits},
|
|
|
|
|
new AstAnd{flp, caseExprp, new AstConst{flp, matchMask}});
|
2026-06-12 15:15:41 +02:00
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// 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;
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
// Replace expression in tree. Needs to be non-null, so add a constant false if
|
|
|
|
|
// needed
|
2026-06-12 15:15:41 +02:00
|
|
|
if (!newCondp) newCondp = new AstConst{flp, AstConst::BitFalse{}};
|
|
|
|
|
itemp->addCondsp(newCondp);
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// 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) {
|
2022-11-20 23:40:38 +01:00
|
|
|
nodep->addItemsp(new AstCaseItem{
|
|
|
|
|
nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr});
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
|
2019-05-19 22:13:13 +02:00
|
|
|
// Now build the IF statement tree
|
2026-06-12 15:15:41 +02:00
|
|
|
// The tree can be quite huge. Pull every group of 8 out, and make a OR tree.
|
2019-05-19 22:13:13 +02:00
|
|
|
// 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;
|
2025-10-17 15:00:32 +02:00
|
|
|
AstIf* grouprootp = nullptr;
|
2020-08-15 16:12:55 +02:00
|
|
|
AstIf* groupnextp = nullptr;
|
|
|
|
|
AstIf* itemnextp = nullptr;
|
2020-04-15 13:58:34 +02:00
|
|
|
for (AstCaseItem* itemp = nodep->itemsp(); itemp;
|
2021-10-22 14:56:48 +02:00
|
|
|
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
// Grab the statements from this item. May be empty.
|
|
|
|
|
AstNode* const istmtsp = itemp->stmtsp();
|
2019-05-19 22:13:13 +02:00
|
|
|
if (istmtsp) istmtsp->unlinkFrBackWithNext();
|
2026-06-12 15:15:41 +02:00
|
|
|
|
2019-05-19 22:13:13 +02:00
|
|
|
// Expressioned clause
|
2022-11-13 21:33:11 +01:00
|
|
|
AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack();
|
2020-04-15 13:58:34 +02:00
|
|
|
{ // Prepare for next group
|
2019-05-19 22:13:13 +02:00
|
|
|
if (++depth > CASE_ENCODER_GROUP_DEPTH) depth = 1;
|
|
|
|
|
if (depth == 1) { // First group or starting new group
|
2020-08-15 16:12:55 +02:00
|
|
|
itemnextp = nullptr;
|
2023-09-17 04:50:54 +02:00
|
|
|
AstIf* const newp = new AstIf{itemp->fileline(), ifexprp->cloneTreePure(true)};
|
2020-04-15 13:58:34 +02:00
|
|
|
if (groupnextp) {
|
|
|
|
|
groupnextp->addElsesp(newp);
|
|
|
|
|
} else {
|
|
|
|
|
grouprootp = newp;
|
|
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
groupnextp = newp;
|
|
|
|
|
} else { // Continue group, modify if condition to OR in this new condition
|
2022-11-13 21:33:11 +01:00
|
|
|
AstNodeExpr* const condp = groupnextp->condp()->unlinkFrBack();
|
2020-04-15 13:58:34 +02:00
|
|
|
groupnextp->condp(
|
2023-09-17 04:50:54 +02:00
|
|
|
new AstOr{ifexprp->fileline(), condp, ifexprp->cloneTreePure(true)});
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-04-15 13:58:34 +02:00
|
|
|
{ // Make the new lower IF and attach in the tree
|
2022-11-13 21:33:11 +01:00
|
|
|
AstNodeExpr* itemexprp = ifexprp;
|
2020-04-15 13:58:34 +02:00
|
|
|
VL_DANGLING(ifexprp);
|
|
|
|
|
if (depth == CASE_ENCODER_GROUP_DEPTH) { // End of group - can skip the condition
|
2020-01-17 02:17:11 +01:00
|
|
|
VL_DO_DANGLING(itemexprp->deleteTree(), itemexprp);
|
2022-11-20 23:40:38 +01:00
|
|
|
itemexprp = new AstConst{itemp->fileline(), AstConst::BitTrue{}};
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2022-11-20 23:40:38 +01:00
|
|
|
AstIf* const newp = new AstIf{itemp->fileline(), itemexprp, istmtsp};
|
2020-04-15 13:58:34 +02:00
|
|
|
if (itemnextp) {
|
|
|
|
|
itemnextp->addElsesp(newp);
|
|
|
|
|
} else {
|
2022-09-15 20:43:56 +02:00
|
|
|
groupnextp->addThensp(newp); // First in a new group
|
2020-04-15 13:58:34 +02:00
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
itemnextp = newp;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
return grouprootp;
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// Convert the given case statement to a representation not using AstCase
|
|
|
|
|
// TODO: should return AstNodeStmt after #6280
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
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);
|
|
|
|
|
|
2026-06-19 20:46:13 +02:00
|
|
|
// 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);
|
|
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// Determine if we should use the fast bitwise branching tree method
|
|
|
|
|
const bool useFastBitTree = [&]() {
|
|
|
|
|
// Not if disabled
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
if (!v3Global.opt.fCaseTree()) return false;
|
2026-06-13 09:45:26 +02:00
|
|
|
// 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;
|
|
|
|
|
}();
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
if (useFastBitTree) return convertCaseFast(nodep);
|
2026-06-13 09:45:26 +02:00
|
|
|
|
|
|
|
|
// 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);
|
2008-07-22 19:07:19 +02:00
|
|
|
}
|
|
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
// VISITORS
|
2022-09-16 12:22:11 +02:00
|
|
|
void visit(AstCase* nodep) override {
|
2026-06-12 07:38:21 +02:00
|
|
|
UASSERT_OBJ(nodep->exprp()->isPure(), nodep,
|
|
|
|
|
"Impure case expression should have been removed by V3LiftExpr");
|
2026-06-12 15:15:41 +02:00
|
|
|
|
|
|
|
|
CaseLintVisitor::apply(nodep);
|
|
|
|
|
|
|
|
|
|
// Convert any children first
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// Analyze this case statement
|
|
|
|
|
analyzeCase(nodep);
|
2026-06-12 15:15:41 +02:00
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// Take the 'notParallelp' statements under the case statement created by V3Assert.
|
2026-06-12 15:15:41 +02:00
|
|
|
// 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.
|
2026-06-13 09:45:26 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2019-05-19 22:13:13 +02:00
|
|
|
}
|
2026-06-12 15:15:41 +02:00
|
|
|
|
2026-06-13 09:45:26 +02:00
|
|
|
// Convert the case statement and replace the original
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
if (AstNode* const replacementp = convertCase(nodep)) {
|
2026-06-12 15:15:41 +02:00
|
|
|
nodep->replaceWith(replacementp);
|
|
|
|
|
} else {
|
|
|
|
|
nodep->unlinkFrBack();
|
|
|
|
|
}
|
|
|
|
|
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
|
|
|
|
|
void visit(AstScope* nodep) override {
|
|
|
|
|
VL_RESTORER(m_scopep);
|
|
|
|
|
m_scopep = nodep;
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-14 13:22:49 +02:00
|
|
|
void visit(AstAlways* nodep) override {
|
2025-04-03 05:13:43 +02:00
|
|
|
VL_RESTORER(m_alwaysp);
|
2023-09-14 13:22:49 +02:00
|
|
|
m_alwaysp = nodep;
|
2021-01-05 20:26:01 +01:00
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
2023-09-14 13:22:49 +02:00
|
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
2006-08-26 13:35:28 +02:00
|
|
|
|
|
|
|
|
public:
|
2019-09-12 13:22:22 +02:00
|
|
|
// CONSTRUCTORS
|
2026-06-12 15:15:41 +02:00
|
|
|
explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); }
|
2022-09-16 12:22:11 +02:00
|
|
|
~CaseVisitor() override {
|
2026-06-19 20:46:13 +02:00
|
|
|
V3Stats::addStat("Optimizations, Cases decoder", m_stats.caseDecoder);
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
V3Stats::addStat("Optimizations, Cases table normal", m_stats.caseTableNormal);
|
|
|
|
|
V3Stats::addStat("Optimizations, Cases table tiny", m_stats.caseTableTiny);
|
2026-06-13 09:45:26 +02:00
|
|
|
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);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
Optimize decoder case statements into lookup tables (#7795)
Recognize "decoder" case statements (where every case item only assigns
constants to a fixed set of left-hand sides) and replace them with a
single packed constant lookup table indexed by the case expression.
Small tables are materialized inline in the generated code, and are
always optimized. Larger ones are placed in the constant pool and only
optimized if deemed beneficial over branches.
While this slightly conflicts with V3Table, and is not worth that much
on it's own, there will be a follow up patch that converts more cases of
this form which will be much more valuable. This patch does the
necessary analysis and the simple table conversion when possible.
Split -fcase into -fcase-table (this new conversion) and -fcase-tree (the
existing bitwise branch-tree conversion); -fno-case is now an alias for
both.
Default branches, assignments preceding the case (used as default values),
casez wildcards, multiple and partial left-hand sides, and both blocking and
non-blocking assignments are handled. Cases that cannot be safely tabled (e.g.
non-exhaustive with no default, overlapping writes to one variable, or mixed
blocking/non-blocking assignments) fall back to the existing if/else lowering.
Consequently disabled re-inlining of constant pool variables in V3Const,
and rebuild the constant pool hash in V3Dead (previously we didn't
create constant pool entries early enough for this to matter)
2026-06-18 10:30:50 +02:00
|
|
|
size_t CaseVisitor::LhsRecord::s_nextId = 0;
|
|
|
|
|
|
2006-08-26 13:35:28 +02:00
|
|
|
//######################################################################
|
|
|
|
|
// Case class functions
|
|
|
|
|
|
|
|
|
|
void V3Case::caseAll(AstNetlist* nodep) {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(2, __FUNCTION__ << ":");
|
2021-11-26 16:52:36 +01:00
|
|
|
{ CaseVisitor{nodep}; } // Destruct before checking
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("case", 0, dumpTreeEitherLevel() >= 3);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|
2025-09-23 20:49:01 +02:00
|
|
|
void V3Case::caseLint(AstGenCase* nodep) {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(4, __FUNCTION__ << ": ");
|
2026-06-12 15:15:41 +02:00
|
|
|
CaseLintVisitor::apply(nodep);
|
2006-08-26 13:35:28 +02:00
|
|
|
}
|