[llvm] [X86] Optimize (a & 0xff) | ((b & 0xff) << 8) into high-byte register insertions (PR #214645)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 7 18:07:54 PDT 2026


https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/214645

>From eb4a27eed1b0a16043273371fa28374ad86dfa9d Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 7 Aug 2026 01:24:04 -0400
Subject: [PATCH 1/4] Pre-commit test (NFC)

---
 llvm/test/CodeGen/X86/h-register-insert.ll | 73 ++++++++++++++++++++++
 1 file changed, 73 insertions(+)
 create mode 100644 llvm/test/CodeGen/X86/h-register-insert.ll

diff --git a/llvm/test/CodeGen/X86/h-register-insert.ll b/llvm/test/CodeGen/X86/h-register-insert.ll
new file mode 100644
index 0000000000000..77c1d8d6ffa24
--- /dev/null
+++ b/llvm/test/CodeGen/X86/h-register-insert.ll
@@ -0,0 +1,73 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc < %s -mtriple=i686-unknown-unknown | FileCheck %s --check-prefix=X86
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown | FileCheck %s --check-prefix=X64
+
+define i32 @test_insert_32(i8 inreg %A, i8 inreg %B) {
+; X86-LABEL: test_insert_32:
+; X86:       # %bb.0:
+; X86-NEXT:    movzbl %al, %ecx
+; X86-NEXT:    movzbl %dl, %eax
+; X86-NEXT:    shll $8, %eax
+; X86-NEXT:    orl %ecx, %eax
+; X86-NEXT:    retl
+;
+; X64-LABEL: test_insert_32:
+; X64:       # %bb.0:
+; X64-NEXT:    movzbl %dil, %ecx
+; X64-NEXT:    movzbl %sil, %eax
+; X64-NEXT:    shll $8, %eax
+; X64-NEXT:    orl %ecx, %eax
+; X64-NEXT:    retq
+  %conv = zext i8 %A to i32
+  %conv1 = zext i8 %B to i32
+  %shl = shl nuw nsw i32 %conv1, 8
+  %or = or disjoint i32 %shl, %conv
+  ret i32 %or
+}
+
+define i16 @test_insert_16(i8 inreg %A, i8 inreg %B) {
+; X86-LABEL: test_insert_16:
+; X86:       # %bb.0:
+; X86-NEXT:    movzbl %al, %eax
+; X86-NEXT:    shll $8, %edx
+; X86-NEXT:    orl %edx, %eax
+; X86-NEXT:    # kill: def $ax killed $ax killed $eax
+; X86-NEXT:    retl
+;
+; X64-LABEL: test_insert_16:
+; X64:       # %bb.0:
+; X64-NEXT:    movzbl %dil, %eax
+; X64-NEXT:    shll $8, %esi
+; X64-NEXT:    orl %esi, %eax
+; X64-NEXT:    # kill: def $ax killed $ax killed $eax
+; X64-NEXT:    retq
+  %conv = zext i8 %A to i16
+  %conv1 = zext i8 %B to i16
+  %shl = shl nuw nsw i16 %conv1, 8
+  %or = or disjoint i16 %shl, %conv
+  ret i16 %or
+}
+
+define i64 @test_insert_64(i8 inreg %A, i8 inreg %B) {
+; X86-LABEL: test_insert_64:
+; X86:       # %bb.0:
+; X86-NEXT:    movzbl %al, %ecx
+; X86-NEXT:    movzbl %dl, %eax
+; X86-NEXT:    shll $8, %eax
+; X86-NEXT:    orl %ecx, %eax
+; X86-NEXT:    xorl %edx, %edx
+; X86-NEXT:    retl
+;
+; X64-LABEL: test_insert_64:
+; X64:       # %bb.0:
+; X64-NEXT:    movzbl %dil, %ecx
+; X64-NEXT:    movzbl %sil, %eax
+; X64-NEXT:    shll $8, %eax
+; X64-NEXT:    orl %ecx, %eax
+; X64-NEXT:    retq
+  %conv = zext i8 %A to i64
+  %conv1 = zext i8 %B to i64
+  %shl = shl nuw nsw i64 %conv1, 8
+  %or = or disjoint i64 %shl, %conv
+  ret i64 %or
+}

>From c17cfba4cbbab1333e177e4c4a5c1c1e9785f9e3 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 7 Aug 2026 02:05:48 -0400
Subject: [PATCH 2/4] [X86] Optimize (a & 0xff) | ((b & 0xff) << 8) into
 high-byte register insertions

This had to be done in dagtodag because tablegen would not work.
---
 llvm/lib/Target/X86/X86ISelDAGToDAG.cpp    | 159 +++++++++++++++++++++
 llvm/test/CodeGen/X86/extract-bits.ll      |  51 +++----
 llvm/test/CodeGen/X86/h-register-insert.ll | 133 +++++++++++++++--
 3 files changed, 299 insertions(+), 44 deletions(-)

diff --git a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
index d078117061677..38559c02fc111 100644
--- a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
@@ -17,6 +17,7 @@
 #include "X86TargetMachine.h"
 #include "llvm/ADT/Statistic.h"
 #include "llvm/CodeGen/MachineModuleInfo.h"
+#include "llvm/CodeGen/SDPatternMatch.h"
 #include "llvm/CodeGen/SelectionDAGISel.h"
 #include "llvm/Config/llvm-config.h"
 #include "llvm/IR/ConstantRange.h"
@@ -599,6 +600,7 @@ namespace {
                         uint8_t Imm);
     bool tryVPTESTM(SDNode *Root, SDValue Setcc, SDValue Mask);
     bool tryMatchBitSelect(SDNode *N);
+    bool tryMatchHRegisterInsert(SDNode *N);
 
     MachineSDNode *emitPCMPISTR(unsigned ROpc, unsigned MOpc, bool MayFoldLoad,
                                 const SDLoc &dl, MVT VT, SDNode *Node);
@@ -5403,6 +5405,161 @@ bool X86DAGToDAGISel::tryMatchBitSelect(SDNode *N) {
                         Ternlog.getNode(), A, B, C, 0xCA);
 }
 
