[gcc r17-3487] gcov: Split atomic bitwise-or for some targets

Sebastian Huber via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:177b84bd12e00f6f3c0f43aa8e8e101264119201

commit r17-3487-g177b84bd12e00f6f3c0f43aa8e8e101264119201
Author: Sebastian Huber <[email protected]>
Date:   Wed Jul 9 11:37:06 2025 +0200

    gcov: Split atomic bitwise-or for some targets
    
    There are targets which only offer 32-bit atomic operations (for example
    32-bit RISC-V).  For these targets, split the 64-bit atomic bitwise-or
    operation used to update condition/decision coverage counters into up to
    two 32-bit atomic bitwise-or operations, one per 32-bit half of the
    counter.  If a half folds to a compile-time constant zero, then the
    atomic bitwise-or operation for that half is a no-op and is omitted, the
    same optimization already applied to whole-counter updates.
    
    For this test case
    
    int a(int i);
    int b(int i);
    
    int f(int i)
    {
      if (i) {
        return a(i);
      } else {
        return b(i);
      }
    }
    
    with options
    
    -O2 -fprofile-update=atomic -fcondition-coverage
    
    on a 32-bit RISC-V target with the atomic extension but no 64-bit atomic
    instructions, each counter update now generates a single inlined
    
      amoor.w zero,a4,0(a5)
    
    instead of a call into libatomic.  The always-zero high 32-bit half of
    the counter is folded away at compile time, so its atomic bitwise-or
    operation is omitted.  On a target with 64-bit atomic instructions, such
    as 64-bit RISC-V, a single
    
      amoor.d zero,a4,0(a5)
    
    is generated, unchanged from before this patch.
    
    Tested on x86_64-pc-linux-gnu (gcov.exp, gcc.dg gcov*.c, tree-prof.exp)
    with no regressions, and cross-checked code generation on 32-bit and
    64-bit RISC-V (rv32ima/ilp32 and rv64ima/lp64).
    
    gcc/ChangeLog:
    
            * tree-profile.cc (split_update_decision_counter): New.
            (instrument_decisions): Use counter_update to determine which
            atomic operations are available.  Use
            split_update_decision_counter() if 64-bit atomic operations can
            be split up into two 32-bit atomic operations.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.target/riscv/amo/gcov-condition-coverage-split-ior.c: New
            test.
    
    Signed-off-by: Sebastian Huber <[email protected]>

Diff:
---
 .../riscv/amo/gcov-condition-coverage-split-ior.c  | 27 +++++++++
 gcc/tree-profile.cc                                | 66 ++++++++++++++++++++--
 2 files changed, 88 insertions(+), 5 deletions(-)

diff --git a/gcc/testsuite/gcc.target/riscv/amo/gcov-condition-coverage-split-ior.c b/gcc/testsuite/gcc.target/riscv/amo/gcov-condition-coverage-split-ior.c
new file mode 100644
index 000000000000..f21b1697f7d0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/amo/gcov-condition-coverage-split-ior.c
@@ -0,0 +1,27 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -march=rv32gc -mabi=ilp32 -fprofile-update=atomic -fcondition-coverage" { target { rv32 } } } */
+/* { dg-options "-O2 -march=rv64gc -mabi=lp64 -fprofile-update=atomic -fcondition-coverage" { target { rv64 } } } */
+
+/* rv32 only has 32-bit atomic instructions, so the 64-bit gcov decision
+   counter update is split into up to two 32-bit atomic bitwise-or
+   operations, one per 32-bit half.  Here each counter update is a small
+   compile-time constant, so the always-zero high half is folded away and
+   only one atomic bitwise-or per branch remains.  rv64 has 64-bit atomic
+   instructions, so a single atomic bitwise-or suffices there too.  */
+
+int a (int);
+int b (int);
+
+int
+f (int i)
+{
+  if (i)
+    return a (i);
+  else
+    return b (i);
+}
+
+/* { dg-final { scan-assembler-times "\tamoor\\.w\t" 2 { target { rv32 } } } } */
+/* { dg-final { scan-assembler-not "\tamoor\\.d\t" { target { rv32 } } } } */
+/* { dg-final { scan-assembler-times "\tamoor\\.d\t" 2 { target { rv64 } } } } */
+/* { dg-final { scan-assembler-not "\tamoor\\.w\t" { target { rv64 } } } } */
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 26aa00c78f2d..882a31a6806d 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -1033,6 +1033,53 @@ resolve_counters (vec<counters> &cands)
 
 }
 
