[clang] [clang] Support target_clones on function templates (PR #218210)
Mocus Zhang via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 23 00:00:59 PDT 2026
https://github.com/mocusez created https://github.com/llvm/llvm-project/pull/218210
Function template specializations are not returned as separate results when looking up their primary template, so visit target_clones specializations directly. Emit multiversion bodies before the final replacement sweeps so constructor and other symbol replacements discovered during clone generation are applied.
Fixes #116501
Constraint: Other multiversion kinds remain template-restricted
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep multiversion body emission before the replacement sweeps unless all late replacement producers are handled another way
Tested: Full check-clang with isolated valid /dev/null (48,447 passed, zero unexpected failures)
Tested: DuckDB Release build(https://github.com/mocusez/duckdb/tree/fmv_autovec_pr) 577/577 and 4,426/4,426 tests with 983,782 assertions
Tested: LLVM issue #116501 default/popcnt example compiled, linked, dispatched, and ran
Not-tested: Runtime dispatch on non-x86 hardware
>From 7c7a5952de2f49c1cf98920a37c6573fd3e42643 Mon Sep 17 00:00:00 2001
From: MocusEZ <mocusez at outlook.com>
Date: Sun, 23 Aug 2026 06:39:16 +0000
Subject: [PATCH] [clang] Support target_clones on function templates
Function template specializations are not returned as separate results when looking up their primary template, so visit target_clones specializations directly. Emit multiversion bodies before the final replacement sweeps so constructor and other symbol replacements discovered during clone generation are applied.
Fixes #116501
Constraint: Other multiversion kinds remain template-restricted
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep multiversion body emission before the replacement sweeps unless all late replacement producers are handled another way
Tested: Full check-clang with isolated valid /dev/null (48,447 passed, zero unexpected failures)
Tested: All 15 target_clones tests across x86, AArch64, RISC-V, and PowerPC
Tested: DuckDB Release build 577/577 and 4,426/4,426 tests with 983,782 assertions
Tested: LLVM issue #116501 default/popcnt example compiled, linked, dispatched, and ran
Not-tested: Runtime dispatch on non-x86 hardware
---
clang/docs/ReleaseNotes.md | 3 +
clang/include/clang/Basic/AttrDocs.td | 3 +
clang/lib/AST/ASTContext.cpp | 10 +-
clang/lib/CodeGen/CodeGenModule.cpp | 3 +-
clang/lib/Sema/SemaDecl.cpp | 2 +-
.../attr-target-clones-templates.cpp | 98 +++++++++++++++++++
clang/test/SemaCXX/attr-target-clones.cpp | 29 +++++-
7 files changed, 142 insertions(+), 6 deletions(-)
create mode 100644 clang/test/CodeGenCXX/attr-target-clones-templates.cpp
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index fcd58e38261bb..eb1f794c63965 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -232,6 +232,9 @@ features cannot lower the translation-unit ABI level;
### Attribute Changes in Clang
+- The `target_clones` attribute now supports function templates. Each
+ specialization is multiversioned and dispatched independently. (#GH116501)
+
- Clang now properly propagates attributes on class and variable templates to their redeclarations, which will result in redeclarations not interfering with diagnostics. (#GH209812)
### Improvements to Clang's diagnostics
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 3052dd6c77ab1..6e1a5ebfcd1fd 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -3397,6 +3397,9 @@ generation options. Additionally, these versions will be resolved at runtime
based on the priority of their attribute options. All `target_clone` functions
are considered multiversioned functions.
+In C++, the attribute may be applied to a function template. Each specialization
+of the template is multiversioned and dispatched independently.
+
For AArch64 target:
The attribute contains comma-separated strings of target features joined by "+"
sign. For example:
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 03a28ea295887..1a1413940f215 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13467,7 +13467,15 @@ void ASTContext::forEachMultiversionedFunctionVersion(
const FunctionDecl *FD,
llvm::function_ref<void(FunctionDecl *)> Pred) const {
assert(FD->isMultiVersion() && "Only valid for multiversioned functions");
- llvm::SmallDenseSet<const FunctionDecl*, 4> SeenDecls;
+ // Function template specializations do not appear as separate results when
+ // looking up the name of their primary template. The target_clones attribute
+ // stores every version on the specialization itself, so visit it directly.
+ if (FD->isTargetClonesMultiVersion() &&
+ FD->isFunctionTemplateSpecialization()) {
+ Pred(const_cast<FunctionDecl *>(FD)->getMostRecentDecl());
+ return;
+ }
+ llvm::SmallDenseSet<const FunctionDecl *, 4> SeenDecls;
FD = FD->getMostRecentDecl();
// FIXME: The order of traversal here matters and depends on the order of
// lookup results, which happens to be (mostly) oldest-to-newest, but we
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 435f67542268a..eb00611a2e6fc 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1162,9 +1162,10 @@ void CodeGenModule::Release() {
DeferredDecls.insert_range(EmittedDeferredDecls);
EmittedDeferredDecls.clear();
EmitVTablesOpportunistically();
+ // Multiversion bodies can add deferred definitions and replacements.
+ emitMultiVersionFunctions();
applyGlobalValReplacements();
applyReplacements();
- emitMultiVersionFunctions();
emitPFPFieldsWithEvaluatedOffset();
emitGlobalDeleteForwardingBodies();
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 5055ef3d1cbb1..bf01230f1f098 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11816,7 +11816,7 @@ static bool CheckMultiVersionAdditionalRules(Sema &S, const FunctionDecl *OldFD,
<< static_cast<unsigned>(MVKind)),
PartialDiagnosticAt(NewFD->getLocation(),
S.PDiag(diag::err_multiversion_diff)),
- /*TemplatesSupported=*/false,
+ /*TemplatesSupported=*/MVKind == MultiVersionKind::TargetClones,
/*ConstexprSupported=*/!IsCPUSpecificCPUDispatchMVKind,
/*CLinkageMayDiffer=*/false);
}
diff --git a/clang/test/CodeGenCXX/attr-target-clones-templates.cpp b/clang/test/CodeGenCXX/attr-target-clones-templates.cpp
new file mode 100644
index 0000000000000..127f1f65e3495
--- /dev/null
+++ b/clang/test/CodeGenCXX/attr-target-clones-templates.cpp
@@ -0,0 +1,98 @@
+// RUN: %clang_cc1 -std=c++14 -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s
+
+struct InlineDependency {
+ InlineDependency(int &value) : value(value) {}
+ int &value;
+};
+
+template <typename T>
+T __attribute__((target_clones("sse4.2", "default"))) templated(T value) {
+ return value;
+}
+
+int call_int(int value) {
+ return templated(value);
+}
+
+float call_float(float value) {
+ return templated(value);
+}
+
+int (*address_int())(int) {
+ return &templated<int>;
+}
+
+template double templated<double>(double);
+
+template <typename T>
+T __attribute__((target_clones("sse4.2", "default")))
+with_inline_dependency(T &value) {
+ InlineDependency dependency(value);
+ return dependency.value;
+}
+
+int call_with_inline_dependency(int &value) {
+ return with_inline_dependency(value);
+}
+
+// CHECK-DAG: $_Z9templatedIiET_S0_.resolver = comdat any
+
+// CHECK-DAG: $_Z9templatedIfET_S0_.resolver = comdat any
+// CHECK-DAG: $_Z9templatedIdET_S0_.resolver = comdat any
+// CHECK-DAG: $_Z9templatedIiET_S0_.sse4.2.0 = comdat any
+// CHECK-DAG: $_Z9templatedIiET_S0_.default.1 = comdat any
+// CHECK-DAG: $_Z9templatedIfET_S0_.sse4.2.0 = comdat any
+// CHECK-DAG: $_Z9templatedIfET_S0_.default.1 = comdat any
+// CHECK-DAG: $_Z9templatedIdET_S0_.sse4.2.0 = comdat any
+// CHECK-DAG: $_Z9templatedIdET_S0_.default.1 = comdat any
+// CHECK-DAG: $_Z22with_inline_dependencyIiET_RS0_.resolver = comdat any
+// CHECK-DAG: $_Z22with_inline_dependencyIiET_RS0_.sse4.2.0 = comdat any
+// CHECK-DAG: $_Z22with_inline_dependencyIiET_RS0_.default.1 = comdat any
+
+// CHECK-DAG: @_Z9templatedIiET_S0_.ifunc = weak_odr alias i32 (i32), ptr @_Z9templatedIiET_S0_
+// CHECK-DAG: @_Z9templatedIfET_S0_.ifunc = weak_odr alias float (float), ptr @_Z9templatedIfET_S0_
+// CHECK-DAG: @_Z9templatedIdET_S0_.ifunc = weak_odr alias double (double), ptr @_Z9templatedIdET_S0_
+// CHECK-DAG: @_Z9templatedIiET_S0_ = weak_odr ifunc i32 (i32), ptr @_Z9templatedIiET_S0_.resolver
+// CHECK-DAG: @_Z9templatedIfET_S0_ = weak_odr ifunc float (float), ptr @_Z9templatedIfET_S0_.resolver
+// CHECK-DAG: @_Z9templatedIdET_S0_ = weak_odr ifunc double (double), ptr @_Z9templatedIdET_S0_.resolver
+
+// CHECK-DAG: @_Z22with_inline_dependencyIiET_RS0_.ifunc = weak_odr alias i32 (ptr), ptr @_Z22with_inline_dependencyIiET_RS0_
+// CHECK-DAG: @_Z22with_inline_dependencyIiET_RS0_ = weak_odr ifunc i32 (ptr), ptr @_Z22with_inline_dependencyIiET_RS0_.resolver
+// CHECK-LABEL: define dso_local noundef i32 @_Z8call_inti(
+// CHECK: call noundef i32 @_Z9templatedIiET_S0_(i32 noundef
+
+// CHECK-LABEL: define weak_odr ptr @_Z9templatedIiET_S0_.resolver() {{.*}} comdat
+// CHECK: ret ptr @_Z9templatedIiET_S0_.sse4.2.0
+// CHECK: ret ptr @_Z9templatedIiET_S0_.default.1
+
+// CHECK-LABEL: define dso_local noundef float @_Z10call_floatf(
+// CHECK: call noundef float @_Z9templatedIfET_S0_(float noundef
+
+// CHECK-LABEL: define weak_odr ptr @_Z9templatedIfET_S0_.resolver() {{.*}} comdat
+// CHECK: ret ptr @_Z9templatedIfET_S0_.sse4.2.0
+// CHECK: ret ptr @_Z9templatedIfET_S0_.default.1
+
+// CHECK-LABEL: define dso_local noundef ptr @_Z11address_intv(
+// CHECK: ret ptr @_Z9templatedIiET_S0_
+
+// CHECK-LABEL: define weak_odr noundef double @_Z9templatedIdET_S0_.sse4.2.0(
+// CHECK-LABEL: define weak_odr noundef double @_Z9templatedIdET_S0_.default.1(
+// CHECK-LABEL: define weak_odr ptr @_Z9templatedIdET_S0_.resolver() {{.*}} comdat
+// CHECK: ret ptr @_Z9templatedIdET_S0_.sse4.2.0
+// CHECK: ret ptr @_Z9templatedIdET_S0_.default.1
+
+// CHECK-LABEL: define dso_local noundef i32 @_Z27call_with_inline_dependencyRi(
+// CHECK: call noundef i32 @_Z22with_inline_dependencyIiET_RS0_(
+
+// CHECK-LABEL: define weak_odr ptr @_Z22with_inline_dependencyIiET_RS0_.resolver() {{.*}} comdat
+// CHECK: ret ptr @_Z22with_inline_dependencyIiET_RS0_.sse4.2.0
+// CHECK: ret ptr @_Z22with_inline_dependencyIiET_RS0_.default.1
+
+// CHECK-LABEL: define linkonce_odr noundef i32 @_Z9templatedIiET_S0_.sse4.2.0(
+// CHECK-LABEL: define linkonce_odr noundef i32 @_Z9templatedIiET_S0_.default.1(
+// CHECK-LABEL: define linkonce_odr noundef float @_Z9templatedIfET_S0_.sse4.2.0(
+// CHECK-LABEL: define linkonce_odr noundef float @_Z9templatedIfET_S0_.default.1(
+// CHECK-LABEL: define linkonce_odr noundef i32 @_Z22with_inline_dependencyIiET_RS0_.sse4.2.0(
+// CHECK-LABEL: define linkonce_odr void @_ZN16InlineDependencyC1ERi(
+// CHECK-LABEL: define linkonce_odr noundef i32 @_Z22with_inline_dependencyIiET_RS0_.default.1(
+// CHECK-LABEL: define linkonce_odr void @_ZN16InlineDependencyC2ERi(
diff --git a/clang/test/SemaCXX/attr-target-clones.cpp b/clang/test/SemaCXX/attr-target-clones.cpp
index 68c9a4ff48ed9..2eb2c816649e1 100644
--- a/clang/test/SemaCXX/attr-target-clones.cpp
+++ b/clang/test/SemaCXX/attr-target-clones.cpp
@@ -1,8 +1,31 @@
// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsyntax-only -verify -fexceptions -fcxx-exceptions %s -std=c++14
-// expected-error at +2 {{attribute 'target_clones' multiversioned functions do not yet support function templates}}
-template<typename T, typename U>
-int __attribute__((target_clones("sse4.2", "default"))) foo(){ return 1;}
+template <typename T>
+T __attribute__((target_clones("sse4.2", "default"))) templated(T value) {
+ return value;
+}
+
+int use_templated() {
+ int (*ptr)(int) = &templated<int>;
+ return ptr(1) + templated(2.0f);
+}
+
+template double templated<double>(double);
+
+struct HasMemberTemplate {
+ template <typename T>
+ T __attribute__((target_clones("sse4.2", "default"))) member(T value) {
+ return value;
+ }
+};
+
+int use_member_template(HasMemberTemplate &object) {
+ return object.member(1);
+}
+
+// expected-error at +2 {{attribute 'target_clones' multiversioned functions do not yet support deduced return types}}
+template <typename T>
+auto __attribute__((target_clones("sse4.2", "default"))) undeduced(T);
void uses_lambda() {
// expected-error at +1 {{attribute 'target_clones' multiversioned functions do not yet support lambdas}}
More information about the cfe-commits
mailing list