Skip to content

Prefetch sample adjust#902

Open
tastynoob wants to merge 2 commits into
xs-devfrom
prefetch-sample-adjust
Open

Prefetch sample adjust#902
tastynoob wants to merge 2 commits into
xs-devfrom
prefetch-sample-adjust

Conversation

@tastynoob

@tastynoob tastynoob commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features
    • Added windowed prefetch admission control system with configurable acceptance percentages and per-source tuning.
    • Added adaptive admission control that dynamically adjusts prefetch acceptance rates based on performance metrics.
    • Extended prefetch source type enumeration and tracking for improved prefetcher feedback.
    • Added new prefetch control statistics and performance counters for admission decisions and prefetch quality metrics.

tastynoob added 2 commits June 9, 2026 14:53
Add per-source prefetch admission control with adaptive PFBad feedback and generated PrefetchSourceType names for stats. Enable the control path from the SMT Python config while leaving exploration scripts uncommitted.

Change-Id: I002f06049555180409c148c11c06abb91d05ef67
Change-Id: Ied73acdc20d072925567d6cdf855f67183dce4fa
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a comprehensive prefetch admission control system to GEM5's prefetching framework, enabling windowed and online-adaptive admission control for multiple prefetch sources. The implementation spans the request layer, cache integration, prefetcher base interface, queued prefetcher logic, and configuration infrastructure.

Changes

Prefetch Admission Control Implementation

Layer / File(s) Summary
Prefetch Source Type Enumeration & Migration
src/mem/Request.py, src/mem/SConscript, src/mem/request.hh
PrefetchSourceType is defined in a new enum module and centralized; code migrates from local enum definition to enums::PrefetchSourceType with a prefetchSourceTypeName() string mapping function.
Request XsMetadata Updates
src/mem/request.hh
Request::XsMetadata now explicitly tracks and propagates both prefetchSource and prefetchDepth; setPFSource/setPFDepth methods update embedded metadata when present, and invalidate() resets depth to zero.
Prefetcher Base Virtual Interface & Stats
src/mem/cache/prefetch/base.hh, src/mem/cache/prefetch/base.cc
Base prefetcher adds PFBad statistics, recordIssuedPrefetch() overloads, recordPfBadHit(), and virtual notification hooks for demand MSHR misses, demand accesses, cache miss requests, prefetch usefulness, prefetch-induced evictions, and cacheline refills; subclasses override for custom behavior.
Cache Prefetch Fill & Block Allocation Tracking
src/mem/cache/base.hh, src/mem/cache/base.cc
handleFill() and allocateBlock() now accept an explicit PrefetchSourceType parameter; cache detects pure prefetch fills and propagates source/depth into block metadata; cache tracks prefetch-induced victim evictions and notifies prefetcher of demand MSHR misses, cache miss requests, and cacheline refills.
Queued Prefetcher Configuration Parameters
src/mem/cache/prefetch/Prefetcher.py
QueuedPrefetcher gains 21+ new parameters for windowed/adaptive admission control including per-source overrides, sweep/warmup sizing, gradient step limits, PFBad weighting, quantization, and table sizing.
Queued Prefetcher Admission Control Core
src/mem/cache/prefetch/queued.hh, src/mem/cache/prefetch/queued.cc
Queued prefetcher implements full windowed and adaptive admission control: per-source admit percentage tracking, local admission decisions (including pfahead hints), per-window counter maintenance, adaptive gradient computation from demand/PFBad/unused samples, online controller updates with bounded step sizes, and PFBad FIFO table for tracking misprefetches; request creation and enqueue paths sanitize sources and apply admission gating; statistics extended with control/adaptive metrics.
Prefetcher Forwarder Notification Delegation
src/mem/cache/prefetch/forwarder.hh, src/mem/cache/prefetch/forwarder.cc
PrefetcherForwarder filters incoming prefetch packets by admission, records issued prefetches on the backing prefetcher, and forwards all demand/prefetch event notifications to the real prefetcher when present.
Worker & Composite Prefetcher Control Integration
src/mem/cache/prefetch/worker.cc, src/mem/cache/prefetch/composite_with_worker.cc, src/mem/cache/prefetch/cdp.cc
WorkerPrefetcher applies pf_control admission gating to deferred packet transfer and explicitly deallocates rejected packets; CompositeWithWorkerPrefetcher applies pf_control filtering to candidate insertion; CDP updates loop constant from PrefetchSourceType::NUM_PF_SOURCES to NUM_PF_SOURCES.
Python Configuration System & Example Setup
configs/common/PrefetcherConfig.py, configs/example/smt_idealkmhv3.py
New configuration system loads and validates prefetch control settings from environment-specified override file, deep-merges into defaults, and applies windowed/adaptive parameters to compatible prefetcher objects during simulation setup; example enables control with specific window/adaptive thresholds.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

perf

Suggested reviewers

  • happy-lx
  • Ergou-ren
  • edwu186

Poem

