On Thu, 15 Oct 2026, Richard Biener wrote:

> On Wed, 14 Oct 2026, Kees Cook wrote:
> 
> > On Sun, Oct 11, 2026 at 04:50:41PM +0200, Richard Biener wrote:
> > > >     In function 'small_to_big', inlined from 'f' at t.c:22:3:
> > > >     warning: array subscript 'struct big[0]' is partly outside array 
> > > > bounds of 'struct small[1]' [-Warray-bounds=]
> > >
> > > I think these diagnostics are dubious with -fno-strict-aliasing which
> > > essentially says you can access every object via any other objects type.
> > 
> > The source has no such access, though: each function reads and writes
> > members through its own types, and every byte touched is inside the
> > object. The "struct big" access to a "struct small" exists only in the
> > body ICF picked, so the warning is about code GCC wrote rather than
> > code the programmer wrote.
> > 
> > > So instead of this I'd rather work towards inlining the original
> > > body rather than the ICF prevailing one.
> > 
> > That does seem like it could be better, but it's not clear to me how
> > large a project that would be to accomplish.
> 
> I think for the case of ICF and non-LTO it should be possible to
> "hack" this in;  it should be just to make sure the inline edges
> point to the original body and hopefully the ICF "merge" is somehow
> encoded clearly.  When the inline edge is set up correctly it should
> possibly even work with LTO.

I one-shotted the attached.  It definitely fails to do this in a
proper IPA way - it should have used IPA function/variable transform
stages and stream out the "defered aliases" in 
write/read_optimization_summary.

I'm sure this can be completed with some interaction, but our AI
policy makes this questionable.

Richard.

> > This patch seems to DTRT, though. It doesn't appear to regress merging
> > in a meaningful way for the Linux at least.
> 
> I don't dispute this, but it feels like a hack.  Either ICF should
> work as if -fstrict-aliasing were given, but consistently so, and
> for a good reason, or you could as well do -fno-ipa-icf when
> building the kernel as you are doing so many we-don't-quite-want-C
> things already.
> 
> > > And force-disable these kind of diagnostics with -fno-strict-aliasing?
> > 
> > I'd really rather not. Linux builds everything with -fno-strict-aliasing,
> > so that would remove these checks from the kernel entirely. GCC's
> > -Warray-bounds is still off by default for the kernel because of false
> > positives like this one (which is why I'm trying to solve it), and I've
> > been working toward enabling it, since it finds real bugs. (I have a
> > stack of a dozen real issues that this found while doing a recent pass.)
> > 
> > For example (since I don't yet have links to the above stack), here
> > are some random examples from a few years ago: a response struct passed
> > where a bigger error struct was expected, whose "error" field was past
> > the end of the object[1]:
> > 
> >   drivers/thunderbolt/xdomain.c:226:22: error: array subscript 'const 
> > struct tb_xdp_error_response[0]' is partly outside array bounds of 'struct 
> > tb_xdp_properties_changed_response[1]'
> > 
> > and a u64 remainder stored into an int on the stack[2]:
> > 
> >   include/linux/math64.h:56:20: error: array subscript 'u64 {aka long long 
> > unsigned int}[0]' is partly outside array bounds of 'int[1]'
> > 
> > But if you did disable diagnostics, which did you have in mind? If it's
> > only warnings about an access whose type is bigger than the object while
> > the bytes it touches are in bounds, that's narrower, but the first
> > example above would still need to be caught, so I'd much rather just
> > get ICF behaving nicely. :)
> > 
> > -Kees
> > 
> > [1] https://git.kernel.org/linus/19813551701d0
> > [2] https://git.kernel.org/linus/9f3d543a9f737
> > 
> > 
> 
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Stefan Gaiser, Jochen Jaser, Abhinav Puri; (HRB 36809, AG Nuernberg)
From 60418b7da3a4b88a47336488b1d1c2656786c5c4 Mon Sep 17 00:00:00 2001
From: Richard Biener <[email protected]>
Date: Thu, 15 Oct 2026 10:42:08 +0200
Subject: [PATCH] ipa-icf: Defer creating aliases until after inlining
 [PR105438]

When IPA-ICF merges two functions by creating an alias and releasing
the alias's body before inlining, inlining calls to the alias inlines
the ICF-chosen prevailing body instead of the original body.  When the
functions operate on different types (such as different array bounds
or structure layouts), this mixes up types and declarations in the
inlined body, triggering bogus diagnostics such as -Warray-bounds.

This patch defers the creation of ICF symbol aliases and body removal
until after inlining.  If an alias function was inlined into all of its
callers, its original body was inlined with its original types and declarations
and no alias needs to be created.  If calls remain or the function is
needed offline, the alias to the prevailing function is created after inlining.

        PR ipa/105438

