[clang] [clang] Add support for MSVC force inline attrs (PR #185282)
Trung Nguyen via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 2 06:05:24 PDT 2026
https://github.com/trungnt2910 updated https://github.com/llvm/llvm-project/pull/185282
>From f261399901d7c0141f4aac7029f92848ff22ed81 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] [clang] Add support for MSVC force inline attrs
Add support for `[[msvc::forceinline]]` and
`[[msvc::forceinline_calls]]`.
`[[msvc::forceinline]]` is equivalent to Microsoft's `__forceinline`
when placed before a function declaration.
Unlike `__forceinline`, `[[msvc::forceinline]]` works with lambdas.
`[[msvc::forceinline_calls]]` is simliar to `[[clang::always_inline]]`
but only works on statements.
Both are implemented as aliases of `[[clang::always_inline]]` with
special checks.
Fixes #186539.
---
clang/docs/ReleaseNotes.rst | 5 +++
clang/include/clang/Basic/Attr.td | 10 ++++-
.../clang/Basic/DiagnosticSemaKinds.td | 5 +++
clang/lib/Sema/SemaDeclAttr.cpp | 12 ++++++
clang/lib/Sema/SemaStmtAttr.cpp | 12 +++++-
clang/test/CodeGen/attr-ms-forceinline.cpp | 33 ++++++++++++++++
clang/test/Sema/attr-ms-forceinline.c | 32 ++++++++++++++++
clang/test/Sema/attr-ms-forceinline.cpp | 38 +++++++++++++++++++
8 files changed, 144 insertions(+), 3 deletions(-)
create mode 100644 clang/test/CodeGen/attr-ms-forceinline.cpp
create mode 100644 clang/test/Sema/attr-ms-forceinline.c
create mode 100644 clang/test/Sema/attr-ms-forceinline.cpp
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3341ec17c87cd..7f6049b77fa7e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -252,6 +252,11 @@ Attribute Changes in Clang
sound because any writer must hold all capabilities, so holding any one
prevents concurrent writes.
+- Added support for ``[[msvc::forceinline]]`` for functions and
+ ``[[msvc::forceinline_calls]]`` for statements. Both are aliases to
+ ``[[clang::always_inline]]`` with additional checks to ensure that they
+ are only accepted in places where MSVC also does.
+
Improvements to Clang's diagnostics
-----------------------------------
- ``-Wunused-but-set-variable`` now diagnoses file-scope variables with
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 8d716bd05b6ab..328e70b3ed900 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -934,9 +934,15 @@ 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">,
+ 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 6b9fa4a257397..603e91d598f79 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3277,6 +3277,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 63a70e4b77bc4..8a856215a9627 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5181,6 +5181,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 f7de98a3e5cf0..58d5332565d10 100644
--- a/clang/lib/Sema/SemaStmtAttr.cpp
+++ b/clang/lib/Sema/SemaStmtAttr.cpp
@@ -314,7 +314,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
new file mode 100644
index 0000000000000..795e6705f7826
--- /dev/null
+++ b/clang/test/CodeGen/attr-ms-forceinline.cpp
@@ -0,0 +1,33 @@
+// RUN: %clang_cc1 -std=c++23 -disable-llvm-passes -emit-llvm %s -triple x86_64-pc-windows-msvc -fms-extensions -o - | FileCheck -check-prefix=CHECK-MSVC %s
+// RUN: %clang_cc1 -std=c++23 -disable-llvm-passes -emit-llvm %s -triple x86_64-unknown-linux-gnu -fms-extensions -o - | FileCheck -check-prefix=CHECK-LINUX %s
+
+// CHECK-MSVC: define {{.*}} @"?foo@@YAXXZ"() #[[ATTR_FOO:[0-9]+]]
+// CHECK-LINUX: define {{.*}} @_Z3foov() #[[ATTR_FOO:[0-9]+]]
+// Add another attribute to prevent overlapping sets.
+[[msvc::forceinline, gnu::hot]] void foo() {}
+
+void bar();
+
+void call_lambda() {
+ auto lambda = [] [[msvc::forceinline]] () { bar(); };
+ lambda();
+}
+
+// CHECK-MSVC: define internal void @"??R<lambda_0>@?0??call_lambda@@YAXXZ at QEBA?A?<auto>@@XZ"{{.*}} #[[ATTR_LAMBDA:[0-9]+]]
+// CHECK-LINUX: define internal void @"_ZZ11call_lambdavENK3$_0clEv"{{.*}} #[[ATTR_LAMBDA:[0-9]+]]
+
+void call_bar() {
+// CHECK-MSVC-LABEL: define {{.*}} @"?call_bar@@YAXXZ"()
+// CHECK-MSVC: call void @"?bar@@YAXXZ"() #[[ATTR_CALLSITE:[0-9]+]]
+// CHECK-LINUX-LABEL: define {{.*}} @_Z8call_barv()
+// CHECK-LINUX: call void @_Z3barv() #[[ATTR_CALLSITE:[0-9]+]]
+ [[msvc::forceinline_calls]] bar();
+}
+
+// CHECK-MSVC-DAG: attributes #[[ATTR_FOO]] = { alwaysinline hot {{.*}}}
+// CHECK-MSVC-DAG: attributes #[[ATTR_LAMBDA]] = { alwaysinline {{.*}}}
+// CHECK-MSVC-DAG: attributes #[[ATTR_CALLSITE]] = { alwaysinline }
+
+// CHECK-LINUX-DAG: attributes #[[ATTR_FOO]] = { alwaysinline hot {{.*}}}
+// CHECK-LINUX-DAG: attributes #[[ATTR_LAMBDA]] = { alwaysinline {{.*}}}
+// CHECK-LINUX-DAG: attributes #[[ATTR_CALLSITE]] = { 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..1da92e89f2178
--- /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-error at -2 {{'msvc::forceinline' attribute takes no arguments}}
+
+void func4(void) {
+ [[msvc::forceinline_calls("foo")]] foo();
+ // ms-ext-error at -1 {{'msvc::forceinline_calls' attribute takes no arguments}}
+ // no-ms-ext-error at -2 {{'msvc::forceinline_calls' attribute takes no arguments}}
+}
diff --git a/clang/test/Sema/attr-ms-forceinline.cpp b/clang/test/Sema/attr-ms-forceinline.cpp
new file mode 100644
index 0000000000000..5e1d2b3eeee85
--- /dev/null
+++ b/clang/test/Sema/attr-ms-forceinline.cpp
@@ -0,0 +1,38 @@
+// 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
+
+void foo();
+
+[[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-error at -2 {{'msvc::forceinline' attribute takes no arguments}}
+
+void func4() {
+ [[msvc::forceinline_calls("foo")]] foo();
+ // ms-ext-error at -1 {{'msvc::forceinline_calls' attribute takes no arguments}}
+ // no-ms-ext-error at -2 {{'msvc::forceinline_calls' attribute takes no arguments}}
+}
More information about the cfe-commits
mailing list