+using namespace llvm::SDPatternMatch;
+
+static bool isBEXTRControlOperand(SDNode *N) {
+  if (!N->hasOneUse())
+    return false;
+  SDNode *User = *N->user_begin();
+
+  if (User->getOpcode() == X86ISD::BEXTR)
+    return User->getOperand(1).getNode() == N;
+
+  if (!User->isMachineOpcode())
+    return false;
+
+  switch (User->getMachineOpcode()) {
+  case X86::BEXTR32rr:
+  case X86::BEXTR64rr:
+    return User->getOperand(1).getNode() == N;
+  case X86::BEXTR32rm:
+  case X86::BEXTR64rm:
+    return User->getOperand(5).getNode() == N;
+  default:
+    return false;
+  }
+}
+
+static std::optional<std::pair<SDValue, bool>> getLoBase(SDValue V) {
+  SDValue Base;
+  uint64_t Mask;
+
+  if (sd_match(V, m_And(m_Value(Base), m_ConstInt(Mask)))) {
+    if (Mask == 0xFF)
+      return std::make_pair(Base, true);
+    if (Mask == 0xFFFF00FF)
+      return std::make_pair(Base, false);
+  }
+
+  if (sd_match(V, m_ZExt(m_Value(Base))) ||
+      sd_match(V, m_AnyExt(m_Value(Base)))) {
+    if (Base.getValueType() == MVT::i8)
+      return std::make_pair(Base, true);
+  }
+
+  return std::nullopt;
+}
+
+static std::optional<SDValue> getHiBase(SDValue V, SDNode *Root) {
+  if (!V.hasOneUse())
+    return std::nullopt;
+
+  SDValue Src;
+  if (!sd_match(V, m_Shl(m_Value(Src), m_SpecificInt(8))))
+    return std::nullopt;
+
+  SDValue Base;
+  if (sd_match(Src, m_And(m_Value(Base), m_SpecificInt(255))))
+    return Base;
+
+  if (sd_match(Src, m_ZExt(m_Value(Base))) ||
+      sd_match(Src, m_AnyExt(m_Value(Base)))) {
+    if (Base.getValueType() == MVT::i8)
+      return Base;
+  }
+
+  // Fallback: Upper bits are ignored by BEXTR.
+  // Only bits 0-15 of the BEXTR control operand are consumed:
+  // bits 0-7 specify the start and bits 8-15 specify the length.
+  // After the shift, Src bits 8+ become bits 16+, so they are irrelevant.
+  if (isBEXTRControlOperand(Root)) {
+    return Src;
+  }
+
+  return std::nullopt;
+}
+
+bool X86DAGToDAGISel::tryMatchHRegisterInsert(SDNode *N) {
+  // High-byte registers (AH, BH, CH, DH) are not supported by APX and have
+  // microarchitectural penalties on recent 64-bit targets (see #210321).
+  // Ideally, this should be gated on a TuningSlowHighByteRegs subtarget
+  // feature instead of just bitness, but for now we disable it entirely on
+  // 64-bit.
+  if (Subtarget->is64Bit())
+    return false;
+
+  // This matcher attempts to fold (or (and X, 0xff), (shl Y, 8)) into
+  // INSERT_SUBREG(X, Y, sub_8bit_hi). It handles 0xFFFF00FF masks by skipping
+  // the AND, and ZERO_EXTEND/ANY_EXTEND by emitting a MOVZX.
+  // If the OR result is uniquely used as a BEXTR control operand, the upper
+  // bits of the shift source are ignored by BEXTR, so we can safely bypass
+  // byte-cleanness checks for Y.
+  if (N->getOpcode() != ISD::OR)
+    return false;
+  if (N->getValueType(0) != MVT::i32)
+    return false;
+
+  SDValue LHS = N->getOperand(0);
+  SDValue RHS = N->getOperand(1);
+
+  struct MatchResult {
+    SDValue LoBase;
+    bool NeedMovzx;
+    SDValue HiBase;
+  };
+
+  auto tryMatch = [&](SDValue LoVal,
+                      SDValue HiVal) -> std::optional<MatchResult> {
+    auto Lo = getLoBase(LoVal);
+    if (!Lo)
+      return std::nullopt;
+    auto Hi = getHiBase(HiVal, N);
+    if (!Hi)
+      return std::nullopt;
+    return MatchResult{Lo->first, Lo->second, *Hi};
+  };
+
+  auto Match = tryMatch(LHS, RHS);
+  if (!Match)
+    Match = tryMatch(RHS, LHS);
+
+  if (!Match)
+    return false;
+
+  SDLoc dl(N);
+  SDValue Sub8 = CurDAG->getTargetConstant(X86::sub_8bit, dl, MVT::i32);
+  SDValue RC_ABCD =
+      CurDAG->getTargetConstant(X86::GR32_ABCDRegClassID, dl, MVT::i32);
+
+  SDValue DestBase = Match->LoBase;
+  if (Match->NeedMovzx) {
+    if (DestBase.getValueType() != MVT::i8) {
+      DestBase = SDValue(CurDAG->getMachineNode(TargetOpcode::EXTRACT_SUBREG,
+                                                dl, MVT::i8, DestBase, Sub8),
+                         0);
+    }
+    DestBase = SDValue(
+        CurDAG->getMachineNode(X86::MOVZX32rr8, dl, MVT::i32, DestBase), 0);
+  }
+
+  DestBase = SDValue(CurDAG->getMachineNode(TargetOpcode::COPY_TO_REGCLASS, dl,
+                                            MVT::i32, DestBase, RC_ABCD),
+                     0);
+
+  SDValue HiExtr = Match->HiBase;
+  if (HiExtr.getValueType() != MVT::i8)
+    HiExtr = SDValue(CurDAG->getMachineNode(TargetOpcode::EXTRACT_SUBREG, dl,
+                                            MVT::i8, HiExtr, Sub8),
+                     0);
+
+  SDValue Sub8Hi = CurDAG->getTargetConstant(X86::sub_8bit_hi, dl, MVT::i32);
+  SDNode *Insert = CurDAG->getMachineNode(TargetOpcode::INSERT_SUBREG, dl,
+                                          MVT::i32, DestBase, HiExtr, Sub8Hi);
+
+  ReplaceNode(N, Insert);
+  return true;
+}
+
 void X86DAGToDAGISel::Select(SDNode *Node) {
   MVT NVT = Node->getSimpleValueType(0);
   unsigned Opcode = Node->getOpcode();
@@ -5715,6 +5872,8 @@ void X86DAGToDAGISel::Select(SDNode *Node) {
       return;
     if (Opcode == ISD::OR && tryMatchBitSelect(Node))
       return;
+    if (Opcode == ISD::OR && tryMatchHRegisterInsert(Node))
+      return;
     if (tryVPTERNLOG(Node))
       return;
 
diff --git a/llvm/test/CodeGen/X86/extract-bits.ll b/llvm/test/CodeGen/X86/extract-bits.ll
index 90e075bfabf0a..d7b812a04de46 100644
--- a/llvm/test/CodeGen/X86/extract-bits.ll
+++ b/llvm/test/CodeGen/X86/extract-bits.ll
@@ -49,9 +49,8 @@ define i32 @bextr32_a0(i32 %val, i32 %numskipbits, i32 %numlowbits) nounwind {
 ; X86-BMI1-LABEL: bextr32_a0:
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    orl %eax, %ecx
+; X86-BMI1-NEXT:    movb %al, %ch
 ; X86-BMI1-NEXT:    bextrl %ecx, {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -181,9 +180,8 @@ define i32 @bextr32_a1_indexzext(i32 %val, i8 zeroext %numskipbits, i8 zeroext %
 ; X86-BMI1-LABEL: bextr32_a1_indexzext:
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    orl %eax, %ecx
+; X86-BMI1-NEXT:    movb %al, %ch
 ; X86-BMI1-NEXT:    bextrl %ecx, {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -249,9 +247,8 @@ define i32 @bextr32_a2_load(ptr %w, i32 %numskipbits, i32 %numlowbits) nounwind
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movl {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    shll $8, %ecx
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %edx
-; X86-BMI1-NEXT:    orl %ecx, %edx
+; X86-BMI1-NEXT:    movb %cl, %dh
 ; X86-BMI1-NEXT:    bextrl %edx, (%eax), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -319,9 +316,8 @@ define i32 @bextr32_a3_load_indexzext(ptr %w, i8 zeroext %numskipbits, i8 zeroex
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movl {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    shll $8, %ecx
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %edx
-; X86-BMI1-NEXT:    orl %ecx, %edx
+; X86-BMI1-NEXT:    movb %cl, %dh
 ; X86-BMI1-NEXT:    bextrl %edx, (%eax), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -388,9 +384,8 @@ define i32 @bextr32_a4_commutative(i32 %val, i32 %numskipbits, i32 %numlowbits)
 ; X86-BMI1-LABEL: bextr32_a4_commutative:
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    orl %eax, %ecx
+; X86-BMI1-NEXT:    movb %al, %ch
 ; X86-BMI1-NEXT:    bextrl %ecx, {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -464,9 +459,8 @@ define i32 @bextr32_a5_skipextrauses(i32 %val, i32 %numskipbits, i32 %numlowbits
 ; X86-BMI1-NEXT:    subl $8, %esp
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
 ; X86-BMI1-NEXT:    movl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %ecx
 ; X86-BMI1-NEXT:    movzbl %al, %edx
-; X86-BMI1-NEXT:    orl %ecx, %edx
+; X86-BMI1-NEXT:    movb %cl, %dh
 ; X86-BMI1-NEXT:    bextrl %edx, {{[0-9]+}}(%esp), %esi
 ; X86-BMI1-NEXT:    movl %eax, (%esp)
 ; X86-BMI1-NEXT:    calll use32 at PLT
@@ -2231,9 +2225,8 @@ define i32 @bextr32_b0(i32 %val, i32 %numskipbits, i32 %numlowbits) nounwind {
 ; X86-BMI1-LABEL: bextr32_b0:
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    orl %eax, %ecx
+; X86-BMI1-NEXT:    movb %al, %ch
 ; X86-BMI1-NEXT:    bextrl %ecx, {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -2296,9 +2289,8 @@ define i32 @bextr32_b1_indexzext(i32 %val, i8 zeroext %numskipbits, i8 zeroext %
 ; X86-BMI1-LABEL: bextr32_b1_indexzext:
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    orl %eax, %ecx
+; X86-BMI1-NEXT:    movb %al, %ch
 ; X86-BMI1-NEXT:    bextrl %ecx, {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -2364,9 +2356,8 @@ define i32 @bextr32_b2_load(ptr %w, i32 %numskipbits, i32 %numlowbits) nounwind
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movl {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    shll $8, %ecx
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %edx
-; X86-BMI1-NEXT:    orl %ecx, %edx
+; X86-BMI1-NEXT:    movb %cl, %dh
 ; X86-BMI1-NEXT:    bextrl %edx, (%eax), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -2434,9 +2425,8 @@ define i32 @bextr32_b3_load_indexzext(ptr %w, i8 zeroext %numskipbits, i8 zeroex
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movl {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    shll $8, %ecx
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %edx
-; X86-BMI1-NEXT:    orl %ecx, %edx
+; X86-BMI1-NEXT:    movb %cl, %dh
 ; X86-BMI1-NEXT:    bextrl %edx, (%eax), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -2503,9 +2493,8 @@ define i32 @bextr32_b4_commutative(i32 %val, i32 %numskipbits, i32 %numlowbits)
 ; X86-BMI1-LABEL: bextr32_b4_commutative:
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    orl %eax, %ecx
+; X86-BMI1-NEXT:    movb %al, %ch
 ; X86-BMI1-NEXT:    bextrl %ecx, {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -2579,9 +2568,8 @@ define i32 @bextr32_b5_skipextrauses(i32 %val, i32 %numskipbits, i32 %numlowbits
 ; X86-BMI1-NEXT:    subl $8, %esp
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
 ; X86-BMI1-NEXT:    movl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %ecx
 ; X86-BMI1-NEXT:    movzbl %al, %edx
-; X86-BMI1-NEXT:    orl %ecx, %edx
+; X86-BMI1-NEXT:    movb %cl, %dh
 ; X86-BMI1-NEXT:    bextrl %edx, {{[0-9]+}}(%esp), %esi
 ; X86-BMI1-NEXT:    movl %eax, (%esp)
 ; X86-BMI1-NEXT:    calll use32 at PLT
@@ -6552,9 +6540,8 @@ define i32 @bextr32_d0(i32 %val, i32 %numskipbits, i32 %numlowbits) nounwind {
 ; X86-BMI1-LABEL: bextr32_d0:
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    orl %eax, %ecx
+; X86-BMI1-NEXT:    movb %al, %ch
 ; X86-BMI1-NEXT:    bextrl %ecx, {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -6614,9 +6601,8 @@ define i32 @bextr32_d1_indexzext(i32 %val, i8 %numskipbits, i8 %numlowbits) noun
 ; X86-BMI1-LABEL: bextr32_d1_indexzext:
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    orl %eax, %ecx
+; X86-BMI1-NEXT:    movb %al, %ch
 ; X86-BMI1-NEXT:    bextrl %ecx, {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -6680,9 +6666,8 @@ define i32 @bextr32_d2_load(ptr %w, i32 %numskipbits, i32 %numlowbits) nounwind
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movl {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    shll $8, %ecx
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %edx
-; X86-BMI1-NEXT:    orl %ecx, %edx
+; X86-BMI1-NEXT:    movb %cl, %dh
 ; X86-BMI1-NEXT:    bextrl %edx, (%eax), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -6746,9 +6731,8 @@ define i32 @bextr32_d3_load_indexzext(ptr %w, i8 %numskipbits, i8 %numlowbits) n
 ; X86-BMI1:       # %bb.0:
 ; X86-BMI1-NEXT:    movl {{[0-9]+}}(%esp), %eax
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
-; X86-BMI1-NEXT:    shll $8, %ecx
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %edx
-; X86-BMI1-NEXT:    orl %ecx, %edx
+; X86-BMI1-NEXT:    movb %cl, %dh
 ; X86-BMI1-NEXT:    bextrl %edx, (%eax), %eax
 ; X86-BMI1-NEXT:    retl
 ;
@@ -6823,9 +6807,8 @@ define i32 @bextr32_d5_skipextrauses(i32 %val, i32 %numskipbits, i32 %numlowbits
 ; X86-BMI1-NEXT:    subl $8, %esp
 ; X86-BMI1-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
 ; X86-BMI1-NEXT:    movl {{[0-9]+}}(%esp), %eax
-; X86-BMI1-NEXT:    shll $8, %ecx
 ; X86-BMI1-NEXT:    movzbl %al, %edx
-; X86-BMI1-NEXT:    orl %ecx, %edx
+; X86-BMI1-NEXT:    movb %cl, %dh
 ; X86-BMI1-NEXT:    bextrl %edx, {{[0-9]+}}(%esp), %esi
 ; X86-BMI1-NEXT:    movl %eax, (%esp)
 ; X86-BMI1-NEXT:    calll use32 at PLT
diff --git a/llvm/test/CodeGen/X86/h-register-insert.ll b/llvm/test/CodeGen/X86/h-register-insert.ll
index 77c1d8d6ffa24..8f1882c106bb4 100644
--- a/llvm/test/CodeGen/X86/h-register-insert.ll
+++ b/llvm/test/CodeGen/X86/h-register-insert.ll
@@ -1,14 +1,12 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
-; RUN: llc < %s -mtriple=i686-unknown-unknown | FileCheck %s --check-prefix=X86
-; RUN: llc < %s -mtriple=x86_64-unknown-unknown | FileCheck %s --check-prefix=X64
+; RUN: llc < %s -mtriple=i686-unknown-unknown -mattr=+bmi | FileCheck %s --check-prefix=X86
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+bmi | FileCheck %s --check-prefix=X64
 
 define i32 @test_insert_32(i8 inreg %A, i8 inreg %B) {
 ; X86-LABEL: test_insert_32:
 ; X86:       # %bb.0:
-; X86-NEXT:    movzbl %al, %ecx
-; X86-NEXT:    movzbl %dl, %eax
-; X86-NEXT:    shll $8, %eax
-; X86-NEXT:    orl %ecx, %eax
+; X86-NEXT:    movzbl %al, %eax
+; X86-NEXT:    movb %dl, %ah
 ; X86-NEXT:    retl
 ;
 ; X64-LABEL: test_insert_32:
@@ -51,10 +49,8 @@ define i16 @test_insert_16(i8 inreg %A, i8 inreg %B) {
 define i64 @test_insert_64(i8 inreg %A, i8 inreg %B) {
 ; X86-LABEL: test_insert_64:
 ; X86:       # %bb.0:
-; X86-NEXT:    movzbl %al, %ecx
-; X86-NEXT:    movzbl %dl, %eax
-; X86-NEXT:    shll $8, %eax
-; X86-NEXT:    orl %ecx, %eax
+; X86-NEXT:    movzbl %al, %eax
+; X86-NEXT:    movb %dl, %ah
 ; X86-NEXT:    xorl %edx, %edx
 ; X86-NEXT:    retl
 ;
@@ -71,3 +67,120 @@ define i64 @test_insert_64(i8 inreg %A, i8 inreg %B) {
   %or = or disjoint i64 %shl, %conv
   ret i64 %or
 }
+
+define i32 @test_insert_mask_32(i32 %a, i8 inreg %b) {
+; X86-LABEL: test_insert_mask_32:
+; X86:       # %bb.0:
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %ecx
+; X86-NEXT:    movb %al, %ch
+; X86-NEXT:    movl %ecx, %eax
+; X86-NEXT:    retl
+;
+; X64-LABEL: test_insert_mask_32:
+; X64:       # %bb.0:
+; X64-NEXT:    andl $-65281, %edi # imm = 0xFFFF00FF
+; X64-NEXT:    movzbl %sil, %eax
+; X64-NEXT:    shll $8, %eax
+; X64-NEXT:    orl %edi, %eax
+; X64-NEXT:    retq
+  %mask = and i32 %a, 4294902015 ; 0xFFFF00FF
+  %conv = zext i8 %b to i32
+  %shl = shl nuw nsw i32 %conv, 8
+  %or = or disjoint i32 %mask, %shl
+  ret i32 %or
+}
+
+define i32 @test_insert_negative_not_byte_sized(i32 %a, i32 %b) {
+; X86-LABEL: test_insert_negative_not_byte_sized:
+; X86:       # %bb.0:
+; X86-NEXT:    movzbl {{[0-9]+}}(%esp), %ecx
+; X86-NEXT:    movl {{[0-9]+}}(%esp), %eax
+; X86-NEXT:    shll $8, %eax
+; X86-NEXT:    orl %ecx, %eax
+; X86-NEXT:    retl
+;
+; X64-LABEL: test_insert_negative_not_byte_sized:
+; X64:       # %bb.0:
+; X64-NEXT:    movzbl %dil, %eax
+; X64-NEXT:    shll $8, %esi
+; X64-NEXT:    orl %esi, %eax
+; X64-NEXT:    retq
+  %mask = and i32 %a, 255
+  %shl = shl i32 %b, 8
+  %or = or disjoint i32 %mask, %shl
+  ret i32 %or
+}
+
+define i32 @test_insert_shared_lo(i32 inreg %a, i32 inreg %b) {
+; X86-LABEL: test_insert_shared_lo:
+; X86:       # %bb.0:
+; X86-NEXT:    movzbl %al, %eax
+; X86-NEXT:    testl %eax, %eax
+; X86-NEXT:    je .LBB5_2
+; X86-NEXT:  # %bb.1:
+; X86-NEXT:    movb %dl, %ah
+; X86-NEXT:  .LBB5_2:
+; X86-NEXT:    retl
+;
+; X64-LABEL: test_insert_shared_lo:
+; X64:       # %bb.0:
+; X64-NEXT:    movzbl %dil, %ecx
+; X64-NEXT:    movzbl %sil, %eax
+; X64-NEXT:    shll $8, %eax
+; X64-NEXT:    orl %ecx, %eax
+; X64-NEXT:    testl %ecx, %ecx
+; X64-NEXT:    cmovel %ecx, %eax
+; X64-NEXT:    retq
+  %mask = and i32 %a, 255
+  %cmp = icmp eq i32 %mask, 0
+  %mask2 = and i32 %b, 255
+  %shl = shl nuw nsw i32 %mask2, 8
+  %or = or disjoint i32 %mask, %shl
+  %sel = select i1 %cmp, i32 0, i32 %or
+  ret i32 %sel
+}
+
+define i32 @test_insert_32_zext(i8 inreg %a, i8 inreg %b) {
+; X86-LABEL: test_insert_32_zext:
+; X86:       # %bb.0:
+; X86-NEXT:    movzbl %al, %eax
+; X86-NEXT:    movb %dl, %ah
+; X86-NEXT:    retl
+;
+; X64-LABEL: test_insert_32_zext:
+; X64:       # %bb.0:
+; X64-NEXT:    movzbl %dil, %ecx
+; X64-NEXT:    movzbl %sil, %eax
+; X64-NEXT:    shll $8, %eax
+; X64-NEXT:    orl %ecx, %eax
+; X64-NEXT:    retq
+  %zext_a = zext i8 %a to i32
+  %zext_b = zext i8 %b to i32
+  %shl = shl nuw nsw i32 %zext_b, 8
+  %or = or disjoint i32 %zext_a, %shl
+  ret i32 %or
+}
+
+declare i32 @llvm.x86.bmi.bextr.32(i32, i32)
+
+define i32 @test_insert_bextr_control(i32 inreg %a, i32 inreg %b, i32 inreg %src) {
+; X86-LABEL: test_insert_bextr_control:
+; X86:       # %bb.0:
+; X86-NEXT:    movzbl %al, %eax
+; X86-NEXT:    movb %dl, %ah
+; X86-NEXT:    bextrl %eax, %ecx, %eax
+; X86-NEXT:    retl
+;
+; X64-LABEL: test_insert_bextr_control:
+; X64:       # %bb.0:
+; X64-NEXT:    movzbl %dil, %eax
+; X64-NEXT:    shll $8, %esi
+; X64-NEXT:    orl %eax, %esi
+; X64-NEXT:    bextrl %esi, %edx, %eax
+; X64-NEXT:    retq
+  %mask_a = and i32 %a, 255
+  %shl_b = shl i32 %b, 8
+  %or = or disjoint i32 %mask_a, %shl_b
+  %res = call i32 @llvm.x86.bmi.bextr.32(i32 %src, i32 %or)
+  ret i32 %res
+}

>From 97907ecb4eb195c5f179ceaee76cc57369a9fc4e Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 7 Aug 2026 18:44:42 -0400
Subject: [PATCH 3/4] Fix test_insert_16

---
 llvm/lib/Target/X86/X86ISelDAGToDAG.cpp    | 49 ++++++++++++++--------
 llvm/test/CodeGen/X86/h-register-insert.ll |  3 +-
 2 files changed, 33 insertions(+), 19 deletions(-)

diff --git a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
index 38559c02fc111..1e0cad9cae9d5 100644
--- a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
@@ -5430,27 +5430,45 @@ static bool isBEXTRControlOperand(SDNode *N) {
   }
 }
 
-static std::optional<std::pair<SDValue, bool>> getLoBase(SDValue V) {
+struct LoInfo {
+  SDValue Base;
+  bool NeedMovzx;
+};
+
+static std::optional<LoInfo> getLoBase(SDValue V) {
   SDValue Base;
   uint64_t Mask;
 
   if (sd_match(V, m_And(m_Value(Base), m_ConstInt(Mask)))) {
     if (Mask == 0xFF)
-      return std::make_pair(Base, true);
+      return LoInfo{Base, true};
     if (Mask == 0xFFFF00FF)
-      return std::make_pair(Base, false);
+      return LoInfo{Base, false};
   }
 
   if (sd_match(V, m_ZExt(m_Value(Base))) ||
       sd_match(V, m_AnyExt(m_Value(Base)))) {
     if (Base.getValueType() == MVT::i8)
-      return std::make_pair(Base, true);
+      return LoInfo{Base, true};
   }
 
   return std::nullopt;
 }
 
-static std::optional<SDValue> getHiBase(SDValue V, SDNode *Root) {
+static bool upperBitsAreDiscarded(SDNode *N) {
+  if (!N->hasOneUse())
+    return false;
+
+  SDNode *User = *N->user_begin();
+  if (User->getOpcode() == ISD::TRUNCATE) {
+    MVT TruncVT = User->getSimpleValueType(0);
+    return TruncVT == MVT::i16 || TruncVT == MVT::i8;
+  }
+
+  return false;
+}
+
+static std::optional<SDValue> getHiBase(SDValue V, bool UpperBitsDiscarded) {
   if (!V.hasOneUse())
     return std::nullopt;
 
@@ -5468,13 +5486,8 @@ static std::optional<SDValue> getHiBase(SDValue V, SDNode *Root) {
       return Base;
   }
 
-  // Fallback: Upper bits are ignored by BEXTR.
-  // Only bits 0-15 of the BEXTR control operand are consumed:
-  // bits 0-7 specify the start and bits 8-15 specify the length.
-  // After the shift, Src bits 8+ become bits 16+, so they are irrelevant.
-  if (isBEXTRControlOperand(Root)) {
+  if (UpperBitsDiscarded)
     return Src;
-  }
 
   return std::nullopt;
 }
@@ -5499,12 +5512,14 @@ bool X86DAGToDAGISel::tryMatchHRegisterInsert(SDNode *N) {
   if (N->getValueType(0) != MVT::i32)
     return false;
 
+  bool UpperBitsDiscarded =
+      isBEXTRControlOperand(N) || upperBitsAreDiscarded(N);
+
   SDValue LHS = N->getOperand(0);
   SDValue RHS = N->getOperand(1);
 
   struct MatchResult {
-    SDValue LoBase;
-    bool NeedMovzx;
+    LoInfo Lo;
     SDValue HiBase;
   };
 
@@ -5513,10 +5528,10 @@ bool X86DAGToDAGISel::tryMatchHRegisterInsert(SDNode *N) {
     auto Lo = getLoBase(LoVal);
     if (!Lo)
       return std::nullopt;
-    auto Hi = getHiBase(HiVal, N);
+    auto Hi = getHiBase(HiVal, UpperBitsDiscarded);
     if (!Hi)
       return std::nullopt;
-    return MatchResult{Lo->first, Lo->second, *Hi};
+    return MatchResult{*Lo, *Hi};
   };
 
   auto Match = tryMatch(LHS, RHS);
@@ -5531,8 +5546,8 @@ bool X86DAGToDAGISel::tryMatchHRegisterInsert(SDNode *N) {
   SDValue RC_ABCD =
       CurDAG->getTargetConstant(X86::GR32_ABCDRegClassID, dl, MVT::i32);
 
-  SDValue DestBase = Match->LoBase;
-  if (Match->NeedMovzx) {
+  SDValue DestBase = Match->Lo.Base;
+  if (Match->Lo.NeedMovzx) {
     if (DestBase.getValueType() != MVT::i8) {
       DestBase = SDValue(CurDAG->getMachineNode(TargetOpcode::EXTRACT_SUBREG,
                                                 dl, MVT::i8, DestBase, Sub8),
diff --git a/llvm/test/CodeGen/X86/h-register-insert.ll b/llvm/test/CodeGen/X86/h-register-insert.ll
index 8f1882c106bb4..9cedab478e1bb 100644
--- a/llvm/test/CodeGen/X86/h-register-insert.ll
+++ b/llvm/test/CodeGen/X86/h-register-insert.ll
@@ -27,8 +27,7 @@ define i16 @test_insert_16(i8 inreg %A, i8 inreg %B) {
 ; X86-LABEL: test_insert_16:
 ; X86:       # %bb.0:
 ; X86-NEXT:    movzbl %al, %eax
-; X86-NEXT:    shll $8, %edx
-; X86-NEXT:    orl %edx, %eax
+; X86-NEXT:    movb %dl, %ah
 ; X86-NEXT:    # kill: def $ax killed $ax killed $eax
 ; X86-NEXT:    retl
 ;

>From 2abc7f9cfa5baa263a247699c7c173ad202c5acd Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Fri, 7 Aug 2026 21:07:10 -0400
Subject: [PATCH 4/4] Fix test

---
 llvm/lib/Target/X86/X86ISelDAGToDAG.cpp | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
index 1e0cad9cae9d5..cd85cea975075 100644
--- a/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
+++ b/llvm/lib/Target/X86/X86ISelDAGToDAG.cpp
@@ -5465,6 +5465,15 @@ static bool upperBitsAreDiscarded(SDNode *N) {
     return TruncVT == MVT::i16 || TruncVT == MVT::i8;
   }
 
+  if (User->isMachineOpcode() &&
+      User->getMachineOpcode() == TargetOpcode::EXTRACT_SUBREG) {
+    if (auto *IdxNode = dyn_cast<ConstantSDNode>(User->getOperand(1))) {
+      unsigned Idx = IdxNode->getZExtValue();
+      if (Idx == X86::sub_16bit || Idx == X86::sub_8bit)
+        return true;
+    }
+  }
+
   return false;
 }
 



More information about the llvm-commits mailing list