Skip to content

[SimplifyLibCalls] Fix memchr misoptimization #106121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 26, 2024

Conversation

s-barannikov
Copy link
Contributor

The ch argument of memcmp should be truncated to unsigned char before using it in comparisons. This didn't happen on all code paths. The following program miscompiled at -O1 and higher:

#include <cstring>
#include <iostream>

char ch = '\x81';

int main() {
    bool found = std::strchr("\x80\x81\x82", ch) != nullptr;
    std::cout << std::boolalpha << found << '\n';
}

The `ch` argument of memcmp should be truncated to `unsigned char`
before using it in comparisons. This didn't happen on all code paths.
The following program miscompiled at -O1 and higher:

```C++
#include <cstring>
#include <iostream>

char ch = '\x81';

int main() {
    bool found = std::strchr("\x80\x81\x82", ch) != nullptr;
    std::cout << std::boolalpha << found << '\n';
}
```
@s-barannikov s-barannikov requested a review from nikic August 26, 2024 19:18
@s-barannikov
Copy link
Contributor Author

@s-barannikov s-barannikov marked this pull request as ready for review August 26, 2024 19:18
@llvmbot
Copy link
Member

llvmbot commented Aug 26, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Sergei Barannikov (s-barannikov)

Changes

The ch argument of memcmp should be truncated to unsigned char before using it in comparisons. This didn't happen on all code paths. The following program miscompiled at -O1 and higher:

#include &lt;cstring&gt;
#include &lt;iostream&gt;

char ch = '\x81';

int main() {
    bool found = std::strchr("\x80\x81\x82", ch) != nullptr;
    std::cout &lt;&lt; std::boolalpha &lt;&lt; found &lt;&lt; '\n';
}

Full diff: https://github.com/llvm/llvm-project/pull/106121.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp (+4-2)
  • (modified) llvm/test/Transforms/InstCombine/memchr-7.ll (+30-25)
diff --git a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
index fb2efe581ac6bb..1e6dc88ed93532 100644
--- a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
@@ -1454,10 +1454,12 @@ Value *LibCallSimplifier::optimizeMemChr(CallInst *CI, IRBuilderBase &B) {
     if (NonContRanges > 2)
       return nullptr;
 
+    // Slice off the character's high end bits.
+    CharVal = B.CreateTrunc(CharVal, B.getInt8Ty());
+
     SmallVector<Value *> CharCompares;
     for (unsigned char C : SortedStr)
-      CharCompares.push_back(
-          B.CreateICmpEQ(CharVal, ConstantInt::get(CharVal->getType(), C)));
+      CharCompares.push_back(B.CreateICmpEQ(CharVal, B.getInt8(C)));
 
     return B.CreateIntToPtr(B.CreateOr(CharCompares), CI->getType());
   }
