[llvm] [ExpandMemCmp] Check misaligned access per overlapping/tail load (PR #210707)

Pengcheng Wang via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 22 04:51:32 PDT 2026


https://github.com/wangpc-pp updated https://github.com/llvm/llvm-project/pull/210707

>From 618c2a9eb0c76b5fda2bb2cd73614ff4b7c0abef Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Tue, 21 Jul 2026 17:11:55 +0800
Subject: [PATCH 1/9] [ExpandMemCmp] Fix swapped LoadSize/Offset in tail-merge
 LoadEntry

optimiseLoadSequence() builds the merged tail LoadEntry with the
constructor arguments in the wrong order:

  LoadEntry(unsigned LoadSize, uint64_t Offset)
  ...
  LoadSequence.emplace_back(PreLast.Offset, LoadSize);

so the merged entry records Offset as its size and LoadSize as its
offset. This has been latent since the tail-merge path was added
because the only common consumer, getMemCmpOneBlock(), derives its
load width from the memcmp Size and uses offset 0, ignoring the
LoadEntry fields. The bug is only observable through the multi-block
emitLoadCompareBlock() path, which no in-tree target currently reaches:
every target that sets AllowedTailExpansions also sets
AllowOverlappingLoads, and the overlapping path collapses any >2-entry
sequence to two entries before the tail merge, which then merges to a
single block.

No functional change for existing targets; all in-tree memcmp tests are
unaffected. This is a prerequisite for correctly handling multi-block
tail merges on strict-alignment targets.

Assisted-by: TRAE CLI (DeepSeek V4 Pro)
---
 llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
index a77da0e91ca4e..3675487aceb3b 100644
--- a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
+++ b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
@@ -249,7 +249,7 @@ void MemCmpExpansion::optimiseLoadSequence(
     // Remove the last two sequences and replace with the combined sequence
     LoadSequence.pop_back();
     LoadSequence.pop_back();
-    LoadSequence.emplace_back(PreLast.Offset, LoadSize);
+    LoadSequence.emplace_back(LoadSize, PreLast.Offset);
   }
 }
 

>From d648884638ab52359a6c06521e7f60eae16df3f6 Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Tue, 21 Jul 2026 18:12:24 +0800
Subject: [PATCH 2/9] [ExpandMemCmp][RISCV] Pre-commit test for misaligned
 overlapping/tail loads

Add ordering-memcmp tests on strict-align RISC-V with various pointer
alignments to capture the current handling of merged tail expansions and
overlapping loads, including tail merges (i24/i48) and a multi-block case
whose tail would merge past MaxLoadSize. These currently do not account
for the per-offset alignment of the wide load; a follow-up will make the
decision precise.

Assisted-by: TRAE CLI (DeepSeek V4 Pro)
---
 .../RISCV/memcmp-misaligned-access.ll         | 396 ++++++++++++++++++
 1 file changed, 396 insertions(+)
 create mode 100644 llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll

