[llvm] [DAGCombiner] Teach MatchLoadCombine to look through AND masks (PR #200247)

MichaƂ Kurek via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 2 07:45:14 PDT 2026


https://github.com/MKJM2 updated https://github.com/llvm/llvm-project/pull/200247

>From cc695ccd6c55cca03d84bd13b0d84f52f7b6e469 Mon Sep 17 00:00:00 2001
From: mkurek7 <mkurek7 at bloomberg.net>
Date: Thu, 28 May 2026 14:24:03 -0400
Subject: [PATCH 1/4] [LLVM][DAGCombiner] Add tests for load combining through
 AND masks (NFC)

Add baseline tests showing MatchLoadCombine behavior with AND-masked loads.
Tests cover both AArch64 (little-endian and big-endian) and X86_64.

Positive tests: high byte masked, low byte masked, both masked, 4-byte chain,
bswap+mask.
Negative tests: non-adjacent, volatile, variable mask, multi-use AND result.

Assisted-by: Claude <noreply at anthropic.com>
---
 .../CodeGen/AArch64/load-combine-masked.ll    | 302 ++++++++++++++++++
 llvm/test/CodeGen/X86/load-combine-masked.ll  | 257 +++++++++++++++
 2 files changed, 559 insertions(+)
 create mode 100644 llvm/test/CodeGen/AArch64/load-combine-masked.ll
 create mode 100644 llvm/test/CodeGen/X86/load-combine-masked.ll

diff --git a/llvm/test/CodeGen/AArch64/load-combine-masked.ll b/llvm/test/CodeGen/AArch64/load-combine-masked.ll
new file mode 100644
index 0000000000000..63a96ab931920
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/load-combine-masked.ll
@@ -0,0 +1,302 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=aarch64-apple-macosx < %s | FileCheck %s --check-prefix=LE
+; RUN: llc -mtriple=aarch64_be-unknown < %s | FileCheck %s --check-prefix=BE
+
+; Test that consecutive byte loads combined via OR with AND masks on individual
+; loads are folded into a single wider load + mask at the DAG level.
+; This is the pattern from manual bitfield extraction code.
+; https://github.com/llvm/llvm-project/issues/191193
+
+; --- Positive tests (should fold to wide load + mask) ---
+
+; High byte masked.
+define i16 @load_2_bytes_high_masked(ptr %p) {
+; LE-LABEL: load_2_bytes_high_masked:
+; LE:       ; %bb.0:
+; LE-NEXT:    ldrb w8, [x0]
+; LE-NEXT:    ldrb w9, [x0, #1]
+; LE-NEXT:    bfi w8, w9, #8, #2
+; LE-NEXT:    mov w0, w8
+; LE-NEXT:    ret
+;
+; BE-LABEL: load_2_bytes_high_masked:
+; BE:       // %bb.0:
+; BE-NEXT:    ldrb w8, [x0]
+; BE-NEXT:    ldrb w9, [x0, #1]
+; BE-NEXT:    bfi w8, w9, #8, #2
+; BE-NEXT:    mov w0, w8
+; BE-NEXT:    ret
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %hi.m = and i8 %hi, 3
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Low byte masked.
+define i16 @load_2_bytes_low_masked(ptr %p) {
+; LE-LABEL: load_2_bytes_low_masked:
+; LE:       ; %bb.0:
+; LE-NEXT:    ldrb w8, [x0]
+; LE-NEXT:    ldrb w9, [x0, #1]
+; LE-NEXT:    and w8, w8, #0x3f
+; LE-NEXT:    orr w0, w8, w9, lsl #8
+; LE-NEXT:    ret
+;
+; BE-LABEL: load_2_bytes_low_masked:
+; BE:       // %bb.0:
+; BE-NEXT:    ldrb w8, [x0]
+; BE-NEXT:    ldrb w9, [x0, #1]
+; BE-NEXT:    and w8, w8, #0x3f
+; BE-NEXT:    orr w0, w8, w9, lsl #8
+; BE-NEXT:    ret
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %lo.m = and i8 %lo, 63
+  %lo.ext = zext i8 %lo.m to i16
+  %hi.ext = zext i8 %hi to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Both bytes masked.
+define i16 @load_2_bytes_both_masked(ptr %p) {
+; LE-LABEL: load_2_bytes_both_masked:
+; LE:       ; %bb.0:
+; LE-NEXT:    ldrh w8, [x0]
+; LE-NEXT:    mov w9, #783 ; =0x30f
+; LE-NEXT:    and w0, w8, w9
+; LE-NEXT:    ret
+;
+; BE-LABEL: load_2_bytes_both_masked:
+; BE:       // %bb.0:
+; BE-NEXT:    ldrh w9, [x0]
+; BE-NEXT:    mov w8, #783 // =0x30f
+; BE-NEXT:    rev w9, w9
+; BE-NEXT:    and w0, w8, w9, lsr #16
+; BE-NEXT:    ret
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %lo.m = and i8 %lo, 15
+  %hi.m = and i8 %hi, 3
+  %lo.ext = zext i8 %lo.m to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Four bytes, last byte masked.
+define i32 @load_4_bytes_last_masked(ptr %p) {
+; LE-LABEL: load_4_bytes_last_masked:
+; LE:       ; %bb.0:
+; LE-NEXT:    ldrb w8, [x0, #2]
+; LE-NEXT:    ldrh w9, [x0]
+; LE-NEXT:    ldrb w10, [x0, #3]
+; LE-NEXT:    orr w0, w9, w8, lsl #16
+; LE-NEXT:    bfi w0, w10, #24, #6
+; LE-NEXT:    ret
+;
+; BE-LABEL: load_4_bytes_last_masked:
+; BE:       // %bb.0:
+; BE-NEXT:    ldrh w8, [x0]
+; BE-NEXT:    ldrb w9, [x0, #2]
+; BE-NEXT:    ldrb w10, [x0, #3]
+; BE-NEXT:    rev w8, w8
+; BE-NEXT:    extr w0, w9, w8, #16
+; BE-NEXT:    bfi w0, w10, #24, #6
+; BE-NEXT:    ret
+  %p1 = getelementptr i8, ptr %p, i64 1
+  %p2 = getelementptr i8, ptr %p, i64 2
+  %p3 = getelementptr i8, ptr %p, i64 3
+  %l0 = load i8, ptr %p, align 1
+  %l1 = load i8, ptr %p1, align 1
+  %l2 = load i8, ptr %p2, align 1
+  %l3 = load i8, ptr %p3, align 1
+  %l3.m = and i8 %l3, 63
+  %e0 = zext i8 %l0 to i32
+  %e1 = zext i8 %l1 to i32
+  %e2 = zext i8 %l2 to i32
+  %e3 = zext i8 %l3.m to i32
+  %s1 = shl i32 %e1, 8
+  %s2 = shl i32 %e2, 16
+  %s3 = shl i32 %e3, 24
+  %o1 = or i32 %e0, %s1
+  %o2 = or i32 %o1, %s2
+  %o3 = or i32 %o2, %s3
+  ret i32 %o3
+}
+
+; Big-endian byte order on LE target (requires bswap + mask).
+; Both targets should fold.
+define i16 @load_2_bytes_bswap_masked(ptr %p) {
+; LE-LABEL: load_2_bytes_bswap_masked:
+; LE:       ; %bb.0:
+; LE-NEXT:    ldrb w8, [x0, #1]
+; LE-NEXT:    ldrb w9, [x0]
+; LE-NEXT:    and w8, w8, #0x3
+; LE-NEXT:    orr w0, w8, w9, lsl #8
+; LE-NEXT:    ret
+;
+; BE-LABEL: load_2_bytes_bswap_masked:
+; BE:       // %bb.0:
+; BE-NEXT:    ldrb w8, [x0, #1]
+; BE-NEXT:    ldrb w9, [x0]
+; BE-NEXT:    and w8, w8, #0x3
+; BE-NEXT:    orr w0, w8, w9, lsl #8
+; BE-NEXT:    ret
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %hi.m = and i8 %hi, 3
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %lo.shift = shl i16 %lo.ext, 8
+  %result = or i16 %lo.shift, %hi.ext
+  ret i16 %result
+}
+
+; --- Negative tests (should NOT fold) ---
+
+; Non-adjacent loads (gap between them).
+define i16 @negative_non_adjacent(ptr %p) {
+; LE-LABEL: negative_non_adjacent:
+; LE:       ; %bb.0:
+; LE-NEXT:    ldrb w8, [x0]
+; LE-NEXT:    ldrb w9, [x0, #2]
+; LE-NEXT:    bfi w8, w9, #8, #2
+; LE-NEXT:    mov w0, w8
+; LE-NEXT:    ret
+;
+; BE-LABEL: negative_non_adjacent:
+; BE:       // %bb.0:
+; BE-NEXT:    ldrb w8, [x0]
+; BE-NEXT:    ldrb w9, [x0, #2]
+; BE-NEXT:    bfi w8, w9, #8, #2
+; BE-NEXT:    mov w0, w8
+; BE-NEXT:    ret
+  %p2 = getelementptr inbounds i8, ptr %p, i64 2
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p2, align 1
+  %hi.m = and i8 %hi, 3
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Volatile load cannot be combined.
+define i16 @negative_volatile(ptr %p) {
+; LE-LABEL: negative_volatile:
+; LE:       ; %bb.0:
+; LE-NEXT:    ldrb w8, [x0]
+; LE-NEXT:    ldrb w9, [x0, #1]
+; LE-NEXT:    bfi w8, w9, #8, #2
+; LE-NEXT:    mov w0, w8
+; LE-NEXT:    ret
+;
+; BE-LABEL: negative_volatile:
+; BE:       // %bb.0:
+; BE-NEXT:    ldrb w8, [x0]
+; BE-NEXT:    ldrb w9, [x0, #1]
+; BE-NEXT:    bfi w8, w9, #8, #2
+; BE-NEXT:    mov w0, w8
+; BE-NEXT:    ret
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load volatile i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %hi.m = and i8 %hi, 3
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Non-constant mask.
+define i16 @negative_variable_mask(ptr %p, i8 %mask) {
+; LE-LABEL: negative_variable_mask:
+; LE:       ; %bb.0:
+; LE-NEXT:    ldrb w8, [x0, #1]
+; LE-NEXT:    ldrb w9, [x0]
+; LE-NEXT:    and w8, w8, w1
+; LE-NEXT:    orr w0, w9, w8, lsl #8
+; LE-NEXT:    ret
+;
+; BE-LABEL: negative_variable_mask:
+; BE:       // %bb.0:
+; BE-NEXT:    ldrb w8, [x0, #1]
+; BE-NEXT:    ldrb w9, [x0]
+; BE-NEXT:    and w8, w8, w1
+; BE-NEXT:    orr w0, w9, w8, lsl #8
+; BE-NEXT:    ret
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %hi.m = and i8 %hi, %mask
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Multi-use: AND result is used elsewhere. The one-use check in
+; calculateByteProvider should prevent combining since the AND can't be removed.
+declare void @use.i8(i8)
+define i16 @negative_multiuse_and(ptr %p) {
+; LE-LABEL: negative_multiuse_and:
+; LE:       ; %bb.0:
+; LE-NEXT:    stp x20, x19, [sp, #-32]! ; 16-byte Folded Spill
+; LE-NEXT:    stp x29, x30, [sp, #16] ; 16-byte Folded Spill
+; LE-NEXT:    .cfi_def_cfa_offset 32
+; LE-NEXT:    .cfi_offset w30, -8
+; LE-NEXT:    .cfi_offset w29, -16
+; LE-NEXT:    .cfi_offset w19, -24
+; LE-NEXT:    .cfi_offset w20, -32
+; LE-NEXT:    ldrb w20, [x0, #1]
+; LE-NEXT:    ldrb w19, [x0]
+; LE-NEXT:    and w0, w20, #0x3
+; LE-NEXT:    bl _use.i8
+; LE-NEXT:    bfi w19, w20, #8, #2
+; LE-NEXT:    ldp x29, x30, [sp, #16] ; 16-byte Folded Reload
+; LE-NEXT:    mov w0, w19
+; LE-NEXT:    ldp x20, x19, [sp], #32 ; 16-byte Folded Reload
+; LE-NEXT:    ret
+;
+; BE-LABEL: negative_multiuse_and:
+; BE:       // %bb.0:
+; BE-NEXT:    str x30, [sp, #-32]! // 8-byte Folded Spill
+; BE-NEXT:    stp x20, x19, [sp, #16] // 16-byte Folded Spill
+; BE-NEXT:    .cfi_def_cfa_offset 32
+; BE-NEXT:    .cfi_offset w19, -8
+; BE-NEXT:    .cfi_offset w20, -16
+; BE-NEXT:    .cfi_offset w30, -32
+; BE-NEXT:    ldrb w20, [x0, #1]
+; BE-NEXT:    ldrb w19, [x0]
+; BE-NEXT:    and w0, w20, #0x3
+; BE-NEXT:    bl use.i8
+; BE-NEXT:    bfi w19, w20, #8, #2
+; BE-NEXT:    mov w0, w19
+; BE-NEXT:    ldp x20, x19, [sp, #16] // 16-byte Folded Reload
+; BE-NEXT:    ldr x30, [sp], #32 // 8-byte Folded Reload
+; BE-NEXT:    ret
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %hi.m = and i8 %hi, 3
+  call void @use.i8(i8 %hi.m)
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
diff --git a/llvm/test/CodeGen/X86/load-combine-masked.ll b/llvm/test/CodeGen/X86/load-combine-masked.ll
new file mode 100644
index 0000000000000..8c990d1939063
--- /dev/null
+++ b/llvm/test/CodeGen/X86/load-combine-masked.ll
@@ -0,0 +1,257 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu < %s | FileCheck %s
+
+; Test that consecutive byte loads combined via OR with AND masks on individual
+; loads are folded into a single wider load + mask at the DAG level.
+; This is the pattern from manual bitfield extraction code.
+; https://github.com/llvm/llvm-project/issues/191193
+
+; --- Positive tests (should fold to wide load + mask) ---
+
+; High byte masked.
+define i16 @load_2_bytes_high_masked(ptr %p) {
+; CHECK-LABEL: load_2_bytes_high_masked:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movzbl (%rdi), %ecx
+; CHECK-NEXT:    movzbl 1(%rdi), %eax
+; CHECK-NEXT:    andl $3, %eax
+; CHECK-NEXT:    shll $8, %eax
+; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    # kill: def $ax killed $ax killed $eax
+; CHECK-NEXT:    retq
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %hi.m = and i8 %hi, 3
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Low byte masked.
+define i16 @load_2_bytes_low_masked(ptr %p) {
+; CHECK-LABEL: load_2_bytes_low_masked:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movzbl (%rdi), %ecx
+; CHECK-NEXT:    andl $63, %ecx
+; CHECK-NEXT:    movzbl 1(%rdi), %eax
+; CHECK-NEXT:    shll $8, %eax
+; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    # kill: def $ax killed $ax killed $eax
+; CHECK-NEXT:    retq
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %lo.m = and i8 %lo, 63
+  %lo.ext = zext i8 %lo.m to i16
+  %hi.ext = zext i8 %hi to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Both bytes masked.
+define i16 @load_2_bytes_both_masked(ptr %p) {
+; CHECK-LABEL: load_2_bytes_both_masked:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movzbl (%rdi), %ecx
+; CHECK-NEXT:    movzbl 1(%rdi), %eax
+; CHECK-NEXT:    andl $15, %ecx
+; CHECK-NEXT:    andl $3, %eax
+; CHECK-NEXT:    shll $8, %eax
+; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    # kill: def $ax killed $ax killed $eax
+; CHECK-NEXT:    retq
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %lo.m = and i8 %lo, 15
+  %hi.m = and i8 %hi, 3
+  %lo.ext = zext i8 %lo.m to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Four bytes, last byte masked.
+define i32 @load_4_bytes_last_masked(ptr %p) {
+; CHECK-LABEL: load_4_bytes_last_masked:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movzbl 2(%rdi), %ecx
+; CHECK-NEXT:    movzbl 3(%rdi), %edx
+; CHECK-NEXT:    andl $63, %edx
+; CHECK-NEXT:    shll $16, %ecx
+; CHECK-NEXT:    shll $24, %edx
+; CHECK-NEXT:    movzwl (%rdi), %eax
+; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    orl %edx, %eax
+; CHECK-NEXT:    retq
+  %p1 = getelementptr i8, ptr %p, i64 1
+  %p2 = getelementptr i8, ptr %p, i64 2
+  %p3 = getelementptr i8, ptr %p, i64 3
+  %l0 = load i8, ptr %p, align 1
+  %l1 = load i8, ptr %p1, align 1
+  %l2 = load i8, ptr %p2, align 1
+  %l3 = load i8, ptr %p3, align 1
+  %l3.m = and i8 %l3, 63
+  %e0 = zext i8 %l0 to i32
+  %e1 = zext i8 %l1 to i32
+  %e2 = zext i8 %l2 to i32
+  %e3 = zext i8 %l3.m to i32
+  %s1 = shl i32 %e1, 8
+  %s2 = shl i32 %e2, 16
+  %s3 = shl i32 %e3, 24
+  %o1 = or i32 %e0, %s1
+  %o2 = or i32 %o1, %s2
+  %o3 = or i32 %o2, %s3
+  ret i32 %o3
+}
+
+; Big-endian byte order on LE target (requires bswap + mask).
+define i32 @load_4_bytes_bswap_masked(ptr %p) {
+; CHECK-LABEL: load_4_bytes_bswap_masked:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movzbl 1(%rdi), %ecx
+; CHECK-NEXT:    movzbl 2(%rdi), %eax
+; CHECK-NEXT:    movzbl 3(%rdi), %edx
+; CHECK-NEXT:    andl $63, %edx
+; CHECK-NEXT:    movzbl (%rdi), %esi
+; CHECK-NEXT:    shll $24, %esi
+; CHECK-NEXT:    shll $16, %ecx
+; CHECK-NEXT:    orl %esi, %ecx
+; CHECK-NEXT:    shll $8, %eax
+; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    orl %edx, %eax
+; CHECK-NEXT:    retq
+  %p1 = getelementptr i8, ptr %p, i64 1
+  %p2 = getelementptr i8, ptr %p, i64 2
+  %p3 = getelementptr i8, ptr %p, i64 3
+  %b0 = load i8, ptr %p, align 1
+  %b1 = load i8, ptr %p1, align 1
+  %b2 = load i8, ptr %p2, align 1
+  %b3 = load i8, ptr %p3, align 1
+  %b3.m = and i8 %b3, 63
+  %e0 = zext i8 %b0 to i32
+  %e1 = zext i8 %b1 to i32
+  %e2 = zext i8 %b2 to i32
+  %e3 = zext i8 %b3.m to i32
+  %s0 = shl i32 %e0, 24
+  %s1 = shl i32 %e1, 16
+  %s2 = shl i32 %e2, 8
+  %o1 = or i32 %s0, %s1
+  %o2 = or i32 %o1, %s2
+  %result = or i32 %o2, %e3
+  ret i32 %result
+}
+
+; --- Negative tests (should NOT fold) ---
+
+; Non-adjacent loads (gap between them).
+define i16 @negative_non_adjacent(ptr %p) {
+; CHECK-LABEL: negative_non_adjacent:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movzbl (%rdi), %ecx
+; CHECK-NEXT:    movzbl 2(%rdi), %eax
+; CHECK-NEXT:    andl $3, %eax
+; CHECK-NEXT:    shll $8, %eax
+; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    # kill: def $ax killed $ax killed $eax
+; CHECK-NEXT:    retq
+  %p2 = getelementptr inbounds i8, ptr %p, i64 2
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p2, align 1
+  %hi.m = and i8 %hi, 3
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Volatile load cannot be combined.
+define i16 @negative_volatile(ptr %p) {
+; CHECK-LABEL: negative_volatile:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movzbl (%rdi), %ecx
+; CHECK-NEXT:    movzbl 1(%rdi), %eax
+; CHECK-NEXT:    andl $3, %eax
+; CHECK-NEXT:    shll $8, %eax
+; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    # kill: def $ax killed $ax killed $eax
+; CHECK-NEXT:    retq
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load volatile i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %hi.m = and i8 %hi, 3
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Non-constant mask.
+define i16 @negative_variable_mask(ptr %p, i8 %mask) {
+; CHECK-LABEL: negative_variable_mask:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movzbl (%rdi), %ecx
+; CHECK-NEXT:    movzbl 1(%rdi), %eax
+; CHECK-NEXT:    andl %esi, %eax
+; CHECK-NEXT:    shll $8, %eax
+; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    # kill: def $ax killed $ax killed $eax
+; CHECK-NEXT:    retq
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %hi.m = and i8 %hi, %mask
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
+; Multi-use: AND result is used elsewhere. The one-use check in
+; calculateByteProvider should prevent combining since the AND can't be removed.
+declare void @use.i8(i8)
+define i16 @negative_multiuse_and(ptr %p) {
+; CHECK-LABEL: negative_multiuse_and:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    pushq %rbp
+; CHECK-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NEXT:    pushq %rbx
+; CHECK-NEXT:    .cfi_def_cfa_offset 24
+; CHECK-NEXT:    pushq %rax
+; CHECK-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-NEXT:    .cfi_offset %rbx, -24
+; CHECK-NEXT:    .cfi_offset %rbp, -16
+; CHECK-NEXT:    movzbl (%rdi), %ebp
+; CHECK-NEXT:    movzbl 1(%rdi), %ebx
+; CHECK-NEXT:    andl $3, %ebx
+; CHECK-NEXT:    movl %ebx, %edi
+; CHECK-NEXT:    callq use.i8 at PLT
+; CHECK-NEXT:    shll $8, %ebx
+; CHECK-NEXT:    orl %ebp, %ebx
+; CHECK-NEXT:    movl %ebx, %eax
+; CHECK-NEXT:    addq $8, %rsp
+; CHECK-NEXT:    .cfi_def_cfa_offset 24
+; CHECK-NEXT:    popq %rbx
+; CHECK-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NEXT:    popq %rbp
+; CHECK-NEXT:    .cfi_def_cfa_offset 8
+; CHECK-NEXT:    retq
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %hi.m = and i8 %hi, 3
+  call void @use.i8(i8 %hi.m)
+  %lo.ext = zext i8 %lo to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}

>From 2f43ad58d7b54c8ae30ee0d9d8d3da1db33265ff Mon Sep 17 00:00:00 2001
From: mkurek7 <mkurek7 at bloomberg.net>
Date: Thu, 28 May 2026 14:24:57 -0400
Subject: [PATCH 2/4] [LLVM][DAGCombiner] Teach MatchLoadCombine to look
 through AND masks

Extend calculateByteProvider() to handle ISD::AND with constant masks
when combining consecutive byte loads into a wider load. Previously,
any AND node in the load chain caused MatchLoadCombine to bail out.

For the bitfield extraction pattern from the issue:
  ldrb w8, [x0]        ; 4 instructions
  ldrb w9, [x0, #1]
  bfi w8, w9, #8, #2
  mov w0, w8
becomes:
  ldrh w8, [x0]        ; 2 instructions
  and w0, w8, #0x3ff

The mask is applied after any required byte swap, which is correct
because ByteMasks[i] is recorded in final-result byte order.

Fixes https://github.com/llvm/llvm-project/issues/191193

Assisted-by: Claude <noreply at anthropic.com>
---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 74 ++++++++++++++++---
 .../CodeGen/AArch64/load-combine-masked.ll    | 55 ++++++--------
 llvm/test/CodeGen/X86/load-combine-masked.ll  | 46 +++---------
 3 files changed, 94 insertions(+), 81 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 778153496ca71..0aae40d3d4271 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -9539,7 +9539,7 @@ using SDByteProvider = ByteProvider<SDNode *>;
 static std::optional<SDByteProvider>
 calculateByteProvider(SDValue Op, unsigned Index, unsigned Depth,
                       std::optional<uint64_t> VectorIndex,
-                      unsigned StartingIndex = 0) {
+                      unsigned StartingIndex = 0, uint8_t *ByteMask = nullptr) {
 
   // Typical i64 by i8 pattern requires recursion up to 8 calls depth
   if (Depth == 10)
@@ -9565,12 +9565,12 @@ calculateByteProvider(SDValue Op, unsigned Index, unsigned Depth,
 
   switch (Op.getOpcode()) {
   case ISD::OR: {
-    auto LHS =
-        calculateByteProvider(Op->getOperand(0), Index, Depth + 1, VectorIndex);
+    auto LHS = calculateByteProvider(Op->getOperand(0), Index, Depth + 1,
+                                     VectorIndex, StartingIndex, ByteMask);
     if (!LHS)
       return std::nullopt;
-    auto RHS =
-        calculateByteProvider(Op->getOperand(1), Index, Depth + 1, VectorIndex);
+    auto RHS = calculateByteProvider(Op->getOperand(1), Index, Depth + 1,
+                                     VectorIndex, StartingIndex, ByteMask);
     if (!RHS)
       return std::nullopt;
 
@@ -9597,7 +9597,7 @@ calculateByteProvider(SDValue Op, unsigned Index, unsigned Depth,
     return Index < ByteShift
                ? SDByteProvider::getConstantZero()
                : calculateByteProvider(Op->getOperand(0), Index - ByteShift,
-                                       Depth + 1, VectorIndex, Index);
+                                       Depth + 1, VectorIndex, Index, ByteMask);
   }
   case ISD::ANY_EXTEND:
   case ISD::SIGN_EXTEND:
@@ -9614,11 +9614,39 @@ calculateByteProvider(SDValue Op, unsigned Index, unsigned Depth,
                        SDByteProvider::getConstantZero())
                  : std::nullopt;
     return calculateByteProvider(NarrowOp, Index, Depth + 1, VectorIndex,
-                                 StartingIndex);
+                                 StartingIndex, ByteMask);
   }
   case ISD::BSWAP:
     return calculateByteProvider(Op->getOperand(0), ByteWidth - Index - 1,
-                                 Depth + 1, VectorIndex, StartingIndex);
+                                 Depth + 1, VectorIndex, StartingIndex,
+                                 ByteMask);
+  case ISD::AND: {
+    unsigned MaskIdx = 1;
+    auto MaskOp = dyn_cast<ConstantSDNode>(Op->getOperand(MaskIdx));
+    if (!MaskOp) {
+      MaskIdx = 0;
+      MaskOp = dyn_cast<ConstantSDNode>(Op->getOperand(MaskIdx));
+      if (!MaskOp)
+        return std::nullopt;
+    }
+
+    uint64_t Mask = MaskOp->getZExtValue();
+    uint8_t MaskByte = (Mask >> (Index * 8)) & 0xFF;
+
+    if (MaskByte == 0x00)
+      return SDByteProvider::getConstantZero();
+
+    auto Result =
+        calculateByteProvider(Op->getOperand(1 - MaskIdx), Index, Depth + 1,
+                              VectorIndex, StartingIndex, ByteMask);
+    if (!Result)
+      return std::nullopt;
+
+    if (MaskByte != 0xFF && ByteMask)
+      ByteMask[StartingIndex] &= MaskByte;
+
+    return Result;
+  }
   case ISD::EXTRACT_VECTOR_ELT: {
     auto OffsetOp = dyn_cast<ConstantSDNode>(Op->getOperand(1));
     if (!OffsetOp)
@@ -9648,7 +9676,7 @@ calculateByteProvider(SDValue Op, unsigned Index, unsigned Depth,
       return std::nullopt;
 
     return calculateByteProvider(Op->getOperand(0), Index, Depth + 1,
-                                 VectorIndex, StartingIndex);
+                                 VectorIndex, StartingIndex, ByteMask);
   }
   case ISD::LOAD: {
     auto L = cast<LoadSDNode>(Op.getNode());
@@ -9999,11 +10027,12 @@ SDValue DAGCombiner::MatchLoadCombine(SDNode *N) {
   // Check if all the bytes of the OR we are looking at are loaded from the same
   // base address. Collect bytes offsets from Base address in ByteOffsets.
   SmallVector<int64_t, 8> ByteOffsets(ByteWidth);
+  SmallVector<uint8_t, 8> ByteMasks(ByteWidth, 0xFF);
   unsigned ZeroExtendedBytes = 0;
   for (int i = ByteWidth - 1; i >= 0; --i) {
     auto P =
         calculateByteProvider(SDValue(N, 0), i, 0, /*VectorIndex*/ std::nullopt,
-                              /*StartingIndex*/ i);
+                              /*StartingIndex*/ i, ByteMasks.data());
     if (!P)
       return SDValue();
 
@@ -10139,15 +10168,36 @@ SDValue DAGCombiner::MatchLoadCombine(SDNode *N) {
   for (LoadSDNode *L : Loads)
     DAG.makeEquivalentMemoryOrdering(L, NewLoad);
 
-  if (!NeedsBswap)
+  // Apply combined mask if any bytes were partially masked by AND operations.
+  bool HasPartialMask = false;
+  uint64_t CombinedMask = 0;
+  for (unsigned i = 0; i < ByteWidth; ++i) {
+    CombinedMask |= (uint64_t)ByteMasks[i] << (i * 8);
+    if (ByteMasks[i] != 0xFF)
+      HasPartialMask = true;
+  }
+
+  if (!NeedsBswap) {
+    if (HasPartialMask)
+      NewLoad = DAG.getNode(ISD::AND, SDLoc(N), VT, NewLoad,
+                            DAG.getConstant(CombinedMask, SDLoc(N), VT));
     return NewLoad;
+  }
 
   SDValue ShiftedLoad =
       NeedsZext ? DAG.getNode(ISD::SHL, SDLoc(N), VT, NewLoad,
                               DAG.getShiftAmountConstant(ZeroExtendedBytes * 8,
                                                          VT, SDLoc(N)))
                 : NewLoad;
-  return DAG.getNode(ISD::BSWAP, SDLoc(N), VT, ShiftedLoad);
+  SDValue Result = DAG.getNode(ISD::BSWAP, SDLoc(N), VT, ShiftedLoad);
+
+  // The mask is built in final-result byte order (ByteMasks[i] corresponds to
+  // byte i of the result), so it is correct to apply after the bswap.
+  if (HasPartialMask)
+    Result = DAG.getNode(ISD::AND, SDLoc(N), VT, Result,
+                         DAG.getConstant(CombinedMask, SDLoc(N), VT));
+
+  return Result;
 }
 
 // If the target has andn, bsl, or a similar bit-select instruction,
diff --git a/llvm/test/CodeGen/AArch64/load-combine-masked.ll b/llvm/test/CodeGen/AArch64/load-combine-masked.ll
index 63a96ab931920..1e3a897c8da22 100644
--- a/llvm/test/CodeGen/AArch64/load-combine-masked.ll
+++ b/llvm/test/CodeGen/AArch64/load-combine-masked.ll
@@ -13,18 +13,15 @@
 define i16 @load_2_bytes_high_masked(ptr %p) {
 ; LE-LABEL: load_2_bytes_high_masked:
 ; LE:       ; %bb.0:
-; LE-NEXT:    ldrb w8, [x0]
-; LE-NEXT:    ldrb w9, [x0, #1]
-; LE-NEXT:    bfi w8, w9, #8, #2
-; LE-NEXT:    mov w0, w8
+; LE-NEXT:    ldrh w8, [x0]
+; LE-NEXT:    and w0, w8, #0x3ff
 ; LE-NEXT:    ret
 ;
 ; BE-LABEL: load_2_bytes_high_masked:
 ; BE:       // %bb.0:
-; BE-NEXT:    ldrb w8, [x0]
-; BE-NEXT:    ldrb w9, [x0, #1]
-; BE-NEXT:    bfi w8, w9, #8, #2
-; BE-NEXT:    mov w0, w8
+; BE-NEXT:    ldrh w8, [x0]
+; BE-NEXT:    rev w8, w8
+; BE-NEXT:    ubfx w0, w8, #16, #10
 ; BE-NEXT:    ret
   %p1 = getelementptr inbounds i8, ptr %p, i64 1
   %lo = load i8, ptr %p, align 1
@@ -41,18 +38,16 @@ define i16 @load_2_bytes_high_masked(ptr %p) {
 define i16 @load_2_bytes_low_masked(ptr %p) {
 ; LE-LABEL: load_2_bytes_low_masked:
 ; LE:       ; %bb.0:
-; LE-NEXT:    ldrb w8, [x0]
-; LE-NEXT:    ldrb w9, [x0, #1]
-; LE-NEXT:    and w8, w8, #0x3f
-; LE-NEXT:    orr w0, w8, w9, lsl #8
+; LE-NEXT:    ldrh w8, [x0]
+; LE-NEXT:    and w0, w8, #0xffffff3f
 ; LE-NEXT:    ret
 ;
 ; BE-LABEL: load_2_bytes_low_masked:
 ; BE:       // %bb.0:
-; BE-NEXT:    ldrb w8, [x0]
-; BE-NEXT:    ldrb w9, [x0, #1]
-; BE-NEXT:    and w8, w8, #0x3f
-; BE-NEXT:    orr w0, w8, w9, lsl #8
+; BE-NEXT:    ldrh w8, [x0]
+; BE-NEXT:    rev w8, w8
+; BE-NEXT:    lsr w8, w8, #16
+; BE-NEXT:    and w0, w8, #0xffffff3f
 ; BE-NEXT:    ret
   %p1 = getelementptr inbounds i8, ptr %p, i64 1
   %lo = load i8, ptr %p, align 1
@@ -97,21 +92,15 @@ define i16 @load_2_bytes_both_masked(ptr %p) {
 define i32 @load_4_bytes_last_masked(ptr %p) {
 ; LE-LABEL: load_4_bytes_last_masked:
 ; LE:       ; %bb.0:
-; LE-NEXT:    ldrb w8, [x0, #2]
-; LE-NEXT:    ldrh w9, [x0]
-; LE-NEXT:    ldrb w10, [x0, #3]
-; LE-NEXT:    orr w0, w9, w8, lsl #16
-; LE-NEXT:    bfi w0, w10, #24, #6
+; LE-NEXT:    ldr w8, [x0]
+; LE-NEXT:    and w0, w8, #0x3fffffff
 ; LE-NEXT:    ret
 ;
 ; BE-LABEL: load_4_bytes_last_masked:
 ; BE:       // %bb.0:
-; BE-NEXT:    ldrh w8, [x0]
-; BE-NEXT:    ldrb w9, [x0, #2]
-; BE-NEXT:    ldrb w10, [x0, #3]
+; BE-NEXT:    ldr w8, [x0]
 ; BE-NEXT:    rev w8, w8
-; BE-NEXT:    extr w0, w9, w8, #16
-; BE-NEXT:    bfi w0, w10, #24, #6
+; BE-NEXT:    and w0, w8, #0x3fffffff
 ; BE-NEXT:    ret
   %p1 = getelementptr i8, ptr %p, i64 1
   %p2 = getelementptr i8, ptr %p, i64 2
@@ -139,18 +128,16 @@ define i32 @load_4_bytes_last_masked(ptr %p) {
 define i16 @load_2_bytes_bswap_masked(ptr %p) {
 ; LE-LABEL: load_2_bytes_bswap_masked:
 ; LE:       ; %bb.0:
-; LE-NEXT:    ldrb w8, [x0, #1]
-; LE-NEXT:    ldrb w9, [x0]
-; LE-NEXT:    and w8, w8, #0x3
-; LE-NEXT:    orr w0, w8, w9, lsl #8
+; LE-NEXT:    ldrh w8, [x0]
+; LE-NEXT:    rev w8, w8
+; LE-NEXT:    lsr w8, w8, #16
+; LE-NEXT:    and w0, w8, #0xffffff03
 ; LE-NEXT:    ret
 ;
 ; BE-LABEL: load_2_bytes_bswap_masked:
 ; BE:       // %bb.0:
-; BE-NEXT:    ldrb w8, [x0, #1]
-; BE-NEXT:    ldrb w9, [x0]
-; BE-NEXT:    and w8, w8, #0x3
-; BE-NEXT:    orr w0, w8, w9, lsl #8
+; BE-NEXT:    ldrh w8, [x0]
+; BE-NEXT:    and w0, w8, #0xffffff03
 ; BE-NEXT:    ret
   %p1 = getelementptr inbounds i8, ptr %p, i64 1
   %lo = load i8, ptr %p, align 1
diff --git a/llvm/test/CodeGen/X86/load-combine-masked.ll b/llvm/test/CodeGen/X86/load-combine-masked.ll
index 8c990d1939063..eb7d23b0ed6b9 100644
--- a/llvm/test/CodeGen/X86/load-combine-masked.ll
+++ b/llvm/test/CodeGen/X86/load-combine-masked.ll
@@ -12,11 +12,8 @@
 define i16 @load_2_bytes_high_masked(ptr %p) {
 ; CHECK-LABEL: load_2_bytes_high_masked:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    movzbl (%rdi), %ecx
-; CHECK-NEXT:    movzbl 1(%rdi), %eax
-; CHECK-NEXT:    andl $3, %eax
-; CHECK-NEXT:    shll $8, %eax
-; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    movzwl (%rdi), %eax
+; CHECK-NEXT:    andl $1023, %eax # imm = 0x3FF
 ; CHECK-NEXT:    # kill: def $ax killed $ax killed $eax
 ; CHECK-NEXT:    retq
   %p1 = getelementptr inbounds i8, ptr %p, i64 1
@@ -34,11 +31,8 @@ define i16 @load_2_bytes_high_masked(ptr %p) {
 define i16 @load_2_bytes_low_masked(ptr %p) {
 ; CHECK-LABEL: load_2_bytes_low_masked:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    movzbl (%rdi), %ecx
-; CHECK-NEXT:    andl $63, %ecx
-; CHECK-NEXT:    movzbl 1(%rdi), %eax
-; CHECK-NEXT:    shll $8, %eax
-; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    movzwl (%rdi), %eax
+; CHECK-NEXT:    andl $65343, %eax # imm = 0xFF3F
 ; CHECK-NEXT:    # kill: def $ax killed $ax killed $eax
 ; CHECK-NEXT:    retq
   %p1 = getelementptr inbounds i8, ptr %p, i64 1
@@ -56,12 +50,8 @@ define i16 @load_2_bytes_low_masked(ptr %p) {
 define i16 @load_2_bytes_both_masked(ptr %p) {
 ; CHECK-LABEL: load_2_bytes_both_masked:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    movzbl (%rdi), %ecx
-; CHECK-NEXT:    movzbl 1(%rdi), %eax
-; CHECK-NEXT:    andl $15, %ecx
-; CHECK-NEXT:    andl $3, %eax
-; CHECK-NEXT:    shll $8, %eax
-; CHECK-NEXT:    orl %ecx, %eax
+; CHECK-NEXT:    movzwl (%rdi), %eax
+; CHECK-NEXT:    andl $783, %eax # imm = 0x30F
 ; CHECK-NEXT:    # kill: def $ax killed $ax killed $eax
 ; CHECK-NEXT:    retq
   %p1 = getelementptr inbounds i8, ptr %p, i64 1
@@ -80,14 +70,8 @@ define i16 @load_2_bytes_both_masked(ptr %p) {
 define i32 @load_4_bytes_last_masked(ptr %p) {
 ; CHECK-LABEL: load_4_bytes_last_masked:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    movzbl 2(%rdi), %ecx
-; CHECK-NEXT:    movzbl 3(%rdi), %edx
-; CHECK-NEXT:    andl $63, %edx
-; CHECK-NEXT:    shll $16, %ecx
-; CHECK-NEXT:    shll $24, %edx
-; CHECK-NEXT:    movzwl (%rdi), %eax
-; CHECK-NEXT:    orl %ecx, %eax
-; CHECK-NEXT:    orl %edx, %eax
+; CHECK-NEXT:    movl $1073741823, %eax # imm = 0x3FFFFFFF
+; CHECK-NEXT:    andl (%rdi), %eax
 ; CHECK-NEXT:    retq
   %p1 = getelementptr i8, ptr %p, i64 1
   %p2 = getelementptr i8, ptr %p, i64 2
@@ -114,17 +98,9 @@ define i32 @load_4_bytes_last_masked(ptr %p) {
 define i32 @load_4_bytes_bswap_masked(ptr %p) {
 ; CHECK-LABEL: load_4_bytes_bswap_masked:
 ; CHECK:       # %bb.0:
-; CHECK-NEXT:    movzbl 1(%rdi), %ecx
-; CHECK-NEXT:    movzbl 2(%rdi), %eax
-; CHECK-NEXT:    movzbl 3(%rdi), %edx
-; CHECK-NEXT:    andl $63, %edx
-; CHECK-NEXT:    movzbl (%rdi), %esi
-; CHECK-NEXT:    shll $24, %esi
-; CHECK-NEXT:    shll $16, %ecx
-; CHECK-NEXT:    orl %esi, %ecx
-; CHECK-NEXT:    shll $8, %eax
-; CHECK-NEXT:    orl %ecx, %eax
-; CHECK-NEXT:    orl %edx, %eax
+; CHECK-NEXT:    movl (%rdi), %eax
+; CHECK-NEXT:    bswapl %eax
+; CHECK-NEXT:    andl $-193, %eax
 ; CHECK-NEXT:    retq
   %p1 = getelementptr i8, ptr %p, i64 1
   %p2 = getelementptr i8, ptr %p, i64 2

>From 78709d442ee6f369e9d9e52256f74335c2a4a8a4 Mon Sep 17 00:00:00 2001
From: mkurek7 <mkurek7 at bloomberg.net>
Date: Thu, 28 May 2026 15:09:05 -0400
Subject: [PATCH 3/4] [LLVM][DAGCombiner] Fix mask leak for ConstantZero
 providers and add non-contiguous mask test

Don't update ByteMask when the AND subtree resolves to ConstantZero,
since the OR handler may discard that side in favor of the other
operand. Also add a test with non-low-bit masks (0xA5, 0x5A) to
demonstrate we handle arbitrary bit patterns.

Assisted-by: Claude <noreply at anthropic.com>
---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp |  5 +++-
 .../CodeGen/AArch64/load-combine-masked.ll    | 28 +++++++++++++++++++
 llvm/test/CodeGen/X86/load-combine-masked.ll  | 20 +++++++++++++
 3 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index 0aae40d3d4271..f518211ebb20b 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -9642,7 +9642,10 @@ calculateByteProvider(SDValue Op, unsigned Index, unsigned Depth,
     if (!Result)
       return std::nullopt;
 
-    if (MaskByte != 0xFF && ByteMask)
+    // Only record the mask if this byte is actually provided (not zero).
+    // A ConstantZero result may be discarded by the OR handler in favor of
+    // the other operand, so writing the mask here would corrupt ByteMask.
+    if (MaskByte != 0xFF && ByteMask && !Result->isConstantZero())
       ByteMask[StartingIndex] &= MaskByte;
 
     return Result;
diff --git a/llvm/test/CodeGen/AArch64/load-combine-masked.ll b/llvm/test/CodeGen/AArch64/load-combine-masked.ll
index 1e3a897c8da22..137dbb920a27e 100644
--- a/llvm/test/CodeGen/AArch64/load-combine-masked.ll
+++ b/llvm/test/CodeGen/AArch64/load-combine-masked.ll
@@ -88,6 +88,34 @@ define i16 @load_2_bytes_both_masked(ptr %p) {
   ret i16 %result
 }
 
+; Non-low-bit masks (high bits and non-contiguous).
+define i16 @load_2_bytes_noncontiguous_masked(ptr %p) {
+; LE-LABEL: load_2_bytes_noncontiguous_masked:
+; LE:       ; %bb.0:
+; LE-NEXT:    ldrh w8, [x0]
+; LE-NEXT:    mov w9, #23205 ; =0x5aa5
+; LE-NEXT:    and w0, w8, w9
+; LE-NEXT:    ret
+;
+; BE-LABEL: load_2_bytes_noncontiguous_masked:
+; BE:       // %bb.0:
+; BE-NEXT:    ldrh w9, [x0]
+; BE-NEXT:    mov w8, #23205 // =0x5aa5
+; BE-NEXT:    rev w9, w9
+; BE-NEXT:    and w0, w8, w9, lsr #16
+; BE-NEXT:    ret
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %lo.m = and i8 %lo, 165
+  %hi.m = and i8 %hi, 90
+  %lo.ext = zext i8 %lo.m to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
 ; Four bytes, last byte masked.
 define i32 @load_4_bytes_last_masked(ptr %p) {
 ; LE-LABEL: load_4_bytes_last_masked:
diff --git a/llvm/test/CodeGen/X86/load-combine-masked.ll b/llvm/test/CodeGen/X86/load-combine-masked.ll
index eb7d23b0ed6b9..40c76c73bd3e2 100644
--- a/llvm/test/CodeGen/X86/load-combine-masked.ll
+++ b/llvm/test/CodeGen/X86/load-combine-masked.ll
@@ -66,6 +66,26 @@ define i16 @load_2_bytes_both_masked(ptr %p) {
   ret i16 %result
 }
 
+; Non-low-bit masks (high bits and non-contiguous).
+define i16 @load_2_bytes_noncontiguous_masked(ptr %p) {
+; CHECK-LABEL: load_2_bytes_noncontiguous_masked:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    movzwl (%rdi), %eax
+; CHECK-NEXT:    andl $23205, %eax # imm = 0x5AA5
+; CHECK-NEXT:    # kill: def $ax killed $ax killed $eax
+; CHECK-NEXT:    retq
+  %p1 = getelementptr inbounds i8, ptr %p, i64 1
+  %lo = load i8, ptr %p, align 1
+  %hi = load i8, ptr %p1, align 1
+  %lo.m = and i8 %lo, 165
+  %hi.m = and i8 %hi, 90
+  %lo.ext = zext i8 %lo.m to i16
+  %hi.ext = zext i8 %hi.m to i16
+  %hi.shift = shl i16 %hi.ext, 8
+  %result = or i16 %hi.shift, %lo.ext
+  ret i16 %result
+}
+
 ; Four bytes, last byte masked.
 define i32 @load_4_bytes_last_masked(ptr %p) {
 ; CHECK-LABEL: load_4_bytes_last_masked:

>From a625fe35774d9473232ad4743091d0a43ca69d46 Mon Sep 17 00:00:00 2001
From: mkurek7 <mkurek7 at bloomberg.net>
Date: Thu, 2 Jul 2026 10:44:27 -0400
Subject: [PATCH 4/4] [LLVM][DAGCombiner] Address review feedback and
 regenerate AMDGPU tests

- Only check operand 1 of AND for the constant mask (canonicalized to RHS)
- Use APInt::extractBitsAsZExtValue instead of getZExtValue + shift
- Pass ByteMask as MutableArrayRef<uint8_t> instead of a raw pointer
- Add nounwind to negative_multiuse_and to drop CFI noise from checks
- Regenerate AMDGPU load-{constant,global,local}-i16.ll: the combine now
  merges adjacent i16 loads into a single i32 load (fewer fetches/ALU ops)

Assisted-by: Claude <noreply at anthropic.com>
---
 llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp | 29 ++++----
 .../CodeGen/AArch64/load-combine-masked.ll    | 13 +---
 llvm/test/CodeGen/AMDGPU/load-constant-i16.ll | 38 +++++-----
 llvm/test/CodeGen/AMDGPU/load-global-i16.ll   | 71 ++++++++-----------
 llvm/test/CodeGen/AMDGPU/load-local-i16.ll    | 22 ++----
 llvm/test/CodeGen/X86/load-combine-masked.ll  | 10 +--
 6 files changed, 69 insertions(+), 114 deletions(-)

diff --git a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
index f518211ebb20b..33dc178e6793d 100644
--- a/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/DAGCombiner.cpp
@@ -9539,7 +9539,8 @@ using SDByteProvider = ByteProvider<SDNode *>;
 static std::optional<SDByteProvider>
 calculateByteProvider(SDValue Op, unsigned Index, unsigned Depth,
                       std::optional<uint64_t> VectorIndex,
-                      unsigned StartingIndex = 0, uint8_t *ByteMask = nullptr) {
+                      unsigned StartingIndex = 0,
+                      MutableArrayRef<uint8_t> ByteMask = {}) {
 
   // Typical i64 by i8 pattern requires recursion up to 8 calls depth
   if (Depth == 10)
@@ -9621,31 +9622,27 @@ calculateByteProvider(SDValue Op, unsigned Index, unsigned Depth,
                                  Depth + 1, VectorIndex, StartingIndex,
                                  ByteMask);
   case ISD::AND: {
-    unsigned MaskIdx = 1;
-    auto MaskOp = dyn_cast<ConstantSDNode>(Op->getOperand(MaskIdx));
-    if (!MaskOp) {
-      MaskIdx = 0;
-      MaskOp = dyn_cast<ConstantSDNode>(Op->getOperand(MaskIdx));
-      if (!MaskOp)
-        return std::nullopt;
-    }
+    // Constants are canonicalized to the RHS of AND, so only operand 1 needs
+    // to be checked.
+    auto *MaskOp = dyn_cast<ConstantSDNode>(Op->getOperand(1));
+    if (!MaskOp)
+      return std::nullopt;
 
-    uint64_t Mask = MaskOp->getZExtValue();
-    uint8_t MaskByte = (Mask >> (Index * 8)) & 0xFF;
+    uint8_t MaskByte =
+        MaskOp->getAPIntValue().extractBitsAsZExtValue(8, Index * 8);
 
     if (MaskByte == 0x00)
       return SDByteProvider::getConstantZero();
 
-    auto Result =
-        calculateByteProvider(Op->getOperand(1 - MaskIdx), Index, Depth + 1,
-                              VectorIndex, StartingIndex, ByteMask);
+    auto Result = calculateByteProvider(Op->getOperand(0), Index, Depth + 1,
+                                        VectorIndex, StartingIndex, ByteMask);
     if (!Result)
       return std::nullopt;
 
     // Only record the mask if this byte is actually provided (not zero).
     // A ConstantZero result may be discarded by the OR handler in favor of
     // the other operand, so writing the mask here would corrupt ByteMask.
-    if (MaskByte != 0xFF && ByteMask && !Result->isConstantZero())
+    if (MaskByte != 0xFF && !ByteMask.empty() && !Result->isConstantZero())
       ByteMask[StartingIndex] &= MaskByte;
 
     return Result;
@@ -10035,7 +10032,7 @@ SDValue DAGCombiner::MatchLoadCombine(SDNode *N) {
   for (int i = ByteWidth - 1; i >= 0; --i) {
     auto P =
         calculateByteProvider(SDValue(N, 0), i, 0, /*VectorIndex*/ std::nullopt,
-                              /*StartingIndex*/ i, ByteMasks.data());
+                              /*StartingIndex*/ i, ByteMasks);
     if (!P)
       return SDValue();
 
diff --git a/llvm/test/CodeGen/AArch64/load-combine-masked.ll b/llvm/test/CodeGen/AArch64/load-combine-masked.ll
index 137dbb920a27e..a19634835a075 100644
--- a/llvm/test/CodeGen/AArch64/load-combine-masked.ll
+++ b/llvm/test/CodeGen/AArch64/load-combine-masked.ll
@@ -267,18 +267,13 @@ define i16 @negative_variable_mask(ptr %p, i8 %mask) {
 ; Multi-use: AND result is used elsewhere. The one-use check in
 ; calculateByteProvider should prevent combining since the AND can't be removed.
 declare void @use.i8(i8)
-define i16 @negative_multiuse_and(ptr %p) {
+define i16 @negative_multiuse_and(ptr %p) nounwind {
 ; LE-LABEL: negative_multiuse_and:
 ; LE:       ; %bb.0:
 ; LE-NEXT:    stp x20, x19, [sp, #-32]! ; 16-byte Folded Spill
-; LE-NEXT:    stp x29, x30, [sp, #16] ; 16-byte Folded Spill
-; LE-NEXT:    .cfi_def_cfa_offset 32
-; LE-NEXT:    .cfi_offset w30, -8
-; LE-NEXT:    .cfi_offset w29, -16
-; LE-NEXT:    .cfi_offset w19, -24
-; LE-NEXT:    .cfi_offset w20, -32
 ; LE-NEXT:    ldrb w20, [x0, #1]
 ; LE-NEXT:    ldrb w19, [x0]
+; LE-NEXT:    stp x29, x30, [sp, #16] ; 16-byte Folded Spill
 ; LE-NEXT:    and w0, w20, #0x3
 ; LE-NEXT:    bl _use.i8
 ; LE-NEXT:    bfi w19, w20, #8, #2
@@ -291,10 +286,6 @@ define i16 @negative_multiuse_and(ptr %p) {
 ; BE:       // %bb.0:
 ; BE-NEXT:    str x30, [sp, #-32]! // 8-byte Folded Spill
 ; BE-NEXT:    stp x20, x19, [sp, #16] // 16-byte Folded Spill
-; BE-NEXT:    .cfi_def_cfa_offset 32
-; BE-NEXT:    .cfi_offset w19, -8
-; BE-NEXT:    .cfi_offset w20, -16
-; BE-NEXT:    .cfi_offset w30, -32
 ; BE-NEXT:    ldrb w20, [x0, #1]
 ; BE-NEXT:    ldrb w19, [x0]
 ; BE-NEXT:    and w0, w20, #0x3
diff --git a/llvm/test/CodeGen/AMDGPU/load-constant-i16.ll b/llvm/test/CodeGen/AMDGPU/load-constant-i16.ll
index 9e0870ad62358..c8f88ec63fb21 100644
--- a/llvm/test/CodeGen/AMDGPU/load-constant-i16.ll
+++ b/llvm/test/CodeGen/AMDGPU/load-constant-i16.ll
@@ -232,38 +232,32 @@ define amdgpu_kernel void @constant_load_v3i16(ptr addrspace(1) %out, ptr addrsp
 ;
 ; EG-LABEL: constant_load_v3i16:
 ; EG:       ; %bb.0: ; %entry
-; EG-NEXT:    ALU 0, @12, KC0[CB0:0-32], KC1[]
-; EG-NEXT:    TEX 2 @6
-; EG-NEXT:    ALU 19, @13, KC0[CB0:0-32], KC1[]
-; EG-NEXT:    MEM_RAT_CACHELESS STORE_RAW T6.X, T7.X, 0
-; EG-NEXT:    MEM_RAT MSKOR T5.XW, T8.X
+; EG-NEXT:    ALU 0, @10, KC0[CB0:0-32], KC1[]
+; EG-NEXT:    TEX 1 @6
+; EG-NEXT:    ALU 14, @11, KC0[CB0:0-32], KC1[]
+; EG-NEXT:    MEM_RAT_CACHELESS STORE_RAW T5.X, T8.X, 0
+; EG-NEXT:    MEM_RAT MSKOR T6.XW, T7.X
 ; EG-NEXT:    CF_END
 ; EG-NEXT:    Fetch clause starting at 6:
-; EG-NEXT:     VTX_READ_16 T6.X, T5.X, 0, #1
-; EG-NEXT:     VTX_READ_16 T7.X, T5.X, 2, #1
-; EG-NEXT:     VTX_READ_16 T5.X, T5.X, 4, #1
-; EG-NEXT:    ALU clause starting at 12:
+; EG-NEXT:     VTX_READ_16 T6.X, T5.X, 4, #1
+; EG-NEXT:     VTX_READ_32 T5.X, T5.X, 0, #1
+; EG-NEXT:    ALU clause starting at 10:
 ; EG-NEXT:     MOV * T5.X, KC0[2].Z,
-; EG-NEXT:    ALU clause starting at 13:
+; EG-NEXT:    ALU clause starting at 11:
 ; EG-NEXT:     ADD_INT * T0.W, KC0[2].Y, literal.x,
 ; EG-NEXT:    4(5.605194e-45), 0(0.000000e+00)
 ; EG-NEXT:     AND_INT T1.W, PV.W, literal.x,
-; EG-NEXT:     AND_INT * T2.W, T5.X, literal.y,
+; EG-NEXT:     AND_INT * T2.W, T6.X, literal.y,
 ; EG-NEXT:    3(4.203895e-45), 65535(9.183409e-41)
 ; EG-NEXT:     LSHL * T1.W, PV.W, literal.x,
 ; EG-NEXT:    3(4.203895e-45), 0(0.000000e+00)
-; EG-NEXT:     LSHL T5.X, T2.W, PV.W,
-; EG-NEXT:     LSHL * T5.W, literal.x, PV.W,
-; EG-NEXT:    65535(9.183409e-41), 0(0.000000e+00)
-; EG-NEXT:     MOV T5.Y, 0.0,
-; EG-NEXT:     MOV * T5.Z, 0.0,
-; EG-NEXT:     LSHR T8.X, T0.W, literal.x,
-; EG-NEXT:     LSHL T0.W, T7.X, literal.y,
-; EG-NEXT:     AND_INT * T1.W, T6.X, literal.z,
-; EG-NEXT:    2(2.802597e-45), 16(2.242078e-44)
+; EG-NEXT:     LSHL T6.X, T2.W, PV.W,
+; EG-NEXT:     LSHL * T6.W, literal.x, PV.W,
 ; EG-NEXT:    65535(9.183409e-41), 0(0.000000e+00)
-; EG-NEXT:     OR_INT T6.X, PV.W, PS,
-; EG-NEXT:     LSHR * T7.X, KC0[2].Y, literal.x,
+; EG-NEXT:     MOV T6.Y, 0.0,
+; EG-NEXT:     MOV * T6.Z, 0.0,
+; EG-NEXT:     LSHR T7.X, T0.W, literal.x,
+; EG-NEXT:     LSHR * T8.X, KC0[2].Y, literal.x,
 ; EG-NEXT:    2(2.802597e-45), 0(0.000000e+00)
 ;
 ; GFX12-LABEL: constant_load_v3i16:
diff --git a/llvm/test/CodeGen/AMDGPU/load-global-i16.ll b/llvm/test/CodeGen/AMDGPU/load-global-i16.ll
index 33defc2ecf6a9..cce75615a2d36 100644
--- a/llvm/test/CodeGen/AMDGPU/load-global-i16.ll
+++ b/llvm/test/CodeGen/AMDGPU/load-global-i16.ll
@@ -263,71 +263,60 @@ define amdgpu_kernel void @global_load_v3i16(ptr addrspace(1) %out, ptr addrspac
 ;
 ; EG-LABEL: global_load_v3i16:
 ; EG:       ; %bb.0: ; %entry
-; EG-NEXT:    ALU 0, @12, KC0[CB0:0-32], KC1[]
-; EG-NEXT:    TEX 2 @6
-; EG-NEXT:    ALU 19, @13, KC0[CB0:0-32], KC1[]
-; EG-NEXT:    MEM_RAT_CACHELESS STORE_RAW T6.X, T7.X, 0
-; EG-NEXT:    MEM_RAT MSKOR T5.XW, T8.X
+; EG-NEXT:    ALU 0, @10, KC0[CB0:0-32], KC1[]
+; EG-NEXT:    TEX 1 @6
+; EG-NEXT:    ALU 14, @11, KC0[CB0:0-32], KC1[]
+; EG-NEXT:    MEM_RAT_CACHELESS STORE_RAW T5.X, T8.X, 0
+; EG-NEXT:    MEM_RAT MSKOR T6.XW, T7.X
 ; EG-NEXT:    CF_END
 ; EG-NEXT:    Fetch clause starting at 6:
-; EG-NEXT:     VTX_READ_16 T6.X, T5.X, 0, #1
-; EG-NEXT:     VTX_READ_16 T7.X, T5.X, 2, #1
-; EG-NEXT:     VTX_READ_16 T5.X, T5.X, 4, #1
-; EG-NEXT:    ALU clause starting at 12:
+; EG-NEXT:     VTX_READ_16 T6.X, T5.X, 4, #1
+; EG-NEXT:     VTX_READ_32 T5.X, T5.X, 0, #1
+; EG-NEXT:    ALU clause starting at 10:
 ; EG-NEXT:     MOV * T5.X, KC0[2].Z,
-; EG-NEXT:    ALU clause starting at 13:
+; EG-NEXT:    ALU clause starting at 11:
 ; EG-NEXT:     ADD_INT * T0.W, KC0[2].Y, literal.x,
 ; EG-NEXT:    4(5.605194e-45), 0(0.000000e+00)
 ; EG-NEXT:     AND_INT T1.W, PV.W, literal.x,
-; EG-NEXT:     AND_INT * T2.W, T5.X, literal.y,
+; EG-NEXT:     AND_INT * T2.W, T6.X, literal.y,
 ; EG-NEXT:    3(4.203895e-45), 65535(9.183409e-41)
 ; EG-NEXT:     LSHL * T1.W, PV.W, literal.x,
 ; EG-NEXT:    3(4.203895e-45), 0(0.000000e+00)
-; EG-NEXT:     LSHL T5.X, T2.W, PV.W,
-; EG-NEXT:     LSHL * T5.W, literal.x, PV.W,
-; EG-NEXT:    65535(9.183409e-41), 0(0.000000e+00)
-; EG-NEXT:     MOV T5.Y, 0.0,
-; EG-NEXT:     MOV * T5.Z, 0.0,
-; EG-NEXT:     LSHR T8.X, T0.W, literal.x,
-; EG-NEXT:     LSHL T0.W, T7.X, literal.y,
-; EG-NEXT:     AND_INT * T1.W, T6.X, literal.z,
-; EG-NEXT:    2(2.802597e-45), 16(2.242078e-44)
+; EG-NEXT:     LSHL T6.X, T2.W, PV.W,
+; EG-NEXT:     LSHL * T6.W, literal.x, PV.W,
 ; EG-NEXT:    65535(9.183409e-41), 0(0.000000e+00)
-; EG-NEXT:     OR_INT T6.X, PV.W, PS,
-; EG-NEXT:     LSHR * T7.X, KC0[2].Y, literal.x,
+; EG-NEXT:     MOV T6.Y, 0.0,
+; EG-NEXT:     MOV * T6.Z, 0.0,
+; EG-NEXT:     LSHR T7.X, T0.W, literal.x,
+; EG-NEXT:     LSHR * T8.X, KC0[2].Y, literal.x,
 ; EG-NEXT:    2(2.802597e-45), 0(0.000000e+00)
 ;
 ; CM-LABEL: global_load_v3i16:
 ; CM:       ; %bb.0: ; %entry
-; CM-NEXT:    ALU 0, @12, KC0[CB0:0-32], KC1[]
-; CM-NEXT:    TEX 2 @6
-; CM-NEXT:    ALU 19, @13, KC0[CB0:0-32], KC1[]
-; CM-NEXT:    MEM_RAT MSKOR T5.XW, T8.X
-; CM-NEXT:    MEM_RAT_CACHELESS STORE_DWORD T6.X, T7.X
+; CM-NEXT:    ALU 0, @10, KC0[CB0:0-32], KC1[]
+; CM-NEXT:    TEX 1 @6
+; CM-NEXT:    ALU 15, @11, KC0[CB0:0-32], KC1[]
+; CM-NEXT:    MEM_RAT MSKOR T6.XW, T8.X
+; CM-NEXT:    MEM_RAT_CACHELESS STORE_DWORD T5.X, T7.X
 ; CM-NEXT:    CF_END
 ; CM-NEXT:    Fetch clause starting at 6:
-; CM-NEXT:     VTX_READ_16 T6.X, T5.X, 0, #1
-; CM-NEXT:     VTX_READ_16 T7.X, T5.X, 2, #1
-; CM-NEXT:     VTX_READ_16 T5.X, T5.X, 4, #1
-; CM-NEXT:    ALU clause starting at 12:
+; CM-NEXT:     VTX_READ_16 T6.X, T5.X, 4, #1
+; CM-NEXT:     VTX_READ_32 T5.X, T5.X, 0, #1
+; CM-NEXT:    ALU clause starting at 10:
 ; CM-NEXT:     MOV * T5.X, KC0[2].Z,
-; CM-NEXT:    ALU clause starting at 13:
+; CM-NEXT:    ALU clause starting at 11:
 ; CM-NEXT:     ADD_INT * T0.W, KC0[2].Y, literal.x,
 ; CM-NEXT:    4(5.605194e-45), 0(0.000000e+00)
 ; CM-NEXT:     AND_INT * T1.W, PV.W, literal.x,
 ; CM-NEXT:    3(4.203895e-45), 0(0.000000e+00)
-; CM-NEXT:     AND_INT T0.Z, T5.X, literal.x,
+; CM-NEXT:     AND_INT T0.Z, T6.X, literal.x,
 ; CM-NEXT:     LSHL * T1.W, PV.W, literal.y,
 ; CM-NEXT:    65535(9.183409e-41), 3(4.203895e-45)
-; CM-NEXT:     LSHL T5.X, PV.Z, PV.W,
-; CM-NEXT:     LSHL * T5.W, literal.x, PV.W,
+; CM-NEXT:     LSHL T6.X, PV.Z, PV.W,
+; CM-NEXT:     LSHL * T6.W, literal.x, PV.W,
 ; CM-NEXT:    65535(9.183409e-41), 0(0.000000e+00)
-; CM-NEXT:     MOV T5.Y, 0.0,
-; CM-NEXT:     MOV * T5.Z, 0.0,
-; CM-NEXT:     LSHL T0.Z, T7.X, literal.x,
-; CM-NEXT:     AND_INT * T1.W, T6.X, literal.y, BS:VEC_120/SCL_212
-; CM-NEXT:    16(2.242078e-44), 65535(9.183409e-41)
-; CM-NEXT:     OR_INT * T6.X, PV.Z, PV.W,
+; CM-NEXT:     MOV T6.Y, 0.0,
+; CM-NEXT:     MOV * T6.Z, 0.0,
 ; CM-NEXT:     LSHR * T7.X, KC0[2].Y, literal.x,
 ; CM-NEXT:    2(2.802597e-45), 0(0.000000e+00)
 ; CM-NEXT:     LSHR * T8.X, T0.W, literal.x,
diff --git a/llvm/test/CodeGen/AMDGPU/load-local-i16.ll b/llvm/test/CodeGen/AMDGPU/load-local-i16.ll
index fd5710edb1257..eb7d713b7bdac 100644
--- a/llvm/test/CodeGen/AMDGPU/load-local-i16.ll
+++ b/llvm/test/CodeGen/AMDGPU/load-local-i16.ll
@@ -151,24 +151,16 @@ define amdgpu_kernel void @local_load_v3i16(ptr addrspace(3) %out, ptr addrspace
 ;
 ; EG-LABEL: local_load_v3i16:
 ; EG:       ; %bb.0: ; %entry
-; EG-NEXT:    ALU 19, @2, KC0[CB0:0-32], KC1[]
-; EG-NEXT:     MOV * T0.W, KC0[2].Z,
-; EG-NEXT:     LDS_USHORT_READ_RET * OQAP, T0.W
-; EG-NEXT:     MOV T0.Y, OQAP,
+; EG-NEXT:    ALU 11, @2, KC0[CB0:0-32], KC1[]
 ; EG-NEXT:     ADD_INT * T0.W, KC0[2].Z, literal.x,
-; EG-NEXT:    2(2.802597e-45), 0(0.000000e+00)
-; EG-NEXT:     LDS_USHORT_READ_RET * OQAP, T0.W
-; EG-NEXT:     MOV * T0.Z, OQAP,
-; EG-NEXT:     LSHL T0.Z, PV.Z, literal.x,
-; EG-NEXT:     AND_INT T0.W, T0.Y, literal.y,
-; EG-NEXT:     ADD_INT * T1.W, KC0[2].Z, literal.z,
-; EG-NEXT:    16(2.242078e-44), 65535(9.183409e-41)
 ; EG-NEXT:    4(5.605194e-45), 0(0.000000e+00)
-; EG-NEXT:     LDS_USHORT_READ_RET * OQAP, T1.W
+; EG-NEXT:     LDS_USHORT_READ_RET * OQAP, T0.W
 ; EG-NEXT:     MOV T0.Y, OQAP,
-; EG-NEXT:     OR_INT T0.W, T0.Z, T0.W,
-; EG-NEXT:     MOV * T1.W, KC0[2].Y,
-; EG-NEXT:     LDS_WRITE * T1.W, T0.W,
+; EG-NEXT:     MOV * T0.W, KC0[2].Z,
+; EG-NEXT:     LDS_READ_RET * OQAP, T0.W
+; EG-NEXT:     MOV T0.Z, OQAP,
+; EG-NEXT:     MOV * T0.W, KC0[2].Y,
+; EG-NEXT:     LDS_WRITE * T0.W, T0.Z,
 ; EG-NEXT:     ADD_INT * T0.W, KC0[2].Y, literal.x,
 ; EG-NEXT:    4(5.605194e-45), 0(0.000000e+00)
 ; EG-NEXT:     LDS_SHORT_WRITE * T0.W, T0.Y,
diff --git a/llvm/test/CodeGen/X86/load-combine-masked.ll b/llvm/test/CodeGen/X86/load-combine-masked.ll
index 40c76c73bd3e2..96561c16ddfb8 100644
--- a/llvm/test/CodeGen/X86/load-combine-masked.ll
+++ b/llvm/test/CodeGen/X86/load-combine-masked.ll
@@ -214,17 +214,12 @@ define i16 @negative_variable_mask(ptr %p, i8 %mask) {
 ; Multi-use: AND result is used elsewhere. The one-use check in
 ; calculateByteProvider should prevent combining since the AND can't be removed.
 declare void @use.i8(i8)
-define i16 @negative_multiuse_and(ptr %p) {
+define i16 @negative_multiuse_and(ptr %p) nounwind {
 ; CHECK-LABEL: negative_multiuse_and:
 ; CHECK:       # %bb.0:
 ; CHECK-NEXT:    pushq %rbp
-; CHECK-NEXT:    .cfi_def_cfa_offset 16
 ; CHECK-NEXT:    pushq %rbx
-; CHECK-NEXT:    .cfi_def_cfa_offset 24
 ; CHECK-NEXT:    pushq %rax
-; CHECK-NEXT:    .cfi_def_cfa_offset 32
-; CHECK-NEXT:    .cfi_offset %rbx, -24
-; CHECK-NEXT:    .cfi_offset %rbp, -16
 ; CHECK-NEXT:    movzbl (%rdi), %ebp
 ; CHECK-NEXT:    movzbl 1(%rdi), %ebx
 ; CHECK-NEXT:    andl $3, %ebx
@@ -234,11 +229,8 @@ define i16 @negative_multiuse_and(ptr %p) {
 ; CHECK-NEXT:    orl %ebp, %ebx
 ; CHECK-NEXT:    movl %ebx, %eax
 ; CHECK-NEXT:    addq $8, %rsp
-; CHECK-NEXT:    .cfi_def_cfa_offset 24
 ; CHECK-NEXT:    popq %rbx
-; CHECK-NEXT:    .cfi_def_cfa_offset 16
 ; CHECK-NEXT:    popq %rbp
-; CHECK-NEXT:    .cfi_def_cfa_offset 8
 ; CHECK-NEXT:    retq
   %p1 = getelementptr inbounds i8, ptr %p, i64 1
   %lo = load i8, ptr %p, align 1



More information about the llvm-commits mailing list