[llvm] [LoopIdiom] Use SCEV for deciding memmove validity (PR #211274)
Björn Pettersson via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 19 07:46:52 PDT 2026
================
@@ -0,0 +1,341 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes="loop-idiom" < %s -S | FileCheck %s
+
+; Check that we can form memmove when the loop index variable is sign or zero
+; extended.
+
+define void @move_up_dec_loop_zext(i16 %n, ptr %p) {
+; CHECK-LABEL: define void @move_up_dec_loop_zext(
+; CHECK-SAME: i16 [[N:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = zext i16 [[N]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = add i16 [[N]], -1
+; CHECK-NEXT: [[SMIN:%.*]] = call i16 @llvm.smin.i16(i16 [[TMP1]], i16 0)
+; CHECK-NEXT: [[TMP2:%.*]] = sub i16 [[TMP1]], [[SMIN]]
+; CHECK-NEXT: [[TMP3:%.*]] = zext i16 [[TMP2]] to i64
+; CHECK-NEXT: [[TMP4:%.*]] = sub i64 [[TMP0]], [[TMP3]]
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[P]], i64 [[TMP4]]
+; CHECK-NEXT: [[TMP5:%.*]] = add i64 [[TMP0]], -1
+; CHECK-NEXT: [[TMP6:%.*]] = sub i64 [[TMP5]], [[TMP3]]
+; CHECK-NEXT: [[SCEVGEP1:%.*]] = getelementptr i8, ptr [[P]], i64 [[TMP6]]
+; CHECK-NEXT: [[TMP7:%.*]] = add nuw nsw i64 [[TMP3]], 1
+; CHECK-NEXT: call void @llvm.memmove.p0.p0.i64(ptr align 1 [[SCEVGEP]], ptr align 1 [[SCEVGEP1]], i64 [[TMP7]], i1 false)
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i16 [ [[IV_NEXT:%.*]], %[[LOOP]] ], [ [[N]], %[[ENTRY]] ]
+; CHECK-NEXT: [[IV_EXT:%.*]] = zext nneg i16 [[IV]] to i64
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw nsw i64 [[IV_EXT]], 1
+; CHECK-NEXT: [[SRC:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[SUB]]
+; CHECK-NEXT: [[DST:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[IV_EXT]]
+; CHECK-NEXT: [[VAL:%.*]] = load i8, ptr [[SRC]], align 1
+; CHECK-NEXT: [[IV_NEXT]] = add i16 [[IV]], -1
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i16 [[IV_NEXT]], 0
+; CHECK-NEXT: br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i16 [ %iv.next, %loop ], [ %n, %entry ]
+ %iv.ext = zext nneg i16 %iv to i64
+ %sub = sub nuw nsw i64 %iv.ext, 1
+ %src = getelementptr inbounds nuw i8, ptr %p, i64 %sub
+ %dst = getelementptr inbounds nuw i8, ptr %p, i64 %iv.ext
+ %val = load i8, ptr %src, align 1
+ store i8 %val, ptr %dst, align 1
+ %iv.next = add i16 %iv, -1
+ %cmp = icmp sgt i16 %iv.next, 0
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @move_down_inc_loop_zext(i16 %n, ptr %p) {
+; CHECK-LABEL: define void @move_down_inc_loop_zext(
+; CHECK-SAME: i16 [[N:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr nuw i8, ptr [[P]], i64 -1
+; CHECK-NEXT: [[SMAX:%.*]] = call i16 @llvm.smax.i16(i16 [[N]], i16 1)
+; CHECK-NEXT: [[TMP0:%.*]] = zext nneg i16 [[SMAX]] to i64
+; CHECK-NEXT: call void @llvm.memmove.p0.p0.i64(ptr align 1 [[SCEVGEP]], ptr align 1 [[P]], i64 [[TMP0]], i1 false)
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i16 [ [[IV_NEXT:%.*]], %[[LOOP]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: [[IV_EXT:%.*]] = zext nneg i16 [[IV]] to i64
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw nsw i64 [[IV_EXT]], 1
+; CHECK-NEXT: [[SRC:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[IV_EXT]]
+; CHECK-NEXT: [[DST:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[SUB]]
+; CHECK-NEXT: [[VAL:%.*]] = load i8, ptr [[SRC]], align 1
+; CHECK-NEXT: [[IV_NEXT]] = add i16 [[IV]], 1
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i16 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i16 [ %iv.next, %loop ], [ 0, %entry ]
+ %iv.ext = zext nneg i16 %iv to i64
+ %sub = sub nuw nsw i64 %iv.ext, 1
+ %src = getelementptr inbounds nuw i8, ptr %p, i64 %iv.ext
+ %dst = getelementptr inbounds nuw i8, ptr %p, i64 %sub
+ %val = load i8, ptr %src, align 1
+ store i8 %val, ptr %dst, align 1
+ %iv.next = add i16 %iv, 1
+ %cmp = icmp slt i16 %iv.next, %n
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @move_up_dec_loop_sext(i16 %n, ptr %p) {
+; CHECK-LABEL: define void @move_up_dec_loop_sext(
+; CHECK-SAME: i16 [[N:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[TMP0:%.*]] = sext i16 [[N]] to i64
+; CHECK-NEXT: [[TMP1:%.*]] = add i16 [[N]], -1
+; CHECK-NEXT: [[TMP8:%.*]] = add nsw i16 [[N]], -1
+; CHECK-NEXT: [[SMIN:%.*]] = call i16 @llvm.smin.i16(i16 [[TMP8]], i16 0)
+; CHECK-NEXT: [[TMP2:%.*]] = sub i16 [[TMP1]], [[SMIN]]
+; CHECK-NEXT: [[TMP3:%.*]] = zext i16 [[TMP2]] to i64
+; CHECK-NEXT: [[TMP4:%.*]] = sub i64 [[TMP0]], [[TMP3]]
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr i8, ptr [[P]], i64 [[TMP4]]
+; CHECK-NEXT: [[TMP5:%.*]] = add i64 [[TMP0]], -1
+; CHECK-NEXT: [[TMP6:%.*]] = sub i64 [[TMP5]], [[TMP3]]
+; CHECK-NEXT: [[SCEVGEP1:%.*]] = getelementptr i8, ptr [[P]], i64 [[TMP6]]
+; CHECK-NEXT: [[TMP7:%.*]] = add nuw nsw i64 [[TMP3]], 1
+; CHECK-NEXT: call void @llvm.memmove.p0.p0.i64(ptr align 1 [[SCEVGEP]], ptr align 1 [[SCEVGEP1]], i64 [[TMP7]], i1 false)
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i16 [ [[IV_NEXT:%.*]], %[[LOOP]] ], [ [[N]], %[[ENTRY]] ]
+; CHECK-NEXT: [[IV_EXT:%.*]] = sext i16 [[IV]] to i64
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw nsw i64 [[IV_EXT]], 1
+; CHECK-NEXT: [[SRC:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[SUB]]
+; CHECK-NEXT: [[DST:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[IV_EXT]]
+; CHECK-NEXT: [[VAL:%.*]] = load i8, ptr [[SRC]], align 1
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i16 [[IV]], -1
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i16 [[IV_NEXT]], 0
+; CHECK-NEXT: br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i16 [ %iv.next, %loop ], [ %n, %entry ]
+ %iv.ext = sext i16 %iv to i64
+ %sub = sub nuw nsw i64 %iv.ext, 1
+ %src = getelementptr inbounds nuw i8, ptr %p, i64 %sub
+ %dst = getelementptr inbounds nuw i8, ptr %p, i64 %iv.ext
+ %val = load i8, ptr %src, align 1
+ store i8 %val, ptr %dst, align 1
+ %iv.next = add nsw i16 %iv, -1
+ %cmp = icmp sgt i16 %iv.next, 0
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @move_down_inc_loop_sext(i16 %n, ptr %p) {
+; CHECK-LABEL: define void @move_down_inc_loop_sext(
+; CHECK-SAME: i16 [[N:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: [[SCEVGEP:%.*]] = getelementptr nuw i8, ptr [[P]], i64 -1
+; CHECK-NEXT: [[SMAX:%.*]] = call i16 @llvm.smax.i16(i16 [[N]], i16 1)
+; CHECK-NEXT: [[TMP0:%.*]] = zext nneg i16 [[SMAX]] to i64
+; CHECK-NEXT: call void @llvm.memmove.p0.p0.i64(ptr align 1 [[SCEVGEP]], ptr align 1 [[P]], i64 [[TMP0]], i1 false)
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i16 [ [[IV_NEXT:%.*]], %[[LOOP]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: [[IV_EXT:%.*]] = sext i16 [[IV]] to i64
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw nsw i64 [[IV_EXT]], 1
+; CHECK-NEXT: [[SRC:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[IV_EXT]]
+; CHECK-NEXT: [[DST:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[SUB]]
+; CHECK-NEXT: [[VAL:%.*]] = load i8, ptr [[SRC]], align 1
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i16 [[IV]], 1
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i16 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i16 [ %iv.next, %loop ], [ 0, %entry ]
+ %iv.ext = sext i16 %iv to i64
+ %sub = sub nuw nsw i64 %iv.ext, 1
+ %src = getelementptr inbounds nuw i8, ptr %p, i64 %iv.ext
+ %dst = getelementptr inbounds nuw i8, ptr %p, i64 %sub
+ %val = load i8, ptr %src, align 1
+ store i8 %val, ptr %dst, align 1
+ %iv.next = add nsw i16 %iv, 1
+ %cmp = icmp slt i16 %iv.next, %n
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+; In the following tests the load reads from the location that was stored to in
+; the previous loop iteration, so using memmove is not valid.
+
+define void @move_down_dec_loop_zext(i16 %n, ptr %p) {
+; CHECK-LABEL: define void @move_down_dec_loop_zext(
+; CHECK-SAME: i16 [[N:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i16 [ [[IV_NEXT:%.*]], %[[LOOP]] ], [ [[N]], %[[ENTRY]] ]
+; CHECK-NEXT: [[IV_EXT:%.*]] = zext nneg i16 [[IV]] to i64
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw nsw i64 [[IV_EXT]], 1
+; CHECK-NEXT: [[SRC:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[IV_EXT]]
+; CHECK-NEXT: [[DST:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[SUB]]
+; CHECK-NEXT: [[VAL:%.*]] = load i8, ptr [[SRC]], align 1
+; CHECK-NEXT: store i8 [[VAL]], ptr [[DST]], align 1
+; CHECK-NEXT: [[IV_NEXT]] = add i16 [[IV]], -1
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i16 [[IV_NEXT]], 0
+; CHECK-NEXT: br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i16 [ %iv.next, %loop ], [ %n, %entry ]
+ %iv.ext = zext nneg i16 %iv to i64
+ %sub = sub nuw nsw i64 %iv.ext, 1
+ %src = getelementptr inbounds nuw i8, ptr %p, i64 %iv.ext
+ %dst = getelementptr inbounds nuw i8, ptr %p, i64 %sub
+ %val = load i8, ptr %src, align 1
+ store i8 %val, ptr %dst, align 1
+ %iv.next = add i16 %iv, -1
+ %cmp = icmp sgt i16 %iv.next, 0
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @move_up_inc_loop_zext(i16 %n, ptr %p) {
+; CHECK-LABEL: define void @move_up_inc_loop_zext(
+; CHECK-SAME: i16 [[N:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i16 [ [[IV_NEXT:%.*]], %[[LOOP]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: [[IV_EXT:%.*]] = zext nneg i16 [[IV]] to i64
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw nsw i64 [[IV_EXT]], 1
+; CHECK-NEXT: [[SRC:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[SUB]]
+; CHECK-NEXT: [[DST:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[IV_EXT]]
+; CHECK-NEXT: [[VAL:%.*]] = load i8, ptr [[SRC]], align 1
+; CHECK-NEXT: store i8 [[VAL]], ptr [[DST]], align 1
+; CHECK-NEXT: [[IV_NEXT]] = add i16 [[IV]], 1
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i16 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i16 [ %iv.next, %loop ], [ 0, %entry ]
+ %iv.ext = zext nneg i16 %iv to i64
+ %sub = sub nuw nsw i64 %iv.ext, 1
+ %src = getelementptr inbounds nuw i8, ptr %p, i64 %sub
+ %dst = getelementptr inbounds nuw i8, ptr %p, i64 %iv.ext
+ %val = load i8, ptr %src, align 1
+ store i8 %val, ptr %dst, align 1
+ %iv.next = add i16 %iv, 1
+ %cmp = icmp slt i16 %iv.next, %n
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @move_down_dec_loop_sext(i16 %n, ptr %p) {
+; CHECK-LABEL: define void @move_down_dec_loop_sext(
+; CHECK-SAME: i16 [[N:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i16 [ [[IV_NEXT:%.*]], %[[LOOP]] ], [ [[N]], %[[ENTRY]] ]
+; CHECK-NEXT: [[IV_EXT:%.*]] = sext i16 [[IV]] to i64
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw nsw i64 [[IV_EXT]], 1
+; CHECK-NEXT: [[SRC:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[IV_EXT]]
+; CHECK-NEXT: [[DST:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[SUB]]
+; CHECK-NEXT: [[VAL:%.*]] = load i8, ptr [[SRC]], align 1
+; CHECK-NEXT: store i8 [[VAL]], ptr [[DST]], align 1
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i16 [[IV]], -1
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i16 [[IV_NEXT]], 0
+; CHECK-NEXT: br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i16 [ %iv.next, %loop ], [ %n, %entry ]
+ %iv.ext = sext i16 %iv to i64
+ %sub = sub nuw nsw i64 %iv.ext, 1
+ %src = getelementptr inbounds nuw i8, ptr %p, i64 %iv.ext
+ %dst = getelementptr inbounds nuw i8, ptr %p, i64 %sub
+ %val = load i8, ptr %src, align 1
+ store i8 %val, ptr %dst, align 1
+ %iv.next = add nsw i16 %iv, -1
+ %cmp = icmp sgt i16 %iv.next, 0
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret void
+}
+
+define void @move_up_inc_loop_sext(i16 %n, ptr %p) {
+; CHECK-LABEL: define void @move_up_inc_loop_sext(
+; CHECK-SAME: i16 [[N:%.*]], ptr [[P:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[IV:%.*]] = phi i16 [ [[IV_NEXT:%.*]], %[[LOOP]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT: [[IV_EXT:%.*]] = sext i16 [[IV]] to i64
+; CHECK-NEXT: [[SUB:%.*]] = sub nuw nsw i64 [[IV_EXT]], 1
+; CHECK-NEXT: [[SRC:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[SUB]]
+; CHECK-NEXT: [[DST:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 [[IV_EXT]]
+; CHECK-NEXT: [[VAL:%.*]] = load i8, ptr [[SRC]], align 1
+; CHECK-NEXT: store i8 [[VAL]], ptr [[DST]], align 1
+; CHECK-NEXT: [[IV_NEXT]] = add nsw i16 [[IV]], 1
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i16 [[IV_NEXT]], [[N]]
+; CHECK-NEXT: br i1 [[CMP]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %loop
+
+loop:
+ %iv = phi i16 [ %iv.next, %loop ], [ 0, %entry ]
+ %iv.ext = sext i16 %iv to i64
+ %sub = sub nuw nsw i64 %iv.ext, 1
----------------
bjope wrote:
Hi @john-brawn-arm ,
Some of these tests (taking this one as an example) looks a bit weird.
In this test case
- %iv starts at 0
- then the "sub nuw nsw" would result in poison in the first iteration
- the "inbounds nuw" in the getelementptr indicate that we only add to the base pointer %p, so %sub can be treated as an unsigned value, so if removing the "nuw" on the sub we should also remove "nuw" on the GEP if we want to access `%p-1`
So not quite sure if these tests actually prove what they are supposed to prove (I think @move_up_inc_loop_zext, @move_down_inc_loop_zext and @move_down_inc_loop_sext has the same problem).
Maybe they should all start on index 1 instead of 0? Or maybe we should remove the "nuw" attributes?
https://github.com/llvm/llvm-project/pull/211274
More information about the llvm-commits
mailing list