]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
[tree-optimization] Allow LICM to hoist loads in "self write" patterns
authorKugan Vivekanandarajah <kvivekananda@nvidia.com>
Sun, 23 Nov 2025 04:27:10 +0000 (15:27 +1100)
committerKugan Vivekanandarajah <kvivekananda@nvidia.com>
Sun, 23 Nov 2025 04:28:24 +0000 (15:28 +1100)
This patch enables Loop Invariant Code Motion (LICM) to hoist loads that
alias with stores when SSA def-use analysis proves the stored value comes
from the loaded value.

The pattern a[i] = a[0] is common in TSVC benchmarks (s293):

  for (int i = 0; i < N; i++)
    a[i] = a[0];

Previously, GCC conservatively rejected hoisting a[0] due to potential
aliasing when i==0. However, this is a "self write" - even when aliasing
occurs, we're writing back the same value, making hoisting safe.

The optimization checks that:
1. One reference is a load, the other is a store
2. The stored SSA value equals the loaded SSA value
3. Only simple cases with single accesses per reference

This enables vectorization of these patterns by allowing the vectorizer
to see the hoisted loop-invariant value.

With the patch, the loop now vectorizes and generates:

        .L2:
-       ldr     s31, [x1]
-       str     s31, [x0], 4
-       cmp     x0, x2
+       str     q31, [x0], 16
+       cmp     x0, x1
        bne     .L2

gcc/ChangeLog:

* tree-ssa-loop-im.cc (is_self_write): New.
(ref_indep_loop_p): Allow hoisting when aliasing references
form a self write pattern.

gcc/testsuite/ChangeLog:

* gcc.dg/vect/vect-licm-hoist-1.c: New.
* gcc.dg/vect/vect-licm-hoist-2.c: Likewise.

Signed-off-by: Kugan Vivekanandarajah <kvivekananda@nvidia.com>
gcc/testsuite/gcc.dg/vect/vect-licm-hoist-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/vect/vect-licm-hoist-2.c [new file with mode: 0644]
gcc/tree-ssa-loop-im.cc

diff --git a/gcc/testsuite/gcc.dg/vect/vect-licm-hoist-1.c b/gcc/testsuite/gcc.dg/vect/vect-licm-hoist-1.c
new file mode 100644 (file)
index 0000000..2e850eb
--- /dev/null
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target vect_float } */
+/* { dg-additional-options "-fdump-tree-lim2-details -fdump-tree-vect-details" } */
+
+/* Test vectorization of "self write" pattern: a[i] = a[0].
+   LICM should hoist a[0] by recognizing that even when i==0 causes
+   aliasing, the stored value equals the loaded value (via SSA).  */
+
+#define N 32000
+
+float a[N];
+
+/* Should vectorize.  */
+
+void
+test_safe_hoist (void)
+{
+  for (int i = 0; i < N; i++)
+    a[i] = a[0];
+}
+
+/* { dg-final { scan-tree-dump "loop vectorized" "vect" } } */
+/* { dg-final { scan-tree-dump "independent \\(self write\\)" "lim2" } } */
+
diff --git a/gcc/testsuite/gcc.dg/vect/vect-licm-hoist-2.c b/gcc/testsuite/gcc.dg/vect/vect-licm-hoist-2.c
new file mode 100644 (file)
index 0000000..c42dc3f
--- /dev/null
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target vect_float } */
+/* { dg-additional-options "-fdump-tree-lim2-details -fdump-tree-vect-details" } */
+
+/* Negative test: ensure we don't incorrectly hoist when
+   a store invalidates the loaded value.  */
+
+#define N 32000
+
+float a[N];
+
+/* Should NOT hoist: a[0] = 5.0f breaks the SSA dependency.  */
+
+void
+test_unsafe_hoist (void)
+{
+  for (int i = 0; i < N; i++)
+    {
+      float x = a[0];
+      a[i] = x;
+      a[0] = 5.0f;
+    }
+}
+
+/* { dg-final { scan-tree-dump-not "independent \\(constant-indexed load" "lim2" } } */
+
index c8f4676b6f66781d832da84011b614390259c531..61f08beb9ff6c0a5175c4317be9724b90855a6ce 100644 (file)
@@ -3148,6 +3148,35 @@ ref_always_accessed_p (class loop *loop, im_mem_ref *ref, bool stored_p)
                               ref_always_accessed (loop, stored_p));
 }
 
+/* Returns true if LOAD_REF and STORE_REF form a "self write" pattern
+   where the stored value comes from the loaded value via SSA.
+   Example: a[i] = a[0] is safe to hoist a[0] even when i==0.  */
+
+static bool
+is_self_write (im_mem_ref *load_ref, im_mem_ref *store_ref)
+{
+  /* Only handle the simple case with a single access per ref.
+     Bail out on multiple accesses to be conservative.  */
+  if (load_ref->accesses_in_loop.length () != 1
+      || store_ref->accesses_in_loop.length () != 1)
+    return false;
+
+  gimple *load_stmt = load_ref->accesses_in_loop[0].stmt;
+  gimple *store_stmt = store_ref->accesses_in_loop[0].stmt;
+
+  if (!is_gimple_assign (load_stmt) || !is_gimple_assign (store_stmt))
+    return false;
+
+  tree loaded_val = gimple_assign_lhs (load_stmt);
+  tree stored_val = gimple_assign_rhs1 (store_stmt);
+
+  if (TREE_CODE (loaded_val) != SSA_NAME || TREE_CODE (stored_val) != SSA_NAME)
+    return false;
+
+  /* Self write: stored value is the loaded value.  */
+  return stored_val == loaded_val;
+}
+
 /* Returns true if REF1 and REF2 are independent.  */
 
 static bool
@@ -3235,6 +3264,20 @@ ref_indep_loop_p (class loop *loop, im_mem_ref *ref, dep_kind kind)
                      break;
                    }
                }
+             /* For hoisting loads (lim_raw), allow "self write": the store
+                writes back the loaded value.  Example: a[i] = a[0]
+                is safe even when i==0 causes aliasing.  */
+             else if (kind == lim_raw
+                      && ref->loaded && aref->stored
+                      && is_self_write (ref, aref))
+               {
+                 if (dump_file && (dump_flags & TDF_DETAILS))
+                   fprintf (dump_file,
+                            "Dependency of refs %u and %u: "
+                            "independent (self write).\n",
+                            ref->id, aref->id);
+               }
+
              else if (!refs_independent_p (ref, aref, kind != sm_waw))
                {
                  indep_p = false;