[InstCombine] Fold memchr of sequences of same characters
authorMartin Sebor <msebor@redhat.com>
Tue, 7 Jun 2022 19:45:10 +0000 (13:45 -0600)
committerMartin Sebor <msebor@redhat.com>
Tue, 7 Jun 2022 19:45:10 +0000 (13:45 -0600)
Enhance memchr libcall folder to handle constant arrays consisting
of one or two sequences of cosecutive equal characters.

Reviewed By: nikic

Differential Revision: https://reviews.llvm.org/D126515

llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
llvm/test/Transforms/InstCombine/memchr-6.ll [new file with mode: 0644]
llvm/test/Transforms/InstCombine/memchr.ll
llvm/test/Transforms/InstCombine/strchr-3.ll [new file with mode: 0644]

index 97b02de..7fa4955 100644 (file)
@@ -1006,11 +1006,12 @@ Value *LibCallSimplifier::optimizeMemChr(CallInst *CI, IRBuilderBase &B) {
   Value *CharVal = CI->getArgOperand(1);
   ConstantInt *CharC = dyn_cast<ConstantInt>(CharVal);
   ConstantInt *LenC = dyn_cast<ConstantInt>(Size);
+  Value *NullPtr = Constant::getNullValue(CI->getType());
 
   // memchr(x, y, 0) -> null
   if (LenC) {
     if (LenC->isZero())
-      return Constant::getNullValue(CI->getType());
+      return NullPtr;
 
     if (LenC->isOne()) {
       // Fold memchr(x, y, 1) --> *x == y ? x : null for any x and y,
@@ -1019,7 +1020,6 @@ Value *LibCallSimplifier::optimizeMemChr(CallInst *CI, IRBuilderBase &B) {
       // Slice off the character's high end bits.
       CharVal = B.CreateTrunc(CharVal, B.getInt8Ty());
       Value *Cmp = B.CreateICmpEQ(Val, CharVal, "memchr.char0cmp");
-      Value *NullPtr = Constant::getNullValue(CI->getType());
       return B.CreateSelect(Cmp, SrcStr, NullPtr, "memchr.sel");
     }
   }
@@ -1033,27 +1033,62 @@ Value *LibCallSimplifier::optimizeMemChr(CallInst *CI, IRBuilderBase &B) {
     if (Pos == StringRef::npos)
       // When the character is not in the source array fold the result
       // to null regardless of Size.
-      return Constant::getNullValue(CI->getType());
+      return NullPtr;
 
     // Fold memchr(s, c, n) -> n <= Pos ? null : s + Pos
     // When the constant Size is less than or equal to the character
     // position also fold the result to null.
     Value *Cmp = B.CreateICmpULE(Size, ConstantInt::get(Size->getType(), Pos),
                                  "memchr.cmp");
-    Value *NullPtr = Constant::getNullValue(CI->getType());
     Value *SrcPlus =
         B.CreateGEP(B.getInt8Ty(), SrcStr, B.getInt64(Pos), "memchr.ptr");
     return B.CreateSelect(Cmp, NullPtr, SrcPlus);
   }
 
+  if (LenC)
+    Str = substr(Str, LenC->getZExtValue());
+
+  size_t Pos = Str.find_first_not_of(Str[0]);
+  if (Pos == StringRef::npos
+      || Str.find_first_not_of(Str[Pos], Pos) == StringRef::npos) {
+    // If the source array consists of at most two consecutive sequences
+    // of the same characters, then for any C and N (whether in bounds or
+    // not), fold memchr(S, C, N) to
+    //   N != 0 && *S == C ? S : null
+    // or for the two sequences to:
+    //   N != 0 && *S == C ? S : (N > Pos && S[Pos] == C ? S + Pos : null)
+    //   ^Sel2                   ^Sel1 are denoted above.
+    // The latter makes it also possible to fold strchr() calls with strings
+    // of the same characters.
+    Type *SizeTy = Size->getType();
+    Type *Int8Ty = B.getInt8Ty();
+
+    // Slice off the sought character's high end bits.
+    CharVal = B.CreateTrunc(CharVal, Int8Ty);
+
+    Value *Sel1 = NullPtr;
+    if (Pos != StringRef::npos) {
+      // Handle two consecutive sequences of the same characters.
+      Value *PosVal = ConstantInt::get(SizeTy, Pos);
+      Value *StrPos = ConstantInt::get(Int8Ty, Str[Pos]);
+      Value *CEqSPos = B.CreateICmpEQ(CharVal, StrPos);
+      Value *NGtPos = B.CreateICmp(ICmpInst::ICMP_UGT, Size, PosVal);
+      Value *And = B.CreateAnd(CEqSPos, NGtPos);
+      Value *SrcPlus = B.CreateInBoundsGEP(B.getInt8Ty(), SrcStr, PosVal);
+      Sel1 = B.CreateSelect(And, SrcPlus, NullPtr, "memchr.sel1");
+    }
+
+    Value *Str0 = ConstantInt::get(Int8Ty, Str[0]);
+    Value *CEqS0 = B.CreateICmpEQ(Str0, CharVal);
+    Value *NNeZ = B.CreateICmpNE(Size, ConstantInt::get(SizeTy, 0));
+    Value *And = B.CreateAnd(NNeZ, CEqS0);
+    return B.CreateSelect(And, SrcStr, Sel1, "memchr.sel2");
+  }
+
   if (!LenC)
     // From now on we need a constant length and constant array.
     return nullptr;
 
-  // Truncate the string to LenC without slicing when targeting LP64
-  // on an ILP32 host.
-  Str = substr(Str, LenC->getZExtValue());
-
   // If the char is variable but the input str and length are not we can turn
   // this memchr call into a simple bit field test. Of course this only works
   // when the return value is only checked against null.
diff --git a/llvm/test/Transforms/InstCombine/memchr-6.ll b/llvm/test/Transforms/InstCombine/memchr-6.ll
new file mode 100644 (file)
index 0000000..813367b
--- /dev/null
@@ -0,0 +1,148 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+;
+; Verify that memchr calls with a string consisting of all the same
+; characters are folded and those with mixed strings are not.
+
+declare i8* @memchr(i8*, i32, i64)
+
+@a00000 = constant [5 x i8] zeroinitializer
+@a11111 = constant [5 x i8] c"\01\01\01\01\01"
+@a111122 = constant [6 x i8] c"\01\01\01\01\02\02"
+@a1110111 = constant [7 x i8] c"\01\01\01\00\01\01\01"
+
+
+; Fold memchr(a00000, C, 5) to *a00000 == C ? a00000 : null.
+; TODO: This depends on getConstantStringInfo() being able to handle
+; implicitly zeroed out constants.
+
+define i8* @fold_memchr_a00000_c_5(i32 %C) {
+; CHECK-LABEL: @fold_memchr_a00000_c_5(
+; CHECK-NEXT:    [[RET:%.*]] = call i8* @memchr(i8* noundef nonnull dereferenceable(1) getelementptr inbounds ([5 x i8], [5 x i8]* @a00000, i64 0, i64 0), i32 [[C:%.*]], i64 5)
+; CHECK-NEXT:    ret i8* [[RET]]
+;
+
+  %ptr = getelementptr [5 x i8], [5 x i8]* @a00000, i64 0, i64 0
+  %ret = call i8* @memchr(i8* %ptr, i32 %C, i64 5)
+  ret i8* %ret
+}
+
+
+; Fold memchr(a11111, C, 5) to *a11111 == C ? a11111 : null.
+
+define i8* @fold_memchr_a11111_c_5(i32 %C) {
+; CHECK-LABEL: @fold_memchr_a11111_c_5(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 1
+; CHECK-NEXT:    [[MEMCHR_SEL2:%.*]] = select i1 [[TMP2]], i8* getelementptr inbounds ([5 x i8], [5 x i8]* @a11111, i64 0, i64 0), i8* null
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL2]]
+;
+
+  %ptr = getelementptr [5 x i8], [5 x i8]* @a11111, i64 0, i64 0
+  %ret = call i8* @memchr(i8* %ptr, i32 %C, i64 5)
+  ret i8* %ret
+}
+
+
+; Fold memchr(a11111, C, N) to N && *a11111 == C ? a11111 : null,
+; on the assumption that N is in bounds.
+
+define i8* @fold_memchr_a11111_c_n(i32 %C, i64 %N) {
+; CHECK-LABEL: @fold_memchr_a11111_c_n(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 1
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp ne i64 [[N:%.*]], 0
+; CHECK-NEXT:    [[TMP4:%.*]] = and i1 [[TMP3]], [[TMP2]]
+; CHECK-NEXT:    [[MEMCHR_SEL2:%.*]] = select i1 [[TMP4]], i8* getelementptr inbounds ([5 x i8], [5 x i8]* @a11111, i64 0, i64 0), i8* null
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL2]]
+;
+
+  %ptr = getelementptr [5 x i8], [5 x i8]* @a11111, i64 0, i64 0
+  %ret = call i8* @memchr(i8* %ptr, i32 %C, i64 %N)
+  ret i8* %ret
+}
+
+
+; Fold memchr(a111122, C, N) to
+;   N != 0 && C == 1 ? a111122 : N > 4 && C == 2 ? a111122 + 4 : null.
+
+define i8* @fold_memchr_a111122_c_n(i32 %C, i64 %N) {
+; CHECK-LABEL: @fold_memchr_a111122_c_n(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 2
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp ugt i64 [[N:%.*]], 4
+; CHECK-NEXT:    [[TMP4:%.*]] = and i1 [[TMP2]], [[TMP3]]
+; CHECK-NEXT:    [[MEMCHR_SEL1:%.*]] = select i1 [[TMP4]], i8* getelementptr inbounds ([6 x i8], [6 x i8]* @a111122, i64 0, i64 4), i8* null
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp eq i8 [[TMP1]], 1
+; CHECK-NEXT:    [[TMP6:%.*]] = icmp ne i64 [[N]], 0
+; CHECK-NEXT:    [[TMP7:%.*]] = and i1 [[TMP6]], [[TMP5]]
+; CHECK-NEXT:    [[MEMCHR_SEL2:%.*]] = select i1 [[TMP7]], i8* getelementptr inbounds ([6 x i8], [6 x i8]* @a111122, i64 0, i64 0), i8* [[MEMCHR_SEL1]]
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL2]]
+;
+
+  %ptr = getelementptr [6 x i8], [6 x i8]* @a111122, i64 0, i64 0
+  %ret = call i8* @memchr(i8* %ptr, i32 %C, i64 %N)
+  ret i8* %ret
+}
+
+
+; Fold memchr(a1110111, C, 3) to a1110111[2] == C ? a1110111 : null.
+
+define i8* @fold_memchr_a1110111_c_3(i32 %C) {
+; CHECK-LABEL: @fold_memchr_a1110111_c_3(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 1
+; CHECK-NEXT:    [[MEMCHR_SEL2:%.*]] = select i1 [[TMP2]], i8* getelementptr inbounds ([7 x i8], [7 x i8]* @a1110111, i64 0, i64 0), i8* null
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL2]]
+;
+
+  %ptr = getelementptr [7 x i8], [7 x i8]* @a1110111, i64 0, i64 0
+  %ret = call i8* @memchr(i8* %ptr, i32 %C, i64 3)
+  ret i8* %ret
+}
+
+
+; Don't fold memchr(a1110111, C, 4).
+
+define i8* @call_memchr_a1110111_c_4(i32 %C) {
+; CHECK-LABEL: @call_memchr_a1110111_c_4(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT:    [[MEMCHR_SEL1:%.*]] = select i1 [[TMP2]], i8* getelementptr inbounds ([7 x i8], [7 x i8]* @a1110111, i64 0, i64 3), i8* null
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq i8 [[TMP1]], 1
+; CHECK-NEXT:    [[MEMCHR_SEL2:%.*]] = select i1 [[TMP3]], i8* getelementptr inbounds ([7 x i8], [7 x i8]* @a1110111, i64 0, i64 0), i8* [[MEMCHR_SEL1]]
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL2]]
+;
+
+  %ptr = getelementptr [7 x i8], [7 x i8]* @a1110111, i64 0, i64 0
+  %ret = call i8* @memchr(i8* %ptr, i32 %C, i64 4)
+  ret i8* %ret
+}
+
+
+; Don't fold memchr(a1110111, C, 7).
+
+define i8* @call_memchr_a1110111_c_7(i32 %C) {
+; CHECK-LABEL: @call_memchr_a1110111_c_7(
+; CHECK-NEXT:    [[RET:%.*]] = call i8* @memchr(i8* noundef nonnull dereferenceable(1) getelementptr inbounds ([7 x i8], [7 x i8]* @a1110111, i64 0, i64 0), i32 [[C:%.*]], i64 7)
+; CHECK-NEXT:    ret i8* [[RET]]
+;
+
+  %ptr = getelementptr [7 x i8], [7 x i8]* @a1110111, i64 0, i64 0
+  %ret = call i8* @memchr(i8* %ptr, i32 %C, i64 7)
+  ret i8* %ret
+}
+
+
+; Don't fold memchr(a1110111, C, N).
+
+define i8* @call_memchr_a1110111_c_n(i32 %C, i64 %N) {
+; CHECK-LABEL: @call_memchr_a1110111_c_n(
+; CHECK-NEXT:    [[RET:%.*]] = call i8* @memchr(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @a1110111, i64 0, i64 0), i32 [[C:%.*]], i64 [[N:%.*]])
+; CHECK-NEXT:    ret i8* [[RET]]
+;
+
+  %ptr = getelementptr [7 x i8], [7 x i8]* @a1110111, i64 0, i64 0
+  %ret = call i8* @memchr(i8* %ptr, i32 %C, i64 %N)
+  ret i8* %ret
+}
index c702964..0278631 100644 (file)
@@ -128,19 +128,19 @@ define void @test10() {
   ret void
 }
 
-; Check transformation memchr("\r\n", C, 2) != nullptr -> (C & 9216) != 0
+; Check transformation memchr("\r\n", C, 3) != nullptr -> (C & 9217) != 0
 define i1 @test11(i32 %C) {
 ; CHECK-LABEL: @test11(
 ; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i16
 ; CHECK-NEXT:    [[TMP2:%.*]] = and i16 [[TMP1]], 255
 ; CHECK-NEXT:    [[MEMCHR_BOUNDS:%.*]] = icmp ult i16 [[TMP2]], 16
 ; CHECK-NEXT:    [[TMP3:%.*]] = shl i16 1, [[TMP2]]
-; CHECK-NEXT:    [[TMP4:%.*]] = and i16 [[TMP3]], 9216
+; CHECK-NEXT:    [[TMP4:%.*]] = and i16 [[TMP3]], 9217
 ; CHECK-NEXT:    [[MEMCHR_BITS:%.*]] = icmp ne i16 [[TMP4]], 0
 ; CHECK-NEXT:    [[MEMCHR:%.*]] = select i1 [[MEMCHR_BOUNDS]], i1 [[MEMCHR_BITS]], i1 false
 ; CHECK-NEXT:    ret i1 [[MEMCHR]]
 ;
-  %dst = call i8* @memchr(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @newlines, i64 0, i64 0), i32 %C, i32 2)
+  %dst = call i8* @memchr(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @newlines, i64 0, i64 0), i32 %C, i32 3)
   %cmp = icmp ne i8* %dst, null
   ret i1 %cmp
 }
