https://github.com/vitalybuka updated 
https://github.com/llvm/llvm-project/pull/234081

>From 37cdcd48c1f20e6011e8aada7cd31a296b6bced7 Mon Sep 17 00:00:00 2001
From: Vitaly Buka <[email protected]>
Date: Tue, 20 Oct 2026 22:20:37 -0700
Subject: [PATCH 1/7] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20in?=
 =?UTF-8?q?itial=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Created using spr 2.0.0-beta.1
---
 clang/lib/CodeGen/CGExpr.cpp                  |  2 ++
 clang/lib/CodeGen/CodeGenFunction.cpp         |  8 ++++---
 .../CodeGen/memcpy-inline-builtin-no-extern.c | 22 ++++++++++++++-----
 3 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index f6810d84cd235f..86a8aa34318f8b 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -6723,6 +6723,8 @@ static CGCallee EmitDirectCallee(CodeGenFunction &CGF, 
GlobalDecl GD) {
         Clone = llvm::Function::Create(Fn->getFunctionType(),
                                        llvm::GlobalValue::InternalLinkage,
                                        Fn->getAddressSpace(), FDInlineName, M);
+        Clone->setCallingConv(Fn->getCallingConv());
+        Clone->setAttributes(Fn->getAttributes());
         Clone->addFnAttr(llvm::Attribute::AlwaysInline);
       }
       return CGCallee::forDirect(Clone, GD);
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp 
b/clang/lib/CodeGen/CodeGenFunction.cpp
index e5bca359e6a855..51588a7ebc5567 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -1501,12 +1501,14 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, 
llvm::Function *Fn,
     std::string FDInlineName = (Fn->getName() + ".inline").str();
     llvm::Module *M = Fn->getParent();
     llvm::Function *Clone = M->getFunction(FDInlineName);
-    if (!Clone) {
+    if (!Clone)
       Clone = llvm::Function::Create(Fn->getFunctionType(),
                                      llvm::GlobalValue::InternalLinkage,
                                      Fn->getAddressSpace(), FDInlineName, M);
-      Clone->addFnAttr(llvm::Attribute::AlwaysInline);
-    }
+    Clone->setCallingConv(Fn->getCallingConv());
+    Clone->setAttributes(Fn->getAttributes());
+    CGM.SetLLVMFunctionAttributesForDefinition(FD, Clone);
+    Clone->addFnAttr(llvm::Attribute::AlwaysInline);
     Fn->setLinkage(llvm::GlobalValue::ExternalLinkage);
     Fn = Clone;
   } else {
diff --git a/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c 
b/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
index f6edbbf6aeb44f..72da4803edf46b 100644
--- a/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
+++ b/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
@@ -1,6 +1,7 @@
-// RUN: %clang_cc1 -triple x86_64 -emit-llvm -disable-llvm-passes -o - %s | 
FileCheck %s
+// RUN: %clang_cc1 -triple x86_64 -target-cpu x86-64 -tune-cpu generic 
-funwind-tables=2 -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
 //
-// Verifies that clang-generated *.inline are flagged as internal.
+// Verifies that clang-generated *.inline are flagged as internal and preserve
+// function, return, and parameter attributes.
 
 typedef unsigned long size_t;
 
@@ -10,20 +11,31 @@ AVAILABLE_EXTERNALLY int memcmp(const void *a, const void 
*b, size_t c) {
   return __builtin_memcmp(a, b, c);
 }
 
-// CHECK: internal{{.*}}memcmp.inline
+// CHECK-LABEL: define dso_local i32 @bar(
+// CHECK:         call i32 @memcmp.inline(ptr noundef %{{.*}}, ptr noundef 
%{{.*}}, i64 noundef %{{.*}})
 int bar(const void *a, const void *b, size_t c) {
   return memcmp(a, b, c);
 }
 
+// CHECK: declare i32 @memcmp(ptr noundef, ptr noundef, i64 noundef) 
#[[ATTR_DECL:[0-9]+]]
+// CHECK: define internal i32 @memcmp.inline(ptr noundef %a, ptr noundef %b, 
i64 noundef %c) #[[ATTR_INLINE:[0-9]+]]
+
 // Note that extern has been omitted here.
 #define TRIPLE_INLINE inline __attribute__((always_inline)) 
__attribute__((gnu_inline))
 
 // Clang recognizes an inline builtin and renames it to memcpy.inline to 