gcc/ChangeLog:

        * ipa-icf.h (ipa_icf_create_deferred_aliases): Declare.
        * tree-pass.h (ipa_icf_create_deferred_aliases): Declare.
        * ipa-icf.cc (deferred_merge_aliases): New static variable.
        (sem_function::merge): Defer alias creation by pushing to
        deferred_merge_aliases.
        (ipa_icf_create_deferred_aliases): New function.
        (ipa_icf_cc_finalize): Release deferred_merge_aliases.
        * ipa-inline.cc (ipa_inline): Call ipa_icf_create_deferred_aliases.

gcc/testsuite/ChangeLog:

        * g++.dg/ipa/pr105438.C: New test.
        * g++.dg/ipa/pr105438-2.C: New test.
---
 gcc/ipa-icf.cc                        | 74 +++++++++++++++++++++------
 gcc/ipa-icf.h                         |  2 +
 gcc/ipa-inline.cc                     |  5 ++
 gcc/testsuite/g++.dg/ipa/pr105438-2.C | 25 +++++++++
 gcc/testsuite/g++.dg/ipa/pr105438.C   | 25 +++++++++
 gcc/tree-pass.h                       |  1 +
 6 files changed, 117 insertions(+), 15 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/ipa/pr105438-2.C
 create mode 100644 gcc/testsuite/g++.dg/ipa/pr105438.C

diff --git a/gcc/ipa-icf.cc b/gcc/ipa-icf.cc
index 64cc722bd7a..5e13b267b7e 100644
--- a/gcc/ipa-icf.cc
+++ b/gcc/ipa-icf.cc
@@ -100,6 +100,7 @@ namespace ipa_icf {
    are propagated later on.  */
 
 static sem_item_optimizer *optimizer = NULL;
+static vec<std::pair<cgraph_node *, cgraph_node *> > deferred_merge_aliases;
 
 /* Constructor.  */
 
@@ -1285,22 +1286,9 @@ sem_function::merge (sem_item *alias_item)
   else if (create_alias)
     {
       alias->icf_merged = true;
+      original->icf_merged = true;
 
-      /* Remove the function's body.  */
-      ipa_merge_profiles (original, alias);
-      symtab->call_cgraph_removal_hooks (alias);
-      alias->release_body (true);
-      alias->reset ();
-      /* Notice global symbol possibly produced RTL.  */
-      ((symtab_node *)alias)->call_for_symbol_and_aliases (clear_decl_rtl,
-                                                          NULL, true);
-
-      /* Create the alias.  */
-      cgraph_node::create_alias (alias_func->decl, decl);
-      alias->resolve_alias (original);
-
-      original->call_for_symbol_thunks_and_aliases
-        (set_local, (void *)(size_t) original->local_p (), true);
+      deferred_merge_aliases.safe_push (std::make_pair (original, alias));
 
       if (dump_enabled_p ())
        dump_printf (MSG_OPTIMIZED_LOCATIONS,
@@ -3722,12 +3710,67 @@ public:
 
 } // ipa_icf namespace
 
+using namespace ipa_icf;
+
 ipa_opt_pass_d *
 make_pass_ipa_icf (gcc::context *ctxt)
 {
   return new ipa_icf::pass_ipa_icf (ctxt);
 }
 
+/* Create deferred ICF aliases after inlining.  */
+
+void
+ipa_icf_create_deferred_aliases (void)
+{
+  for (unsigned i = 0; i < deferred_merge_aliases.length (); i++)
+    {
+      cgraph_node *original
+       = deferred_merge_aliases[i].first->ultimate_alias_target ();
+      cgraph_node *alias = deferred_merge_aliases[i].second;
+
+      if (alias->body_removed || alias->inlined_to)
+       continue;
+
+      /* If the alias function was inlined into all callers and can be
+        removed, its original body was inlined and no offline copy is
+        needed.  */
+      if (!alias->callers && alias->can_remove_if_no_direct_calls_p ())
+       continue;
+
+      if (original->inlined_to)
+       continue;
+
+      if (alias->clones)
+       {
+         /* If alias has inline clones, we cannot release its body.
+            Redirect direct callers to original instead.  */
+         redirect_all_callers (alias, original);
+         continue;
+       }
+
+      cgraph_edge *e, *next;
+      for (e = alias->callees; e; e = next)
+       {
+         next = e->next_callee;
+         if (!e->inline_failed)
+           e->callee->remove_symbol_and_inline_clones ();
+       }
+
+      ipa_merge_profiles (original, alias);
+      symtab->call_cgraph_removal_hooks (alias);
+      alias->release_body (true);
+      alias->reset ();
+      ((symtab_node *)alias)->call_for_symbol_and_aliases (clear_decl_rtl,
+                                                          NULL, true);
+      cgraph_node::create_alias (alias->decl, original->decl);
+      alias->resolve_alias (original);
+      original->call_for_symbol_thunks_and_aliases
+       (set_local, (void *)(size_t) original->local_p (), true);
+    }
+  deferred_merge_aliases.release ();
+}
+
 /* Reset all state within ipa-icf.cc so that we can rerun the compiler
    within the same process.  For use by toplev::finalize.  */
 
@@ -3735,4 +3778,5 @@ void
 ipa_icf_cc_finalize (void)
 {
   ipa_icf::optimizer = NULL;
+  deferred_merge_aliases.release ();
 }
diff --git a/gcc/ipa-icf.h b/gcc/ipa-icf.h
index 4135ad3eb9c..daaa15e9d55 100644
--- a/gcc/ipa-icf.h
+++ b/gcc/ipa-icf.h
@@ -673,3 +673,5 @@ private:
 }; // class sem_item_optimizer
 
 } // ipa_icf namespace
