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]>
---

v2:

* Properly use a right shift (RSHIFT_EXPR) to get the higher order
  32-bits of the counter.

* Fix style issue.

v3:

* Update to take into account the recent counter optimization changes.

* Add test case.

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

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 00000000000..f21b1697f7d
--- /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 26aa00c78f2..7c0c980e7a6 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 address 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);
-- 
2.53.0

Reply via email to