Repository: DynamoRIO/dynamorio
Pull request #7314: i#7287: Decouple Cache Replacement Policies.
Author: login: oralmer
Created: 2025-03-02T13:15:54Z | Merged: 2025-03-24T12:10:33Z

Add `cache_replacement_policy_t` - An interface that allows defining a cache replacement policy used by `caching_device_t`

We now no longer need to re-implement a cache for each replacement policy, instead passing a `unique_ptr` to a policy. The policies themselves can implement the replacement procedures with the appropriate data structures. Any user can add his own replacement policies and pass them to any caching device.

Fixed all relevant uses and tests.

Removed all subclasses re-implementing policies.

Had to implement a small functionality change in `fifo` - Previously, the fifo cache policy in `cache_fifo_t` ignored invalidations, but now that invalidations are handled by the caching device and replacement by the policy, we can't completely ignore them. There don't seem to be any uses that depend on it, and I think this design makes more sense, but it does constitute a functionality change. Should we make an effort to support it? We can move the valid bits into the policy, for instance, but I personally think this is cleaner - Now it actually implements FIFO, rather than round robin.

There is a cache policy test in `unit_test_cache_replacement_policy`, but I can add another that looks directly at the cache policies if you prefer.

Note that some files show as "renamed" - They are actually deleted files, and new cache policies with similar names, and this confuses git.

Fixes #7287

Linked issue #7287: Refactor Cache Replacement Policies to Allow Reuse Between Caches and TLBs.
Currently, we have separate deriving classes of `caching_device_t` that each implement a certain cache replacement policy - `tlb_t` has only LFU, `cache_lru_t` has only LRU etc. These classes cannot share their implementation details - If I want to use LRU in the TLB, even though we have the correct code to implement it over  `caching_device_t`'s data members.

We should find a way to share this code across the various implementation, and make newly implemented code shares as well.

One proposal suggested by @brettcoon is as follows:

 > One possibility is to put all the caching policy implementations in helper methods in the shared caching_device.h/.cpp files, and then let the policy-specific class overrides in cache_t and tlb_t invoke the appropriate caching_device_t helper routines.

>So for example the LRU policy currently implemented in cache_lru_t::get_next_way_to_replace() in cache_lru.cpp would move to caching_device.h/cpp as caching_device_t::get_next_way_to_replace_lru(), and  cache_lru_t::get_next_way_to_replace() would just call get_next_way_to_replace_lru().  Similarly, cache_lru_t::access_update() would be available as caching_device_t::access_update_lru().

>That seems like the easiest way to share the cache device policy code.

Another option could be having an `{lru,plru,lfu,..}_replacement_policy` object held by `caching_device_t` that handles access updates + choosing which way to replace given a block and way. This way we won't have to keep all data structures for all relevant replacement policies (bit fields, lru queues, lfu counters etc) in the base class, and build them all for every instance.

Then we could pass the replacement policy object at construction depending on the requested policy and use it. We could also have this as a template and construct it inside `caching_device_t`'s constructor, if we want to save in polymorphism and save the user building it himself from the parameters.

@derekbruening @brettcoon 

Diff:
diff --git a/clients/drcachesim/CMakeLists.txt b/clients/drcachesim/CMakeLists.txt
index 6b24cb3b43..2f3374f102 100644
--- a/clients/drcachesim/CMakeLists.txt
+++ b/clients/drcachesim/CMakeLists.txt
@@ -193,8 +193,6 @@ configure_DynamoRIO_standalone(drmemtrace_invariant_checker)
 add_exported_library(drmemtrace_simulator STATIC
   simulator/simulator.cpp
   simulator/cache.cpp
-  simulator/cache_lru.cpp
-  simulator/cache_fifo.cpp
   simulator/cache_miss_analyzer.cpp
   simulator/caching_device.cpp
   simulator/caching_device_stats.cpp
@@ -204,6 +202,11 @@ add_exported_library(drmemtrace_simulator STATIC
   simulator/snoop_filter.cpp
   simulator/tlb.cpp
   simulator/tlb_simulator.cpp
+  simulator/create_cache_replacement_policy.cpp
+  simulator/policy_bit_plru.cpp
+  simulator/policy_fifo.cpp
+  simulator/policy_lfu.cpp
+  simulator/policy_lru.cpp
   )
 
 add_exported_library(drmemtrace_record_filter STATIC
diff --git a/clients/drcachesim/common/options.cpp b/clients/drcachesim/common/options.cpp
index bfbe43a07a..55e1b9bf84 100644
--- a/clients/drcachesim/common/options.cpp
+++ b/clients/drcachesim/common/options.cpp
@@ -502,7 +502,9 @@ droption_t<std::string>
                           "Specifies the replacement policy for TLBs. "
                           "Supported policies: " REPLACE_POLICY_LFU
                           " (Least Frequently Used), " REPLACE_POLICY_BIT_PLRU
-                          " (Pseudo Least Recently Used).");
+                          " (Pseudo Least Recently Used) " REPLACE_POLICY_LRU
+                          " (Least Recently Used) " REPLACE_POLICY_FIFO
+                          " (First-In-First-Out)");
 
 droption_t<std::string>
     op_tool(DROPTION_SCOPE_FRONTEND,
diff --git a/clients/drcachesim/simulator/cache.cpp b/clients/drcachesim/simulator/cache.cpp
index a98255154e..09deeff5e7 100644
--- a/clients/drcachesim/simulator/cache.cpp
+++ b/clients/drcachesim/simulator/cache.cpp
@@ -35,11 +35,13 @@
 #include <stddef.h>
 
 #include <cstdint>
+#include <memory>
 #include <utility>
 #include <vector>
 
 #include "memref.h"
 #include "cache_line.h"
+#include "cache_replacement_policy.h"
 #include "cache_stats.h"
 #include "caching_device.h"
 #include "caching_device_block.h"
@@ -55,6 +57,7 @@ class snoop_filter_t;
 bool
 cache_t::init(int associativity, int64_t line_size, int64_t total_size,
               caching_device_t *parent, caching_device_stats_t *stats,
+              std::unique_ptr<cache_replacement_policy_t> replacement_policy,
               prefetcher_t *prefetcher, cache_inclusion_policy_t inclusion_policy,
               bool coherent_cache, int id, snoop_filter_t *snoop_filter,
               const std::vector<caching_device_t *> &children)
@@ -65,9 +68,9 @@ cache_t::init(int associativity, int64_t line_size, int64_t total_size,
     // convert total_size to num_blocks to fit for caching_device_t::init
     int64_t num_lines = total_size / line_size;
 
-    return caching_device_t::init(associativity, line_size, num_lines, parent, stats,
-                                  prefetcher, inclusion_policy, coherent_cache, id,
-                                  snoop_filter, children);
+    return caching_device_t::init(
+        associativity, line_size, num_lines, parent, stats, std::move(replacement_policy),
+        prefetcher, inclusion_policy, coherent_cache, id, snoop_filter, children);
 }
 
 void
@@ -95,6 +98,8 @@ cache_t::flush(const memref_t &memref)
         auto block_way = find_caching_device_block(tag);
         if (block_way.first == nullptr)
             continue;
+        replacement_policy_->invalidation_update(compute_block_idx(tag),
+                                                 block_way.second);
         invalidate_caching_device_block(block_way.first);
     }
     // We flush parent_'s code cache here.
diff --git a/clients/drcachesim/simulator/cache.h b/clients/drcachesim/simulator/cache.h
index d977cefc52..ff26b0a32a 100644
--- a/clients/drcachesim/simulator/cache.h
+++ b/clients/drcachesim/simulator/cache.h
@@ -37,9 +37,11 @@
 #define _CACHE_H_ 1
 
 #include <string>
+#include <memory>
 #include <vector>
 
 #include "cache_line.h"
+#include "cache_replacement_policy.h"
 #include "cache_stats.h"
 #include "caching_device.h"
 #include "memref.h"
@@ -63,6 +65,7 @@ class cache_t : public caching_device_t {
     bool
     init(int associativity, int64_t line_size, int64_t total_size,
          caching_device_t *parent, caching_device_stats_t *stats,
+         std::unique_ptr<cache_replacement_policy_t> replacement_policy,
          prefetcher_t *prefetcher = nullptr,
          cache_inclusion_policy_t inclusion_policy =
              cache_inclusion_policy_t::NON_INC_NON_EXC,
diff --git a/clients/drcachesim/simulator/cache_fifo.cpp b/clients/drcachesim/simulator/cache_fifo.cpp
deleted file mode 100644
index cd4903f6d8..0000000000
--- a/clients/drcachesim/simulator/cache_fifo.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/* **********************************************************
- * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
- * **********************************************************/
-
-/*
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- *   this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- *   this list of conditions and the following disclaimer in the documentation
- *   and/or other materials provided with the distribution.
- *
- * * Neither the name of Google, Inc. nor the names of its contributors may be
- *   used to endorse or promote products derived from this software without
- *   specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- */
-
-#include "cache_fifo.h"
-
-#include <assert.h>
-
-#include <cstdint>
-#include <vector>
-
-#include "cache.h"
-#include "caching_device.h"
-#include "caching_device_block.h"
-#include "caching_device_stats.h"
-#include "prefetcher.h"
-#include "snoop_filter.h"
-
-namespace dynamorio {
-namespace drmemtrace {
-
-// For the FIFO/Round-Robin implementation, all the cache blocks in a set are organized
-// as a FIFO. The counters of a set of blocks simulate the replacement pointer.
-// The counter of the victim block is 1, and others are 0.
-// While replacing happens, the victim block will be replaced and its counter will
-// be cleared. The counter of the next block will be set to 1.
-
-bool
-cache_fifo_t::init(int associativity, int64_t block_size, int64_t total_size,
-                   caching_device_t *parent, caching_device_stats_t *stats,
-                   prefetcher_t *prefetcher, cache_inclusion_policy_t inclusion_policy,
-                   bool coherent_cache, int id, snoop_filter_t *snoop_filter,
-                   const std::vector<caching_device_t *> &children)
-{
-    // Works in the same way as the base class,
-    // except that the counters are initialized in a different way.
-
-    bool ret_val =
-        cache_t::init(associativity, block_size, total_size, parent, stats, prefetcher,
-                      inclusion_policy, coherent_cache, id, snoop_filter, children);
-    if (ret_val == false)
-        return false;
-
-    // Create a replacement pointer for each set, and
-    // initialize it to point to the first block.
-    for (int i = 0; i < blocks_per_way_; i++) {
-        get_caching_device_block(i * associativity_, 0).counter_ = 1;
-    }
-    return true;
-}
-
-void
-cache_fifo_t::access_update(int block_idx, int way)
-{
-    // Since the FIFO replacement policy is independent of cache hit,
-    // we do not need to do anything here.
-    return;
-}
-
-// This method replaces the current victim way and returns the next victim way
-// to be replaced. As opposed to get_next_way_to_replace() which just returns the
-// next way to be replaced without updating the cache state, replace_which_way()
-// updates the cache state.
-int
-cache_fifo_t::replace_which_way(int block_idx)
-{
-    int victim_way = get_next_way_to_replace(block_idx);
-    // clear the counter of the victim block
-    get_caching_device_block(block_idx, victim_way).counter_ = 0;
-    // set the next block as victim
-    get_caching_device_block(block_idx, (victim_way + 1) & (associativity_ - 1))
-        .counter_ = 1;
-    return victim_way;
-}
-
-// This method just returns the next way to be replaced without actually
-// replacing it, hence doesn't have a side-effect.
-int
-cache_fifo_t::get_next_way_to_replace(const int block_idx) const
-{
-    for (int i = 0; i < associativity_; i++) {
-        // We return the block whose counter is 1.
-        if (get_caching_device_block(block_idx, i).counter_ == 1) {
-            return i;
-        }
-    }
-
-    assert(false);
-    return 0;
-}
-
-} // namespace drmemtrace
-} // namespace dynamorio
diff --git a/clients/drcachesim/simulator/cache_lru.cpp b/clients/drcachesim/simulator/cache_lru.cpp
deleted file mode 100644
index 7f023de5a6..0000000000
--- a/clients/drcachesim/simulator/cache_lru.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-/* **********************************************************
- * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
- * **********************************************************/
-
-/*
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- *   this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- *   this list of conditions and the following disclaimer in the documentation
- *   and/or other materials provided with the distribution.
- *
- * * Neither the name of Google, Inc. nor the names of its contributors may be
- *   used to endorse or promote products derived from this software without
- *   specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- */
-
-#include "cache_lru.h"
-
-#include <cstdint>
-#include <vector>
-
-#include "cache.h"
-#include "caching_device.h"
-#include "caching_device_block.h"
-#include "caching_device_stats.h"
-#include "prefetcher.h"
-#include "snoop_filter.h"
-
-namespace dynamorio {
-namespace drmemtrace {
-
-// For LRU implementation, we use the cache line counter to represent
-// how recently a cache line is accessed.
-// The count value 0 means the most recent access, and the cache line with the
-// highest counter value will be picked for replacement in replace_which_way.
-
-bool
-cache_lru_t::init(int associativity, int64_t block_size, int64_t total_size,
-                  caching_device_t *parent, caching_device_stats_t *stats,
-                  prefetcher_t *prefetcher, cache_inclusion_policy_t inclusion_policy,
-                  bool coherent_cache, int id, snoop_filter_t *snoop_filter,
-                  const std::vector<caching_device_t *> &children)
-{
-    // Works in the same way as the base class,
-    // except that the counters are initialized in a different way.
-
-    bool ret_val =
-        cache_t::init(associativity, block_size, total_size, parent, stats, prefetcher,
-                      inclusion_policy, coherent_cache, id, snoop_filter, children);
-    if (ret_val == false)
-        return false;
-
-    // Initialize line counters with 0, 1, 2, ..., associativity - 1.
-    for (int i = 0; i < blocks_per_way_; i++) {
-        for (int way = 0; way < associativity_; ++way) {
-            get_caching_device_block(i * associativity_, way).counter_ = way;
-        }
-    }
-    return true;
-}
-
-void
-cache_lru_t::access_update(int block_idx, int way)
-{
-    int cnt = get_caching_device_block(block_idx, way).counter_;
-    // Optimization: return early if it is a repeated access.
-    if (cnt == 0)
-        return;
-    // We inc all the counters that are not larger than cnt for LRU.
-    for (int i = 0; i < associativity_; ++i) {
-        if (i != way && get_caching_device_block(block_idx, i).counter_ <= cnt)
-            get_caching_device_block(block_idx, i).counter_++;
-    }
-    // Clear the counter for LRU.
-    get_caching_device_block(block_idx, way).counter_ = 0;
-}
-
-int
-cache_lru_t::replace_which_way(int block_idx)
-{
-    return get_next_way_to_replace(block_idx);
-}
-
-int
-cache_lru_t::get_next_way_to_replace(int block_idx) const
-{
-    // We implement LRU by picking the slot with the largest counter value.
-    int max_counter = 0;
-    int max_way = 0;
-    for (int way = 0; way < associativity_; ++way) {
-        if (get_caching_device_block(block_idx, way).tag_ == TAG_INVALID) {
-            max_way = way;
-            break;
-        }
-        if (get_caching_device_block(block_idx, way).counter_ > max_counter) {
-            max_counter = get_caching_device_block(block_idx, way).counter_;
-            max_way = way;
-        }
-    }
-    return max_way;
-}
-
-} // namespace drmemtrace
-} // namespace dynamorio
diff --git a/clients/drcachesim/simulator/cache_replacement_policy.h b/clients/drcachesim/simulator/cache_replacement_policy.h
new file mode 100644
index 0000000000..2791d3bebb
--- /dev/null
+++ b/clients/drcachesim/simulator/cache_replacement_policy.h
@@ -0,0 +1,101 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necessary information to implement a cache replacement policy,
+ * and provides a replacement-specific get_next_way_to_replace() method for
+ * `caching_device_t`.
+ *
+ * The policy receives the following updates:
+ *  - When an existing way is accessed, `access_update()` is called.
+ *  - When a way is evicted, `eviction_update()` is called on the evicted way, and
+ * `access_update()` is called on the new way immediately after. Can be called on invalid
+ * ways.
+ *  - When a way is invalidated, `invalidation_update()` is called.
+ *
+ * The policy also provides a `get_next_way_to_replace()` method that returns
+ * the next way to replace in the block. This function assumes that all ways are valid,
+ * and is called by `caching_device_t` when it cannot just replace an invalid way.
+ *
+ * Note that the policy receives the set index, not the block index as it is in
+ * `caching_device_t`, which is the index of the first way in the set when all ways are
+ * stored in a contiguous array. This can be obtained with `compute_set_index()` in
+ * caching_device_t.
+ */
+class cache_replacement_policy_t {
+public:
+    cache_replacement_policy_t(int num_sets, int associativity)
+        : associativity_(associativity)
+        , num_sets_(num_sets)
+    {
+    }
+    /// Informs the replacement policy that an access has occurred.
+    virtual void
+    access_update(int set_idx, int way) = 0;
+    /// Informs the replacement policy that an eviction has occurred.
+    virtual void
+    eviction_update(int set_idx, int way) = 0;
+    /// Informs the replacement policy that an invalidation has occurred.
+    virtual void
+    invalidation_update(int set_idx, int way) = 0;
+    /*
+     * Returns the next way to replace in the set.
+     * Assumes that all ways are valid.
+     */
+    virtual int
+    get_next_way_to_replace(int set_idx) const = 0;
+    /// Returns the name of the replacement policy.
+    virtual std::string
+    get_name() const = 0;
+
+    virtual ~cache_replacement_policy_t() = default;
+
+protected:
+    int associativity_;
+    int num_sets_;
+};
+
+} // namespace drmemtrace
+} // namespace dynamorio
+
+#endif /* _CACHE_REPLACEMENT_POLICY_H_ */
diff --git a/clients/drcachesim/simulator/cache_simulator.cpp b/clients/drcachesim/simulator/cache_simulator.cpp
index f3d0f358f6..bae1638fe6 100644
--- a/clients/drcachesim/simulator/cache_simulator.cpp
+++ b/clients/drcachesim/simulator/cache_simulator.cpp
@@ -51,12 +51,11 @@
 #include "file_reader.h"
 #include "ipc_reader.h"
 #include "cache.h"
