base: Add operators to SatCounter

Add shift, add and subtract assignment operators, as well as
copy and move constructor and assignments to SatCounter, so
that it they can be used by the prefetchers.

Also add extra useful functions to calculate saturation
oercentile so that the instantiator does not need to be aware
of the counter's maximum value.

Change-Id: I61d0cb28c8375b9d2774a39011e4a0aa6fe9ccb7
Signed-off-by: Daniel <odanrc@yahoo.com.br>
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/17996
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jason Lowe-Power <jason@lowepower.com>
Maintainer: Jason Lowe-Power <jason@lowepower.com>
diff --git a/src/base/sat_counter.hh b/src/base/sat_counter.hh
index 342a338..3de9486 100644
--- a/src/base/sat_counter.hh
+++ b/src/base/sat_counter.hh
@@ -56,6 +56,9 @@
 class SatCounter
 {
   public:
+    /** The default constructor should never be used. */
+    SatCounter() = delete;
+
     /**
      * Constructor for the counter. The explicit keyword is used to make
      * sure the user does not assign a number to the counter thinking it
@@ -75,6 +78,58 @@
                  "Saturating counter's Initial value exceeds max value.");
     }
 
+    /** Copy constructor. */
+    SatCounter(const SatCounter& other)
+        : initialVal(other.initialVal), maxVal(other.maxVal),
+          counter(other.counter)
+    {
+    }
+
+    /** Copy assignment. */
+    SatCounter& operator=(const SatCounter& other) {
+        if (this != &other) {
+            SatCounter temp(other);
+            this->swap(temp);
+        }
+        return *this;
+    }
+
+    /** Move constructor. */
+    SatCounter(SatCounter&& other)
+    {
+        initialVal = other.initialVal;
+        maxVal = other.maxVal;
+        counter = other.counter;
+        SatCounter temp(0);
+        other.swap(temp);
+    }
+
+    /** Move assignment. */
+    SatCounter& operator=(SatCounter&& other) {
+        if (this != &other) {
+            initialVal = other.initialVal;
+            maxVal = other.maxVal;
+            counter = other.counter;
+            SatCounter temp(0);
+            other.swap(temp);
+        }
+        return *this;
+    }
+
+    /**
+     * Swap the contents of every member of the class. Used for the default
+     * copy-assignment created by the compiler.
+     *
+     * @param other The other object to swap contents with.
+     */
+    void
+    swap(SatCounter& other)
+    {
+        std::swap(initialVal, other.initialVal);
+        std::swap(maxVal, other.maxVal);
+        std::swap(counter, other.counter);
+    }
+
     /** Pre-increment operator. */
     SatCounter&
     operator++()
@@ -113,6 +168,49 @@
         return old_counter;
     }
 
+    /** Shift-right-assignment. */
+    SatCounter&
+    operator>>=(const int& shift)
+    {
+        this->counter >>= shift;
+        return *this;
+    }
+
+    /** Shift-left-assignment. */
+    SatCounter&
+    operator<<=(const int& shift)
+    {
+        this->counter <<= shift;
+        if (this->counter > maxVal) {
+            this->counter = maxVal;
+        }
+        return *this;
+    }
+
+    /** Add-assignment. */
+    SatCounter&
+    operator+=(const int& value)
+    {
+        if (maxVal - this->counter >= value) {
+            this->counter += value;
+        } else {
+            this->counter = maxVal;
+        }
+        return *this;
+    }
+
+    /** Subtract-assignment. */
+    SatCounter&
+    operator-=(const int& value)
+    {
+        if (this->counter > value) {
+            this->counter -= value;
+        } else {
+            this->counter = 0;
+        }
+        return *this;
+    }
+
     /**
      * Read the counter's value.
      */
@@ -121,6 +219,22 @@
     /** Reset the counter to its initial value. */
     void reset() { counter = initialVal; }
 
+    /**
+     * Calculate saturation percentile of the current counter's value
+     * with regard to its maximum possible value.
+     *
+     * @return A value between 0.0 and 1.0 to indicate which percentile of
+     *         the maximum value the current value is.
+     */
+    double calcSaturation() const { return (double) counter / maxVal; }
+
+    /**
+     * Whether the counter has achieved its maximum value or not.
+     *
+     * @return True if the counter saturated.
+     */
+    bool isSaturated() const { return counter == maxVal; }
+
   private:
     uint8_t initialVal;
     uint8_t maxVal;
diff --git a/src/base/sat_counter.test.cc b/src/base/sat_counter.test.cc
index efc79e0..dbdaf0a 100644
--- a/src/base/sat_counter.test.cc
+++ b/src/base/sat_counter.test.cc
@@ -30,6 +30,8 @@
 
 #include <gtest/gtest.h>
 
+#include <utility>
+
 #include "base/sat_counter.hh"
 
 /**
@@ -78,6 +80,23 @@
 }
 
 /**
+ * Test calculating saturation percentile.
+ */
+TEST(SatCounterTest, SaturationPercentile)
+{
+    const unsigned bits = 3;
+    const unsigned max_value = (1 << bits) - 1;
+    SatCounter counter(bits);
+
+    ASSERT_FALSE(counter.isSaturated());
+    for (double value = 0.0; value <= max_value; value++, counter++) {
+        const double saturation = value / max_value;
+        ASSERT_DOUBLE_EQ(counter.calcSaturation(), saturation);
+    }
+    ASSERT_TRUE(counter.isSaturated());
+}
+
+/**
  * Test back and forth against an int.
  */
 TEST(SatCounterTest, IntComparison)