+
+void ipa_icf_create_deferred_aliases (void);
diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
index ba8bf11b66e..767068e85ea 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -124,6 +124,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "ipa-modref-tree.h"
 #include "ipa-modref.h"
 
+extern void ipa_icf_create_deferred_aliases (void);
+
 /* Inliner uses greedy algorithm to inline calls in a priority order.
    Badness is used as the key in a Fibonacci heap which roughly corresponds
    to negation of benefit to cost ratios.
@@ -3004,6 +3006,9 @@ ipa_inline (void)
 
   if (dump_file)
     ipa_dump_fn_summaries (dump_file);
+
+  ipa_icf_create_deferred_aliases ();
+
   return remove_functions ? TODO_remove_functions : 0;
 }
 
diff --git a/gcc/testsuite/g++.dg/ipa/pr105438-2.C 
b/gcc/testsuite/g++.dg/ipa/pr105438-2.C
new file mode 100644
index 00000000000..64aa30c588a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ipa/pr105438-2.C
@@ -0,0 +1,25 @@
+// PR ipa/105438
+// { dg-do compile }
+// { dg-options "-O2 -fno-strict-aliasing -fno-early-inlining -Warray-bounds" }
+
+int longer[7] = {};
+int shorter[2] = {};
+int out[10] = {};
+
+template <int N>
+void configure(const int(&in)[N], const int nrows = N)
+{
+    if (nrows <= 10)
+    {
+        for (int i = 0; i < nrows; i++)
+        {
+            out[i] = in[i]; // { dg-bogus "outside array bounds" }
+        }
+    }
+}
+
+int main()
+{
+  configure(longer);
+  configure(shorter);
+}
diff --git a/gcc/testsuite/g++.dg/ipa/pr105438.C 
b/gcc/testsuite/g++.dg/ipa/pr105438.C
new file mode 100644
index 00000000000..c84fcb67b9f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ipa/pr105438.C
@@ -0,0 +1,25 @@
+// PR ipa/105438
+// { dg-do compile }
+// { dg-options "-O2 -fno-strict-aliasing -Warray-bounds" }
+
+int longer[7] = {};
+int shorter[2] = {};
+int out[10] = {};
+
+template <int N>
+void configure(const int(&in)[N], const int nrows = N)
+{
+    if (nrows <= 10)
+    {
+        for (int i = 0; i < nrows; i++)
+        {
+            out[i] = in[i]; // { dg-bogus "outside array bounds" }
+        }
+    }
+}
+
+int main()
+{
+  configure(longer);
+  configure(shorter);
+}
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index a3b35e009e0..bdd60f72d81 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -539,6 +539,7 @@ extern simple_ipa_opt_pass *make_pass_ipa_free_fn_summary 
(gcc::context *ctxt);
 extern ipa_opt_pass_d *make_pass_ipa_cp (gcc::context *ctxt);
 extern ipa_opt_pass_d *make_pass_ipa_sra (gcc::context *ctxt);
 extern ipa_opt_pass_d *make_pass_ipa_icf (gcc::context *ctxt);
+extern void ipa_icf_create_deferred_aliases (void);
 extern ipa_opt_pass_d *make_pass_ipa_devirt (gcc::context *ctxt);
 extern ipa_opt_pass_d *make_pass_ipa_odr (gcc::context *ctxt);
 extern ipa_opt_pass_d *make_pass_ipa_reference (gcc::context *ctxt);
-- 
2.51.0

Reply via email to