@@ -159,13 +159,11 @@ define i1 @test12(i32 %C) {
 
 define i1 @test13(i32 %C) {
 ; CHECK-LABEL: @test13(
-; CHECK-NEXT:    [[TMP1:%.*]] = and i32 [[C:%.*]], 255
-; CHECK-NEXT:    [[MEMCHR_BOUNDS:%.*]] = icmp ult i32 [[TMP1]], 32
-; CHECK-NEXT:    [[TMP2:%.*]] = shl i32 1, [[TMP1]]
-; CHECK-NEXT:    [[TMP3:%.*]] = and i32 [[TMP2]], -2147483647
-; CHECK-NEXT:    [[MEMCHR_BITS:%.*]] = icmp ne i32 [[TMP3]], 0
-; CHECK-NEXT:    [[MEMCHR:%.*]] = select i1 [[MEMCHR_BOUNDS]], i1 [[MEMCHR_BITS]], i1 false
-; CHECK-NEXT:    ret i1 [[MEMCHR]]
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq i8 [[TMP1]], 31
+; CHECK-NEXT:    [[CMP:%.*]] = or i1 [[TMP3]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %dst = call i8* @memchr(i8* getelementptr inbounds ([2 x i8], [2 x i8]* @single, i64 0, i64 0), i32 %C, i32 2)
   %cmp = icmp ne i8* %dst, null
diff --git a/llvm/test/Transforms/InstCombine/strchr-3.ll b/llvm/test/Transforms/InstCombine/strchr-3.ll
new file mode 100644 (file)
index 0000000..5aa5130
--- /dev/null
@@ -0,0 +1,129 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+;
+; Verify that strchr calls with a string consisting of one or two sequences
+; of all the same characters are folded and those with mixed strings are not.
+
+@s1 = constant [2 x i8] c"\01\00"
+@s11 = constant [3 x i8] c"\01\01\00"
+@s111 = constant [4 x i8] c"\01\01\01\00"
+@s000 = constant [4 x i8] c"\00\00\00\00"
+@s11102 = constant [6 x i8] c"\01\01\01\00\02\00"
+@s21111 = constant [6 x i8] c"\02\01\01\01\01\00"
+
+declare i8* @strchr(i8*, i32)
+
+
+; Fold strchr(S = "\01", C) to C == '\01' ? S : C == '\0' ? S + 1 : null.
+
+define i8* @fold_strchr_s1_C(i32 %C) {
+; CHECK-LABEL: @fold_strchr_s1_C(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT:    [[MEMCHR_SEL1:%.*]] = select i1 [[TMP2]], i8* getelementptr inbounds ([2 x i8], [2 x i8]* @s1, i64 0, i64 1), i8* null
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq i8 [[TMP1]], 1
+; CHECK-NEXT:    [[MEMCHR_SEL2:%.*]] = select i1 [[TMP3]], i8* getelementptr inbounds ([2 x i8], [2 x i8]* @s1, i64 0, i64 0), i8* [[MEMCHR_SEL1]]
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL2]]
+;
+  %ptr = getelementptr inbounds [2 x i8], [2 x i8]* @s1, i64 0, i64 0
+  %ret = call i8* @strchr(i8* %ptr, i32 %C)
+  ret i8* %ret
+}
+
+
+; Fold strchr(S = "\01\01", C) to C == '\01' ? S : C == '\0' ? S + 2 : null.
+
+define i8* @fold_strchr_s11_C(i32 %C) {
+; CHECK-LABEL: @fold_strchr_s11_C(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT:    [[MEMCHR_SEL1:%.*]] = select i1 [[TMP2]], i8* getelementptr inbounds ([3 x i8], [3 x i8]* @s11, i64 0, i64 2), i8* null
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq i8 [[TMP1]], 1
+; CHECK-NEXT:    [[MEMCHR_SEL2:%.*]] = select i1 [[TMP3]], i8* getelementptr inbounds ([3 x i8], [3 x i8]* @s11, i64 0, i64 0), i8* [[MEMCHR_SEL1]]
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL2]]
+;
+  %ptr = getelementptr inbounds [3 x i8], [3 x i8]* @s11, i64 0, i64 0
+  %ret = call i8* @strchr(i8* %ptr, i32 %C)
+  ret i8* %ret
+}
+
+
+; Fold strchr(S = "\01\01\01", C) to C == '\01' ? S : C == '\0' ? S + 3 : null.
+
+define i8* @fold_strchr_s111_C(i32 %C) {
+; CHECK-LABEL: @fold_strchr_s111_C(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT:    [[MEMCHR_SEL1:%.*]] = select i1 [[TMP2]], i8* getelementptr inbounds ([4 x i8], [4 x i8]* @s111, i64 0, i64 3), i8* null
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq i8 [[TMP1]], 1
+; CHECK-NEXT:    [[MEMCHR_SEL2:%.*]] = select i1 [[TMP3]], i8* getelementptr inbounds ([4 x i8], [4 x i8]* @s111, i64 0, i64 0), i8* [[MEMCHR_SEL1]]
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL2]]
+;
+  %ptr = getelementptr inbounds [4 x i8], [4 x i8]* @s111, i64 0, i64 0
+  %ret = call i8* @strchr(i8* %ptr, i32 %C)
+  ret i8* %ret
+}
+
+
+; Fold strchr(S = "\00\00\00", C) to C == '\0' ? S : null.
+
+define i8* @fold_strchr_s000_C(i32 %C) {
+; CHECK-LABEL: @fold_strchr_s000_C(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[MEMCHR_CHAR0CMP:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT:    [[MEMCHR_SEL:%.*]] = select i1 [[MEMCHR_CHAR0CMP]], i8* getelementptr inbounds ([4 x i8], [4 x i8]* @s000, i64 0, i64 0), i8* null
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL]]
+;
+  %ptr = getelementptr inbounds [4 x i8], [4 x i8]* @s000, i64 0, i64 0
+  %ret = call i8* @strchr(i8* %ptr, i32 %C)
+  ret i8* %ret
+}
+
+
+; Do not fold strchr(S = "\02\01\01\01\01", C).  It's transformed to
+; memchr(S, C, 6).
+
+define i8* @xform_strchr_s21111_C(i32 %C) {
+; CHECK-LABEL: @xform_strchr_s21111_C(
+; CHECK-NEXT:    [[MEMCHR:%.*]] = call i8* @memchr(i8* noundef nonnull dereferenceable(1) getelementptr inbounds ([6 x i8], [6 x i8]* @s21111, i64 0, i64 0), i32 [[C:%.*]], i64 6)
+; CHECK-NEXT:    ret i8* [[MEMCHR]]
+;
+  %ptr = getelementptr inbounds [6 x i8], [6 x i8]* @s21111, i64 0, i64 0
+  %ret = call i8* @strchr(i8* %ptr, i32 %C)
+  ret i8* %ret
+}
+
+
+; Fold strchr(S = "\02\01\01\01\01" + 1, C) to
+;   C == '\01' ? S + 1 : C == '\0' ? S + 5 : null.
+
+define i8* @fold_strchr_s21111p1_C(i32 %C) {
+; CHECK-LABEL: @fold_strchr_s21111p1_C(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT:    [[MEMCHR_SEL1:%.*]] = select i1 [[TMP2]], i8* getelementptr inbounds ([6 x i8], [6 x i8]* @s21111, i64 0, i64 5), i8* null
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq i8 [[TMP1]], 1
+; CHECK-NEXT:    [[MEMCHR_SEL2:%.*]] = select i1 [[TMP3]], i8* getelementptr inbounds ([6 x i8], [6 x i8]* @s21111, i64 0, i64 1), i8* [[MEMCHR_SEL1]]
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL2]]
+;
+  %ptr = getelementptr inbounds [6 x i8], [6 x i8]* @s21111, i64 0, i64 1
+  %ret = call i8* @strchr(i8* %ptr, i32 %C)
+  ret i8* %ret
+}
+
+; Fold strchr(S = "\01\01\01\00\02", C) to
+;   C == '\01' ? S : C == '\0' ? S + 3 : null.
+
+define i8* @fold_strchr_s11102_C(i32 %C) {
+; CHECK-LABEL: @fold_strchr_s11102_C(
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT:    [[MEMCHR_SEL1:%.*]] = select i1 [[TMP2]], i8* getelementptr inbounds ([6 x i8], [6 x i8]* @s11102, i64 0, i64 3), i8* null
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp eq i8 [[TMP1]], 1
+; CHECK-NEXT:    [[MEMCHR_SEL2:%.*]] = select i1 [[TMP3]], i8* getelementptr inbounds ([6 x i8], [6 x i8]* @s11102, i64 0, i64 0), i8* [[MEMCHR_SEL1]]
+; CHECK-NEXT:    ret i8* [[MEMCHR_SEL2]]
+;
+  %ptr = getelementptr inbounds [6 x i8], [6 x i8]* @s11102, i64 0, i64 0
+  %ret = call i8* @strchr(i8* %ptr, i32 %C)
+  ret i8* %ret
+}