prevent conflict with builtins.
-TRIPLE_INLINE void *memcpy(void *a, const void *b, size_t c) {
+TRIPLE_INLINE __attribute__((returns_nonnull)) void *memcpy(void *a, const 
void *b, size_t c) {
   return __builtin_memcpy(a, b, c);
 }
 
-// CHECK: internal{{.*}}memcpy.inline
+// CHECK: declare nonnull ptr @memcpy(ptr noundef, ptr noundef, i64 noundef) 
#[[ATTR_DECL]]
+// CHECK: define internal nonnull ptr @memcpy.inline(ptr noundef %a, ptr 
noundef %b, i64 noundef %c) #[[ATTR_INLINE]]
+
+// CHECK-LABEL: define dso_local ptr @foo(
+// CHECK:         call nonnull ptr @memcpy.inline(ptr noundef %{{.*}}, ptr 
noundef %{{.*}}, i64 noundef %{{.*}})
 void *foo(void *a, const void *b, size_t c) {
   return memcpy(a, b, c);
 }
+
+// CHECK: attributes #[[ATTR_DECL]] = { alwaysinline nobuiltin nounwind 
uwtable {{.*}}"target-cpu"="x86-64" 
"target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK: attributes #[[ATTR_INLINE]] = { alwaysinline nobuiltin nounwind 
uwtable "min-legal-vector-width"="0" {{.*}}"target-cpu"="x86-64" 
"target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

>From 3b83f1158db8a650f1dd038284fada1eca0f3ab8 Mon Sep 17 00:00:00 2001
From: Vitaly Buka <[email protected]>
Date: Tue, 20 Oct 2026 22:37:41 -0700
Subject: [PATCH 2/7] Update test using update_cc_test_checks.py

Created using spr 2.0.0-beta.1
---
 .../CodeGen/memcpy-inline-builtin-no-extern.c | 89 +++++++++++++++----
 1 file changed, 71 insertions(+), 18 deletions(-)

diff --git a/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c 
b/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
index 72da4803edf46b..937f485a56118d 100644
--- a/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
+++ b/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
@@ -1,7 +1,6 @@
-// RUN: %clang_cc1 -triple x86_64 -target-cpu x86-64 -tune-cpu generic 
-funwind-tables=2 -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
-//
-// Verifies that clang-generated *.inline are flagged as internal and preserve
-// function, return, and parameter attributes.
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --check-attributes --include-generated-funcs --version 6
+// RUN: %clang_cc1 -triple x86_64 -emit-llvm -disable-llvm-passes -o - %s | 
FileCheck %s
+// Verifies that clang-generated *.inline are flagged as internal.
 
 typedef unsigned long size_t;
 
@@ -11,31 +10,85 @@ AVAILABLE_EXTERNALLY int memcmp(const void *a, const void 
*b, size_t c) {
   return __builtin_memcmp(a, b, c);
 }
 
-// CHECK-LABEL: define dso_local i32 @bar(
-// CHECK:         call i32 @memcmp.inline(ptr noundef %{{.*}}, ptr noundef 
%{{.*}}, i64 noundef %{{.*}})
 int bar(const void *a, const void *b, size_t c) {
   return memcmp(a, b, c);
 }
 
-// CHECK: declare i32 @memcmp(ptr noundef, ptr noundef, i64 noundef) 
#[[ATTR_DECL:[0-9]+]]
-// CHECK: define internal i32 @memcmp.inline(ptr noundef %a, ptr noundef %b, 
i64 noundef %c) #[[ATTR_INLINE:[0-9]+]]
-
 // Note that extern has been omitted here.
 #define TRIPLE_INLINE inline __attribute__((always_inline)) 
__attribute__((gnu_inline))
 
 // Clang recognizes an inline builtin and renames it to memcpy.inline to 
prevent conflict with builtins.
-TRIPLE_INLINE __attribute__((returns_nonnull)) void *memcpy(void *a, const 
void *b, size_t c) {
+TRIPLE_INLINE void *memcpy(void *a, const void *b, size_t c) {
   return __builtin_memcpy(a, b, c);
 }
 
-// CHECK: declare nonnull ptr @memcpy(ptr noundef, ptr noundef, i64 noundef) 
#[[ATTR_DECL]]
-// CHECK: define internal nonnull ptr @memcpy.inline(ptr noundef %a, ptr 
noundef %b, i64 noundef %c) #[[ATTR_INLINE]]
-
-// CHECK-LABEL: define dso_local ptr @foo(
-// CHECK:         call nonnull ptr @memcpy.inline(ptr noundef %{{.*}}, ptr 
noundef %{{.*}}, i64 noundef %{{.*}})
 void *foo(void *a, const void *b, size_t c) {
   return memcpy(a, b, c);
 }
