[llvm] d24f20a - [X86] Pool sparse disassembler opcode rows (#202666)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 3 09:49:51 PDT 2026
Author: David Zbarsky
Date: 2026-07-03T09:49:47-07:00
New Revision: d24f20ab649a28dd48b09c0bebc1f255e7ebf909
URL: https://github.com/llvm/llvm-project/commit/d24f20ab649a28dd48b09c0bebc1f255e7ebf909
DIFF: https://github.com/llvm/llvm-project/commit/d24f20ab649a28dd48b09c0bebc1f255e7ebf909.diff
LOG: [X86] Pool sparse disassembler opcode rows (#202666)
The XOP, 3DNow, and VEX/EVEX map 4-7 decoder tables contain 1,760
context rows but only 266 distinct values. Keep the four common opcode
maps as direct tables and intern rows only for these eight sparse maps.
This reduces fully stripped arm64 llvm-mc from 8,333,344 to 6,797,728
bytes, saving 1,535,616 bytes (18.43%). X86Disassembler.cpp.o decreases
by 1,525,328 bytes (52.36%); constant data falls by 1,526,328 bytes
while text grows by 848 bytes. The stripped all-tools multicall binary
decreases from 145,103,200 to 143,567,584 bytes, saving 1,535,616 bytes
(1.058%).
Work towards #202616
AI tool disclosure: Co-authored with OpenAI Codex.
Added:
Modified:
llvm/include/llvm/Support/X86DisassemblerDecoderCommon.h
llvm/lib/Target/X86/Disassembler/X86Disassembler.cpp
llvm/utils/TableGen/DisassemblerEmitter.cpp
llvm/utils/TableGen/X86DisassemblerTables.cpp
llvm/utils/TableGen/X86DisassemblerTables.h
Removed:
################################################################################
diff --git a/llvm/include/llvm/Support/X86DisassemblerDecoderCommon.h b/llvm/include/llvm/Support/X86DisassemblerDecoderCommon.h
index 6f6f65dc075f3..199c3b17b7f22 100644
--- a/llvm/include/llvm/Support/X86DisassemblerDecoderCommon.h
+++ b/llvm/include/llvm/Support/X86DisassemblerDecoderCommon.h
@@ -26,14 +26,9 @@ namespace llvm::X86Disassembler {
#define TWOBYTE_SYM x86DisassemblerTwoByteOpcodes
#define THREEBYTE38_SYM x86DisassemblerThreeByte38Opcodes
#define THREEBYTE3A_SYM x86DisassemblerThreeByte3AOpcodes
-#define XOP8_MAP_SYM x86DisassemblerXOP8Opcodes
-#define XOP9_MAP_SYM x86DisassemblerXOP9Opcodes
-#define XOPA_MAP_SYM x86DisassemblerXOPAOpcodes
-#define THREEDNOW_MAP_SYM x86Disassembler3DNowOpcodes
-#define MAP4_SYM x86DisassemblerMap4Opcodes
-#define MAP5_SYM x86DisassemblerMap5Opcodes
-#define MAP6_SYM x86DisassemblerMap6Opcodes
-#define MAP7_SYM x86DisassemblerMap7Opcodes
+#define SPARSE_OPCODE_DECISIONS_SYM x86DisassemblerSparseOpcodeDecisions
+#define SPARSE_OPCODE_DECISION_INDICES_SYM \
+ x86DisassemblerSparseOpcodeDecisionIndices
#define INSTRUCTIONS_STR "x86DisassemblerInstrSpecifiers"
#define CONTEXTS_STR "x86DisassemblerContexts"
@@ -41,14 +36,9 @@ namespace llvm::X86Disassembler {
#define TWOBYTE_STR "x86DisassemblerTwoByteOpcodes"
#define THREEBYTE38_STR "x86DisassemblerThreeByte38Opcodes"
#define THREEBYTE3A_STR "x86DisassemblerThreeByte3AOpcodes"
-#define XOP8_MAP_STR "x86DisassemblerXOP8Opcodes"
-#define XOP9_MAP_STR "x86DisassemblerXOP9Opcodes"
-#define XOPA_MAP_STR "x86DisassemblerXOPAOpcodes"
-#define THREEDNOW_MAP_STR "x86Disassembler3DNowOpcodes"
-#define MAP4_STR "x86DisassemblerMap4Opcodes"
-#define MAP5_STR "x86DisassemblerMap5Opcodes"
-#define MAP6_STR "x86DisassemblerMap6Opcodes"
-#define MAP7_STR "x86DisassemblerMap7Opcodes"
+#define SPARSE_OPCODE_DECISIONS_STR "x86DisassemblerSparseOpcodeDecisions"
+#define SPARSE_OPCODE_DECISION_INDICES_STR \
+ "x86DisassemblerSparseOpcodeDecisionIndices"
// Attributes of an instruction that must be known before the opcode can be
// processed correctly. Most of these indicate the presence of particular
diff --git a/llvm/lib/Target/X86/Disassembler/X86Disassembler.cpp b/llvm/lib/Target/X86/Disassembler/X86Disassembler.cpp
index 7d2b5eb900133..d499dc43dcab6 100644
--- a/llvm/lib/Target/X86/Disassembler/X86Disassembler.cpp
+++ b/llvm/lib/Target/X86/Disassembler/X86Disassembler.cpp
@@ -85,6 +85,7 @@
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/raw_ostream.h"
@@ -121,70 +122,54 @@ struct ContextDecision {
#include "X86GenDisassemblerTables.inc"
-static InstrUID decode(OpcodeType type, InstructionContext insnContext,
- uint8_t opcode, uint8_t modRM) {
- const struct ModRMDecision *dec;
-
- switch (type) {
+static const ModRMDecision &
+getDecision(OpcodeType Type, InstructionContext Context, uint8_t Opcode) {
+ const OpcodeDecision *Decision;
+ switch (Type) {
case ONEBYTE:
- dec = &ONEBYTE_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
+ Decision = &ONEBYTE_SYM.opcodeDecisions[Context];
break;
case TWOBYTE:
- dec = &TWOBYTE_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
+ Decision = &TWOBYTE_SYM.opcodeDecisions[Context];
break;
case THREEBYTE_38:
- dec = &THREEBYTE38_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
+ Decision = &THREEBYTE38_SYM.opcodeDecisions[Context];
break;
case THREEBYTE_3A:
- dec = &THREEBYTE3A_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
- break;
- case XOP8_MAP:
- dec = &XOP8_MAP_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
- break;
- case XOP9_MAP:
- dec = &XOP9_MAP_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
- break;
- case XOPA_MAP:
- dec = &XOPA_MAP_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
- break;
- case THREEDNOW_MAP:
- dec =
- &THREEDNOW_MAP_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
- break;
- case MAP4:
- dec = &MAP4_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
+ Decision = &THREEBYTE3A_SYM.opcodeDecisions[Context];
break;
- case MAP5:
- dec = &MAP5_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
- break;
- case MAP6:
- dec = &MAP6_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
- break;
- case MAP7:
- dec = &MAP7_SYM.opcodeDecisions[insnContext].modRMDecisions[opcode];
+ default:
+ static_assert(XOP8_MAP == 4 && MAP7 == 11);
+ unsigned DecisionIndex =
+ SPARSE_OPCODE_DECISION_INDICES_SYM[Type - XOP8_MAP][Context];
+ Decision = &SPARSE_OPCODE_DECISIONS_SYM[DecisionIndex];
break;
}
+ return Decision->modRMDecisions[Opcode];
+}
- switch (dec->modrm_type) {
+static LLVM_ATTRIBUTE_NOINLINE InstrUID
+decodeModRM(const ModRMDecision &Decision, uint8_t ModRM) {
+ switch (Decision.modrm_type) {
default:
llvm_unreachable("Corrupt table! Unknown modrm_type");
return 0;
case MODRM_ONEENTRY:
- return modRMTable[dec->instructionIDs];
+ llvm_unreachable("MODRM_ONEENTRY does not require a ModR/M byte");
case MODRM_SPLITRM:
- if (modFromModRM(modRM) == 0x3)
- return modRMTable[dec->instructionIDs + 1];
- return modRMTable[dec->instructionIDs];
+ if (modFromModRM(ModRM) == 0x3)
+ return modRMTable[Decision.instructionIDs + 1];
+ return modRMTable[Decision.instructionIDs];
case MODRM_SPLITREG:
- if (modFromModRM(modRM) == 0x3)
- return modRMTable[dec->instructionIDs + ((modRM & 0x38) >> 3) + 8];
- return modRMTable[dec->instructionIDs + ((modRM & 0x38) >> 3)];
+ if (modFromModRM(ModRM) == 0x3)
+ return modRMTable[Decision.instructionIDs + ((ModRM & 0x38) >> 3) + 8];
+ return modRMTable[Decision.instructionIDs + ((ModRM & 0x38) >> 3)];
case MODRM_SPLITMISC:
- if (modFromModRM(modRM) == 0x3)
- return modRMTable[dec->instructionIDs + (modRM & 0x3f) + 8];
- return modRMTable[dec->instructionIDs + ((modRM & 0x38) >> 3)];
+ if (modFromModRM(ModRM) == 0x3)
+ return modRMTable[Decision.instructionIDs + (ModRM & 0x3f) + 8];
+ return modRMTable[Decision.instructionIDs + ((ModRM & 0x38) >> 3)];
case MODRM_FULL:
- return modRMTable[dec->instructionIDs + modRM];
+ return modRMTable[Decision.instructionIDs + ModRM];
}
}
@@ -1094,55 +1079,15 @@ static int getInstructionIDWithAttrMask(uint16_t *instructionID,
struct InternalInstruction *insn,
uint16_t attrMask) {
auto insnCtx = InstructionContext(x86DisassemblerContexts[attrMask]);
- const ContextDecision *decision;
- switch (insn->opcodeType) {
- case ONEBYTE:
- decision = &ONEBYTE_SYM;
- break;
- case TWOBYTE:
- decision = &TWOBYTE_SYM;
- break;
- case THREEBYTE_38:
- decision = &THREEBYTE38_SYM;
- break;
- case THREEBYTE_3A:
- decision = &THREEBYTE3A_SYM;
- break;
- case XOP8_MAP:
- decision = &XOP8_MAP_SYM;
- break;
- case XOP9_MAP:
- decision = &XOP9_MAP_SYM;
- break;
- case XOPA_MAP:
- decision = &XOPA_MAP_SYM;
- break;
- case THREEDNOW_MAP:
- decision = &THREEDNOW_MAP_SYM;
- break;
- case MAP4:
- decision = &MAP4_SYM;
- break;
- case MAP5:
- decision = &MAP5_SYM;
- break;
- case MAP6:
- decision = &MAP6_SYM;
- break;
- case MAP7:
- decision = &MAP7_SYM;
- break;
- }
+ const ModRMDecision &Decision =
+ getDecision(insn->opcodeType, insnCtx, insn->opcode);
- if (decision->opcodeDecisions[insnCtx]
- .modRMDecisions[insn->opcode]
- .modrm_type != MODRM_ONEENTRY) {
+ if (Decision.modrm_type != MODRM_ONEENTRY) {
if (readModRM(insn))
return -1;
- *instructionID =
- decode(insn->opcodeType, insnCtx, insn->opcode, insn->modRM);
+ *instructionID = decodeModRM(Decision, insn->modRM);
} else {
- *instructionID = decode(insn->opcodeType, insnCtx, insn->opcode, 0);
+ *instructionID = modRMTable[Decision.instructionIDs];
}
return 0;
diff --git a/llvm/utils/TableGen/DisassemblerEmitter.cpp b/llvm/utils/TableGen/DisassemblerEmitter.cpp
index 5bc259a5fc8a4..8b505653752bd 100644
--- a/llvm/utils/TableGen/DisassemblerEmitter.cpp
+++ b/llvm/utils/TableGen/DisassemblerEmitter.cpp
@@ -41,12 +41,13 @@ using namespace llvm::X86Disassembler;
/// all cases as a 64-bit instruction with only OPSIZE set. (The XS prefix
/// may have effects on its execution, but does not change the instruction
/// returned.) This allows considerable space savings in other tables.
-/// - Six tables (ONEBYTE_SYM, TWOBYTE_SYM, THREEBYTE38_SYM, THREEBYTE3A_SYM,
-/// THREEBYTEA6_SYM, and THREEBYTEA7_SYM contain the hierarchy that the
-/// decoder traverses while decoding an instruction. At the lowest level of
-/// this hierarchy are instruction UIDs, 16-bit integers that can be used to
-/// uniquely identify the instruction and correspond exactly to its position
-/// in the list of CodeGenInstructions for the target.
+/// - The common opcode maps use direct context tables. The remaining sparse
+/// maps share unique rows in SPARSE_OPCODE_DECISIONS_SYM, with
+/// SPARSE_OPCODE_DECISION_INDICES_SYM mapping each sparse map and instruction
+/// context to a row. At the lowest level of this hierarchy are instruction
+/// UIDs, 16-bit integers that can be used to uniquely identify the
+/// instruction and correspond exactly to its position in the list of
+/// CodeGenInstructions for the target.
/// - One table (INSTRUCTIONS_SYM) contains information about the operands of
/// each instruction and how to decode them.
///
diff --git a/llvm/utils/TableGen/X86DisassemblerTables.cpp b/llvm/utils/TableGen/X86DisassemblerTables.cpp
index 483fc58fcfba4..35ae89d1acc1f 100644
--- a/llvm/utils/TableGen/X86DisassemblerTables.cpp
+++ b/llvm/utils/TableGen/X86DisassemblerTables.cpp
@@ -20,6 +20,7 @@
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/raw_ostream.h"
+#include <array>
#include <map>
using namespace llvm;
@@ -641,7 +642,7 @@ static inline bool outranks(InstructionContext upper,
///
/// @param decision - The decision to be compacted.
/// @return - The compactest available representation for the decision.
-static ModRMDecisionType getDecisionType(ModRMDecision &decision) {
+static ModRMDecisionType getDecisionType(const ModRMDecision &decision) {
bool satisfiesOneEntry = true;
bool satisfiesSplitRM = true;
bool satisfiesSplitReg = true;
@@ -847,7 +848,6 @@ void DisassemblerTables::emitContextDecision(raw_ostream &o1, raw_ostream &o2,
o2.indent(i2) << "/*";
o2 << stringForContext((InstructionContext)index);
o2 << "*/ ";
-
emitOpcodeDecision(o1, o2, i1, i2, ModRMTableNum,
decision.opcodeDecisions[index]);
}
@@ -1052,21 +1052,91 @@ void DisassemblerTables::emitContextTable(raw_ostream &o, unsigned &i) const {
void DisassemblerTables::emitContextDecisions(raw_ostream &o1, raw_ostream &o2,
unsigned &i1, unsigned &i2,
unsigned &ModRMTableNum) const {
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[0], ONEBYTE_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[1], TWOBYTE_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[2],
+ emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[ONEBYTE],
+ ONEBYTE_STR);
+ emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[TWOBYTE],
+ TWOBYTE_STR);
+ emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[THREEBYTE_38],
THREEBYTE38_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[3],
+ emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[THREEBYTE_3A],
THREEBYTE3A_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[4], XOP8_MAP_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[5], XOP9_MAP_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[6], XOPA_MAP_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[7],
- THREEDNOW_MAP_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[8], MAP4_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[9], MAP5_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[10], MAP6_STR);
- emitContextDecision(o1, o2, i1, i2, ModRMTableNum, *Tables[11], MAP7_STR);
+
+ using OpcodeDecisionKey = std::vector<uint16_t>;
+ std::map<OpcodeDecisionKey, unsigned> OpcodeDecisionMap;
+ static_assert(XOP8_MAP == 4 && MAP7 == 11);
+ std::array<std::array<unsigned, IC_max>, MAP7 - XOP8_MAP + 1> Indices;
+
+ o2 << "static const struct OpcodeDecision " SPARSE_OPCODE_DECISIONS_STR
+ "[] = {\n";
+ ++i2;
+
+ for (unsigned TableIndex = XOP8_MAP; TableIndex <= MAP7; ++TableIndex) {
+ for (unsigned ContextIndex = 0; ContextIndex < IC_max; ++ContextIndex) {
+ OpcodeDecision &Decision =
+ Tables[TableIndex]->opcodeDecisions[ContextIndex];
+ OpcodeDecisionKey Key;
+
+ for (const ModRMDecision &ModRM : Decision.modRMDecisions) {
+ ModRMDecisionType Type = getDecisionType(ModRM);
+ Key.push_back(Type);
+
+ switch (Type) {
+ default:
+ llvm_unreachable("Unknown decision type");
+ case MODRM_ONEENTRY:
+ Key.push_back(ModRM.instructionIDs[0]);
+ break;
+ case MODRM_SPLITRM:
+ Key.push_back(ModRM.instructionIDs[0x00]);
+ Key.push_back(ModRM.instructionIDs[0xc0]);
+ break;
+ case MODRM_SPLITREG:
+ for (unsigned Index = 0; Index < 64; Index += 8)
+ Key.push_back(ModRM.instructionIDs[Index]);
+ for (unsigned Index = 0xc0; Index < 256; Index += 8)
+ Key.push_back(ModRM.instructionIDs[Index]);
+ break;
+ case MODRM_SPLITMISC:
+ for (unsigned Index = 0; Index < 64; Index += 8)
+ Key.push_back(ModRM.instructionIDs[Index]);
+ for (unsigned Index = 0xc0; Index < 256; ++Index)
+ Key.push_back(ModRM.instructionIDs[Index]);
+ break;
+ case MODRM_FULL:
+ llvm::append_range(Key, ModRM.instructionIDs);
+ break;
+ }
+ }
+
+ auto [It, Inserted] = OpcodeDecisionMap.try_emplace(
+ std::move(Key), OpcodeDecisionMap.size());
+ Indices[TableIndex - XOP8_MAP][ContextIndex] = It->second;
+ if (Inserted)
+ emitOpcodeDecision(o1, o2, i1, i2, ModRMTableNum, Decision);
+ }
+ }
+
+ --i2;
+ o2 << "};\n\n";
+
+ assert(OpcodeDecisionMap.size() <= UINT16_MAX &&
+ "Too many unique sparse opcode decisions");
+ o2 << "static const uint16_t " SPARSE_OPCODE_DECISION_INDICES_STR
+ "[][IC_max] = {\n";
+ ++i2;
+ for (const auto &Table : Indices) {
+ o2.indent(i2) << "{\n";
+ ++i2;
+ for (auto [ContextIndex, DecisionIndex] : enumerate(Table)) {
+ o2.indent(i2) << DecisionIndex << ", // "
+ << stringForContext(InstructionContext(ContextIndex))
+ << "\n";
+ }
+ --i2;
+ o2.indent(i2) << "},\n";
+ }
+ --i2;
+ o2 << "};\n";
}
void DisassemblerTables::emit(raw_ostream &o) const {
diff --git a/llvm/utils/TableGen/X86DisassemblerTables.h b/llvm/utils/TableGen/X86DisassemblerTables.h
index 325bb572b0587..8fbb0be4f5b11 100644
--- a/llvm/utils/TableGen/X86DisassemblerTables.h
+++ b/llvm/utils/TableGen/X86DisassemblerTables.h
@@ -205,8 +205,8 @@ class DisassemblerTables {
/// @param i - The indent level for use with the stream.
void emitContextTable(raw_ostream &o, uint32_t &i) const;
- /// emitContextDecisions - Prints all four ContextDecision structures using
- /// emitContextDecision().
+ /// emitContextDecisions - Prints direct tables for the common opcode maps,
+ /// then a pool and index table for the remaining sparse maps.
///
/// @param o1 - The output stream to print the ID tables generated by
/// emitModRMDecision() to.
More information about the llvm-commits
mailing list