🐰 Hops through prefetch with careful grace,
Admitting sources at the right pace,
Windows sliding, gradients computed,
Bad entries wisely executed,
Control takes wing, efficiency soars! 🚀

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Prefetch sample adjust' is vague and does not clearly convey the substantial changes made to the prefetch admission control and adaptive systems. Use a more descriptive title such as 'Add prefetch admission control and adaptive policies' or 'Implement windowed prefetch admission control with adaptive tuning' to accurately reflect the scope of changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch prefetch-sample-adjust

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Infer (1.2.0)
src/mem/cache/prefetch/base.cc

src/mem/cache/prefetch/base.cc:46:10: fatal error: 'mem/cache/prefetch/base.hh' file not found
46 | #include "mem/cache/prefetch/base.hh"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/f38e5ac2d640b930a8ba07d4b57f3c26dced39a7-f20c0580892343cf/tmp/clang_command_.tmp.c7465c.txt
++Contents of '/tmp/coderabbit-infer/f38e5ac2d640b930a8ba07d4b57f3c26dced39a7-f20c0580892343cf/tmp/clang_command_.tmp.c7465c.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExporter" "MAX_STRING_SIZE=65535" "-cc1" "-triple"
"x86_64-unknown-linux-g

... [truncated 1104 characters] ...

tall/lib/clang/18/include"
"-internal-isystem" "/usr/local/include" "-internal-isystem"
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
"-internal-externc-isystem" "/usr/include/x86_64-linux-gnu"
"-internal-externc-isystem" "/include" "-internal-externc-isystem"
"/usr/include" "-Wno-ignored-optimization-argument" "-Wno-everything"
"-fdeprecated-macro" "-ferror-limit" "19" "-fgnuc-version=4.2.1"
"-fskip-odr-check-in-gmf" "-fcxx-exceptions" "-fexceptions"
"-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o"
"/tmp/coderabbit-infer/f20c0580892343cf/file.o" "-x" "c++"
"src/mem/cache/prefetch/base.cc" "-O0" "-fno-builtin" "-include"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../lib/clang_wrappers/global_defines.h"
"-Wno-everything"

src/mem/cache/base.cc

src/mem/cache/base.cc:46:10: fatal error: 'mem/cache/base.hh' file not found
46 | #include "mem/cache/base.hh"
| ^~~~~~~~~~~~~~~~~~~
1 error generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/f38e5ac2d640b930a8ba07d4b57f3c26dced39a7-2d9c7cc528a4db58/tmp/clang_command_.tmp.065948.txt
++Contents of '/tmp/coderabbit-infer/f38e5ac2d640b930a8ba07d4b57f3c26dced39a7-2d9c7cc528a4db58/tmp/clang_command_.tmp.065948.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExporter" "MAX_STRING_SIZE=65535" "-cc1" "-triple"
"x86_64-unknown-linux-gnu" "-emit-obj" "-mrelax-all" "-disa

... [truncated 1059 characters] ...

clang/install/lib/clang/18/include"
"-internal-isystem" "/usr/local/include" "-internal-isystem"
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
"-internal-externc-isystem" "/usr/include/x86_64-linux-gnu"
"-internal-externc-isystem" "/include" "-internal-externc-isystem"
"/usr/include" "-Wno-ignored-optimization-argument" "-Wno-everything"
"-fdeprecated-macro" "-ferror-limit" "19" "-fgnuc-version=4.2.1"
"-fskip-odr-check-in-gmf" "-fcxx-exceptions" "-fexceptions"
"-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o"
"/tmp/coderabbit-infer/2d9c7cc528a4db58/file.o" "-x" "c++"
"src/mem/cache/base.cc" "-O0" "-fno-builtin" "-include"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../lib/clang_wrappers/global_defines.h"
"-Wno-everything"

src/mem/cache/prefetch/composite_with_worker.cc

src/mem/cache/prefetch/composite_with_worker.cc:1:10: fatal error: 'mem/cache/prefetch/composite_with_worker.hh' file not found
1 | #include "mem/cache/prefetch/composite_with_worker.hh"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/f38e5ac2d640b930a8ba07d4b57f3c26dced39a7-54bfca7c626aaf56/tmp/clang_command_.tmp.fd8458.txt
++Contents of '/tmp/coderabbit-infer/f38e5ac2d640b930a8ba07d4b57f3c26dced39a7-54bfca7c626aaf56/tmp/clang_command_.tmp.fd8458.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExporter" "

... [truncated 1213 characters] ...

lude"
"-internal-isystem" "/usr/local/include" "-internal-isystem"
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
"-internal-externc-isystem" "/usr/include/x86_64-linux-gnu"
"-internal-externc-isystem" "/include" "-internal-externc-isystem"
"/usr/include" "-Wno-ignored-optimization-argument" "-Wno-everything"
"-fdeprecated-macro" "-ferror-limit" "19" "-fgnuc-version=4.2.1"
"-fskip-odr-check-in-gmf" "-fcxx-exceptions" "-fexceptions"
"-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o"
"/tmp/coderabbit-infer/54bfca7c626aaf56/file.o" "-x" "c++"
"src/mem/cache/prefetch/composite_with_worker.cc" "-O0" "-fno-builtin"
"-include"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../lib/clang_wrappers/global_defines.h"
"-Wno-everything"

  • 4 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/mem/request.hh (1)

