[Lldb-commits] [lldb] f477555 - [lldb] Memory cache: no overlap and read across cache entries (#222688)
via lldb-commits
lldb-commits at lists.llvm.org
Mon Sep 14 03:39:45 PDT 2026
Author: Yao Qi
Date: 2026-09-14T11:39:40+01:00
New Revision: f477555c212d2eddd65f5a791b19efe075e2c708
URL: https://github.com/llvm/llvm-project/commit/f477555c212d2eddd65f5a791b19efe075e2c708
DIFF: https://github.com/llvm/llvm-project/commit/f477555c212d2eddd65f5a791b19efe075e2c708.diff
LOG: [lldb] Memory cache: no overlap and read across cache entries (#222688)
The memory cache keeps two collections of cached bytes, L1 and L2, which
are named confusingly. L2 holds cache lines at aligned addresses, but
each line size is less than or equal to the fixed line size. L1 holds
pieces from arbitrary addresses with arbitrary lengths. This caused two
problems:
1. A lookup had to find every byte it wanted inside a *single*
entry. A range that spanned two adjacent entries missed even when the
two collections together already held every byte of it. The lookup
rejected such a read rather than take part of it from each entry.
2. The pieces could also overlap each other, which made the lookup
incomplete as well as ambiguous. It examined only the piece starting at
or below the address, so a short piece hid a longer one that covered the
whole read. The read missed with the bytes resident.
This change fixes these problems by introducing these properties to the
cache:
- At most one collection holds any given address.
- The line collection holds only whole, aligned lines (`LineCache`).
- The piece collection holds only fragments smaller than a line, and
they never overlap each other (`ChunkCache`).
- A read takes what it needs from consecutive entries in turn until a
miss (`ReadFromCaches`).
- `AddCacheData` (renamed from `AddL1CacheData`) splits incoming bytes
at line boundaries and drops bytes already held, so callers no longer
pick the cache tier.
Disjoint entries make the cache simple to reason about:
- A read no longer depends on which copy of an address is found first,
because there is only one.
- Probing one candidate is now complete. Since entries cannot
overlap, the entry starting at or below an address is the only one
that can hold it. No byte is stored twice.
- Invalidating an address touches one entry, with no second copy
elsewhere to find and drop.
- Callers of the cache need no knowledge of cache internals.
`ReadRanges` also uses `ReadFromCaches`. Previously, each requested
range had to fit entirely within a single cache entry. Ranges can now
be served across multiple entries and line boundaries. For example,
`Process::ReadCStringsFromMemory` reads strings in 256-byte windows.
Because strings start at arbitrary offsets, these windows frequently
span
two adjacent cache lines. Under the old lookup, such a window missed in
`ReadRanges` even when both lines were resident. It now hits from cache
across the line boundary.
`Flush` now drops all cached entries that intersect the range instead of
erasing only the entry matching the start address. It uses saturating
addition to prevent address wraparound. Its runtime is bounded by the
number of cached entries rather than the range size.
`GrowReadRange` isolates the read-ahead policy from `Read`. It aligns
the read address down to the line base so fetches land in `LineCache` as
whole lines. It caps read-ahead to two lines, skips the last line if
already resident, and clips the read at invalid ranges.
Assited-by: claude
Added:
Modified:
lldb/include/lldb/Target/Memory.h
lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
lldb/source/Target/Memory.cpp
lldb/unittests/Target/MemoryTest.cpp
Removed:
################################################################################
diff --git a/lldb/include/lldb/Target/Memory.h b/lldb/include/lldb/Target/Memory.h
index ad902ea584449..c874a8456b200 100644
--- a/lldb/include/lldb/Target/Memory.h
+++ b/lldb/include/lldb/Target/Memory.h
@@ -12,12 +12,97 @@
#include "lldb/Utility/RangeMap.h"
#include "lldb/lldb-private.h"
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include <map>
+#include <memory>
#include <mutex>
#include <vector>
namespace lldb_private {
+
+/// A set of cache entries, all of which are aligned and have the same size.
+/// Entries cannot be partially filled.
+class LineCache {
+ /// Keyed by line index, so a key names a whole line.
+ using Collection = llvm::DenseMap<uint64_t, std::unique_ptr<uint8_t[]>>;
+
+public:
+ explicit LineCache(uint32_t line_byte_size)
+ : m_line_byte_size(line_byte_size) {}
+
+ uint32_t GetLineByteSize() const { return m_line_byte_size; }
+
+ /// The cached bytes from \a addr to the end of the line holding it, empty if
+ /// that line is not resident.
+ llvm::ArrayRef<uint8_t> Lookup(lldb::addr_t addr) const;
+
+ bool Holds(lldb::addr_t addr) const {
+ return m_lines.contains(IndexOf(addr));
+ }
+
+ /// Add one whole line. \a addr must be line aligned and \a src must hold a
+ /// whole line.
+ void Insert(lldb::addr_t addr, llvm::ArrayRef<uint8_t> src);
+
+ /// Drop every line that intersects [addr, addr+size).
+ void EraseRange(lldb::addr_t addr, lldb::addr_t size);
+
+ void Clear(uint32_t new_line_byte_size) {
+ m_lines.clear();
+ m_line_byte_size = new_line_byte_size;
+ }
+
+ size_t GetSize() const { return m_lines.size(); }
+
+ /// Iteration yields a line index and its bytes, in unspecified order.
+ using const_iterator = Collection::const_iterator;
+ const_iterator begin() const { return m_lines.begin(); }
+ const_iterator end() const { return m_lines.end(); }
+
+private:
+ uint64_t IndexOf(lldb::addr_t addr) const { return addr / m_line_byte_size; }
+
+ Collection m_lines;
+ uint32_t m_line_byte_size;
+};
+
+/// A set of non-overlapping byte ranges at arbitrary addresses. Lengths vary,
+/// so every chunk carries its own.
+class ChunkCache {
+ using Collection = std::map<lldb::addr_t, std::vector<uint8_t>>;
+
+public:
+ /// The cached bytes from \a addr to the end of the chunk holding it, empty
+ /// if no chunk holds it.
+ llvm::ArrayRef<uint8_t> Lookup(lldb::addr_t addr) const;
+
+ bool Holds(lldb::addr_t addr) const { return !Lookup(addr).empty(); }
+
+ /// Add the bytes of [addr, addr+src.size()) that no chunk holds yet. Bytes
+ /// already held are kept: which read produced a byte does not matter.
+ void InsertMissing(lldb::addr_t addr, llvm::ArrayRef<uint8_t> src);
+
+ /// Drop every chunk that intersects [addr, addr+size).
+ void EraseRange(lldb::addr_t addr, lldb::addr_t size);
+
+ void Clear() { m_chunks.clear(); }
+
+ size_t GetSize() const { return m_chunks.size(); }
+
+ /// Iteration yields a chunk's start address and its bytes, in address order.
+ using const_iterator = Collection::const_iterator;
+ const_iterator begin() const { return m_chunks.begin(); }
+ const_iterator end() const { return m_chunks.end(); }
+
+private:
+ /// The chunk holding \a addr, or end(). Chunks never overlap, so only the
+ /// one starting at or below \a addr can hold it.
+ Collection::const_iterator FindChunkContaining(lldb::addr_t addr) const;
+
+ Collection m_chunks;
+};
+
// A class to track memory that was read from a live process between
// runs.
class MemoryCache {
@@ -33,66 +118,69 @@ class MemoryCache {
size_t Read(lldb::addr_t addr, void *dst, size_t dst_len, Status &error);
- /// Reads multiple memory ranges, serving cache hits from L1 and L2 and
- /// batching all misses through Process::DoReadMemoryRanges. The semantics of
- /// the return value match Process::ReadMemoryRanges.
+ /// Reads memory ranges, serving hits from the cache and batching misses
+ /// through Process::DoReadMemoryRanges. Matches Process::ReadMemoryRanges.
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>
ReadRanges(llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges,
llvm::MutableArrayRef<uint8_t> buffer);
- uint32_t GetMemoryCacheLineSize() const { return m_L2_cache_line_byte_size; }
+ uint32_t GetMemoryCacheLineSize() const {
+ return m_L2_cache.GetLineByteSize();
+ }
void AddInvalidRange(lldb::addr_t base_addr, lldb::addr_t byte_size);
bool RemoveInvalidRange(lldb::addr_t base_addr, lldb::addr_t byte_size);
- // Allow external sources to populate data into the L1 memory cache
- void AddL1CacheData(lldb::addr_t addr, const void *src, size_t src_len);
+ /// Allow external sources to populate data into the memory cache.
+ void AddCacheData(lldb::addr_t addr, const void *src, size_t src_len);
- void AddL1CacheData(lldb::addr_t addr, llvm::ArrayRef<uint8_t> src) {
+ void AddCacheData(lldb::addr_t addr, llvm::ArrayRef<uint8_t> src) {
if (!src.empty())
- AddL1CacheData(addr, src.data(), src.size());
+ AddCacheData(addr, src.data(), src.size());
}
- void AddL1CacheData(lldb::addr_t addr,
- const lldb::DataBufferSP &data_buffer_sp);
+ void AddCacheData(lldb::addr_t addr,
+ const lldb::DataBufferSP &data_buffer_sp);
protected:
- typedef std::map<lldb::addr_t, lldb::DataBufferSP> BlockMap;
typedef RangeVector<lldb::addr_t, lldb::addr_t, 4> InvalidRanges;
typedef Range<lldb::addr_t, lldb::addr_t> AddrRange;
// Classes that inherit from MemoryCache can see and modify these
std::recursive_mutex m_mutex;
- BlockMap m_L1_cache; // A first level memory cache whose chunk sizes vary that
- // will be used only if the memory read fits entirely in
- // a chunk
- BlockMap m_L2_cache; // A memory cache of fixed size chinks
- // (m_L2_cache_line_byte_size bytes in size each)
+ // L1 and L2 partition the cache. An address is held by at most one. L2
+ // holds whole, aligned lines; L1 holds smaller, non-overlapping pieces.
+ ChunkCache m_L1_cache; // Chunks smaller than a cache line.
+ LineCache m_L2_cache; // Whole cache lines.
InvalidRanges m_invalid_ranges;
Process &m_process;
- uint32_t m_L2_cache_line_byte_size;
private:
MemoryCache(const MemoryCache &) = delete;
const MemoryCache &operator=(const MemoryCache &) = delete;
- lldb::DataBufferSP GetL2CacheLine(lldb::addr_t addr, Status &error);
-
- // If the entire range [addr, addr+len) is covered by a single L1 entry,
- // returns a pointer into that entry's data at the correct offset. Returns
- // nullptr on a miss. Caller must hold m_mutex.
- const uint8_t *FindL1CacheEntry(lldb::addr_t addr, size_t len) const;
-
- // If the entire range [addr, addr+len) is covered by a single cache line
- // that is already in L2, returns a pointer into that line's data at the
- // correct offset. Never reads from the inferior. Returns nullptr on a miss.
- // Caller must hold m_mutex.
- const uint8_t *FindL2CacheEntry(lldb::addr_t addr, size_t len) const;
-
- // Looks the range [addr, addr+len) up in L1 and then L2, returning a pointer
- // into the data of whichever entry covers it. Returns nullptr on a miss.
+ // Add a whole cache line to L2 and drop the L1 entries it supersedes.
// Caller must hold m_mutex.
- const uint8_t *FindCacheEntry(lldb::addr_t addr, size_t len) const;
+ void InsertWholeLine(lldb::addr_t line_base_addr,
+ llvm::ArrayRef<uint8_t> src);
+
+ // Add the bytes of [addr, addr+src.size()) that no entry holds yet to L1.
+ // The range must lie within one cache line. Caller must hold m_mutex.
+ void InsertPartialLine(lldb::addr_t addr, llvm::ArrayRef<uint8_t> src);
+
+ // Split [addr, addr+src.size()) at cache line boundaries: whole lines to L2,
+ // shorter pieces to L1. Takes m_mutex.
+ void InsertData(lldb::addr_t addr, llvm::ArrayRef<uint8_t> src);
+
+ // Copy the cached bytes of [addr, addr+len), stopping at the first miss,
+ // and return the count. Never reads from the inferior; caller holds m_mutex.
+ size_t ReadFromCaches(lldb::addr_t addr, void *dst, size_t len) const;
+
+ // Returns the range to fetch from the inferior for a read of
+ // [read_addr, caller_end), where the caches already supplied bytes_filled
+ // bytes immediately below read_addr. Caller must hold m_mutex.
+ AddrRange GrowReadRange(lldb::addr_t read_addr, lldb::addr_t caller_end,
+ size_t bytes_filled) const;
};
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index 2f1a3f6f7abed..ccc7ed26a8c9b 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -2373,8 +2373,7 @@ ProcessGDBRemote::SetThreadStopInfo(StructuredData::Dictionary *thread_dict) {
const size_t bytes_copied =
bytes.GetHexBytes(data_buffer_sp->GetData(), 0);
if (bytes_copied == byte_size)
- m_memory_cache.AddL1CacheData(mem_cache_addr,
- data_buffer_sp);
+ m_memory_cache.AddCacheData(mem_cache_addr, data_buffer_sp);
}
}
}
@@ -2564,7 +2563,7 @@ StateType ProcessGDBRemote::SetThreadStopInfo(StringExtractor &stop_packet) {
const size_t bytes_copied =
bytes.GetHexBytes(data_buffer_sp->GetData(), 0);
if (bytes_copied == byte_size)
- m_memory_cache.AddL1CacheData(mem_cache_addr, data_buffer_sp);
+ m_memory_cache.AddCacheData(mem_cache_addr, data_buffer_sp);
}
}
} else if (key.compare("watch") == 0 || key.compare("rwatch") == 0 ||
diff --git a/lldb/source/Target/Memory.cpp b/lldb/source/Target/Memory.cpp
index 5782be0d92f85..c7f58c8e52bb4 100644
--- a/lldb/source/Target/Memory.cpp
+++ b/lldb/source/Target/Memory.cpp
@@ -8,47 +8,183 @@
#include "lldb/Target/Memory.h"
#include "lldb/Target/Process.h"
-#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/RangeMap.h"
#include "lldb/Utility/State.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/MathExtras.h"
+#include <algorithm>
#include <cinttypes>
#include <memory>
+#include <utility>
using namespace lldb;
using namespace lldb_private;
+llvm::ArrayRef<uint8_t> LineCache::Lookup(addr_t addr) const {
+ const auto pos = m_lines.find(IndexOf(addr));
+ if (pos == m_lines.end())
+ return {};
+ const addr_t line_offset = addr % m_line_byte_size;
+ return llvm::ArrayRef(pos->second.get(), m_line_byte_size)
+ .drop_front(line_offset);
+}
+
+void LineCache::Insert(addr_t addr, llvm::ArrayRef<uint8_t> src) {
+ assert((addr % m_line_byte_size) == 0 &&
+ "whole line inserted at an unaligned address!");
+ assert(src.size() == m_line_byte_size &&
+ "whole line inserted with a partial buffer!");
+ auto line = std::make_unique<uint8_t[]>(m_line_byte_size);
+ std::copy(src.begin(), src.end(), line.get());
+ m_lines[IndexOf(addr)] = std::move(line);
+}
+
+void LineCache::EraseRange(addr_t addr, addr_t size) {
+ if (size == 0)
+ return;
+ const addr_t end_addr = llvm::SaturatingAdd(addr, size - 1);
+ const uint64_t first_idx = IndexOf(addr);
+ const uint64_t last_idx = IndexOf(end_addr);
+ m_lines.remove_if([first_idx, last_idx](const auto &entry) {
+ return entry.getFirst() >= first_idx && entry.getFirst() <= last_idx;
+ });
+}
+
+ChunkCache::Collection::const_iterator
+ChunkCache::FindChunkContaining(addr_t addr) const {
+ auto pos = m_chunks.upper_bound(addr);
+ if (pos == m_chunks.begin())
+ return m_chunks.end();
+ --pos;
+ // pos->first + size would overflow for a chunk at the top of the address
+ // space, do subtraction instead.
+ return addr - pos->first < pos->second.size() ? pos : m_chunks.end();
+}
+
+llvm::ArrayRef<uint8_t> ChunkCache::Lookup(addr_t addr) const {
+ auto pos = FindChunkContaining(addr);
+ if (pos == m_chunks.end())
+ return {};
+ return llvm::ArrayRef(pos->second).drop_front(addr - pos->first);
+}
+
+void ChunkCache::InsertMissing(addr_t addr, llvm::ArrayRef<uint8_t> src) {
+ if (src.empty())
+ return;
+ const addr_t last_addr = llvm::SaturatingAdd<addr_t>(addr, src.size() - 1);
+ const uint64_t len = last_addr - addr + 1;
+
+ for (uint64_t offset = 0; offset < len;) {
+ const addr_t curr_addr = addr + offset;
+ if (const llvm::ArrayRef<uint8_t> held = Lookup(curr_addr); !held.empty()) {
+ offset += std::min<uint64_t>(held.size(), len - offset);
+ continue;
+ }
+ // Nothing holds curr_addr, so the gap runs to the next chunk or to the end.
+ auto next = m_chunks.lower_bound(curr_addr);
+ const uint64_t gap_len =
+ next == m_chunks.end()
+ ? len - offset
+ : std::min<uint64_t>(next->first - curr_addr, len - offset);
+ const llvm::ArrayRef<uint8_t> gap_bytes = src.slice(offset, gap_len);
+ m_chunks[curr_addr].assign(gap_bytes.begin(), gap_bytes.end());
+ offset += gap_len;
+ }
+}
+
+void ChunkCache::EraseRange(addr_t addr, addr_t size) {
+ if (size == 0)
+ return;
+ const addr_t end_addr = llvm::SaturatingAdd(addr, size - 1);
+
+ auto pos = m_chunks.lower_bound(addr);
+ // A chunk starting below addr can still reach into the range.
+ if (pos != m_chunks.begin()) {
+ auto prev = std::prev(pos);
+ if (addr - prev->first < prev->second.size())
+ m_chunks.erase(prev);
+ }
+ while (pos != m_chunks.end() && pos->first <= end_addr)
+ pos = m_chunks.erase(pos);
+}
+
// MemoryCache constructor
MemoryCache::MemoryCache(Process &process)
- : m_mutex(), m_L1_cache(), m_L2_cache(), m_invalid_ranges(),
- m_process(process),
- m_L2_cache_line_byte_size(process.GetMemoryCacheLineSize()) {}
+ : m_mutex(), m_L1_cache(), m_L2_cache(process.GetMemoryCacheLineSize()),
+ m_invalid_ranges(), m_process(process) {}
// Destructor
MemoryCache::~MemoryCache() = default;
void MemoryCache::Clear(bool clear_invalid_ranges) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
- m_L1_cache.clear();
- m_L2_cache.clear();
+ m_L1_cache.Clear();
+ m_L2_cache.Clear(m_process.GetMemoryCacheLineSize());
if (clear_invalid_ranges)
m_invalid_ranges.Clear();
- m_L2_cache_line_byte_size = m_process.GetMemoryCacheLineSize();
}
-void MemoryCache::AddL1CacheData(lldb::addr_t addr, const void *src,
- size_t src_len) {
- AddL1CacheData(addr, std::make_shared<DataBufferHeap>(src, src_len));
+void MemoryCache::AddCacheData(lldb::addr_t addr, const void *src,
+ size_t src_len) {
+ InsertData(addr, {static_cast<const uint8_t *>(src), src_len});
+}
+
+void MemoryCache::InsertWholeLine(addr_t line_base_addr,
+ llvm::ArrayRef<uint8_t> src) {
+ m_L2_cache.Insert(line_base_addr, src);
+ // The new line holds every byte the L1 entries inside it held.
+ m_L1_cache.EraseRange(line_base_addr, src.size());
+}
+
+void MemoryCache::InsertPartialLine(addr_t addr, llvm::ArrayRef<uint8_t> src) {
+ const uint32_t line_size = m_L2_cache.GetLineByteSize();
+ assert(src.size() <= line_size &&
+ addr / line_size == (addr + src.size() - 1) / line_size &&
+ "a partial-line insert must not cross a cache line boundary");
+ // L2 holds only whole lines, so a range inside a resident line is held
+ // already.
+ if (m_L2_cache.Holds(addr))
+ return;
+ m_L1_cache.InsertMissing(addr, src);
}
-void MemoryCache::AddL1CacheData(lldb::addr_t addr,
- const DataBufferSP &data_buffer_sp) {
+void MemoryCache::InsertData(addr_t addr, llvm::ArrayRef<uint8_t> src) {
+ if (src.empty())
+ return;
+
std::lock_guard<std::recursive_mutex> guard(m_mutex);
- m_L1_cache[addr] = data_buffer_sp;
+ const addr_t last_addr = llvm::SaturatingAdd<addr_t>(addr, src.size() - 1);
+ src = src.take_front(last_addr - addr + 1);
+ const uint32_t line_size = m_L2_cache.GetLineByteSize();
+
+ // A leading piece, up to the first line boundary.
+ if (const uint64_t line_offset = addr % line_size) {
+ const uint64_t head_len =
+ std::min<uint64_t>(line_size - line_offset, src.size());
+ InsertPartialLine(addr, src.take_front(head_len));
+ addr += head_len;
+ src = src.drop_front(head_len);
+ }
+
+ // Whole, aligned lines.
+ while (src.size() >= line_size) {
+ InsertWholeLine(addr, src.take_front(line_size));
+ addr += line_size;
+ src = src.drop_front(line_size);
+ }
+
+ // A trailing piece, shorter than a line.
+ if (!src.empty())
+ InsertPartialLine(addr, src);
+}
+
+void MemoryCache::AddCacheData(lldb::addr_t addr,
+ const DataBufferSP &data_buffer_sp) {
+ InsertData(addr, {data_buffer_sp->GetBytes(), data_buffer_sp->GetByteSize()});
}
void MemoryCache::Flush(addr_t addr, size_t size) {
@@ -57,46 +193,8 @@ void MemoryCache::Flush(addr_t addr, size_t size) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
- // L1 chunks can overlap, and a chunk starting below addr can still reach
- // into the flushed range, so scan the whole L1 cache and erase every chunk
- // that intersects it.
- if (!m_L1_cache.empty()) {
- AddrRange flush_range(addr, size);
- BlockMap::iterator pos = m_L1_cache.begin();
- while (pos != m_L1_cache.end()) {
- AddrRange chunk_range(pos->first, pos->second->GetByteSize());
- if (chunk_range.DoesIntersect(flush_range))
- pos = m_L1_cache.erase(pos);
- else
- ++pos;
- }
- }
-
- if (!m_L2_cache.empty()) {
- const uint32_t cache_line_byte_size = m_L2_cache_line_byte_size;
- const addr_t end_addr = (addr + size - 1);
- const addr_t first_cache_line_addr = addr - (addr % cache_line_byte_size);
- const addr_t last_cache_line_addr =
- end_addr - (end_addr % cache_line_byte_size);
- // Watch for overflow where size will cause us to go off the end of the
- // 64 bit address space
- uint32_t num_cache_lines;
- if (last_cache_line_addr >= first_cache_line_addr)
- num_cache_lines = ((last_cache_line_addr - first_cache_line_addr) /
- cache_line_byte_size) +
- 1;
- else
- num_cache_lines =
- (UINT64_MAX - first_cache_line_addr + 1) / cache_line_byte_size;
-
- uint32_t cache_idx = 0;
- for (addr_t curr_addr = first_cache_line_addr; cache_idx < num_cache_lines;
- curr_addr += cache_line_byte_size, ++cache_idx) {
- BlockMap::iterator pos = m_L2_cache.find(curr_addr);
- if (pos != m_L2_cache.end())
- m_L2_cache.erase(pos);
- }
- }
+ m_L1_cache.EraseRange(addr, size);
+ m_L2_cache.EraseRange(addr, size);
}
void MemoryCache::AddInvalidRange(lldb::addr_t base_addr,
@@ -124,67 +222,69 @@ bool MemoryCache::RemoveInvalidRange(lldb::addr_t base_addr,
return false;
}
-const uint8_t *MemoryCache::FindL1CacheEntry(lldb::addr_t addr,
- size_t len) const {
- if (m_L1_cache.empty())
- return nullptr;
- AddrRange read_range(addr, len);
- BlockMap::const_iterator pos = m_L1_cache.upper_bound(addr);
- if (pos != m_L1_cache.begin())
- --pos;
- AddrRange chunk_range(pos->first, pos->second->GetByteSize());
- if (!chunk_range.Contains(read_range))
- return nullptr;
- return pos->second->GetBytes() + (addr - chunk_range.GetRangeBase());
-}
+size_t MemoryCache::ReadFromCaches(lldb::addr_t addr, void *dst,
+ size_t len) const {
+ size_t bytes_filled = 0;
+ // Bytes from addr to the last addressable byte. The walk must not pass
+ // it, or curr_addr wraps to 0.
+ const uint64_t space_to_top = UINT64_MAX - addr;
+ while (bytes_filled < len) {
+ if (bytes_filled > space_to_top)
+ break;
+ const addr_t curr_addr = addr + bytes_filled;
-const uint8_t *MemoryCache::FindL2CacheEntry(lldb::addr_t addr,
- size_t len) const {
- if (m_L2_cache.empty())
- return nullptr;
- const lldb::addr_t line_offset = addr % m_L2_cache_line_byte_size;
- BlockMap::const_iterator pos = m_L2_cache.find(addr - line_offset);
- if (pos == m_L2_cache.end())
- return nullptr;
- if (line_offset + len > pos->second->GetByteSize())
- return nullptr;
- return pos->second->GetBytes() + line_offset;
-}
+ // At most one of the caches can hold curr_addr.
+ llvm::ArrayRef<uint8_t> cached = m_L2_cache.Lookup(curr_addr);
+ if (cached.empty())
+ cached = m_L1_cache.Lookup(curr_addr);
+ if (cached.empty())
+ break;
-const uint8_t *MemoryCache::FindCacheEntry(lldb::addr_t addr,
- size_t len) const {
- const uint8_t *cached = FindL1CacheEntry(addr, len);
- if (!cached)
- cached = FindL2CacheEntry(addr, len);
- return cached;
+ const size_t to_copy = std::min(cached.size(), len - bytes_filled);
+ memcpy(static_cast<uint8_t *>(dst) + bytes_filled, cached.data(), to_copy);
+ bytes_filled += to_copy;
+ }
+ return bytes_filled;
}
-lldb::DataBufferSP MemoryCache::GetL2CacheLine(lldb::addr_t line_base_addr,
- Status &error) {
- // This function assumes that the address given is aligned correctly.
- assert((line_base_addr % m_L2_cache_line_byte_size) == 0);
-
- std::lock_guard<std::recursive_mutex> guard(m_mutex);
- auto pos = m_L2_cache.find(line_base_addr);
- if (pos != m_L2_cache.end())
- return pos->second;
-
- auto data_buffer_heap_sp =
- std::make_shared<DataBufferHeap>(m_L2_cache_line_byte_size, 0);
- size_t process_bytes_read = m_process.ReadMemoryFromInferior(
- line_base_addr, data_buffer_heap_sp->GetBytes(),
- data_buffer_heap_sp->GetByteSize(), error);
-
- // If we failed a read, not much we can do.
- if (process_bytes_read == 0)
- return lldb::DataBufferSP();
-
- // If we didn't get a complete read, we can still cache what we did get.
- if (process_bytes_read < m_L2_cache_line_byte_size)
- data_buffer_heap_sp->SetByteSize(process_bytes_read);
-
- m_L2_cache[line_base_addr] = data_buffer_heap_sp;
- return data_buffer_heap_sp;
+MemoryCache::AddrRange MemoryCache::GrowReadRange(addr_t read_addr,
+ addr_t caller_end,
+ size_t bytes_filled) const {
+ const uint64_t line_size = m_L2_cache.GetLineByteSize();
+ const addr_t line_base_addr = llvm::alignDown(read_addr, line_size);
+ // Caps read-ahead at this many whole cache lines.
+ static constexpr uint32_t kMaxCacheLinesPerRead = 2;
+ const uint64_t grow_span = kMaxCacheLinesPerRead * line_size;
+
+ // A request already past the cap spans a line, and one whose growth would
+ // wrap cannot be grown, so both are asked for as they stand.
+ if (line_base_addr > UINT64_MAX - grow_span ||
+ caller_end > line_base_addr + grow_span)
+ return AddrRange(read_addr, caller_end - read_addr);
+
+ // Grow down to the line base so the fetch lands in L2 as a whole line rather
+ // than an unaligned L1 fragment.
+ if (!m_invalid_ranges.FindEntryThatIntersects(
+ InvalidRanges::Entry(line_base_addr, read_addr - line_base_addr)) &&
+ (caller_end <= line_base_addr + line_size || bytes_filled == 0))
+ read_addr = line_base_addr;
+
+ // Read up to the last line the request touches, skipping that line when L2
+ // holds it.
+ addr_t last_line_addr = llvm::alignDown(caller_end - 1, line_size);
+ if (last_line_addr > line_base_addr && m_L2_cache.Holds(last_line_addr))
+ last_line_addr -= line_size;
+ const addr_t grow_target = last_line_addr + line_size;
+
+ // Growth stops at the first invalid range among the bytes it adds.
+ addr_t read_end = grow_target;
+ if (grow_target > caller_end) {
+ if (const InvalidRanges::Entry *invalid =
+ m_invalid_ranges.FindEntryThatIntersects(
+ InvalidRanges::Entry(caller_end, grow_target - caller_end)))
+ read_end = invalid->GetRangeBase();
+ }
+ return AddrRange(read_addr, read_end - read_addr);
}
size_t MemoryCache::Read(addr_t addr, void *dst, size_t dst_len,
@@ -193,11 +293,11 @@ size_t MemoryCache::Read(addr_t addr, void *dst, size_t dst_len,
return 0;
std::lock_guard<std::recursive_mutex> guard(m_mutex);
-
+ addr_t invalid_addr = LLDB_INVALID_ADDRESS;
if (const InvalidRanges::Entry *invalid =
m_invalid_ranges.FindEntryThatIntersects(
InvalidRanges::Entry(addr, dst_len))) {
- const addr_t invalid_addr = invalid->GetRangeBase();
+ invalid_addr = invalid->GetRangeBase();
error = Status::FromErrorStringWithFormat(
"memory read failed for 0x%" PRIx64, invalid_addr);
if (invalid_addr <= addr)
@@ -205,91 +305,33 @@ size_t MemoryCache::Read(addr_t addr, void *dst, size_t dst_len,
dst_len = invalid_addr - addr;
}
- // Check the L1 cache for a range that contains the entire memory read.
- // L1 cache contains chunks of memory that are not required to be the size of
- // an L2 cache line. We avoid trying to do partial reads from the L1 cache to
- // simplify the implementation.
- if (const uint8_t *l1_data = FindL1CacheEntry(addr, dst_len)) {
- memcpy(dst, l1_data, dst_len);
+ size_t bytes_from_cache = ReadFromCaches(addr, dst, dst_len);
+ if (bytes_from_cache == dst_len)
return dst_len;
- }
- // If the size of the read is greater than the size of an L2 cache line, we'll
- // just read from the inferior. If that read is successful, we'll cache what
- // we read in the L1 cache for future use.
- if (dst_len > m_L2_cache_line_byte_size) {
- size_t bytes_read =
- m_process.ReadMemoryFromInferior(addr, dst, dst_len, error);
- if (bytes_read > 0)
- AddL1CacheData(addr, dst, bytes_read);
- return bytes_read;
+ addr_t read_addr = addr + bytes_from_cache;
+ addr_t read_end = addr + dst_len;
+ // A request hits the invalid range above, don't grow.
+ if (invalid_addr == LLDB_INVALID_ADDRESS) {
+ const AddrRange grown =
+ GrowReadRange(read_addr, read_end, bytes_from_cache);
+ read_addr = grown.GetRangeBase();
+ read_end = grown.GetRangeEnd();
}
- // If the size of the read fits inside one L2 cache line, we'll try reading
- // from the L2 cache. Note that if the range of memory we're reading sits
- // between two contiguous cache lines, we'll touch two cache lines instead of
- // just one.
-
- // We're going to have all of our loads and reads be cache line aligned.
- addr_t cache_line_offset = addr % m_L2_cache_line_byte_size;
- addr_t cache_line_base_addr = addr - cache_line_offset;
- DataBufferSP first_cache_line = GetL2CacheLine(cache_line_base_addr, error);
- // If we get nothing, then the read to the inferior likely failed. Nothing to
- // do here.
- if (!first_cache_line)
- return 0;
+ std::vector<uint8_t> read_buf(read_end - read_addr);
+ const size_t bytes_from_inferior = m_process.ReadMemoryFromInferior(
+ read_addr, read_buf.data(), read_buf.size(), error);
+ if (bytes_from_inferior == 0)
+ return bytes_from_cache;
- // If the cache line was not filled out completely and the offset is greater
- // than what we have available, we can't do anything further here.
- if (cache_line_offset >= first_cache_line->GetByteSize())
- return 0;
-
- uint8_t *dst_buf = (uint8_t *)dst;
- size_t bytes_left = dst_len;
- size_t read_size = first_cache_line->GetByteSize() - cache_line_offset;
- if (read_size > bytes_left)
- read_size = bytes_left;
-
- memcpy(dst_buf + dst_len - bytes_left,
- first_cache_line->GetBytes() + cache_line_offset, read_size);
- bytes_left -= read_size;
-
- // If the cache line was not filled out completely and we still have data to
- // read, we can't do anything further.
- if (first_cache_line->GetByteSize() < m_L2_cache_line_byte_size &&
- bytes_left > 0)
- return dst_len - bytes_left;
-
- // We'll hit this scenario if our read straddles two cache lines.
- if (bytes_left > 0) {
- cache_line_base_addr += m_L2_cache_line_byte_size;
-
- // FIXME: Until we are able to more thoroughly check for invalid ranges, we
- // will have to check the second line to see if it is in an invalid range as
- // well. See the check near the beginning of the function for more details.
- if (m_invalid_ranges.FindEntryThatContains(cache_line_base_addr)) {
- error = Status::FromErrorStringWithFormat(
- "memory read failed for 0x%" PRIx64, cache_line_base_addr);
- return dst_len - bytes_left;
- }
-
- DataBufferSP second_cache_line =
- GetL2CacheLine(cache_line_base_addr, error);
- if (!second_cache_line)
- return dst_len - bytes_left;
-
- read_size = bytes_left;
- if (read_size > second_cache_line->GetByteSize())
- read_size = second_cache_line->GetByteSize();
-
- memcpy(dst_buf + dst_len - bytes_left, second_cache_line->GetBytes(),
- read_size);
- bytes_left -= read_size;
-
- return dst_len - bytes_left;
- }
+ AddCacheData(read_addr, read_buf.data(), bytes_from_inferior);
- return dst_len;
+ // The grown or clipped fetch may not align with what the caller asked for,
+ // so pull back only the portion contiguous with what dst already holds.
+ uint8_t *dst_tail = static_cast<uint8_t *>(dst) + bytes_from_cache;
+ return bytes_from_cache + ReadFromCaches(addr + bytes_from_cache, dst_tail,
+ dst_len - bytes_from_cache);
}
llvm::SmallVector<llvm::MutableArrayRef<uint8_t>>
@@ -322,11 +364,9 @@ MemoryCache::ReadRanges(llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges,
continue;
}
- const uint8_t *cached = FindCacheEntry(addr, len);
- if (cached) {
+ if (ReadFromCaches(addr, buffer.data(), len) == len) {
results.push_back(buffer.take_front(len));
buffer = buffer.drop_front(len);
- memcpy(results.back().data(), cached, len);
continue;
}
@@ -343,7 +383,7 @@ MemoryCache::ReadRanges(llvm::ArrayRef<Range<lldb::addr_t, size_t>> ranges,
auto fetched_buffers = llvm::ArrayRef(fetched_buffers_vec);
for (auto [missed_range, fetched] : llvm::zip(missed_ranges, fetched_buffers))
- AddL1CacheData(missed_range.GetRangeBase(), fetched);
+ AddCacheData(missed_range.GetRangeBase(), fetched);
// Use the just-fetched memory to fill in the gaps left by the cache.
for (auto &result : results)
diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp
index 7891be4698c6c..bf73d0724d3cf 100644
--- a/lldb/unittests/Target/MemoryTest.cpp
+++ b/lldb/unittests/Target/MemoryTest.cpp
@@ -131,15 +131,37 @@ class DummyProcess : public Process {
void SetFiller(int filler) { m_filler = filler; }
};
-// A MemoryCache subclass that exposes the otherwise-protected L1 cache so a
-// test can assert on the exact set of chunks it holds.
+// A MemoryCache subclass that exposes the otherwise-protected caches so a
+// test can assert on the exact set of entries they hold.
class TestMemoryCache : public MemoryCache {
public:
using MemoryCache::MemoryCache;
- const BlockMap &GetL1Cache() const { return m_L1_cache; }
- const BlockMap &GetL2Cache() const { return m_L2_cache; }
+ const ChunkCache &GetL1Cache() const { return m_L1_cache; }
+ const LineCache &GetL2Cache() const { return m_L2_cache; }
};
+
+using CacheEntries =
+ std::vector<std::pair<lldb::addr_t, llvm::ArrayRef<uint8_t>>>;
+
+// The chunks of \a cache, which iterates in address order already.
+CacheEntries Snapshot(const ChunkCache &cache) {
+ CacheEntries entries;
+ for (const auto &[addr, chunk] : cache)
+ entries.emplace_back(addr, llvm::ArrayRef(chunk));
+ return entries;
+}
+
+// The lines of \a cache in address order, which its iteration does not give.
+CacheEntries Snapshot(const LineCache &cache) {
+ const uint32_t line_size = cache.GetLineByteSize();
+ CacheEntries entries;
+ for (const auto &[line_idx, line] : cache)
+ entries.emplace_back(line_idx * line_size,
+ llvm::ArrayRef(line.get(), line_size));
+ llvm::sort(entries, llvm::less_first());
+ return entries;
+}
} // namespace
TargetSP CreateTarget(DebuggerSP &debugger_sp, ArchSpec &arch) {
@@ -194,7 +216,7 @@ class CacheTestProcess {
void AddCacheChunk(TestMemoryCache &cache, lldb::addr_t addr, size_t size,
uint8_t fill) {
- cache.AddL1CacheData(addr, std::make_shared<DataBufferHeap>(size, fill));
+ cache.AddCacheData(addr, std::make_shared<DataBufferHeap>(size, fill));
}
bool AllBytesAre(llvm::ArrayRef<uint8_t> bytes, uint8_t fill) {
@@ -296,12 +318,10 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) {
bytes_read = mem_cache.Read(0x4001, data_sp->GetBytes(),
data_sp->GetByteSize(), error);
ASSERT_TRUE(bytes_read == l2_cache_size);
- // One aligned line fetch per line touched.
- ASSERT_EQ(process->m_reads.size(), 2u);
+ // One request, both lines whole.
+ ASSERT_EQ(process->m_reads.size(), 1u);
EXPECT_EQ(process->m_reads[0].first, 0x4000u);
- EXPECT_EQ(process->m_reads[0].second, l2_cache_size);
- EXPECT_EQ(process->m_reads[1].first, 0x4000u + l2_cache_size);
- EXPECT_EQ(process->m_reads[1].second, l2_cache_size);
+ EXPECT_EQ(process->m_reads[0].second, 2 * l2_cache_size);
// What happens when we try to straddle 2 cache lines where the first one is
// only partially filled?
@@ -312,11 +332,14 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) {
data_sp->GetByteSize(), error);
ASSERT_TRUE(bytes_read == l2_cache_size - 6); // Ignoring the first 5 bytes,
// missing the last byte
+ ASSERT_TRUE(error.Success());
+ // The request is grown to both lines it touches and starts at the line base,
+ // so it spends part of the mock's byte budget below the read.
ASSERT_EQ(process->m_reads.size(), 2u);
EXPECT_EQ(process->m_reads[0].first, 0x5000u);
- EXPECT_EQ(process->m_reads[0].second, l2_cache_size);
+ EXPECT_EQ(process->m_reads[0].second, 2 * l2_cache_size);
EXPECT_EQ(process->m_reads[1].first, 0x5000u + l2_cache_size - 1);
- EXPECT_EQ(process->m_reads[1].second, 1u);
+ EXPECT_EQ(process->m_reads[1].second, l2_cache_size + 1);
// What happens if we add an invalid range and try to do a read larger than
// a cache line?
@@ -363,69 +386,75 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) {
EXPECT_EQ(process->m_reads[0].second, l2_cache_size);
}
-TEST_F(MemoryTest, TestL1Cache) {
+TEST_F(MemoryTest, TestCachePartition) {
CacheTestProcess proc;
ASSERT_TRUE(proc.GetProcess());
DummyProcess *process = proc.GetProcess();
TestMemoryCache mem_cache(*process);
+ const lldb::addr_t line = process->GetMemoryCacheLineSize();
+ ASSERT_EQ(line, 512u);
auto add = [&](lldb::addr_t addr, size_t size, uint8_t fill) {
AddCacheChunk(mem_cache, addr, size, fill);
};
- // Asserts the L1 cache holds exactly `expected` chunks, matched by start
- // address, byte size, and a single repeated fill byte, in address order.
+ // Asserts a snapshot holds exactly `expected` entries, in address order.
struct Chunk {
lldb::addr_t addr;
size_t size;
uint8_t fill;
};
- auto expect_l1 = [&](std::vector<Chunk> expected) {
- const auto &l1 = mem_cache.GetL1Cache();
- ASSERT_EQ(l1.size(), expected.size());
+ auto expect = [](const CacheEntries &entries, std::vector<Chunk> expected) {
+ ASSERT_EQ(entries.size(), expected.size());
size_t i = 0;
- for (const auto &[addr, data_sp] : l1) {
+ for (const auto &[addr, bytes] : entries) {
const Chunk &c = expected[i++];
EXPECT_EQ(addr, c.addr);
- ASSERT_EQ(data_sp->GetByteSize(), c.size);
- const uint8_t *bytes = data_sp->GetBytes();
+ ASSERT_EQ(bytes.size(), c.size);
for (size_t j = 0; j < c.size; ++j)
EXPECT_EQ(bytes[j], c.fill)
<< "chunk 0x" << std::hex << addr << " byte " << std::dec << j;
}
};
+ auto expect_l1 = [&](std::vector<Chunk> expected) {
+ expect(Snapshot(mem_cache.GetL1Cache()), expected);
+ };
+ auto expect_l2 = [&](std::vector<Chunk> expected) {
+ expect(Snapshot(mem_cache.GetL2Cache()), expected);
+ };
- // Partial overlap: the new chunk overhangs the existing one on the right.
+ // Partial overlap: only the part not already held is added on the right.
mem_cache.Clear();
add(0x1000, 0x100, 0xAA);
add(0x1080, 0x100, 0xBB);
- expect_l1({{0x1000, 0x100, 0xAA}, {0x1080, 0x100, 0xBB}});
+ expect_l1({{0x1000, 0x100, 0xAA}, {0x1100, 0x80, 0xBB}});
+ expect_l2({});
- // Partial overlap: the new chunk overhangs the existing one on the left.
+ // Partial overlap: the new chunk is added on the left.
mem_cache.Clear();
add(0x2080, 0x100, 0xAA);
add(0x2000, 0x100, 0xBB);
- expect_l1({{0x2000, 0x100, 0xBB}, {0x2080, 0x100, 0xAA}});
+ expect_l1({{0x2000, 0x80, 0xBB}, {0x2080, 0x100, 0xAA}});
- // New chunk fully contains an existing one: both are kept.
+ // New chunk fully contains an existing one: added around it.
mem_cache.Clear();
add(0x3040, 0x40, 0xAA);
add(0x3000, 0x100, 0xBB);
- expect_l1({{0x3000, 0x100, 0xBB}, {0x3040, 0x40, 0xAA}});
+ expect_l1({{0x3000, 0x40, 0xBB}, {0x3040, 0x40, 0xAA}, {0x3080, 0x80, 0xBB}});
- // New chunk is fully contained by an existing one: both are kept.
+ // New chunk is fully contained by an existing one: adds nothing.
mem_cache.Clear();
- add(0x4000, 0x200, 0xAA);
+ add(0x4000, 0x100, 0xAA);
add(0x4080, 0x80, 0xBB);
- expect_l1({{0x4000, 0x200, 0xAA}, {0x4080, 0x80, 0xBB}});
+ expect_l1({{0x4000, 0x100, 0xAA}});
- // New chunk partially overlaps two existing chunks; all three are kept.
+ // New chunk partially overlaps two existing chunks; fills only the whole
+ // between them.
mem_cache.Clear();
add(0x5000, 0x80, 0xAA);
add(0x5100, 0x80, 0xCC);
add(0x5040, 0x100, 0xBB);
- expect_l1(
- {{0x5000, 0x80, 0xAA}, {0x5040, 0x100, 0xBB}, {0x5100, 0x80, 0xCC}});
+ expect_l1({{0x5000, 0x80, 0xAA}, {0x5080, 0x80, 0xBB}, {0x5100, 0x80, 0xCC}});
// Disjoint chunks stay separate.
mem_cache.Clear();
@@ -439,24 +468,79 @@ TEST_F(MemoryTest, TestL1Cache) {
add(0x7080, 0x80, 0xBB);
expect_l1({{0x7000, 0x80, 0xAA}, {0x7080, 0x80, 0xBB}});
- // Flush must erase every chunk intersecting the flush range, including a
- // chunk that starts below the flushed address. Here 0x8140 lies only in the
- // lower-starting, longer chunk; it must be dropped while the chunk that does
- // not intersect survives untouched.
+ // Flush must erase an entry starting below the flushed address, and keep one
+ // in the same line that it does not intersect.
mem_cache.Clear();
- add(0x8000, 0x180, 0xAA);
- add(0x8080, 0x40, 0xBB);
+ add(0x8100, 0x80, 0xAA);
+ add(0x8000, 0x40, 0xBB);
mem_cache.Flush(0x8140, 0x4);
- expect_l1({{0x8080, 0x40, 0xBB}});
+ expect_l1({{0x8000, 0x40, 0xBB}});
// A flush intersecting several partially overlapping chunks drops all of
// them, while a chunk it does not intersect is left in place.
mem_cache.Clear();
add(0x9000, 0x80, 0xAA);
- add(0x9040, 0x100, 0xBB);
+ add(0x9080, 0x40, 0xBB);
add(0x9100, 0x80, 0xCC);
- mem_cache.Flush(0x9060, 0x1);
+ mem_cache.Flush(0x9020, 0x80);
expect_l1({{0x9100, 0x80, 0xCC}});
+ expect_l2({});
+
+ // Flush reaches a line and leaves the remainders on either side of it alone.
+ mem_cache.Clear();
+ add(0xB000 + line - 10, 10 + line + 10, 0xAA);
+ expect_l2({{0xB000 + line, line, 0xAA}});
+ mem_cache.Flush(0xB000 + line + 4, 0x4);
+ expect_l1({{0xB000 + line - 10, 10, 0xAA}, {0xB000 + 2 * line, 10, 0xAA}});
+ expect_l2({});
+
+ // A whole line at an aligned address belongs to L2, not L1.
+ mem_cache.Clear();
+ add(0x9000, line, 0xAA);
+ expect_l1({});
+ expect_l2({{0x9000, line, 0xAA}});
+
+ // A partial range at an aligned address stays in L1.
+ mem_cache.Clear();
+ add(0xA000, 0x40, 0xAA);
+ expect_l1({{0xA000, 0x40, 0xAA}});
+ expect_l2({});
+
+ // An unaligned range longer than a line splits into one whole line plus a
+ // remainder on each side.
+ mem_cache.Clear();
+ add(0xB000 + line - 10, 10 + line + 10, 0xAA);
+ expect_l1({{0xB000 + line - 10, 10, 0xAA}, {0xB000 + 2 * line, 10, 0xAA}});
+ expect_l2({{0xB000 + line, line, 0xAA}});
+
+ // A range crossing a line boundary but covering no whole line splits in two.
+ mem_cache.Clear();
+ add(0xC000 + line - 12, 20, 0xAA);
+ expect_l1({{0xC000 + line - 12, 12, 0xAA}, {0xC000 + line, 8, 0xAA}});
+ expect_l2({});
+
+ // A range already held by a line in L2 is dropped.
+ mem_cache.Clear();
+ add(0xD000, line, 0xAA);
+ add(0xD000 + 8, 16, 0xBB);
+ expect_l1({});
+ expect_l2({{0xD000, line, 0xAA}});
+
+ // A flush whose first line is absent from L2 still erases the later lines it
+ // covers, because the range start is a lower bound and not a lookup.
+ mem_cache.Clear();
+ add(0xF000 + line, line, 0xAA);
+ add(0xF000 + 2 * line, line, 0xBB);
+ expect_l2({{0xF000 + line, line, 0xAA}, {0xF000 + 2 * line, line, 0xBB}});
+ mem_cache.Flush(0xF000, 2 * line);
+ expect_l2({{0xF000 + 2 * line, line, 0xBB}});
+
+ // A whole line evicts an entry it only partly covers.
+ mem_cache.Clear();
+ add(0xE000 + line - 8, 16, 0xAA);
+ add(0xE000 + line, line, 0xBB);
+ expect_l1({{0xE000 + line - 8, 8, 0xAA}});
+ expect_l2({{0xE000 + line, line, 0xBB}});
}
TEST_F(MemoryTest, TestReadStopsAtAnInvalidRange) {
@@ -464,7 +548,6 @@ TEST_F(MemoryTest, TestReadStopsAtAnInvalidRange) {
ASSERT_TRUE(proc.GetProcess());
DummyProcess *process = proc.GetProcess();
MemoryCache &cache = process->GetMemoryCache();
- const lldb::addr_t line = process->GetMemoryCacheLineSize();
const lldb::addr_t base = 0xE000;
cache.AddInvalidRange(base + 16, 16);
@@ -480,11 +563,11 @@ TEST_F(MemoryTest, TestReadStopsAtAnInvalidRange) {
EXPECT_TRUE(error.Fail());
EXPECT_TRUE(AllBytesAre(llvm::ArrayRef(buf).take_front(16), 0xBB));
- // The whole aligned line is still fetched, crossing the invalid range, even
- // though only the 16 bytes below it may be served.
+ // The request stops where the invalid range starts, so the unreadable bytes
+ // are never asked for.
ASSERT_EQ(process->m_reads.size(), 1u);
EXPECT_EQ(process->m_reads[0].first, base);
- EXPECT_EQ(process->m_reads[0].second, line);
+ EXPECT_EQ(process->m_reads[0].second, 16u);
// A read starting inside the range has nothing to serve.
Status inside_error;
@@ -502,8 +585,22 @@ TEST_F(MemoryTest, TestReadRangesFromCaches) {
DummyProcess *process = proc.GetProcess();
const uint64_t line_size = proc.GetLineSize();
+ { // ReadRanges serves a range one entry covers.
+ const lldb::addr_t base = 0x6000 + line_size - 10;
+ process->GetMemoryCache().AddCacheData(
+ base, std::make_shared<DataBufferHeap>(10 + line_size, 0xAA));
+ process->SetMaxReadSize(0);
+ llvm::SmallVector<uint8_t, 0> buffer(20, 0);
+ llvm::SmallVector<Range<addr_t, size_t>> ranges = {{base, 20}};
+ llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> results =
+ process->ReadMemoryRanges(ranges, buffer);
+ ASSERT_EQ(results.size(), 1u);
+ ASSERT_EQ(results[0].size(), 20u);
+ EXPECT_TRUE(AllBytesAre(results[0], 0xAA));
+ }
+
{ // An entry serves a range only if it covers it all. A short fetch is all
- // the caller sees and all L1 keeps of the range it was for.
+ // the caller sees, and adds to L1 only the bytes no entry holds.
TestMemoryCache cache(*process);
AddCacheChunk(cache, 0xB000, 40, 0xAA);
AddCacheChunk(cache, 0xC000, 8, 0xCC);
@@ -527,17 +624,17 @@ TEST_F(MemoryTest, TestReadRangesFromCaches) {
EXPECT_EQ(process->m_reads[1].first, 0xB000u + 20u);
EXPECT_EQ(process->m_reads[1].second, 64u - 20u);
- ASSERT_EQ(cache.GetL1Cache().size(), 2u);
- EXPECT_EQ(cache.GetL1Cache().count(0xB000), 1u);
- EXPECT_EQ(cache.GetL1Cache().count(0xC000), 1u);
-
- auto l1cache_line = cache.GetL1Cache().at(0xB000);
- // was 40 bytes of 0xAA
- EXPECT_EQ(l1cache_line->GetByteSize(), 20u);
- EXPECT_TRUE(AllBytesAre(l1cache_line->GetData(), 0xBB));
- l1cache_line = cache.GetL1Cache().at(0xC000);
- EXPECT_EQ(l1cache_line->GetByteSize(), 8u);
- EXPECT_TRUE(AllBytesAre(l1cache_line->GetData(), 0xCC));
+ ASSERT_EQ(cache.GetL1Cache().GetSize(), 2u);
+ const auto l1_chunks = Snapshot(cache.GetL1Cache());
+ ASSERT_EQ(l1_chunks.size(), 2u);
+ EXPECT_EQ(l1_chunks[0].first, 0xB000u);
+ EXPECT_EQ(l1_chunks[1].first, 0xC000u);
+
+ // The fetch's 20 bytes are all held already, so the chunk is unchanged.
+ EXPECT_EQ(l1_chunks[0].second.size(), 40u);
+ EXPECT_TRUE(AllBytesAre(l1_chunks[0].second, 0xAA));
+ EXPECT_EQ(l1_chunks[1].second.size(), 8u);
+ EXPECT_TRUE(AllBytesAre(l1_chunks[1].second, 0xCC));
}
{ // A range ReadRanges fetched is cached, so asking for it again serves it
@@ -598,12 +695,36 @@ TEST_F(MemoryTest, TestReadRequestShape) {
DummyProcess *process = proc.GetProcess();
const uint64_t line_size = proc.GetLineSize();
- { // A read longer than a line that L1 cannot serve whole goes to the inferior
- // as one request for the whole range.
+ { // The request starts at the cache entry, but longer than the cache entry.
+ TestMemoryCache cache(*process);
+ Status error;
+ const lldb::addr_t base = 0x14000;
+
+ // v base v base + line
+ // cache:|AAAAAAAAAAAAAAAA|
+ // process:|BBBBBBBBBBBBBBBB|BBBBBBBBBBBBBBB|
+ // buf :|AAAAAAAAAAAAAAAA|BBB|
+ // ^ base + line + 88
+ AddCacheChunk(cache, base, 256, 0xAA);
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xBB);
+ process->m_reads.clear();
+ std::vector<uint8_t> buf(line_size + 88, 0);
+ EXPECT_EQ(cache.Read(base, buf.data(), buf.size(), error), buf.size());
+ EXPECT_TRUE(AllBytesAre(llvm::ArrayRef(buf).take_front(256), 0xAA));
+ EXPECT_TRUE(AllBytesAre(llvm::ArrayRef(buf).drop_front(256), 0xBB));
+ // One request, from the first missing byte to the second line's end.
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].first, base + 256);
+ EXPECT_EQ(process->m_reads[0].second, 2 * line_size - 256);
+ }
+
+ { // A read longer than a line that cache cannot serve, read the rest from the
+ // inferior.
// v base v base + line_size
// cache: |AAAAAAAAAAAAAA|AA|
// process:|BBBBBBBBBBBBBB|BBBBBBBBBBBB|BBBBBBBBBBBB|
- // buf: |BBBBBBBBBBBBBB|BBBBBBBBBBBB|BBBBBBBBBBBB|
+ // buf: |AAAAAAAAAAAAAA|AABBBBBBBBBB|BBBBBBBBBBBB|
TestMemoryCache cache(*process);
Status error;
const lldb::addr_t base = 0x15000;
@@ -614,11 +735,14 @@ TEST_F(MemoryTest, TestReadRequestShape) {
process->m_reads.clear();
std::vector<uint8_t> buf(3 * line_size, 0);
ASSERT_EQ(cache.Read(base, buf.data(), buf.size(), error), buf.size());
- EXPECT_TRUE(AllBytesAre(buf, 0xBB));
- // One request, for exactly what the caller asked.
+ EXPECT_TRUE(
+ AllBytesAre(llvm::ArrayRef(buf).take_front(line_size + 8), 0xAA));
+ EXPECT_TRUE(
+ AllBytesAre(llvm::ArrayRef(buf).drop_front(line_size + 8), 0xBB));
+ // One request, starting past the cached prefix and covering only the rest.
ASSERT_EQ(process->m_reads.size(), 1u);
- EXPECT_EQ(process->m_reads[0].first, base);
- EXPECT_EQ(process->m_reads[0].second, buf.size());
+ EXPECT_EQ(process->m_reads[0].first, base + line_size + 8);
+ EXPECT_EQ(process->m_reads[0].second, 2 * line_size - 8);
// Cached where it was read from, so the same read now sends nothing and
// returns the same bytes.
@@ -630,32 +754,313 @@ TEST_F(MemoryTest, TestReadRequestShape) {
EXPECT_EQ(again, buf);
EXPECT_TRUE(process->m_reads.empty());
}
+
+ { // Data split across L1 and L2 stitches back together. The counting
+ // pattern catches an offset error a uniform fill would hide.
+ TestMemoryCache cache(*process);
+ Status error;
+ const lldb::addr_t base = 0x11000 + line_size - 7;
+ const size_t size = 7 + 2 * line_size + 5;
+ auto byte_at = [](size_t i) { return static_cast<uint8_t>(i * 7 + 1); };
+ auto pattern = std::make_shared<DataBufferHeap>(size, 0);
+ for (size_t i = 0; i < size; ++i)
+ pattern->GetBytes()[i] = byte_at(i);
+ cache.AddCacheData(base, pattern);
+
+ // One remainder on each side and two whole lines between them.
+ ASSERT_EQ(cache.GetL1Cache().GetSize(), 2u);
+ ASSERT_EQ(cache.GetL2Cache().GetSize(), 2u);
+
+ process->SetMaxReadSize(0);
+ std::vector<uint8_t> buf(size, 0);
+ ASSERT_EQ(cache.Read(base, buf.data(), buf.size(), error), size);
+ for (size_t i = 0; i < size; ++i)
+ ASSERT_EQ(buf[i], byte_at(i)) << "byte " << i;
+
+ // A read starting inside an entry, which a read at its base cannot check.
+ auto expect_at = [&](size_t offset, size_t len, const char *what) {
+ SCOPED_TRACE(what);
+ std::vector<uint8_t> got(len, 0);
+ ASSERT_EQ(cache.Read(base + offset, got.data(), got.size(), error), len);
+ for (size_t i = 0; i < len; ++i)
+ ASSERT_EQ(got[i], byte_at(offset + i)) << "byte " << i;
+ };
+
+ expect_at(3, 4, "inside the leading L1 remainder");
+ expect_at(7 + 9, 8, "inside the first whole line");
+ expect_at(7 - 2, 8, "across the remainder into the line");
+ }
+
+ { // A short read must not hide cached bytes that start where it stopped. The
+ // count is taken from the caches, not from what the inferior returned.
+ // v base v base+300
+ // cache: |CC|
+ // buf:|AAAAAAAAAAACC|
+ // ^ base+310
+ TestMemoryCache cache(*process);
+ Status error;
+ const lldb::addr_t base = 0x18000;
+
+ AddCacheChunk(cache, base + 300, 10, 0xCC);
+ process->SetMaxReadSize(300);
+ process->SetFiller(0xAA);
+ std::vector<uint8_t> buf(600, 0);
+ EXPECT_EQ(cache.Read(base, buf.data(), buf.size(), error), 310u);
+ EXPECT_TRUE(AllBytesAre(llvm::ArrayRef(buf).take_front(300), 0xAA));
+ EXPECT_TRUE(AllBytesAre(llvm::ArrayRef(buf).slice(300, 10), 0xCC));
+ }
+
+ { // A read at an expedited chunk must not reach the inferior once a larger
+ // read already covers it.
+ // v fp-line v fp v fp+line
+ // cache: |AAAA|
+ // buf: |BBBBBBBBB|
+ TestMemoryCache cache(*process);
+ Status error;
+ const lldb::addr_t fp = 0xF000 + line_size;
+ cache.AddCacheData(fp, std::make_shared<DataBufferHeap>(16, 0xAA));
+
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xBB);
+ std::vector<uint8_t> big(2 * line_size, 0);
+ ASSERT_EQ(cache.Read(fp - line_size, big.data(), big.size(), error),
+ big.size());
+
+ process->SetMaxReadSize(0);
+ process->m_reads.clear();
+ std::vector<uint8_t> out(32, 0);
+ EXPECT_EQ(cache.Read(fp, out.data(), out.size(), error), out.size());
+ EXPECT_TRUE(AllBytesAre(out, 0xBB));
+ EXPECT_TRUE(process->m_reads.empty());
+ }
+}
+
+// A read straddling two lines fetches whole lines, so both land in L2 and
+// a later read of either hits.
+TEST_F(MemoryTest, TestReadStraddlingTwoLines) {
+ CacheTestProcess proc;
+ ASSERT_TRUE(proc.GetProcess());
+ DummyProcess *process = proc.GetProcess();
+ const uint64_t line_size = proc.GetLineSize();
+ const lldb::addr_t first = 0x20000;
+ const lldb::addr_t second = first + line_size;
+
+ { // Neither line cached: both are fetched whole, in one request.
+ TestMemoryCache cache(*process);
+ Status error;
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xAA);
+ process->m_reads.clear();
+
+ std::vector<uint8_t> buf(16, 0);
+ EXPECT_EQ(cache.Read(second - 8, buf.data(), buf.size(), error),
+ buf.size());
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].first, first);
+ EXPECT_EQ(process->m_reads[0].second, 2 * line_size);
+ EXPECT_TRUE(cache.GetL2Cache().Holds(first));
+ EXPECT_TRUE(cache.GetL2Cache().Holds(second));
+
+ process->m_reads.clear();
+ std::vector<uint8_t> again(8, 0);
+ EXPECT_EQ(cache.Read(first, again.data(), again.size(), error),
+ again.size());
+ EXPECT_TRUE(process->m_reads.empty());
+ }
+
+ { // The second line is already in L2, only the first is missing, so only
+ // that one is fetched and the second is not sent again.
+ TestMemoryCache cache(*process);
+ Status error;
+ std::vector<uint8_t> whole(line_size, 0xBB);
+ cache.AddCacheData(second, whole.data(), whole.size());
+ ASSERT_TRUE(cache.GetL2Cache().Holds(second));
+
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xAA);
+ process->m_reads.clear();
+ std::vector<uint8_t> buf(16, 0);
+ EXPECT_EQ(cache.Read(second - 8, buf.data(), buf.size(), error),
+ buf.size());
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].first, first);
+ EXPECT_EQ(process->m_reads[0].second, line_size);
+ // The tail of the request came out of the line that was already there.
+ EXPECT_TRUE(AllBytesAre(llvm::ArrayRef(buf).take_front(8), 0xAA));
+ EXPECT_TRUE(AllBytesAre(llvm::ArrayRef(buf).drop_front(8), 0xBB));
+ }
+
+ { // A short second line does not count as present: skipping it would leave a
+ // hole, so both lines are fetched.
+ TestMemoryCache cache(*process);
+ Status error;
+ std::vector<uint8_t> partial(line_size / 2, 0xBB);
+ cache.AddCacheData(second, partial.data(), partial.size());
+
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xAA);
+ process->m_reads.clear();
+ std::vector<uint8_t> buf(16, 0);
+ EXPECT_EQ(cache.Read(second - 8, buf.data(), buf.size(), error),
+ buf.size());
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].second, 2 * line_size);
+ EXPECT_EQ(cache.GetL2Cache().GetSize(), 2u);
+ EXPECT_EQ(cache.GetL1Cache().GetSize(), 0u);
+ }
+
+ { // A request longer than a line still grows when it fits in the two lines it
+ // touches.
+ TestMemoryCache cache(*process);
+ Status error;
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xAA);
+ process->m_reads.clear();
+
+ std::vector<uint8_t> buf(line_size + 4, 0);
+ EXPECT_EQ(cache.Read(first, buf.data(), buf.size(), error), buf.size());
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].first, first);
+ EXPECT_EQ(process->m_reads[0].second, 2 * line_size);
+ EXPECT_TRUE(cache.GetL2Cache().Holds(first));
+ EXPECT_TRUE(cache.GetL2Cache().Holds(second));
+
+ process->m_reads.clear();
+ std::vector<uint8_t> again(8, 0);
+ EXPECT_EQ(
+ cache.Read(second + line_size - 8, again.data(), again.size(), error),
+ again.size());
+ EXPECT_TRUE(process->m_reads.empty());
+ }
+
+ { // A request reaching a third line is read as asked.
+ // v first v first+line v fist+2*line
+ // cache:| | |
+ // buf: | | |
+ // ^ first+8 ^first+8+2*line
+ TestMemoryCache cache(*process);
+ Status error;
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xAA);
+ process->m_reads.clear();
+
+ std::vector<uint8_t> buf(2 * line_size, 0);
+ EXPECT_EQ(cache.Read(first + 8, buf.data(), buf.size(), error), buf.size());
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].first, first + 8);
+ EXPECT_EQ(process->m_reads[0].second, 2 * line_size);
+ }
+
+ {
+ TestMemoryCache cache(*process);
+ Status error;
+ std::vector<uint8_t> held(8, 0xCC);
+ cache.AddCacheData(first, held.data(), held.size());
+
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xAA);
+ process->m_reads.clear();
+ std::vector<uint8_t> buf(line_size + 4, 0);
+ EXPECT_EQ(cache.Read(first, buf.data(), buf.size(), error), buf.size());
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ // A prefix served from the caches stops the straddle arm growing down.
+ EXPECT_EQ(process->m_reads[0].first, first + 8);
+ EXPECT_TRUE(AllBytesAre(llvm::ArrayRef(buf).take_front(8), 0xCC));
+ }
+}
+
+TEST_F(MemoryTest, TestReadGrowthAgainstInvalidRanges) {
+ CacheTestProcess proc;
+ ASSERT_TRUE(proc.GetProcess());
+ DummyProcess *process = proc.GetProcess();
+ const uint64_t line_size = proc.GetLineSize();
+ const lldb::addr_t first = 0x30000;
+ const lldb::addr_t second = first + line_size;
+
+ { // An invalid range below the read, ending inside the line.
+ TestMemoryCache cache(*process);
+ Status error;
+ cache.AddInvalidRange(first, 0x100);
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xAA);
+ process->m_reads.clear();
+
+ std::vector<uint8_t> buf(16, 0);
+ EXPECT_EQ(cache.Read(first + 0x180, buf.data(), buf.size(), error),
+ buf.size());
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].first, first + 0x180);
+ EXPECT_EQ(process->m_reads[0].second, line_size - 0x180);
+ }
+
+ { // An invalid range in the bytes growth adds above, inside the same line.
+ TestMemoryCache cache(*process);
+ Status error;
+ cache.AddInvalidRange(first + 0x180, 0x80);
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xAA);
+ process->m_reads.clear();
+
+ std::vector<uint8_t> buf(16, 0);
+ EXPECT_EQ(cache.Read(first + 0x100, buf.data(), buf.size(), error),
+ buf.size());
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].first, first);
+ EXPECT_EQ(process->m_reads[0].second, 0x180u);
+ }
+
+ { // An invalid range in the second line, which growth would add: the request
+ // stops below it.
+ TestMemoryCache cache(*process);
+ Status error;
+ cache.AddInvalidRange(second + 0x100, 0x80);
+ process->SetMaxReadSize(4 * line_size);
+ process->SetFiller(0xAA);
+ process->m_reads.clear();
+
+ std::vector<uint8_t> buf(16, 0);
+ EXPECT_EQ(cache.Read(second - 8, buf.data(), buf.size(), error),
+ buf.size());
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].first, first);
+ EXPECT_EQ(process->m_reads[0].second, line_size + 0x100);
+ }
}
// A flushed range whose end wraps past UINT64_MAX must stop at the top line.
-// FIXME: a range whose end wraps past UINT64_MAX leaves the wrapped lines
-// cached.
TEST_F(MemoryTest, TestFlushAtTheTopOfTheAddressSpace) {
CacheTestProcess proc;
ASSERT_TRUE(proc.GetProcess());
DummyProcess *process = proc.GetProcess();
const uint64_t line_size = proc.GetLineSize();
const lldb::addr_t top_line = UINT64_MAX - line_size + 1;
+ const lldb::addr_t line_below_top = top_line - line_size;
- // Only L2 is walked line by line, so seed it by reading. The line at 0 is
- // the one a wrap would reach first.
TestMemoryCache cache(*process);
Status error;
process->SetMaxReadSize(4 * line_size);
- std::vector<uint8_t> buf(8, 0);
+ std::vector<uint8_t> buf(line_size, 0);
cache.Read(top_line, buf.data(), buf.size(), error);
cache.Read(0, buf.data(), buf.size(), error);
- ASSERT_EQ(cache.GetL2Cache().size(), 2u);
+ ASSERT_EQ(cache.GetL2Cache().GetSize(), 2u);
+ ASSERT_TRUE(cache.GetL2Cache().Holds(top_line));
// This range ends past UINT64_MAX.
cache.Flush(UINT64_MAX - 8, 100);
- EXPECT_EQ(cache.GetL2Cache().count(top_line), 0u);
- EXPECT_EQ(cache.GetL2Cache().count(0), 1u);
+ EXPECT_FALSE(cache.GetL2Cache().Holds(top_line));
+ EXPECT_TRUE(cache.GetL2Cache().Holds(0));
+
+ { // A one-byte flush of the last byte covers only the topmost line, so it
+ // must not reach the line below it.
+ TestMemoryCache cache(*process);
+ cache.AddCacheData(line_below_top, buf.data(), buf.size());
+ cache.AddCacheData(top_line, buf.data(), buf.size());
+ ASSERT_EQ(cache.GetL2Cache().GetSize(), 2u);
+
+ cache.Flush(UINT64_MAX, 1);
+ EXPECT_FALSE(cache.GetL2Cache().Holds(top_line));
+ EXPECT_TRUE(cache.GetL2Cache().Holds(line_below_top));
+ }
}
// The cache copies raw bytes, which have no buffer behind them to retain.
@@ -666,9 +1071,9 @@ TEST_F(MemoryTest, TestCacheCopiesRawBytes) {
Status error;
TestMemoryCache cache(*process);
std::vector<uint8_t> raw(16, 0xAA);
- cache.AddL1CacheData(0x5000, raw.data(), raw.size());
- ASSERT_EQ(cache.GetL1Cache().count(0x5000), 1u);
- EXPECT_NE(cache.GetL1Cache().at(0x5000)->GetBytes(), raw.data());
+ cache.AddCacheData(0x5000, raw.data(), raw.size());
+ ASSERT_TRUE(cache.GetL1Cache().Holds(0x5000));
+ EXPECT_NE(cache.GetL1Cache().Lookup(0x5000).data(), raw.data());
// Editing the caller's bytes must not change what the cache returns, and the
// inferior supplies nothing, so every byte read came from the cache.
@@ -676,6 +1081,7 @@ TEST_F(MemoryTest, TestCacheCopiesRawBytes) {
process->SetMaxReadSize(0);
std::vector<uint8_t> out(16, 0);
EXPECT_EQ(cache.Read(0x5000, out.data(), out.size(), error), out.size());
+ ASSERT_TRUE(process->m_reads.empty());
EXPECT_TRUE(AllBytesAre(out, 0xAA));
}
@@ -1039,8 +1445,8 @@ TEST_F(MemoryDeathTest, TestReadRangesWithShortBufferAndCacheHit) {
DummyProcess *process = static_cast<DummyProcess *>(process_sp.get());
TestMemoryCache cache(*process);
- cache.AddL1CacheData(0x1000, std::make_shared<DataBufferHeap>(16, 0xAA));
- ASSERT_EQ(cache.GetL1Cache().count(0x1000), 1u);
+ cache.AddCacheData(0x1000, std::make_shared<DataBufferHeap>(16, 0xAA));
+ ASSERT_TRUE(cache.GetL1Cache().Holds(0x1000));
llvm::SmallVector<uint8_t, 0> short_buffer(8, 0);
llvm::SmallVector<Range<addr_t, size_t>> ranges = {{0x1000, 16}};
@@ -1183,7 +1589,7 @@ TEST_F(MemoryDeathTest, TestVerifyMemoryReads) {
// DummyReaderProcess returns the low byte of each address, so a run of
// zeroes cannot be what it would read.
process_sp->GetMemoryCache().Clear();
- process_sp->GetMemoryCache().AddL1CacheData(
+ process_sp->GetMemoryCache().AddCacheData(
0x2000, std::make_shared<DataBufferHeap>(16, 0));
std::vector<uint8_t> bad(16, 0);
ASSERT_DEATH(
More information about the lldb-commits
mailing list