+/* Append statements to SEQ that update the decision counter referenced by
+   REF with the COUNTER.  Generate two separate 32-bit atomic bitwise-or
+   operations specified by ATOMIC_IOR_32 in the RELAXED memory order.  If a
+   32-bit part of COUNTER folds to a constant zero, then the atomic
+   bitwise-or operation for this part is a no-op and is omitted.  */
+static void
+split_update_decision_counter (gimple_seq *seq, tree ref, tree counter,
+			       tree atomic_ior_32, tree relaxed)
+{
+  ref = unshare_expr (ref);
+
+  /* Get the low and high addresses of the referenced counter.  */
+  tree addr_low = build_addr (ref);
+  tree four = build_int_cst (size_type_node, 4);
+  tree addr_high = gimple_build (seq, POINTER_PLUS_EXPR,
+				 TREE_TYPE (addr_low), addr_low, four);
+  if (WORDS_BIG_ENDIAN)
+    std::swap (addr_low, addr_high);
+
+  /* Get the low and high 32-bit parts of the counter.  Fold to a
+     constant if COUNTER is a compile-time constant, so that a part
+     which is known to be zero can be recognized below.  */
+  tree counter_low_32 = gimple_build (seq, NOP_EXPR, uint32_type_node,
+				      counter);
+  tree shift_32 = build_int_cst (integer_type_node, 32);
+  tree counter_high_64 = gimple_build (seq, RSHIFT_EXPR, gcov_type_node,
+				       counter, shift_32);
+  tree counter_high_32 = gimple_build (seq, NOP_EXPR, uint32_type_node,
+				       counter_high_64);
+
+  /* Atomically bitwise-or the low 32-bit counter parts.  */
+  if (!integer_zerop (counter_low_32))
+    {
+      gcall *call1 = gimple_build_call (atomic_ior_32, 3, addr_low,
+					counter_low_32, relaxed);
+      gimple_seq_add_stmt (seq, call1);
+    }
+
+  /* Atomically bitwise-or the high 32-bit counter parts.  */
+  if (!integer_zerop (counter_high_32))
+    {
+      gcall *call2 = gimple_build_call (atomic_ior_32, 3, addr_high,
+					counter_high_32, relaxed);
+      gimple_seq_add_stmt (seq, call2);
+    }
+}
+
 /* Add instrumentation to a decision subgraph.  EXPR should be the
    (topologically sorted) block of nodes returned by cov_blocks, MAPS the
    bitmaps returned by cov_maps, and MASKS the block of bitsets returned by
@@ -1138,11 +1185,17 @@ instrument_decisions (array_slice<basic_block> expr, size_t condno,
   gcc_assert (xi == bitmap_count_bits (core));
 
   const tree relaxed = build_int_cst (integer_type_node, MEMMODEL_RELAXED);
-  const bool atomic = flag_profile_update == PROFILE_UPDATE_ATOMIC;
+  const bool use_atomic_builtin
+    = counter_update == COUNTER_UPDATE_ATOMIC_BUILTIN;
+  const bool use_atomic_split
+    = counter_update == COUNTER_UPDATE_ATOMIC_SPLIT
+      || counter_update == COUNTER_UPDATE_ATOMIC_PARTIAL;
+  const tree atomic_ior_32
+    = builtin_decl_explicit (BUILT_IN_ATOMIC_FETCH_OR_4);
   const tree atomic_ior
-    = builtin_decl_explicit (TYPE_PRECISION (gcov_type_node) > 32
-			     ? BUILT_IN_ATOMIC_FETCH_OR_8
-			     : BUILT_IN_ATOMIC_FETCH_OR_4);
+    = TYPE_PRECISION (gcov_type_node) > 32
+      ? builtin_decl_explicit (BUILT_IN_ATOMIC_FETCH_OR_8)
+      : atomic_ior_32;
 
   /* Flush to the gcov accumulators.  */
   for (const basic_block b : expr)
@@ -1183,7 +1236,7 @@ instrument_decisions (array_slice<basic_block> expr, size_t condno,
 		continue;
 	      tree ref = tree_coverage_counter_ref (GCOV_COUNTER_CONDS,
 						    2 * condno + k);
-	      if (atomic)
+	      if (use_atomic_builtin)
 		{
 		  ref = unshare_expr (ref);
 		  gcall *flush = gimple_build_call (atomic_ior, 3,
@@ -1191,6 +1244,9 @@ instrument_decisions (array_slice<basic_block> expr, size_t condno,
 						    next[k], relaxed);
 		  gimple_seq_add_stmt (&seq, flush);
 		}
+	      else if (use_atomic_split)
+		split_update_decision_counter (&seq, ref, next[k],
+					       atomic_ior_32, relaxed);
 	      else
 		{
 		  tree get = emit_assign (&seq, ref);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.