1220-1225: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep cloned Request objects consistent with the new PF metadata mirror.

These setters now synchronize _xsMetadata with pfSource/pfDepth, but Request(const Request& other) on Line 613 still copies only _xsMetadata. splitOnVaddr() on Line 744 uses that copy path, so a split/cloned request can carry a non-default XsMetadata while getPFSource()/getPFDepth() fall back to PF_NONE/0. That leaves downstream code dependent on which accessor it reads.

Suggested fix
 Request(const Request& other)
         : _paddr(other._paddr), _size(other._size),
           _byteEnable(other._byteEnable),
           _requestorId(other._requestorId),
           _flags(other._flags),
           _cacheCoherenceFlags(other._cacheCoherenceFlags),
           privateFlags(other.privateFlags),
           _time(other._time),
           _taskId(other._taskId), _vaddr(other._vaddr),
           _extraData(other._extraData), _contextId(other._contextId),
           _pc(other._pc), _reqInstSeqNum(other._reqInstSeqNum),
           _xsMetadata(other._xsMetadata),
           _localAccessor(other._localAccessor),
           translateDelta(other.translateDelta),
-          accessDelta(other.accessDelta), depth(other.depth)
+          accessDelta(other.accessDelta), depth(other.depth),
+          pfSource(other.pfSource),
+          pfDepth(other.pfDepth),
+          firstReqAfterSquash(other.firstReqAfterSquash)
     {
         atomicOpFunctor.reset(other.atomicOpFunctor ?
                                 other.atomicOpFunctor->clone() : nullptr);
     }

Also applies to: 1339-1354

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mem/request.hh` around lines 1220 - 1225, The copy path for cloned
Requests leaves pfSource/pfDepth out of sync with _xsMetadata: update
Request(const Request& other) (and any other clone/copy paths around the second
region) to copy other._xsMetadata, set privateFlags VALID_XS_METADATA, and
assign pfSource = other._xsMetadata.prefetchSource and pfDepth =
other._xsMetadata.prefetchDepth so getPFSource()/getPFDepth() reflect the
mirrored PF metadata (splitOnVaddr() uses the copy ctor so this will fix split
clones as well).
🧹 Nitpick comments (3)
configs/example/smt_idealkmhv3.py (1)

8-8: 💤 Low value

Consider adding a public API to reset the config cache.

Directly accessing the private _LOADED_PF_CONTROL_CONFIG variable (line 29) couples this example script to implementation details of PrefetcherConfig. If the caching mechanism changes, this code will break silently.

♻️ Suggested approach

Add a public function in PrefetcherConfig.py:

def reset_pf_control_config():
    """Clear cached config so next load picks up any PF_CONTROL_CONFIG changes."""
    global _LOADED_PF_CONTROL_CONFIG
    _LOADED_PF_CONTROL_CONFIG = None

Then in smt_idealkmhv3.py:

     PrefetcherConfig.PF_CONTROL_CONFIG = {
         ...
     }
-    PrefetcherConfig._LOADED_PF_CONTROL_CONFIG = None
+    PrefetcherConfig.reset_pf_control_config()

Also applies to: 13-29

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configs/example/smt_idealkmhv3.py` at line 8, The example imports
PrefetcherConfig but directly manipulates the private _LOADED_PF_CONTROL_CONFIG;
add a public reset API in PrefetcherConfig (e.g., reset_pf_control_config())
that clears the internal cache, then update this example (smt_idealkmhv3.py) to
call PrefetcherConfig.reset_pf_control_config() instead of touching
_LOADED_PF_CONTROL_CONFIG; ensure the new function sets the module's cache
variable to None so subsequent PrefetcherConfig.load/get calls pick up changes.
src/mem/cache/prefetch/worker.cc (1)

68-97: ⚡ Quick win

Consider extracting common admission control logic.

Both the queueFilter enabled and disabled branches now duplicate the same admission control check and drop logic. The only difference is the queue-duplicate checks in the enabled branch.

