[clang] [clang] Add support for MSVC force inline attrs (PR #185282)
Trung Nguyen via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 31 06:10:50 PDT 2026
https://github.com/trungnt2910 updated https://github.com/llvm/llvm-project/pull/185282
>From 825d065ef2d32e3374104b7b607abade434701a9 Mon Sep 17 00:00:00 2001
From: Trung Nguyen <trungnt282910 at gmail.com>
Date: Mon, 9 Mar 2026 01:28:03 +1100
Subject: [PATCH 1/5] [clang] Add support for `[[msvc::forceinline]]`
This is equivalent to Microsoft's `__forceinline` when placed before
a function declaration.
Unlike `__forceinline`, `[[msvc::forceinline]]` works with lambdas.
This is implemented as an alias of `[[clang::always_inline]]`.
---
clang/include/clang/Basic/Attr.td | 3 ++-
clang/test/CodeGen/attr-ms-forceinline.cpp | 16 ++++++++++++++++
clang/test/Sema/attr-ms-forceinline.cpp | 9 +++++++++
3 files changed, 27 insertions(+), 1 deletion(-)
create mode 100644 clang/test/CodeGen/attr-ms-forceinline.cpp
create mode 100644 clang/test/Sema/attr-ms-forceinline.cpp
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index befd671393c7c..446a5abc250e8 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -927,7 +927,8 @@ def AlignNatural : InheritableAttr {
def AlwaysInline : DeclOrStmtAttr {
let Spellings = [GCC<"always_inline">, CXX11<"clang", "always_inline">,
- C23<"clang", "always_inline">, CustomKeyword<"__forceinline">];
+ C23<"clang", "always_inline">, CustomKeyword<"__forceinline">,
+ CXX11<"msvc", "forceinline">, C23<"msvc", "forceinline">];
let Accessors = [Accessor<"isClangAlwaysInline", [CXX11<"clang", "always_inline">,
C23<"clang", "always_inline">]>];
let Subjects = SubjectList<[Function, Stmt], WarnDiag,
diff --git a/clang/test/CodeGen/attr-ms-forceinline.cpp b/clang/test/CodeGen/attr-ms-forceinline.cpp
new file mode 100644
index 0000000000000..240e58b8a8e52
--- /dev/null
+++ b/clang/test/CodeGen/attr-ms-forceinline.cpp
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -std=c++23 -emit-llvm %s -triple x86_64-pc-windows-msvc -o - | FileCheck %s
+
+[[msvc::forceinline]] void func() {}
+
+void call_func() {
+// CHECK-LABEL: @"?call_func@@YAXXZ"()
+// CHECK-NOT: call void @"?func@@YAXXZ"()
+ func();
+}
+
+void call_lambda() {
+// CHECK-LABEL: @"?call_lambda@@YAXXZ"()
+// CHECK-NOT: call void @"??R<lambda_
+ auto lambda = [] [[msvc::forceinline]] () {};
+ lambda();
+}
diff --git a/clang/test/Sema/attr-ms-forceinline.cpp b/clang/test/Sema/attr-ms-forceinline.cpp
new file mode 100644
index 0000000000000..6e5ac191ce8d1
--- /dev/null
+++ b/clang/test/Sema/attr-ms-forceinline.cpp
@@ -0,0 +1,9 @@
+// RUN: %clang_cc1 -verify -fsyntax-only -std=c++23 -triple x86_64-windows %s
+// expected-no-diagnostics
+
+[[msvc::forceinline]] void func(void) {}
+
+void lambda() {
+ auto l = [] [[msvc::forceinline]] () {};
+ l();
+}
>From 72a2f22ab2e02aaf034539343ca7d6a4ad458fd8 Mon Sep 17 00:00:00 2001
From: Trung Nguyen <57174311+trungnt2910 at users.noreply.github.com>
Date: Wed, 18 Mar 2026 07:21:16 +1100
Subject: [PATCH 2/5] [clang] Try removing triple from test
---
clang/test/Sema/attr-ms-forceinline.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/Sema/attr-ms-forceinline.cpp b/clang/test/Sema/attr-ms-forceinline.cpp
index 6e5ac191ce8d1..619c7c8fb62da 100644
--- a/clang/test/Sema/attr-ms-forceinline.cpp
+++ b/clang/test/Sema/attr-ms-forceinline.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -verify -fsyntax-only -std=c++23 -triple x86_64-windows %s
+// RUN: %clang_cc1 -verify -fsyntax-only -std=c++23 %s
// expected-no-diagnostics
[[msvc::forceinline]] void func(void) {}
>From b1d3deed72d24a44e8a864fd913499eee586334f Mon Sep 17 00:00:00 2001
From: Trung Nguyen <57174311+trungnt2910 at users.noreply.github.com>
Date: Fri, 27 Mar 2026 23:31:42 +1100
Subject: [PATCH 3/5] Test [[msvc::forceinline]] with non-MSVC triple
This ensures that the feature is not artificially restricted to the MSVC compatibility mode.
---
clang/test/CodeGen/attr-ms-forceinline.cpp | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/clang/test/CodeGen/attr-ms-forceinline.cpp b/clang/test/CodeGen/attr-ms-forceinline.cpp
index 240e58b8a8e52..580fea044a803 100644
--- a/clang/test/CodeGen/attr-ms-forceinline.cpp
+++ b/clang/test/CodeGen/attr-ms-forceinline.cpp
@@ -1,16 +1,21 @@
-// RUN: %clang_cc1 -std=c++23 -emit-llvm %s -triple x86_64-pc-windows-msvc -o - | FileCheck %s
+// RUN: %clang_cc1 -std=c++23 -emit-llvm %s -triple x86_64-pc-windows-msvc -o - | FileCheck -check-prefix=CHECK-MSVC %s
+// RUN: %clang_cc1 -std=c++23 -emit-llvm %s -triple x86_64-unknown-linux-gnu -o - | FileCheck -check-prefix=CHECK-LINUX %s
[[msvc::forceinline]] void func() {}
void call_func() {
-// CHECK-LABEL: @"?call_func@@YAXXZ"()
-// CHECK-NOT: call void @"?func@@YAXXZ"()
+// CHECK-MSVC-LABEL: @"?call_func@@YAXXZ"()
+// CHECK-MSVC-NOT: call void @"?func@@YAXXZ"()
+// CHECK-LINUX-LABEL: @_Z9call_funcv()
+// CHECK-LINUX-NOT: call void @_Z4funcv()
func();
}
void call_lambda() {
-// CHECK-LABEL: @"?call_lambda@@YAXXZ"()
-// CHECK-NOT: call void @"??R<lambda_
+// CHECK-MSVC-LABEL: @"?call_lambda@@YAXXZ"()
+// CHECK-MSVC-NOT: call void @"??R<lambda_
+// CHECK-LINUX-LABEL: @_Z11call_lambdav()
+// CHECK-LINUX-NOT: call void @"_ZZ11call_lambdavENK3$_0clEv"
auto lambda = [] [[msvc::forceinline]] () {};
lambda();
}
>From b4e09722a11c9b6c53dff529f38611cebf131fd0 Mon Sep 17 00:00:00 2001
From: Trung Nguyen <trungnt282910 at gmail.com>
Date: Tue, 31 Mar 2026 23:30:48 +1100
Subject: [PATCH 4/5] [clang] Address review feedback
- Add tests for C23 and later attributes. They are identical to the C++
tests except for the lambda.
- Added sema tests for:
+ Applying `[[msvc::forceinline]]` to a statement.
+ Applying `[[msvc::forceinline_calls]]` to a function.
+ Passing unwanted arguments to the attributes.
+ Using the attributes without enabling MS extensions (they are
ignored).
- Add support for `[[msvc::forceinline_calls]]`.
- Guard the two attributes with `-fms-extensions`.
---
clang/include/clang/Basic/Attr.td | 9 ++++-
.../clang/Basic/DiagnosticSemaKinds.td | 5 +++
clang/lib/Sema/SemaDeclAttr.cpp | 12 ++++++
clang/lib/Sema/SemaStmtAttr.cpp | 12 +++++-
clang/test/CodeGen/attr-ms-forceinline.cpp | 31 +++++++++++-----
clang/test/Sema/attr-ms-forceinline.c | 32 ++++++++++++++++
clang/test/Sema/attr-ms-forceinline.cpp | 37 +++++++++++++++++--
7 files changed, 122 insertions(+), 16 deletions(-)
create mode 100644 clang/test/Sema/attr-ms-forceinline.c
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 446a5abc250e8..6a1c91c58ff29 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -928,9 +928,14 @@ def AlignNatural : InheritableAttr {
def AlwaysInline : DeclOrStmtAttr {
let Spellings = [GCC<"always_inline">, CXX11<"clang", "always_inline">,
C23<"clang", "always_inline">, CustomKeyword<"__forceinline">,
- CXX11<"msvc", "forceinline">, C23<"msvc", "forceinline">];
+ CXX11<"msvc", "forceinline">, C23<"msvc", "forceinline">,
+ CXX11<"msvc", "forceinline_calls">, C23<"msvc", "forceinline_calls">];
let Accessors = [Accessor<"isClangAlwaysInline", [CXX11<"clang", "always_inline">,
- C23<"clang", "always_inline">]>];
+ C23<"clang", "always_inline">]>,
+ Accessor<"isMSVCForceInline", [CXX11<"msvc", "forceinline">,
+ C23<"msvc", "forceinline">]>,
+ Accessor<"isMSVCForceInlineCalls", [CXX11<"msvc", "forceinline_calls">,
+ C23<"msvc", "forceinline_calls">]>];
let Subjects = SubjectList<[Function, Stmt], WarnDiag,
"functions and statements">;
let Documentation = [AlwaysInlineDocs];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 0e6b3f51a5231..ebf3245221dbc 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3269,6 +3269,11 @@ def warn_function_attribute_ignored_in_stmt : Warning<
"use '%0' on statements">,
InGroup<IgnoredAttributes>;
+def warn_stmt_attribute_ignored_in_function : Warning<
+ "attribute is ignored on this function as it only applies to statements; "
+ "use '%0' for functions">,
+ InGroup<IgnoredAttributes>;
+
def err_musttail_needs_trivial_args : Error<
"tail call requires that the return value, all parameters, and any "
"temporaries created by the expression are trivially destructible">;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 1b7f41061061d..94ff47790a1d4 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5130,6 +5130,18 @@ OptimizeNoneAttr *Sema::mergeOptimizeNoneAttr(Decl *D,
}
static void handleAlwaysInlineAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+ AlwaysInlineAttr AIA(S.Context, AL);
+ if (!S.getLangOpts().MicrosoftExt &&
+ (AIA.isMSVCForceInline() || AIA.isMSVCForceInlineCalls())) {
+ S.Diag(AL.getLoc(), diag::warn_attribute_ignored) << AL;
+ return;
+ }
+ if (AIA.isMSVCForceInlineCalls()) {
+ S.Diag(AL.getLoc(), diag::warn_stmt_attribute_ignored_in_function)
+ << "[[msvc::forceinline]]";
+ return;
+ }
+
if (AlwaysInlineAttr *Inline =
S.mergeAlwaysInlineAttr(D, AL, AL.getAttrName()))
D->addAttr(Inline);
diff --git a/clang/lib/Sema/SemaStmtAttr.cpp b/clang/lib/Sema/SemaStmtAttr.cpp
index 27fd5563cc40e..f45c4333f94f6 100644
--- a/clang/lib/Sema/SemaStmtAttr.cpp
+++ b/clang/lib/Sema/SemaStmtAttr.cpp
@@ -312,7 +312,17 @@ static Attr *handleNoInlineAttr(Sema &S, Stmt *St, const ParsedAttr &A,
static Attr *handleAlwaysInlineAttr(Sema &S, Stmt *St, const ParsedAttr &A,
SourceRange Range) {
AlwaysInlineAttr AIA(S.Context, A);
- if (!AIA.isClangAlwaysInline()) {
+ if (!S.getLangOpts().MicrosoftExt &&
+ (AIA.isMSVCForceInline() || AIA.isMSVCForceInlineCalls())) {
+ S.Diag(St->getBeginLoc(), diag::warn_attribute_ignored) << A;
+ return nullptr;
+ }
+ if (AIA.isMSVCForceInline()) {
+ S.Diag(St->getBeginLoc(), diag::warn_function_attribute_ignored_in_stmt)
+ << "[[msvc::forceinline_calls]]";
+ return nullptr;
+ }
+ if (!AIA.isClangAlwaysInline() && !AIA.isMSVCForceInlineCalls()) {
S.Diag(St->getBeginLoc(), diag::warn_function_attribute_ignored_in_stmt)
<< "[[clang::always_inline]]";
return nullptr;
diff --git a/clang/test/CodeGen/attr-ms-forceinline.cpp b/clang/test/CodeGen/attr-ms-forceinline.cpp
index 580fea044a803..dd9e00f536e20 100644
--- a/clang/test/CodeGen/attr-ms-forceinline.cpp
+++ b/clang/test/CodeGen/attr-ms-forceinline.cpp
@@ -1,14 +1,16 @@
-// RUN: %clang_cc1 -std=c++23 -emit-llvm %s -triple x86_64-pc-windows-msvc -o - | FileCheck -check-prefix=CHECK-MSVC %s
-// RUN: %clang_cc1 -std=c++23 -emit-llvm %s -triple x86_64-unknown-linux-gnu -o - | FileCheck -check-prefix=CHECK-LINUX %s
+// RUN: %clang_cc1 -std=c++23 -emit-llvm %s -triple x86_64-pc-windows-msvc -fms-extensions -o - | FileCheck -check-prefix=CHECK-MSVC %s
+// RUN: %clang_cc1 -std=c++23 -emit-llvm %s -triple x86_64-unknown-linux-gnu -fms-extensions -o - | FileCheck -check-prefix=CHECK-LINUX %s
-[[msvc::forceinline]] void func() {}
+[[msvc::forceinline]] void foo() {}
-void call_func() {
-// CHECK-MSVC-LABEL: @"?call_func@@YAXXZ"()
-// CHECK-MSVC-NOT: call void @"?func@@YAXXZ"()
-// CHECK-LINUX-LABEL: @_Z9call_funcv()
-// CHECK-LINUX-NOT: call void @_Z4funcv()
- func();
+void bar() {}
+
+void call_foo() {
+// CHECK-MSVC-LABEL: @"?call_foo@@YAXXZ"()
+// CHECK-MSVC-NOT: call void @"?foo@@YAXXZ"()
+// CHECK-LINUX-LABEL: @_Z8call_foov()
+// CHECK-LINUX-NOT: call void @_Z3foov()
+ foo();
}
void call_lambda() {
@@ -19,3 +21,14 @@ void call_lambda() {
auto lambda = [] [[msvc::forceinline]] () {};
lambda();
}
+
+void call_bar_stmt() {
+// CHECK-MSVC-LABEL: @"?call_bar_stmt@@YAXXZ"()
+// CHECK-MSVC: call void @"?bar@@YAXXZ"() #[[ALWAYSINLINEATTR:[0-9]+]]
+// CHECK-LINUX-LABEL: @_Z13call_bar_stmtv()
+// CHECK-LINUX: call void @_Z3barv() #[[ALWAYSINLINEATTR:[0-9]+]]
+ [[msvc::forceinline_calls]] bar();
+}
+
+// CHECK-MSVC: attributes #[[ALWAYSINLINEATTR]] = { alwaysinline }
+// CHECK-LINUX: attributes #[[ALWAYSINLINEATTR]] = { alwaysinline }
diff --git a/clang/test/Sema/attr-ms-forceinline.c b/clang/test/Sema/attr-ms-forceinline.c
new file mode 100644
index 0000000000000..bda31ea3eff97
--- /dev/null
+++ b/clang/test/Sema/attr-ms-forceinline.c
@@ -0,0 +1,32 @@
+// RUN: %clang_cc1 -verify=ms-ext -fms-extensions -fsyntax-only -std=c23 %s
+// RUN: %clang_cc1 -verify=no-ms-ext -fsyntax-only -std=c23 %s
+
+void foo(void);
+
+[[msvc::forceinline]] void func(void) {}
+// no-ms-ext-warning at -1 {{'msvc::forceinline' attribute ignored}}
+
+void stmt_forceinline(void) {
+ [[msvc::forceinline]] func();
+ // ms-ext-warning at -1 {{attribute is ignored on this statement as it only applies to functions; use '[[msvc::forceinline_calls]]' on statements}}
+ // no-ms-ext-warning at -2 {{'msvc::forceinline' attribute ignored}}
+}
+
+[[msvc::forceinline_calls]] void func2(void) {}
+// ms-ext-warning at -1 {{attribute is ignored on this function as it only applies to statements; use '[[msvc::forceinline]]' for functions}}
+// no-ms-ext-warning at -2 {{'msvc::forceinline_calls' attribute ignored}}
+
+void stmt_forceinline_calls(void) {
+ [[msvc::forceinline_calls]] foo();
+ // no-ms-ext-warning at -1 {{'msvc::forceinline_calls' attribute ignored}}
+}
+
+[[msvc::forceinline(0)]] void func3(void);
+// ms-ext-error at -1 {{'msvc::forceinline' attribute takes no arguments}}
+// no-ms-ext-warning at -2 {{'msvc::forceinline' attribute ignored}}
+
+void func4(void) {
+ [[msvc::forceinline_calls("foo")]] foo();
+ // ms-ext-error at -1 {{'msvc::forceinline' attribute takes no arguments}}
+ // no-ms-ext-warning at -2 {{'msvc::forceinline_calls' attribute ignored}}
+}
diff --git a/clang/test/Sema/attr-ms-forceinline.cpp b/clang/test/Sema/attr-ms-forceinline.cpp
index 619c7c8fb62da..5292470514a9e 100644
--- a/clang/test/Sema/attr-ms-forceinline.cpp
+++ b/clang/test/Sema/attr-ms-forceinline.cpp
@@ -1,9 +1,38 @@
-// RUN: %clang_cc1 -verify -fsyntax-only -std=c++23 %s
-// expected-no-diagnostics
+// RUN: %clang_cc1 -verify=ms-ext -fms-extensions -fsyntax-only -std=c++23 %s
+// RUN: %clang_cc1 -verify=no-ms-ext -fsyntax-only -std=c++23 %s
-[[msvc::forceinline]] void func(void) {}
+void foo();
-void lambda() {
+[[msvc::forceinline]] void func() {}
+// no-ms-ext-warning at -1 {{'msvc::forceinline' attribute ignored}}
+
+void lambda_func() {
auto l = [] [[msvc::forceinline]] () {};
+ // no-ms-ext-warning at -1 {{'msvc::forceinline' attribute ignored}}
l();
}
+
+void stmt_forceinline() {
+ [[msvc::forceinline]] func();
+ // ms-ext-warning at -1 {{attribute is ignored on this statement as it only applies to functions; use '[[msvc::forceinline_calls]]' on statements}}
+ // no-ms-ext-warning at -2 {{'msvc::forceinline' attribute ignored}}
+}
+
+[[msvc::forceinline_calls]] void func2() {}
+// ms-ext-warning at -1 {{attribute is ignored on this function as it only applies to statements; use '[[msvc::forceinline]]' for functions}}
+// no-ms-ext-warning at -2 {{'msvc::forceinline_calls' attribute ignored}}
+
+void stmt_forceinline_calls() {
+ [[msvc::forceinline_calls]] foo();
+ // no-ms-ext-warning at -1 {{'msvc::forceinline_calls' attribute ignored}}
+}
+
+[[msvc::forceinline(0)]] void func3();
+// ms-ext-error at -1 {{'msvc::forceinline' attribute takes no arguments}}
+// no-ms-ext-warning at -2 {{'msvc::forceinline' attribute ignored}}
+
+void func4() {
+ [[msvc::forceinline_calls("foo")]] foo();
+ // ms-ext-error at -1 {{'msvc::forceinline' attribute takes no arguments}}
+ // no-ms-ext-warning at -2 {{'msvc::forceinline_calls' attribute ignored}}
+}
>From 0ab171d706ee963093d9276fd69b53a9902f0006 Mon Sep 17 00:00:00 2001
From: Trung Nguyen <trungnt282910 at gmail.com>
Date: Wed, 1 Apr 2026 00:09:53 +1100
Subject: [PATCH 5/5] [clang] Fix test failures
---
clang/test/CodeGen/attr-ms-forceinline.cpp | 2 +-
clang/test/Sema/attr-ms-forceinline.c | 6 +++---
clang/test/Sema/attr-ms-forceinline.cpp | 6 +++---
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/clang/test/CodeGen/attr-ms-forceinline.cpp b/clang/test/CodeGen/attr-ms-forceinline.cpp
index dd9e00f536e20..a44f5d05b29af 100644
--- a/clang/test/CodeGen/attr-ms-forceinline.cpp
+++ b/clang/test/CodeGen/attr-ms-forceinline.cpp
@@ -3,7 +3,7 @@
[[msvc::forceinline]] void foo() {}
-void bar() {}
+void bar();
void call_foo() {
// CHECK-MSVC-LABEL: @"?call_foo@@YAXXZ"()
diff --git a/clang/test/Sema/attr-ms-forceinline.c b/clang/test/Sema/attr-ms-forceinline.c
index bda31ea3eff97..d80cabdba67b8 100644
--- a/clang/test/Sema/attr-ms-forceinline.c
+++ b/clang/test/Sema/attr-ms-forceinline.c
@@ -26,7 +26,7 @@ void stmt_forceinline_calls(void) {
// no-ms-ext-warning at -2 {{'msvc::forceinline' attribute ignored}}
void func4(void) {
- [[msvc::forceinline_calls("foo")]] foo();
- // ms-ext-error at -1 {{'msvc::forceinline' attribute takes no arguments}}
- // no-ms-ext-warning at -2 {{'msvc::forceinline_calls' attribute ignored}}
+ [[msvc::forceinline_calls("foo")]] foo();
+ // ms-ext-error at -1 {{'msvc::forceinline_calls' attribute takes no arguments}}
+ // no-ms-ext-warning at -2 {{'msvc::forceinline_calls' attribute ignored}}
}
diff --git a/clang/test/Sema/attr-ms-forceinline.cpp b/clang/test/Sema/attr-ms-forceinline.cpp
index 5292470514a9e..72b165b25c021 100644
--- a/clang/test/Sema/attr-ms-forceinline.cpp
+++ b/clang/test/Sema/attr-ms-forceinline.cpp
@@ -32,7 +32,7 @@ void stmt_forceinline_calls() {
// no-ms-ext-warning at -2 {{'msvc::forceinline' attribute ignored}}
void func4() {
- [[msvc::forceinline_calls("foo")]] foo();
- // ms-ext-error at -1 {{'msvc::forceinline' attribute takes no arguments}}
- // no-ms-ext-warning at -2 {{'msvc::forceinline_calls' attribute ignored}}
+ [[msvc::forceinline_calls("foo")]] foo();
+ // ms-ext-error at -1 {{'msvc::forceinline_calls' attribute takes no arguments}}
+ // no-ms-ext-warning at -2 {{'msvc::forceinline_calls' attribute ignored}}
}
More information about the cfe-commits
mailing list