diff --git a/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll b/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
new file mode 100644
index 0000000000000..756c5f99ee44a
--- /dev/null
+++ b/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
@@ -0,0 +1,396 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: sed 's/iXLen/i64/g' %s | opt -S -passes=expand-memcmp -mtriple=riscv64 -mattr=+m,+zbb | FileCheck %s --check-prefixes=RV64
+; RUN: sed 's/iXLen/i64/g' %s | opt -S -passes=expand-memcmp -mtriple=riscv64 -mattr=+m,+zbb,+unaligned-scalar-mem | FileCheck %s --check-prefixes=RV64-UNALIGNED
+
+; Overlapping loads and merged tail expansions place a load at an offset that
+; need not be a multiple of its size. On strict-alignment targets they are only
+; formed when the load is accessible at its actual per-offset alignment, so the
+; decision depends on the known pointer alignment rather than on a target-wide
+; flag. These are ordering (non-zero) memcmp compares so that the tail-merge and
+; overlapping-load paths are exercised.
+
+declare i32 @memcmp(ptr, ptr, iXLen)
+
+; Tail merge: 3-byte tail at offset 0. With align 4 the merged i24 load is
+; naturally aligned, so it is formed even on a strict-align target.
+define i32 @memcmp_size_3_align_4(ptr align 4 %a, ptr align 4 %b) {
+; RV64-LABEL: define i32 @memcmp_size_3_align_4(
+; RV64-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0:[0-9]+]] {
+; RV64-NEXT:    br label %[[LOADBB:.*]]
+; RV64:       [[RES_BLOCK:.*]]:
+; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i16 [[TMP5:%.*]], [[TMP6:%.*]]
+; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
+; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
+; RV64:       [[LOADBB]]:
+; RV64-NEXT:    [[TMP3:%.*]] = load i16, ptr [[A]], align 4
+; RV64-NEXT:    [[TMP4:%.*]] = load i16, ptr [[B]], align 4
+; RV64-NEXT:    [[TMP5]] = call i16 @llvm.bswap.i16(i16 [[TMP3]])
+; RV64-NEXT:    [[TMP6]] = call i16 @llvm.bswap.i16(i16 [[TMP4]])
+; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i16 [[TMP5]], [[TMP6]]
+; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1:.*]], label %[[RES_BLOCK]]
+; RV64:       [[LOADBB1]]:
+; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 2
+; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 2
+; RV64-NEXT:    [[TMP10:%.*]] = load i8, ptr [[TMP8]], align 2
+; RV64-NEXT:    [[TMP11:%.*]] = load i8, ptr [[TMP9]], align 2
+; RV64-NEXT:    [[TMP12:%.*]] = zext i8 [[TMP10]] to i32
+; RV64-NEXT:    [[TMP13:%.*]] = zext i8 [[TMP11]] to i32
+; RV64-NEXT:    [[TMP14:%.*]] = sub i32 [[TMP12]], [[TMP13]]
+; RV64-NEXT:    br label %[[ENDBLOCK]]
+; RV64:       [[ENDBLOCK]]:
+; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ [[TMP14]], %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
+; RV64-NEXT:    ret i32 [[PHI_RES]]
+;
+; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_4(
+; RV64-UNALIGNED-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0:[0-9]+]] {
+; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 4
+; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 4
+; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
+; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
+; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
+; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+;
+  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 3)
+  ret i32 %r
+}
+
+; Same size with unknown alignment: the merged i24 (and any i16) would be
+; misaligned, so on a strict-align target only byte loads remain, while an
+; unaligned target still merges into a single i24.
+define i32 @memcmp_size_3_align_1(ptr %a, ptr %b) {
+; RV64-LABEL: define i32 @memcmp_size_3_align_1(
+; RV64-SAME: ptr [[A:%.*]], ptr [[B:%.*]]) #[[ATTR0]] {
+; RV64-NEXT:    br label %[[LOADBB:.*]]
+; RV64:       [[RES_BLOCK:.*]]:
+; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
+; RV64:       [[LOADBB]]:
+; RV64-NEXT:    [[TMP1:%.*]] = load i8, ptr [[A]], align 1
+; RV64-NEXT:    [[TMP2:%.*]] = load i8, ptr [[B]], align 1
+; RV64-NEXT:    [[TMP3:%.*]] = zext i8 [[TMP1]] to i32
+; RV64-NEXT:    [[TMP4:%.*]] = zext i8 [[TMP2]] to i32
+; RV64-NEXT:    [[TMP5:%.*]] = sub i32 [[TMP3]], [[TMP4]]
+; RV64-NEXT:    [[TMP6:%.*]] = icmp ne i32 [[TMP5]], 0
+; RV64-NEXT:    br i1 [[TMP6]], label %[[ENDBLOCK]], label %[[LOADBB1:.*]]
+; RV64:       [[LOADBB1]]:
+; RV64-NEXT:    [[TMP7:%.*]] = getelementptr i8, ptr [[A]], i64 1
+; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[B]], i64 1
+; RV64-NEXT:    [[TMP9:%.*]] = load i8, ptr [[TMP7]], align 1
+; RV64-NEXT:    [[TMP10:%.*]] = load i8, ptr [[TMP8]], align 1
+; RV64-NEXT:    [[TMP11:%.*]] = zext i8 [[TMP9]] to i32
+; RV64-NEXT:    [[TMP12:%.*]] = zext i8 [[TMP10]] to i32
+; RV64-NEXT:    [[TMP13:%.*]] = sub i32 [[TMP11]], [[TMP12]]
+; RV64-NEXT:    [[TMP14:%.*]] = icmp ne i32 [[TMP13]], 0
+; RV64-NEXT:    br i1 [[TMP14]], label %[[ENDBLOCK]], label %[[LOADBB2:.*]]
+; RV64:       [[LOADBB2]]:
+; RV64-NEXT:    [[TMP15:%.*]] = getelementptr i8, ptr [[A]], i64 2
+; RV64-NEXT:    [[TMP16:%.*]] = getelementptr i8, ptr [[B]], i64 2
+; RV64-NEXT:    [[TMP17:%.*]] = load i8, ptr [[TMP15]], align 1
+; RV64-NEXT:    [[TMP18:%.*]] = load i8, ptr [[TMP16]], align 1
+; RV64-NEXT:    [[TMP19:%.*]] = zext i8 [[TMP17]] to i32
+; RV64-NEXT:    [[TMP20:%.*]] = zext i8 [[TMP18]] to i32
+; RV64-NEXT:    [[TMP21:%.*]] = sub i32 [[TMP19]], [[TMP20]]
+; RV64-NEXT:    br label %[[ENDBLOCK]]
+; RV64:       [[ENDBLOCK]]:
+; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ [[TMP5]], %[[LOADBB]] ], [ [[TMP13]], %[[LOADBB1]] ], [ [[TMP21]], %[[LOADBB2]] ], [ poison, %[[RES_BLOCK]] ]
+; RV64-NEXT:    ret i32 [[PHI_RES]]
+;
+; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_1(
+; RV64-UNALIGNED-SAME: ptr [[A:%.*]], ptr [[B:%.*]]) #[[ATTR0]] {
+; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 1
+; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 1
+; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
+; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
+; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
+; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+;
+  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 3)
+  ret i32 %r
+}
+
+; 6-byte tail merge (AllowedTailExpansions has 6). align 8 makes the merged i48
+; at offset 0 naturally aligned, so it is formed on a strict-align target.
+define i32 @memcmp_size_6_align_8(ptr align 8 %a, ptr align 8 %b) {
+; RV64-LABEL: define i32 @memcmp_size_6_align_8(
+; RV64-SAME: ptr align 8 [[A:%.*]], ptr align 8 [[B:%.*]]) #[[ATTR0]] {
+; RV64-NEXT:    br label %[[LOADBB:.*]]
+; RV64:       [[RES_BLOCK:.*]]:
+; RV64-NEXT:    [[PHI_SRC1:%.*]] = phi i32 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP14:%.*]], %[[LOADBB1:.*]] ]
+; RV64-NEXT:    [[PHI_SRC2:%.*]] = phi i32 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP15:%.*]], %[[LOADBB1]] ]
+; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i32 [[PHI_SRC1]], [[PHI_SRC2]]
+; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
+; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
+; RV64:       [[LOADBB]]:
+; RV64-NEXT:    [[TMP3:%.*]] = load i32, ptr [[A]], align 8
+; RV64-NEXT:    [[TMP4:%.*]] = load i32, ptr [[B]], align 8
+; RV64-NEXT:    [[TMP5]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; RV64-NEXT:    [[TMP6]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i32 [[TMP5]], [[TMP6]]
+; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
+; RV64:       [[LOADBB1]]:
+; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 4
+; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 4
+; RV64-NEXT:    [[TMP10:%.*]] = load i16, ptr [[TMP8]], align 4
+; RV64-NEXT:    [[TMP11:%.*]] = load i16, ptr [[TMP9]], align 4
+; RV64-NEXT:    [[TMP12:%.*]] = call i16 @llvm.bswap.i16(i16 [[TMP10]])
+; RV64-NEXT:    [[TMP13:%.*]] = call i16 @llvm.bswap.i16(i16 [[TMP11]])
+; RV64-NEXT:    [[TMP14]] = zext i16 [[TMP12]] to i32
+; RV64-NEXT:    [[TMP15]] = zext i16 [[TMP13]] to i32
+; RV64-NEXT:    [[TMP16:%.*]] = icmp eq i32 [[TMP14]], [[TMP15]]
+; RV64-NEXT:    br i1 [[TMP16]], label %[[ENDBLOCK]], label %[[RES_BLOCK]]
+; RV64:       [[ENDBLOCK]]:
+; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ 0, %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
+; RV64-NEXT:    ret i32 [[PHI_RES]]
+;
+; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_8(
+; RV64-UNALIGNED-SAME: ptr align 8 [[A:%.*]], ptr align 8 [[B:%.*]]) #[[ATTR0]] {
+; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 8
+; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 8
+; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
+; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
+; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
+; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
+; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
+; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+;
+  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 6)
+  ret i32 %r
+}
+
+; With align 2 the wide loads are misaligned, so the strict-align target uses
+; the greedy naturally-aligned i16 sequence instead of merging/overlapping.
+define i32 @memcmp_size_6_align_2(ptr align 2 %a, ptr align 2 %b) {
+; RV64-LABEL: define i32 @memcmp_size_6_align_2(
+; RV64-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; RV64-NEXT:    br label %[[LOADBB:.*]]
+; RV64:       [[RES_BLOCK:.*]]:
+; RV64-NEXT:    [[PHI_SRC1:%.*]] = phi i16 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP12:%.*]], %[[LOADBB1:.*]] ], [ [[TMP19:%.*]], %[[LOADBB2:.*]] ]
+; RV64-NEXT:    [[PHI_SRC2:%.*]] = phi i16 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP13:%.*]], %[[LOADBB1]] ], [ [[TMP20:%.*]], %[[LOADBB2]] ]
+; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i16 [[PHI_SRC1]], [[PHI_SRC2]]
+; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
+; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
+; RV64:       [[LOADBB]]:
+; RV64-NEXT:    [[TMP3:%.*]] = load i16, ptr [[A]], align 2
+; RV64-NEXT:    [[TMP4:%.*]] = load i16, ptr [[B]], align 2
+; RV64-NEXT:    [[TMP5]] = call i16 @llvm.bswap.i16(i16 [[TMP3]])
+; RV64-NEXT:    [[TMP6]] = call i16 @llvm.bswap.i16(i16 [[TMP4]])
+; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i16 [[TMP5]], [[TMP6]]
+; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
+; RV64:       [[LOADBB1]]:
+; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 2
+; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 2
+; RV64-NEXT:    [[TMP10:%.*]] = load i16, ptr [[TMP8]], align 2
+; RV64-NEXT:    [[TMP11:%.*]] = load i16, ptr [[TMP9]], align 2
+; RV64-NEXT:    [[TMP12]] = call i16 @llvm.bswap.i16(i16 [[TMP10]])
+; RV64-NEXT:    [[TMP13]] = call i16 @llvm.bswap.i16(i16 [[TMP11]])
+; RV64-NEXT:    [[TMP14:%.*]] = icmp eq i16 [[TMP12]], [[TMP13]]
+; RV64-NEXT:    br i1 [[TMP14]], label %[[LOADBB2]], label %[[RES_BLOCK]]
+; RV64:       [[LOADBB2]]:
+; RV64-NEXT:    [[TMP15:%.*]] = getelementptr i8, ptr [[A]], i64 4
+; RV64-NEXT:    [[TMP16:%.*]] = getelementptr i8, ptr [[B]], i64 4
+; RV64-NEXT:    [[TMP17:%.*]] = load i16, ptr [[TMP15]], align 2
+; RV64-NEXT:    [[TMP18:%.*]] = load i16, ptr [[TMP16]], align 2
+; RV64-NEXT:    [[TMP19]] = call i16 @llvm.bswap.i16(i16 [[TMP17]])
+; RV64-NEXT:    [[TMP20]] = call i16 @llvm.bswap.i16(i16 [[TMP18]])
+; RV64-NEXT:    [[TMP21:%.*]] = icmp eq i16 [[TMP19]], [[TMP20]]
+; RV64-NEXT:    br i1 [[TMP21]], label %[[ENDBLOCK]], label %[[RES_BLOCK]]
+; RV64:       [[ENDBLOCK]]:
+; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ 0, %[[LOADBB2]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
+; RV64-NEXT:    ret i32 [[PHI_RES]]
+;
+; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_2(
+; RV64-UNALIGNED-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 2
+; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 2
+; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
+; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
+; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
+; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
+; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
+; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+;
+  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 6)
+  ret i32 %r
+}
+
+; A tail expansion merges adjacent loads and is always legalized by the backend,
+; so it is formed regardless of the pointer alignment (the i24 covers the same
+; bytes as the i16+i8 it replaces).
+define i32 @memcmp_size_3_align_2(ptr align 2 %a, ptr align 2 %b) {
+; RV64-LABEL: define i32 @memcmp_size_3_align_2(
+; RV64-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; RV64-NEXT:    br label %[[LOADBB:.*]]
+; RV64:       [[RES_BLOCK:.*]]:
+; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i16 [[TMP5:%.*]], [[TMP6:%.*]]
+; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
+; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
+; RV64:       [[LOADBB]]:
+; RV64-NEXT:    [[TMP3:%.*]] = load i16, ptr [[A]], align 2
+; RV64-NEXT:    [[TMP4:%.*]] = load i16, ptr [[B]], align 2
+; RV64-NEXT:    [[TMP5]] = call i16 @llvm.bswap.i16(i16 [[TMP3]])
+; RV64-NEXT:    [[TMP6]] = call i16 @llvm.bswap.i16(i16 [[TMP4]])
+; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i16 [[TMP5]], [[TMP6]]
+; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1:.*]], label %[[RES_BLOCK]]
+; RV64:       [[LOADBB1]]:
+; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 2
+; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 2
+; RV64-NEXT:    [[TMP10:%.*]] = load i8, ptr [[TMP8]], align 2
+; RV64-NEXT:    [[TMP11:%.*]] = load i8, ptr [[TMP9]], align 2
+; RV64-NEXT:    [[TMP12:%.*]] = zext i8 [[TMP10]] to i32
+; RV64-NEXT:    [[TMP13:%.*]] = zext i8 [[TMP11]] to i32
+; RV64-NEXT:    [[TMP14:%.*]] = sub i32 [[TMP12]], [[TMP13]]
+; RV64-NEXT:    br label %[[ENDBLOCK]]
+; RV64:       [[ENDBLOCK]]:
+; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ [[TMP14]], %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
+; RV64-NEXT:    ret i32 [[PHI_RES]]
+;
+; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_2(
+; RV64-UNALIGNED-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 2
+; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 2
+; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
+; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
+; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
+; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+;
+  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 3)
+  ret i32 %r
+}
+
+; Likewise the i48 tail merge is formed independent of alignment.
+define i32 @memcmp_size_6_align_4(ptr align 4 %a, ptr align 4 %b) {
+; RV64-LABEL: define i32 @memcmp_size_6_align_4(
+; RV64-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0]] {
+; RV64-NEXT:    br label %[[LOADBB:.*]]
+; RV64:       [[RES_BLOCK:.*]]:
+; RV64-NEXT:    [[PHI_SRC1:%.*]] = phi i32 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP14:%.*]], %[[LOADBB1:.*]] ]
+; RV64-NEXT:    [[PHI_SRC2:%.*]] = phi i32 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP15:%.*]], %[[LOADBB1]] ]
+; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i32 [[PHI_SRC1]], [[PHI_SRC2]]
+; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
+; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
+; RV64:       [[LOADBB]]:
+; RV64-NEXT:    [[TMP3:%.*]] = load i32, ptr [[A]], align 4
+; RV64-NEXT:    [[TMP4:%.*]] = load i32, ptr [[B]], align 4
+; RV64-NEXT:    [[TMP5]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; RV64-NEXT:    [[TMP6]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i32 [[TMP5]], [[TMP6]]
+; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
+; RV64:       [[LOADBB1]]:
+; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 4
+; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 4
+; RV64-NEXT:    [[TMP10:%.*]] = load i16, ptr [[TMP8]], align 4
+; RV64-NEXT:    [[TMP11:%.*]] = load i16, ptr [[TMP9]], align 4
+; RV64-NEXT:    [[TMP12:%.*]] = call i16 @llvm.bswap.i16(i16 [[TMP10]])
+; RV64-NEXT:    [[TMP13:%.*]] = call i16 @llvm.bswap.i16(i16 [[TMP11]])
+; RV64-NEXT:    [[TMP14]] = zext i16 [[TMP12]] to i32
+; RV64-NEXT:    [[TMP15]] = zext i16 [[TMP13]] to i32
+; RV64-NEXT:    [[TMP16:%.*]] = icmp eq i32 [[TMP14]], [[TMP15]]
+; RV64-NEXT:    br i1 [[TMP16]], label %[[ENDBLOCK]], label %[[RES_BLOCK]]
+; RV64:       [[ENDBLOCK]]:
+; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ 0, %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
+; RV64-NEXT:    ret i32 [[PHI_RES]]
+;
+; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_4(
+; RV64-UNALIGNED-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0]] {
+; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 4
+; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 4
+; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
+; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
+; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
+; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
+; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
+; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+;
+  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 6)
+  ret i32 %r
+}
+
+; The per-call-site alignment filter can shrink MaxLoadSize (here to 2 bytes at
+; align 2), leaving a multi-block expansion whose tail (i16+i8) would merge to a
+; load wider than MaxLoadSize. Such a merge must be skipped, otherwise
+; emitLoadCompareBlock hits an oversized load; the greedy i16+i8 tail is used.
+define i32 @memcmp_size_7_align_2(ptr align 2 %a, ptr align 2 %b) {
+; RV64-LABEL: define i32 @memcmp_size_7_align_2(
+; RV64-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; RV64-NEXT:    br label %[[LOADBB:.*]]
+; RV64:       [[RES_BLOCK:.*]]:
+; RV64-NEXT:    [[PHI_SRC1:%.*]] = phi i16 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP12:%.*]], %[[LOADBB1:.*]] ], [ [[TMP19:%.*]], %[[LOADBB2:.*]] ]
+; RV64-NEXT:    [[PHI_SRC2:%.*]] = phi i16 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP13:%.*]], %[[LOADBB1]] ], [ [[TMP20:%.*]], %[[LOADBB2]] ]
+; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i16 [[PHI_SRC1]], [[PHI_SRC2]]
+; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
+; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
+; RV64:       [[LOADBB]]:
+; RV64-NEXT:    [[TMP3:%.*]] = load i16, ptr [[A]], align 2
+; RV64-NEXT:    [[TMP4:%.*]] = load i16, ptr [[B]], align 2
+; RV64-NEXT:    [[TMP5]] = call i16 @llvm.bswap.i16(i16 [[TMP3]])
+; RV64-NEXT:    [[TMP6]] = call i16 @llvm.bswap.i16(i16 [[TMP4]])
+; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i16 [[TMP5]], [[TMP6]]
+; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
+; RV64:       [[LOADBB1]]:
+; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 2
+; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 2
+; RV64-NEXT:    [[TMP10:%.*]] = load i16, ptr [[TMP8]], align 2
+; RV64-NEXT:    [[TMP11:%.*]] = load i16, ptr [[TMP9]], align 2
+; RV64-NEXT:    [[TMP12]] = call i16 @llvm.bswap.i16(i16 [[TMP10]])
+; RV64-NEXT:    [[TMP13]] = call i16 @llvm.bswap.i16(i16 [[TMP11]])
+; RV64-NEXT:    [[TMP14:%.*]] = icmp eq i16 [[TMP12]], [[TMP13]]
+; RV64-NEXT:    br i1 [[TMP14]], label %[[LOADBB2]], label %[[RES_BLOCK]]
+; RV64:       [[LOADBB2]]:
+; RV64-NEXT:    [[TMP15:%.*]] = getelementptr i8, ptr [[A]], i64 4
+; RV64-NEXT:    [[TMP16:%.*]] = getelementptr i8, ptr [[B]], i64 4
+; RV64-NEXT:    [[TMP17:%.*]] = load i16, ptr [[TMP15]], align 2
+; RV64-NEXT:    [[TMP18:%.*]] = load i16, ptr [[TMP16]], align 2
+; RV64-NEXT:    [[TMP19]] = call i16 @llvm.bswap.i16(i16 [[TMP17]])
+; RV64-NEXT:    [[TMP20]] = call i16 @llvm.bswap.i16(i16 [[TMP18]])
+; RV64-NEXT:    [[TMP21:%.*]] = icmp eq i16 [[TMP19]], [[TMP20]]
+; RV64-NEXT:    br i1 [[TMP21]], label %[[LOADBB3:.*]], label %[[RES_BLOCK]]
+; RV64:       [[LOADBB3]]:
+; RV64-NEXT:    [[TMP22:%.*]] = getelementptr i8, ptr [[A]], i64 6
+; RV64-NEXT:    [[TMP23:%.*]] = getelementptr i8, ptr [[B]], i64 6
+; RV64-NEXT:    [[TMP24:%.*]] = load i8, ptr [[TMP22]], align 2
+; RV64-NEXT:    [[TMP25:%.*]] = load i8, ptr [[TMP23]], align 2
+; RV64-NEXT:    [[TMP26:%.*]] = zext i8 [[TMP24]] to i32
+; RV64-NEXT:    [[TMP27:%.*]] = zext i8 [[TMP25]] to i32
+; RV64-NEXT:    [[TMP28:%.*]] = sub i32 [[TMP26]], [[TMP27]]
+; RV64-NEXT:    br label %[[ENDBLOCK]]
+; RV64:       [[ENDBLOCK]]:
+; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ [[TMP28]], %[[LOADBB3]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
+; RV64-NEXT:    ret i32 [[PHI_RES]]
+;
+; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_7_align_2(
+; RV64-UNALIGNED-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; RV64-UNALIGNED-NEXT:    br label %[[LOADBB:.*]]
+; RV64-UNALIGNED:       [[RES_BLOCK:.*]]:
+; RV64-UNALIGNED-NEXT:    [[PHI_SRC1:%.*]] = phi i32 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP12:%.*]], %[[LOADBB1:.*]] ]
+; RV64-UNALIGNED-NEXT:    [[PHI_SRC2:%.*]] = phi i32 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP13:%.*]], %[[LOADBB1]] ]
+; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = icmp ult i32 [[PHI_SRC1]], [[PHI_SRC2]]
+; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
+; RV64-UNALIGNED-NEXT:    br label %[[ENDBLOCK:.*]]
+; RV64-UNALIGNED:       [[LOADBB]]:
+; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = load i32, ptr [[A]], align 2
+; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = load i32, ptr [[B]], align 2
+; RV64-UNALIGNED-NEXT:    [[TMP5]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; RV64-UNALIGNED-NEXT:    [[TMP6]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = icmp eq i32 [[TMP5]], [[TMP6]]
+; RV64-UNALIGNED-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
+; RV64-UNALIGNED:       [[LOADBB1]]:
+; RV64-UNALIGNED-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 3
+; RV64-UNALIGNED-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 3
+; RV64-UNALIGNED-NEXT:    [[TMP10:%.*]] = load i32, ptr [[TMP8]], align 1
+; RV64-UNALIGNED-NEXT:    [[TMP11:%.*]] = load i32, ptr [[TMP9]], align 1
+; RV64-UNALIGNED-NEXT:    [[TMP12]] = call i32 @llvm.bswap.i32(i32 [[TMP10]])
+; RV64-UNALIGNED-NEXT:    [[TMP13]] = call i32 @llvm.bswap.i32(i32 [[TMP11]])
+; RV64-UNALIGNED-NEXT:    [[TMP14:%.*]] = icmp eq i32 [[TMP12]], [[TMP13]]
+; RV64-UNALIGNED-NEXT:    br i1 [[TMP14]], label %[[ENDBLOCK]], label %[[RES_BLOCK]]
+; RV64-UNALIGNED:       [[ENDBLOCK]]:
+; RV64-UNALIGNED-NEXT:    [[PHI_RES:%.*]] = phi i32 [ 0, %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
+; RV64-UNALIGNED-NEXT:    ret i32 [[PHI_RES]]
+;
+  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 7)
+  ret i32 %r
+}

>From 8681941485d41ff104e031321b6d61c36ab5056d Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Tue, 21 Jul 2026 18:13:31 +0800
Subject: [PATCH 3/9] [ExpandMemCmp] Check misaligned access per
 overlapping/tail load

Overlapping loads place a power-of-two load at an offset that need not be
a multiple of its size, so the access can be misaligned even when the base
pointers are aligned. Rather than have each target gate
AllowOverlappingLoads on unaligned support, give MemCmpExpansion the target
info and check the overlapping load against its actual alignment
(commonAlignment(baseAlign, offset)) via
TargetTransformInfo::allowsMisalignedMemoryAccesses. It is only formed when
the target can access it; otherwise the expansion falls back to the greedy
(naturally aligned) sequence.

Tail expansions are different: they merge already-legal adjacent loads
covering the same bytes, and the backend always legalizes the merged
(possibly non-power-of-two) load into aligned power-of-two pieces, so they
need no alignment gate. The one real constraint is size: a merged load
wider than MaxLoadSize can only be emitted when it is the sole load
(getMemCmpOneBlock). In a multi-block expansion, emitLoadCompareBlock and
the result-block phis are sized to MaxLoadSize and assume every load fits.
Because the per-call-site alignment filter can shrink MaxLoadSize, skip a
tail merge only when the result would stay multi-block and exceed it.

RISCV therefore offers overlapping loads and tail expansions
unconditionally and relies on the pass to enforce access legality. This
lets strict-align targets expand e.g. a size-3 tail merge whose offset
happens to be sufficiently aligned.

Assisted-by: TRAE CLI (DeepSeek V4 Pro)
---
 .../Target/RISCV/RISCVTargetTransformInfo.cpp |  17 +--
 llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp   | 112 ++++++++++-----
 .../RISCV/memcmp-misaligned-access.ll         | 136 +++++-------------
 3 files changed, 112 insertions(+), 153 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index ee85564cd32e5..ed49f8b7a201a 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -3642,26 +3642,15 @@ RISCVTTIImpl::enableMemCmpExpansion(bool OptSize, bool IsZeroCmp) const {
   if (!ST->hasStdExtZbb() && !ST->hasStdExtZbkb() && !IsZeroCmp)
     return Options;
 
-  // Even if the target does not support unaligned scalar memory access,
-  // expansion is still possible when both pointers are statically known to be
-  // sufficiently aligned. ExpandMemCmp queries the target for each load size
-  // and keeps only the ones the target can actually access at the known
-  // per-call-site alignment, falling back to the libcall when none fits.
-  // Overlapping loads and merged tail expansions produce accesses that need
-  // not be naturally aligned, so they are only offered when unaligned scalar
-  // access is supported.
-  bool UnalignedScalar = ST->enableUnalignedScalarMem();
-  Options.AllowOverlappingLoads = UnalignedScalar;
+  Options.AllowOverlappingLoads = true;
   Options.MaxNumLoads = TLI->getMaxExpandSizeMemcmp(OptSize);
   Options.NumLoadsPerBlock = Options.MaxNumLoads;
   if (ST->is64Bit()) {
     Options.LoadSizes = {8, 4, 2, 1};
-    if (UnalignedScalar)
-      Options.AllowedTailExpansions = {3, 5, 6};
+    Options.AllowedTailExpansions = {3, 5, 6};
   } else {
     Options.LoadSizes = {4, 2, 1};
-    if (UnalignedScalar)
-      Options.AllowedTailExpansions = {3};
+    Options.AllowedTailExpansions = {3};
   }
 
   if (IsZeroCmp && ST->hasVInstructions()) {
diff --git a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
index 3675487aceb3b..62821fbf3b72c 100644
--- a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
+++ b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
@@ -89,6 +89,9 @@ class MemCmpExpansion {
   PHINode *PhiRes = nullptr;
   const bool IsUsedForZeroCmp;
   const DataLayout &DL;
+  const TargetTransformInfo &TTI;
+  // The known common alignment of the two source pointers.
+  const Align BaseAlign;
   DomTreeUpdater *DTU = nullptr;
   IRBuilder<> Builder;
   // Represents the decomposition in blocks of the expansion. For example,
@@ -127,24 +130,30 @@ class MemCmpExpansion {
   LoadPair getLoadPair(Type *LoadSizeType, Type *BSwapSizeType,
                        Type *CmpSizeType, unsigned OffsetBytes);
 
+  // Return true if a load of `LoadSize` bytes at `Offset` from the base
+  // pointers is accessible on the target: either it is naturally aligned given
+  // the known common base alignment, or the target allows a misaligned access
+  // of that width.
+  bool isAccessAllowed(unsigned LoadSize, uint64_t Offset) const;
+
   static LoadEntryVector
   computeGreedyLoadSequence(uint64_t Size, llvm::ArrayRef<unsigned> LoadSizes,
                             unsigned MaxNumLoads, unsigned &NumLoadsNonOneByte);
-  static LoadEntryVector
+  LoadEntryVector
   computeOverlappingLoadSequence(uint64_t Size, unsigned MaxLoadSize,
                                  unsigned MaxNumLoads,
-                                 unsigned &NumLoadsNonOneByte);
+                                 unsigned &NumLoadsNonOneByte) const;
 
-  static void optimiseLoadSequence(
+  void optimiseLoadSequence(
       LoadEntryVector &LoadSequence,
       const TargetTransformInfo::MemCmpExpansionOptions &Options,
-      bool IsUsedForZeroCmp);
+      bool IsUsedForZeroCmp) const;
 
 public:
   MemCmpExpansion(CallInst *CI, uint64_t Size,
                   const TargetTransformInfo::MemCmpExpansionOptions &Options,
                   const bool IsUsedForZeroCmp, const DataLayout &TheDataLayout,
-                  DomTreeUpdater *DTU);
+                  DomTreeUpdater *DTU, const TargetTransformInfo &TTI);
 
   unsigned getNumBlocks();
   uint64_t getNumLoads() const { return LoadSequence.size(); }
@@ -152,6 +161,32 @@ class MemCmpExpansion {
   Value *getMemCmpExpansion();
 };
 
+// Return true if a load of `LoadSize` bytes at `Offset` from the base pointers
+// is accessible on the target: either it is naturally aligned given the known
+// common base alignment, or the target allows a misaligned access of that
+// width. We query whether the access is *allowed*, not whether it is *fast*,
+// matching the historical behavior of forming unaligned loads whenever the
+// target permits them.
+static bool isAccessAllowed(const CallInst *CI, const TargetTransformInfo &TTI,
+                            Align BaseAlign, unsigned LoadSize,
+                            uint64_t Offset) {
+  Align AccessAlign = commonAlignment(BaseAlign, Offset);
+  if (AccessAlign >= LoadSize)
+    return true;
+  unsigned AS = CI->getArgOperand(0)->getType()->getPointerAddressSpace();
+  return TTI.allowsMisalignedMemoryAccesses(CI->getContext(), LoadSize * 8, AS,
+                                            AccessAlign);
+}
+
+// Return true if a load of `LoadSize` bytes at `Offset` from the base pointers
+// is accessible on the target given the known common base alignment. This gates
+// the (power-of-two) overlapping loads; tail expansions are always legalized by
+// the backend and skip this check.
+bool MemCmpExpansion::isAccessAllowed(unsigned LoadSize,
+                                      uint64_t Offset) const {
+  return ::isAccessAllowed(CI, TTI, BaseAlign, LoadSize, Offset);
+}
+
 MemCmpExpansion::LoadEntryVector MemCmpExpansion::computeGreedyLoadSequence(
     uint64_t Size, llvm::ArrayRef<unsigned> LoadSizes,
     const unsigned MaxNumLoads, unsigned &NumLoadsNonOneByte) {
@@ -183,10 +218,9 @@ MemCmpExpansion::LoadEntryVector MemCmpExpansion::computeGreedyLoadSequence(
 }
 
 MemCmpExpansion::LoadEntryVector
-MemCmpExpansion::computeOverlappingLoadSequence(uint64_t Size,
-                                                const unsigned MaxLoadSize,
-                                                const unsigned MaxNumLoads,
-                                                unsigned &NumLoadsNonOneByte) {
+MemCmpExpansion::computeOverlappingLoadSequence(
+    uint64_t Size, const unsigned MaxLoadSize, const unsigned MaxNumLoads,
+    unsigned &NumLoadsNonOneByte) const {
   // These are already handled by the greedy approach.
   if (Size < 2 || MaxLoadSize < 2)
     return {};
@@ -215,9 +249,14 @@ MemCmpExpansion::computeOverlappingLoadSequence(uint64_t Size,
     Offset += MaxLoadSize;
   }
 
-  // Add the last overlapping load.
+  // Add the last overlapping load. Its offset is not a multiple of the load
+  // size, so it may be misaligned; bail if the target cannot access it.
   assert(Size > 0 && Size < MaxLoadSize && "broken invariant");
-  LoadSequence.push_back({MaxLoadSize, Offset - (MaxLoadSize - Size)});
+  uint64_t OverlapOffset = Offset - (MaxLoadSize - Size);
+  if (!isAccessAllowed(MaxLoadSize, OverlapOffset))
+    return {};
+
+  LoadSequence.push_back({MaxLoadSize, OverlapOffset});
   NumLoadsNonOneByte = 1;
   return LoadSequence;
 }
@@ -225,7 +264,7 @@ MemCmpExpansion::computeOverlappingLoadSequence(uint64_t Size,
 void MemCmpExpansion::optimiseLoadSequence(
     LoadEntryVector &LoadSequence,
     const TargetTransformInfo::MemCmpExpansionOptions &Options,
-    bool IsUsedForZeroCmp) {
+    bool IsUsedForZeroCmp) const {
   // This part of code attempts to optimize the LoadSequence by merging allowed
   // subsequences into single loads of allowed sizes from
   // `MemCmpExpansionOptions::AllowedTailExpansions`. If it is for zero
@@ -246,6 +285,15 @@ void MemCmpExpansion::optimiseLoadSequence(
         Options.AllowedTailExpansions.end())
       break;
 
+    // A merged load wider than MaxLoadSize can only be emitted when it is the
+    // sole load (getMemCmpOneBlock); in a multi-block expansion
+    // emitLoadCompareBlock requires every load to fit in MaxLoadSize (the
+    // result-block phis are sized to it). The per-call-site alignment filter
+    // can shrink MaxLoadSize, so stop merging when the result would still be
+    // multi-block and the merged load exceeds it.
+    if (LoadSize > MaxLoadSize && LoadSequence.size() > 2)
+      break;
+
     // Remove the last two sequences and replace with the combined sequence
     LoadSequence.pop_back();
     LoadSequence.pop_back();
@@ -265,10 +313,12 @@ MemCmpExpansion::MemCmpExpansion(
     CallInst *const CI, uint64_t Size,
     const TargetTransformInfo::MemCmpExpansionOptions &Options,
     const bool IsUsedForZeroCmp, const DataLayout &TheDataLayout,
-    DomTreeUpdater *DTU)
+    DomTreeUpdater *DTU, const TargetTransformInfo &TTI)
     : CI(CI), Size(Size), NumLoadsPerBlockForZeroCmp(Options.NumLoadsPerBlock),
-      IsUsedForZeroCmp(IsUsedForZeroCmp), DL(TheDataLayout), DTU(DTU),
-      Builder(CI) {
+      IsUsedForZeroCmp(IsUsedForZeroCmp), DL(TheDataLayout), TTI(TTI),
+      BaseAlign(std::min(getMemCmpArgAlignment(CI, 0, DL),
+                         getMemCmpArgAlignment(CI, 1, DL))),
+      DTU(DTU), Builder(CI) {
   assert(Size > 0 && "zero blocks");
   // Scale the max size down if the target can load more bytes than we need.
   llvm::ArrayRef<unsigned> LoadSizes(Options.LoadSizes);
@@ -870,29 +920,20 @@ static bool expandMemCmp(CallInst *CI, const TargetTransformInfo *TTI,
   if (!OptForSize && MaxLoadsPerMemcmp.getNumOccurrences())
     Options.MaxNumLoads = MaxLoadsPerMemcmp;
 
-  // Keep only the load sizes the target can actually access given the
-  // statically known common alignment of both pointers: either the access is
-  // naturally aligned, or the target allows a misaligned access of that width.
-  // This lets strict-alignment targets expand compares whose pointers happen to
-  // be sufficiently aligned, while still falling back to the libcall when no
-  // load size fits. Because the greedy load sequence only places a load of size
-  // S at an offset that is a multiple of S, a load that does not exceed the
-  // base alignment is guaranteed to be naturally aligned.
-  //
-  // Note we query whether the access is *allowed*, not whether it is *fast*:
-  // this matches the historical behavior of forming unaligned loads whenever
-  // the target permits them, so it is a no-op for targets that allow unaligned
-  // access even when it is slow.
+  // Keep only the load sizes the target can access at the base alignment:
+  // either the access is naturally aligned, or the target allows a misaligned
+  // access of that width. This lets strict-alignment targets expand compares
+  // whose pointers happen to be sufficiently aligned, while still falling back
+  // to the libcall when no load size fits. Because the greedy load sequence
+  // only places a load of size S at an offset that is a multiple of S, a size
+  // kept here is always accessible in that sequence; overlapping loads and
+  // merged tail expansions are checked separately against their actual offsets
+  // in MemCmpExpansion.
   const Align LhsAlign = getMemCmpArgAlignment(CI, 0, *DL);
   const Align RhsAlign = getMemCmpArgAlignment(CI, 1, *DL);
   const Align MinAlign = std::min(LhsAlign, RhsAlign);
-  LLVMContext &Context = CI->getContext();
-  unsigned AS = CI->getArgOperand(0)->getType()->getPointerAddressSpace();
   llvm::erase_if(Options.LoadSizes, [&](unsigned LoadSize) {
-    if (MinAlign >= LoadSize)
-      return false;
-    return !TTI->allowsMisalignedMemoryAccesses(Context, LoadSize * 8, AS,
-                                                MinAlign);
+    return !isAccessAllowed(CI, *TTI, MinAlign, LoadSize, /*Offset=*/0);
   });
   // If the filter removed every load size, bail out to the libcall: the
   // MemCmpExpansion constructor asserts that at least one load size remains.
@@ -901,7 +942,8 @@ static bool expandMemCmp(CallInst *CI, const TargetTransformInfo *TTI,
   if (Options.LoadSizes.empty())
     return false;
 
-  MemCmpExpansion Expansion(CI, SizeVal, Options, IsUsedForZeroCmp, *DL, DTU);
+  MemCmpExpansion Expansion(CI, SizeVal, Options, IsUsedForZeroCmp, *DL, DTU,
+                            *TTI);
 
   // Don't expand if this will require more loads than desired by the target.
   if (Expansion.getNumLoads() == 0) {
diff --git a/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll b/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
index 756c5f99ee44a..475e5c980c13f 100644
--- a/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
+++ b/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
@@ -16,30 +16,14 @@ declare i32 @memcmp(ptr, ptr, iXLen)
 define i32 @memcmp_size_3_align_4(ptr align 4 %a, ptr align 4 %b) {
 ; RV64-LABEL: define i32 @memcmp_size_3_align_4(
 ; RV64-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0:[0-9]+]] {
-; RV64-NEXT:    br label %[[LOADBB:.*]]
-; RV64:       [[RES_BLOCK:.*]]:
-; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i16 [[TMP5:%.*]], [[TMP6:%.*]]
-; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
-; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
-; RV64:       [[LOADBB]]:
-; RV64-NEXT:    [[TMP3:%.*]] = load i16, ptr [[A]], align 4
-; RV64-NEXT:    [[TMP4:%.*]] = load i16, ptr [[B]], align 4
-; RV64-NEXT:    [[TMP5]] = call i16 @llvm.bswap.i16(i16 [[TMP3]])
-; RV64-NEXT:    [[TMP6]] = call i16 @llvm.bswap.i16(i16 [[TMP4]])
-; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i16 [[TMP5]], [[TMP6]]
-; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1:.*]], label %[[RES_BLOCK]]
-; RV64:       [[LOADBB1]]:
-; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 2
-; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 2
-; RV64-NEXT:    [[TMP10:%.*]] = load i8, ptr [[TMP8]], align 2
-; RV64-NEXT:    [[TMP11:%.*]] = load i8, ptr [[TMP9]], align 2
-; RV64-NEXT:    [[TMP12:%.*]] = zext i8 [[TMP10]] to i32
-; RV64-NEXT:    [[TMP13:%.*]] = zext i8 [[TMP11]] to i32
-; RV64-NEXT:    [[TMP14:%.*]] = sub i32 [[TMP12]], [[TMP13]]
-; RV64-NEXT:    br label %[[ENDBLOCK]]
-; RV64:       [[ENDBLOCK]]:
-; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ [[TMP14]], %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
-; RV64-NEXT:    ret i32 [[PHI_RES]]
+; RV64-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 4
+; RV64-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 4
+; RV64-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
+; RV64-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
+; RV64-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; RV64-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; RV64-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
+; RV64-NEXT:    ret i32 [[TMP7]]
 ;
 ; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_4(
 ; RV64-UNALIGNED-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0:[0-9]+]] {
@@ -116,34 +100,14 @@ define i32 @memcmp_size_3_align_1(ptr %a, ptr %b) {
 define i32 @memcmp_size_6_align_8(ptr align 8 %a, ptr align 8 %b) {
 ; RV64-LABEL: define i32 @memcmp_size_6_align_8(
 ; RV64-SAME: ptr align 8 [[A:%.*]], ptr align 8 [[B:%.*]]) #[[ATTR0]] {
-; RV64-NEXT:    br label %[[LOADBB:.*]]
-; RV64:       [[RES_BLOCK:.*]]:
-; RV64-NEXT:    [[PHI_SRC1:%.*]] = phi i32 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP14:%.*]], %[[LOADBB1:.*]] ]
-; RV64-NEXT:    [[PHI_SRC2:%.*]] = phi i32 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP15:%.*]], %[[LOADBB1]] ]
-; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i32 [[PHI_SRC1]], [[PHI_SRC2]]
-; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
-; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
-; RV64:       [[LOADBB]]:
-; RV64-NEXT:    [[TMP3:%.*]] = load i32, ptr [[A]], align 8
-; RV64-NEXT:    [[TMP4:%.*]] = load i32, ptr [[B]], align 8
-; RV64-NEXT:    [[TMP5]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
-; RV64-NEXT:    [[TMP6]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
-; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i32 [[TMP5]], [[TMP6]]
-; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
-; RV64:       [[LOADBB1]]:
-; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 4
-; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 4
-; RV64-NEXT:    [[TMP10:%.*]] = load i16, ptr [[TMP8]], align 4
-; RV64-NEXT:    [[TMP11:%.*]] = load i16, ptr [[TMP9]], align 4
-; RV64-NEXT:    [[TMP12:%.*]] = call i16 @llvm.bswap.i16(i16 [[TMP10]])
-; RV64-NEXT:    [[TMP13:%.*]] = call i16 @llvm.bswap.i16(i16 [[TMP11]])
-; RV64-NEXT:    [[TMP14]] = zext i16 [[TMP12]] to i32
-; RV64-NEXT:    [[TMP15]] = zext i16 [[TMP13]] to i32
-; RV64-NEXT:    [[TMP16:%.*]] = icmp eq i32 [[TMP14]], [[TMP15]]
-; RV64-NEXT:    br i1 [[TMP16]], label %[[ENDBLOCK]], label %[[RES_BLOCK]]
-; RV64:       [[ENDBLOCK]]:
-; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ 0, %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
-; RV64-NEXT:    ret i32 [[PHI_RES]]
+; RV64-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 8
+; RV64-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 8
+; RV64-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
+; RV64-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
+; RV64-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
+; RV64-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
+; RV64-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
+; RV64-NEXT:    ret i32 [[TMP7]]
 ;
 ; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_8(
 ; RV64-UNALIGNED-SAME: ptr align 8 [[A:%.*]], ptr align 8 [[B:%.*]]) #[[ATTR0]] {
@@ -222,30 +186,14 @@ define i32 @memcmp_size_6_align_2(ptr align 2 %a, ptr align 2 %b) {
 define i32 @memcmp_size_3_align_2(ptr align 2 %a, ptr align 2 %b) {
 ; RV64-LABEL: define i32 @memcmp_size_3_align_2(
 ; RV64-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
-; RV64-NEXT:    br label %[[LOADBB:.*]]
-; RV64:       [[RES_BLOCK:.*]]:
-; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i16 [[TMP5:%.*]], [[TMP6:%.*]]
-; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
-; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
-; RV64:       [[LOADBB]]:
-; RV64-NEXT:    [[TMP3:%.*]] = load i16, ptr [[A]], align 2
-; RV64-NEXT:    [[TMP4:%.*]] = load i16, ptr [[B]], align 2
-; RV64-NEXT:    [[TMP5]] = call i16 @llvm.bswap.i16(i16 [[TMP3]])
-; RV64-NEXT:    [[TMP6]] = call i16 @llvm.bswap.i16(i16 [[TMP4]])
-; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i16 [[TMP5]], [[TMP6]]
-; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1:.*]], label %[[RES_BLOCK]]
-; RV64:       [[LOADBB1]]:
-; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 2
-; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 2
-; RV64-NEXT:    [[TMP10:%.*]] = load i8, ptr [[TMP8]], align 2
-; RV64-NEXT:    [[TMP11:%.*]] = load i8, ptr [[TMP9]], align 2
-; RV64-NEXT:    [[TMP12:%.*]] = zext i8 [[TMP10]] to i32
-; RV64-NEXT:    [[TMP13:%.*]] = zext i8 [[TMP11]] to i32
-; RV64-NEXT:    [[TMP14:%.*]] = sub i32 [[TMP12]], [[TMP13]]
-; RV64-NEXT:    br label %[[ENDBLOCK]]
-; RV64:       [[ENDBLOCK]]:
-; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ [[TMP14]], %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
-; RV64-NEXT:    ret i32 [[PHI_RES]]
+; RV64-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 2
+; RV64-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 2
+; RV64-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
+; RV64-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
+; RV64-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; RV64-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; RV64-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
+; RV64-NEXT:    ret i32 [[TMP7]]
 ;
 ; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_2(
 ; RV64-UNALIGNED-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
@@ -266,34 +214,14 @@ define i32 @memcmp_size_3_align_2(ptr align 2 %a, ptr align 2 %b) {
 define i32 @memcmp_size_6_align_4(ptr align 4 %a, ptr align 4 %b) {
 ; RV64-LABEL: define i32 @memcmp_size_6_align_4(
 ; RV64-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0]] {
-; RV64-NEXT:    br label %[[LOADBB:.*]]
-; RV64:       [[RES_BLOCK:.*]]:
-; RV64-NEXT:    [[PHI_SRC1:%.*]] = phi i32 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP14:%.*]], %[[LOADBB1:.*]] ]
-; RV64-NEXT:    [[PHI_SRC2:%.*]] = phi i32 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP15:%.*]], %[[LOADBB1]] ]
-; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i32 [[PHI_SRC1]], [[PHI_SRC2]]
-; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
-; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
-; RV64:       [[LOADBB]]:
-; RV64-NEXT:    [[TMP3:%.*]] = load i32, ptr [[A]], align 4
-; RV64-NEXT:    [[TMP4:%.*]] = load i32, ptr [[B]], align 4
-; RV64-NEXT:    [[TMP5]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
-; RV64-NEXT:    [[TMP6]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
-; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i32 [[TMP5]], [[TMP6]]
-; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
-; RV64:       [[LOADBB1]]:
-; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 4
-; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 4
-; RV64-NEXT:    [[TMP10:%.*]] = load i16, ptr [[TMP8]], align 4
-; RV64-NEXT:    [[TMP11:%.*]] = load i16, ptr [[TMP9]], align 4
-; RV64-NEXT:    [[TMP12:%.*]] = call i16 @llvm.bswap.i16(i16 [[TMP10]])
-; RV64-NEXT:    [[TMP13:%.*]] = call i16 @llvm.bswap.i16(i16 [[TMP11]])
-; RV64-NEXT:    [[TMP14]] = zext i16 [[TMP12]] to i32
-; RV64-NEXT:    [[TMP15]] = zext i16 [[TMP13]] to i32
-; RV64-NEXT:    [[TMP16:%.*]] = icmp eq i32 [[TMP14]], [[TMP15]]
-; RV64-NEXT:    br i1 [[TMP16]], label %[[ENDBLOCK]], label %[[RES_BLOCK]]
-; RV64:       [[ENDBLOCK]]:
-; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ 0, %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
-; RV64-NEXT:    ret i32 [[PHI_RES]]
+; RV64-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 4
+; RV64-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 4
+; RV64-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
+; RV64-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
+; RV64-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
+; RV64-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
+; RV64-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
+; RV64-NEXT:    ret i32 [[TMP7]]
 ;
 ; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_4(
 ; RV64-UNALIGNED-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0]] {

>From 8ec3f491b927d5e151b9911a58ae26acf7ab195f Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Wed, 22 Jul 2026 18:43:59 +0800
Subject: [PATCH 4/9] Rework tests: remove sed and change prefixes

---
 .../RISCV/memcmp-misaligned-access.ll         | 512 +++++++++---------
 1 file changed, 256 insertions(+), 256 deletions(-)

diff --git a/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll b/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
index 475e5c980c13f..78b3dfe0e0b45 100644
--- a/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
+++ b/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
@@ -1,6 +1,6 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: sed 's/iXLen/i64/g' %s | opt -S -passes=expand-memcmp -mtriple=riscv64 -mattr=+m,+zbb | FileCheck %s --check-prefixes=RV64
-; RUN: sed 's/iXLen/i64/g' %s | opt -S -passes=expand-memcmp -mtriple=riscv64 -mattr=+m,+zbb,+unaligned-scalar-mem | FileCheck %s --check-prefixes=RV64-UNALIGNED
+; RUN: opt -S -passes=expand-memcmp -mtriple=riscv64 -mattr=+m,+zbb < %s | FileCheck %s --check-prefixes=CHECK
+; RUN: opt -S -passes=expand-memcmp -mtriple=riscv64 -mattr=+m,+zbb,+unaligned-scalar-mem < %s | FileCheck %s --check-prefixes=CHECK-UNALIGNED
 
 ; Overlapping loads and merged tail expansions place a load at an offset that
 ; need not be a multiple of its size. On strict-alignment targets they are only
@@ -9,34 +9,34 @@
 ; flag. These are ordering (non-zero) memcmp compares so that the tail-merge and
 ; overlapping-load paths are exercised.
 
-declare i32 @memcmp(ptr, ptr, iXLen)
+declare i32 @memcmp(ptr, ptr, i64)
 
 ; Tail merge: 3-byte tail at offset 0. With align 4 the merged i24 load is
 ; naturally aligned, so it is formed even on a strict-align target.
 define i32 @memcmp_size_3_align_4(ptr align 4 %a, ptr align 4 %b) {
-; RV64-LABEL: define i32 @memcmp_size_3_align_4(
-; RV64-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0:[0-9]+]] {
-; RV64-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 4
-; RV64-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 4
-; RV64-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
-; RV64-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
-; RV64-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
-; RV64-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
-; RV64-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
-; RV64-NEXT:    ret i32 [[TMP7]]
+; CHECK-LABEL: define i32 @memcmp_size_3_align_4(
+; CHECK-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 4
+; CHECK-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 4
+; CHECK-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
+; CHECK-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
+; CHECK-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; CHECK-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
+; CHECK-NEXT:    ret i32 [[TMP7]]
 ;
-; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_4(
-; RV64-UNALIGNED-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0:[0-9]+]] {
-; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 4
-; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 4
-; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
-; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
-; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
-; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
-; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
-; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+; CHECK-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_4(
+; CHECK-UNALIGNED-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 4
+; CHECK-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 4
+; CHECK-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
+; CHECK-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
+; CHECK-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; CHECK-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; CHECK-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
+; CHECK-UNALIGNED-NEXT:    ret i32 [[TMP7]]
 ;
-  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 3)
+  %r = call i32 @memcmp(ptr %a, ptr %b, i64 3)
   ret i32 %r
 }
 
@@ -44,139 +44,139 @@ define i32 @memcmp_size_3_align_4(ptr align 4 %a, ptr align 4 %b) {
 ; misaligned, so on a strict-align target only byte loads remain, while an
 ; unaligned target still merges into a single i24.
 define i32 @memcmp_size_3_align_1(ptr %a, ptr %b) {
-; RV64-LABEL: define i32 @memcmp_size_3_align_1(
-; RV64-SAME: ptr [[A:%.*]], ptr [[B:%.*]]) #[[ATTR0]] {
-; RV64-NEXT:    br label %[[LOADBB:.*]]
-; RV64:       [[RES_BLOCK:.*]]:
-; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
-; RV64:       [[LOADBB]]:
-; RV64-NEXT:    [[TMP1:%.*]] = load i8, ptr [[A]], align 1
-; RV64-NEXT:    [[TMP2:%.*]] = load i8, ptr [[B]], align 1
-; RV64-NEXT:    [[TMP3:%.*]] = zext i8 [[TMP1]] to i32
-; RV64-NEXT:    [[TMP4:%.*]] = zext i8 [[TMP2]] to i32
-; RV64-NEXT:    [[TMP5:%.*]] = sub i32 [[TMP3]], [[TMP4]]
-; RV64-NEXT:    [[TMP6:%.*]] = icmp ne i32 [[TMP5]], 0
-; RV64-NEXT:    br i1 [[TMP6]], label %[[ENDBLOCK]], label %[[LOADBB1:.*]]
-; RV64:       [[LOADBB1]]:
-; RV64-NEXT:    [[TMP7:%.*]] = getelementptr i8, ptr [[A]], i64 1
-; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[B]], i64 1
-; RV64-NEXT:    [[TMP9:%.*]] = load i8, ptr [[TMP7]], align 1
-; RV64-NEXT:    [[TMP10:%.*]] = load i8, ptr [[TMP8]], align 1
-; RV64-NEXT:    [[TMP11:%.*]] = zext i8 [[TMP9]] to i32
-; RV64-NEXT:    [[TMP12:%.*]] = zext i8 [[TMP10]] to i32
-; RV64-NEXT:    [[TMP13:%.*]] = sub i32 [[TMP11]], [[TMP12]]
-; RV64-NEXT:    [[TMP14:%.*]] = icmp ne i32 [[TMP13]], 0
-; RV64-NEXT:    br i1 [[TMP14]], label %[[ENDBLOCK]], label %[[LOADBB2:.*]]
-; RV64:       [[LOADBB2]]:
-; RV64-NEXT:    [[TMP15:%.*]] = getelementptr i8, ptr [[A]], i64 2
-; RV64-NEXT:    [[TMP16:%.*]] = getelementptr i8, ptr [[B]], i64 2
-; RV64-NEXT:    [[TMP17:%.*]] = load i8, ptr [[TMP15]], align 1
-; RV64-NEXT:    [[TMP18:%.*]] = load i8, ptr [[TMP16]], align 1
-; RV64-NEXT:    [[TMP19:%.*]] = zext i8 [[TMP17]] to i32
-; RV64-NEXT:    [[TMP20:%.*]] = zext i8 [[TMP18]] to i32
-; RV64-NEXT:    [[TMP21:%.*]] = sub i32 [[TMP19]], [[TMP20]]
-; RV64-NEXT:    br label %[[ENDBLOCK]]
-; RV64:       [[ENDBLOCK]]:
-; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ [[TMP5]], %[[LOADBB]] ], [ [[TMP13]], %[[LOADBB1]] ], [ [[TMP21]], %[[LOADBB2]] ], [ poison, %[[RES_BLOCK]] ]
-; RV64-NEXT:    ret i32 [[PHI_RES]]
+; CHECK-LABEL: define i32 @memcmp_size_3_align_1(
+; CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    br label %[[LOADBB:.*]]
+; CHECK:       [[RES_BLOCK:.*]]:
+; CHECK-NEXT:    br label %[[ENDBLOCK:.*]]
+; CHECK:       [[LOADBB]]:
+; CHECK-NEXT:    [[TMP1:%.*]] = load i8, ptr [[A]], align 1
+; CHECK-NEXT:    [[TMP2:%.*]] = load i8, ptr [[B]], align 1
+; CHECK-NEXT:    [[TMP3:%.*]] = zext i8 [[TMP1]] to i32
+; CHECK-NEXT:    [[TMP4:%.*]] = zext i8 [[TMP2]] to i32
+; CHECK-NEXT:    [[TMP5:%.*]] = sub i32 [[TMP3]], [[TMP4]]
+; CHECK-NEXT:    [[TMP6:%.*]] = icmp ne i32 [[TMP5]], 0
+; CHECK-NEXT:    br i1 [[TMP6]], label %[[ENDBLOCK]], label %[[LOADBB1:.*]]
+; CHECK:       [[LOADBB1]]:
+; CHECK-NEXT:    [[TMP7:%.*]] = getelementptr i8, ptr [[A]], i64 1
+; CHECK-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[B]], i64 1
+; CHECK-NEXT:    [[TMP9:%.*]] = load i8, ptr [[TMP7]], align 1
+; CHECK-NEXT:    [[TMP10:%.*]] = load i8, ptr [[TMP8]], align 1
+; CHECK-NEXT:    [[TMP11:%.*]] = zext i8 [[TMP9]] to i32
+; CHECK-NEXT:    [[TMP12:%.*]] = zext i8 [[TMP10]] to i32
+; CHECK-NEXT:    [[TMP13:%.*]] = sub i32 [[TMP11]], [[TMP12]]
+; CHECK-NEXT:    [[TMP14:%.*]] = icmp ne i32 [[TMP13]], 0
+; CHECK-NEXT:    br i1 [[TMP14]], label %[[ENDBLOCK]], label %[[LOADBB2:.*]]
+; CHECK:       [[LOADBB2]]:
+; CHECK-NEXT:    [[TMP15:%.*]] = getelementptr i8, ptr [[A]], i64 2
+; CHECK-NEXT:    [[TMP16:%.*]] = getelementptr i8, ptr [[B]], i64 2
+; CHECK-NEXT:    [[TMP17:%.*]] = load i8, ptr [[TMP15]], align 1
+; CHECK-NEXT:    [[TMP18:%.*]] = load i8, ptr [[TMP16]], align 1
+; CHECK-NEXT:    [[TMP19:%.*]] = zext i8 [[TMP17]] to i32
+; CHECK-NEXT:    [[TMP20:%.*]] = zext i8 [[TMP18]] to i32
+; CHECK-NEXT:    [[TMP21:%.*]] = sub i32 [[TMP19]], [[TMP20]]
+; CHECK-NEXT:    br label %[[ENDBLOCK]]
+; CHECK:       [[ENDBLOCK]]:
+; CHECK-NEXT:    [[PHI_RES:%.*]] = phi i32 [ [[TMP5]], %[[LOADBB]] ], [ [[TMP13]], %[[LOADBB1]] ], [ [[TMP21]], %[[LOADBB2]] ], [ poison, %[[RES_BLOCK]] ]
+; CHECK-NEXT:    ret i32 [[PHI_RES]]
 ;
-; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_1(
-; RV64-UNALIGNED-SAME: ptr [[A:%.*]], ptr [[B:%.*]]) #[[ATTR0]] {
-; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 1
-; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 1
-; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
-; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
-; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
-; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
-; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
-; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+; CHECK-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_1(
+; CHECK-UNALIGNED-SAME: ptr [[A:%.*]], ptr [[B:%.*]]) #[[ATTR0]] {
+; CHECK-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 1
+; CHECK-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 1
+; CHECK-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
+; CHECK-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
+; CHECK-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; CHECK-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; CHECK-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
+; CHECK-UNALIGNED-NEXT:    ret i32 [[TMP7]]
 ;
-  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 3)
+  %r = call i32 @memcmp(ptr %a, ptr %b, i64 3)
   ret i32 %r
 }
 
 ; 6-byte tail merge (AllowedTailExpansions has 6). align 8 makes the merged i48
 ; at offset 0 naturally aligned, so it is formed on a strict-align target.
 define i32 @memcmp_size_6_align_8(ptr align 8 %a, ptr align 8 %b) {
-; RV64-LABEL: define i32 @memcmp_size_6_align_8(
-; RV64-SAME: ptr align 8 [[A:%.*]], ptr align 8 [[B:%.*]]) #[[ATTR0]] {
-; RV64-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 8
-; RV64-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 8
-; RV64-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
-; RV64-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
-; RV64-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
-; RV64-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
-; RV64-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
-; RV64-NEXT:    ret i32 [[TMP7]]
+; CHECK-LABEL: define i32 @memcmp_size_6_align_8(
+; CHECK-SAME: ptr align 8 [[A:%.*]], ptr align 8 [[B:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 8
+; CHECK-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 8
+; CHECK-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
+; CHECK-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
+; CHECK-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
+; CHECK-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
+; CHECK-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
+; CHECK-NEXT:    ret i32 [[TMP7]]
 ;
-; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_8(
-; RV64-UNALIGNED-SAME: ptr align 8 [[A:%.*]], ptr align 8 [[B:%.*]]) #[[ATTR0]] {
-; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 8
-; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 8
-; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
-; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
-; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
-; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
-; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
-; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+; CHECK-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_8(
+; CHECK-UNALIGNED-SAME: ptr align 8 [[A:%.*]], ptr align 8 [[B:%.*]]) #[[ATTR0]] {
+; CHECK-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 8
+; CHECK-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 8
+; CHECK-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
+; CHECK-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
+; CHECK-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
+; CHECK-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
+; CHECK-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
+; CHECK-UNALIGNED-NEXT:    ret i32 [[TMP7]]
 ;
-  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 6)
+  %r = call i32 @memcmp(ptr %a, ptr %b, i64 6)
   ret i32 %r
 }
 
 ; With align 2 the wide loads are misaligned, so the strict-align target uses
 ; the greedy naturally-aligned i16 sequence instead of merging/overlapping.
 define i32 @memcmp_size_6_align_2(ptr align 2 %a, ptr align 2 %b) {
-; RV64-LABEL: define i32 @memcmp_size_6_align_2(
-; RV64-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
-; RV64-NEXT:    br label %[[LOADBB:.*]]
-; RV64:       [[RES_BLOCK:.*]]:
-; RV64-NEXT:    [[PHI_SRC1:%.*]] = phi i16 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP12:%.*]], %[[LOADBB1:.*]] ], [ [[TMP19:%.*]], %[[LOADBB2:.*]] ]
-; RV64-NEXT:    [[PHI_SRC2:%.*]] = phi i16 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP13:%.*]], %[[LOADBB1]] ], [ [[TMP20:%.*]], %[[LOADBB2]] ]
-; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i16 [[PHI_SRC1]], [[PHI_SRC2]]
-; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
-; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
-; RV64:       [[LOADBB]]:
-; RV64-NEXT:    [[TMP3:%.*]] = load i16, ptr [[A]], align 2
-; RV64-NEXT:    [[TMP4:%.*]] = load i16, ptr [[B]], align 2
-; RV64-NEXT:    [[TMP5]] = call i16 @llvm.bswap.i16(i16 [[TMP3]])
-; RV64-NEXT:    [[TMP6]] = call i16 @llvm.bswap.i16(i16 [[TMP4]])
-; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i16 [[TMP5]], [[TMP6]]
-; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
-; RV64:       [[LOADBB1]]:
-; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 2
-; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 2
-; RV64-NEXT:    [[TMP10:%.*]] = load i16, ptr [[TMP8]], align 2
-; RV64-NEXT:    [[TMP11:%.*]] = load i16, ptr [[TMP9]], align 2
-; RV64-NEXT:    [[TMP12]] = call i16 @llvm.bswap.i16(i16 [[TMP10]])
-; RV64-NEXT:    [[TMP13]] = call i16 @llvm.bswap.i16(i16 [[TMP11]])
-; RV64-NEXT:    [[TMP14:%.*]] = icmp eq i16 [[TMP12]], [[TMP13]]
-; RV64-NEXT:    br i1 [[TMP14]], label %[[LOADBB2]], label %[[RES_BLOCK]]
-; RV64:       [[LOADBB2]]:
-; RV64-NEXT:    [[TMP15:%.*]] = getelementptr i8, ptr [[A]], i64 4
-; RV64-NEXT:    [[TMP16:%.*]] = getelementptr i8, ptr [[B]], i64 4
-; RV64-NEXT:    [[TMP17:%.*]] = load i16, ptr [[TMP15]], align 2
-; RV64-NEXT:    [[TMP18:%.*]] = load i16, ptr [[TMP16]], align 2
-; RV64-NEXT:    [[TMP19]] = call i16 @llvm.bswap.i16(i16 [[TMP17]])
-; RV64-NEXT:    [[TMP20]] = call i16 @llvm.bswap.i16(i16 [[TMP18]])
-; RV64-NEXT:    [[TMP21:%.*]] = icmp eq i16 [[TMP19]], [[TMP20]]
-; RV64-NEXT:    br i1 [[TMP21]], label %[[ENDBLOCK]], label %[[RES_BLOCK]]
-; RV64:       [[ENDBLOCK]]:
-; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ 0, %[[LOADBB2]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
-; RV64-NEXT:    ret i32 [[PHI_RES]]
+; CHECK-LABEL: define i32 @memcmp_size_6_align_2(
+; CHECK-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    br label %[[LOADBB:.*]]
+; CHECK:       [[RES_BLOCK:.*]]:
+; CHECK-NEXT:    [[PHI_SRC1:%.*]] = phi i16 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP12:%.*]], %[[LOADBB1:.*]] ], [ [[TMP19:%.*]], %[[LOADBB2:.*]] ]
+; CHECK-NEXT:    [[PHI_SRC2:%.*]] = phi i16 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP13:%.*]], %[[LOADBB1]] ], [ [[TMP20:%.*]], %[[LOADBB2]] ]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp ult i16 [[PHI_SRC1]], [[PHI_SRC2]]
+; CHECK-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
+; CHECK-NEXT:    br label %[[ENDBLOCK:.*]]
+; CHECK:       [[LOADBB]]:
+; CHECK-NEXT:    [[TMP3:%.*]] = load i16, ptr [[A]], align 2
+; CHECK-NEXT:    [[TMP4:%.*]] = load i16, ptr [[B]], align 2
+; CHECK-NEXT:    [[TMP5]] = call i16 @llvm.bswap.i16(i16 [[TMP3]])
+; CHECK-NEXT:    [[TMP6]] = call i16 @llvm.bswap.i16(i16 [[TMP4]])
+; CHECK-NEXT:    [[TMP7:%.*]] = icmp eq i16 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
+; CHECK:       [[LOADBB1]]:
+; CHECK-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 2
+; CHECK-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 2
+; CHECK-NEXT:    [[TMP10:%.*]] = load i16, ptr [[TMP8]], align 2
+; CHECK-NEXT:    [[TMP11:%.*]] = load i16, ptr [[TMP9]], align 2
+; CHECK-NEXT:    [[TMP12]] = call i16 @llvm.bswap.i16(i16 [[TMP10]])
+; CHECK-NEXT:    [[TMP13]] = call i16 @llvm.bswap.i16(i16 [[TMP11]])
+; CHECK-NEXT:    [[TMP14:%.*]] = icmp eq i16 [[TMP12]], [[TMP13]]
+; CHECK-NEXT:    br i1 [[TMP14]], label %[[LOADBB2]], label %[[RES_BLOCK]]
+; CHECK:       [[LOADBB2]]:
+; CHECK-NEXT:    [[TMP15:%.*]] = getelementptr i8, ptr [[A]], i64 4
+; CHECK-NEXT:    [[TMP16:%.*]] = getelementptr i8, ptr [[B]], i64 4
+; CHECK-NEXT:    [[TMP17:%.*]] = load i16, ptr [[TMP15]], align 2
+; CHECK-NEXT:    [[TMP18:%.*]] = load i16, ptr [[TMP16]], align 2
+; CHECK-NEXT:    [[TMP19]] = call i16 @llvm.bswap.i16(i16 [[TMP17]])
+; CHECK-NEXT:    [[TMP20]] = call i16 @llvm.bswap.i16(i16 [[TMP18]])
+; CHECK-NEXT:    [[TMP21:%.*]] = icmp eq i16 [[TMP19]], [[TMP20]]
+; CHECK-NEXT:    br i1 [[TMP21]], label %[[ENDBLOCK]], label %[[RES_BLOCK]]
+; CHECK:       [[ENDBLOCK]]:
+; CHECK-NEXT:    [[PHI_RES:%.*]] = phi i32 [ 0, %[[LOADBB2]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
+; CHECK-NEXT:    ret i32 [[PHI_RES]]
 ;
-; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_2(
-; RV64-UNALIGNED-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
-; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 2
-; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 2
-; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
-; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
-; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
-; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
-; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
-; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+; CHECK-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_2(
+; CHECK-UNALIGNED-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; CHECK-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 2
+; CHECK-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 2
+; CHECK-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
+; CHECK-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
+; CHECK-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
+; CHECK-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
+; CHECK-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
+; CHECK-UNALIGNED-NEXT:    ret i32 [[TMP7]]
 ;
-  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 6)
+  %r = call i32 @memcmp(ptr %a, ptr %b, i64 6)
   ret i32 %r
 }
 
@@ -184,57 +184,57 @@ define i32 @memcmp_size_6_align_2(ptr align 2 %a, ptr align 2 %b) {
 ; so it is formed regardless of the pointer alignment (the i24 covers the same
 ; bytes as the i16+i8 it replaces).
 define i32 @memcmp_size_3_align_2(ptr align 2 %a, ptr align 2 %b) {
-; RV64-LABEL: define i32 @memcmp_size_3_align_2(
-; RV64-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
-; RV64-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 2
-; RV64-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 2
-; RV64-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
-; RV64-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
-; RV64-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
-; RV64-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
-; RV64-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
-; RV64-NEXT:    ret i32 [[TMP7]]
+; CHECK-LABEL: define i32 @memcmp_size_3_align_2(
+; CHECK-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 2
+; CHECK-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 2
+; CHECK-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
+; CHECK-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
+; CHECK-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; CHECK-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; CHECK-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
+; CHECK-NEXT:    ret i32 [[TMP7]]
 ;
-; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_2(
-; RV64-UNALIGNED-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
-; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 2
-; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 2
-; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
-; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
-; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
-; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
-; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
-; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+; CHECK-UNALIGNED-LABEL: define i32 @memcmp_size_3_align_2(
+; CHECK-UNALIGNED-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; CHECK-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i24, ptr [[A]], align 2
+; CHECK-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i24, ptr [[B]], align 2
+; CHECK-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i24 [[TMP1]] to i32
+; CHECK-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i24 [[TMP2]] to i32
+; CHECK-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; CHECK-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; CHECK-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i32(i32 [[TMP5]], i32 [[TMP6]])
+; CHECK-UNALIGNED-NEXT:    ret i32 [[TMP7]]
 ;
-  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 3)
+  %r = call i32 @memcmp(ptr %a, ptr %b, i64 3)
   ret i32 %r
 }
 
 ; Likewise the i48 tail merge is formed independent of alignment.
 define i32 @memcmp_size_6_align_4(ptr align 4 %a, ptr align 4 %b) {
-; RV64-LABEL: define i32 @memcmp_size_6_align_4(
-; RV64-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0]] {
-; RV64-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 4
-; RV64-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 4
-; RV64-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
-; RV64-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
-; RV64-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
-; RV64-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
-; RV64-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
-; RV64-NEXT:    ret i32 [[TMP7]]
+; CHECK-LABEL: define i32 @memcmp_size_6_align_4(
+; CHECK-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 4
+; CHECK-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 4
+; CHECK-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
+; CHECK-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
+; CHECK-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
+; CHECK-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
+; CHECK-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
+; CHECK-NEXT:    ret i32 [[TMP7]]
 ;
-; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_4(
-; RV64-UNALIGNED-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0]] {
-; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 4
-; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 4
-; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
-; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
-; RV64-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
-; RV64-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
-; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
-; RV64-UNALIGNED-NEXT:    ret i32 [[TMP7]]
+; CHECK-UNALIGNED-LABEL: define i32 @memcmp_size_6_align_4(
+; CHECK-UNALIGNED-SAME: ptr align 4 [[A:%.*]], ptr align 4 [[B:%.*]]) #[[ATTR0]] {
+; CHECK-UNALIGNED-NEXT:    [[TMP1:%.*]] = load i48, ptr [[A]], align 4
+; CHECK-UNALIGNED-NEXT:    [[TMP2:%.*]] = load i48, ptr [[B]], align 4
+; CHECK-UNALIGNED-NEXT:    [[TMP3:%.*]] = zext i48 [[TMP1]] to i64
+; CHECK-UNALIGNED-NEXT:    [[TMP4:%.*]] = zext i48 [[TMP2]] to i64
+; CHECK-UNALIGNED-NEXT:    [[TMP5:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP3]])
+; CHECK-UNALIGNED-NEXT:    [[TMP6:%.*]] = call i64 @llvm.bswap.i64(i64 [[TMP4]])
+; CHECK-UNALIGNED-NEXT:    [[TMP7:%.*]] = call i32 @llvm.ucmp.i32.i64(i64 [[TMP5]], i64 [[TMP6]])
+; CHECK-UNALIGNED-NEXT:    ret i32 [[TMP7]]
 ;
-  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 6)
+  %r = call i32 @memcmp(ptr %a, ptr %b, i64 6)
   ret i32 %r
 }
 
@@ -243,82 +243,82 @@ define i32 @memcmp_size_6_align_4(ptr align 4 %a, ptr align 4 %b) {
 ; load wider than MaxLoadSize. Such a merge must be skipped, otherwise
 ; emitLoadCompareBlock hits an oversized load; the greedy i16+i8 tail is used.
 define i32 @memcmp_size_7_align_2(ptr align 2 %a, ptr align 2 %b) {
-; RV64-LABEL: define i32 @memcmp_size_7_align_2(
-; RV64-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
-; RV64-NEXT:    br label %[[LOADBB:.*]]
-; RV64:       [[RES_BLOCK:.*]]:
-; RV64-NEXT:    [[PHI_SRC1:%.*]] = phi i16 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP12:%.*]], %[[LOADBB1:.*]] ], [ [[TMP19:%.*]], %[[LOADBB2:.*]] ]
-; RV64-NEXT:    [[PHI_SRC2:%.*]] = phi i16 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP13:%.*]], %[[LOADBB1]] ], [ [[TMP20:%.*]], %[[LOADBB2]] ]
-; RV64-NEXT:    [[TMP1:%.*]] = icmp ult i16 [[PHI_SRC1]], [[PHI_SRC2]]
-; RV64-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
-; RV64-NEXT:    br label %[[ENDBLOCK:.*]]
-; RV64:       [[LOADBB]]:
-; RV64-NEXT:    [[TMP3:%.*]] = load i16, ptr [[A]], align 2
-; RV64-NEXT:    [[TMP4:%.*]] = load i16, ptr [[B]], align 2
-; RV64-NEXT:    [[TMP5]] = call i16 @llvm.bswap.i16(i16 [[TMP3]])
-; RV64-NEXT:    [[TMP6]] = call i16 @llvm.bswap.i16(i16 [[TMP4]])
-; RV64-NEXT:    [[TMP7:%.*]] = icmp eq i16 [[TMP5]], [[TMP6]]
-; RV64-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
-; RV64:       [[LOADBB1]]:
-; RV64-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 2
-; RV64-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 2
-; RV64-NEXT:    [[TMP10:%.*]] = load i16, ptr [[TMP8]], align 2
-; RV64-NEXT:    [[TMP11:%.*]] = load i16, ptr [[TMP9]], align 2
-; RV64-NEXT:    [[TMP12]] = call i16 @llvm.bswap.i16(i16 [[TMP10]])
-; RV64-NEXT:    [[TMP13]] = call i16 @llvm.bswap.i16(i16 [[TMP11]])
-; RV64-NEXT:    [[TMP14:%.*]] = icmp eq i16 [[TMP12]], [[TMP13]]
-; RV64-NEXT:    br i1 [[TMP14]], label %[[LOADBB2]], label %[[RES_BLOCK]]
-; RV64:       [[LOADBB2]]:
-; RV64-NEXT:    [[TMP15:%.*]] = getelementptr i8, ptr [[A]], i64 4
-; RV64-NEXT:    [[TMP16:%.*]] = getelementptr i8, ptr [[B]], i64 4
-; RV64-NEXT:    [[TMP17:%.*]] = load i16, ptr [[TMP15]], align 2
-; RV64-NEXT:    [[TMP18:%.*]] = load i16, ptr [[TMP16]], align 2
-; RV64-NEXT:    [[TMP19]] = call i16 @llvm.bswap.i16(i16 [[TMP17]])
-; RV64-NEXT:    [[TMP20]] = call i16 @llvm.bswap.i16(i16 [[TMP18]])
-; RV64-NEXT:    [[TMP21:%.*]] = icmp eq i16 [[TMP19]], [[TMP20]]
-; RV64-NEXT:    br i1 [[TMP21]], label %[[LOADBB3:.*]], label %[[RES_BLOCK]]
-; RV64:       [[LOADBB3]]:
-; RV64-NEXT:    [[TMP22:%.*]] = getelementptr i8, ptr [[A]], i64 6
-; RV64-NEXT:    [[TMP23:%.*]] = getelementptr i8, ptr [[B]], i64 6
-; RV64-NEXT:    [[TMP24:%.*]] = load i8, ptr [[TMP22]], align 2
-; RV64-NEXT:    [[TMP25:%.*]] = load i8, ptr [[TMP23]], align 2
-; RV64-NEXT:    [[TMP26:%.*]] = zext i8 [[TMP24]] to i32
-; RV64-NEXT:    [[TMP27:%.*]] = zext i8 [[TMP25]] to i32
-; RV64-NEXT:    [[TMP28:%.*]] = sub i32 [[TMP26]], [[TMP27]]
-; RV64-NEXT:    br label %[[ENDBLOCK]]
-; RV64:       [[ENDBLOCK]]:
-; RV64-NEXT:    [[PHI_RES:%.*]] = phi i32 [ [[TMP28]], %[[LOADBB3]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
-; RV64-NEXT:    ret i32 [[PHI_RES]]
+; CHECK-LABEL: define i32 @memcmp_size_7_align_2(
+; CHECK-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    br label %[[LOADBB:.*]]
+; CHECK:       [[RES_BLOCK:.*]]:
+; CHECK-NEXT:    [[PHI_SRC1:%.*]] = phi i16 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP12:%.*]], %[[LOADBB1:.*]] ], [ [[TMP19:%.*]], %[[LOADBB2:.*]] ]
+; CHECK-NEXT:    [[PHI_SRC2:%.*]] = phi i16 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP13:%.*]], %[[LOADBB1]] ], [ [[TMP20:%.*]], %[[LOADBB2]] ]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp ult i16 [[PHI_SRC1]], [[PHI_SRC2]]
+; CHECK-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
+; CHECK-NEXT:    br label %[[ENDBLOCK:.*]]
+; CHECK:       [[LOADBB]]:
+; CHECK-NEXT:    [[TMP3:%.*]] = load i16, ptr [[A]], align 2
+; CHECK-NEXT:    [[TMP4:%.*]] = load i16, ptr [[B]], align 2
+; CHECK-NEXT:    [[TMP5]] = call i16 @llvm.bswap.i16(i16 [[TMP3]])
+; CHECK-NEXT:    [[TMP6]] = call i16 @llvm.bswap.i16(i16 [[TMP4]])
+; CHECK-NEXT:    [[TMP7:%.*]] = icmp eq i16 [[TMP5]], [[TMP6]]
+; CHECK-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
+; CHECK:       [[LOADBB1]]:
+; CHECK-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 2
+; CHECK-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 2
+; CHECK-NEXT:    [[TMP10:%.*]] = load i16, ptr [[TMP8]], align 2
+; CHECK-NEXT:    [[TMP11:%.*]] = load i16, ptr [[TMP9]], align 2
+; CHECK-NEXT:    [[TMP12]] = call i16 @llvm.bswap.i16(i16 [[TMP10]])
+; CHECK-NEXT:    [[TMP13]] = call i16 @llvm.bswap.i16(i16 [[TMP11]])
+; CHECK-NEXT:    [[TMP14:%.*]] = icmp eq i16 [[TMP12]], [[TMP13]]
+; CHECK-NEXT:    br i1 [[TMP14]], label %[[LOADBB2]], label %[[RES_BLOCK]]
+; CHECK:       [[LOADBB2]]:
+; CHECK-NEXT:    [[TMP15:%.*]] = getelementptr i8, ptr [[A]], i64 4
+; CHECK-NEXT:    [[TMP16:%.*]] = getelementptr i8, ptr [[B]], i64 4
+; CHECK-NEXT:    [[TMP17:%.*]] = load i16, ptr [[TMP15]], align 2
+; CHECK-NEXT:    [[TMP18:%.*]] = load i16, ptr [[TMP16]], align 2
+; CHECK-NEXT:    [[TMP19]] = call i16 @llvm.bswap.i16(i16 [[TMP17]])
+; CHECK-NEXT:    [[TMP20]] = call i16 @llvm.bswap.i16(i16 [[TMP18]])
+; CHECK-NEXT:    [[TMP21:%.*]] = icmp eq i16 [[TMP19]], [[TMP20]]
+; CHECK-NEXT:    br i1 [[TMP21]], label %[[LOADBB3:.*]], label %[[RES_BLOCK]]
+; CHECK:       [[LOADBB3]]:
+; CHECK-NEXT:    [[TMP22:%.*]] = getelementptr i8, ptr [[A]], i64 6
+; CHECK-NEXT:    [[TMP23:%.*]] = getelementptr i8, ptr [[B]], i64 6
+; CHECK-NEXT:    [[TMP24:%.*]] = load i8, ptr [[TMP22]], align 2
+; CHECK-NEXT:    [[TMP25:%.*]] = load i8, ptr [[TMP23]], align 2
+; CHECK-NEXT:    [[TMP26:%.*]] = zext i8 [[TMP24]] to i32
+; CHECK-NEXT:    [[TMP27:%.*]] = zext i8 [[TMP25]] to i32
+; CHECK-NEXT:    [[TMP28:%.*]] = sub i32 [[TMP26]], [[TMP27]]
+; CHECK-NEXT:    br label %[[ENDBLOCK]]
+; CHECK:       [[ENDBLOCK]]:
+; CHECK-NEXT:    [[PHI_RES:%.*]] = phi i32 [ [[TMP28]], %[[LOADBB3]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
+; CHECK-NEXT:    ret i32 [[PHI_RES]]
 ;
-; RV64-UNALIGNED-LABEL: define i32 @memcmp_size_7_align_2(
-; RV64-UNALIGNED-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
-; RV64-UNALIGNED-NEXT:    br label %[[LOADBB:.*]]
-; RV64-UNALIGNED:       [[RES_BLOCK:.*]]:
-; RV64-UNALIGNED-NEXT:    [[PHI_SRC1:%.*]] = phi i32 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP12:%.*]], %[[LOADBB1:.*]] ]
-; RV64-UNALIGNED-NEXT:    [[PHI_SRC2:%.*]] = phi i32 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP13:%.*]], %[[LOADBB1]] ]
-; RV64-UNALIGNED-NEXT:    [[TMP1:%.*]] = icmp ult i32 [[PHI_SRC1]], [[PHI_SRC2]]
-; RV64-UNALIGNED-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
-; RV64-UNALIGNED-NEXT:    br label %[[ENDBLOCK:.*]]
-; RV64-UNALIGNED:       [[LOADBB]]:
-; RV64-UNALIGNED-NEXT:    [[TMP3:%.*]] = load i32, ptr [[A]], align 2
-; RV64-UNALIGNED-NEXT:    [[TMP4:%.*]] = load i32, ptr [[B]], align 2
-; RV64-UNALIGNED-NEXT:    [[TMP5]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
-; RV64-UNALIGNED-NEXT:    [[TMP6]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
-; RV64-UNALIGNED-NEXT:    [[TMP7:%.*]] = icmp eq i32 [[TMP5]], [[TMP6]]
-; RV64-UNALIGNED-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
-; RV64-UNALIGNED:       [[LOADBB1]]:
-; RV64-UNALIGNED-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 3
-; RV64-UNALIGNED-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 3
-; RV64-UNALIGNED-NEXT:    [[TMP10:%.*]] = load i32, ptr [[TMP8]], align 1
-; RV64-UNALIGNED-NEXT:    [[TMP11:%.*]] = load i32, ptr [[TMP9]], align 1
-; RV64-UNALIGNED-NEXT:    [[TMP12]] = call i32 @llvm.bswap.i32(i32 [[TMP10]])
-; RV64-UNALIGNED-NEXT:    [[TMP13]] = call i32 @llvm.bswap.i32(i32 [[TMP11]])
-; RV64-UNALIGNED-NEXT:    [[TMP14:%.*]] = icmp eq i32 [[TMP12]], [[TMP13]]
-; RV64-UNALIGNED-NEXT:    br i1 [[TMP14]], label %[[ENDBLOCK]], label %[[RES_BLOCK]]
-; RV64-UNALIGNED:       [[ENDBLOCK]]:
-; RV64-UNALIGNED-NEXT:    [[PHI_RES:%.*]] = phi i32 [ 0, %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
-; RV64-UNALIGNED-NEXT:    ret i32 [[PHI_RES]]
+; CHECK-UNALIGNED-LABEL: define i32 @memcmp_size_7_align_2(
+; CHECK-UNALIGNED-SAME: ptr align 2 [[A:%.*]], ptr align 2 [[B:%.*]]) #[[ATTR0]] {
+; CHECK-UNALIGNED-NEXT:    br label %[[LOADBB:.*]]
+; CHECK-UNALIGNED:       [[RES_BLOCK:.*]]:
+; CHECK-UNALIGNED-NEXT:    [[PHI_SRC1:%.*]] = phi i32 [ [[TMP5:%.*]], %[[LOADBB]] ], [ [[TMP12:%.*]], %[[LOADBB1:.*]] ]
+; CHECK-UNALIGNED-NEXT:    [[PHI_SRC2:%.*]] = phi i32 [ [[TMP6:%.*]], %[[LOADBB]] ], [ [[TMP13:%.*]], %[[LOADBB1]] ]
+; CHECK-UNALIGNED-NEXT:    [[TMP1:%.*]] = icmp ult i32 [[PHI_SRC1]], [[PHI_SRC2]]
+; CHECK-UNALIGNED-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i32 -1, i32 1
+; CHECK-UNALIGNED-NEXT:    br label %[[ENDBLOCK:.*]]
+; CHECK-UNALIGNED:       [[LOADBB]]:
+; CHECK-UNALIGNED-NEXT:    [[TMP3:%.*]] = load i32, ptr [[A]], align 2
+; CHECK-UNALIGNED-NEXT:    [[TMP4:%.*]] = load i32, ptr [[B]], align 2
+; CHECK-UNALIGNED-NEXT:    [[TMP5]] = call i32 @llvm.bswap.i32(i32 [[TMP3]])
+; CHECK-UNALIGNED-NEXT:    [[TMP6]] = call i32 @llvm.bswap.i32(i32 [[TMP4]])
+; CHECK-UNALIGNED-NEXT:    [[TMP7:%.*]] = icmp eq i32 [[TMP5]], [[TMP6]]
+; CHECK-UNALIGNED-NEXT:    br i1 [[TMP7]], label %[[LOADBB1]], label %[[RES_BLOCK]]
+; CHECK-UNALIGNED:       [[LOADBB1]]:
+; CHECK-UNALIGNED-NEXT:    [[TMP8:%.*]] = getelementptr i8, ptr [[A]], i64 3
+; CHECK-UNALIGNED-NEXT:    [[TMP9:%.*]] = getelementptr i8, ptr [[B]], i64 3
+; CHECK-UNALIGNED-NEXT:    [[TMP10:%.*]] = load i32, ptr [[TMP8]], align 1
+; CHECK-UNALIGNED-NEXT:    [[TMP11:%.*]] = load i32, ptr [[TMP9]], align 1
+; CHECK-UNALIGNED-NEXT:    [[TMP12]] = call i32 @llvm.bswap.i32(i32 [[TMP10]])
+; CHECK-UNALIGNED-NEXT:    [[TMP13]] = call i32 @llvm.bswap.i32(i32 [[TMP11]])
+; CHECK-UNALIGNED-NEXT:    [[TMP14:%.*]] = icmp eq i32 [[TMP12]], [[TMP13]]
+; CHECK-UNALIGNED-NEXT:    br i1 [[TMP14]], label %[[ENDBLOCK]], label %[[RES_BLOCK]]
+; CHECK-UNALIGNED:       [[ENDBLOCK]]:
+; CHECK-UNALIGNED-NEXT:    [[PHI_RES:%.*]] = phi i32 [ 0, %[[LOADBB1]] ], [ [[TMP2]], %[[RES_BLOCK]] ]
+; CHECK-UNALIGNED-NEXT:    ret i32 [[PHI_RES]]
 ;
-  %r = call i32 @memcmp(ptr %a, ptr %b, iXLen 7)
+  %r = call i32 @memcmp(ptr %a, ptr %b, i64 7)
   ret i32 %r
 }

>From c214b6d00dddd78b8b2889b07cd66a98cdc72fe4 Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Wed, 22 Jul 2026 19:00:29 +0800
Subject: [PATCH 5/9] [ExpandMemCmp] Pass BaseAlign to MemCmpExpansion to avoid
 recompute

Assisted-by: TRAE CLI (DeepSeek V4 Pro)
---
 llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
index 62821fbf3b72c..f99b9a2e9f5d2 100644
--- a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
+++ b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
@@ -153,7 +153,8 @@ class MemCmpExpansion {
   MemCmpExpansion(CallInst *CI, uint64_t Size,
                   const TargetTransformInfo::MemCmpExpansionOptions &Options,
                   const bool IsUsedForZeroCmp, const DataLayout &TheDataLayout,
-                  DomTreeUpdater *DTU, const TargetTransformInfo &TTI);
+                  DomTreeUpdater *DTU, const TargetTransformInfo &TTI,
+                  Align BaseAlign);
 
   unsigned getNumBlocks();
   uint64_t getNumLoads() const { return LoadSequence.size(); }
@@ -313,12 +314,10 @@ MemCmpExpansion::MemCmpExpansion(
     CallInst *const CI, uint64_t Size,
     const TargetTransformInfo::MemCmpExpansionOptions &Options,
     const bool IsUsedForZeroCmp, const DataLayout &TheDataLayout,
-    DomTreeUpdater *DTU, const TargetTransformInfo &TTI)
+    DomTreeUpdater *DTU, const TargetTransformInfo &TTI, Align BaseAlign)
     : CI(CI), Size(Size), NumLoadsPerBlockForZeroCmp(Options.NumLoadsPerBlock),
       IsUsedForZeroCmp(IsUsedForZeroCmp), DL(TheDataLayout), TTI(TTI),
-      BaseAlign(std::min(getMemCmpArgAlignment(CI, 0, DL),
-                         getMemCmpArgAlignment(CI, 1, DL))),
-      DTU(DTU), Builder(CI) {
+      BaseAlign(BaseAlign), DTU(DTU), Builder(CI) {
   assert(Size > 0 && "zero blocks");
   // Scale the max size down if the target can load more bytes than we need.
   llvm::ArrayRef<unsigned> LoadSizes(Options.LoadSizes);
@@ -929,11 +928,10 @@ static bool expandMemCmp(CallInst *CI, const TargetTransformInfo *TTI,
   // kept here is always accessible in that sequence; overlapping loads and
   // merged tail expansions are checked separately against their actual offsets
   // in MemCmpExpansion.
-  const Align LhsAlign = getMemCmpArgAlignment(CI, 0, *DL);
-  const Align RhsAlign = getMemCmpArgAlignment(CI, 1, *DL);
-  const Align MinAlign = std::min(LhsAlign, RhsAlign);
+  const Align BaseAlign = std::min(getMemCmpArgAlignment(CI, 0, *DL),
+                                   getMemCmpArgAlignment(CI, 1, *DL));
   llvm::erase_if(Options.LoadSizes, [&](unsigned LoadSize) {
-    return !isAccessAllowed(CI, *TTI, MinAlign, LoadSize, /*Offset=*/0);
+    return !isAccessAllowed(CI, *TTI, BaseAlign, LoadSize, /*Offset=*/0);
   });
   // If the filter removed every load size, bail out to the libcall: the
   // MemCmpExpansion constructor asserts that at least one load size remains.
@@ -943,7 +941,7 @@ static bool expandMemCmp(CallInst *CI, const TargetTransformInfo *TTI,
     return false;
 
   MemCmpExpansion Expansion(CI, SizeVal, Options, IsUsedForZeroCmp, *DL, DTU,
-                            *TTI);
+                            *TTI, BaseAlign);
 
   // Don't expand if this will require more loads than desired by the target.
   if (Expansion.getNumLoads() == 0) {

>From 7a917ea6e3d5a37bc0eeb503b0863b269888faaa Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Wed, 22 Jul 2026 19:13:40 +0800
Subject: [PATCH 6/9] Simplify RUN and CHECK prefixes

---
 .../ExpandMemCmp/RISCV/memcmp-misaligned-access.ll          | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll b/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
index 78b3dfe0e0b45..0efe65fa9aecd 100644
--- a/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
+++ b/llvm/test/Transforms/ExpandMemCmp/RISCV/memcmp-misaligned-access.ll
@@ -1,6 +1,8 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
-; RUN: opt -S -passes=expand-memcmp -mtriple=riscv64 -mattr=+m,+zbb < %s | FileCheck %s --check-prefixes=CHECK
-; RUN: opt -S -passes=expand-memcmp -mtriple=riscv64 -mattr=+m,+zbb,+unaligned-scalar-mem < %s | FileCheck %s --check-prefixes=CHECK-UNALIGNED
+; RUN: opt -S -passes=expand-memcmp -mtriple=riscv64 -mattr=+m,+zbb < %s \
+; RUN:   | FileCheck %s
+; RUN: opt -S -passes=expand-memcmp -mtriple=riscv64 -mattr=+m,+zbb,+unaligned-scalar-mem < %s \
+; RUN:   | FileCheck %s --check-prefix=CHECK-UNALIGNED
 
 ; Overlapping loads and merged tail expansions place a load at an offset that
 ; need not be a multiple of its size. On strict-alignment targets they are only

>From 15d7dcb2ee43ffce78b358bdd01a4eb7231f7641 Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Wed, 22 Jul 2026 19:23:39 +0800
Subject: [PATCH 7/9] [ExpandMemCmp] Use isAligned for natural-alignment check

Assisted-by: TRAE CLI (DeepSeek V4 Pro)
---
 llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
index f99b9a2e9f5d2..74fde3cbf85e5 100644
--- a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
+++ b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
@@ -171,8 +171,13 @@ class MemCmpExpansion {
 static bool isAccessAllowed(const CallInst *CI, const TargetTransformInfo &TTI,
                             Align BaseAlign, unsigned LoadSize,
                             uint64_t Offset) {
+  // LoadSize is always a power of two here: the only non-power-of-two sizes
+  // come from tail expansions, which are legalized by the backend and never
+  // reach this check. A power-of-two access is thus naturally aligned exactly
+  // when the known alignment is a multiple of it.
+  assert(isPowerOf2_32(LoadSize) && "expected a power-of-two load size");
   Align AccessAlign = commonAlignment(BaseAlign, Offset);
-  if (AccessAlign >= LoadSize)
+  if (isAligned(Align(LoadSize), AccessAlign.value()))
     return true;
   unsigned AS = CI->getArgOperand(0)->getType()->getPointerAddressSpace();
   return TTI.allowsMisalignedMemoryAccesses(CI->getContext(), LoadSize * 8, AS,

>From eea11b10225ae9df8e9a2f7963d19fc0ca3f03e1 Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Wed, 22 Jul 2026 19:48:06 +0800
Subject: [PATCH 8/9] Rename BaseAlign to CommonAlign

---
 llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
index 74fde3cbf85e5..76b5fc939a9c6 100644
--- a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
+++ b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
@@ -91,7 +91,7 @@ class MemCmpExpansion {
   const DataLayout &DL;
   const TargetTransformInfo &TTI;
   // The known common alignment of the two source pointers.
-  const Align BaseAlign;
+  const Align CommonAlign;
   DomTreeUpdater *DTU = nullptr;
   IRBuilder<> Builder;
   // Represents the decomposition in blocks of the expansion. For example,
@@ -169,14 +169,14 @@ class MemCmpExpansion {
 // matching the historical behavior of forming unaligned loads whenever the
 // target permits them.
 static bool isAccessAllowed(const CallInst *CI, const TargetTransformInfo &TTI,
-                            Align BaseAlign, unsigned LoadSize,
+                            Align CommonAlign, unsigned LoadSize,
                             uint64_t Offset) {
   // LoadSize is always a power of two here: the only non-power-of-two sizes
   // come from tail expansions, which are legalized by the backend and never
   // reach this check. A power-of-two access is thus naturally aligned exactly
   // when the known alignment is a multiple of it.
   assert(isPowerOf2_32(LoadSize) && "expected a power-of-two load size");
-  Align AccessAlign = commonAlignment(BaseAlign, Offset);
+  Align AccessAlign = commonAlignment(CommonAlign, Offset);
   if (isAligned(Align(LoadSize), AccessAlign.value()))
     return true;
   unsigned AS = CI->getArgOperand(0)->getType()->getPointerAddressSpace();
@@ -190,7 +190,7 @@ static bool isAccessAllowed(const CallInst *CI, const TargetTransformInfo &TTI,
 // the backend and skip this check.
 bool MemCmpExpansion::isAccessAllowed(unsigned LoadSize,
                                       uint64_t Offset) const {
-  return ::isAccessAllowed(CI, TTI, BaseAlign, LoadSize, Offset);
+  return ::isAccessAllowed(CI, TTI, CommonAlign, LoadSize, Offset);
 }
 
 MemCmpExpansion::LoadEntryVector MemCmpExpansion::computeGreedyLoadSequence(
@@ -322,7 +322,7 @@ MemCmpExpansion::MemCmpExpansion(
     DomTreeUpdater *DTU, const TargetTransformInfo &TTI, Align BaseAlign)
     : CI(CI), Size(Size), NumLoadsPerBlockForZeroCmp(Options.NumLoadsPerBlock),
       IsUsedForZeroCmp(IsUsedForZeroCmp), DL(TheDataLayout), TTI(TTI),
-      BaseAlign(BaseAlign), DTU(DTU), Builder(CI) {
+      CommonAlign(BaseAlign), DTU(DTU), Builder(CI) {
   assert(Size > 0 && "zero blocks");
   // Scale the max size down if the target can load more bytes than we need.
   llvm::ArrayRef<unsigned> LoadSizes(Options.LoadSizes);

>From d0edfa3a984c36f4168e0bf3a5aab3d0bafa3cfc Mon Sep 17 00:00:00 2001
From: Pengcheng Wang <wangpengcheng.pp at bytedance.com>
Date: Wed, 22 Jul 2026 19:50:10 +0800
Subject: [PATCH 9/9] Rename more BaseAlign to CommonAlign

---
 llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
index 76b5fc939a9c6..eccd731f310c0 100644
--- a/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
+++ b/llvm/lib/Transforms/Scalar/ExpandMemCmp.cpp
@@ -154,7 +154,7 @@ class MemCmpExpansion {
                   const TargetTransformInfo::MemCmpExpansionOptions &Options,
                   const bool IsUsedForZeroCmp, const DataLayout &TheDataLayout,
                   DomTreeUpdater *DTU, const TargetTransformInfo &TTI,
-                  Align BaseAlign);
+                  Align CommonAlign);
 
   unsigned getNumBlocks();
   uint64_t getNumLoads() const { return LoadSequence.size(); }
@@ -319,10 +319,10 @@ MemCmpExpansion::MemCmpExpansion(
     CallInst *const CI, uint64_t Size,
     const TargetTransformInfo::MemCmpExpansionOptions &Options,
     const bool IsUsedForZeroCmp, const DataLayout &TheDataLayout,
-    DomTreeUpdater *DTU, const TargetTransformInfo &TTI, Align BaseAlign)
+    DomTreeUpdater *DTU, const TargetTransformInfo &TTI, Align CommonAlign)
     : CI(CI), Size(Size), NumLoadsPerBlockForZeroCmp(Options.NumLoadsPerBlock),
       IsUsedForZeroCmp(IsUsedForZeroCmp), DL(TheDataLayout), TTI(TTI),
-      CommonAlign(BaseAlign), DTU(DTU), Builder(CI) {
+      CommonAlign(CommonAlign), DTU(DTU), Builder(CI) {
   assert(Size > 0 && "zero blocks");
   // Scale the max size down if the target can load more bytes than we need.
   llvm::ArrayRef<unsigned> LoadSizes(Options.LoadSizes);
@@ -933,10 +933,10 @@ static bool expandMemCmp(CallInst *CI, const TargetTransformInfo *TTI,
   // kept here is always accessible in that sequence; overlapping loads and
   // merged tail expansions are checked separately against their actual offsets
   // in MemCmpExpansion.
-  const Align BaseAlign = std::min(getMemCmpArgAlignment(CI, 0, *DL),
-                                   getMemCmpArgAlignment(CI, 1, *DL));
+  const Align CommonAlign = std::min(getMemCmpArgAlignment(CI, 0, *DL),
+                                     getMemCmpArgAlignment(CI, 1, *DL));
   llvm::erase_if(Options.LoadSizes, [&](unsigned LoadSize) {
-    return !isAccessAllowed(CI, *TTI, BaseAlign, LoadSize, /*Offset=*/0);
+    return !isAccessAllowed(CI, *TTI, CommonAlign, LoadSize, /*Offset=*/0);
   });
   // If the filter removed every load size, bail out to the libcall: the
   // MemCmpExpansion constructor asserts that at least one load size remains.
@@ -946,7 +946,7 @@ static bool expandMemCmp(CallInst *CI, const TargetTransformInfo *TTI,
     return false;
 
   MemCmpExpansion Expansion(CI, SizeVal, Options, IsUsedForZeroCmp, *DL, DTU,
-                            *TTI, BaseAlign);
+                            *TTI, CommonAlign);
 
   // Don't expand if this will require more loads than desired by the target.
   if (Expansion.getNumLoads() == 0) {



More information about the llvm-commits mailing list