-#include "cache_fifo.h"
-#include "cache_lru.h"
 #include "cache_simulator_create.h"
 #include "cache_stats.h"
 #include "caching_device.h"
 #include "caching_device_stats.h"
+#include "create_cache_replacement_policy.h"
 #include "prefetcher.h"
 #include "simulator.h"
 #include "snoop_filter.h"
@@ -100,12 +99,7 @@ cache_simulator_t::cache_simulator_t(const cache_simulator_knobs_t &knobs,
 
     // This configuration allows for one shared LLC only.
     std::string cache_name = "LL";
-    cache_t *llc = create_cache(cache_name, knobs_.replace_policy);
-    if (llc == NULL) {
-        error_string_ = "create_cache failed for the LLC";
-        success_ = false;
-        return;
-    }
+    cache_t *llc = new cache_t(cache_name);
 
     all_caches_[cache_name] = llc;
     llcaches_[cache_name] = llc;
@@ -131,7 +125,10 @@ cache_simulator_t::cache_simulator_t(const cache_simulator_knobs_t &knobs,
 
     if (!llc->init(knobs_.LL_assoc, (int)knobs_.line_size, (int)knobs_.LL_size, NULL,
                    new cache_stats_t((int)knobs_.line_size, knobs_.LL_miss_file,
-                                     warmup_enabled_))) {
+                                     warmup_enabled_),
+                   create_cache_replacement_policy(
+                       knobs_.replace_policy, (int)knobs_.LL_size / (int)knobs_.line_size,
+                       (int)knobs_.LL_assoc))) {
         error_string_ =
             "Usage error: failed to initialize LL cache.  Ensure size divided by "
             "associativity is a power of 2, that the total size is a multiple "
@@ -150,32 +147,28 @@ cache_simulator_t::cache_simulator_t(const cache_simulator_knobs_t &knobs,
 
     for (unsigned int i = 0; i < knobs_.num_cores; i++) {
         cache_name = "L1I" + (knobs_.num_cores > 0 ? std::to_string(i) : "");
-        l1_icaches_[i] = create_cache(cache_name, knobs_.replace_policy);
-        if (l1_icaches_[i] == NULL) {
-            error_string_ = "create_cache failed for an l1_icache";
-            success_ = false;
-            return;
-        }
+        l1_icaches_[i] = new cache_t(cache_name);
         snooped_caches_[2 * i] = l1_icaches_[i];
         cache_name = "L1D" + (knobs_.num_cores > 0 ? std::to_string(i) : "");
-        l1_dcaches_[i] = create_cache(cache_name, knobs_.replace_policy);
-        if (l1_dcaches_[i] == NULL) {
-            error_string_ = "create_cache failed for an l1_dcache";
-            success_ = false;
-            return;
-        }
+        l1_dcaches_[i] = new cache_t(cache_name);
         snooped_caches_[(2 * i) + 1] = l1_dcaches_[i];
 
         if (!l1_icaches_[i]->init(
                 knobs_.L1I_assoc, (int)knobs_.line_size, (int)knobs_.L1I_size, llc,
                 new cache_stats_t((int)knobs_.line_size, "", warmup_enabled_,
                                   knobs_.model_coherence),
+                create_cache_replacement_policy(
+                    knobs_.replace_policy, (int)knobs_.L1I_size / (int)knobs_.line_size,
+                    (int)knobs_.L1I_assoc) /*replacement_policy*/,
                 nullptr /*prefetcher*/, cache_inclusion_policy_t::NON_INC_NON_EXC,
                 knobs_.model_coherence, 2 * i, snoop_filter_) ||
             !l1_dcaches_[i]->init(
                 knobs_.L1D_assoc, (int)knobs_.line_size, (int)knobs_.L1D_size, llc,
                 new cache_stats_t((int)knobs_.line_size, "", warmup_enabled_,
                                   knobs_.model_coherence),
+                create_cache_replacement_policy(
+                    knobs_.replace_policy, (int)knobs_.L1D_size / (int)knobs_.line_size,
+                    (int)knobs_.L1D_assoc) /*replacement_policy*/,
                 get_prefetcher(knobs_.data_prefetcher),
                 cache_inclusion_policy_t::NON_INC_NON_EXC, knobs_.model_coherence,
                 (2 * i) + 1, snoop_filter_)) {
@@ -245,14 +238,7 @@ cache_simulator_t::cache_simulator_t(std::istream *config_file,
     // Create all the caches in the hierarchy.
     for (const auto &cache_params_it : cache_params) {
         std::string cache_name = cache_params_it.first;
-        const auto &cache_config = cache_params_it.second;
-
-        cache_t *cache = create_cache(cache_name, cache_config.replace_policy);
-        if (cache == NULL) {
-            success_ = false;
-            return;
-        }
-
+        cache_t *cache = new cache_t(cache_name);
         all_caches_[cache_name] = cache;
     }
 
@@ -358,6 +344,10 @@ cache_simulator_t::cache_simulator_t(std::istream *config_file,
                          (int)cache_config.size, parent_,
                          new cache_stats_t((int)knobs_.line_size, cache_config.miss_file,
                                            warmup_enabled_, is_coherent_),
+                         create_cache_replacement_policy(cache_config.replace_policy,
+                                                         (int)cache_config.size /
+                                                             (int)knobs_.line_size,
+                                                         (int)cache_config.assoc),
                          get_prefetcher(cache_config.prefetcher), inclusion_policy,
                          is_coherent_, is_snooped ? snoop_id : -1,
                          is_snooped ? snoop_filter_ : nullptr, children)) {
@@ -714,23 +704,6 @@ cache_simulator_t::get_knobs() const
     return knobs_;
 }
 
-cache_t *
-cache_simulator_t::create_cache(const std::string &name, const std::string &policy)
-{
-    if (policy == REPLACE_POLICY_NON_SPECIFIED || // default LRU
-        policy == REPLACE_POLICY_LRU)             // set to LRU
-        return new cache_lru_t(name);
-    if (policy == REPLACE_POLICY_LFU) // set to LFU
-        return new cache_t(name);
-    if (policy == REPLACE_POLICY_FIFO) // set to FIFO
-        return new cache_fifo_t(name);
-
-    // undefined replacement policy
-    ERRMSG("Usage error: undefined replacement policy. "
-           "Please choose " REPLACE_POLICY_LRU " or " REPLACE_POLICY_LFU ".\n");
-    return NULL;
-}
-
 // Access snoop filter stats.
 int64_t
 cache_simulator_t::get_num_snooped_caches(void)
diff --git a/clients/drcachesim/simulator/cache_simulator.h b/clients/drcachesim/simulator/cache_simulator.h
index a4540cf911..116c248645 100644
--- a/clients/drcachesim/simulator/cache_simulator.h
+++ b/clients/drcachesim/simulator/cache_simulator.h
@@ -110,9 +110,6 @@ class cache_simulator_t : public simulator_t {
     get_knobs() const;
 
 protected:
-    // Create a cache_t object with a specific replacement policy.
-    virtual cache_t *
-    create_cache(const std::string &name, const std::string &policy);
     prefetcher_t *
     get_prefetcher(std::string prefetcher_name);
 
diff --git a/clients/drcachesim/simulator/caching_device.cpp b/clients/drcachesim/simulator/caching_device.cpp
index 05ab7d2abd..d132c35bc2 100644
--- a/clients/drcachesim/simulator/caching_device.cpp
+++ b/clients/drcachesim/simulator/caching_device.cpp
@@ -38,11 +38,13 @@
 #include <cstdint>
 #include <functional>
 #include <limits>
+#include <memory>
 #include <string>
 #include <unordered_map>
 #include <utility>
 #include <vector>
 
+#include "cache_replacement_policy.h"
 #include "caching_device_block.h"
 #include "caching_device_stats.h"
 #include "memref.h"
@@ -78,21 +80,28 @@ caching_device_t::~caching_device_t()
 bool
 caching_device_t::init(int associativity, int64_t block_size, int64_t num_blocks,
                        caching_device_t *parent, caching_device_stats_t *stats,
+                       std::unique_ptr<cache_replacement_policy_t> replacement_policy,
                        prefetcher_t *prefetcher,
                        cache_inclusion_policy_t inclusion_policy, bool coherent_cache,
                        int id, snoop_filter_t *snoop_filter,
                        const std::vector<caching_device_t *> &children)
 {
     // Assume cache has nonzero capacity.
-    if (associativity < 1 || num_blocks < 1)
+    if (associativity < 1 || num_blocks < 1) {
         return false;
+    }
     // Assume caching device block size is at least 4 bytes.
-    if (!IS_POWER_OF_2(block_size) || block_size < 4)
+    if (!IS_POWER_OF_2(block_size) || block_size < 4) {
         return false;
-    if (stats == NULL)
+    }
+    if (stats == NULL) {
         return false; // A stats must be provided for perf: avoid conditional code
-    else if (!*stats)
+    } else if (!*stats) {
+        return false;
+    }
+    if (replacement_policy == NULL) {
         return false;
+    }
     associativity_ = associativity;
     block_size_ = block_size;
     num_blocks_ = num_blocks;
@@ -124,6 +133,8 @@ caching_device_t::init(int associativity, int64_t block_size, int64_t num_blocks
     inclusion_policy_ = inclusion_policy;
     children_ = children;
 
+    replacement_policy_ = std::move(replacement_policy);
+
     return true;
 }
 
@@ -263,38 +274,25 @@ caching_device_t::request(const memref_t &memref_in)
 void
 caching_device_t::access_update(int block_idx, int way)
 {
-    // We just inc the counter for LFU.  We live with any blip on overflow.
-    get_caching_device_block(block_idx, way).counter_++;
+    replacement_policy_->access_update(compute_set_index(block_idx), way);
 }
 
 int
 caching_device_t::replace_which_way(int block_idx)
 {
-    int min_way = get_next_way_to_replace(block_idx);
-    // Clear the counter for LFU.
-    get_caching_device_block(block_idx, min_way).counter_ = 0;
-    return min_way;
+    int way_to_replace = get_next_way_to_replace(block_idx);
+    replacement_policy_->eviction_update(compute_set_index(block_idx), way_to_replace);
+    return way_to_replace;
 }
 
 int
 caching_device_t::get_next_way_to_replace(const int block_idx) const
 {
-    // The base caching device class only implements LFU.
-    // A subclass can override this and access_update() to implement
-    // some other scheme.
-    int min_counter = 0; /* avoid "may be used uninitialized" with GCC 4.4.7 */
-    int min_way = 0;
     for (int way = 0; way < associativity_; ++way) {
-        if (get_caching_device_block(block_idx, way).tag_ == TAG_INVALID) {
-            min_way = way;
-            break;
-        }
-        if (way == 0 || get_caching_device_block(block_idx, way).counter_ < min_counter) {
-            min_counter = get_caching_device_block(block_idx, way).counter_;
-            min_way = way;
-        }
+        if (get_caching_device_block(block_idx, way).tag_ == TAG_INVALID)
+            return way;
     }
-    return min_way;
+    return replacement_policy_->get_next_way_to_replace(compute_set_index(block_idx));
 }
 
 void
@@ -306,6 +304,8 @@ caching_device_t::invalidate(addr_t tag, invalidation_type_t invalidation_type)
         loaded_blocks_--;
         stats_->invalidate(invalidation_type);
         // Invalidate last_tag_ if it was this tag.
+        replacement_policy_->invalidation_update(
+            compute_set_index(compute_block_idx(tag)), block_way.second);
         if (last_tag_ == tag) {
             last_tag_ = TAG_INVALID;
         }
diff --git a/clients/drcachesim/simulator/caching_device.h b/clients/drcachesim/simulator/caching_device.h
index fbdae6296b..5672fce9fb 100644
--- a/clients/drcachesim/simulator/caching_device.h
+++ b/clients/drcachesim/simulator/caching_device.h
@@ -40,11 +40,13 @@
 #include <cstddef>
 #include <cstdint>
 #include <functional>
+#include <memory>
 #include <string>
 #include <unordered_map>
 #include <utility>
 #include <vector>
 
+#include "cache_replacement_policy.h"
 #include "caching_device_block.h"
 #include "caching_device_stats.h"
 #include "memref.h"
@@ -73,6 +75,7 @@ class caching_device_t {
     virtual bool
     init(int associativity, int64_t block_size, int64_t num_blocks,
          caching_device_t *parent, caching_device_stats_t *stats,
+         std::unique_ptr<cache_replacement_policy_t> replacement_policy,
          prefetcher_t *prefetcher = nullptr,
          cache_inclusion_policy_t inclusion_policy =
              cache_inclusion_policy_t::NON_INC_NON_EXC,
@@ -197,7 +200,12 @@ class caching_device_t {
     virtual std::string
     get_replace_policy() const
     {
-        return "LFU";
+        return replacement_policy_->get_name();
+    }
+    virtual void
+    set_replace_policy(std::unique_ptr<cache_replacement_policy_t> replacement_policy)
+    {
+        replacement_policy_ = std::move(replacement_policy);
     }
     virtual const std::string &
     get_name() const
@@ -231,6 +239,14 @@ class caching_device_t {
     {
         return (tag & blocks_per_way_mask_) * associativity_;
     }
+    virtual int
+    compute_set_index(int block_idx) const
+    {
+        // The block index points to the first way in the set, and the ways are stored
+        // in a contiguous array, so we divide by the associativity to get the block
+        // index.
+        return block_idx / associativity_;
+    }
     inline caching_device_block_t &
     get_caching_device_block(int block_idx, int way) const
     {
@@ -312,6 +328,8 @@ class caching_device_t {
         tag2block;
     bool use_tag2block_table_ = false;
 
+    mutable std::unique_ptr<cache_replacement_policy_t> replacement_policy_;
+
     // Name for this cache.
     const std::string name_;
 };
diff --git a/clients/drcachesim/simulator/create_cache_replacement_policy.cpp b/clients/drcachesim/simulator/create_cache_replacement_policy.cpp
new file mode 100644
index 0000000000..e380035014
--- /dev/null
+++ b/clients/drcachesim/simulator/create_cache_replacement_policy.cpp
@@ -0,0 +1,70 @@
+/* **********************************************************
+ * Copyright (c) 2015-2025 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "create_cache_replacement_policy.h"
+
+#include <memory>
+#include <string>
+
+#include "cache_replacement_policy.h"
+#include "policy_bit_plru.h"
+#include "policy_fifo.h"
+#include "policy_lfu.h"
+#include "policy_lru.h"
+#include "options.h"
+
+namespace dynamorio {
+namespace drmemtrace {
+
+std::unique_ptr<cache_replacement_policy_t>
+create_cache_replacement_policy(const std::string &policy, int num_sets,
+                                int associativity)
+{
+    // default LRU
+    if (policy.empty() || policy == REPLACE_POLICY_LRU) {
+        return std::unique_ptr<policy_lru_t>(new policy_lru_t(num_sets, associativity));
+    }
+    if (policy == REPLACE_POLICY_LFU) {
+        return std::unique_ptr<policy_lfu_t>(new policy_lfu_t(num_sets, associativity));
+    }
+    if (policy == REPLACE_POLICY_FIFO) {
+        return std::unique_ptr<policy_fifo_t>(new policy_fifo_t(num_sets, associativity));
+    }
+    if (policy == REPLACE_POLICY_BIT_PLRU) {
+        return std::unique_ptr<policy_bit_plru_t>(
+            new policy_bit_plru_t(num_sets, associativity));
+    }
+    return nullptr;
+}
+
+} // namespace drmemtrace
+} // namespace dynamorio
diff --git a/clients/drcachesim/simulator/create_cache_replacement_policy.h b/clients/drcachesim/simulator/create_cache_replacement_policy.h
new file mode 100644
index 0000000000..ffaaf3587e
--- /dev/null
+++ b/clients/drcachesim/simulator/create_cache_replacement_policy.h
@@ -0,0 +1,52 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CREATE_CACHE_REPLACEMENT_POLICY_H_
+#define _CREATE_CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <memory>
+#include <string>
+
+#include "cache_replacement_policy.h"
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/// Initializes and returns a specific replacement policy.
+std::unique_ptr<cache_replacement_policy_t>
+create_cache_replacement_policy(const std::string &policy, int num_sets,
+                                int associativity);
+
+} // namespace drmemtrace
+} // namespace dynamorio
+
+#endif /* _CREATE_CACHE_REPLACEMENT_POLICY_H_ */
diff --git a/clients/drcachesim/simulator/policy_bit_plru.cpp b/clients/drcachesim/simulator/policy_bit_plru.cpp
new file mode 100644
index 0000000000..8532e1303f
--- /dev/null
+++ b/clients/drcachesim/simulator/policy_bit_plru.cpp
@@ -0,0 +1,112 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "policy_bit_plru.h"
+
+#include <random>
+#include <string>
+#include <vector>
+
+#include "cache_replacement_policy.h"
+
+namespace dynamorio {
+namespace drmemtrace {
+
+policy_bit_plru_t::policy_bit_plru_t(int num_sets, int associativity, int seed)
+    : cache_replacement_policy_t(num_sets, associativity)
+    , num_ones_(num_sets, 0)
+    , gen_(seed == -1 ? std::random_device()() : seed)
+{
+    // Initialize the bit vector for each set.
+    plru_bits_.reserve(num_sets);
+    for (int i = 0; i < num_sets; ++i) {
+        plru_bits_.emplace_back(associativity, false);
+    }
+}
+
+void
+policy_bit_plru_t::access_update(int set_idx, int way)
+{
+    // Set the bit for the accessed way.
+    if (!plru_bits_[set_idx][way]) {
+        plru_bits_[set_idx][way] = true;
+        num_ones_[set_idx]++;
+    }
+    if (num_ones_[set_idx] < associativity_) {
+        // Finished.
+        return;
+    }
+    // If all bits are set, reset them.
+    for (int i = 0; i < associativity_; ++i) {
+        plru_bits_[set_idx][i] = false;
+    }
+    num_ones_[set_idx] = 1;
+    plru_bits_[set_idx][way] = true;
+}
+
+void
+policy_bit_plru_t::eviction_update(int set_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it.
+}
+
+void
+policy_bit_plru_t::invalidation_update(int set_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it.
+}
+
+int
+policy_bit_plru_t::get_next_way_to_replace(int set_idx) const
+{
+    std::vector<int> unset_bits;
+    for (int i = 0; i < associativity_; ++i) {
+        if (!plru_bits_[set_idx][i]) {
+            unset_bits.push_back(i);
+        }
+    }
+
+    if (unset_bits.empty()) {
+        // Should not reach here.
+        return -1;
+    }
+    return unset_bits[gen_() % unset_bits.size()];
+}
+
+std::string
+policy_bit_plru_t::get_name() const
+{
+    return "BIT_PLRU";
+}
+
+} // namespace drmemtrace
+} // namespace dynamorio
diff --git a/clients/drcachesim/simulator/cache_lru.h b/clients/drcachesim/simulator/policy_bit_plru.h
similarity index 63%
rename from clients/drcachesim/simulator/cache_lru.h
rename to clients/drcachesim/simulator/policy_bit_plru.h
index dfbdd38e87..744cc7e13f 100644
--- a/clients/drcachesim/simulator/cache_lru.h
+++ b/clients/drcachesim/simulator/policy_bit_plru.h
@@ -30,52 +30,50 @@
  * DAMAGE.
  */
 
-/* cache_lru: represents a single hardware cache with LRU algo.
- */
-
-#ifndef _CACHE_LRU_H_
-#define _CACHE_LRU_H_ 1
+#ifndef _BIT_PLRU_H_
+#define _BIT_PLRU_H_
 
+#include <random>
 #include <string>
 #include <vector>
 
-#include "cache.h"
-#include "prefetcher.h"
-#include "snoop_filter.h"
+#include "cache_replacement_policy.h"
 
 namespace dynamorio {
 namespace drmemtrace {
 
-class cache_lru_t : public cache_t {
+/**
+ * A replacement policy that uses a bit per way to track access frequency.
+ *
+ * On access, a way's bit is set to 1. Once all bits are set, the whole set's bits
+ * are set to 0. A random way with a 0 bit is chosen for replacement.
+ */
+class policy_bit_plru_t : public cache_replacement_policy_t {
 public:
-    explicit cache_lru_t(const std::string &name = "cache_lru")
-        : cache_t(name)
-    {
-    }
-    bool
-    init(int associativity, int64_t block_size, int64_t total_size,
-         caching_device_t *parent, caching_device_stats_t *stats,
-         prefetcher_t *prefetcher = nullptr,
-         cache_inclusion_policy_t inclusion_policy =
-             cache_inclusion_policy_t::NON_INC_NON_EXC,
-         bool coherent_cache = false, int id = -1, snoop_filter_t *snoop_filter = nullptr,
-         const std::vector<caching_device_t *> &children = {}) override;
-    std::string
-    get_replace_policy() const override
-    {
-        return "LRU";
-    }
-
-protected:
+    /// If seed is -1, a random seed will be used.
+    policy_bit_plru_t(int num_sets, int associativity, int seed = -1);
     void
-    access_update(int block_idx, int way) override;
-    int
-    replace_which_way(int block_idx) override;
+    access_update(int set_idx, int way) override;
+    void
+    eviction_update(int set_idx, int way) override;
     int
-    get_next_way_to_replace(const int block_idx) const override;
+    get_next_way_to_replace(int set_idx) const override;
+    void
+    invalidation_update(int set_idx, int way) override;
+    std::string
+    get_name() const override;
+
+    ~policy_bit_plru_t() override = default;
+
+private:
+    // A bit per way for each line.
+    std::vector<std::vector<bool>> plru_bits_;
+    // The amount of bits set to 1 for each line.
+    std::vector<int> num_ones_;
+    mutable std::mt19937 gen_;
 };
 
 } // namespace drmemtrace
 } // namespace dynamorio
 
-#endif /* _CACHE_LRU_H_ */
+#endif // _BIT_PLRU_H_
diff --git a/clients/drcachesim/simulator/policy_fifo.cpp b/clients/drcachesim/simulator/policy_fifo.cpp
new file mode 100644
index 0000000000..b0276df890
--- /dev/null
+++ b/clients/drcachesim/simulator/policy_fifo.cpp
@@ -0,0 +1,91 @@
+#/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "policy_fifo.h"
+
+#include <list>
+#include <string>
+
+#include "cache_replacement_policy.h"
+
+namespace dynamorio {
+namespace drmemtrace {
+
+policy_fifo_t::policy_fifo_t(int num_sets, int associativity)
+    : cache_replacement_policy_t(num_sets, associativity)
+{
+    // Initialize the FIFO list for each set.
+    queues_.reserve(num_sets);
+    for (int i = 0; i < num_sets; ++i) {
+        queues_.push_back(std::list<int>());
+        for (int j = 0; j < associativity; ++j) {
+            queues_[i].push_back(j);
+        }
+    }
+}
+
+void
+policy_fifo_t::access_update(int set_idx, int way)
+{
+    // Nothing to update, FIFO does not change on access.
+}
+
+void
+policy_fifo_t::eviction_update(int set_idx, int way)
+{
+    // Move the evicted way to the back of the queue.
+    auto &fifo_set = queues_[set_idx];
+    fifo_set.remove(way);
+    fifo_set.push_back(way);
+}
+
+void
+policy_fifo_t::invalidation_update(int set_idx, int way)
+{
+    // Nothing to update, FIFO does not change on invalidation.
+}
+
+int
+policy_fifo_t::get_next_way_to_replace(int set_idx) const
+{
+    // The next way to replace is at the front of the FIFO list.
+    return queues_[set_idx].front();
+}
+
+std::string
+policy_fifo_t::get_name() const
+{
+    return "FIFO";
+}
+
+} // namespace drmemtrace
+} // namespace dynamorio
diff --git a/clients/drcachesim/simulator/cache_fifo.h b/clients/drcachesim/simulator/policy_fifo.h
similarity index 63%
rename from clients/drcachesim/simulator/cache_fifo.h
rename to clients/drcachesim/simulator/policy_fifo.h
index 504e4ba246..9933b0428f 100644
--- a/clients/drcachesim/simulator/cache_fifo.h
+++ b/clients/drcachesim/simulator/policy_fifo.h
@@ -30,54 +30,46 @@
  * DAMAGE.
  */
 
-/* cache_fifo: represents a single hardware cache with FIFO algo.
- */
-
-#ifndef _CACHE_FIFO_H_
-#define _CACHE_FIFO_H_ 1
+#ifndef _FIFO_H_
+#define _FIFO_H_
 
+#include <list>
 #include <string>
 #include <vector>
 
-#include "cache.h"
-#include "prefetcher.h"
+#include "cache_replacement_policy.h"
 
 namespace dynamorio {
 namespace drmemtrace {
 
-class snoop_filter_t;
-
-class cache_fifo_t : public cache_t {
+/**
+ * A FIFO cache replacement policy.
+ *
+ * It is initialized with the ways in ascending order of their index, and ignores which
+ * ways are valid.
+ */
+class policy_fifo_t : public cache_replacement_policy_t {
 public:
-    explicit cache_fifo_t(const std::string &name = "cache_fifo")
-        : cache_t(name)
-    {
-    }
-    bool
-    init(int associativity, int64_t block_size, int64_t total_size,
-         caching_device_t *parent, caching_device_stats_t *stats,
-         prefetcher_t *prefetcher = nullptr,
-         cache_inclusion_policy_t inclusion_policy =
-             cache_inclusion_policy_t::NON_INC_NON_EXC,
-         bool coherent_cache = false, int id = -1, snoop_filter_t *snoop_filter = nullptr,
-         const std::vector<caching_device_t *> &children = {}) override;
-
-    std::string
-    get_replace_policy() const override
-    {
-        return "FIFO";
-    }
-
-protected:
+    policy_fifo_t(int num_sets, int associativity);
     void
-    access_update(int block_idx, int way) override;
-    int
-    replace_which_way(int block_idx) override;
+    access_update(int set_idx, int way) override;
+    void
+    eviction_update(int set_idx, int way) override;
+    void
+    invalidation_update(int set_idx, int way) override;
     int
-    get_next_way_to_replace(const int block_idx) const override;
+    get_next_way_to_replace(int set_idx) const override;
+    std::string
+    get_name() const override;
+
+    ~policy_fifo_t() override = default;
+
+private:
+    // FIFO queue for each line.
+    std::vector<std::list<int>> queues_;
 };
 
 } // namespace drmemtrace
 } // namespace dynamorio
 
-#endif /* _CACHE_FIFO_H_ */
+#endif // _FIFO_H_
diff --git a/clients/drcachesim/simulator/policy_lfu.cpp b/clients/drcachesim/simulator/policy_lfu.cpp
new file mode 100644
index 0000000000..80cb63813a
--- /dev/null
+++ b/clients/drcachesim/simulator/policy_lfu.cpp
@@ -0,0 +1,92 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "policy_lfu.h"
+
+#include <string>
+#include <vector>
+
+#include "cache_replacement_policy.h"
+
+namespace dynamorio {
+namespace drmemtrace {
+
+policy_lfu_t::policy_lfu_t(int num_sets, int associativity)
+    : cache_replacement_policy_t(num_sets, associativity)
+{
+    access_counts_.reserve(num_sets);
+    for (int i = 0; i < num_sets; ++i) {
+        access_counts_.emplace_back(associativity, 0);
+    }
+}
+
+void
+policy_lfu_t::access_update(int set_idx, int way)
+{
+    access_counts_[set_idx][way]++;
+}
+
+void
+policy_lfu_t::eviction_update(int set_idx, int way)
+{
+    access_counts_[set_idx][way] = 0;
+}
+
+void
+policy_lfu_t::invalidation_update(int set_idx, int way)
+{
+    access_counts_[set_idx][way] = 0;
+}
+
+int
+policy_lfu_t::get_next_way_to_replace(int set_idx) const
+{
+    // Find the way with the minimum frequency counter.
+    int min_freq = access_counts_[set_idx][0];
+    int min_way = 0;
+    for (int i = 1; i < associativity_; ++i) {
+        if (access_counts_[set_idx][i] < min_freq) {
+            min_freq = access_counts_[set_idx][i];
+            min_way = i;
+        }
+    }
+    return min_way;
+}
+
+std::string
+policy_lfu_t::get_name() const
+{
+    return "LFU";
+}
+
+} // namespace drmemtrace
+} // namespace dynamorio
diff --git a/clients/drcachesim/simulator/policy_lfu.h b/clients/drcachesim/simulator/policy_lfu.h
new file mode 100644
index 0000000000..4e9ce6df8a
--- /dev/null
+++ b/clients/drcachesim/simulator/policy_lfu.h
@@ -0,0 +1,72 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _LFU_H_
+#define _LFU_H_
+
+#include <vector>
+
+#include "cache_replacement_policy.h"
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * A Least-Frequently-Used (LFU) cache replacement policy.
+ *
+ * Count all access to each way, the way with the least accesses is evicted.
+ */
+class policy_lfu_t : public cache_replacement_policy_t {
+public:
+    policy_lfu_t(int num_sets, int associativity);
+    void
+    access_update(int set_idx, int way) override;
+    void
+    eviction_update(int set_idx, int way) override;
+    void
+    invalidation_update(int set_idx, int way) override;
+    int
+    get_next_way_to_replace(int set_idx) const override;
+    std::string
+    get_name() const override;
+
+    ~policy_lfu_t() override = default;
+
+private:
+    // Frequency counters for each way in each line.
+    std::vector<std::vector<int>> access_counts_;
+};
+
+} // namespace drmemtrace
+} // namespace dynamorio
+
+#endif // _LFU_H_
diff --git a/clients/drcachesim/simulator/policy_lru.cpp b/clients/drcachesim/simulator/policy_lru.cpp
new file mode 100644
index 0000000000..2c15669d6b
--- /dev/null
+++ b/clients/drcachesim/simulator/policy_lru.cpp
@@ -0,0 +1,106 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "policy_lru.h"
+
+#include <algorithm>
+#include <string>
+
+#include "cache_replacement_policy.h"
+
+namespace dynamorio {
+namespace drmemtrace {
+
+policy_lru_t::policy_lru_t(int num_sets, int associativity)
+    : cache_replacement_policy_t(num_sets, associativity)
+{
+    // Initialize the LRU list for each set.
+    lru_counters_.reserve(num_sets);
+    for (int i = 0; i < num_sets; ++i) {
+        lru_counters_.emplace_back(associativity, 1);
+    }
+}
+
+void
+policy_lru_t::access_update(int set_idx, int way)
+{
+    int count = lru_counters_[set_idx][way];
+    // Optimization: return early if it is a repeated access.
+    if (count == 0)
+        return;
+    // We inc all the counters that are not larger than count for LRU.
+    for (int i = 0; i < associativity_; ++i) {
+        if (i != way && lru_counters_[set_idx][i] <= count)
+            lru_counters_[set_idx][i]++;
+    }
+    // Clear the counter for LRU.
+    lru_counters_[set_idx][way] = 0;
+}
+
+void
+policy_lru_t::eviction_update(int set_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it -
+    // If the way was evicted, it is already at the end of the list.
+}
+
+void
+policy_lru_t::invalidation_update(int set_idx, int way)
+{
+    int max_counter =
+        *std::max_element(lru_counters_[set_idx].begin(), lru_counters_[set_idx].end());
+    lru_counters_[set_idx][way] = max_counter + 1;
+}
+
+int
+policy_lru_t::get_next_way_to_replace(int set_idx) const
+{
+    // We implement LRU by picking the slot with the largest counter value.
+    int max_counter = 0;
+    int max_way = 0;
+    for (int way = 0; way < associativity_; ++way) {
+        if (lru_counters_[set_idx][way] > max_counter) {
+            max_counter = lru_counters_[set_idx][way];
+            max_way = way;
+        }
+    }
+    return max_way;
+}
+
+std::string
+policy_lru_t::get_name() const
+{
+    return "LRU";
+}
+
+} // namespace drmemtrace
+} // namespace dynamorio
diff --git a/clients/drcachesim/simulator/policy_lru.h b/clients/drcachesim/simulator/policy_lru.h
new file mode 100644
index 0000000000..35bcdeba96
--- /dev/null
+++ b/clients/drcachesim/simulator/policy_lru.h
@@ -0,0 +1,74 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _LRU_H_
+#define _LRU_H_
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include "cache_replacement_policy.h"
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An LRU cache replacement policy.
+ *
+ * The way which was accessed the longest time ago is evicted.
+ */
+class policy_lru_t : public cache_replacement_policy_t {
+public:
+    policy_lru_t(int num_sets, int associativity);
+    void
+    access_update(int set_idx, int way) override;
+    void
+    eviction_update(int set_idx, int way) override;
+    void
+    invalidation_update(int set_idx, int way) override;
+    int
+    get_next_way_to_replace(int set_idx) const override;
+    std::string
+    get_name() const override;
+
+    ~policy_lru_t() override = default;
+
+private:
+    // LRU list for each set.
+    std::vector<std::vector<int>> lru_counters_;
+};
+
+} // namespace drmemtrace
+} // namespace dynamorio
+
+#endif // _LRU_H_
diff --git a/clients/drcachesim/simulator/tlb.cpp b/clients/drcachesim/simulator/tlb.cpp
index cade0536f9..83c95acb22 100644
--- a/clients/drcachesim/simulator/tlb.cpp
+++ b/clients/drcachesim/simulator/tlb.cpp
@@ -35,11 +35,6 @@
 #include <assert.h>
 #include <stddef.h>
 
-#include <optional>
-#include <random>
-#include <string>
-#include <vector>
-
 #include "caching_device.h"
 #include "caching_device_block.h"
 #include "memref.h"
@@ -143,62 +138,5 @@ tlb_t::request(const memref_t &memref_in)
     }
 }
 
-tlb_lfu_t::tlb_lfu_t(const std::string &name)
-    : tlb_t(name)
-{
-}
-
-std::string
-tlb_lfu_t::get_replace_policy() const
-{
-    return REPLACE_POLICY_LFU;
-}
-
-tlb_bit_plru_t::tlb_bit_plru_t(const std::string &name, int seed)
-    : tlb_t(name)
-    , gen_(seed == -1 ? std::random_device()() : seed)
-{
-}
-
-void
-tlb_bit_plru_t::access_update(int block_idx, int way)
-{
-    get_caching_device_block(block_idx, way).counter_ = 1;
-    for (int curr_way = 0; curr_way < associativity_; ++curr_way) {
-        if (get_caching_device_block(block_idx, curr_way).counter_ == 0) {
-            return;
-        }
-    }
-    for (int curr_way = 0; curr_way < associativity_; ++curr_way) {
-        get_caching_device_block(block_idx, curr_way).counter_ = 0;
-    }
-    get_caching_device_block(block_idx, way).counter_ = 1;
-}
-
-int
-tlb_bit_plru_t::get_next_way_to_replace(int block_idx) const
-{
-    std::vector<int> zero_counter_ways;
-    for (int way = 0; way < associativity_; ++way) {
-        caching_device_block_t &curr_block = get_caching_device_block(block_idx, way);
-        if (curr_block.tag_ == TAG_INVALID) {
-            return way;
-        }
-        if (curr_block.counter_ == 0) {
-            zero_counter_ways.push_back(way);
-        }
-    }
-    if (!zero_counter_ways.empty()) {
-        return zero_counter_ways[gen_() % zero_counter_ways.size()];
-    }
-    return -1;
-}
-
-std::string
-tlb_bit_plru_t::get_replace_policy() const
-{
-    return REPLACE_POLICY_BIT_PLRU;
-}
-
 } // namespace drmemtrace
 } // namespace dynamorio
diff --git a/clients/drcachesim/simulator/tlb.h b/clients/drcachesim/simulator/tlb.h
index e7cfdc76aa..1b3b46d42e 100644
--- a/clients/drcachesim/simulator/tlb.h
+++ b/clients/drcachesim/simulator/tlb.h
@@ -58,12 +58,6 @@ class tlb_t : public caching_device_t {
     // needs to be imposed on the parent methods invalidate(), contains_tag(), and
     // propagate_eviction() by overriding them.
 
-    // Returns the replacement policy used by this TLB.
-    // XXX: i#7287: This class currently has two subclasses by replacenent policy.
-    // We eventually want to decouple the policies from the classes.
-    std::string
-    get_replace_policy() const override = 0;
-
 protected:
     void
     init_blocks() override;
@@ -71,51 +65,6 @@ class tlb_t : public caching_device_t {
     memref_pid_t last_pid_;
 };
 
-/**
- * TLB with least frequently used replacement policy.
- *
- * LFU has us hold a counter per way in the TLB entry.
- * The counter is incremented when the way is accessed.
- * When evicting, we choose the way with the least frequent access count.
- *
- * Note that the LFU logic itself is implemented
- * in the parent class caching_device_t.
- */
-class tlb_lfu_t : public tlb_t {
-public:
-    tlb_lfu_t(const std::string &name = "tlb_lfu");
-
-    std::string
-    get_replace_policy() const override;
-};
-
-/**
- * TLB with a bit-based PLRU replacement policy.
- *
- * Bit pseudo-LRU has us hold one bit per way in the TLB entry.
- * The bit is set when the way is accessed.
- * Once all bits for a block are set, we reset them all to 0.
- * When evicting, we choose a random way with a zero bit to evict.
- *
- * When seed is -1, a seed is chosen with std::random_device().
- * XXX: Once we update our toolchains to guarantee C++17 support we
- * could use std::optional here.
- */
-class tlb_bit_plru_t : public tlb_t {
-public:
-    tlb_bit_plru_t(const std::string &name = "tlb_bit_plru", int seed = -1);
-
-    std::string
-    get_replace_policy() const override;
-
-protected:
-    void
-    access_update(int block_idx, int way) override;
-    int
-    get_next_way_to_replace(int block_idx) const override;
-    mutable std::mt19937 gen_;
-};
-
 } // namespace drmemtrace
 } // namespace dynamorio
 
diff --git a/clients/drcachesim/simulator/tlb_simulator.cpp b/clients/drcachesim/simulator/tlb_simulator.cpp
index 45e7860747..6a9f19fb0b 100644
--- a/clients/drcachesim/simulator/tlb_simulator.cpp
+++ b/clients/drcachesim/simulator/tlb_simulator.cpp
@@ -37,13 +37,14 @@
 #include <fstream>
 #include <iostream>
 #include <string>
-#include <vector>
+#include <utility>
 
 #include "analysis_tool.h"
+#include "create_cache_replacement_policy.h"
 #include "memref.h"
-#include "options.h"
 #include "utils.h"
 #include "caching_device_stats.h"
+#include "create_cache_replacement_policy.h"
 #include "simulator.h"
 #include "tlb.h"
 #include "tlb_simulator_create.h"
@@ -94,46 +95,42 @@ tlb_simulator_t::tlb_simulator_t(const tlb_simulator_knobs_t &knobs)
         lltlbs_[i] = NULL;
     }
     for (unsigned int i = 0; i < knobs_.num_cores; i++) {
-        itlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
-        if (itlbs_[i] == NULL) {
-            error_string_ = "Failed to create itlbs_";
-            success_ = false;
-            return;
-        }
-        dtlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
-        if (dtlbs_[i] == NULL) {
-            error_string_ = "Failed to create dtlbs_";
-            success_ = false;
-            return;
-        }
-        lltlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
-        if (lltlbs_[i] == NULL) {
-            error_string_ = "Failed to create lltlbs_";
-            success_ = false;
-            return;
-        }
-
+        std::string core_str = std::to_string(i);
+        itlbs_[i] = new tlb_t("itlb " + core_str);
+        dtlbs_[i] = new tlb_t("dtlb " + core_str);
+        lltlbs_[i] = new tlb_t("lltlb " + core_str);
+        auto replace_policy = create_cache_replacement_policy(
+            knobs_.TLB_replace_policy, knobs_.TLB_L1I_entries / knobs_.TLB_L1I_assoc,
+            knobs_.TLB_L1I_assoc);
         if (!itlbs_[i]->init(knobs_.TLB_L1I_assoc, (int)knobs_.page_size,
                              knobs_.TLB_L1I_entries, lltlbs_[i],
-                             new tlb_stats_t((int)knobs_.page_size))) {
+                             new tlb_stats_t((int)knobs_.page_size),
+                             std::move(replace_policy))) {
             error_string_ =
                 "Usage error: failed to initialize itlbs_. Ensure (entry number / "
                 "associativity) is a power of 2.";
             success_ = false;
             return;
         }
+        replace_policy = create_cache_replacement_policy(
+            knobs_.TLB_replace_policy, knobs_.TLB_L1D_entries / knobs_.TLB_L1D_assoc,
+            knobs_.TLB_L1D_assoc);
         if (!dtlbs_[i]->init(knobs_.TLB_L1D_assoc, (int)knobs_.page_size,
                              knobs_.TLB_L1D_entries, lltlbs_[i],
-                             new tlb_stats_t((int)knobs_.page_size))) {
+                             new tlb_stats_t((int)knobs_.page_size),
+                             std::move(replace_policy))) {
             error_string_ =
                 "Usage error: failed to initialize dtlbs_. Ensure (entry number / "
                 "associativity) is a power of 2.";
             success_ = false;
             return;
         }
-        if (!lltlbs_[i]->init(knobs_.TLB_L2_assoc, (int)knobs_.page_size,
-                              knobs_.TLB_L2_entries, NULL,
-                              new tlb_stats_t((int)knobs_.page_size))) {
+        replace_policy = create_cache_replacement_policy(
+            knobs_.TLB_replace_policy, knobs_.TLB_L2_entries / knobs_.TLB_L2_assoc,
+            knobs_.TLB_L2_assoc);
+        if (!lltlbs_[i]->init(
+                knobs_.TLB_L2_assoc, (int)knobs_.page_size, knobs_.TLB_L2_entries, NULL,
+                new tlb_stats_t((int)knobs_.page_size), std::move(replace_policy))) {
             error_string_ =
                 "Usage error: failed to initialize lltlbs_. Ensure (entry number / "
                 "associativity) is a power of 2.";
@@ -292,22 +289,5 @@ tlb_simulator_t::print_results()
     return true;
 }
 
-tlb_t *
-tlb_simulator_t::create_tlb(std::string policy)
-{
-    // XXX: i#7287: Eventually we want to decouple the replacement policies from the cache
-    // classes. Once this happens, we will need to build the policies here and pass them
-    // to the TLB class. We have implemented this as a temporary solution.
-    if (policy == REPLACE_POLICY_NON_SPECIFIED || // default LFU
-        policy == REPLACE_POLICY_LFU)             // set to LFU
-        return new tlb_lfu_t;
-    else if (policy == REPLACE_POLICY_BIT_PLRU)
-        return new tlb_bit_plru_t;
-    // undefined replacement policy
-    ERRMSG("Usage error: undefined replacement policy. "
-           "Please choose " REPLACE_POLICY_LFU " or " REPLACE_POLICY_BIT_PLRU ".\n");
-    return NULL;
-}
-
 } // namespace drmemtrace
 } // namespace dynamorio
diff --git a/clients/drcachesim/simulator/tlb_simulator.h b/clients/drcachesim/simulator/tlb_simulator.h
index b8a82499da..e8f8f9b133 100644
--- a/clients/drcachesim/simulator/tlb_simulator.h
+++ b/clients/drcachesim/simulator/tlb_simulator.h
@@ -39,6 +39,7 @@
 #include <string>
 #include <unordered_map>
 
+#include "cache_replacement_policy.h"
 #include "memref.h"
 #include "simulator.h"
 #include "tlb.h"
@@ -60,10 +61,6 @@ class tlb_simulator_t : public simulator_t {
     create_v2p_from_file(std::istream &v2p_file) override;
 
 protected:
-    // Create a tlb_t object with a specific replacement policy.
-    virtual tlb_t *
-    create_tlb(std::string policy);
-
     tlb_simulator_knobs_t knobs_;
 
     // Each CPU core contains a L1 ITLB, L1 DTLB and L2 TLB.
diff --git a/clients/drcachesim/tests/cache_replacement_policy_unit_test.cpp b/clients/drcachesim/tests/cache_replacement_policy_unit_test.cpp
index a78ec720cf..3d9ab8a2a2 100644
--- a/clients/drcachesim/tests/cache_replacement_policy_unit_test.cpp
+++ b/clients/drcachesim/tests/cache_replacement_policy_unit_test.cpp
@@ -34,10 +34,14 @@
 #include <iostream>
 #undef NDEBUG
 #include <assert.h>
+#include <memory>
 #include <random>
 #include "cache_replacement_policy_unit_test.h"
-#include "simulator/cache_fifo.h"
-#include "simulator/cache_lru.h"
+#include "simulator/policy_bit_plru.h"
+#include "simulator/cache.h"
+#include "simulator/policy_fifo.h"
+#include "simulator/policy_lfu.h"
+#include "simulator/policy_lru.h"
 #include "simulator/tlb.h"
 
 namespace dynamorio {
@@ -127,72 +131,32 @@ template <class T> class caching_device_policy_test_t : public T {
         }
         return true;
     }
-};
-
-template <class T> class cache_policy_test_t : public caching_device_policy_test_t<T> {
-    int total_size_;
-
-public:
-    cache_policy_test_t(int associativity, int line_size, int total_size)
-        : caching_device_policy_test_t<T>(associativity, line_size)
-        , total_size_(total_size)
-    {
-    }
 
+    // Note that caches expect size as total size, while TLBs expect size as
+    // number of entries.
     void
-    initialize_cache()
+    initialize_cache(std::unique_ptr<cache_replacement_policy_t> replacement_policy,
+                     int size)
     {
         caching_device_stats_t *stats =
             new cache_stats_t(this->line_size_, /*miss_file=*/"",
                               /*warmup_enabled=*/true);
-        if (!this->init(this->associativity_, this->line_size_, total_size_, nullptr,
-                        stats, nullptr)) {
+        if (!this->init(this->associativity_, this->line_size_, size, nullptr, stats,
+                        std::move(replacement_policy), nullptr)) {
             std::cerr << this->get_replace_policy() << " cache failed to initialize\n";
             exit(1);
         }
     }
 };
 
-template <class T> class tlb_policy_test_t : public caching_device_policy_test_t<T> {
-    int entries_;
-
-public:
-    tlb_policy_test_t(int associativity, int line_size, int entries)
-        : caching_device_policy_test_t<T>(associativity, line_size)
-        , entries_(entries)
-    {
-    }
-
-    void
-    initialize_cache()
-    {
-        caching_device_stats_t *stats =
-            new cache_stats_t(this->line_size_, /*miss_file=*/"",
-                              /*warmup_enabled=*/true);
-        if (!this->init(this->associativity_, this->line_size_, entries_, nullptr,
-                        stats)) {
-            std::cerr << this->get_replace_policy() << " tlb failed to initialize\n";
-            exit(1);
-        }
-    }
-};
-
-template <class T> class seeded_tlb_policy_test_t : public tlb_policy_test_t<T> {
-public:
-    seeded_tlb_policy_test_t(int associativity, int line_size, int entries)
-        : tlb_policy_test_t<T>(associativity, line_size, entries)
-    {
-        this->gen_ = std::mt19937(0);
-    }
-};
-
 void
 unit_test_cache_lru_four_way()
 {
-    cache_policy_test_t<cache_lru_t> cache_lru_test(/*associativity=*/4,
-                                                    /*line_size=*/32,
-                                                    /*total_size=*/256);
-    cache_lru_test.initialize_cache();
+    caching_device_policy_test_t<cache_t> cache_lru_test(/*associativity=*/4,
+                                                         /*line_size=*/32);
+    cache_lru_test.initialize_cache(std::unique_ptr<policy_lru_t>(new policy_lru_t(
+                                        /*num_blocks=*/256 / 4, /*associativity=*/4)),
+                                    256);
 
     assert(cache_lru_test.get_replace_policy() == "LRU");
     assert(cache_lru_test.block_indices_are_identical(addr_vec));
@@ -228,10 +192,11 @@ unit_test_cache_lru_four_way()
 void
 unit_test_cache_lru_eight_way()
 {
-    cache_policy_test_t<cache_lru_t> cache_lru_test(/*associativity=*/8,
-                                                    /*line_size=*/64,
-                                                    /*total_size=*/1024);
-    cache_lru_test.initialize_cache();
+    caching_device_policy_test_t<cache_t> cache_lru_test(/*associativity=*/8,
+                                                         /*line_size=*/64);
+    cache_lru_test.initialize_cache(std::unique_ptr<policy_lru_t>(new policy_lru_t(
+                                        /*num_blocks=*/1024 / 8, /*associativity=*/8)),
+                                    1024);
 
     assert(cache_lru_test.get_replace_policy() == "LRU");
     assert(cache_lru_test.block_indices_are_identical(addr_vec));
@@ -274,10 +239,11 @@ unit_test_cache_lru_eight_way()
 void
 unit_test_cache_fifo_four_way()
 {
-    cache_policy_test_t<cache_fifo_t> cache_fifo_test(/*associativity=*/4,
-                                                      /*line_size=*/32,
-                                                      /*total_size=*/256);
-    cache_fifo_test.initialize_cache();
+    caching_device_policy_test_t<cache_t> cache_fifo_test(/*associativity=*/4,
+                                                          /*line_size=*/32);
+    cache_fifo_test.initialize_cache(std::unique_ptr<policy_fifo_t>(new policy_fifo_t(
+                                         /*num_blocks=*/256 / 4, /*associativity=*/4)),
+                                     256);
 
     assert(cache_fifo_test.get_replace_policy() == "FIFO");
     assert(cache_fifo_test.block_indices_are_identical(addr_vec));
@@ -299,27 +265,28 @@ unit_test_cache_fifo_four_way()
     cache_fifo_test.access_and_check(addr_vec[ADDR_H], 0); //     e F G H
     cache_fifo_test.access_and_check(addr_vec[ADDR_A], 1); //     A f G H
 
-    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_G], 1); // A f X H
+    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_G], 2); // A F x H
 
-    cache_fifo_test.access_and_check(addr_vec[ADDR_G], 2); //     A G x H
-    cache_fifo_test.access_and_check(addr_vec[ADDR_B], 3); //     A G B h
+    cache_fifo_test.access_and_check(addr_vec[ADDR_G], 1); //     A f G H
+    cache_fifo_test.access_and_check(addr_vec[ADDR_B], 3); //     A B G h
 
-    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_H], 3); // A G B x
-    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_A], 3); // X G B x
+    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_H], 3); // A B G x
+    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_A], 0); // x B G X
 
