[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