Skip to content

[LoopTermFold] Allow pointer IVs with dereferenceable accesses to fold terminating condition - #240066

Open
VedaantS wants to merge 1 commit into
llvm:mainfrom
VedaantS:loop-term-fold-dereference
Open

VedaantS wants to merge 1 commit into
llvm:mainfrom
VedaantS:loop-term-fold-dereference

Conversation

@VedaantS

@VedaantS VedaantS commented Nov 7, 2026

Copy link
Copy Markdown

Fixes #163757

An aligned pointer with a power-of-two stride must visit null before it repeats. If it is dereferenced on every iteration, and the alignment is at least the stride, those iterations cannot all execute in a defined program unless null pointer is defined in the address space.

Relax the hasNoSelfWrap check in LoopTermFold to recognize pointer IVs with dereferenceable memory accesses dominating the loop latch. This eliminates redundant loop counter IVs when pointer accesses already advance monotonically, such as the inner loop of CoreMark's matrix_mul_matrix_bitextract.

Fixes #163757

Tool disclosure: The patch and unit tests were authored manually; Google Antigravity was used to review the diff, run local tests, and write this PR description.

…d terminating condition

An aligned pointer with a power-of-two stride must visit null before it
repeats. If it is dereferenced on every iteration, and the alignment is at
least the stride, those iterations cannot all execute in a defined program
unless null pointer is defined in the address space.

Relax the hasNoSelfWrap check in LoopTermFold to recognize pointer IVs
with dereferenceable memory accesses dominating the loop latch. This eliminates
redundant loop counter IVs when pointer accesses already advance monotonically,
such as the inner loop of CoreMark's matrix_mul_matrix_bitextract.

Fixes llvm#163757
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown

Hello @VedaantS 👋

Thank you for submitting a Pull Request (PR) to the LLVM Project. Since this is your first PR, here are a few useful links covering our main contribution policies and review practices.

  • All contributions to LLVM must follow our LLVM AI Tool Use Policy. In particular, if you used AI while working on this PR, remember to add a note to the PR description.
  • The LLVM Code-Review Policy and Practices document contains practical information about the PR process, including how patches are reviewed and accepted, and who can review a PR.
  • Our LLVM Developer Policy describes our expectations for code quality, commit summaries and contains notes on our CI system.

Please reply to this message to confirm that you have read these policies, especially the LLVM AI Tool Use Policy, and that any AI tool usage has been noted in the PR description.


Frequently asked questions

How do I add reviewers?

This PR will be automatically labeled, and the relevant teams will be notified. For some parts of the project, reviewers may also be added automatically.

You can also add reviewers manually using the Reviewers section on this page. If you cannot use that section, it is probably because you do not have write permissions for the repository. In that case, you can request a review by tagging reviewers in a comment using @ followed by their GitHub username.

What if there are no comments?

If you have not received any comments on your PR after a week, you can request a review by pinging the PR with a comment such as “Ping”. The common courtesy ping rate is once a week. Please remember that you are asking for volunteer time from other developers.

Are any special GitHub settings required to contribute to LLVM?

We only require contributors to have a public email address associated with their GitHub commits, see this section of LLVM Developer Policy for details.


If you have questions, feel free to leave a comment on this PR, or ask on LLVM Discord or LLVM Discourse.

Thank you,
The LLVM Community

@VedaantS

VedaantS commented Nov 7, 2026

Copy link
Copy Markdown
Author

I have read and agree to the LLVM contribution policies, including the LLVM AI Tool Use Policy, and have noted tool usage in the PR description.

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-llvm-transforms

Author: Vedaant Srivastava (VedaantS)

Changes

Fixes #163757

An aligned pointer with a power-of-two stride must visit null before it repeats. If it is dereferenced on every iteration, and the alignment is at least the stride, those iterations cannot all execute in a defined program unless null pointer is defined in the address space.

Relax the hasNoSelfWrap check in LoopTermFold to recognize pointer IVs with dereferenceable memory accesses dominating the loop latch. This eliminates redundant loop counter IVs when pointer accesses already advance monotonically, such as the inner loop of CoreMark's matrix_mul_matrix_bitextract.

Fixes #163757

Tool disclosure: The patch and unit tests were authored manually; Google Antigravity was used to review the diff, run local tests, and write this PR description.


Patch is 27.33 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/240066.diff

4 Files Affected:

  • (modified) llvm/lib/Transforms/Scalar/LoopTermFold.cpp (+46-1)
  • (modified) llvm/test/Transforms/LoopStrengthReduce/lsr-term-fold.ll (+7-7)
  • (added) llvm/test/Transforms/LoopStrengthReduce/term-fold-dereference-narrow-index.ll (+34)
  • (added) llvm/test/Transforms/LoopStrengthReduce/term-fold-dereference.ll (+515)
