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 <[email protected]> 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@+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@+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@+1 {{attribute 'target_clones' multiversioned functions do not yet support lambdas}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
