[gcc r17-3422] profile-count: Handle signed values in profile_probability::apply

Kyrylo Tkachov via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:91742b4e16fd43374a0432b588fb1ec9206f77ea

commit r17-3422-g91742b4e16fd43374a0432b588fb1ec9206f77ea
Author: Kyrylo Tkachov <[email protected]>
Date:   Thu Jul 16 16:55:31 2026 +0200

    profile-count: Handle signed values in profile_probability::apply
    
    profile_probability::apply takes a signed gcov_type value.  It uses RDIV,
    which adds half the denominator before division.  This does not round a
    negative value argument correctly.  Multiplying that signed value by the
    fixed-point probability can also overflow for large inputs.
    
    Scale the unsigned magnitude with safe_scale_64bit, then restore the sign.
    Return the input directly for a unit probability so that the minimum
    gcov_type value remains representable.  Preserve the existing truncation
    for uninitialized probabilities.
    
    Add selftests for signed rounding, zero and unit probabilities,
    uninitialized probabilities, and the minimum and maximum gcov_type values.
    
    Tested with a checking C compiler build and the compiler selftests on
    x86_64-pc-linux-gnu.
    
    gcc/ChangeLog:
    
            * profile-count.h (profile_probability::apply): Scale an unsigned
            magnitude with safe_scale_64bit.
            * profile-count.cc (test_profile_probability_apply): New.
            (profile_count_cc_tests): New.
            * selftest.h (profile_count_cc_tests): Declare.
            * selftest-run-tests.cc (selftest::run_tests): Run it.
    
    Signed-off-by: Kyrylo Tkachov <[email protected]>

Diff:
---
 gcc/profile-count.cc      | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 gcc/profile-count.h       | 26 +++++++++++++++++++++++++-
 gcc/selftest-run-tests.cc |  1 +
 gcc/selftest.h            |  1 +
 4 files changed, 74 insertions(+), 1 deletion(-)

diff --git a/gcc/profile-count.cc b/gcc/profile-count.cc
index 2cce9caa7722..9be209efda24 100644
--- a/gcc/profile-count.cc
+++ b/gcc/profile-count.cc
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "wide-int.h"
 #include "sreal.h"
 #include "profile.h"
+#include "selftest.h"
 
 /* Names from profile_quality enum values.  */
 
@@ -595,3 +596,49 @@ profile_count::force_nonzero () const
     }
   return ret;
 }
+
+#if CHECKING_P
+
+namespace selftest {
+
+/* Verify profile_probability::apply.  */
+
+static void
+test_profile_probability_apply ()
+{
+  const gcov_type min = INTTYPE_MINIMUM (gcov_type);
+  const gcov_type max = INTTYPE_MAXIMUM (gcov_type);
+  profile_probability quarter = profile_probability::guessed_always () / 4;
+  profile_probability even = profile_probability::even ();
+
+  ASSERT_EQ (1, quarter.apply (3));
+  ASSERT_EQ (-1, quarter.apply (-3));
+  ASSERT_EQ (2, even.apply (3));
+  ASSERT_EQ (-2, even.apply (-3));
+  ASSERT_EQ (0, even.apply (0));
+
+  ASSERT_EQ (0, profile_probability::never ().apply (min));
+  ASSERT_EQ (min, profile_probability::always ().apply (min));
+  ASSERT_EQ (max, profile_probability::always ().apply (max));
+  ASSERT_EQ (min, profile_probability::guessed_always ().apply (min));
+  ASSERT_EQ (min / 2, even.apply (min));
+  ASSERT_EQ (max / 2 + 1, even.apply (max));
+
+  profile_probability uninitialized = profile_probability::uninitialized ();
+  ASSERT_EQ (1, uninitialized.apply (3));
+  ASSERT_EQ (-1, uninitialized.apply (-3));
+  ASSERT_EQ (min / 2, uninitialized.apply (min));
+  ASSERT_EQ (max / 2, uninitialized.apply (max));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+profile_count_cc_tests ()
+{
+  test_profile_probability_apply ();
+}
+
+} // namespace selftest
+
+#endif
diff --git a/gcc/profile-count.h b/gcc/profile-count.h
index b424ecba3ea7..76325d0fdfa3 100644
--- a/gcc/profile-count.h
+++ b/gcc/profile-count.h
@@ -513,11 +513,35 @@ public:
       return ret;
     }
 
+  /* Return VAL scaled by this probability.  Round initialized probabilities
+     to the nearest integer, with halfway values away from zero.  Treat an
+     uninitialized probability as one half and truncate toward zero.  */
   gcov_type apply (gcov_type val) const
     {
       if (*this == uninitialized ())
 	return val / 2;
-      return RDIV (val * m_val, max_probability);
+
+      /* A unit probability leaves VAL unchanged.  Return it directly because
+	 the magnitude of the minimum gcov_type value is one greater than the
+	 maximum gcov_type value.  */
+      if (m_val == max_probability)
+	return val;
+
+      /* Convert to unsigned before negating so that the minimum gcov_type
+	 value has a representable magnitude.  Scale the magnitude with
+	 overflow-safe arithmetic, then restore the sign.  */
+      gcov_type_unsigned magnitude
+	= val < 0 ? -(gcov_type_unsigned) val : (gcov_type_unsigned) val;
+      uint64_t scaled;
+      bool scaled_p
+	= safe_scale_64bit (magnitude, m_val, max_probability, &scaled);
+      /* The scaled result fits in uint64_t.  With the unit case handled above,
+	 it also fits in the nonnegative range of gcov_type.  */
+      gcc_checking_assert (scaled_p);
+      gcc_checking_assert
+	(scaled <= (gcov_type_unsigned) INTTYPE_MAXIMUM (gcov_type));
+
+      return val < 0 ? -(gcov_type) scaled : (gcov_type) scaled;
     }
 
   /* Return 1-*THIS.  */
diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc
index 1d21b8751b4a..07eb984e918e 100644
--- a/gcc/selftest-run-tests.cc
+++ b/gcc/selftest-run-tests.cc
@@ -72,6 +72,7 @@ selftest::run_tests ()
   wide_int_cc_tests ();
   ggc_tests_cc_tests ();
   sreal_cc_tests ();
+  profile_count_cc_tests ();
   fibonacci_heap_cc_tests ();
   typed_splay_tree_cc_tests ();
   opt_suggestions_cc_tests ();
diff --git a/gcc/selftest.h b/gcc/selftest.h
index f2e2b49fc20d..acfc4a46e49b 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -248,6 +248,7 @@ extern void path_coverage_cc_tests ();
 extern void predict_cc_tests ();
 extern void pretty_print_cc_tests ();
 extern void pretty_print_token_buffer_cc_tests ();
+extern void profile_count_cc_tests ();
 extern void pub_sub_cc_tests ();
 extern void range_op_tests ();
 extern void range_tests ();
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.