[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