-
-// CHECK: attributes #[[ATTR_DECL]] = { alwaysinline nobuiltin nounwind 
uwtable {{.*}}"target-cpu"="x86-64" 
"target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
-// CHECK: attributes #[[ATTR_INLINE]] = { alwaysinline nobuiltin nounwind 
uwtable "min-legal-vector-width"="0" {{.*}}"target-cpu"="x86-64" 
"target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
+// CHECK: Function Attrs: noinline nounwind optnone
+// CHECK-LABEL: define dso_local i32 @bar(
+// CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[CALL:%.*]] = call i32 @memcmp.inline(ptr noundef [[TMP0]], 
ptr noundef [[TMP1]], i64 noundef [[TMP2]])
+// CHECK-NEXT:    ret i32 [[CALL]]
+//
+//
+// CHECK: Function Attrs: alwaysinline nobuiltin nounwind
+// CHECK-LABEL: define internal i32 @memcmp.inline(
+// CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR2:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[CALL:%.*]] = call i32 @memcmp(ptr noundef [[TMP0]], ptr 
noundef [[TMP1]], i64 noundef [[TMP2]]) #[[ATTR4:[0-9]+]]
+// CHECK-NEXT:    ret i32 [[CALL]]
+//
+//
+// CHECK: Function Attrs: alwaysinline nobuiltin nounwind
+// CHECK-LABEL: define internal ptr @memcpy.inline(
+// CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR2]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[TMP0]], ptr 
align 1 [[TMP1]], i64 [[TMP2]], i1 false)
+// CHECK-NEXT:    ret ptr [[TMP0]]
+//
+//
+// CHECK: Function Attrs: noinline nounwind optnone
+// CHECK-LABEL: define dso_local ptr @foo(
+// CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[CALL:%.*]] = call ptr @memcpy.inline(ptr noundef [[TMP0]], 
ptr noundef [[TMP1]], i64 noundef [[TMP2]]) #[[ATTR4]]
+// CHECK-NEXT:    ret ptr [[CALL]]
+//

>From 41553adec6a7ad57ac9b6538938fb008d616b2e4 Mon Sep 17 00:00:00 2001
From: Vitaly Buka <[email protected]>
Date: Tue, 20 Oct 2026 22:42:59 -0700
Subject: [PATCH 3/7] Rebase onto test regeneration

Created using spr 2.0.0-beta.1
---
 .../CodeGen/memcpy-inline-builtin-no-extern.c | 71 ++++++++++++++++++-
 1 file changed, 68 insertions(+), 3 deletions(-)

diff --git a/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c 
b/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
index f6edbbf6aeb44f..7ce4e16d793454 100644
--- a/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
+++ b/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
@@ -1,5 +1,5 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --check-attributes --include-generated-funcs --version 6
 // RUN: %clang_cc1 -triple x86_64 -emit-llvm -disable-llvm-passes -o - %s | 
FileCheck %s
-//
 // Verifies that clang-generated *.inline are flagged as internal.
 
 typedef unsigned long size_t;
@@ -10,7 +10,6 @@ AVAILABLE_EXTERNALLY int memcmp(const void *a, const void *b, 
size_t c) {
   return __builtin_memcmp(a, b, c);
 }
 
-// CHECK: internal{{.*}}memcmp.inline
 int bar(const void *a, const void *b, size_t c) {
   return memcmp(a, b, c);
 }
@@ -23,7 +22,73 @@ TRIPLE_INLINE void *memcpy(void *a, const void *b, size_t c) 
{
   return __builtin_memcpy(a, b, c);
 }
 
-// CHECK: internal{{.*}}memcpy.inline
 void *foo(void *a, const void *b, size_t c) {
   return memcpy(a, b, c);
 }
+// CHECK: Function Attrs: noinline nounwind optnone
+// CHECK-LABEL: define dso_local i32 @bar(
+// CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[CALL:%.*]] = call i32 @memcmp.inline(ptr noundef [[TMP0]], 
ptr noundef [[TMP1]], i64 noundef [[TMP2]])
+// CHECK-NEXT:    ret i32 [[CALL]]
+//
+//
+// CHECK: Function Attrs: alwaysinline nounwind
+// CHECK-LABEL: define internal i32 @memcmp.inline(
+// CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i64 [[C:%.*]]) #[[ATTR2:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[CALL:%.*]] = call i32 @memcmp(ptr noundef [[TMP0]], ptr 
noundef [[TMP1]], i64 noundef [[TMP2]]) #[[ATTR4:[0-9]+]]
+// CHECK-NEXT:    ret i32 [[CALL]]
+//
+//
+// CHECK: Function Attrs: alwaysinline nounwind
+// CHECK-LABEL: define internal ptr @memcpy.inline(
+// CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i64 [[C:%.*]]) #[[ATTR2]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[TMP0]], ptr 
align 1 [[TMP1]], i64 [[TMP2]], i1 false)
+// CHECK-NEXT:    ret ptr [[TMP0]]
+//
+//
+// CHECK: Function Attrs: noinline nounwind optnone
+// CHECK-LABEL: define dso_local ptr @foo(
+// CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
+// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
+// CHECK-NEXT:    [[CALL:%.*]] = call ptr @memcpy.inline(ptr noundef [[TMP0]], 
ptr noundef [[TMP1]], i64 noundef [[TMP2]]) #[[ATTR4]]
+// CHECK-NEXT:    ret ptr [[CALL]]
+//

