[llvm] [SelectionDAG] Order operand lists by use frequency (PR #202637)
David Zbarsky via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 9 07:00:40 PDT 2026
https://github.com/dzbarsky created https://github.com/llvm/llvm-project/pull/202637
DAGISelMatcherEmitter pools emitted-node operand sequences and stores VBR-encoded offsets in MatcherTable. Extend SequenceToOffsetTable::layout with a stable caller-supplied ordering and rank retained operand sequences by the total use count of their suffixes. The OperandLists wire format and SelectionDAG interpreter are unchanged.
Across generated AArch64, AMDGPU, ARM, Mips, WebAssembly, and X86 matcher tables, frequency ordering reduces VBR offsets by 25,533 bytes (1.14%). The OperandLists arrays retain the same size, and decoding every generated operand-list reference produces the same sequence before and after the change.
Validate 16 positive DAG isel TableGen invocations with 20 FileCheck configurations, 10 expected-failure tests, the focused dag-isel-operand-list-ordering.td test, and compilation of all SequenceToOffsetTable users. git diff --check passes.
Work towards #202616
>From f435cffd265e6ef44f008406237c2d2f23087826 Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Mon, 8 Jun 2026 20:39:08 -0400
Subject: [PATCH] [SelectionDAG] Order operand lists by use frequency
DAGISelMatcherEmitter pools emitted-node operand sequences and stores VBR-encoded offsets in MatcherTable. Extend SequenceToOffsetTable::layout with a stable caller-supplied ordering and rank retained operand sequences by the total use count of their suffixes. The OperandLists wire format and SelectionDAG interpreter are unchanged.
Across generated AArch64, AMDGPU, ARM, Mips, WebAssembly, and X86 matcher tables, frequency ordering reduces VBR offsets by 25,533 bytes (1.14%). The OperandLists arrays retain the same size, and decoding every generated operand-list reference produces the same sequence before and after the change.
Validate 16 positive DAG isel TableGen invocations with 20 FileCheck configurations, 10 expected-failure tests, the focused dag-isel-operand-list-ordering.td test, and compilation of all SequenceToOffsetTable users. git diff --check passes.
---
.../dag-isel-operand-list-ordering.td | 43 +++++++++++++++++++
.../TableGen/Basic/SequenceToOffsetTable.h | 42 +++++++++++++++---
llvm/utils/TableGen/DAGISelMatcherEmitter.cpp | 21 ++++++++-
3 files changed, 99 insertions(+), 7 deletions(-)
create mode 100644 llvm/test/TableGen/dag-isel-operand-list-ordering.td
diff --git a/llvm/test/TableGen/dag-isel-operand-list-ordering.td b/llvm/test/TableGen/dag-isel-operand-list-ordering.td
new file mode 100644
index 0000000000000..ef5bb4721e94a
--- /dev/null
+++ b/llvm/test/TableGen/dag-isel-operand-list-ordering.td
@@ -0,0 +1,43 @@
+// RUN: llvm-tblgen -gen-dag-isel -I %p/../../include %s -o %t
+// RUN: FileCheck %s < %t
+
+include "llvm/Target/Target.td"
+
+def TestInstrInfo : InstrInfo;
+def TestTarget : Target {
+ let InstructionSet = TestInstrInfo;
+}
+
+class TestReg<int Index> : Register<"R"#Index, []>;
+foreach I = 0...3 in
+ def "R"#I : TestReg<I>;
+
+def Reg : RegisterClass<"TestTarget", [i32], 32, (sequence "R%d", 0, 3)>;
+
+class TestInstruction<string Name> : Instruction {
+ let Namespace = "Test";
+ let OutOperandList = (outs Reg:$dst);
+ let InOperandList = (ins Reg:$src0, Reg:$src1);
+ let AsmString = Name;
+}
+
+def CommonAdd : TestInstruction<"common-add">;
+def CommonSub : TestInstruction<"common-sub">;
+def RareMul : TestInstruction<"rare-mul">;
+
+def : Pat<(add i32:$x, i32:$y), (CommonAdd Reg:$x, Reg:$y)>;
+def : Pat<(sub i32:$x, i32:$y), (CommonSub Reg:$x, Reg:$y)>;
+def : Pat<(mul i32:$x, i32:$y), (RareMul Reg:$y, Reg:$x)>;
+
+// The reverse-lexicographical suffix order places (1, 0) before (0, 1).
+// Frequency ordering instead gives the twice-used (0, 1) sequence offset 0.
+// CHECK: OPC_MorphNodeTo1None, TARGET_VAL(Test::CommonAdd),
+// CHECK-NEXT: MVT::i32, 2/*#Ops*/, /*OperandList*/0,
+// CHECK: OPC_MorphNodeTo1None, TARGET_VAL(Test::CommonSub),
+// CHECK-NEXT: MVT::i32, 2/*#Ops*/, /*OperandList*/0,
+// CHECK: OPC_MorphNodeTo1None, TARGET_VAL(Test::RareMul),
+// CHECK-NEXT: MVT::i32, 2/*#Ops*/, /*OperandList*/2,
+
+// CHECK-LABEL: static const uint8_t OperandLists[] = {
+// CHECK-NEXT: /* 0 */ 0, 1,
+// CHECK-NEXT: /* 2 */ 1, 0,
diff --git a/llvm/utils/TableGen/Basic/SequenceToOffsetTable.h b/llvm/utils/TableGen/Basic/SequenceToOffsetTable.h
index 0796a24df162a..8e6d70730ee21 100644
--- a/llvm/utils/TableGen/Basic/SequenceToOffsetTable.h
+++ b/llvm/utils/TableGen/Basic/SequenceToOffsetTable.h
@@ -21,6 +21,7 @@
#include <cassert>
#include <functional>
#include <map>
+#include <vector>
namespace llvm {
@@ -59,6 +60,7 @@ class SequenceToOffsetTable {
// Keep sequences ordered according to SeqLess so suffixes are easy to find.
// Map each sequence to its offset in the table.
using SeqMap = std::map<SeqT, unsigned, SeqLess>;
+ using SeqMapEntry = typename SeqMap::value_type;
// Sequences added so far, with suffixes removed.
SeqMap Seqs;
@@ -77,6 +79,18 @@ class SequenceToOffsetTable {
return A.size() <= B.size() && std::equal(A.rbegin(), A.rend(), B.rbegin());
}
+ std::vector<const SeqMapEntry *> getLayout() const {
+ std::vector<const SeqMapEntry *> Layout;
+ Layout.reserve(Seqs.size());
+ for (const auto &Entry : Seqs)
+ Layout.push_back(&Entry);
+ std::sort(Layout.begin(), Layout.end(),
+ [](const SeqMapEntry *A, const SeqMapEntry *B) {
+ return A->second < B->second;
+ });
+ return Layout;
+ }
+
public:
explicit SequenceToOffsetTable(std::optional<ElemT> Terminator = ElemT())
: Terminator(Terminator) {}
@@ -108,14 +122,28 @@ class SequenceToOffsetTable {
/// layout - Computes the final table layout.
void layout() {
+ layout([](const SeqT &, const SeqT &) { return false; });
+ }
+
+ /// layout - Computes the final table layout using Compare to order the
+ /// retained sequences. Equal sequences retain their original stable order.
+ template <typename Compare> void layout(Compare CompareSequences) {
assert(!IsLaidOut && "Can only call layout() once");
IsLaidOut = true;
- // Lay out the table in Seqs iteration order.
- for (auto &[Seq, Offset] : Seqs) {
- Offset = Size;
+ std::vector<SeqMapEntry *> Layout;
+ Layout.reserve(Seqs.size());
+ for (auto &Entry : Seqs)
+ Layout.push_back(&Entry);
+ std::stable_sort(Layout.begin(), Layout.end(),
+ [&](const SeqMapEntry *A, const SeqMapEntry *B) {
+ return CompareSequences(A->first, B->first);
+ });
+
+ for (SeqMapEntry *Entry : Layout) {
+ Entry->second = Size;
// Include space for a terminator.
- Size += Seq.size() + Terminator.has_value();
+ Size += Entry->first.size() + Terminator.has_value();
}
}
@@ -146,7 +174,8 @@ class SequenceToOffsetTable {
<< "#pragma GCC diagnostic ignored \"-Woverlength-strings\"\n"
<< "#endif\n"
<< Decl << " = {\n";
- for (const auto &[Seq, Offset] : Seqs) {
+ for (const SeqMapEntry *Entry : getLayout()) {
+ const auto &[Seq, Offset] = *Entry;
OS << " /* " << Offset << " */ \"";
OS.write_escaped(Seq);
if (Terminator)
@@ -164,7 +193,8 @@ class SequenceToOffsetTable {
void emit(raw_ostream &OS,
function_ref<void(raw_ostream &, ElemT)> Print) const {
assert(IsLaidOut && "Call layout() before emit()");
- for (const auto &[Seq, Offset] : Seqs) {
+ for (const SeqMapEntry *Entry : getLayout()) {
+ const auto &[Seq, Offset] = *Entry;
OS << " /* " << Offset << " */ ";
for (const ElemT &Element : Seq) {
Print(OS, Element);
diff --git a/llvm/utils/TableGen/DAGISelMatcherEmitter.cpp b/llvm/utils/TableGen/DAGISelMatcherEmitter.cpp
index 3d69c96ebd900..d76bacaef1056 100644
--- a/llvm/utils/TableGen/DAGISelMatcherEmitter.cpp
+++ b/llvm/utils/TableGen/DAGISelMatcherEmitter.cpp
@@ -77,6 +77,7 @@ class MatcherTableEmitter {
std::map<ValueTypeByHwMode, std::pair<unsigned, unsigned>> ValueTypeMap;
SequenceToOffsetTable<std::vector<uint8_t>> OperandTable;
+ std::map<std::vector<uint8_t>, unsigned> OperandSequenceUsage;
unsigned getPatternIdxFromTable(std::string &&P, std::string &&include_loc) {
const auto [It, Inserted] =
@@ -144,6 +145,7 @@ class MatcherTableEmitter {
for (unsigned i = 0; i < Len; ++i)
OpBytes.push_back(Buffer[i]);
}
+ ++OperandSequenceUsage[OpBytes];
OperandTable.add(OpBytes);
}
}
@@ -152,7 +154,24 @@ class MatcherTableEmitter {
sortValueTypeByHwModeByFrequency();
- OperandTable.layout();
+ // Place frequently referenced sequences first to keep their VBR-encoded
+ // offsets small. Suffixes share storage with the retained sequence that
+ // contains them, so include suffix uses when ranking retained sequences.
+ std::map<std::vector<uint8_t>, unsigned> OperandSequencePriority;
+ for (const auto &[Sequence, Count] : OperandSequenceUsage) {
+ for (const auto &CandidateUsage : OperandSequenceUsage) {
+ const std::vector<uint8_t> &Candidate = CandidateUsage.first;
+ if (Sequence.size() <= Candidate.size() &&
+ std::equal(Sequence.rbegin(), Sequence.rend(), Candidate.rbegin()))
+ OperandSequencePriority[Candidate] += Count;
+ }
+ }
+ OperandTable.layout(
+ [&](const std::vector<uint8_t> &A, const std::vector<uint8_t> &B) {
+ unsigned AUses = OperandSequencePriority.at(A);
+ unsigned BUses = OperandSequencePriority.at(B);
+ return AUses > BUses;
+ });
// Sort ComplexPatterns by usage.
std::vector<std::pair<const ComplexPattern *, unsigned>> ComplexPatternList(
More information about the llvm-commits
mailing list