diff --git a/llvm/lib/Transforms/Scalar/LoopTermFold.cpp b/llvm/lib/Transforms/Scalar/LoopTermFold.cpp
index e6eccdd7d616e2..8da886f1da5ee5 100644
--- a/llvm/lib/Transforms/Scalar/LoopTermFold.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopTermFold.cpp
@@ -21,7 +21,9 @@
 #include "llvm/Analysis/ValueTracking.h"
 #include "llvm/Config/llvm-config.h"
 #include "llvm/IR/BasicBlock.h"
+#include "llvm/IR/DataLayout.h"
 #include "llvm/IR/Dominators.h"
+#include "llvm/IR/Function.h"
 #include "llvm/IR/IRBuilder.h"
 #include "llvm/IR/InstrTypes.h"
 #include "llvm/IR/Instruction.h"
@@ -48,6 +50,47 @@ using namespace llvm;
 STATISTIC(NumTermFold,
           "Number of terminating condition fold recognized and performed");
 
+// An aligned pointer with a power-of-two stride must visit null before it
+// repeats. If it is dereferenced on every iteration, and the alignment is at
+// least the stride, those iterations cannot all execute in a defined program.
+static bool hasNoSelfWrapViaDereference(PHINode &PN,
+                                        const SCEVAddRecExpr *AddRec,
+                                        ScalarEvolution &SE, DominatorTree &DT,
+                                        Instruction *LatchTerm) {
+  auto *PtrTy = dyn_cast<PointerType>(PN.getType());
+  if (!PtrTy || AddRec->getLoop()->getHeader() != PN.getParent() ||
+      NullPointerIsDefined(PN.getFunction(), PtrTy->getAddressSpace()))
+    return false;
+
+  const auto *Step = dyn_cast<SCEVConstant>(AddRec->getStepRecurrence(SE));
+  if (!Step)
+    return false;
+  APInt Stride = Step->getAPInt().abs();
+  if (!Stride.isPowerOf2() || Stride.getActiveBits() > 64)
+    return false;
+
+  const DataLayout &DL = PN.getDataLayout();
+  // GEP arithmetic must cover the entire pointer representation for its cycle
+  // to include null.
+  if (DL.isNonIntegralPointerType(PtrTy) ||
+      DL.getIndexSizeInBits(PtrTy->getAddressSpace()) !=
+          DL.getPointerSizeInBits(PtrTy->getAddressSpace()))
+    return false;
+
+  for (User *U : PN.users()) {
+    if (getLoadStorePointerOperand(U) != &PN)
+      continue;
+    // Volatile accesses may legally use address zero (for example, MMIO).
+    if (cast<Instruction>(U)->isVolatile())
+      continue;
+    if (getLoadStoreAlignment(U).value() >= Stride.getZExtValue() &&
+        DL.getTypeStoreSize(getLoadStoreType(U)).getKnownMinValue() != 0 &&
+        DT.dominates(cast<Instruction>(U), LatchTerm))
+      return true;
+  }
+  return false;
+}
+
 static std::optional<std::tuple<PHINode *, PHINode *, const SCEV *, bool>>
 canFoldTermCondOfLoop(Loop *L, ScalarEvolution &SE, DominatorTree &DT,
                       const LoopInfo &LI, const TargetTransformInfo &TTI) {
@@ -152,7 +195,9 @@ canFoldTermCondOfLoop(Loop *L, ScalarEvolution &SE, DominatorTree &DT,
     // iteration. The simplest case to consider is a candidate IV which is
     // narrower than the trip count (and thus original IV), but this can
     // also happen due to non-unit strides on the candidate IVs.
-    if (!AddRec->hasNoSelfWrap() ||
+    if ((!AddRec->hasNoSelfWrap() &&
+         !hasNoSelfWrapViaDereference(PN, AddRec, SE, DT,
+                                      LoopLatch->getTerminator())) ||
         !SE.isKnownNonZero(AddRec->getStepRecurrence(SE)))
       continue;
 
diff --git a/llvm/test/Transforms/LoopStrengthReduce/lsr-term-fold.ll b/llvm/test/Transforms/LoopStrengthReduce/lsr-term-fold.ll
index 3c53befa67e230..1d4167414c9977 100644
--- a/llvm/test/Transforms/LoopStrengthReduce/lsr-term-fold.ll
+++ b/llvm/test/Transforms/LoopStrengthReduce/lsr-term-fold.ll
@@ -230,20 +230,20 @@ for.end:                                          ; preds = %for.body
   ret void
 }
 
-; In this case, the integer IV has a larger bitwidth than the pointer IV.
-; This means that the smaller IV may wrap around multiple times before
-; the original loop exit is taken.
+; The integer IV is wider than the pointer IV. However, the aligned store
+; would dereference null before the pointer repeats, so it is safe to fold.
 define void @iv_size(ptr %a, i128 %N) {
 ; CHECK-LABEL: @iv_size(
 ; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = trunc i128 [[N:%.*]] to i64
+; CHECK-NEXT:    [[TMP1:%.*]] = shl i64 [[TMP0]], 2
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A:%.*]], i64 [[TMP1]]
 ; CHECK-NEXT:    br label [[FOR_BODY:%.*]]
 ; CHECK:       for.body:
-; CHECK-NEXT:    [[LSR_IV1:%.*]] = phi ptr [ [[UGLYGEP2:%.*]], [[FOR_BODY]] ], [ [[A:%.*]], [[ENTRY:%.*]] ]
-; CHECK-NEXT:    [[LSR_IV:%.*]] = phi i128 [ [[LSR_IV_NEXT:%.*]], [[FOR_BODY]] ], [ [[N:%.*]], [[ENTRY]] ]
+; CHECK-NEXT:    [[LSR_IV1:%.*]] = phi ptr [ [[UGLYGEP2:%.*]], [[FOR_BODY]] ], [ [[A]], [[ENTRY:%.*]] ]
 ; CHECK-NEXT:    store i32 1, ptr [[LSR_IV1]], align 4
-; CHECK-NEXT:    [[LSR_IV_NEXT]] = add nsw i128 [[LSR_IV]], -1
 ; CHECK-NEXT:    [[UGLYGEP2]] = getelementptr i8, ptr [[LSR_IV1]], i64 4
-; CHECK-NEXT:    [[EXITCOND_NOT:%.*]] = icmp eq i128 [[LSR_IV_NEXT]], 0
+; CHECK-NEXT:    [[EXITCOND_NOT:%.*]] = icmp eq ptr [[UGLYGEP2]], [[SCEVGEP]]
 ; CHECK-NEXT:    br i1 [[EXITCOND_NOT]], label [[FOR_END:%.*]], label [[FOR_BODY]]
 ; CHECK:       for.end:
 ; CHECK-NEXT:    ret void
diff --git a/llvm/test/Transforms/LoopStrengthReduce/term-fold-dereference-narrow-index.ll b/llvm/test/Transforms/LoopStrengthReduce/term-fold-dereference-narrow-index.ll
new file mode 100644
index 00000000000000..c86326b2619e6a
--- /dev/null
+++ b/llvm/test/Transforms/LoopStrengthReduce/term-fold-dereference-narrow-index.ll
@@ -0,0 +1,34 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=loop-term-fold -S < %s | FileCheck %s
+
+; The unchanged high pointer bits can prevent the recurrence from visiting null.
+target datalayout = "e-p:64:64:64:32"
+
+define void @narrow_pointer_index(ptr %a, i32 %n) {
+; CHECK-LABEL: @narrow_pointer_index(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A:%.*]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[COUNT:%.*]] = phi i32 [ [[N:%.*]], [[ENTRY]] ], [ [[COUNT_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i8 1, ptr [[P]], align 2
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 2
+; CHECK-NEXT:    [[COUNT_NEXT]] = add i32 [[COUNT]], -1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i32 [[COUNT_NEXT]], 0
+; CHECK-NEXT:    br i1 [[DONE]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  store i8 1, ptr %p, align 2
+  %p.next = getelementptr i8, ptr %p, i32 2
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
diff --git a/llvm/test/Transforms/LoopStrengthReduce/term-fold-dereference.ll b/llvm/test/Transforms/LoopStrengthReduce/term-fold-dereference.ll
new file mode 100644
index 00000000000000..1045476589001e
--- /dev/null
+++ b/llvm/test/Transforms/LoopStrengthReduce/term-fold-dereference.ll
@@ -0,0 +1,515 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=loop-term-fold -S < %s | FileCheck %s
+
+target datalayout = "e-p:32:32-p1:32:32"
+
+define void @aligned_load(ptr %a, i32 %n, i1 %do_store) {
+; CHECK-LABEL: @aligned_load(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = shl i32 [[N:%.*]], 1
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A:%.*]], i32 [[TMP0]]
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[V:%.*]] = load i16, ptr [[P]], align 2
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 2
+; CHECK-NEXT:    [[LSR_FOLD_TERM_COND_REPLACED_TERM_COND:%.*]] = icmp eq ptr [[P_NEXT]], [[SCEVGEP]]
+; CHECK-NEXT:    br i1 [[LSR_FOLD_TERM_COND_REPLACED_TERM_COND]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  %v = load i16, ptr %p, align 2
+  %p.next = getelementptr i8, ptr %p, i32 2
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+define void @aligned_store(ptr %a, i32 %n, i1 %do_store) {
+; CHECK-LABEL: @aligned_store(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = shl i32 [[N:%.*]], 1
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A:%.*]], i32 [[TMP0]]
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i8 1, ptr [[P]], align 2
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 2
+; CHECK-NEXT:    [[LSR_FOLD_TERM_COND_REPLACED_TERM_COND:%.*]] = icmp eq ptr [[P_NEXT]], [[SCEVGEP]]
+; CHECK-NEXT:    br i1 [[LSR_FOLD_TERM_COND_REPLACED_TERM_COND]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  store i8 1, ptr %p, align 2
+  %p.next = getelementptr i8, ptr %p, i32 2
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+define void @negative_stride(ptr %a, i32 %n, i1 %do_store) {
+; CHECK-LABEL: @negative_stride(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = mul i32 [[N:%.*]], -2
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A:%.*]], i32 [[TMP0]]
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i8 1, ptr [[P]], align 2
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 -2
+; CHECK-NEXT:    [[LSR_FOLD_TERM_COND_REPLACED_TERM_COND:%.*]] = icmp eq ptr [[P_NEXT]], [[SCEVGEP]]
+; CHECK-NEXT:    br i1 [[LSR_FOLD_TERM_COND_REPLACED_TERM_COND]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  store i8 1, ptr %p, align 2
+  %p.next = getelementptr i8, ptr %p, i32 -2
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+define void @unit_stride(ptr %a, i32 %n, i1 %do_store) {
+; CHECK-LABEL: @unit_stride(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A:%.*]], i32 [[N:%.*]]
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i8 1, ptr [[P]], align 1
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 1
+; CHECK-NEXT:    [[LSR_FOLD_TERM_COND_REPLACED_TERM_COND:%.*]] = icmp eq ptr [[P_NEXT]], [[SCEVGEP]]
+; CHECK-NEXT:    br i1 [[LSR_FOLD_TERM_COND_REPLACED_TERM_COND]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  store i8 1, ptr %p, align 1
+  %p.next = getelementptr i8, ptr %p, i32 1
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+define void @larger_stride(ptr %a, i32 %n, i1 %do_store) {
+; CHECK-LABEL: @larger_stride(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[TMP0:%.*]] = shl i32 [[N:%.*]], 2
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[A:%.*]], i32 [[TMP0]]
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i8 1, ptr [[P]], align 4
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 4
+; CHECK-NEXT:    [[LSR_FOLD_TERM_COND_REPLACED_TERM_COND:%.*]] = icmp eq ptr [[P_NEXT]], [[SCEVGEP]]
+; CHECK-NEXT:    br i1 [[LSR_FOLD_TERM_COND_REPLACED_TERM_COND]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  store i8 1, ptr %p, align 4
+  %p.next = getelementptr i8, ptr %p, i32 4
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+; The pointer can stay in a nonzero residue class and never visit null.
+define void @insufficient_alignment(ptr %a, i32 %n, i1 %do_store) {
+; CHECK-LABEL: @insufficient_alignment(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A:%.*]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[COUNT:%.*]] = phi i32 [ [[N:%.*]], [[ENTRY]] ], [ [[COUNT_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i8 1, ptr [[P]], align 2
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 4
+; CHECK-NEXT:    [[COUNT_NEXT]] = add i32 [[COUNT]], -1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i32 [[COUNT_NEXT]], 0
+; CHECK-NEXT:    br i1 [[DONE]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  store i8 1, ptr %p, align 2
+  %p.next = getelementptr i8, ptr %p, i32 4
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+; Only power-of-two strides support the no-self-wrap bound used here.
+define void @odd_stride(ptr %a, i32 %n, i1 %do_store) {
+; CHECK-LABEL: @odd_stride(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A:%.*]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[COUNT:%.*]] = phi i32 [ [[N:%.*]], [[ENTRY]] ], [ [[COUNT_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i8 1, ptr [[P]], align 4
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 3
+; CHECK-NEXT:    [[COUNT_NEXT]] = add i32 [[COUNT]], -1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i32 [[COUNT_NEXT]], 0
+; CHECK-NEXT:    br i1 [[DONE]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  store i8 1, ptr %p, align 4
+  %p.next = getelementptr i8, ptr %p, i32 3
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+; A nonzero stride is required for a pointer exit test.
+define void @zero_stride(ptr %a, i32 %n, i1 %do_store) {
+; CHECK-LABEL: @zero_stride(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A:%.*]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[COUNT:%.*]] = phi i32 [ [[N:%.*]], [[ENTRY]] ], [ [[COUNT_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i8 1, ptr [[P]], align 2
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 0
+; CHECK-NEXT:    [[COUNT_NEXT]] = add i32 [[COUNT]], -1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i32 [[COUNT_NEXT]], 0
+; CHECK-NEXT:    br i1 [[DONE]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  store i8 1, ptr %p, align 2
+  %p.next = getelementptr i8, ptr %p, i32 0
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+; Dereferencing null does not imply undefined behavior in this function.
+define void @null_is_valid(ptr %a, i32 %n, i1 %do_store) null_pointer_is_valid {
+; CHECK-LABEL: @null_is_valid(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A:%.*]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[COUNT:%.*]] = phi i32 [ [[N:%.*]], [[ENTRY]] ], [ [[COUNT_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i8 1, ptr [[P]], align 2
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 2
+; CHECK-NEXT:    [[COUNT_NEXT]] = add i32 [[COUNT]], -1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i32 [[COUNT_NEXT]], 0
+; CHECK-NEXT:    br i1 [[DONE]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  store i8 1, ptr %p, align 2
+  %p.next = getelementptr i8, ptr %p, i32 2
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+; Do not assume null is invalid in other address spaces.
+define void @other_address_space(ptr addrspace(1) %a, i32 %n, i1 %do_store) {
+; CHECK-LABEL: @other_address_space(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr addrspace(1) [ [[A:%.*]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[COUNT:%.*]] = phi i32 [ [[N:%.*]], [[ENTRY]] ], [ [[COUNT_NEXT:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i8 1, ptr addrspace(1) [[P]], align 2
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr addrspace(1) [[P]], i32 2
+; CHECK-NEXT:    [[COUNT_NEXT]] = add i32 [[COUNT]], -1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i32 [[COUNT_NEXT]], 0
+; CHECK-NEXT:    br i1 [[DONE]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr addrspace(1) [ %a, %entry ], [ %p.next, %loop ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %loop ]
+  store i8 1, ptr addrspace(1) %p, align 2
+  %p.next = getelementptr i8, ptr addrspace(1) %p, i32 2
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+; The pointer may visit null on iterations that skip the store.
+define void @conditional_store(ptr %a, i32 %n, i1 %do_store) {
+; CHECK-LABEL: @conditional_store(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[P:%.*]] = phi ptr [ [[A:%.*]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LATCH:%.*]] ]
+; CHECK-NEXT:    [[COUNT:%.*]] = phi i32 [ [[N:%.*]], [[ENTRY]] ], [ [[COUNT_NEXT:%.*]], [[LATCH]] ]
+; CHECK-NEXT:    br i1 [[DO_STORE:%.*]], label [[ACCESS:%.*]], label [[LATCH]]
+; CHECK:       access:
+; CHECK-NEXT:    store i8 1, ptr [[P]], align 2
+; CHECK-NEXT:    br label [[LATCH]]
+; CHECK:       latch:
+; CHECK-NEXT:    [[P_NEXT]] = getelementptr i8, ptr [[P]], i32 2
+; CHECK-NEXT:    [[COUNT_NEXT]] = add i32 [[COUNT]], -1
+; CHECK-NEXT:    [[DONE:%.*]] = icmp eq i32 [[COUNT_NEXT]], 0
+; CHECK-NEXT:    br i1 [[DONE]], label [[EXIT:%.*]], label [[LOOP]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %loop
+loop:
+  %p = phi ptr [ %a, %entry ], [ %p.next, %latch ]
+  %count = phi i32 [ %n, %entry ], [ %count.next, %latch ]
+  br i1 %do_store, label %access, label %latch
+access:
+  store i8 1, ptr %p, align 2
+  br label %latch
+latch:
+  %p.next = getelementptr i8, ptr %p, i32 2
+  %count.next = add i32 %count, -1
+  %done = icmp eq i32 %count.next, 0
+  br i1 %done, label %exit, label %loop
+exit:
+  ret void
+}
+
+; A zero-size access does not establish that null is invalid.
+define void @empty_store(ptr %a, i32 %n, i1 %do_store) {
+; CHE...
[truncated]

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[RISC-V] Possible performance improvement in matrix multiply function in Coremark

1 participant