diff --git a/llvm/test/Transforms/InstCombine/memchr-7.ll b/llvm/test/Transforms/InstCombine/memchr-7.ll
index 0b364cce656d77..61f1093279f834 100644
--- a/llvm/test/Transforms/InstCombine/memchr-7.ll
+++ b/llvm/test/Transforms/InstCombine/memchr-7.ll
@@ -12,11 +12,12 @@ declare ptr @memchr(ptr, i32, i64)
 
 define zeroext i1 @strchr_to_memchr_n_equals_len(i32 %c) {
 ; CHECK-LABEL: @strchr_to_memchr_n_equals_len(
-; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[C:%.*]], 0
-; CHECK-NEXT:    [[TMP2:%.*]] = add i32 [[C]], -97
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp ult i32 [[TMP2]], 26
-; CHECK-NEXT:    [[TMP4:%.*]] = or i1 [[TMP1]], [[TMP3]]
-; CHECK-NEXT:    ret i1 [[TMP4]]
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT:    [[TMP3:%.*]] = add i8 [[TMP1]], -97
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ult i8 [[TMP3]], 26
+; CHECK-NEXT:    [[TMP5:%.*]] = or i1 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    ret i1 [[TMP5]]
 ;
   %call = tail call ptr @strchr(ptr nonnull dereferenceable(27) @.str, i32 %c)
   %cmp = icmp ne ptr %call, null
@@ -38,9 +39,10 @@ define zeroext i1 @memchr_n_equals_len(i32 %c) {
 
 define zeroext i1 @memchr_n_less_than_len(i32 %c) {
 ; CHECK-LABEL: @memchr_n_less_than_len(
-; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[C:%.*]], -97
-; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], 15
-; CHECK-NEXT:    ret i1 [[TMP2]]
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = add i8 [[TMP1]], -97
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp ult i8 [[TMP2]], 15
+; CHECK-NEXT:    ret i1 [[TMP3]]
 ;
   %call = tail call ptr @memchr(ptr @.str, i32 %c, i64 15)
   %cmp = icmp ne ptr %call, null
@@ -50,11 +52,12 @@ define zeroext i1 @memchr_n_less_than_len(i32 %c) {
 
 define zeroext i1 @memchr_n_more_than_len(i32 %c) {
 ; CHECK-LABEL: @memchr_n_more_than_len(
-; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[C:%.*]], 0
-; CHECK-NEXT:    [[TMP2:%.*]] = add i32 [[C]], -97
-; CHECK-NEXT:    [[TMP3:%.*]] = icmp ult i32 [[TMP2]], 26
-; CHECK-NEXT:    [[TMP4:%.*]] = or i1 [[TMP1]], [[TMP3]]
-; CHECK-NEXT:    ret i1 [[TMP4]]
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i8 [[TMP1]], 0
+; CHECK-NEXT:    [[TMP3:%.*]] = add i8 [[TMP1]], -97
+; CHECK-NEXT:    [[TMP4:%.*]] = icmp ult i8 [[TMP3]], 26
+; CHECK-NEXT:    [[TMP5:%.*]] = or i1 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    ret i1 [[TMP5]]
 ;
   %call = tail call ptr @memchr(ptr @.str, i32 %c, i64 30)
   %cmp = icmp ne ptr %call, null
@@ -114,12 +117,13 @@ define zeroext i1 @memchr_n_equals_len2_minsize(i32 %c) minsize {
 ; Positive test - 2 non-contiguous ranges
 define zeroext i1 @strchr_to_memchr_2_non_cont_ranges(i32 %c) {
 ; CHECK-LABEL: @strchr_to_memchr_2_non_cont_ranges(
-; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[C:%.*]], -97
-; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], 6
-; CHECK-NEXT:    [[TMP3:%.*]] = add i32 [[C]], -109
-; CHECK-NEXT:    [[TMP4:%.*]] = icmp ult i32 [[TMP3]], 3
-; CHECK-NEXT:    [[TMP5:%.*]] = or i1 [[TMP2]], [[TMP4]]
-; CHECK-NEXT:    ret i1 [[TMP5]]
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = add i8 [[TMP1]], -97
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp ult i8 [[TMP2]], 6
+; CHECK-NEXT:    [[TMP4:%.*]] = add i8 [[TMP1]], -109
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp ult i8 [[TMP4]], 3
+; CHECK-NEXT:    [[TMP6:%.*]] = or i1 [[TMP3]], [[TMP5]]
+; CHECK-NEXT:    ret i1 [[TMP6]]
 ;
   %call = tail call ptr @memchr(ptr @.str.2, i32 %c, i64 9)
   %cmp = icmp ne ptr %call, null
@@ -129,12 +133,13 @@ define zeroext i1 @strchr_to_memchr_2_non_cont_ranges(i32 %c) {
 ; Positive test - 2 non-contiguous ranges with char duplication
 define zeroext i1 @strchr_to_memchr_2_non_cont_ranges_char_dup(i32 %c) {
 ; CHECK-LABEL: @strchr_to_memchr_2_non_cont_ranges_char_dup(
-; CHECK-NEXT:    [[TMP1:%.*]] = add i32 [[C:%.*]], -97
-; CHECK-NEXT:    [[TMP2:%.*]] = icmp ult i32 [[TMP1]], 3
-; CHECK-NEXT:    [[TMP3:%.*]] = add i32 [[C]], -109
-; CHECK-NEXT:    [[TMP4:%.*]] = icmp ult i32 [[TMP3]], 2
-; CHECK-NEXT:    [[TMP5:%.*]] = or i1 [[TMP2]], [[TMP4]]
-; CHECK-NEXT:    ret i1 [[TMP5]]
+; CHECK-NEXT:    [[TMP1:%.*]] = trunc i32 [[C:%.*]] to i8
+; CHECK-NEXT:    [[TMP2:%.*]] = add i8 [[TMP1]], -97
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp ult i8 [[TMP2]], 3
+; CHECK-NEXT:    [[TMP4:%.*]] = add i8 [[TMP1]], -109
+; CHECK-NEXT:    [[TMP5:%.*]] = icmp ult i8 [[TMP4]], 2
+; CHECK-NEXT:    [[TMP6:%.*]] = or i1 [[TMP3]], [[TMP5]]
+; CHECK-NEXT:    ret i1 [[TMP6]]
 ;
   %call = tail call ptr @memchr(ptr @.str.4, i32 %c, i64 6)
   %cmp = icmp ne ptr %call, null

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@s-barannikov s-barannikov merged commit 7134d2e into llvm:main Aug 26, 2024
12 checks passed
@s-barannikov s-barannikov deleted the byte/memchr-miscompile branch August 26, 2024 21:11
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.

3 participants