>From b94d59748f0f559f1c3567f25dacec6b6cc2cabd Mon Sep 17 00:00:00 2001
From: Vitaly Buka <[email protected]>
Date: Tue, 20 Oct 2026 22:55:26 -0700
Subject: [PATCH 4/7] Split calling convention fix into separate PR

Created using spr 2.0.0-beta.1
---
 .../CodeGen/memcpy-inline-builtin-no-extern.c | 29 +++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c 
b/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
index 7ce4e16d793454..a373346f5fcee7 100644
--- a/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
+++ b/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
@@ -25,6 +25,14 @@ TRIPLE_INLINE void *memcpy(void *a, const void *b, size_t c) 
{
 void *foo(void *a, const void *b, size_t c) {
   return memcpy(a, b, c);
 }
+
+AVAILABLE_EXTERNALLY __attribute__((preserve_most)) int umask(int m) {
+  return m;
+}
+
+int call_umask(int m) {
+  return umask(m);
+}
 // CHECK: Function Attrs: noinline nounwind optnone
 // CHECK-LABEL: define dso_local i32 @bar(
 // CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR0:[0-9]+]] {
@@ -92,3 +100,24 @@ void *foo(void *a, const void *b, size_t c) {
 // CHECK-NEXT:    [[CALL:%.*]] = call ptr @memcpy.inline(ptr noundef [[TMP0]], 
ptr noundef [[TMP1]], i64 noundef [[TMP2]]) #[[ATTR4]]
 // CHECK-NEXT:    ret ptr [[CALL]]
 //
+//
+// CHECK: Function Attrs: noinline nounwind optnone
+// CHECK-LABEL: define dso_local i32 @call_umask(
+// CHECK-SAME: i32 noundef [[M:%.*]]) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[M_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT:    store i32 [[M]], ptr [[M_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[M_ADDR]], align 4
+// CHECK-NEXT:    [[CALL:%.*]] = call preserve_mostcc i32 @umask.inline(i32 
noundef [[TMP0]]) #[[ATTR4]]
+// CHECK-NEXT:    ret i32 [[CALL]]
+//
+//
+// CHECK: Function Attrs: alwaysinline nounwind
+// CHECK-LABEL: define internal i32 @umask.inline(
+// CHECK-SAME: i32 [[M:%.*]]) #[[ATTR2]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[M_ADDR:%.*]] = alloca i32, align 4
+// CHECK-NEXT:    store i32 [[M]], ptr [[M_ADDR]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[M_ADDR]], align 4
+// CHECK-NEXT:    ret i32 [[TMP0]]
+//

>From 55e650809abb5b99bb0c1da850391f3eb469085a Mon Sep 17 00:00:00 2001
From: Vitaly Buka <[email protected]>
Date: Wed, 21 Oct 2026 15:41:47 -0700
Subject: [PATCH 5/7] split and address review comments

Created using spr 2.0.0-beta.1
---
 clang/test/CodeGen/memcmp-inline-builtin.c    |  24 ++++
 .../CodeGen/memcpy-inline-builtin-no-extern.c | 112 ++----------------
 clang/test/CodeGen/strlen-inline-builtin.c    |  25 ++++
 clang/test/CodeGen/umask-inline-builtin.c     |  20 ++++
 4 files changed, 76 insertions(+), 105 deletions(-)
 create mode 100644 clang/test/CodeGen/memcmp-inline-builtin.c
 create mode 100644 clang/test/CodeGen/strlen-inline-builtin.c
 create mode 100644 clang/test/CodeGen/umask-inline-builtin.c

