https://gcc.gnu.org/g:ed1911b9f1f1ab0c1b631f0b6427b798c7056200
commit r16-5526-ged1911b9f1f1ab0c1b631f0b6427b798c7056200 Author: Kugan Vivekanandarajah <[email protected]> Date: Sun Nov 23 15:27:10 2025 +1100 [tree-optimization] Allow LICM to hoist loads in "self write" patterns 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 <[email protected]> Diff: --- gcc/testsuite/gcc.dg/vect/vect-licm-hoist-1.c | 24 +++++++++++++++ gcc/testsuite/gcc.dg/vect/vect-licm-hoist-2.c | 26 ++++++++++++++++ gcc/tree-ssa-loop-im.cc | 43 +++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) 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 index 000000000000..2e850ebf8d5f --- /dev/null +++ b/gcc/testsuite/gcc.dg/vect/vect-licm-hoist-1.c @@ -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 index 000000000000..c42dc3f6f5c7 --- /dev/null +++ b/gcc/testsuite/gcc.dg/vect/vect-licm-hoist-2.c @@ -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" } } */ + diff --git a/gcc/tree-ssa-loop-im.cc b/gcc/tree-ssa-loop-im.cc index c8f4676b6f66..61f08beb9ff6 100644 --- a/gcc/tree-ssa-loop-im.cc +++ b/gcc/tree-ssa-loop-im.cc @@ -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;