♻️ Suggested refactor to reduce duplication
     auto dpp_it = localBuffer.begin();
     while (count < depth && !localBuffer.empty()) {
         auto drop_local_packet = [&dpp_it]() {
             if (dpp_it->pkt != nullptr) {
                 delete dpp_it->pkt;
                 dpp_it->pkt = nullptr;
             }
         };
+
+        bool should_drop = false;
         if (queueFilter) {
             if (alreadyInQueue(pfq, dpp_it->pfInfo, dpp_it->priority)) {
-                drop_local_packet();
+                should_drop = true;
                 DPRINTF(WorkerPref, "Worker: [%lx, %d] was already in pfq\n", dpp_it->pfInfo.getAddr(),
                         dpp_it->pfahead_host);
             } else if (alreadyInQueue(pfqMissingTranslation, dpp_it->pfInfo,
                                       dpp_it->priority)) {
-                drop_local_packet();
+                should_drop = true;
                 DPRINTF(WorkerPref, "Worker: [%lx, %d] was already in pfq\n", dpp_it->pfInfo.getAddr(),
                         dpp_it->pfahead_host);
-            } else if (!admitPfControlDeferredPacket(*dpp_it)) {
-                drop_local_packet();
-                DPRINTF(WorkerPref, "Worker: [%lx, %d] dropped by PF control admission\n",
-                        dpp_it->pfInfo.getAddr(), dpp_it->pfahead_host);
-            } else {
-                addToQueue(pfq, *dpp_it);
-                DPRINTF(WorkerPref, "Worker: put [%lx, %d] into local pfq\n", dpp_it->pfInfo.getAddr(),
-                        dpp_it->pfahead_host);
             }
-        } else {
+        }
+
+        if (!should_drop) {
             if (!admitPfControlDeferredPacket(*dpp_it)) {
-                drop_local_packet();
+                should_drop = true;
                 DPRINTF(WorkerPref, "Worker: [%lx, %d] dropped by PF control admission\n",
                         dpp_it->pfInfo.getAddr(), dpp_it->pfahead_host);
-            } else {
-                addToQueue(pfq, *dpp_it);
-                DPRINTF(WorkerPref, "Worker: put [%lx, %d] into local pfq\n", dpp_it->pfInfo.getAddr(),
-                        dpp_it->pfahead_host);
             }
         }
+
+        if (should_drop) {
+            drop_local_packet();
+        } else {
+            addToQueue(pfq, *dpp_it);
+            DPRINTF(WorkerPref, "Worker: put [%lx, %d] into local pfq\n", dpp_it->pfInfo.getAddr(),
+                    dpp_it->pfahead_host);
+        }
+
         dpp_it = localBuffer.erase(dpp_it);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mem/cache/prefetch/worker.cc` around lines 68 - 97, The two branches
guarded by queueFilter duplicate the same PF admission control and enqueue/drop
behavior — extract the common admission check into a single path: first, if
queueFilter perform the duplicate checks (alreadyInQueue against pfq and
pfqMissingTranslation using dpp_it->pfInfo and dpp_it->priority) and drop if
found; otherwise run the common admitPfControlDeferredPacket(*dpp_it) check once
and call drop_local_packet() + DPRINTF on failure or addToQueue(pfq, *dpp_it) +
DPRINTF on success. Refactor so the admitPfControlDeferredPacket,
drop_local_packet, addToQueue, and corresponding DPRINTF calls are not
duplicated, keeping only the duplicate queue-filtering calls inside the
queueFilter-specific section and then delegating to the shared admission/enqueue
logic.
src/mem/cache/base.cc (1)

961-975: 💤 Low value

Minor redundant assignment when creating new XsMetadata.

When pkt->req->hasXsMetadata() is false, lines 968-971 construct a new XsMetadata with (pf_source, pf_depth), then lines 972-973 immediately reassign the same values. While harmless, this is slightly inefficient.

♻️ Optional refactor to eliminate redundant assignment
-    Request::XsMetadata xs_meta =
-        pkt->req->hasXsMetadata() ?
-        pkt->req->getXsMetadata() :
-        Request::XsMetadata(pf_source, pf_depth);
-    xs_meta.prefetchSource = pf_source;
-    xs_meta.prefetchDepth = pf_depth;
+    Request::XsMetadata xs_meta = pkt->req->hasXsMetadata() 
+        ? pkt->req->getXsMetadata() 
+        : Request::XsMetadata();
+    xs_meta.prefetchSource = pf_source;
+    xs_meta.prefetchDepth = pf_depth;

Or if the XsMetadata constructor is meant to initialize these fields:

-    Request::XsMetadata xs_meta =
-        pkt->req->hasXsMetadata() ?
-        pkt->req->getXsMetadata() :
-        Request::XsMetadata(pf_source, pf_depth);
-    xs_meta.prefetchSource = pf_source;
-    xs_meta.prefetchDepth = pf_depth;
+    Request::XsMetadata xs_meta = pkt->req->getXsMetadata();
+    if (!pkt->req->hasXsMetadata()) {
+        xs_meta = Request::XsMetadata(pf_source, pf_depth);
+    } else {
+        xs_meta.prefetchSource = pf_source;
+        xs_meta.prefetchDepth = pf_depth;
+    }

Note: Only worthwhile if this code path is performance-sensitive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mem/cache/base.cc` around lines 961 - 975, The code in the
pure_prefetch_fill branch redundantly constructs Request::XsMetadata(pf_source,
pf_depth) then immediately overwrites xs_meta.prefetchSource and
xs_meta.prefetchDepth; update the logic around pkt->req->hasXsMetadata() so that
when there is no existing XsMetadata you create the XsMetadata already
initialized with pf_source and pf_depth and skip the subsequent reassignments,
and only set xs_meta.prefetchSource/prefetchDepth when you created a default
object without constructor args or when pkt->req->hasXsMetadata() is true;
ensure you still call pkt->req->setXsMetadata(xs_meta) and keep the existing
pkt->req->setPFSource(pf_source) / setPFDepth(pf_depth) calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@configs/common/PrefetcherConfig.py`:
- Around line 218-251: The function _parse_source_pct_table returns early for
full-list input but fails to return the populated table for
dict/string/partial-list inputs, causing callers like _apply_pf_control to
receive None for prefetcher.pf_control_source_admit_pcts; fix this by adding a
final "return table" at the end of _parse_source_pct_table (after the for-loop)
so the populated list is always returned for all valid spec types, ensuring a
proper list of ints is passed to the C++ side.

In `@src/mem/cache/base.cc`:
- Around line 2253-2255: The new third parameter prefetch_fill_source on
BaseCache::allocateBlock is breaking existing two-argument calls; to fix, add a
default value for the parameter in the function declaration (and match it in the
definition if needed) so existing callers like allocateBlock(pkt, writebacks)
compile—use a sensible default such as PrefetchSourceType::None (or the
equivalent enum value used elsewhere); alternatively, update every call site
(e.g., the calls currently invoking allocateBlock(pkt, writebacks)) to pass an
explicit PrefetchSourceType argument if a non-default behavior is required.
- Around line 2234-2243: When prefetch_fill_source !=
PrefetchSourceType::PF_NONE the code must guarantee metadata is present before
reading prefetched depth; add a defensive assertion that pkt->req is non-null
and pkt->req->hasXsMetadata() is true (e.g. assert(pkt->req &&
pkt->req->hasXsMetadata())) right before reading
pkt->req->getXsMetadata().prefetchDepth so blk_meta.prefetchDepth cannot remain
stale from blk->getXsMetadata(); keep the existing assignment of
blk_meta.prefetchSource and continue to set blk_meta.prefetchDepth from
pkt->req->getXsMetadata().prefetchDepth after the assertion.

In `@src/mem/cache/prefetch/queued.cc`:
- Around line 383-389: The warmup branch unconditionally returns 100%, bypassing
configured admit pcts; change the early-return so that when
pfAdaptiveSampleCount < pfAdaptiveWarmupWindows you compute and return the
configured fallback/override admit percent for the given source instead of 100.
Specifically, inside the pfAdaptive && isPfAdaptiveLevel() block use
sanitizePfControlSource(source) to get source_idx and then consult per-source
settings (pfControlCurrentAdmitPctBySource[source_idx] or the controller-level
pf_control_admit_pct/pf_control_source_admit_pcts fallback logic) so the same
sweep/warmup policy is applied during adaptive warmup rather than forcing 100%.
- Around line 778-789: The code currently computes allowed/admit using
per-source counters unconditionally; change it so if this source is configured
to "use the global prefetch control action" (detect the fallback case for the
source rather than assuming per-source pct) you compute allowed and admit using
the global counters (pfControlWindowCandidates and pfControlWindowAdmitted) and
the global fallback pct, otherwise keep the existing per-source computation
using pfControlWindowCandidatesBySource[source_idx] and
pfControlWindowAdmittedBySource[source_idx]; update the branch around
sanitizePfControlSource / pfControlCurrentAdmitPctBySource[source_idx] so the
fallback path uses pfControlWindowCandidates, pfControlWindowAdmitted and the
global pct, while explicit per-source or adaptive modes continue to use the
per-source counters.

---

Outside diff comments:
In `@src/mem/request.hh`:
- Around line 1220-1225: The copy path for cloned Requests leaves
pfSource/pfDepth out of sync with _xsMetadata: update Request(const Request&
other) (and any other clone/copy paths around the second region) to copy
other._xsMetadata, set privateFlags VALID_XS_METADATA, and assign pfSource =
other._xsMetadata.prefetchSource and pfDepth = other._xsMetadata.prefetchDepth
so getPFSource()/getPFDepth() reflect the mirrored PF metadata (splitOnVaddr()
uses the copy ctor so this will fix split clones as well).

---

Nitpick comments:
In `@configs/example/smt_idealkmhv3.py`:
- Line 8: The example imports PrefetcherConfig but directly manipulates the
private _LOADED_PF_CONTROL_CONFIG; add a public reset API in PrefetcherConfig
(e.g., reset_pf_control_config()) that clears the internal cache, then update
this example (smt_idealkmhv3.py) to call
PrefetcherConfig.reset_pf_control_config() instead of touching
_LOADED_PF_CONTROL_CONFIG; ensure the new function sets the module's cache
variable to None so subsequent PrefetcherConfig.load/get calls pick up changes.

In `@src/mem/cache/base.cc`:
- Around line 961-975: The code in the pure_prefetch_fill branch redundantly
constructs Request::XsMetadata(pf_source, pf_depth) then immediately overwrites
xs_meta.prefetchSource and xs_meta.prefetchDepth; update the logic around
pkt->req->hasXsMetadata() so that when there is no existing XsMetadata you
create the XsMetadata already initialized with pf_source and pf_depth and skip
the subsequent reassignments, and only set xs_meta.prefetchSource/prefetchDepth
when you created a default object without constructor args or when
pkt->req->hasXsMetadata() is true; ensure you still call
pkt->req->setXsMetadata(xs_meta) and keep the existing
pkt->req->setPFSource(pf_source) / setPFDepth(pf_depth) calls.

In `@src/mem/cache/prefetch/worker.cc`:
- Around line 68-97: The two branches guarded by queueFilter duplicate the same
PF admission control and enqueue/drop behavior — extract the common admission
check into a single path: first, if queueFilter perform the duplicate checks
(alreadyInQueue against pfq and pfqMissingTranslation using dpp_it->pfInfo and
dpp_it->priority) and drop if found; otherwise run the common
admitPfControlDeferredPacket(*dpp_it) check once and call drop_local_packet() +
DPRINTF on failure or addToQueue(pfq, *dpp_it) + DPRINTF on success. Refactor so
the admitPfControlDeferredPacket, drop_local_packet, addToQueue, and
corresponding DPRINTF calls are not duplicated, keeping only the duplicate
queue-filtering calls inside the queueFilter-specific section and then
delegating to the shared admission/enqueue logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9e40a127-d275-44fd-b101-b327ded0146b

📥 Commits

Reviewing files that changed from the base of the PR and between d56bd30 and f38e5ac.

📒 Files selected for processing (17)
  • configs/common/PrefetcherConfig.py
  • configs/example/smt_idealkmhv3.py
  • src/mem/Request.py
  • src/mem/SConscript
  • src/mem/cache/base.cc
  • src/mem/cache/base.hh
  • src/mem/cache/prefetch/Prefetcher.py
  • src/mem/cache/prefetch/base.cc
  • src/mem/cache/prefetch/base.hh
  • src/mem/cache/prefetch/cdp.cc
  • src/mem/cache/prefetch/composite_with_worker.cc
  • src/mem/cache/prefetch/forwarder.cc
  • src/mem/cache/prefetch/forwarder.hh
  • src/mem/cache/prefetch/queued.cc
  • src/mem/cache/prefetch/queued.hh
  • src/mem/cache/prefetch/worker.cc
  • src/mem/request.hh

Comment on lines +218 to +251
def _parse_source_pct_table(spec, path):
table = [-1] * len(PF_SOURCE_NAMES)
if spec in (None, "", {}, []):
return []

if isinstance(spec, dict):
items = spec.items()
elif isinstance(spec, str):
items = []
for raw_pair in spec.split(','):
pair = raw_pair.strip()
if not pair:
continue
if '=' not in pair:
_config_error(path, "source=pct entries", pair)
raw_source, raw_pct = pair.split('=', 1)
items.append((raw_source.strip(), raw_pct.strip()))
elif isinstance(spec, (list, tuple)):
if len(spec) == len(PF_SOURCE_NAMES):
for idx, pct in enumerate(spec):
table[idx] = _validate_source_pct(pct, f"{path}.{idx}")
return table
items = spec
else:
_config_error(path, "a dict, full list, pair list, or string", spec)

for item in items:
try:
raw_source, raw_pct = item
except (TypeError, ValueError):
_config_error(path, "source/pct pairs", item)
source = _parse_pf_source(str(raw_source).strip())
table[source] = _validate_source_pct(raw_pct, f"{path}.{raw_source}")
return table

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing return table statement causes function to return None.

When spec is a dict, string, or partial list, the function parses items and populates table in the for loop (lines 244-250), but never returns it. This causes _apply_pf_control at line 312 to assign None to prefetcher.pf_control_source_admit_pcts, which will likely cause runtime errors when the C++ side expects a vector of integers.

🐛 Proposed fix
     for item in items:
         try:
             raw_source, raw_pct = item
         except (TypeError, ValueError):
             _config_error(path, "source/pct pairs", item)
         source = _parse_pf_source(str(raw_source).strip())
         table[source] = _validate_source_pct(raw_pct, f"{path}.{raw_source}")
+    return table
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configs/common/PrefetcherConfig.py` around lines 218 - 251, The function
_parse_source_pct_table returns early for full-list input but fails to return
the populated table for dict/string/partial-list inputs, causing callers like
_apply_pf_control to receive None for prefetcher.pf_control_source_admit_pcts;
fix this by adding a final "return table" at the end of _parse_source_pct_table
(after the for-loop) so the populated list is always returned for all valid spec
types, ensuring a proper list of ints is passed to the C++ side.

Comment thread src/mem/cache/base.cc
Comment on lines +2234 to +2243
if (prefetch_fill_source != PrefetchSourceType::PF_NONE) {
blk_meta.prefetchSource = prefetch_fill_source;
if (pkt->req && pkt->req->hasXsMetadata()) {
blk_meta.prefetchDepth =
pkt->req->getXsMetadata().prefetchDepth;
}
} else {
blk_meta.prefetchSource = PrefetchSourceType::PF_NONE;
blk_meta.prefetchDepth = 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add defensive assertion for metadata presence during prefetch fills.

When prefetch_fill_source != PF_NONE (indicating a prefetch fill), the code sets blk_meta.prefetchSource but only sets blk_meta.prefetchDepth if pkt->req->hasXsMetadata() returns true. For newly allocated blocks, if the packet lacks metadata, prefetchDepth would be left at whatever blk->getXsMetadata() returned (potentially uninitialized or stale from the evicted block).

The upstream code (lines 963-975) ensures metadata is set for pure prefetch fills, so this shouldn't occur in practice. However, an assertion would make this invariant explicit and catch violations during development.

🛡️ Proposed defensive assertion
 Request::XsMetadata blk_meta = blk->getXsMetadata();
 if (prefetch_fill_source != PrefetchSourceType::PF_NONE) {
     blk_meta.prefetchSource = prefetch_fill_source;
     if (pkt->req && pkt->req->hasXsMetadata()) {
         blk_meta.prefetchDepth =
             pkt->req->getXsMetadata().prefetchDepth;
+    } else {
+        // Pure prefetch fills should always have metadata set by line 963-975
+        panic("Prefetch fill missing XsMetadata: source=%d, addr=%#x",
+              prefetch_fill_source, pkt->getAddr());
     }
 } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (prefetch_fill_source != PrefetchSourceType::PF_NONE) {
blk_meta.prefetchSource = prefetch_fill_source;
if (pkt->req && pkt->req->hasXsMetadata()) {
blk_meta.prefetchDepth =
pkt->req->getXsMetadata().prefetchDepth;
}
} else {
blk_meta.prefetchSource = PrefetchSourceType::PF_NONE;
blk_meta.prefetchDepth = 0;
}
if (prefetch_fill_source != PrefetchSourceType::PF_NONE) {
blk_meta.prefetchSource = prefetch_fill_source;
if (pkt->req && pkt->req->hasXsMetadata()) {
blk_meta.prefetchDepth =
pkt->req->getXsMetadata().prefetchDepth;
} else {
// Pure prefetch fills should always have metadata set by line 963-975
panic("Prefetch fill missing XsMetadata: source=%d, addr=%#x",
prefetch_fill_source, pkt->getAddr());
}
} else {
blk_meta.prefetchSource = PrefetchSourceType::PF_NONE;
blk_meta.prefetchDepth = 0;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mem/cache/base.cc` around lines 2234 - 2243, When prefetch_fill_source !=
PrefetchSourceType::PF_NONE the code must guarantee metadata is present before
reading prefetched depth; add a defensive assertion that pkt->req is non-null
and pkt->req->hasXsMetadata() is true (e.g. assert(pkt->req &&
pkt->req->hasXsMetadata())) right before reading
pkt->req->getXsMetadata().prefetchDepth so blk_meta.prefetchDepth cannot remain
stale from blk->getXsMetadata(); keep the existing assignment of
blk_meta.prefetchSource and continue to set blk_meta.prefetchDepth from
pkt->req->getXsMetadata().prefetchDepth after the assertion.

Comment thread src/mem/cache/base.cc
Comment on lines +2253 to +2255
BaseCache::allocateBlock(
const PacketPtr pkt, PacketList &writebacks,
PrefetchSourceType prefetch_fill_source)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Compilation error: existing callers not updated with new parameter.

The allocateBlock signature now requires a third parameter prefetch_fill_source, but existing callers at lines 1912 and 1992 still call it with only two arguments:

blk = allocateBlock(pkt, writebacks);

These calls will fail to compile unless a default parameter value is added or all call sites are updated.

🔧 Proposed fix options

Option 1: Add default parameter value

 CacheBlk*
 BaseCache::allocateBlock(
     const PacketPtr pkt, PacketList &writebacks,
-    PrefetchSourceType prefetch_fill_source)
+    PrefetchSourceType prefetch_fill_source = PrefetchSourceType::PF_NONE)

Option 2: Update all call sites
At lines 1912 and 1992:

-blk = allocateBlock(pkt, writebacks);
+blk = allocateBlock(pkt, writebacks, PrefetchSourceType::PF_NONE);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mem/cache/base.cc` around lines 2253 - 2255, The new third parameter
prefetch_fill_source on BaseCache::allocateBlock is breaking existing
two-argument calls; to fix, add a default value for the parameter in the
function declaration (and match it in the definition if needed) so existing
callers like allocateBlock(pkt, writebacks) compile—use a sensible default such
as PrefetchSourceType::None (or the equivalent enum value used elsewhere);
alternatively, update every call site (e.g., the calls currently invoking
allocateBlock(pkt, writebacks)) to pass an explicit PrefetchSourceType argument
if a non-default behavior is required.

Comment on lines +383 to +389
if (pfAdaptive && isPfAdaptiveLevel()) {
const unsigned source_idx = sanitizePfControlSource(source);
if (pfAdaptiveSampleCount < pfAdaptiveWarmupWindows) {
return 100;
}
return pfControlCurrentAdmitPctBySource[source_idx];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Adaptive warmup ignores the configured fallback/override policy.

While pfAdaptiveSampleCount < pfAdaptiveWarmupWindows, this forces every source to 100% admission. That bypasses pf_control_admit_pct, pf_control_source_admit_pcts, and the sweep/warmup policy defined for the controller, so the first adaptive samples are collected under a different policy than the one the config requested.

Suggested fix
     if (pfAdaptive && isPfAdaptiveLevel()) {
         const unsigned source_idx = sanitizePfControlSource(source);
-        if (pfAdaptiveSampleCount < pfAdaptiveWarmupWindows) {
-            return 100;
+        if (pfAdaptiveSampleCount >= pfAdaptiveWarmupWindows) {
+            return pfControlCurrentAdmitPctBySource[source_idx];
         }
-        return pfControlCurrentAdmitPctBySource[source_idx];
     }

     if (window_index >= pfControlWarmupWindows) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mem/cache/prefetch/queued.cc` around lines 383 - 389, The warmup branch
unconditionally returns 100%, bypassing configured admit pcts; change the
early-return so that when pfAdaptiveSampleCount < pfAdaptiveWarmupWindows you
compute and return the configured fallback/override admit percent for the given
source instead of 100. Specifically, inside the pfAdaptive &&
isPfAdaptiveLevel() block use sanitizePfControlSource(source) to get source_idx
and then consult per-source settings
(pfControlCurrentAdmitPctBySource[source_idx] or the controller-level
pf_control_admit_pct/pf_control_source_admit_pcts fallback logic) so the same
sweep/warmup policy is applied during adaptive warmup rather than forcing 100%.

Comment on lines +778 to +789
const unsigned source_idx = sanitizePfControlSource(source);
const unsigned pct = pfControlCurrentAdmitPctBySource[source_idx];
statsQueued.pfControlCandidates++;
statsQueued.pfControlCandidatesByPct[pct]++;
statsQueued.pfControlCandidatesBySource[source_idx]++;
pfControlWindowCandidates++;
pfControlWindowCandidatesBySource[source_idx]++;

const uint64_t allowed =
(pfControlWindowCandidatesBySource[source_idx] * pct + 99) / 100;
const bool admit =
allowed > pfControlWindowAdmittedBySource[source_idx];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

The global fallback percentage is being enforced per source, not globally.

For sources whose config is -1 ("use the global prefetch control action"), allowed is still derived from pfControlWindowCandidatesBySource[source_idx]. That gives each active source its own independent budget. For example, with a 10% fallback and one candidate from eight sources in the same window, all eight are admitted. That breaks the documented semantics of the global fallback action.

The fix needs to use pfControlWindowCandidates / pfControlWindowAdmitted on the fallback path, and reserve the per-source counters for explicit per-source overrides or adaptive mode.

Suggested direction
     const unsigned source_idx = sanitizePfControlSource(source);
     const unsigned pct = pfControlCurrentAdmitPctBySource[source_idx];
+    const bool use_per_source_budget =
+        (pfAdaptive && isPfAdaptiveLevel()) ||
+        pfControlSourceAdmitPct[source_idx] >= 0;
+
+    auto &window_candidates = use_per_source_budget ?
+        pfControlWindowCandidatesBySource[source_idx] :
+        pfControlWindowCandidates;
+    auto &window_admitted = use_per_source_budget ?
+        pfControlWindowAdmittedBySource[source_idx] :
+        pfControlWindowAdmitted;
+
     statsQueued.pfControlCandidates++;
     statsQueued.pfControlCandidatesByPct[pct]++;
     statsQueued.pfControlCandidatesBySource[source_idx]++;
     pfControlWindowCandidates++;
     pfControlWindowCandidatesBySource[source_idx]++;

-    const uint64_t allowed =
-        (pfControlWindowCandidatesBySource[source_idx] * pct + 99) / 100;
-    const bool admit =
-        allowed > pfControlWindowAdmittedBySource[source_idx];
+    const uint64_t allowed = (window_candidates * pct + 99) / 100;
+    const bool admit = allowed > window_admitted;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mem/cache/prefetch/queued.cc` around lines 778 - 789, The code currently
computes allowed/admit using per-source counters unconditionally; change it so
if this source is configured to "use the global prefetch control action" (detect
the fallback case for the source rather than assuming per-source pct) you
compute allowed and admit using the global counters (pfControlWindowCandidates
and pfControlWindowAdmitted) and the global fallback pct, otherwise keep the
existing per-source computation using
pfControlWindowCandidatesBySource[source_idx] and
pfControlWindowAdmittedBySource[source_idx]; update the branch around
sanitizePfControlSource / pfControlCurrentAdmitPctBySource[source_idx] so the
fallback path uses pfControlWindowCandidates, pfControlWindowAdmitted and the
global pct, while explicit per-source or adaptive modes continue to use the
per-source counters.

@github-actions

Copy link
Copy Markdown

🚀 Coremark Smoke Test Results

Branch IPC Change
Base (xs-dev) 2.3155 -
This PR 2.3155 ➡️ 0.0000 (0.00%)

✅ Difftest smoke test passed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant