[llvm] [SelectionDAG] Encode common integer constants in matcher opcodes (PR #202640)

David Zbarsky via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 9 07:00:48 PDT 2026


https://github.com/dzbarsky created https://github.com/llvm/llvm-project/pull/202640

DAGISelMatcherEmitter currently writes an EmitInteger opcode followed by a signed-VBR value for common i32 and i64 constants. Add one-byte opcodes for i32 values -1 through 8 and i64 values -1 through 7. SelectionDAGISel decodes each contiguous opcode range with arithmetic and performs the same getSignedConstant call.

The generated AArch64, AMDGPU, ARM, Mips, WebAssembly, and X86 matcher tables shrink from 2,246,495 to 2,200,010 bytes, saving 46,485 bytes (2.07%). The equivalent LLVM 22 encoding changed mean user CPU for a ten-run AMDGPU instruction-selection workload from 1.829 to 1.828 seconds (-0.055%), and generated AArch64, AMDGPU, RISCV, and X86 assembly remained byte-identical.

Add a compile-time assertion that BuiltinOpcodes still fits uint8_t. Validate DAGDefaultOps.td and dag-isel-subregs.td with llvm-tblgen and FileCheck, and compile DAGISelMatcherEmitter.cpp and SelectionDAGISel.cpp with Clang.

Work towards #202616

>From 4545bb1b96fb307aa1f6e1f6dfba8d88be2eb3a2 Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Mon, 8 Jun 2026 20:31:52 -0400
Subject: [PATCH] [SelectionDAG] Encode common integer constants in matcher
 opcodes

DAGISelMatcherEmitter currently writes an EmitInteger opcode followed by a signed-VBR value for common i32 and i64 constants. Add one-byte opcodes for i32 values -1 through 8 and i64 values -1 through 7. SelectionDAGISel decodes each contiguous opcode range with arithmetic and performs the same getSignedConstant call.

The generated AArch64, AMDGPU, ARM, Mips, WebAssembly, and X86 matcher tables shrink from 2,246,495 to 2,200,010 bytes, saving 46,485 bytes (2.07%). The equivalent LLVM 22 encoding changed mean user CPU for a ten-run AMDGPU instruction-selection workload from 1.829 to 1.828 seconds (-0.055%), and generated AArch64, AMDGPU, RISCV, and X86 assembly remained byte-identical.

Add a compile-time assertion that BuiltinOpcodes still fits uint8_t. Validate DAGDefaultOps.td and dag-isel-subregs.td with llvm-tblgen and FileCheck, and compile DAGISelMatcherEmitter.cpp and SelectionDAGISel.cpp with Clang.
---
 llvm/include/llvm/CodeGen/SelectionDAGISel.h  | 22 ++++++++++++++
 .../CodeGen/SelectionDAG/SelectionDAGISel.cpp | 30 +++++++++++++++++++
 llvm/test/TableGen/DAGDefaultOps.td           |  8 ++---
 llvm/test/TableGen/dag-isel-subregs.td        |  2 +-
 llvm/utils/TableGen/DAGISelMatcherEmitter.cpp | 22 ++++++++++++++
 5 files changed, 79 insertions(+), 5 deletions(-)

diff --git a/llvm/include/llvm/CodeGen/SelectionDAGISel.h b/llvm/include/llvm/CodeGen/SelectionDAGISel.h
index 7b406ef4b4cb0..98f5c9e63e26a 100644
--- a/llvm/include/llvm/CodeGen/SelectionDAGISel.h
+++ b/llvm/include/llvm/CodeGen/SelectionDAGISel.h
@@ -288,6 +288,27 @@ class LLVM_ABI SelectionDAGISel {
     OPC_EmitIntegerI16,
     OPC_EmitIntegerI32,
     OPC_EmitIntegerI64,
+    // Space-optimized forms that encode common integer types and values in
+    // the opcode. Keep each type's values contiguous for inexpensive decoding.
+    OPC_EmitIntegerI32Neg1,
+    OPC_EmitIntegerI32_0,
+    OPC_EmitIntegerI32_1,
+    OPC_EmitIntegerI32_2,
+    OPC_EmitIntegerI32_3,
+    OPC_EmitIntegerI32_4,
+    OPC_EmitIntegerI32_5,
+    OPC_EmitIntegerI32_6,
+    OPC_EmitIntegerI32_7,
+    OPC_EmitIntegerI32_8,
+    OPC_EmitIntegerI64Neg1,
+    OPC_EmitIntegerI64_0,
+    OPC_EmitIntegerI64_1,
+    OPC_EmitIntegerI64_2,
+    OPC_EmitIntegerI64_3,
+    OPC_EmitIntegerI64_4,
+    OPC_EmitIntegerI64_5,
+    OPC_EmitIntegerI64_6,
+    OPC_EmitIntegerI64_7,
     OPC_EmitIntegerByHwMode,
     OPC_EmitIntegerByHwMode0,
     OPC_EmitRegister,
@@ -352,6 +373,7 @@ class LLVM_ABI SelectionDAGISel {
     // Contains 32-bit offset in table for pattern being selected
     OPC_Coverage
   };
+  static_assert(OPC_Coverage < 256, "DAGISel opcodes must fit in one byte");
 
   enum {
     OPFL_None = 0,       // Node has no chain or glue input and isn't variadic.
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
index 5ae52cae771fb..9ee60bbad2106 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGISel.cpp
@@ -3997,6 +3997,36 @@ void SelectionDAGISel::SelectCodeCommon(SDNode *NodeToMatch,
 
       continue;
     }
+    case OPC_EmitIntegerI32Neg1:
+    case OPC_EmitIntegerI32_0:
+    case OPC_EmitIntegerI32_1:
+    case OPC_EmitIntegerI32_2:
+    case OPC_EmitIntegerI32_3:
+    case OPC_EmitIntegerI32_4:
+    case OPC_EmitIntegerI32_5:
+    case OPC_EmitIntegerI32_6:
+    case OPC_EmitIntegerI32_7:
+    case OPC_EmitIntegerI32_8:
+    case OPC_EmitIntegerI64Neg1:
+    case OPC_EmitIntegerI64_0:
+    case OPC_EmitIntegerI64_1:
+    case OPC_EmitIntegerI64_2:
+    case OPC_EmitIntegerI64_3:
+    case OPC_EmitIntegerI64_4:
+    case OPC_EmitIntegerI64_5:
+    case OPC_EmitIntegerI64_6:
+    case OPC_EmitIntegerI64_7: {
+      bool IsI32 = Opcode <= OPC_EmitIntegerI32_8;
+      unsigned FirstOpcode =
+          IsI32 ? OPC_EmitIntegerI32Neg1 : OPC_EmitIntegerI64Neg1;
+      MVT::SimpleValueType VT = IsI32 ? MVT::i32 : MVT::i64;
+      int64_t Val = static_cast<int64_t>(Opcode - FirstOpcode) - 1;
+      RecordedNodes.emplace_back(
+          CurDAG->getSignedConstant(Val, SDLoc(NodeToMatch), VT,
+                                    /*isTarget=*/true),
+          nullptr);
+      continue;
+    }
     case OPC_EmitInteger:
     case OPC_EmitIntegerI8:
     case OPC_EmitIntegerI16:
diff --git a/llvm/test/TableGen/DAGDefaultOps.td b/llvm/test/TableGen/DAGDefaultOps.td
index 8c7e3de58b734..4688ce73cd9a7 100644
--- a/llvm/test/TableGen/DAGDefaultOps.td
+++ b/llvm/test/TableGen/DAGDefaultOps.td
@@ -77,20 +77,20 @@ def MulIRRPat : Pat<(mul i32:$x, i32:$y), (MulIRR Reg:$x, Reg:$y)>;
 // ADD: SwitchOpcode{{.*}}TARGET_VAL(ISD::ADD)
 // ADD-NEXT: OPC_RecordChild0
 // ADD-NEXT: OPC_RecordChild1
-// ADD-NEXT: OPC_EmitIntegerI32, 0
+// ADD-NEXT: OPC_EmitIntegerI32_0
 // ADD-NEXT: OPC_MorphNodeTo1None, TARGET_VAL(::AddRRI)
 
 // ADDINT: SwitchOpcode{{.*}}TARGET_VAL(ISD::INTRINSIC_WO_CHAIN)
 // ADDINT-NEXT: OPC_CheckChild0Integer
 // ADDINT-NEXT: OPC_RecordChild1
 // ADDINT-NEXT: OPC_RecordChild2
-// ADDINT-NEXT: OPC_EmitIntegerI32, 1
+// ADDINT-NEXT: OPC_EmitIntegerI32_1
 // ADDINT-NEXT: OPC_MorphNodeTo1None, TARGET_VAL(::AddRRI)
 
 // SUB: SwitchOpcode{{.*}}TARGET_VAL(ISD::SUB)
 // SUB-NEXT: OPC_RecordChild0
 // SUB-NEXT: OPC_RecordChild1
-// SUB-NEXT: OPC_EmitIntegerI32, 0
+// SUB-NEXT: OPC_EmitIntegerI32_0
 // SUB-NEXT: OPC_MorphNodeTo1None, TARGET_VAL(::SubRRI)
 
 // MULINT: SwitchOpcode{{.*}}TARGET_VAL(ISD::INTRINSIC_W_CHAIN)
@@ -105,5 +105,5 @@ def MulIRRPat : Pat<(mul i32:$x, i32:$y), (MulIRR Reg:$x, Reg:$y)>;
 // MUL: SwitchOpcode{{.*}}TARGET_VAL(ISD::MUL)
 // MUL-NEXT: OPC_RecordChild0
 // MUL-NEXT: OPC_RecordChild1
-// MUL-NEXT: OPC_EmitIntegerI32, 0
+// MUL-NEXT: OPC_EmitIntegerI32_0
 // MUL-NEXT: OPC_MorphNodeTo1None, TARGET_VAL(::MulIRR)
diff --git a/llvm/test/TableGen/dag-isel-subregs.td b/llvm/test/TableGen/dag-isel-subregs.td
index e0e94e033a9b2..da90ad0ced635 100644
--- a/llvm/test/TableGen/dag-isel-subregs.td
+++ b/llvm/test/TableGen/dag-isel-subregs.td
@@ -4,7 +4,7 @@ include "reg-with-subregs-common.td"
 
 // CHECK-LABEL: OPC_CheckOpcode, TARGET_VAL(ISD::EXTRACT_SUBVECTOR),
 // CHECK: OPC_CheckChild1Integer, 0,
-// CHECK: OPC_EmitIntegerI32, sub0_sub1,
+// CHECK: OPC_EmitIntegerI32_2,
 def : Pat<(v2i32 (extract_subvector v32i32:$src, (i32 0))),
           (EXTRACT_SUBREG GPR_1024:$src, sub0_sub1)>;
 
diff --git a/llvm/utils/TableGen/DAGISelMatcherEmitter.cpp b/llvm/utils/TableGen/DAGISelMatcherEmitter.cpp
index 3d69c96ebd900..46c0430d6b023 100644
--- a/llvm/utils/TableGen/DAGISelMatcherEmitter.cpp
+++ b/llvm/utils/TableGen/DAGISelMatcherEmitter.cpp
@@ -924,6 +924,28 @@ unsigned MatcherTableEmitter::EmitMatcher(const Matcher *N,
     int64_t Val = IM->getValue();
     const std::string &Str = IM->getString();
     const ValueTypeByHwMode &VTBH = IM->getVT();
+    if (VTBH.isSimple()) {
+      MVT VT = VTBH.getSimple();
+      if ((VT == MVT::i32 && Val >= -1 && Val <= 8) ||
+          (VT == MVT::i64 && Val >= -1 && Val <= 7)) {
+        OS << "OPC_EmitIntegerI" << VT.getSizeInBits();
+        if (Val < 0)
+          OS << "Neg1";
+        else
+          OS << '_' << Val;
+        OS << ',';
+        if (!OmitComments) {
+          OS << " // #" << IM->getResultNo() << " = ";
+          if (!Str.empty())
+            OS << Str;
+          else
+            OS << Val;
+        }
+        OS << '\n';
+        return 1;
+      }
+    }
+
     unsigned TypeBytes = 0;
     if (VTBH.isSimple()) {
       MVT VT = VTBH.getSimple();



More information about the llvm-commits mailing list