diff --git a/clang/test/CodeGen/memcmp-inline-builtin.c 
b/clang/test/CodeGen/memcmp-inline-builtin.c
new file mode 100644
index 00000000000000..c018ce2ccceae6
--- /dev/null
+++ b/clang/test/CodeGen/memcmp-inline-builtin.c
@@ -0,0 +1,24 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --check-attributes --filter "call " --include-generated-funcs 
--version 6
+// RUN: %clang_cc1 -triple x86_64 -emit-llvm -disable-llvm-passes -o - %s | 
FileCheck %s
+
+typedef unsigned long size_t;
+
+extern inline __attribute__((always_inline)) __attribute__((gnu_inline))
+int memcmp(const void *a, const void *b, size_t c) {
+  return __builtin_memcmp(a, b, c);
+}
+
+int bar(const void *a, const void *b, size_t c) {
+  return memcmp(a, b, c);
+}
+// CHECK: Function Attrs: noinline nounwind optnone
+// CHECK-LABEL: define dso_local i32 @bar(
+// CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK:    [[CALL:%.*]] = call i32 @memcmp.inline(ptr noundef [[TMP0:%.*]], 
ptr noundef [[TMP1:%.*]], i64 noundef [[TMP2:%.*]])
+//
+//
+// CHECK: Function Attrs: alwaysinline nounwind
+// CHECK-LABEL: define internal i32 @memcmp.inline(
+// CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i64 [[C:%.*]]) #[[ATTR2:[0-9]+]] {
+// CHECK:    [[CALL:%.*]] = call i32 @memcmp(ptr noundef [[TMP0:%.*]], ptr 
noundef [[TMP1:%.*]], i64 noundef [[TMP2:%.*]]) #[[ATTR3:[0-9]+]]
+//
diff --git a/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c 
b/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
index a373346f5fcee7..4cb0b5f06309c5 100644
--- a/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
+++ b/clang/test/CodeGen/memcpy-inline-builtin-no-extern.c
@@ -1,123 +1,25 @@
-// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --check-attributes --include-generated-funcs --version 6
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --check-attributes --filter "call " --include-generated-funcs 
--version 6
 // RUN: %clang_cc1 -triple x86_64 -emit-llvm -disable-llvm-passes -o - %s | 
FileCheck %s
-// Verifies that clang-generated *.inline are flagged as internal.
 
 typedef unsigned long size_t;
 
-#define AVAILABLE_EXTERNALLY extern inline __attribute__((always_inline)) 
__attribute__((gnu_inline))
-// Clang recognizes an inline builtin and renames it to memcmp.inline to 
prevent conflict with builtins.
-AVAILABLE_EXTERNALLY int memcmp(const void *a, const void *b, size_t c) {
-  return __builtin_memcmp(a, b, c);
-}
-
-int bar(const void *a, const void *b, size_t c) {
-  return memcmp(a, b, c);
-}
-
 // Note that extern has been omitted here.
-#define TRIPLE_INLINE inline __attribute__((always_inline)) 
__attribute__((gnu_inline))
-
-// Clang recognizes an inline builtin and renames it to memcpy.inline to 
prevent conflict with builtins.
-TRIPLE_INLINE void *memcpy(void *a, const void *b, size_t c) {
+inline __attribute__((always_inline)) __attribute__((gnu_inline))
+void *memcpy(void *a, const void *b, size_t c) {
   return __builtin_memcpy(a, b, c);
 }
 
 void *foo(void *a, const void *b, size_t c) {
   return memcpy(a, b, c);
 }
-
-AVAILABLE_EXTERNALLY __attribute__((preserve_most)) int umask(int m) {
-  return m;
-}
-
-int call_umask(int m) {
-  return umask(m);
-}
-// CHECK: Function Attrs: noinline nounwind optnone
-// CHECK-LABEL: define dso_local i32 @bar(
-// CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR0:[0-9]+]] {
-// CHECK-NEXT:  [[ENTRY:.*:]]
-// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
-// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
-// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
-// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
-// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
-// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
-// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
-// CHECK-NEXT:    [[CALL:%.*]] = call i32 @memcmp.inline(ptr noundef [[TMP0]], 
ptr noundef [[TMP1]], i64 noundef [[TMP2]])
-// CHECK-NEXT:    ret i32 [[CALL]]
-//
-//
-// CHECK: Function Attrs: alwaysinline nounwind
-// CHECK-LABEL: define internal i32 @memcmp.inline(
-// CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i64 [[C:%.*]]) #[[ATTR2:[0-9]+]] {
-// CHECK-NEXT:  [[ENTRY:.*:]]
-// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
-// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
-// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
-// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
-// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
-// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
-// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
-// CHECK-NEXT:    [[CALL:%.*]] = call i32 @memcmp(ptr noundef [[TMP0]], ptr 
noundef [[TMP1]], i64 noundef [[TMP2]]) #[[ATTR4:[0-9]+]]
-// CHECK-NEXT:    ret i32 [[CALL]]
-//
-//
 // CHECK: Function Attrs: alwaysinline nounwind
 // CHECK-LABEL: define internal ptr @memcpy.inline(
-// CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i64 [[C:%.*]]) #[[ATTR2]] {
-// CHECK-NEXT:  [[ENTRY:.*:]]
-// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
-// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
-// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
-// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
-// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
-// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
-// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
-// CHECK-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[TMP0]], ptr 
align 1 [[TMP1]], i64 [[TMP2]], i1 false)
-// CHECK-NEXT:    ret ptr [[TMP0]]
+// CHECK-SAME: ptr [[A:%.*]], ptr [[B:%.*]], i64 [[C:%.*]]) #[[ATTR1:[0-9]+]] {
+// CHECK:    call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[TMP0:%.*]], ptr 
align 1 [[TMP1:%.*]], i64 [[TMP2:%.*]], i1 false)
 //
 //
 // CHECK: Function Attrs: noinline nounwind optnone
 // CHECK-LABEL: define dso_local ptr @foo(
-// CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR0]] {
-// CHECK-NEXT:  [[ENTRY:.*:]]
-// CHECK-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT:    [[B_ADDR:%.*]] = alloca ptr, align 8
-// CHECK-NEXT:    [[C_ADDR:%.*]] = alloca i64, align 8
-// CHECK-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
-// CHECK-NEXT:    store ptr [[B]], ptr [[B_ADDR]], align 8
-// CHECK-NEXT:    store i64 [[C]], ptr [[C_ADDR]], align 8
-// CHECK-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
-// CHECK-NEXT:    [[TMP1:%.*]] = load ptr, ptr [[B_ADDR]], align 8
-// CHECK-NEXT:    [[TMP2:%.*]] = load i64, ptr [[C_ADDR]], align 8
-// CHECK-NEXT:    [[CALL:%.*]] = call ptr @memcpy.inline(ptr noundef [[TMP0]], 
ptr noundef [[TMP1]], i64 noundef [[TMP2]]) #[[ATTR4]]
-// CHECK-NEXT:    ret ptr [[CALL]]
-//
-//
-// CHECK: Function Attrs: noinline nounwind optnone
-// CHECK-LABEL: define dso_local i32 @call_umask(
-// CHECK-SAME: i32 noundef [[M:%.*]]) #[[ATTR0]] {
-// CHECK-NEXT:  [[ENTRY:.*:]]
-// CHECK-NEXT:    [[M_ADDR:%.*]] = alloca i32, align 4
-// CHECK-NEXT:    store i32 [[M]], ptr [[M_ADDR]], align 4
-// CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[M_ADDR]], align 4
-// CHECK-NEXT:    [[CALL:%.*]] = call preserve_mostcc i32 @umask.inline(i32 
noundef [[TMP0]]) #[[ATTR4]]
-// CHECK-NEXT:    ret i32 [[CALL]]
-//
-//
-// CHECK: Function Attrs: alwaysinline nounwind
-// CHECK-LABEL: define internal i32 @umask.inline(
-// CHECK-SAME: i32 [[M:%.*]]) #[[ATTR2]] {
-// CHECK-NEXT:  [[ENTRY:.*:]]
-// CHECK-NEXT:    [[M_ADDR:%.*]] = alloca i32, align 4
-// CHECK-NEXT:    store i32 [[M]], ptr [[M_ADDR]], align 4
-// CHECK-NEXT:    [[TMP0:%.*]] = load i32, ptr [[M_ADDR]], align 4
-// CHECK-NEXT:    ret i32 [[TMP0]]
+// CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]], i64 noundef 
[[C:%.*]]) #[[ATTR3:[0-9]+]] {
+// CHECK:    [[CALL:%.*]] = call ptr @memcpy.inline(ptr noundef [[TMP0:%.*]], 
ptr noundef [[TMP1:%.*]], i64 noundef [[TMP2:%.*]]) #[[ATTR4:[0-9]+]]
 //
