This fixes the missed inlining reported in PR ipa/95796:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95796
Functions with matching target_clones attributes remain uninlinable
after their versions are generated.
The C-family target_clones attribute handler sets DECL_UNINLINABLE.
Generated versions inherit that flag, preventing inlining even after
matching caller and callee versions have been selected.
Reject inlining while the target_clones attribute is present, without
caching that temporary restriction in DECL_UNINLINABLE. Once version
expansion replaces the attribute, the usual inlining checks apply.
Add C and C++ checks for inlining matching versions and a negative check
for different target lists. Keep the PR95778 call-routing tests focused
on their existing assertions by marking their callees noinline.
Bootstrapped all default languages and regression-tested on
x86_64-pc-linux-gnu with multilib enabled, including make -k check and
check-c++-all. No new unexpected failures compared with unmodified
7d652a985309ac5cd1492a505805bbd2c3781598.
OK for trunk? If approved, please commit this on my behalf.
Assisted-by: OpenAI Codex
gcc/c-family/ChangeLog:
PR ipa/95796
* c-attribs.cc (handle_target_clones_attribute): Do not set
DECL_UNINLINABLE for target_clones.
gcc/ChangeLog:
PR ipa/95796
* tree-inline.cc (tree_inlinable_function_p): Reject an unexpanded
target_clones function without caching the restriction.
gcc/testsuite/ChangeLog:
PR ipa/95796
* g++.target/i386/pr95796-1.C: New test.
* gcc.target/i386/pr95778-1.c (f2): Add noinline.
* gcc.target/i386/pr95778-2.c (f2): Likewise.
* gcc.target/i386/pr95796-1.c: New test.
* gcc.target/i386/pr95796-3.c: New test.
* gcc.target/i386/tct-ipa-inline-ice.c: Remove obsolete comment.
---
diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index 470054f04..ab7c6f6eb 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -5786,9 +5786,6 @@ handle_target_clones_attribute (tree *node, tree
name, tree ARG_UNUSED (args),
"single %<target_clones%> attribute is ignored");
*no_add_attrs = true;
}
- else
- /* Do not inline functions with multiple clone targets. */
- DECL_UNINLINABLE (*node) = 1;
}
else
{
diff --git a/gcc/testsuite/g++.target/i386/pr95796-1.C
b/gcc/testsuite/g++.target/i386/pr95796-1.C
new file mode 100644
index 000000000..8bd554516
--- /dev/null
+++ b/gcc/testsuite/g++.target/i386/pr95796-1.C
@@ -0,0 +1,21 @@
+/* PR ipa/95796 */
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+template <class T>
+__attribute__((target_clones("default", "sse4.2")))
+static T
+helper (T x)
+{
+ return 3 * x + 1;
+}
+
+__attribute__((target_clones("sse4.2", "default")))
+unsigned
+caller (unsigned x)
+{
+ return helper (x);
+}
+
+/* { dg-final { scan-tree-dump-not "= helper" "optimized" } } */
diff --git a/gcc/testsuite/gcc.target/i386/pr95778-1.c
b/gcc/testsuite/gcc.target/i386/pr95778-1.c
index 18f838321..e537fd578 100644
--- a/gcc/testsuite/gcc.target/i386/pr95778-1.c
+++ b/gcc/testsuite/gcc.target/i386/pr95778-1.c
@@ -2,7 +2,7 @@
/* { dg-options "-O3 -fPIC -fno-asynchronous-unwind-tables" } */
/* { dg-require-ifunc "" } */
-__attribute__((target_clones("default,avx2")))
+__attribute__((noinline, target_clones("default,avx2")))
static int
f2(int *p)
{
diff --git a/gcc/testsuite/gcc.target/i386/pr95778-2.c
b/gcc/testsuite/gcc.target/i386/pr95778-2.c
index 9ef513aab..339086671 100644
--- a/gcc/testsuite/gcc.target/i386/pr95778-2.c
+++ b/gcc/testsuite/gcc.target/i386/pr95778-2.c
@@ -2,7 +2,7 @@
/* { dg-options "-O3 -fPIC -fno-asynchronous-unwind-tables" } */
/* { dg-require-ifunc "" } */
-__attribute__((visibility("internal"),target_clones("default,avx2")))
+__attribute__((noinline, visibility("internal"),target_clones("default,avx2")))
int
f2(int *p)
{
diff --git a/gcc/testsuite/gcc.target/i386/pr95796-1.c
b/gcc/testsuite/gcc.target/i386/pr95796-1.c
new file mode 100644
index 000000000..4d62a989d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr95796-1.c
@@ -0,0 +1,22 @@
+/* PR ipa/95796 */
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+__attribute__((target_clones("default", "sse4.2")))
+static unsigned
+helper (unsigned x)
+{
+ return 3 * x + 1;
+}
+
+__attribute__((target_clones("sse4.2", "default")))
+unsigned
+caller (unsigned x)
+{
+ return helper (x);
+}
+
+/* Both callers should inline the matching helper, despite the different
+ order of the target_clones arguments. */
+/* { dg-final { scan-tree-dump-not "= helper" "optimized" } } */
diff --git a/gcc/testsuite/gcc.target/i386/pr95796-3.c
b/gcc/testsuite/gcc.target/i386/pr95796-3.c
new file mode 100644
index 000000000..1c3c45ffd
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr95796-3.c
@@ -0,0 +1,23 @@
+/* PR ipa/95796 */
+/* { dg-do compile } */
+/* { dg-require-ifunc "" } */
+/* { dg-options "-O2 -fdump-tree-optimized" } */
+
+__attribute__((target_clones("default", "avx2")))
+static unsigned
+helper (unsigned x)
+{
+ return 3 * x + 1;
+}
+
+__attribute__((target_clones("default", "sse4.2")))
+unsigned
+caller (unsigned x)
+{
+ return helper (x);
+}
+
+/* The caller's target_clones list does not determine which version of the
+ helper the resolver would select. Do not inline the unexpanded helper
+ during early inlining and thereby bypass the resolver. */
+/* { dg-final { scan-tree-dump "= helper \\(" "optimized" } } */
diff --git a/gcc/testsuite/gcc.target/i386/tct-ipa-inline-ice.c
b/gcc/testsuite/gcc.target/i386/tct-ipa-inline-ice.c
index b8f77372e..6f390613f 100644
--- a/gcc/testsuite/gcc.target/i386/tct-ipa-inline-ice.c
+++ b/gcc/testsuite/gcc.target/i386/tct-ipa-inline-ice.c
@@ -1,8 +1,5 @@
/* Test function multiversioning and inlining of a function with a
- target_clones attribute. Note, we need to allow inlining of
- target_clones functions, see
- "Do not inline functions with multiple clone targets." in c-attribs.cc,
- otherwise the test would not trigger the ICE. */
+ target_clones attribute. */
/* { dg-do assemble } */
/* { dg-require-ifunc "" } */
/* { dg-options "-O3" } */
diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
index 8162b1f50..425221449 100644
--- a/gcc/tree-inline.cc
+++ b/gcc/tree-inline.cc
@@ -4216,6 +4216,12 @@ tree_inlinable_function_p (tree fn)
bool do_warning;
tree always_inline;
+ /* Do not inline a target_clones function before its versions have been
+ expanded. This restriction is temporary: unlike the other reasons below,
+ it must not be cached in DECL_UNINLINABLE and inherited by the clones. */
+ if (lookup_attribute ("target_clones", DECL_ATTRIBUTES (fn)))
+ return false;
+
/* If we've already decided this function shouldn't be inlined,
there's no need to check again. */
if (DECL_UNINLINABLE (fn))