@@ -102,6 +121,55 @@
 }
 
 /**
+ * Test shift operators.
+ */
+TEST(SatCounterTest, Shift)
+{
+    const unsigned bits = 3;
+    const unsigned max_value = (1 << bits) - 1;
+    const unsigned initial_value = 1;
+    SatCounter counter(bits, initial_value);
+    SatCounter other(bits, initial_value);
+    // The saturated shift value is just enough to saturate, since greater
+    // values could generate undefined behavior
+    SatCounter saturated_counter(bits, bits);
+    int value = initial_value;
+
+    // Test random shifts
+    counter <<= 2;
+    value <<= 2;
+    ASSERT_EQ(counter, value);
+    counter >>= 1;
+    value >>= 1;
+    ASSERT_EQ(counter, value);
+
+    // Test saturation
+    counter <<= bits;
+    ASSERT_EQ(counter, max_value);
+
+    // Test zeroing
+    counter >>= bits;
+    ASSERT_EQ(counter, 0);
+
+    // Test saturation against other saturating counter
+    counter.reset();
+    value = initial_value;
+    counter <<= other;
+    value <<= other;
+    ASSERT_EQ(counter, value);
+    counter <<= saturated_counter;
+    value = max_value;
+    ASSERT_EQ(counter, max_value);
+
+    // Test zeroing against other saturating counter
+    counter >>= other;
+    value >>= other;
+    ASSERT_EQ(counter, value);
+    counter >>= saturated_counter;
+    ASSERT_EQ(counter, 0);
+}
+
+/**
  * Test both pre and post operators.
  */
 TEST(SatCounterTest, PrePostOperators)
@@ -129,3 +197,108 @@
     ASSERT_EQ(counter_pre, 0);
     ASSERT_EQ(counter_post, 0);
 }
+
+/**
+ * Test copy and move for both constructor and assignment.
+ */
+TEST(SatCounterTest, CopyMove)
+{
+    const unsigned bits = 3;
+    const unsigned max_value = (1 << bits) - 1;
+    const unsigned initial_value = 1;
+    SatCounter counter(bits, initial_value);
+    SatCounter deep_copy(1);
+    SatCounter counter_copy(2);
+
+    // Increase counter value so that we can check if the inner counter is
+    // being copied
+    counter++;
+
+    // Copy counter using both the copy constructor and the copy assignment
+    SatCounter counter_copy_constructor(counter);
+    deep_copy = counter_copy = counter;
+    ASSERT_EQ(counter_copy_constructor, initial_value + 1);
+    ASSERT_EQ(counter_copy, initial_value + 1);
+    ASSERT_EQ(deep_copy, initial_value + 1);
+
+    // Make sure max value is the same for all of them, and that modifying
+    // the copies does not modify the original
+    for (int i = 0; i < 2*max_value; i++) {
+        counter_copy_constructor++;
+        counter_copy++;
+        deep_copy++;
+    }
+    ASSERT_EQ(counter, initial_value + 1);
+    ASSERT_EQ(counter_copy_constructor, max_value);
+    ASSERT_EQ(counter_copy, max_value);
+    ASSERT_EQ(deep_copy, max_value);
+
+    // Make sure initial value is the same for all of them
+    counter_copy_constructor.reset();
+    counter_copy.reset();
+    deep_copy.reset();
+    ASSERT_EQ(counter_copy_constructor, initial_value);
+    ASSERT_EQ(counter_copy, initial_value);
+    ASSERT_EQ(deep_copy, initial_value);
+
+    // Now check move
+    SatCounter counter_move_constructor(std::move(counter));
+    ASSERT_EQ(counter, 0);
+    ASSERT_EQ(counter_move_constructor, initial_value + 1);
+
+    SatCounter counter_move(bits);
+    counter_move = std::move(counter_move_constructor);
+    ASSERT_EQ(counter_move_constructor, 0);
+    ASSERT_EQ(counter_move, initial_value + 1);
+}
+
+/**
+ * Test add-assignment and subtract assignment.
+ */
+TEST(SatCounterTest, AddSubAssignment)
+{
+    const unsigned bits = 3;
+    const unsigned max_value = (1 << bits) - 1;
+    SatCounter counter(bits);
+    SatCounter other(bits, 2);
+    SatCounter saturated_counter(bits, max_value);
+    int value = 0;
+
+    // Test add-assignment for a few random values and then saturate
+    counter += 2;
+    value += 2;
+    ASSERT_EQ(counter, value);
+    counter += 3;
+    value += 3;
+    ASSERT_EQ(counter, value);
+    counter += max_value;
+    value = max_value;
+    ASSERT_EQ(counter, value);
+
+    // Test subtract-assignment for a few random values until back to zero
+    counter -= 2;
+    value -= 2;
+    ASSERT_EQ(counter, value);
+    counter -= 3;
+    value -= 3;
+    ASSERT_EQ(counter, value);
+    counter -= max_value;
+    value = 0;
+    ASSERT_EQ(counter, value);
+
+    // Test add-assignment of other saturating counter
+    counter += other;
+    value += other;
+    ASSERT_EQ(counter, value);
+    counter += saturated_counter;
+    value = max_value;
+    ASSERT_EQ(counter, saturated_counter);
+
+    // Test subtract-assignment of other saturating counter
+    counter -= other;
+    value -= other;
+    ASSERT_EQ(counter, value);
+    counter -= saturated_counter;
+    ASSERT_EQ(counter, 0);
+}
+