diff --git a/clang/test/CodeGen/strlen-inline-builtin.c 
b/clang/test/CodeGen/strlen-inline-builtin.c
new file mode 100644
index 00000000000000..f24f5adb6b490c
--- /dev/null
+++ b/clang/test/CodeGen/strlen-inline-builtin.c
@@ -0,0 +1,25 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --check-attributes --filter "call " --include-generated-funcs 
--version 6
+// RUN: %clang_cc1 -triple x86_64 -emit-llvm -disable-llvm-passes -o - %s | 
FileCheck %s
+
+typedef unsigned long size_t;
+
+size_t strlen(const char *s);
+void *strlen_ptr = strlen;
+
+extern inline __attribute__((always_inline)) __attribute__((gnu_inline))
+__attribute__((section("my_sec"))) size_t strlen(const char *s) {
+  return 0;
+}
+
+size_t call_strlen(const char *s) {
+  return strlen(s);
+}
+// CHECK: Function Attrs: noinline nounwind optnone
+// CHECK-LABEL: define dso_local i64 @call_strlen(
+// CHECK-SAME: ptr noundef [[S:%.*]]) #[[ATTR1:[0-9]+]] {
+// CHECK:    [[CALL:%.*]] = call i64 @strlen.inline(ptr noundef [[TMP0:%.*]]) 
#[[ATTR3:[0-9]+]]
+//
+//
+// CHECK: Function Attrs: alwaysinline nounwind
+// CHECK-LABEL: define internal i64 @strlen.inline(
+// CHECK-SAME: ptr [[S:%.*]]) #[[ATTR2:[0-9]+]] {
diff --git a/clang/test/CodeGen/umask-inline-builtin.c 
b/clang/test/CodeGen/umask-inline-builtin.c
new file mode 100644
index 00000000000000..1b66b9dff1bdb2
--- /dev/null
+++ b/clang/test/CodeGen/umask-inline-builtin.c
@@ -0,0 +1,20 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --check-attributes --filter "call " --include-generated-funcs 
--version 6
+// RUN: %clang_cc1 -triple x86_64 -emit-llvm -disable-llvm-passes -o - %s | 
FileCheck %s
+
+extern inline __attribute__((always_inline)) __attribute__((gnu_inline))
+__attribute__((preserve_most)) int umask(int m) {
+  return m;
+}
+
+int call_umask(int m) {
+  return umask(m);
+}
+// CHECK: Function Attrs: noinline nounwind optnone
+// CHECK-LABEL: define dso_local i32 @call_umask(
+// CHECK-SAME: i32 noundef [[M:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK:    [[CALL:%.*]] = call preserve_mostcc i32 @umask.inline(i32 noundef 
[[TMP0:%.*]]) #[[ATTR3:[0-9]+]]
+//
+//
+// CHECK: Function Attrs: alwaysinline nounwind
+// CHECK-LABEL: define internal i32 @umask.inline(
+// CHECK-SAME: i32 [[M:%.*]]) #[[ATTR2:[0-9]+]] {

>From 52c768b9e4e91d19736f7d9a195b68416ad701e8 Mon Sep 17 00:00:00 2001
From: Vitaly Buka <[email protected]>
Date: Wed, 21 Oct 2026 15:42:35 -0700
Subject: [PATCH 6/7] split and address review comments

Created using spr 2.0.0-beta.1
---
 clang/lib/CodeGen/CodeGenFunction.cpp      | 35 ----------------------
 clang/lib/CodeGen/CodeGenModule.cpp        | 35 +++++++++++++++++++++-
 clang/test/CodeGen/strlen-inline-builtin.c |  2 +-
 3 files changed, 35 insertions(+), 37 deletions(-)

diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp 
b/clang/lib/CodeGen/CodeGenFunction.cpp
index e5bca359e6a855..966268214c4653 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -1494,41 +1494,6 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, 
llvm::Function *Fn,
 
   CGM.getTargetCodeGenInfo().checkFunctionABI(CGM, FD);
 
