[llvm] [LoopIdiom] Don't introduce `wcslen` calls when `wchar` size is not the default (PR #213629)

via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 3 02:20:53 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Ömer Sinan Ağacan (osa1)

<details>
<summary>Changes</summary>

Fixes #<!-- -->190340.

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


6 Files Affected:

- (modified) llvm/include/llvm/Analysis/TargetLibraryInfo.h (+8) 
- (modified) llvm/lib/Analysis/TargetLibraryInfo.cpp (+4) 
- (modified) llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp (+11-1) 
- (added) llvm/test/Transforms/LoopIdiom/wcslen-wchar-override.ll (+133) 
- (modified) llvm/test/Transforms/LoopIdiom/wcslen16.ll (+9-12) 
- (modified) llvm/test/Transforms/LoopIdiom/wcslen32.ll (-1) 


``````````diff
diff --git a/llvm/include/llvm/Analysis/TargetLibraryInfo.h b/llvm/include/llvm/Analysis/TargetLibraryInfo.h
index 629b126db17c2..70af9f261b6ce 100644
--- a/llvm/include/llvm/Analysis/TargetLibraryInfo.h
+++ b/llvm/include/llvm/Analysis/TargetLibraryInfo.h
@@ -234,6 +234,9 @@ class TargetLibraryInfoImpl {
   /// This queries the 'wchar_size' metadata.
   LLVM_ABI unsigned getWCharSize(const Module &M) const;
 
+  /// Returns the size of the default wchar_t type in bytes.
+  LLVM_ABI unsigned getDefaultWCharSize(const Module &M) const;
+
   /// Returns the size of the size_t type in bits.
   LLVM_ABI unsigned getSizeTSize(const Module &M) const;
 
@@ -552,6 +555,11 @@ class TargetLibraryInfo {
     return Impl->getWCharSize(M);
   }
 
+  /// \copydoc TargetLibraryInfoImpl::getDefaultWCharSize()
+  unsigned getDefaultWCharSize(const Module &M) const {
+    return Impl->getDefaultWCharSize(M);
+  }
+
   /// \copydoc TargetLibraryInfoImpl::getSizeTSize()
   unsigned getSizeTSize(const Module &M) const { return Impl->getSizeTSize(M); }
 
diff --git a/llvm/lib/Analysis/TargetLibraryInfo.cpp b/llvm/lib/Analysis/TargetLibraryInfo.cpp
index 763c04b9b06f7..f82da068d1888 100644
--- a/llvm/lib/Analysis/TargetLibraryInfo.cpp
+++ b/llvm/lib/Analysis/TargetLibraryInfo.cpp
@@ -1443,6 +1443,10 @@ unsigned TargetLibraryInfoImpl::getWCharSize(const Module &M) const {
   if (auto *ShortWChar = cast_or_null<ConstantAsMetadata>(
       M.getModuleFlag("wchar_size")))
     return cast<ConstantInt>(ShortWChar->getValue())->getZExtValue();
+  return getDefaultWCharSize(M);
+}
+
+unsigned TargetLibraryInfoImpl::getDefaultWCharSize(const Module &M) const {
   return Triple(M.getTargetTriple()).getDefaultWCharSize();
 }
 
diff --git a/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp b/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
index 4e5cd8462cc4d..011e11c11ac3f 100644
--- a/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopIdiomRecognize.cpp
@@ -2107,9 +2107,19 @@ class StrlenVerifier {
       return false;
     if (OpWidth != 8 && OpWidth != 16 && OpWidth != 32)
       return false;
-    if (OpWidth >= 16)
+    if (OpWidth >= 16) {
+      // Do not introduce a wcslen library call if we're overriding the default
+      // wchar size, to avoid ABI incompatibility between the library and the
+      // call site.
+      unsigned DefaultWcharSize =
+          TLI->getDefaultWCharSize(*LoopLoad->getModule());
+      if (WcharSize != DefaultWcharSize) {
+        return false;
+      }
+
       if (OpWidth != WcharSize * 8)
         return false;
+    }
 
     // Scan every instruction in the loop to ensure there are no side effects.
     for (Instruction &I : *LoopBody)
diff --git a/llvm/test/Transforms/LoopIdiom/wcslen-wchar-override.ll b/llvm/test/Transforms/LoopIdiom/wcslen-wchar-override.ll
new file mode 100644
index 0000000000000..6449182b591dd
--- /dev/null
+++ b/llvm/test/Transforms/LoopIdiom/wcslen-wchar-override.ll
@@ -0,0 +1,133 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -passes='loop(loop-idiom)' < %s -S | FileCheck %s
+
+; When wchar size is overridden as a different value than the default, don't
+; introduce calls to wcslen, to avoid ABI mismatch between wcslen and the call
+; sites.
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define i64 @valid_wcslen32(ptr %src) {
+; CHECK-LABEL: define i64 @valid_wcslen32(
+; CHECK-SAME: ptr [[SRC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq ptr [[SRC]], null
+; CHECK-NEXT:    br i1 [[CMP]], label %[[RETURN:.*]], label %[[LOR_LHS_FALSE:.*]]
+; CHECK:       [[LOR_LHS_FALSE]]:
+; CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[SRC]], align 4
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp eq i32 [[TMP0]], 0
+; CHECK-NEXT:    br i1 [[CMP1]], label %[[RETURN]], label %[[WHILE_COND_PREHEADER:.*]]
+; CHECK:       [[WHILE_COND_PREHEADER]]:
+; CHECK-NEXT:    br label %[[WHILE_COND:.*]]
+; CHECK:       [[WHILE_COND]]:
+; CHECK-NEXT:    [[SRC_PN:%.*]] = phi ptr [ [[CURR_0:%.*]], %[[WHILE_COND]] ], [ [[SRC]], %[[WHILE_COND_PREHEADER]] ]
+; CHECK-NEXT:    [[CURR_0]] = getelementptr inbounds i8, ptr [[SRC_PN]], i64 4
+; CHECK-NEXT:    [[TMP3:%.*]] = load i32, ptr [[CURR_0]], align 4
+; CHECK-NEXT:    [[TOBOOL_NOT:%.*]] = icmp eq i32 [[TMP3]], 0
+; CHECK-NEXT:    br i1 [[TOBOOL_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK:       [[WHILE_END]]:
+; CHECK-NEXT:    [[END:%.*]] = phi ptr [ [[CURR_0]], %[[WHILE_COND]] ]
+; CHECK-NEXT:    [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[END]] to i64
+; CHECK-NEXT:    [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[SRC]] to i64
+; CHECK-NEXT:    [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT:    [[SUB_PTR_DIV:%.*]] = ashr exact i64 [[SUB_PTR_SUB]], 2
+; CHECK-NEXT:    br label %[[RETURN]]
+; CHECK:       [[RETURN]]:
+; CHECK-NEXT:    [[RETVAL_0:%.*]] = phi i64 [ [[SUB_PTR_DIV]], %[[WHILE_END]] ], [ 0, %[[LOR_LHS_FALSE]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT:    ret i64 [[RETVAL_0]]
+;
+entry:
+  %cmp = icmp eq ptr %src, null
+  br i1 %cmp, label %return, label %lor.lhs.false
+
+lor.lhs.false:                                    ; preds = %entry
+  %0 = load i32, ptr %src, align 4
+  %cmp1 = icmp eq i32 %0, 0
+  br i1 %cmp1, label %return, label %while.cond.preheader
+
+while.cond.preheader:                             ; preds = %lor.lhs.false
+  br label %while.cond
+
+while.cond:                                       ; preds = %while.cond.preheader, %while.cond
+  %src.pn = phi ptr [ %curr.0, %while.cond ], [ %src, %while.cond.preheader ]
+  %curr.0 = getelementptr inbounds i8, ptr %src.pn, i64 4
+  %1 = load i32, ptr %curr.0, align 4
+  %tobool.not = icmp eq i32 %1, 0
+  br i1 %tobool.not, label %while.end, label %while.cond
+
+while.end:                                        ; preds = %while.cond
+  %curr.0.lcssa = phi ptr [ %curr.0, %while.cond ]
+  %sub.ptr.lhs.cast = ptrtoint ptr %curr.0.lcssa to i64
+  %sub.ptr.rhs.cast = ptrtoint ptr %src to i64
+  %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+  %sub.ptr.div = ashr exact i64 %sub.ptr.sub, 2
+  br label %return
+
+return:                                           ; preds = %entry, %lor.lhs.false, %while.end
+  %retval.0 = phi i64 [ %sub.ptr.div, %while.end ], [ 0, %lor.lhs.false ], [ 0, %entry ]
+  ret i64 %retval.0
+}
+
+define i64 @invalid_char_size(ptr %src) {
+; CHECK-LABEL: define i64 @invalid_char_size(
+; CHECK-SAME: ptr [[SRC:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq ptr [[SRC]], null
+; CHECK-NEXT:    br i1 [[CMP]], label %[[RETURN:.*]], label %[[LOR_LHS_FALSE:.*]]
+; CHECK:       [[LOR_LHS_FALSE]]:
+; CHECK-NEXT:    [[TMP0:%.*]] = load i16, ptr [[SRC]], align 2
+; CHECK-NEXT:    [[CMP1:%.*]] = icmp eq i16 [[TMP0]], 0
+; CHECK-NEXT:    br i1 [[CMP1]], label %[[RETURN]], label %[[WHILE_COND_PREHEADER:.*]]
+; CHECK:       [[WHILE_COND_PREHEADER]]:
+; CHECK-NEXT:    br label %[[WHILE_COND:.*]]
+; CHECK:       [[WHILE_COND]]:
+; CHECK-NEXT:    [[SRC_PN:%.*]] = phi ptr [ [[CURR_0:%.*]], %[[WHILE_COND]] ], [ [[SRC]], %[[WHILE_COND_PREHEADER]] ]
+; CHECK-NEXT:    [[CURR_0]] = getelementptr inbounds i8, ptr [[SRC_PN]], i64 2
+; CHECK-NEXT:    [[TMP1:%.*]] = load i16, ptr [[CURR_0]], align 2
+; CHECK-NEXT:    [[TOBOOL_NOT:%.*]] = icmp eq i16 [[TMP1]], 0
+; CHECK-NEXT:    br i1 [[TOBOOL_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK:       [[WHILE_END]]:
+; CHECK-NEXT:    [[CURR_0_LCSSA:%.*]] = phi ptr [ [[CURR_0]], %[[WHILE_COND]] ]
+; CHECK-NEXT:    [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[CURR_0_LCSSA]] to i64
+; CHECK-NEXT:    [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[SRC]] to i64
+; CHECK-NEXT:    [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
+; CHECK-NEXT:    [[SUB_PTR_DIV:%.*]] = ashr exact i64 [[SUB_PTR_SUB]], 1
+; CHECK-NEXT:    br label %[[RETURN]]
+; CHECK:       [[RETURN]]:
+; CHECK-NEXT:    [[RETVAL_0:%.*]] = phi i64 [ [[SUB_PTR_DIV]], %[[WHILE_END]] ], [ 0, %[[LOR_LHS_FALSE]] ], [ 0, %[[ENTRY]] ]
+; CHECK-NEXT:    ret i64 [[RETVAL_0]]
+;
+entry:
+  %cmp = icmp eq ptr %src, null
+  br i1 %cmp, label %return, label %lor.lhs.false
+
+lor.lhs.false:                                    ; preds = %entry
+  %0 = load i16, ptr %src, align 2
+  %cmp1 = icmp eq i16 %0, 0
+  br i1 %cmp1, label %return, label %while.cond.preheader
+
+while.cond.preheader:                             ; preds = %lor.lhs.false
+  br label %while.cond
+
+while.cond:                                       ; preds = %while.cond.preheader, %while.cond
+  %src.pn = phi ptr [ %curr.0, %while.cond ], [ %src, %while.cond.preheader ]
+  %curr.0 = getelementptr inbounds i8, ptr %src.pn, i64 2
+  %1 = load i16, ptr %curr.0, align 2
+  %tobool.not = icmp eq i16 %1, 0
+  br i1 %tobool.not, label %while.end, label %while.cond
+
+while.end:                                        ; preds = %while.cond
+  %curr.0.lcssa = phi ptr [ %curr.0, %while.cond ]
+  %sub.ptr.lhs.cast = ptrtoint ptr %curr.0.lcssa to i64
+  %sub.ptr.rhs.cast = ptrtoint ptr %src to i64
+  %sub.ptr.sub = sub i64 %sub.ptr.lhs.cast, %sub.ptr.rhs.cast
+  %sub.ptr.div = ashr exact i64 %sub.ptr.sub, 1
+  br label %return
+
+return:                                           ; preds = %entry, %lor.lhs.false, %while.end
+  %retval.0 = phi i64 [ %sub.ptr.div, %while.end ], [ 0, %lor.lhs.false ], [ 0, %entry ]
+  ret i64 %retval.0
+}
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"wchar_size", i32 2}
diff --git a/llvm/test/Transforms/LoopIdiom/wcslen16.ll b/llvm/test/Transforms/LoopIdiom/wcslen16.ll
index cd8a31f4af3dd..13161b44ccde4 100644
--- a/llvm/test/Transforms/LoopIdiom/wcslen16.ll
+++ b/llvm/test/Transforms/LoopIdiom/wcslen16.ll
@@ -2,7 +2,7 @@
 ; RUN: opt -passes='loop(loop-idiom)' < %s -S | FileCheck %s
 
 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
-target triple = "x86_64-unknown-linux-gnu"
+target triple = "i386-unknown-linux-gnu"
 
 define i64 @valid_strlen16(ptr %src) {
 ; CHECK-LABEL: define i64 @valid_strlen16(
@@ -15,19 +15,15 @@ define i64 @valid_strlen16(ptr %src) {
 ; CHECK-NEXT:    [[CMP1:%.*]] = icmp eq i16 [[TMP0]], 0
 ; CHECK-NEXT:    br i1 [[CMP1]], label %[[RETURN]], label %[[WHILE_COND_PREHEADER:.*]]
 ; CHECK:       [[WHILE_COND_PREHEADER]]:
-; CHECK-NEXT:    [[NEWGEP:%.*]] = getelementptr i8, ptr [[SRC]], i64 2
-; CHECK-NEXT:    [[WCSLEN:%.*]] = call i64 @wcslen(ptr [[NEWGEP]])
-; CHECK-NEXT:    [[TMP1:%.*]] = shl i64 [[WCSLEN]], 1
-; CHECK-NEXT:    [[TMP2:%.*]] = add i64 [[TMP1]], 2
-; CHECK-NEXT:    [[END:%.*]] = getelementptr i8, ptr [[SRC]], i64 [[TMP2]]
 ; CHECK-NEXT:    br label %[[WHILE_COND:.*]]
 ; CHECK:       [[WHILE_COND]]:
 ; CHECK-NEXT:    [[SRC_PN:%.*]] = phi ptr [ [[CURR_0:%.*]], %[[WHILE_COND]] ], [ [[SRC]], %[[WHILE_COND_PREHEADER]] ]
 ; CHECK-NEXT:    [[CURR_0]] = getelementptr inbounds i8, ptr [[SRC_PN]], i64 2
 ; CHECK-NEXT:    [[TMP3:%.*]] = load i16, ptr [[CURR_0]], align 2
 ; CHECK-NEXT:    [[TOBOOL_NOT:%.*]] = icmp eq i16 [[TMP3]], 0
-; CHECK-NEXT:    br i1 true, label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK-NEXT:    br i1 [[TOBOOL_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
 ; CHECK:       [[WHILE_END]]:
+; CHECK-NEXT:    [[END:%.*]] = phi ptr [ [[CURR_0]], %[[WHILE_COND]] ]
 ; CHECK-NEXT:    [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[END]] to i64
 ; CHECK-NEXT:    [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[SRC]] to i64
 ; CHECK-NEXT:    [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
@@ -76,15 +72,19 @@ define i64 @invalid_char_size(ptr %src) {
 ; CHECK-NEXT:    [[CMP1:%.*]] = icmp eq i32 [[TMP0]], 0
 ; CHECK-NEXT:    br i1 [[CMP1]], label %[[RETURN]], label %[[WHILE_COND_PREHEADER:.*]]
 ; CHECK:       [[WHILE_COND_PREHEADER]]:
+; CHECK-NEXT:    [[SCEVGEP:%.*]] = getelementptr i8, ptr [[SRC]], i64 4
+; CHECK-NEXT:    [[WCSLEN:%.*]] = call i64 @wcslen(ptr [[SCEVGEP]])
+; CHECK-NEXT:    [[TMP3:%.*]] = shl i64 [[WCSLEN]], 2
+; CHECK-NEXT:    [[TMP2:%.*]] = add i64 [[TMP3]], 4
+; CHECK-NEXT:    [[CURR_0_LCSSA:%.*]] = getelementptr i8, ptr [[SRC]], i64 [[TMP2]]
 ; CHECK-NEXT:    br label %[[WHILE_COND:.*]]
 ; CHECK:       [[WHILE_COND]]:
 ; CHECK-NEXT:    [[SRC_PN:%.*]] = phi ptr [ [[CURR_0:%.*]], %[[WHILE_COND]] ], [ [[SRC]], %[[WHILE_COND_PREHEADER]] ]
 ; CHECK-NEXT:    [[CURR_0]] = getelementptr inbounds i8, ptr [[SRC_PN]], i64 4
 ; CHECK-NEXT:    [[TMP1:%.*]] = load i32, ptr [[CURR_0]], align 4
 ; CHECK-NEXT:    [[TOBOOL_NOT:%.*]] = icmp eq i32 [[TMP1]], 0
-; CHECK-NEXT:    br i1 [[TOBOOL_NOT]], label %[[WHILE_END:.*]], label %[[WHILE_COND]]
+; CHECK-NEXT:    br i1 true, label %[[WHILE_END:.*]], label %[[WHILE_COND]]
 ; CHECK:       [[WHILE_END]]:
-; CHECK-NEXT:    [[CURR_0_LCSSA:%.*]] = phi ptr [ [[CURR_0]], %[[WHILE_COND]] ]
 ; CHECK-NEXT:    [[SUB_PTR_LHS_CAST:%.*]] = ptrtoint ptr [[CURR_0_LCSSA]] to i64
 ; CHECK-NEXT:    [[SUB_PTR_RHS_CAST:%.*]] = ptrtoint ptr [[SRC]] to i64
 ; CHECK-NEXT:    [[SUB_PTR_SUB:%.*]] = sub i64 [[SUB_PTR_LHS_CAST]], [[SUB_PTR_RHS_CAST]]
@@ -121,6 +121,3 @@ return:                                           ; preds = %entry, %lor.lhs.fal
   %retval.0 = phi i64 [ %sub.ptr.div, %while.end ], [ 0, %lor.lhs.false ], [ 0, %entry ]
   ret i64 %retval.0
 }
-!llvm.module.flags = !{!0}
-!0 = !{i32 1, !"wchar_size", i32 2}
-
diff --git a/llvm/test/Transforms/LoopIdiom/wcslen32.ll b/llvm/test/Transforms/LoopIdiom/wcslen32.ll
index f1cddf9424ffd..851f988718fe7 100644
--- a/llvm/test/Transforms/LoopIdiom/wcslen32.ll
+++ b/llvm/test/Transforms/LoopIdiom/wcslen32.ll
@@ -131,4 +131,3 @@ return:                                           ; preds = %entry, %lor.lhs.fal
 }
 !llvm.module.flags = !{!0}
 !0 = !{i32 1, !"wchar_size", i32 4}
-

``````````

</details>


https://github.com/llvm/llvm-project/pull/213629


More information about the llvm-commits mailing list