-    cache_fifo_test.access_and_check(addr_vec[ADDR_A], 0); //     x G B A
-    cache_fifo_test.access_and_check(addr_vec[ADDR_B], 0); //     x G B A
-    cache_fifo_test.access_and_check(addr_vec[ADDR_C], 1); //     C g B A
-    cache_fifo_test.access_and_check(addr_vec[ADDR_D], 2); //     C D B A
+    cache_fifo_test.access_and_check(addr_vec[ADDR_A], 3); //     A B G x
+    cache_fifo_test.access_and_check(addr_vec[ADDR_B], 3); //     A B G x
+    cache_fifo_test.access_and_check(addr_vec[ADDR_C], 2); //     A B g C
+    cache_fifo_test.access_and_check(addr_vec[ADDR_D], 1); //     A b D C
 }
 
 void
 unit_test_cache_fifo_eight_way()
 {
-    cache_policy_test_t<cache_fifo_t> cache_fifo_test(/*associativity=*/8,
-                                                      /*line_size=*/64,
-                                                      /*total_size=*/1024);
-    cache_fifo_test.initialize_cache();
+    caching_device_policy_test_t<cache_t> cache_fifo_test(/*associativity=*/8,
+                                                          /*line_size=*/64);
+    cache_fifo_test.initialize_cache(std::unique_ptr<policy_fifo_t>(new policy_fifo_t(
+                                         /*num_blocks=*/1024 / 8, /*associativity=*/8)),
+                                     1024);
 
     assert(cache_fifo_test.get_replace_policy() == "FIFO");
     assert(cache_fifo_test.block_indices_are_identical(addr_vec));
@@ -344,22 +311,23 @@ unit_test_cache_fifo_eight_way()
     cache_fifo_test.access_and_check(addr_vec[ADDR_L], 4); //     I  J  K  L  e  F  G  H
     cache_fifo_test.access_and_check(addr_vec[ADDR_L], 4); //     I  J  K  L  e  F  G  H
 
-    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_G], 4); // I  J  K  L  e  F  X  H
-    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_K], 4); // I  J  X  L  e  F  X  H
+    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_G], 6); // I  J  K  L  R  F  x  H
+    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_K], 2); // I  J  x  L  E  F  X  H
 