-  if (FD->isInlineBuiltinDeclaration()) {
-    // When generating code for a builtin with an inline declaration, use a
-    // mangled name to hold the actual body, while keeping an external
-    // definition in case the function pointer is referenced somewhere.
-    std::string FDInlineName = (Fn->getName() + ".inline").str();
-    llvm::Module *M = Fn->getParent();
-    llvm::Function *Clone = M->getFunction(FDInlineName);
-    if (!Clone) {
-      Clone = llvm::Function::Create(Fn->getFunctionType(),
-                                     llvm::GlobalValue::InternalLinkage,
-                                     Fn->getAddressSpace(), FDInlineName, M);
-      Clone->addFnAttr(llvm::Attribute::AlwaysInline);
-    }
-    Fn->setLinkage(llvm::GlobalValue::ExternalLinkage);
-    Fn = Clone;
-  } else {
-    // Detect the unusual situation where an inline version is shadowed by a
-    // non-inline version. In that case we should pick the external one
-    // everywhere. That's GCC behavior too. Unfortunately, I cannot find a way
-    // to detect that situation before we reach codegen, so do some late
-    // replacement.
-    for (const FunctionDecl *PD = FD->getPreviousDecl(); PD;
-         PD = PD->getPreviousDecl()) {
-      if (LLVM_UNLIKELY(PD->isInlineBuiltinDeclaration())) {
-        std::string FDInlineName = (Fn->getName() + ".inline").str();
-        llvm::Module *M = Fn->getParent();
-        if (llvm::Function *Clone = M->getFunction(FDInlineName)) {
-          Clone->replaceAllUsesWith(Fn);
-          Clone->eraseFromParent();
-        }
-        break;
-      }
-    }
-  }
-
   // Check if we should generate debug info for this function.
   if (FD->hasAttr<NoDebugAttr>()) {
     // Clear non-distinct debug info that was possibly attached to the function
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp 
b/clang/lib/CodeGen/CodeGenModule.cpp
index 372d975d62c2ea..918e5940f39a17 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7065,7 +7065,40 @@ void 
CodeGenModule::EmitGlobalFunctionDefinition(GlobalDecl GD,
   // want to propagate this information down (e.g. to local static
   // declarations).
   auto *Fn = cast<llvm::Function>(GV);
-  setFunctionLinkage(GD, Fn);
+  if (D->isInlineBuiltinDeclaration()) {
+    // When generating code for a builtin with an inline declaration, use a
+    // mangled name to hold the actual body, while keeping an external
+    // definition in case the function pointer is referenced somewhere.
+    std::string FDInlineName = (Fn->getName() + ".inline").str();
+    llvm::Function *Clone = getModule().getFunction(FDInlineName);
+    if (!Clone) {
+      Clone = llvm::Function::Create(
+          Fn->getFunctionType(), llvm::GlobalValue::InternalLinkage,
+          Fn->getAddressSpace(), FDInlineName, &getModule());
+      Clone->addFnAttr(llvm::Attribute::AlwaysInline);
+    }
+    if (!Clone->isDeclaration())
+      return;
+    Fn = Clone;
+  } else {
+    // Detect the unusual situation where an inline version is shadowed by a
+    // non-inline version. In that case we should pick the external one
+    // everywhere. That's GCC behavior too. Unfortunately, I cannot find a way
+    // to detect that situation before we reach codegen, so do some late
+    // replacement.
+    for (const FunctionDecl *PD = D->getPreviousDecl(); PD;
+         PD = PD->getPreviousDecl()) {
+      if (LLVM_UNLIKELY(PD->isInlineBuiltinDeclaration())) {
+        std::string FDInlineName = (Fn->getName() + ".inline").str();
+        if (llvm::Function *Clone = getModule().getFunction(FDInlineName)) {
+          Clone->replaceAllUsesWith(Fn);
+          Clone->eraseFromParent();
+        }
+        break;
+      }
+    }
+    setFunctionLinkage(GD, Fn);
+  }
 
   if (getTriple().isOSAIX() && D->isTargetClonesMultiVersion())
     Fn->setLinkage(llvm::GlobalValue::InternalLinkage);
diff --git a/clang/test/CodeGen/strlen-inline-builtin.c 
b/clang/test/CodeGen/strlen-inline-builtin.c
index f24f5adb6b490c..68fe5217ec5db1 100644
--- a/clang/test/CodeGen/strlen-inline-builtin.c
+++ b/clang/test/CodeGen/strlen-inline-builtin.c
@@ -22,4 +22,4 @@ size_t call_strlen(const char *s) {
 //
 // CHECK: Function Attrs: alwaysinline nounwind
 // CHECK-LABEL: define internal i64 @strlen.inline(
-// CHECK-SAME: ptr [[S:%.*]]) #[[ATTR2:[0-9]+]] {
+// CHECK-SAME: ptr [[S:%.*]]) #[[ATTR2:[0-9]+]] section "my_sec" {

>From b8715fe8cf912ef00fb3874bce31d99fd9d0ed2a Mon Sep 17 00:00:00 2001
From: Vitaly Buka <[email protected]>
Date: Wed, 21 Oct 2026 16:31:42 -0700
Subject: [PATCH 7/7] reorder stack and squash fixups

Created using spr 2.0.0-beta.1
---
 .../CodeGen/strlen-inline-builtin-dllexport.c | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)
 create mode 100644 clang/test/CodeGen/strlen-inline-builtin-dllexport.c

diff --git a/clang/test/CodeGen/strlen-inline-builtin-dllexport.c 
b/clang/test/CodeGen/strlen-inline-builtin-dllexport.c
new file mode 100644
index 00000000000000..059a43cdd16e16
--- /dev/null
+++ b/clang/test/CodeGen/strlen-inline-builtin-dllexport.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple x86_64-windows-gnu 
-DDLLEXPORT='__attribute__((dllexport))' -emit-llvm -disable-llvm-passes -o - 
%s | FileCheck %s
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix 
-mdefault-visibility-export-mapping=all -DDLLEXPORT= -emit-llvm 
-disable-llvm-passes -o - %s | FileCheck %s
+//
+// The `.inline` clone of an inline builtin has internal linkage, so it must
+// not get a DLL storage class even if the declaration is dllexport.
+
+typedef __SIZE_TYPE__ size_t;
+
+size_t strlen(const char *s);
+
+extern inline __attribute__((always_inline)) __attribute__((gnu_inline))
+DLLEXPORT size_t strlen(const char *s) {
+  return 0;
+}
+
+size_t call_strlen(const char *s) {
+  return strlen(s);
+}
+
+// CHECK-LABEL: define {{.*}}i64 @call_strlen(
+// CHECK: call i64 @strlen.inline(
+//
+// CHECK: declare {{.*}}i64 @strlen(
+//
+// CHECK-LABEL: define internal i64 @strlen.inline(

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to