-    cache_fifo_test.access_and_check(addr_vec[ADDR_A], 5); //     I  J  K  L  A  f  G  H
-    cache_fifo_test.access_and_check(addr_vec[ADDR_B], 6); //     I  J  K  L  A  B  g  H
-    cache_fifo_test.access_and_check(addr_vec[ADDR_C], 7); //     I  J  K  L  A  B  C  h
-    cache_fifo_test.access_and_check(addr_vec[ADDR_D], 0); //     i  J  K  L  A  B  C  D
+    cache_fifo_test.access_and_check(addr_vec[ADDR_A], 6); //     I  J  A  L  E  F  x  H
+    cache_fifo_test.access_and_check(addr_vec[ADDR_B], 4); //     I  J  K  L  e  F  B  H
+    cache_fifo_test.access_and_check(addr_vec[ADDR_C], 5); //     i  J  K  L  C  f  B  H
+    cache_fifo_test.access_and_check(addr_vec[ADDR_D], 7); //     I  j  K  L  C  D  b  H
 }
 
 void
 unit_test_cache_lfu_four_way()
 {
-    cache_policy_test_t<cache_t> cache_lfu_test(/*associativity=*/4,
-                                                /*line_size=*/32,
-                                                /*total_size=*/256);
-    cache_lfu_test.initialize_cache();
+    caching_device_policy_test_t<cache_t> cache_lfu_test(/*associativity=*/4,
+                                                         /*line_size=*/32);
+    cache_lfu_test.initialize_cache(std::unique_ptr<policy_lfu_t>(new policy_lfu_t(
+                                        /*num_blocks=*/256 / 4, /*associativity=*/4)),
+                                    256);
 
     assert(cache_lfu_test.get_replace_policy() == "LFU");
     assert(cache_lfu_test.block_indices_are_identical(addr_vec));
@@ -396,10 +364,11 @@ unit_test_cache_lfu_four_way()
 void
 unit_test_cache_lfu_eight_way()
 {
-    cache_policy_test_t<cache_t> cache_lfu_test(/*associativity=*/8,
-                                                /*line_size=*/64,
-                                                /*total_size=*/1024);
-    cache_lfu_test.initialize_cache();
+    caching_device_policy_test_t<cache_t> cache_lfu_test(/*associativity=*/8,
+                                                         /*line_size=*/64);
+    cache_lfu_test.initialize_cache(std::unique_ptr<policy_lfu_t>(new policy_lfu_t(
+                                        /*num_blocks=*/1024 / 8, /*associativity=*/8)),
+                                    1024);
 
     assert(cache_lfu_test.get_replace_policy() == "LFU");
     assert(cache_lfu_test.block_indices_are_identical(addr_vec));
@@ -443,10 +412,12 @@ unit_test_cache_lfu_eight_way()
 void
 unit_test_tlb_plru_four_way()
 {
-    seeded_tlb_policy_test_t<tlb_bit_plru_t> tlb_plru_test(/*associativity=*/4,
-                                                           /*line_size=*/64,
-                                                           /*entries=*/4);
-    tlb_plru_test.initialize_cache();
+    caching_device_policy_test_t<tlb_t> tlb_plru_test(/*associativity=*/4,
+                                                      /*line_size=*/64);
+    tlb_plru_test.initialize_cache(
+        std::unique_ptr<policy_bit_plru_t>(
+            new policy_bit_plru_t(1 /*num_blocks*/, 4 /*associativity*/, 0 /*seed*/)),
+        4);
     assert(tlb_plru_test.get_replace_policy() == "BIT_PLRU");
     assert(tlb_plru_test.block_indices_are_identical(addr_vec));
     assert(tlb_plru_test.tags_are_different(addr_vec));
@@ -465,10 +436,11 @@ unit_test_tlb_plru_four_way()
 void
 unit_test_tlb_lfu_four_way()
 {
-    tlb_policy_test_t<tlb_lfu_t> tlb_lfu_test(/*associativity=*/4,
-                                              /*line_size=*/64,
-                                              /*entries=*/4);
-    tlb_lfu_test.initialize_cache();
+    caching_device_policy_test_t<tlb_t> tlb_lfu_test(/*associativity=*/4,
+                                                     /*line_size=*/64);
+    tlb_lfu_test.initialize_cache(std::unique_ptr<policy_lfu_t>(new policy_lfu_t(
+                                      1 /*num_blocks*/, 4 /*associativity*/)),
+                                  4);
     assert(tlb_lfu_test.get_replace_policy() == "LFU");
     assert(tlb_lfu_test.block_indices_are_identical(addr_vec));
     assert(tlb_lfu_test.tags_are_different(addr_vec));
diff --git a/clients/drcachesim/tests/drcachesim_unit_tests.cpp b/clients/drcachesim/tests/drcachesim_unit_tests.cpp
index 7238d31b53..eafbe7a31a 100644
--- a/clients/drcachesim/tests/drcachesim_unit_tests.cpp
+++ b/clients/drcachesim/tests/drcachesim_unit_tests.cpp
@@ -43,8 +43,9 @@
 #include "tlb_simulator_unit_test.h"
 #include "cache_replacement_policy_unit_test.h"
 #include "simulator/cache.h"
-#include "simulator/cache_lru.h"
 #include "simulator/cache_simulator.h"
+#include "simulator/policy_lfu.h"
+#include "simulator/policy_lru.h"
 #include "simulator/prefetcher.h"
 #include "../common/memref.h"
 #include "../common/utils.h"
@@ -463,9 +464,12 @@ unit_test_set_parent()
     cache_t child_1;
     cache_t child_2;
     cache_t parent;
-    assert(child_1.init(1, 64, 1024, nullptr, new cache_stats_t(64, "", false, false)));
-    assert(child_2.init(1, 64, 1024, nullptr, new cache_stats_t(64, "", false, false)));
-    assert(parent.init(1, 64, 1024, nullptr, new cache_stats_t(64, "", false, false)));
+    assert(child_1.init(1, 64, 1024, nullptr, new cache_stats_t(64, "", false, false),
+                        std::unique_ptr<policy_lru_t>(new policy_lru_t(1024, 1))));
+    assert(child_2.init(1, 64, 1024, nullptr, new cache_stats_t(64, "", false, false),
+                        std::unique_ptr<policy_lru_t>(new policy_lru_t(1024, 1))));
+    assert(parent.init(1, 64, 1024, nullptr, new cache_stats_t(64, "", false, false),
+                       std::unique_ptr<policy_lru_t>(new policy_lru_t(1024, 1))));
     // Test setting parent.
     child_1.set_parent(&parent);
     assert(child_1.get_parent() == &parent);
@@ -611,7 +615,6 @@ LLC {
     TEST_EQ(new_l2_misses - l2_misses,
             NUM_LOOPS * MORE_CONFLICTING_ADDRESSES - CONFLICTING_ADDRESSES);
     TEST_EQ(new_l2_hits - l2_hits, CONFLICTING_ADDRESSES);
-
     TEST_EQ(new_llc_misses - llc_misses,
             MORE_CONFLICTING_ADDRESSES - CONFLICTING_ADDRESSES);
     TEST_EQ(new_llc_hits - llc_hits, (NUM_LOOPS - 1) * MORE_CONFLICTING_ADDRESSES);
@@ -688,10 +691,12 @@ unit_test_cache_associativity()
         int total_size = LINE_SIZE * BLOCKS_PER_WAY * assoc;
         // Test access patterns that stress increasing associativity.
         for (uint32_t test_assoc = 1; test_assoc <= 2 * assoc; ++test_assoc) {
-            cache_lru_t cache;
+            cache_t cache;
             caching_device_stats_t stats(/*miss_file=*/"", LINE_SIZE);
             bool initialized =
-                cache.init(assoc, LINE_SIZE, total_size, /*parent=*/nullptr, &stats);
+                cache.init(assoc, LINE_SIZE, total_size, /*parent=*/nullptr, &stats,
+                           std::unique_ptr<policy_lru_t>(
+                               new policy_lru_t(total_size / assoc, assoc)));
             assert(initialized);
             assert(cache.get_associativity() == assoc);
             // Test start address is arbitrary.
@@ -737,10 +742,13 @@ unit_test_cache_size()
         // Access a buffer of increasing size, make sure hits + misses are expected.
         for (int buffer_size = cache_size / 2; buffer_size < cache_size * 2;
              buffer_size *= 2) {
-            cache_lru_t cache;
+            cache_t cache;
             caching_device_stats_t stats(/*miss_file=*/"", LINE_SIZE);
-            bool initialized = cache.init(associativity, LINE_SIZE, cache_size,
-                                          /*parent=*/nullptr, &stats);
+            bool initialized =
+                cache.init(associativity, LINE_SIZE, cache_size,
+                           /*parent=*/nullptr, &stats,
+                           std::unique_ptr<policy_lru_t>(new policy_lru_t(
+                               cache_size / associativity, associativity)));
             assert(initialized);
             assert(cache.get_size_bytes() == cache_size);
             static constexpr int NUM_LOOPS = 3; // Anything >=2 should work.
@@ -789,8 +797,11 @@ unit_test_cache_line_size()
             int total_cache_size = line_size * cache_line_count;
             cache_t cache;
             caching_device_stats_t stats(/*miss_file=*/"", line_size);
-            bool initialized = cache.init(ASSOCIATIVITY, line_size, total_cache_size,
-                                          /*parent=*/nullptr, &stats);
+            bool initialized =
+                cache.init(ASSOCIATIVITY, line_size, total_cache_size,
+                           /*parent=*/nullptr, &stats,
+                           std::unique_ptr<policy_lfu_t>(new policy_lfu_t(
+                               total_cache_size / ASSOCIATIVITY, ASSOCIATIVITY)));
             assert(initialized);
             auto read_count =
                 generate_1D_accesses(cache, 0, stride, total_cache_size / stride);
@@ -830,16 +841,33 @@ unit_test_cache_bad_configs()
 
     // 0 values are bad for any of these parameters.
     std::cerr << "Testing 0 parameters.\n";
-    assert(!cache.init(0, SAFE_LINE_SIZE, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats));
-    assert(!cache.init(SAFE_ASSOC, 0, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats));
-    assert(!cache.init(SAFE_ASSOC, SAFE_LINE_SIZE, 0, /*parent=*/nullptr, &stats));
+
+    assert(!cache.init(0, SAFE_LINE_SIZE, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats,
+                       std::unique_ptr<policy_lru_t>(
+                           new policy_lru_t(SAFE_CACHE_SIZE / SAFE_ASSOC, SAFE_ASSOC))));
+    assert(!cache.init(SAFE_ASSOC, 0, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats,
+                       std::unique_ptr<policy_lru_t>(
+                           new policy_lru_t(SAFE_CACHE_SIZE / SAFE_ASSOC, SAFE_ASSOC))));
+    assert(!cache.init(SAFE_ASSOC, SAFE_LINE_SIZE, 0, /*parent=*/nullptr, &stats,
+                       std::unique_ptr<policy_lru_t>(
+                           new policy_lru_t(SAFE_CACHE_SIZE / SAFE_ASSOC, SAFE_ASSOC))));
+    assert(!cache.init(SAFE_ASSOC, SAFE_LINE_SIZE, SAFE_CACHE_SIZE, /*parent=*/nullptr,
+                       &stats, nullptr));
 
     // Test other bad line sizes: <4 and/or non-power-of-two.
     std::cerr << "Testing bad line size parameters.\n";
-    assert(!cache.init(SAFE_ASSOC, 1, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats));
-    assert(!cache.init(SAFE_ASSOC, 2, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats));
-    assert(!cache.init(SAFE_ASSOC, 7, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats));
-    assert(!cache.init(SAFE_ASSOC, 65, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats));
+    assert(!cache.init(SAFE_ASSOC, 1, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats,
+                       std::unique_ptr<policy_lru_t>(
+                           new policy_lru_t(SAFE_CACHE_SIZE / SAFE_ASSOC, SAFE_ASSOC))));
+    assert(!cache.init(SAFE_ASSOC, 2, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats,
+                       std::unique_ptr<policy_lru_t>(
+                           new policy_lru_t(SAFE_CACHE_SIZE / SAFE_ASSOC, SAFE_ASSOC))));
+    assert(!cache.init(SAFE_ASSOC, 7, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats,
+                       std::unique_ptr<policy_lru_t>(
+                           new policy_lru_t(SAFE_CACHE_SIZE / SAFE_ASSOC, SAFE_ASSOC))));
+    assert(!cache.init(SAFE_ASSOC, 65, SAFE_CACHE_SIZE, /*parent=*/nullptr, &stats,
+                       std::unique_ptr<policy_lru_t>(
+                           new policy_lru_t(SAFE_CACHE_SIZE / SAFE_ASSOC, SAFE_ASSOC))));
 
     // Size, associativity, and line_size are related.  The requirement is that
     // size/associativity is a power-of-two, and >= line_size, so try some
@@ -853,7 +881,9 @@ unit_test_cache_bad_configs()
     };
     for (const auto &combo : bad_combinations) {
         assert(!cache.init(combo.assoc, SAFE_LINE_SIZE, combo.size, /*parent=*/nullptr,
-                           &stats));
+                           &stats,
+                           std::unique_ptr<policy_lru_t>(new policy_lru_t(
+                               SAFE_CACHE_SIZE / SAFE_ASSOC, SAFE_ASSOC))));
     }
 }
 
@@ -884,10 +914,14 @@ unit_test_cache_accessors()
                 caching_device_stats_t stats(/*miss_file=*/"", line_size);
                 // Only test LRU here.  Other replacement policy accessors are
                 // tested in the cache_replacement_policy_unit_test.
-                cache_lru_t cache(cache_name);
-                bool initialized = cache.init(associativity, line_size, total_size,
-                                              /*parent=*/nullptr, &stats,
-                                              /*prefetcher=*/nullptr, policy, coherent);
+                cache_t cache(cache_name);
+                bool initialized =
+                    cache.init(associativity, line_size, total_size,
+                               /*parent=*/nullptr, &stats,
+                               /*replacement_policy=*/
+                               std::unique_ptr<policy_lru_t>(new policy_lru_t(
+                                   total_size / associativity, associativity)),
+                               /*prefetcher=*/nullptr, policy, coherent);
                 assert(initialized);
                 assert(cache.get_stats() == &stats);
                 assert(stats.get_caching_device() == &cache);
diff --git a/clients/drcachesim/tools/filter/cache_filter.cpp b/clients/drcachesim/tools/filter/cache_filter.cpp
index 470bc3b958..84ef98988c 100644
--- a/clients/drcachesim/tools/filter/cache_filter.cpp
+++ b/clients/drcachesim/tools/filter/cache_filter.cpp
@@ -32,14 +32,16 @@
 
 #include "cache_filter.h"
 
+#include <memory>
 #include <string>
 
-#include "cache_lru.h"
-#include "memref.h"
-#include "memtrace_stream.h"
+#include "cache.h"
 #include "cache_stats.h"
 #include "caching_device_block.h"
 #include "caching_device_stats.h"
+#include "policy_lru.h"
+#include "memref.h"
+#include "memtrace_stream.h"
 #include "trace_entry.h"
 
 namespace dynamorio {
@@ -70,7 +72,7 @@ class cache_filter_stats_t : public cache_stats_t {
 };
 
 struct per_shard_t {
-    cache_lru_t cache;
+    cache_t cache;
 };
 
 void *
@@ -78,9 +80,12 @@ cache_filter_t::parallel_shard_init(memtrace_stream_t *shard_stream,
                                     bool partial_trace_filter)
 {
     per_shard_t *per_shard = new per_shard_t;
-    if (!(per_shard->cache.init(cache_associativity_, cache_line_size_, cache_size_,
-                                nullptr, new cache_filter_stats_t(cache_line_size_),
-                                nullptr))) {
+    if (!(per_shard->cache.init(
+            cache_associativity_, cache_line_size_, cache_size_, nullptr,
+            new cache_filter_stats_t(cache_line_size_),
+            std::unique_ptr<policy_lru_t>(new policy_lru_t(
+                cache_size_ / cache_associativity_, cache_associativity_)),
+            nullptr))) {
         error_string_ = "Failed to initialize cache.";
         return nullptr;
     }


Reviews:
user: login: brettcoon
body: I didn't get through all the files yet; I'll review more tomorrow.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T06:19:43Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T09:09:42Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T09:09:46Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T09:09:51Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T09:09:56Z
user: login: brettcoon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:41:11Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:54:07Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:33:37Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:58:25Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:58:27Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:58:33Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:58:37Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:58:47Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:59:08Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:59:24Z
user: login: brettcoon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-08T00:38:19Z
user: login: brettcoon
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-08T01:49:34Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T15:37:47Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-09T15:37:54Z
user: login: brettcoon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T22:59:03Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T14:53:16Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T15:03:25Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T15:05:24Z
user: login: brettcoon
body: Looking better.  I got a late start reviewing this because I was struggling to get a new test working for exclusive caches.  I'll try my new test on your latest updates tomorrow, and try to finish reviewing then as well.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T06:51:29Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T08:10:22Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T08:10:41Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T08:11:41Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T08:11:53Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T08:12:16Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T08:14:12Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T08:18:39Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T08:18:50Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T08:57:58Z
user: login: brettcoon
body: One leftover comment to remove or cleanup, but otherwise looks good!

FYI, I wrote a new test for LRU caching in exclusive-mode caches, and found a couple bugs.  But since the bugs aren't in the new policy logic, so I'll go ahead and put the tests and fixes in a subsequent CL.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-16T23:29:52Z
user: login: oralmer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T06:43:17Z
user: login: derekbruening
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-10-09T17:26:32Z

Review comments:
diff_hunk: @@ -0,0 +1,86 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necassary information to implement a cache replacement policy,
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: necessary
created_at: 2025-03-02T21:35:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necassary information to implement a cache replacement policy,
+ * And provides an interface for caching_device_t find which way to replace
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: How about "and provides a replacement-specific get_next_way_to_replace() method for caching_device_t.
created_at: 2025-03-02T21:36:16Z
author_association: CONTRIBUTOR
path: clients/drcachesim/simulator/bit_plru.h
user: login: brettcoon
body: I suggest adding a "policy_" prefix to the policy implementation files and classes (or something along those lines).  This would group them together in directory listings, as well as make it clear that fairly generic policy names like "fifo.h" refer to a cache policy rather than a FIFO data structure.
created_at: 2025-03-02T21:43:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,51 +30,40 @@
  * DAMAGE.
  */
 
-/* cache_lru: represents a single hardware cache with LRU algo.
path: clients/drcachesim/simulator/bit_plru.h
user: login: brettcoon
body: Can you add a comment to the start of each of these policy implementations with a brief explanation of the policy being implemented?
created_at: 2025-03-02T23:16:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,51 +30,40 @@
  * DAMAGE.
  */
 
-/* cache_lru: represents a single hardware cache with LRU algo.
path: clients/drcachesim/simulator/bit_plru.h
user: login: oralmer
body: done.
created_at: 2025-03-03T09:09:42Z
author_association: CONTRIBUTOR
path: clients/drcachesim/simulator/bit_plru.h
user: login: oralmer
body: done.
created_at: 2025-03-03T09:09:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necassary information to implement a cache replacement policy,
+ * And provides an interface for caching_device_t find which way to replace
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: done.
created_at: 2025-03-03T09:09:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necassary information to implement a cache replacement policy,
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: done.
created_at: 2025-03-03T09:09:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -38,8 +38,10 @@
 
 #include <string>
 #include <vector>
+#include <memory>
path: clients/drcachesim/simulator/cache.h
user: login: brettcoon
body: I think these should be sorted, but maybe I'm misremembering.
created_at: 2025-03-04T06:02:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -261,38 +270,25 @@ caching_device_t::request(const memref_t &memref_in)
 void
 caching_device_t::access_update(int block_idx, int way)
 {
-    // We just inc the counter for LFU.  We live with any blip on overflow.
-    get_caching_device_block(block_idx, way).counter_++;
+    replacement_policy_->access_update(block_idx, way);
 }
 
 int
 caching_device_t::replace_which_way(int block_idx)
 {
-    int min_way = get_next_way_to_replace(block_idx);
-    // Clear the counter for LFU.
-    get_caching_device_block(block_idx, min_way).counter_ = 0;
-    return min_way;
+    int way_to_replace = get_next_way_to_replace(block_idx);
+    replacement_policy_->eviction_update(block_idx, way_to_replace);
+    return way_to_replace;
 }
 
 int
 caching_device_t::get_next_way_to_replace(const int block_idx) const
 {
-    // The base caching device class only implements LFU.
-    // A subclass can override this and access_update() to implement
-    // some other scheme.
-    int min_counter = 0; /* avoid "may be used uninitialized" with GCC 4.4.7 */
-    int min_way = 0;
     for (int way = 0; way < associativity_; ++way) {
-        if (get_caching_device_block(block_idx, way).tag_ == TAG_INVALID) {
-            min_way = way;
-            break;
-        }
-        if (way == 0 || get_caching_device_block(block_idx, way).counter_ < min_counter) {
-            min_counter = get_caching_device_block(block_idx, way).counter_;
-            min_way = way;
-        }
+        if (get_caching_device_block(block_idx, way).tag_ == TAG_INVALID)
path: clients/drcachesim/simulator/caching_device.cpp
user: login: brettcoon
body: This code suggests cache_replacement_policy implementations can rely on all tags being valid.  If that guarantee is intended, can you add a comment stating this in the cache_replacement_policy.h comments for get_next_way_to_replace()?
created_at: 2025-03-04T06:41:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,6 +47,7 @@
 #include "caching_device_stats.h"
 #include "memref.h"
 #include "trace_entry.h"
+#include "cache_replacement_policy.h"
path: clients/drcachesim/simulator/caching_device.h
user: login: brettcoon
body: Sort includes?
created_at: 2025-03-04T06:42:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,52 +30,46 @@
  * DAMAGE.
  */
 
-/* cache_lru: represents a single hardware cache with LRU algo.
- */
-
-#ifndef _CACHE_LRU_H_
-#define _CACHE_LRU_H_ 1
+#ifndef _BIT_PLRU_H_
+#define _BIT_PLRU_H_
 
+#include <random>
 #include <string>
 #include <vector>
 
-#include "cache.h"
-#include "prefetcher.h"
-#include "snoop_filter.h"
+#include "cache_replacement_policy.h"
 
 namespace dynamorio {
 namespace drmemtrace {
 
-class cache_lru_t : public cache_t {
+/**
+ * A replacement policy that uses a bit per wat to track access frequency.
+ *
+ * On access, a way's bit is set to 1. Once all bits are set, the whole block's bits
+ * are set to 0. A random way with a 0 bit is chosen for replacement.
+ */
+class policy_bit_plru_t : public cache_replacement_policy_t {
 public:
-    explicit cache_lru_t(const std::string &name = "cache_lru")
-        : cache_t(name)
-    {
-    }
-    bool
-    init(int associativity, int64_t block_size, int64_t total_size,
-         caching_device_t *parent, caching_device_stats_t *stats,
-         prefetcher_t *prefetcher = nullptr,
-         cache_inclusion_policy_t inclusion_policy =
-             cache_inclusion_policy_t::NON_INC_NON_EXC,
-         bool coherent_cache = false, int id = -1, snoop_filter_t *snoop_filter = nullptr,
-         const std::vector<caching_device_t *> &children = {}) override;
-    std::string
-    get_replace_policy() const override
-    {
-        return "LRU";
-    }
-
-protected:
+    /// If seed is -1, a random seed will be used.
+    policy_bit_plru_t(int num_blocks, int associativity, int seed = -1);
     void
     access_update(int block_idx, int way) override;
+    void
+    eviction_update(int block_idx, int way) override;
     int
-    replace_which_way(int block_idx) override;
-    int
-    get_next_way_to_replace(const int block_idx) const override;
+    get_next_way_to_replace(int block_idx) override;
+    std::string
+    get_name() const override;
+
+    ~policy_bit_plru_t() override = default;
+
+private:
+    std::vector<std::vector<bool>> block_bits_;
+    std::vector<int> block_set_counts_;
path: clients/drcachesim/simulator/policy_bit_plru.h
user: login: brettcoon
body: A couple descriptive comments on these data items would be really helpful.
It took me a while to realize block_set_counts is counting the number of 1 bits in the PLRU bit-vector.  "block", "set", and "counts" can all be nouns or verbs, so I wasn't quite sure what it meant just from the name.
created_at: 2025-03-04T06:54:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "policy_bit_plru.h"
+
+#include <random>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+policy_bit_plru_t::policy_bit_plru_t(int num_blocks, int associativity, int seed)
+    : cache_replacement_policy_t(num_blocks, associativity)
+    , block_set_counts_(num_blocks, 0)
+    , gen_(seed == -1 ? std::random_device()() : seed)
+{
+    // Initialize the bit vector for each block.
+    block_bits_.reserve(num_blocks);
+    for (int i = 0; i < num_blocks; ++i) {
+        block_bits_.emplace_back(associativity, false);
+    }
+}
+
+void
+policy_bit_plru_t::access_update(int block_idx, int way)
+{
+    block_idx = get_block_index(block_idx);
+    // Set the bit for the accessed way.
+    if (!block_bits_[block_idx][way]) {
+        block_bits_[block_idx][way] = true;
+        block_set_counts_[block_idx]++;
+    }
+    if (block_set_counts_[block_idx] < associativity_) {
+        // Finished.
+        return;
+    }
+    // If all bits are set, reset them.
+    for (int i = 0; i < associativity_; ++i) {
+        block_bits_[block_idx][i] = false;
+    }
+    block_set_counts_[block_idx] = 1;
+    block_bits_[block_idx][way] = true;
+}
+
+void
+policy_bit_plru_t::eviction_update(int block_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it.
path: clients/drcachesim/simulator/policy_bit_plru.cpp
user: login: brettcoon
body: Does this rely on caching_device.cpp handling way selection when there are invalid lines?  So when a line is evicted, the policy doesn't hear about it again until the replacement has already been loaded?  It feels like the PLRU state for this way could influence a subsequent update of another way in this set.
I'm not entirely sure about that, but minimally I think a comment explaining _why_ PLRU state doesn't need to be reset when a block is evicted would be very helpful.
created_at: 2025-03-04T07:08:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,52 +30,46 @@
  * DAMAGE.
  */
 
-/* cache_lru: represents a single hardware cache with LRU algo.
- */
-
-#ifndef _CACHE_LRU_H_
-#define _CACHE_LRU_H_ 1
+#ifndef _BIT_PLRU_H_
+#define _BIT_PLRU_H_
 
+#include <random>
 #include <string>
 #include <vector>
 
-#include "cache.h"
-#include "prefetcher.h"
-#include "snoop_filter.h"
+#include "cache_replacement_policy.h"
 
 namespace dynamorio {
 namespace drmemtrace {
 
-class cache_lru_t : public cache_t {
+/**
+ * A replacement policy that uses a bit per wat to track access frequency.
path: clients/drcachesim/simulator/policy_bit_plru.h
user: login: brettcoon
body: s/wat/way/
created_at: 2025-03-04T07:10:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,90 +30,67 @@
  * DAMAGE.
  */
 
-#include "cache_lru.h"
+#include "policy_lru.h"
 
-#include <vector>
-
-#include "cache.h"
-#include "caching_device.h"
-#include "caching_device_block.h"
-#include "caching_device_stats.h"
-#include "prefetcher.h"
-#include "snoop_filter.h"
+#include <list>
 
 namespace dynamorio {
 namespace drmemtrace {
 
-// For LRU implementation, we use the cache line counter to represent
-// how recently a cache line is accessed.
-// The count value 0 means the most recent access, and the cache line with the
-// highest counter value will be picked for replacement in replace_which_way.
-
-bool
-cache_lru_t::init(int associativity, int64_t block_size, int64_t total_size,
-                  caching_device_t *parent, caching_device_stats_t *stats,
-                  prefetcher_t *prefetcher, cache_inclusion_policy_t inclusion_policy,
-                  bool coherent_cache, int id, snoop_filter_t *snoop_filter,
-                  const std::vector<caching_device_t *> &children)
+policy_lru_t::policy_lru_t(int num_blocks, int associativity)
+    : cache_replacement_policy_t(num_blocks, associativity)
 {
-    // Works in the same way as the base class,
-    // except that the counters are initialized in a different way.
-
-    bool ret_val =
-        cache_t::init(associativity, block_size, total_size, parent, stats, prefetcher,
-                      inclusion_policy, coherent_cache, id, snoop_filter, children);
-    if (ret_val == false)
-        return false;
-
-    // Initialize line counters with 0, 1, 2, ..., associativity - 1.
-    for (int i = 0; i < blocks_per_way_; i++) {
-        for (int way = 0; way < associativity_; ++way) {
-            get_caching_device_block(i * associativity_, way).counter_ = way;
-        }
+    // Initialize the LRU list for each block.
+    lru_counters_.reserve(num_blocks);
+    for (int i = 0; i < num_blocks; ++i) {
+        lru_counters_.emplace_back(associativity, 1);
     }
-    return true;
 }
 
 void
-cache_lru_t::access_update(int block_idx, int way)
+policy_lru_t::access_update(int block_idx, int way)
 {
-    int cnt = get_caching_device_block(block_idx, way).counter_;
+    block_idx = get_block_index(block_idx);
+    int count = lru_counters_[block_idx][way];
     // Optimization: return early if it is a repeated access.
-    if (cnt == 0)
+    if (count == 0)
         return;
     // We inc all the counters that are not larger than cnt for LRU.
path: clients/drcachesim/simulator/policy_lru.cpp
user: login: brettcoon
body: s/cnt/count/
created_at: 2025-03-04T07:18:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,46 +95,56 @@ tlb_simulator_t::tlb_simulator_t(const tlb_simulator_knobs_t &knobs)
         lltlbs_[i] = NULL;
     }
     for (unsigned int i = 0; i < knobs_.num_cores; i++) {
-        itlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
+        itlbs_[i] = new tlb_t("itlb");
path: clients/drcachesim/simulator/tlb_simulator.cpp
user: login: brettcoon
body: Not sure if anything uses the names, but it might be helpful to append the core number to these names.  Just a thought.
created_at: 2025-03-04T07:23:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,46 +95,56 @@ tlb_simulator_t::tlb_simulator_t(const tlb_simulator_knobs_t &knobs)
         lltlbs_[i] = NULL;
     }
     for (unsigned int i = 0; i < knobs_.num_cores; i++) {
-        itlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
+        itlbs_[i] = new tlb_t("itlb");
         if (itlbs_[i] == NULL) {
path: clients/drcachesim/simulator/tlb_simulator.cpp
user: login: brettcoon
body: Here and below, is it possible for these pointers to be NULL?
created_at: 2025-03-04T07:26:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -299,27 +265,28 @@ unit_test_cache_fifo_four_way()
     cache_fifo_test.access_and_check(addr_vec[ADDR_H], 0); //     e F G H
     cache_fifo_test.access_and_check(addr_vec[ADDR_A], 1); //     A f G H
 
-    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_G], 1); // A f X H
+    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_G], 2); // A F x H
 
-    cache_fifo_test.access_and_check(addr_vec[ADDR_G], 2); //     A G x H
-    cache_fifo_test.access_and_check(addr_vec[ADDR_B], 3); //     A G B h
+    cache_fifo_test.access_and_check(addr_vec[ADDR_G], 1); //     A f G H
+    cache_fifo_test.access_and_check(addr_vec[ADDR_B], 3); //     A B G h
path: clients/drcachesim/tests/cache_replacement_policy_unit_test.cpp
user: login: brettcoon
body: So the new FIFO behaves differently than the old FIFO?
created_at: 2025-03-04T07:34:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -299,27 +265,28 @@ unit_test_cache_fifo_four_way()
     cache_fifo_test.access_and_check(addr_vec[ADDR_H], 0); //     e F G H
     cache_fifo_test.access_and_check(addr_vec[ADDR_A], 1); //     A f G H
 
-    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_G], 1); // A f X H
+    cache_fifo_test.invalidate_and_check(addr_vec[ADDR_G], 2); // A F x H
 
-    cache_fifo_test.access_and_check(addr_vec[ADDR_G], 2); //     A G x H
-    cache_fifo_test.access_and_check(addr_vec[ADDR_B], 3); //     A G B h
+    cache_fifo_test.access_and_check(addr_vec[ADDR_G], 1); //     A f G H
+    cache_fifo_test.access_and_check(addr_vec[ADDR_B], 3); //     A B G h
path: clients/drcachesim/tests/cache_replacement_policy_unit_test.cpp
user: login: oralmer
body: Yea, I mention this in the CR description - The old fifo was a round-robin that was unaware of whether cache lines are valid or not. Now that the policy is split from the caching device, and the device handles invalid lines, this can't be implemented. 

we can pass a vector of states per way or something along those lines to allow replacement policies to handle such cases, but this feels a bit odd to me - Why would you ever want to evict a valid line when there's an invalid one available for use? I lack domain knowledge here though, so LMK if this should change.
created_at: 2025-03-04T07:54:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "policy_bit_plru.h"
+
+#include <random>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+policy_bit_plru_t::policy_bit_plru_t(int num_blocks, int associativity, int seed)
+    : cache_replacement_policy_t(num_blocks, associativity)
+    , block_set_counts_(num_blocks, 0)
+    , gen_(seed == -1 ? std::random_device()() : seed)
+{
+    // Initialize the bit vector for each block.
+    block_bits_.reserve(num_blocks);
+    for (int i = 0; i < num_blocks; ++i) {
+        block_bits_.emplace_back(associativity, false);
+    }
+}
+
+void
+policy_bit_plru_t::access_update(int block_idx, int way)
+{
+    block_idx = get_block_index(block_idx);
+    // Set the bit for the accessed way.
+    if (!block_bits_[block_idx][way]) {
+        block_bits_[block_idx][way] = true;
+        block_set_counts_[block_idx]++;
+    }
+    if (block_set_counts_[block_idx] < associativity_) {
+        // Finished.
+        return;
+    }
+    // If all bits are set, reset them.
+    for (int i = 0; i < associativity_; ++i) {
+        block_bits_[block_idx][i] = false;
+    }
+    block_set_counts_[block_idx] = 1;
+    block_bits_[block_idx][way] = true;
+}
+
+void
+policy_bit_plru_t::eviction_update(int block_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it.
path: clients/drcachesim/simulator/policy_bit_plru.cpp
user: login: oralmer
body: see other comments, currently I assume that if we reached the policy then all ways are valid - caching device returns the first invalid way without consulting the policy otherwise. should we do something else? is it reasonable for the policy to be aware of valid\invalid ways?
created_at: 2025-03-04T08:33:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -38,8 +38,10 @@
 
 #include <string>
 #include <vector>
+#include <memory>
path: clients/drcachesim/simulator/cache.h
user: login: oralmer
body: done.
created_at: 2025-03-04T08:58:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,6 +47,7 @@
 #include "caching_device_stats.h"
 #include "memref.h"
 #include "trace_entry.h"
+#include "cache_replacement_policy.h"
path: clients/drcachesim/simulator/caching_device.h
user: login: oralmer
body: done.
created_at: 2025-03-04T08:58:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,52 +30,46 @@
  * DAMAGE.
  */
 
-/* cache_lru: represents a single hardware cache with LRU algo.
- */
-
-#ifndef _CACHE_LRU_H_
-#define _CACHE_LRU_H_ 1
+#ifndef _BIT_PLRU_H_
+#define _BIT_PLRU_H_
 
+#include <random>
 #include <string>
 #include <vector>
 
-#include "cache.h"
-#include "prefetcher.h"
-#include "snoop_filter.h"
+#include "cache_replacement_policy.h"
 
 namespace dynamorio {
 namespace drmemtrace {
 
-class cache_lru_t : public cache_t {
+/**
+ * A replacement policy that uses a bit per wat to track access frequency.
path: clients/drcachesim/simulator/policy_bit_plru.h
user: login: oralmer
body: done.
created_at: 2025-03-04T08:58:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,46 +95,56 @@ tlb_simulator_t::tlb_simulator_t(const tlb_simulator_knobs_t &knobs)
         lltlbs_[i] = NULL;
     }
     for (unsigned int i = 0; i < knobs_.num_cores; i++) {
-        itlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
+        itlbs_[i] = new tlb_t("itlb");
path: clients/drcachesim/simulator/tlb_simulator.cpp
user: login: oralmer
body: done.
created_at: 2025-03-04T08:58:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,46 +95,56 @@ tlb_simulator_t::tlb_simulator_t(const tlb_simulator_knobs_t &knobs)
         lltlbs_[i] = NULL;
     }
     for (unsigned int i = 0; i < knobs_.num_cores; i++) {
-        itlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
+        itlbs_[i] = new tlb_t("itlb");
         if (itlbs_[i] == NULL) {
path: clients/drcachesim/simulator/tlb_simulator.cpp
user: login: oralmer
body: good catch, removed the check
created_at: 2025-03-04T08:58:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,90 +30,67 @@
  * DAMAGE.
  */
 
-#include "cache_lru.h"
+#include "policy_lru.h"
 
-#include <vector>
-
-#include "cache.h"
-#include "caching_device.h"
-#include "caching_device_block.h"
-#include "caching_device_stats.h"
-#include "prefetcher.h"
-#include "snoop_filter.h"
+#include <list>
 
 namespace dynamorio {
 namespace drmemtrace {
 
-// For LRU implementation, we use the cache line counter to represent
-// how recently a cache line is accessed.
-// The count value 0 means the most recent access, and the cache line with the
-// highest counter value will be picked for replacement in replace_which_way.
-
-bool
-cache_lru_t::init(int associativity, int64_t block_size, int64_t total_size,
-                  caching_device_t *parent, caching_device_stats_t *stats,
-                  prefetcher_t *prefetcher, cache_inclusion_policy_t inclusion_policy,
-                  bool coherent_cache, int id, snoop_filter_t *snoop_filter,
-                  const std::vector<caching_device_t *> &children)
+policy_lru_t::policy_lru_t(int num_blocks, int associativity)
+    : cache_replacement_policy_t(num_blocks, associativity)
 {
-    // Works in the same way as the base class,
-    // except that the counters are initialized in a different way.
-
-    bool ret_val =
-        cache_t::init(associativity, block_size, total_size, parent, stats, prefetcher,
-                      inclusion_policy, coherent_cache, id, snoop_filter, children);
-    if (ret_val == false)
-        return false;
-
-    // Initialize line counters with 0, 1, 2, ..., associativity - 1.
-    for (int i = 0; i < blocks_per_way_; i++) {
-        for (int way = 0; way < associativity_; ++way) {
-            get_caching_device_block(i * associativity_, way).counter_ = way;
-        }
+    // Initialize the LRU list for each block.
+    lru_counters_.reserve(num_blocks);
+    for (int i = 0; i < num_blocks; ++i) {
+        lru_counters_.emplace_back(associativity, 1);
     }
-    return true;
 }
 
 void
-cache_lru_t::access_update(int block_idx, int way)
+policy_lru_t::access_update(int block_idx, int way)
 {
-    int cnt = get_caching_device_block(block_idx, way).counter_;
+    block_idx = get_block_index(block_idx);
+    int count = lru_counters_[block_idx][way];
     // Optimization: return early if it is a repeated access.
-    if (cnt == 0)
+    if (count == 0)
         return;
     // We inc all the counters that are not larger than cnt for LRU.
path: clients/drcachesim/simulator/policy_lru.cpp
user: login: oralmer
body: done.
created_at: 2025-03-04T08:59:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -30,52 +30,46 @@
  * DAMAGE.
  */
 
-/* cache_lru: represents a single hardware cache with LRU algo.
- */
-
-#ifndef _CACHE_LRU_H_
-#define _CACHE_LRU_H_ 1
+#ifndef _BIT_PLRU_H_
+#define _BIT_PLRU_H_
 
+#include <random>
 #include <string>
 #include <vector>
 
-#include "cache.h"
-#include "prefetcher.h"
-#include "snoop_filter.h"
+#include "cache_replacement_policy.h"
 
 namespace dynamorio {
 namespace drmemtrace {
 
-class cache_lru_t : public cache_t {
+/**
+ * A replacement policy that uses a bit per wat to track access frequency.
+ *
+ * On access, a way's bit is set to 1. Once all bits are set, the whole block's bits
+ * are set to 0. A random way with a 0 bit is chosen for replacement.
+ */
+class policy_bit_plru_t : public cache_replacement_policy_t {
 public:
-    explicit cache_lru_t(const std::string &name = "cache_lru")
-        : cache_t(name)
-    {
-    }
-    bool
-    init(int associativity, int64_t block_size, int64_t total_size,
-         caching_device_t *parent, caching_device_stats_t *stats,
-         prefetcher_t *prefetcher = nullptr,
-         cache_inclusion_policy_t inclusion_policy =
-             cache_inclusion_policy_t::NON_INC_NON_EXC,
-         bool coherent_cache = false, int id = -1, snoop_filter_t *snoop_filter = nullptr,
-         const std::vector<caching_device_t *> &children = {}) override;
-    std::string
-    get_replace_policy() const override
-    {
-        return "LRU";
-    }
-
-protected:
+    /// If seed is -1, a random seed will be used.
+    policy_bit_plru_t(int num_blocks, int associativity, int seed = -1);
     void
     access_update(int block_idx, int way) override;
+    void
+    eviction_update(int block_idx, int way) override;
     int
-    replace_which_way(int block_idx) override;
-    int
-    get_next_way_to_replace(const int block_idx) const override;
+    get_next_way_to_replace(int block_idx) override;
+    std::string
+    get_name() const override;
+
+    ~policy_bit_plru_t() override = default;
+
+private:
+    std::vector<std::vector<bool>> block_bits_;
+    std::vector<int> block_set_counts_;
path: clients/drcachesim/simulator/policy_bit_plru.h
user: login: oralmer
body: added comment and changed names
created_at: 2025-03-04T08:59:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "policy_bit_plru.h"
+
+#include <random>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+policy_bit_plru_t::policy_bit_plru_t(int num_blocks, int associativity, int seed)
+    : cache_replacement_policy_t(num_blocks, associativity)
+    , block_set_counts_(num_blocks, 0)
+    , gen_(seed == -1 ? std::random_device()() : seed)
+{
+    // Initialize the bit vector for each block.
+    block_bits_.reserve(num_blocks);
+    for (int i = 0; i < num_blocks; ++i) {
+        block_bits_.emplace_back(associativity, false);
+    }
+}
+
+void
+policy_bit_plru_t::access_update(int block_idx, int way)
+{
+    block_idx = get_block_index(block_idx);
+    // Set the bit for the accessed way.
+    if (!block_bits_[block_idx][way]) {
+        block_bits_[block_idx][way] = true;
+        block_set_counts_[block_idx]++;
+    }
+    if (block_set_counts_[block_idx] < associativity_) {
+        // Finished.
+        return;
+    }
+    // If all bits are set, reset them.
+    for (int i = 0; i < associativity_; ++i) {
+        block_bits_[block_idx][i] = false;
+    }
+    block_set_counts_[block_idx] = 1;
+    block_bits_[block_idx][way] = true;
+}
+
+void
+policy_bit_plru_t::eviction_update(int block_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it.
path: clients/drcachesim/simulator/policy_bit_plru.cpp
user: login: brettcoon
body: It _seems_ like the policy might need to be aware of valid/invalid ways.
The thing I'm wrestling with is the difference between the initial policy state when all lines are invalid (from the constructor), versus the policy state after all lines have been invalidated after previously being valid, as well as any possible cache modifications that aren't communicated to policy.  It seems that if eviction_update() doesn't need to do anything, then the policy state also should not require initialization.  I guess in the LFU policy the eviction_update _does_ update the state, so in that case the invalid-line state is consistent, so long as all invalidations call eviction_update().
I'll put a comment in the base file, but I think what I'm really looking for is a clearer statement of the contract between caching_device and cache_replacement_policy_t.
created_at: 2025-03-08T00:38:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -38,8 +38,10 @@
 
 #include <string>
 #include <vector>
+#include <memory>
path: clients/drcachesim/simulator/cache.h
user: login: brettcoon
body: I'm still seeing them unsorted.
created_at: 2025-03-08T00:40:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necessary information to implement a cache replacement policy,
+ * and provides a replacement-specific get_next_way_to_replace() method for
+ * `caching_device_t`.
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: Can you expand this comment with a more precise statement of how cache_replacement_polity_t is used, and the "contract" between the caching_device and this policy?
Specifically, please clarify:
- cache_replacement_polity_t is notified of all cache accesses and evictions via access_update() and eviction_update(), respectively.  (Is this accurate?  Looking more, it seems the caching_device.cpp code may not forward invalidate() to the policy, which begs the question of what is the difference between **invalidation** and **eviction**, and does it matter?)
- cache_replacement_policy_t::get_next_way_to_replace() is called only when all cache ways are valid and an eviction is required: if there are any invalid ways, caching_device picks which one to fill.

I believe this means that in the case of a new line being inserted in the cache, the only call into cache_replacement_policy will be to access_update() for the new line.  But when a new line needs to replace an existing line, caching_device will make three calls into cache_replacement_policy:  (1) get_next_way_to_replace to choose the line to evict, then (2) eviction_update, and then (3) access_update.  Is that accurate?

Since I think the FIFO policy is the only one implemented that does something special for evictions, it's probably the only policy sensitive to invalidations right now.
created_at: 2025-03-08T01:09:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,46 +96,47 @@ tlb_simulator_t::tlb_simulator_t(const tlb_simulator_knobs_t &knobs)
         lltlbs_[i] = NULL;
     }
     for (unsigned int i = 0; i < knobs_.num_cores; i++) {
-        itlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
-        if (itlbs_[i] == NULL) {
-            error_string_ = "Failed to create itlbs_";
-            success_ = false;
-            return;
-        }
-        dtlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
-        if (dtlbs_[i] == NULL) {
-            error_string_ = "Failed to create dtlbs_";
-            success_ = false;
-            return;
-        }
-        lltlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
-        if (lltlbs_[i] == NULL) {
-            error_string_ = "Failed to create lltlbs_";
-            success_ = false;
-            return;
-        }
-
+        std::stringstream ss_itlb;
+        ss_itlb << "itlb " << i;
+        itlbs_[i] = new tlb_t(ss_itlb.str());
path: clients/drcachesim/simulator/tlb_simulator.cpp
user: login: brettcoon
body: Alternately, I _think_ you could have done something like:
```suggestion
        std::string i_str = std::to_string(i);
        itlbs_[i] = new tlb_t("itlb" + i_str);
        dtlbs_[i] = new tlb_t("dtlb" + i_str);
        ...
```
created_at: 2025-03-08T01:40:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necessary information to implement a cache replacement policy,
+ * and provides a replacement-specific get_next_way_to_replace() method for
+ * `caching_device_t`.
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: I expanded the comment, and now pass `valid_ways` to `get_next_way_to_replace`. This allows us to keep the same policy logic as before, without having duplicate data between the device and policy. Now, the device is in charge of tracking validity, and the policy just gets the state of the ways. Evictions and invalidations are still separate concepts, and the policy can handle each one differently, with evictions calling `eviction_update` and invalidations changing the `valid_ways` vector.

WDYT?
created_at: 2025-03-09T15:37:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -94,46 +96,47 @@ tlb_simulator_t::tlb_simulator_t(const tlb_simulator_knobs_t &knobs)
         lltlbs_[i] = NULL;
     }
     for (unsigned int i = 0; i < knobs_.num_cores; i++) {
-        itlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
-        if (itlbs_[i] == NULL) {
-            error_string_ = "Failed to create itlbs_";
-            success_ = false;
-            return;
-        }
-        dtlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
-        if (dtlbs_[i] == NULL) {
-            error_string_ = "Failed to create dtlbs_";
-            success_ = false;
-            return;
-        }
-        lltlbs_[i] = create_tlb(knobs_.TLB_replace_policy);
-        if (lltlbs_[i] == NULL) {
-            error_string_ = "Failed to create lltlbs_";
-            success_ = false;
-            return;
-        }
-
+        std::stringstream ss_itlb;
+        ss_itlb << "itlb " << i;
+        itlbs_[i] = new tlb_t(ss_itlb.str());
path: clients/drcachesim/simulator/tlb_simulator.cpp
user: login: oralmer
body: done.
created_at: 2025-03-09T15:37:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -44,6 +45,17 @@ namespace drmemtrace {
  * Holds the necessary information to implement a cache replacement policy,
  * and provides a replacement-specific get_next_way_to_replace() method for
  * `caching_device_t`.
+ *
+ * The policy recieves the following updates:
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: receives
created_at: 2025-03-10T18:20:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necessary information to implement a cache replacement policy,
+ * and provides a replacement-specific get_next_way_to_replace() method for
+ * `caching_device_t`.
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: Thanks, the updated comment is much clearer.

As for adding valid_ways to get_next_way_to_replace, I'm a little concerned about the performance cost of computing the valid mask before every call to get_next_way_to_replace(), especially since the ways are usually all valid and most policies don't need that information anyway.  with this change, we now require two loops over the ways for every replacement when everything is valid.

Instead, I suggest going back to letting caching_device_t handle filling invalid ways, but adding an "invalidation_update()" method to this policy interface, and having caching_device_t call it whenever a line is invalidated.  If the policy cares, it can track and/or update state as needed to reflect the invalidation.  If the policy doesn't care about invalidations, we're only adding a bit of overhead to invalidation events.  And I also think it's more consistent if the policy is notified of all cache state transactions, including invalidations.

I suspect most (all?) policies will treat eviction_update() exactly the same as invalidation_update(), so it's not actually clear a separate method is needed.  I believe the _only_ difference between eviction and invalidation is whether or not the line is going to be immediately replaced, suggesting the easiest thing might be to just use a single method for both; whether it's called eviction_update() or invalidation_update() doesn't matter to me.

created_at: 2025-03-10T19:35:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -44,6 +45,17 @@ namespace drmemtrace {
  * Holds the necessary information to implement a cache replacement policy,
  * and provides a replacement-specific get_next_way_to_replace() method for
  * `caching_device_t`.
+ *
+ * The policy recieves the following updates:
+ *  - When an existing way is accessed, `access_update()` is called.
+ *  - When a way is evicted, `eviction_update()` is called on the evicted way, and
+ * `access_update()` is called on the new way immediately after.
+ *  - When a way is invalidated, nothing is done - caching_device_t should keep track of
+ * which ways are valid.
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: This is better, but as I suggest above, I think it would be even better to notify the policy of invalidations by also calling eviction_update() or invalidation_update() for these events.
created_at: 2025-03-10T19:37:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,113 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necessary information to implement a cache replacement policy,
+ * and provides a replacement-specific get_next_way_to_replace() method for
+ * `caching_device_t`.
+ *
+ * The policy recieves the following updates:
+ *  - When an existing way is accessed, `access_update()` is called.
+ *  - When a way is evicted, `eviction_update()` is called on the evicted way, and
+ * `access_update()` is called on the new way immediately after.
+ *  - When a way is invalidated, nothing is done - caching_device_t should keep track of
+ * which ways are valid.
+ *
+ * The policy also provides a `get_next_way_to_replace()` method that returns
+ * the next way to replace in the block. `valid_ways` is a vector of booleans,
+ * where `valid_ways[way]` is true if the way is currently valid.
+ */
+class cache_replacement_policy_t {
+public:
+    cache_replacement_policy_t(int num_blocks, int associativity)
+        : associativity_(associativity)
+        , num_blocks_(num_blocks)
+    {
+    }
+    /// Informs the replacement policy that an access has occurred.
+    virtual void
+    access_update(int block_idx, int way) = 0;
+    /// Informs the replacement policy that an eviction has occurred.
+    virtual void
+    eviction_update(int block_idx, int way) = 0;
+    /*
+     * Returns the next way to replace in the block.
+     * valid_ways is a vector of booleans, where valid_ways[way] is true if the way
+     * is currently valid.
+     */
+    virtual int
+    get_next_way_to_replace(int block_idx, const std::vector<bool> &valid_ways) const = 0;
+    /// Returns the name of the replacement policy.
+    virtual std::string
+    get_name() const = 0;
+
+    virtual ~cache_replacement_policy_t() = default;
+
+protected:
+    virtual int
+    get_block_index(int block_idx) const
+    {
+        // The block index points to the first way in the block, and the ways are stored
+        // in a contiguous array, so we divide by the associativity to get the block
+        // index.
+        return block_idx / associativity_;
+    }
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: I should have noticed this before, but this method calls both its input and its output "block index", so it's not clear what "block index" means.  The comment effectively says "we divide block index by associativity to get block index".
Since "block_idx" is used throughout caching_device_t to refer to the array index for a pointer to the *first* block in a set, we need a different name for what this is computing.  I suggest something like "set_index".

In fact, it would be best if cache policy knew nothing about the underlying cache array organization.  I suggest having this API deal only with set_index instead of block_idx in the argument lists, and letting caching_device_t handle the computation of set_index.  It looks like the caching_device code actually starts with set_index and then multiplies it by associativity to get block_idx, so having the policy API take in set_index directly might actually be more efficient as well.
created_at: 2025-03-10T22:35:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -78,8 +78,13 @@ policy_bit_plru_t::eviction_update(int block_idx, int way)
 }
 
 int
-policy_bit_plru_t::get_next_way_to_replace(int block_idx)
+policy_bit_plru_t::get_next_way_to_replace(int block_idx,
+                                           const std::vector<bool> &valid_ways) const
 {
+    int first_invalid_way = get_first_invalid_way(valid_ways);
+    if (first_invalid_way != -1) {
+        return first_invalid_way;
+    }
     block_idx = get_block_index(block_idx);
path: clients/drcachesim/simulator/policy_bit_plru.cpp
user: login: brettcoon
body: As mentioned above, the dual meanings of "block_idx" are confusing.
created_at: 2025-03-10T22:52:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -44,6 +45,17 @@ namespace drmemtrace {
  * Holds the necessary information to implement a cache replacement policy,
  * and provides a replacement-specific get_next_way_to_replace() method for
  * `caching_device_t`.
+ *
+ * The policy recieves the following updates:
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: done
created_at: 2025-03-11T14:53:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,86 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necessary information to implement a cache replacement policy,
+ * and provides a replacement-specific get_next_way_to_replace() method for
+ * `caching_device_t`.
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: Added validation tracking to the replacement policy

This also forced us to add a `validation_update` function - since when using snoop with exclusive caches the cache entries can be moved between caches without generating evictions or accesses.

I think this is a bit more bug prone - Now you can reach an illegal state, where the tag is invalid but the policy thinks it it valid, or vice versa.
created_at: 2025-03-11T15:03:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,113 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necessary information to implement a cache replacement policy,
+ * and provides a replacement-specific get_next_way_to_replace() method for
+ * `caching_device_t`.
+ *
+ * The policy recieves the following updates:
+ *  - When an existing way is accessed, `access_update()` is called.
+ *  - When a way is evicted, `eviction_update()` is called on the evicted way, and
+ * `access_update()` is called on the new way immediately after.
+ *  - When a way is invalidated, nothing is done - caching_device_t should keep track of
+ * which ways are valid.
+ *
+ * The policy also provides a `get_next_way_to_replace()` method that returns
+ * the next way to replace in the block. `valid_ways` is a vector of booleans,
+ * where `valid_ways[way]` is true if the way is currently valid.
+ */
+class cache_replacement_policy_t {
+public:
+    cache_replacement_policy_t(int num_blocks, int associativity)
+        : associativity_(associativity)
+        , num_blocks_(num_blocks)
+    {
+    }
+    /// Informs the replacement policy that an access has occurred.
+    virtual void
+    access_update(int block_idx, int way) = 0;
+    /// Informs the replacement policy that an eviction has occurred.
+    virtual void
+    eviction_update(int block_idx, int way) = 0;
+    /*
+     * Returns the next way to replace in the block.
+     * valid_ways is a vector of booleans, where valid_ways[way] is true if the way
+     * is currently valid.
+     */
+    virtual int
+    get_next_way_to_replace(int block_idx, const std::vector<bool> &valid_ways) const = 0;
+    /// Returns the name of the replacement policy.
+    virtual std::string
+    get_name() const = 0;
+
+    virtual ~cache_replacement_policy_t() = default;
+
+protected:
+    virtual int
+    get_block_index(int block_idx) const
+    {
+        // The block index points to the first way in the block, and the ways are stored
+        // in a contiguous array, so we divide by the associativity to get the block
+        // index.
+        return block_idx / associativity_;
+    }
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: Changed to line index

Regarding the change - I considered this, but felt it makes the code in caching_device more complex and error prone without saving too much work for the policy - Now you'll have to indices to manage in the caching device, while in the policy you only have one (though you do need to make sure to compute it)
created_at: 2025-03-11T15:05:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -56,12 +56,19 @@ namespace drmemtrace {
  *
  * The policy also provides a `get_next_way_to_replace()` method that returns
  * the next way to replace in the block.
+ *
+ * Currently, the policy recieves the block index as it is in `caching_device_t`,
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: s/recieves/receives/
created_at: 2025-03-14T06:09:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -91,26 +95,15 @@ class cache_replacement_policy_t {
 
 protected:
     virtual int
-    get_line_index(int block_idx) const
+    get_set_index(int block_idx) const
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: As I suggested in my prior comment, I **don't** believe policy should have a get_set_index() method at all:  instead, all the methods should take set_idx as an argument rather than block_idx.  That hides caching_device's block organization from the policy, and also simplifies the methods since they don't always have to convert the input argument.
created_at: 2025-03-14T06:15:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,110 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necessary information to implement a cache replacement policy,
+ * and provides a replacement-specific get_next_way_to_replace() method for
+ * `caching_device_t`.
+ *
+ * The policy receives the following updates:
+ *  - When an existing way is accessed, `access_update()` is called.
+ *  - When a way is evicted, `eviction_update()` is called on the evicted way, and
+ * `access_update()` is called on the new way immediately after.
+ *  - When a way is invalidated, `invalidation_update()` is called.
+ *
+ * The policy also provides a `get_next_way_to_replace()` method that returns
+ * the next way to replace in the block. This function assumes that all ways are valid,
+ * and is called by `caching_device_t` when it cannot just replace an invalid way.
+ *
+ * Currently, the policy recieves the block index as it is in `caching_device_t`,
+ * which is the index of the first way in the set when all ways are stored in a
+ * contiguous array. Most policies however only need the set index, which is the
+ * index of the set in the cache. This can be obtained with `get_set_index()` - In the
+ * future, we may want to change the interface to receive the set index directly, saving
+ * the need for the division by the associativity.
+ */
+class cache_replacement_policy_t {
+public:
+    cache_replacement_policy_t(int num_sets, int associativity)
+        : associativity_(associativity)
+        , num_sets_(num_sets)
+    {
+    }
+    /// Informs the replacement policy that an access has occurred.
+    virtual void
+    access_update(int block_idx, int way) = 0;
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: For this and the others:
`access_update(int set_idx, int way) = 0;`

See my comment below.
created_at: 2025-03-14T06:18:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -263,38 +272,25 @@ caching_device_t::request(const memref_t &memref_in)
 void
 caching_device_t::access_update(int block_idx, int way)
 {
-    // We just inc the counter for LFU.  We live with any blip on overflow.
-    get_caching_device_block(block_idx, way).counter_++;
+    replacement_policy_->access_update(block_idx, way);
 }
 
 int
 caching_device_t::replace_which_way(int block_idx)
 {
-    int min_way = get_next_way_to_replace(block_idx);
-    // Clear the counter for LFU.
-    get_caching_device_block(block_idx, min_way).counter_ = 0;
-    return min_way;
+    int way_to_replace = get_next_way_to_replace(block_idx);
+    replacement_policy_->eviction_update(block_idx, way_to_replace);
path: clients/drcachesim/simulator/caching_device.cpp
user: login: brettcoon
body: It looks like this can call eviction_update for invalid ways; is that intentional?  I don't think it's a problem, though maybe a bit surprising.  Please mention this ("may be called to 'evict' invalid lines") in the method description in cache_replacement_policy.h.
created_at: 2025-03-14T06:32:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,111 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "policy_bit_plru.h"
+
+#include <random>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+policy_bit_plru_t::policy_bit_plru_t(int num_sets, int associativity, int seed)
+    : cache_replacement_policy_t(num_sets, associativity)
+    , num_ones_(num_sets, 0)
+    , gen_(seed == -1 ? std::random_device()() : seed)
+{
+    // Initialize the bit vector for each set.
+    plru_bits_.reserve(num_sets);
+    for (int i = 0; i < num_sets; ++i) {
+        plru_bits_.emplace_back(associativity, false);
+    }
+}
+
+void
+policy_bit_plru_t::access_update(int block_idx, int way)
+{
+    int set_idx = get_set_index(block_idx);
+    // Set the bit for the accessed way.
+    if (!plru_bits_[set_idx][way]) {
+        plru_bits_[set_idx][way] = true;
+        num_ones_[set_idx]++;
+    }
+    if (num_ones_[set_idx] < associativity_) {
+        // Finished.
+        return;
+    }
+    // If all bits are set, reset them.
+    for (int i = 0; i < associativity_; ++i) {
+        plru_bits_[set_idx][i] = false;
+    }
+    num_ones_[set_idx] = 1;
+    plru_bits_[set_idx][way] = true;
+}
+
+void
+policy_bit_plru_t::eviction_update(int set_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it.
+}
+
+void
+policy_bit_plru_t::invalidation_update(int set_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it.
+}
+
+int
+policy_bit_plru_t::get_next_way_to_replace(int block_idx) const
path: clients/drcachesim/simulator/policy_bit_plru.cpp
user: login: brettcoon
body: I see you changed the arguments for both eviction_update and invalidation_update to use `int set_idx`, but not this one.  Please update them all, and be sure to use the same names in both the .cpp and .h files.
created_at: 2025-03-14T06:49:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -261,38 +270,25 @@ caching_device_t::request(const memref_t &memref_in)
 void
 caching_device_t::access_update(int block_idx, int way)
 {
-    // We just inc the counter for LFU.  We live with any blip on overflow.
-    get_caching_device_block(block_idx, way).counter_++;
+    replacement_policy_->access_update(block_idx, way);
 }
 
 int
 caching_device_t::replace_which_way(int block_idx)
 {
-    int min_way = get_next_way_to_replace(block_idx);
-    // Clear the counter for LFU.
-    get_caching_device_block(block_idx, min_way).counter_ = 0;
-    return min_way;
+    int way_to_replace = get_next_way_to_replace(block_idx);
+    replacement_policy_->eviction_update(block_idx, way_to_replace);
+    return way_to_replace;
 }
 
 int
 caching_device_t::get_next_way_to_replace(const int block_idx) const
 {
-    // The base caching device class only implements LFU.
-    // A subclass can override this and access_update() to implement
-    // some other scheme.
-    int min_counter = 0; /* avoid "may be used uninitialized" with GCC 4.4.7 */
-    int min_way = 0;
     for (int way = 0; way < associativity_; ++way) {
-        if (get_caching_device_block(block_idx, way).tag_ == TAG_INVALID) {
-            min_way = way;
-            break;
-        }
-        if (way == 0 || get_caching_device_block(block_idx, way).counter_ < min_counter) {
-            min_counter = get_caching_device_block(block_idx, way).counter_;
-            min_way = way;
-        }
+        if (get_caching_device_block(block_idx, way).tag_ == TAG_INVALID)
path: clients/drcachesim/simulator/caching_device.cpp
user: login: oralmer
body: added.
created_at: 2025-03-16T08:10:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "policy_bit_plru.h"
+
+#include <random>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+policy_bit_plru_t::policy_bit_plru_t(int num_blocks, int associativity, int seed)
+    : cache_replacement_policy_t(num_blocks, associativity)
+    , block_set_counts_(num_blocks, 0)
+    , gen_(seed == -1 ? std::random_device()() : seed)
+{
+    // Initialize the bit vector for each block.
+    block_bits_.reserve(num_blocks);
+    for (int i = 0; i < num_blocks; ++i) {
+        block_bits_.emplace_back(associativity, false);
+    }
+}
+
+void
+policy_bit_plru_t::access_update(int block_idx, int way)
+{
+    block_idx = get_block_index(block_idx);
+    // Set the bit for the accessed way.
+    if (!block_bits_[block_idx][way]) {
+        block_bits_[block_idx][way] = true;
+        block_set_counts_[block_idx]++;
+    }
+    if (block_set_counts_[block_idx] < associativity_) {
+        // Finished.
+        return;
+    }
+    // If all bits are set, reset them.
+    for (int i = 0; i < associativity_; ++i) {
+        block_bits_[block_idx][i] = false;
+    }
+    block_set_counts_[block_idx] = 1;
+    block_bits_[block_idx][way] = true;
+}
+
+void
+policy_bit_plru_t::eviction_update(int block_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it.
path: clients/drcachesim/simulator/policy_bit_plru.cpp
user: login: oralmer
body: fixed
created_at: 2025-03-16T08:10:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -44,6 +45,17 @@ namespace drmemtrace {
  * Holds the necessary information to implement a cache replacement policy,
  * and provides a replacement-specific get_next_way_to_replace() method for
  * `caching_device_t`.
+ *
+ * The policy recieves the following updates:
+ *  - When an existing way is accessed, `access_update()` is called.
+ *  - When a way is evicted, `eviction_update()` is called on the evicted way, and
+ * `access_update()` is called on the new way immediately after.
+ *  - When a way is invalidated, nothing is done - caching_device_t should keep track of
+ * which ways are valid.
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: added
created_at: 2025-03-16T08:11:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -78,8 +78,13 @@ policy_bit_plru_t::eviction_update(int block_idx, int way)
 }
 
 int
-policy_bit_plru_t::get_next_way_to_replace(int block_idx)
+policy_bit_plru_t::get_next_way_to_replace(int block_idx,
+                                           const std::vector<bool> &valid_ways) const
 {
+    int first_invalid_way = get_first_invalid_way(valid_ways);
+    if (first_invalid_way != -1) {
+        return first_invalid_way;
+    }
     block_idx = get_block_index(block_idx);
path: clients/drcachesim/simulator/policy_bit_plru.cpp
user: login: oralmer
body: changed to set_index
created_at: 2025-03-16T08:11:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -56,12 +56,19 @@ namespace drmemtrace {
  *
  * The policy also provides a `get_next_way_to_replace()` method that returns
  * the next way to replace in the block.
+ *
+ * Currently, the policy recieves the block index as it is in `caching_device_t`,
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: fixed
created_at: 2025-03-16T08:12:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -91,26 +95,15 @@ class cache_replacement_policy_t {
 
 protected:
     virtual int
-    get_line_index(int block_idx) const
+    get_set_index(int block_idx) const
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: of course, my bad
I understood from the email that we want to keep calculating it, rather than go through the changes required to caching device to completely avoid the calculation. It does make more sense to calculate it outside of the policy, like you suggest. will fix
created_at: 2025-03-16T08:14:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,110 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#ifndef _CACHE_REPLACEMENT_POLICY_H_
+#define _CACHE_REPLACEMENT_POLICY_H_ 1
+
+#include <string>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+/**
+ * An interface for cache replacement policies.
+ *
+ * Holds the necessary information to implement a cache replacement policy,
+ * and provides a replacement-specific get_next_way_to_replace() method for
+ * `caching_device_t`.
+ *
+ * The policy receives the following updates:
+ *  - When an existing way is accessed, `access_update()` is called.
+ *  - When a way is evicted, `eviction_update()` is called on the evicted way, and
+ * `access_update()` is called on the new way immediately after.
+ *  - When a way is invalidated, `invalidation_update()` is called.
+ *
+ * The policy also provides a `get_next_way_to_replace()` method that returns
+ * the next way to replace in the block. This function assumes that all ways are valid,
+ * and is called by `caching_device_t` when it cannot just replace an invalid way.
+ *
+ * Currently, the policy recieves the block index as it is in `caching_device_t`,
+ * which is the index of the first way in the set when all ways are stored in a
+ * contiguous array. Most policies however only need the set index, which is the
+ * index of the set in the cache. This can be obtained with `get_set_index()` - In the
+ * future, we may want to change the interface to receive the set index directly, saving
+ * the need for the division by the associativity.
+ */
+class cache_replacement_policy_t {
+public:
+    cache_replacement_policy_t(int num_sets, int associativity)
+        : associativity_(associativity)
+        , num_sets_(num_sets)
+    {
+    }
+    /// Informs the replacement policy that an access has occurred.
+    virtual void
+    access_update(int block_idx, int way) = 0;
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: fixed.
created_at: 2025-03-16T08:18:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,111 @@
+/* **********************************************************
+ * Copyright (c) 2015-2023 Google, Inc.  All rights reserved.
+ * **********************************************************/
+
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Google, Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+#include "policy_bit_plru.h"
+
+#include <random>
+#include <vector>
+
+namespace dynamorio {
+namespace drmemtrace {
+
+policy_bit_plru_t::policy_bit_plru_t(int num_sets, int associativity, int seed)
+    : cache_replacement_policy_t(num_sets, associativity)
+    , num_ones_(num_sets, 0)
+    , gen_(seed == -1 ? std::random_device()() : seed)
+{
+    // Initialize the bit vector for each set.
+    plru_bits_.reserve(num_sets);
+    for (int i = 0; i < num_sets; ++i) {
+        plru_bits_.emplace_back(associativity, false);
+    }
+}
+
+void
+policy_bit_plru_t::access_update(int block_idx, int way)
+{
+    int set_idx = get_set_index(block_idx);
+    // Set the bit for the accessed way.
+    if (!plru_bits_[set_idx][way]) {
+        plru_bits_[set_idx][way] = true;
+        num_ones_[set_idx]++;
+    }
+    if (num_ones_[set_idx] < associativity_) {
+        // Finished.
+        return;
+    }
+    // If all bits are set, reset them.
+    for (int i = 0; i < associativity_; ++i) {
+        plru_bits_[set_idx][i] = false;
+    }
+    num_ones_[set_idx] = 1;
+    plru_bits_[set_idx][way] = true;
+}
+
+void
+policy_bit_plru_t::eviction_update(int set_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it.
+}
+
+void
+policy_bit_plru_t::invalidation_update(int set_idx, int way)
+{
+    // Nothing to update, when the way is accessed we will update it.
+}
+
+int
+policy_bit_plru_t::get_next_way_to_replace(int block_idx) const
path: clients/drcachesim/simulator/policy_bit_plru.cpp
user: login: oralmer
body: fixed.
created_at: 2025-03-16T08:18:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -263,38 +272,25 @@ caching_device_t::request(const memref_t &memref_in)
 void
 caching_device_t::access_update(int block_idx, int way)
 {
-    // We just inc the counter for LFU.  We live with any blip on overflow.
-    get_caching_device_block(block_idx, way).counter_++;
+    replacement_policy_->access_update(block_idx, way);
 }
 
 int
 caching_device_t::replace_which_way(int block_idx)
 {
-    int min_way = get_next_way_to_replace(block_idx);
-    // Clear the counter for LFU.
-    get_caching_device_block(block_idx, min_way).counter_ = 0;
-    return min_way;
+    int way_to_replace = get_next_way_to_replace(block_idx);
+    replacement_policy_->eviction_update(block_idx, way_to_replace);
path: clients/drcachesim/simulator/caching_device.cpp
user: login: oralmer
body: added comment
created_at: 2025-03-16T08:57:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,14 +49,15 @@ namespace drmemtrace {
  * The policy receives the following updates:
  *  - When an existing way is accessed, `access_update()` is called.
  *  - When a way is evicted, `eviction_update()` is called on the evicted way, and
- * `access_update()` is called on the new way immediately after.
+ * `access_update()` is called on the new way immediately after. Can be called on invalid
+ * ways.
  *  - When a way is invalidated, `invalidation_update()` is called.
  *
  * The policy also provides a `get_next_way_to_replace()` method that returns
  * the next way to replace in the block. This function assumes that all ways are valid,
  * and is called by `caching_device_t` when it cannot just replace an invalid way.
  *
- * Currently, the policy recieves the block index as it is in `caching_device_t`,
+ * Currently, the policy receives the block index as it is in `caching_device_t`,
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: brettcoon
body: This comment is obsolete now that the policy only receives set_index.
created_at: 2025-03-16T21:49:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,14 +49,15 @@ namespace drmemtrace {
  * The policy receives the following updates:
  *  - When an existing way is accessed, `access_update()` is called.
  *  - When a way is evicted, `eviction_update()` is called on the evicted way, and
- * `access_update()` is called on the new way immediately after.
+ * `access_update()` is called on the new way immediately after. Can be called on invalid
+ * ways.
  *  - When a way is invalidated, `invalidation_update()` is called.
  *
  * The policy also provides a `get_next_way_to_replace()` method that returns
  * the next way to replace in the block. This function assumes that all ways are valid,
  * and is called by `caching_device_t` when it cannot just replace an invalid way.
  *
- * Currently, the policy recieves the block index as it is in `caching_device_t`,
+ * Currently, the policy receives the block index as it is in `caching_device_t`,
path: clients/drcachesim/simulator/cache_replacement_policy.h
user: login: oralmer
body: fixed
created_at: 2025-03-17T06:43:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -131,7 +125,10 @@ cache_simulator_t::cache_simulator_t(const cache_simulator_knobs_t &knobs,
 
     if (!llc->init(knobs_.LL_assoc, (int)knobs_.line_size, (int)knobs_.LL_size, NULL,
                    new cache_stats_t((int)knobs_.line_size, knobs_.LL_miss_file,
-                                     warmup_enabled_))) {
+                                     warmup_enabled_),
+                   create_cache_replacement_policy(
+                       knobs_.replace_policy, (int)knobs_.LL_size / (int)knobs_.line_size,
path: clients/drcachesim/simulator/cache_simulator.cpp
user: login: derekbruening
body: The num_sets should be LL_size/line_size/LL_assoc?  I.e., right now it's passing the num_sets*assoc while the replacement policies want the set count per way, right?
created_at: 2025-10-09T17:26:32Z
author_association: CONTRIBUTOR

PR comments:
user: login: oralmer
created_at: 2025-03-02T15:07:22Z
body: @derekbruening I guess this begets an update to the release doc? How do you think it should look?
author_association: CONTRIBUTOR
user: login: derekbruening
created_at: 2025-03-03T21:26:11Z
body: Going to defer to @brettcoon for the review here.
author_association: CONTRIBUTOR
user: login: derekbruening
created_at: 2025-03-24T14:57:17Z
body: A change like this needs to be called out in the changelog in api/docs/release.dox: @oralmer remember I requested that on similar changes that affect compatibility of these C++ classes in your prior code; @brettcoon please keep in mind in future reviews.

@oralmer if you could submit a follow-up PR adding to the changelog.
author_association: CONTRIBUTOR