[Lldb-commits] [lldb] [lldb] [unittests] Cover what the memory cache reads from the inferior (PR #221015)
Yao Qi via lldb-commits
lldb-commits at lists.llvm.org
Wed Sep 9 01:04:46 PDT 2026
https://github.com/qiyao updated https://github.com/llvm/llvm-project/pull/221015
>From 7e1bf59711cdbc14e41e9dfcaa5e858b8a8c2094 Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Fri, 28 Aug 2026 21:41:43 +0100
Subject: [PATCH 1/6] [lldb] [unittests] Cover what the memory cache reads from
the inferior
`MemoryCache`'s tests checked how many bytes a read returned, never
which requests the cache made to get them, so a change to the request
shape could not fail a test. Add four tests over the request log
`DummyProcess` now keeps.
Two assert what the cache sends: `TestReadRequestShape` checks the
address and size of every request, and `TestReadRangesFromCaches` checks
that a range the cache already holds is served without reaching the
inferior.
`TestFlushAtTheTopOfTheAddressSpace` pins `Flush`'s current behavior at
the wrap, not correct behavior. When a flushed range wraps past
`UINT64_MAX`, `Flush` counts cache lines only from the start of the
range to the top of the address space, never the lines the wrapped
portion covers between address 0 and the range's end. This bug predates
this change; the following cache-partition change fixes it, and this
test's assertion that address 0 survives the flush will need to flip once
that lands.
`TesetMemoryCacheRead` gains one assertion, that a line whose reply
falls short leaves the error alone, next to the two requests it already
checks.
Also add a `CacheTestProcess` helper and use it in the seven cache
tests, so each states its setup in two lines rather than thirteen, and
expose the L2 map next to L1 for assertions.
---
lldb/unittests/Target/MemoryTest.cpp | 254 ++++++++++++++++++++++-----
1 file changed, 212 insertions(+), 42 deletions(-)
diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp
index 67d061e707732..93830c56f053d 100644
--- a/lldb/unittests/Target/MemoryTest.cpp
+++ b/lldb/unittests/Target/MemoryTest.cpp
@@ -138,6 +138,7 @@ class TestMemoryCache : public MemoryCache {
using MemoryCache::MemoryCache;
const BlockMap &GetL1Cache() const { return m_L1_cache; }
+ const BlockMap &GetL2Cache() const { return m_L2_cache; }
};
} // namespace
@@ -161,21 +162,51 @@ static ProcessSP CreateProcess(lldb::TargetSP target_sp) {
return process_sp;
}
-TEST_F(MemoryTest, TesetMemoryCacheRead) {
- ArchSpec arch("x86_64-apple-macosx-");
+// Builds the debugger, target and process a cache test needs and keeps them
+// alive.
+namespace {
+class CacheTestProcess {
+public:
+ explicit CacheTestProcess(const char *triple = "arm64-apple-macosx")
+ : m_arch(triple) {
+ Platform::SetHostPlatform(
+ PlatformRemoteMacOSX::CreateInstance(true, &m_arch));
+ m_debugger_sp = Debugger::CreateInstance();
+ if (!m_debugger_sp)
+ return;
+ m_target_sp = CreateTarget(m_debugger_sp, m_arch);
+ if (!m_target_sp)
+ return;
+ m_process_sp = CreateProcess(m_target_sp);
+ m_process = static_cast<DummyProcess *>(m_process_sp.get());
+ }
- Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch));
+ DummyProcess *process() const { return m_process; }
+ // Widened from uint32_t so tests can do address arithmetic with it.
+ lldb::addr_t line() const { return m_process->GetMemoryCacheLineSize(); }
- DebuggerSP debugger_sp = Debugger::CreateInstance();
- ASSERT_TRUE(debugger_sp);
+private:
+ ArchSpec m_arch;
+ lldb::DebuggerSP m_debugger_sp;
+ lldb::TargetSP m_target_sp;
+ lldb::ProcessSP m_process_sp;
+ DummyProcess *m_process = nullptr;
+};
- TargetSP target_sp = CreateTarget(debugger_sp, arch);
- ASSERT_TRUE(target_sp);
+void AddCacheChunk(TestMemoryCache &cache, lldb::addr_t addr, size_t size,
+ uint8_t fill) {
+ cache.AddL1CacheData(addr, std::make_shared<DataBufferHeap>(size, fill));
+}
- ProcessSP process_sp = CreateProcess(target_sp);
- ASSERT_TRUE(process_sp);
+bool AllBytesAre(llvm::ArrayRef<uint8_t> bytes, uint8_t fill) {
+ return llvm::all_of(bytes, [fill](uint8_t byte) { return byte == fill; });
+}
+} // namespace
- DummyProcess *process = static_cast<DummyProcess *>(process_sp.get());
+TEST_F(MemoryTest, TesetMemoryCacheRead) {
+ CacheTestProcess proc("x86_64-apple-macosx-");
+ ASSERT_TRUE(proc.process());
+ DummyProcess *process = proc.process();
MemoryCache &mem_cache = process->GetMemoryCache();
const uint64_t l2_cache_size = process->GetMemoryCacheLineSize();
Status error;
@@ -243,6 +274,7 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) {
bytes_read = mem_cache.Read(0x3000, data_sp->GetBytes(),
data_sp->GetByteSize(), error);
ASSERT_TRUE(bytes_read == l2_cache_size - 10);
+ EXPECT_TRUE(error.Success());
ASSERT_EQ(process->m_reads.size(), 2u);
EXPECT_EQ(process->m_reads[0].first, 0x3000u);
EXPECT_EQ(process->m_reads[0].second, l2_cache_size);
@@ -333,25 +365,13 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) {
}
TEST_F(MemoryTest, TestL1Cache) {
- ArchSpec arch("arm64-apple-macosx");
-
- Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch));
-
- DebuggerSP debugger_sp = Debugger::CreateInstance();
- ASSERT_TRUE(debugger_sp);
-
- TargetSP target_sp = CreateTarget(debugger_sp, arch);
- ASSERT_TRUE(target_sp);
-
- ProcessSP process_sp = CreateProcess(target_sp);
- ASSERT_TRUE(process_sp);
-
- DummyProcess *process = static_cast<DummyProcess *>(process_sp.get());
+ CacheTestProcess proc;
+ ASSERT_TRUE(proc.process());
+ DummyProcess *process = proc.process();
TestMemoryCache mem_cache(*process);
auto add = [&](lldb::addr_t addr, size_t size, uint8_t fill) {
- mem_cache.AddL1CacheData(addr,
- std::make_shared<DataBufferHeap>(size, fill));
+ AddCacheChunk(mem_cache, addr, size, fill);
};
// Asserts the L1 cache holds exactly `expected` chunks, matched by start
@@ -441,20 +461,9 @@ TEST_F(MemoryTest, TestL1Cache) {
}
TEST_F(MemoryTest, TestReadStopsAtAnInvalidRange) {
- ArchSpec arch("arm64-apple-macosx");
-
- Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch));
-
- DebuggerSP debugger_sp = Debugger::CreateInstance();
- ASSERT_TRUE(debugger_sp);
-
- TargetSP target_sp = CreateTarget(debugger_sp, arch);
- ASSERT_TRUE(target_sp);
-
- ProcessSP process_sp = CreateProcess(target_sp);
- ASSERT_TRUE(process_sp);
-
- DummyProcess *process = static_cast<DummyProcess *>(process_sp.get());
+ CacheTestProcess proc;
+ ASSERT_TRUE(proc.process());
+ DummyProcess *process = proc.process();
MemoryCache &cache = process->GetMemoryCache();
const lldb::addr_t line = process->GetMemoryCacheLineSize();
const lldb::addr_t base = 0xE000;
@@ -470,8 +479,7 @@ TEST_F(MemoryTest, TestReadStopsAtAnInvalidRange) {
std::vector<uint8_t> buf(64, 0);
EXPECT_EQ(cache.Read(base, buf.data(), buf.size(), error), 16u);
EXPECT_TRUE(error.Fail());
- for (size_t i = 0; i < 16; ++i)
- EXPECT_EQ(buf[i], 0xBB) << "byte " << i;
+ 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.
@@ -489,6 +497,168 @@ TEST_F(MemoryTest, TestReadStopsAtAnInvalidRange) {
EXPECT_TRUE(process->m_reads.empty());
}
+TEST_F(MemoryTest, TestReadRangesFromCaches) {
+ CacheTestProcess proc;
+ ASSERT_TRUE(proc.process());
+ DummyProcess *process = proc.process();
+ const lldb::addr_t line = proc.line();
+
+ { // A short fetch must expose only the bytes it read, and must not disturb a
+ // range the caches already served.
+ TestMemoryCache cache(*process);
+ AddCacheChunk(cache, 0xB000, 40, 0xAA);
+ AddCacheChunk(cache, 0xC000, 8, 0xCC);
+ process->SetMaxReadSize(20);
+ process->SetFiller(0xBB);
+ process->m_reads.clear();
+ llvm::SmallVector<uint8_t, 0> buffer(72, 0);
+ llvm::SmallVector<Range<addr_t, size_t>> ranges = {{0xB000, 64},
+ {0xC000, 8}};
+ llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> results =
+ cache.ReadRanges(ranges, buffer);
+ ASSERT_EQ(results.size(), 2u);
+ ASSERT_EQ(results[0].size(), 20u);
+ EXPECT_TRUE(AllBytesAre(results[0], 0xBB));
+ ASSERT_EQ(results[1].size(), 8u);
+ EXPECT_TRUE(AllBytesAre(results[1], 0xCC));
+ }
+
+ { // A range ReadRanges fetched is cached, so asking for it again serves it
+ // without going to the inferior.
+ TestMemoryCache cache(*process);
+ process->SetMaxReadSize(4 * line);
+ process->SetFiller(0xBB);
+ process->m_reads.clear();
+ llvm::SmallVector<uint8_t, 0> buffer(24, 0);
+ llvm::SmallVector<Range<addr_t, size_t>> ranges = {{0x16000, 24}};
+ llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> results =
+ cache.ReadRanges(ranges, buffer);
+ ASSERT_EQ(results.size(), 1u);
+ ASSERT_EQ(results[0].size(), 24u);
+ EXPECT_TRUE(AllBytesAre(results[0], 0xBB));
+ ASSERT_FALSE(process->m_reads.empty());
+
+ process->SetMaxReadSize(0);
+ process->m_reads.clear();
+ llvm::SmallVector<uint8_t, 0> again(24, 0);
+ llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> second =
+ cache.ReadRanges(ranges, again);
+ ASSERT_EQ(second.size(), 1u);
+ ASSERT_EQ(second[0].size(), 24u);
+ EXPECT_TRUE(AllBytesAre(second[0], 0xBB));
+ EXPECT_TRUE(process->m_reads.empty());
+ }
+
+ { // A range inside an invalid range gets an empty result without reaching the
+ // inferior, and leaves the ranges on either side of it alone.
+ TestMemoryCache cache(*process);
+ AddCacheChunk(cache, 0x17000, 8, 0xAA);
+ cache.AddInvalidRange(0x17100, 8);
+ process->SetMaxReadSize(4 * line);
+ process->SetFiller(0xBB);
+ process->m_reads.clear();
+ llvm::SmallVector<uint8_t, 0> buffer(24, 0);
+ llvm::SmallVector<Range<addr_t, size_t>> ranges = {
+ {0x17000, 8}, {0x17100, 8}, {0x17200, 8}};
+ llvm::SmallVector<llvm::MutableArrayRef<uint8_t>> results =
+ cache.ReadRanges(ranges, buffer);
+ ASSERT_EQ(results.size(), 3u);
+ ASSERT_EQ(results[0].size(), 8u); // a cache hit
+ EXPECT_TRUE(AllBytesAre(results[0], 0xAA));
+ EXPECT_TRUE(results[1].empty()); // the invalid range
+ ASSERT_EQ(results[2].size(), 8u); // a miss, fetched
+ EXPECT_TRUE(AllBytesAre(results[2], 0xBB));
+ // Only the miss reached the inferior.
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].first, 0x17200u);
+ EXPECT_EQ(process->m_reads[0].second, 8);
+ }
+}
+
+TEST_F(MemoryTest, TestReadRequestShape) {
+ CacheTestProcess proc;
+ ASSERT_TRUE(proc.process());
+ DummyProcess *process = proc.process();
+ const lldb::addr_t line = proc.line();
+
+ { // A read longer than a line that L1 cannot serve whole goes to the inferior
+ // as one request for the whole range.
+ // v base v base + line
+ // cache: |AAAAAAAAAAAAAA|AA|
+ // process:|BBBBBBBBBBBBBB|BBBBBBBBBBBB|BBBBBBBBBBBB|
+ // buf: |BBBBBBBBBBBBBB|BBBBBBBBBBBB|BBBBBBBBBBBB|
+ TestMemoryCache cache(*process);
+ Status error;
+ const lldb::addr_t base = 0x15000;
+ AddCacheChunk(cache, base, line + 8, 0xAA); // a whole line plus a remainder
+ process->SetMaxReadSize(4 * line);
+ process->SetFiller(0xBB);
+ process->m_reads.clear();
+ std::vector<uint8_t> buf(3 * line, 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.
+ ASSERT_EQ(process->m_reads.size(), 1u);
+ EXPECT_EQ(process->m_reads[0].first, base);
+ EXPECT_EQ(process->m_reads[0].second, buf.size());
+
+ // Cached where it was read from, so the same read now sends nothing and
+ // returns the same bytes.
+ process->SetMaxReadSize(0);
+ process->m_reads.clear();
+ std::vector<uint8_t> again(3 * line, 0);
+ EXPECT_EQ(cache.Read(base, again.data(), again.size(), error),
+ again.size());
+ EXPECT_EQ(again, buf);
+ EXPECT_TRUE(process->m_reads.empty());
+ }
+}
+
+// A flushed range whose end wraps past UINT64_MAX must stop at the top line.
+TEST_F(MemoryTest, TestFlushAtTheTopOfTheAddressSpace) {
+ CacheTestProcess proc;
+ ASSERT_TRUE(proc.process());
+ DummyProcess *process = proc.process();
+ const lldb::addr_t line = proc.line();
+ const lldb::addr_t top_line = UINT64_MAX - line + 1;
+
+ // 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);
+ std::vector<uint8_t> buf(8, 0);
+ cache.Read(top_line, buf.data(), buf.size(), error);
+ cache.Read(0, buf.data(), buf.size(), error);
+ ASSERT_EQ(cache.GetL2Cache().size(), 2u);
+
+ // 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);
+}
+
+// The cache copies raw bytes, which have no buffer behind them to retain.
+TEST_F(MemoryTest, TestCacheCopiesRawBytes) {
+ CacheTestProcess proc;
+ ASSERT_TRUE(proc.process());
+ DummyProcess *process = proc.process();
+ 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());
+
+ // 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.
+ raw.assign(raw.size(), 0xBB);
+ process->SetMaxReadSize(0);
+ std::vector<uint8_t> out(16, 0);
+ EXPECT_EQ(cache.Read(0x5000, out.data(), out.size(), error), out.size());
+ EXPECT_TRUE(AllBytesAre(out, 0xAA));
+}
+
TEST_F(MemoryTest, TestUnusableCacheLineSize) {
ArchSpec arch("arm64-apple-macosx");
>From 503852b286d86574a083e92f3b0585fa238af5df Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Thu, 3 Sep 2026 22:01:54 +0100
Subject: [PATCH 2/6] Fix sign comparison error
---
lldb/unittests/Target/MemoryTest.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp
index 93830c56f053d..00156b39a184b 100644
--- a/lldb/unittests/Target/MemoryTest.cpp
+++ b/lldb/unittests/Target/MemoryTest.cpp
@@ -571,7 +571,7 @@ TEST_F(MemoryTest, TestReadRangesFromCaches) {
// Only the miss reached the inferior.
ASSERT_EQ(process->m_reads.size(), 1u);
EXPECT_EQ(process->m_reads[0].first, 0x17200u);
- EXPECT_EQ(process->m_reads[0].second, 8);
+ EXPECT_EQ(process->m_reads[0].second, 8u);
}
}
>From c064877a1acd4017a9a28dfe154e919061eb4e72 Mon Sep 17 00:00:00 2001
From: Yao Qi <qiyaoltc at gmail.com>
Date: Fri, 4 Sep 2026 09:58:27 +0100
Subject: [PATCH 3/6] Apply batched suggestions from code review
Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
lldb/unittests/Target/MemoryTest.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp
index 00156b39a184b..70ae3c2fe7245 100644
--- a/lldb/unittests/Target/MemoryTest.cpp
+++ b/lldb/unittests/Target/MemoryTest.cpp
@@ -167,7 +167,7 @@ static ProcessSP CreateProcess(lldb::TargetSP target_sp) {
namespace {
class CacheTestProcess {
public:
- explicit CacheTestProcess(const char *triple = "arm64-apple-macosx")
+ explicit CacheTestProcess(llvm::StringRef triple = "arm64-apple-macosx")
: m_arch(triple) {
Platform::SetHostPlatform(
PlatformRemoteMacOSX::CreateInstance(true, &m_arch));
@@ -181,9 +181,9 @@ class CacheTestProcess {
m_process = static_cast<DummyProcess *>(m_process_sp.get());
}
- DummyProcess *process() const { return m_process; }
+ DummyProcess *GetProcess() const { return m_process; }
// Widened from uint32_t so tests can do address arithmetic with it.
- lldb::addr_t line() const { return m_process->GetMemoryCacheLineSize(); }
+ lldb::addr_t GetLine() const { return m_process->GetMemoryCacheLineSize(); }
private:
ArchSpec m_arch;
>From 379d97e1f176bf1c4a1543963af37619d07dc961 Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Fri, 4 Sep 2026 13:16:42 +0100
Subject: [PATCH 4/6] Address review comments
---
lldb/unittests/Target/MemoryTest.cpp | 55 +++++++++++++++++++---------
1 file changed, 37 insertions(+), 18 deletions(-)
diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp
index 70ae3c2fe7245..568de7cd8f59b 100644
--- a/lldb/unittests/Target/MemoryTest.cpp
+++ b/lldb/unittests/Target/MemoryTest.cpp
@@ -182,7 +182,6 @@ class CacheTestProcess {
}
DummyProcess *GetProcess() const { return m_process; }
- // Widened from uint32_t so tests can do address arithmetic with it.
lldb::addr_t GetLine() const { return m_process->GetMemoryCacheLineSize(); }
private:
@@ -205,8 +204,8 @@ bool AllBytesAre(llvm::ArrayRef<uint8_t> bytes, uint8_t fill) {
TEST_F(MemoryTest, TesetMemoryCacheRead) {
CacheTestProcess proc("x86_64-apple-macosx-");
- ASSERT_TRUE(proc.process());
- DummyProcess *process = proc.process();
+ ASSERT_TRUE(proc.GetProcess());
+ DummyProcess *process = proc.GetProcess();
MemoryCache &mem_cache = process->GetMemoryCache();
const uint64_t l2_cache_size = process->GetMemoryCacheLineSize();
Status error;
@@ -366,8 +365,8 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) {
TEST_F(MemoryTest, TestL1Cache) {
CacheTestProcess proc;
- ASSERT_TRUE(proc.process());
- DummyProcess *process = proc.process();
+ ASSERT_TRUE(proc.GetProcess());
+ DummyProcess *process = proc.GetProcess();
TestMemoryCache mem_cache(*process);
auto add = [&](lldb::addr_t addr, size_t size, uint8_t fill) {
@@ -462,8 +461,8 @@ TEST_F(MemoryTest, TestL1Cache) {
TEST_F(MemoryTest, TestReadStopsAtAnInvalidRange) {
CacheTestProcess proc;
- ASSERT_TRUE(proc.process());
- DummyProcess *process = proc.process();
+ 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;
@@ -499,12 +498,12 @@ TEST_F(MemoryTest, TestReadStopsAtAnInvalidRange) {
TEST_F(MemoryTest, TestReadRangesFromCaches) {
CacheTestProcess proc;
- ASSERT_TRUE(proc.process());
- DummyProcess *process = proc.process();
- const lldb::addr_t line = proc.line();
+ ASSERT_TRUE(proc.GetProcess());
+ DummyProcess *process = proc.GetProcess();
+ const lldb::addr_t line = proc.GetLine();
- { // A short fetch must expose only the bytes it read, and must not disturb a
- // range the caches already served.
+ { // 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.
TestMemoryCache cache(*process);
AddCacheChunk(cache, 0xB000, 40, 0xAA);
AddCacheChunk(cache, 0xC000, 8, 0xCC);
@@ -521,6 +520,24 @@ TEST_F(MemoryTest, TestReadRangesFromCaches) {
EXPECT_TRUE(AllBytesAre(results[0], 0xBB));
ASSERT_EQ(results[1].size(), 8u);
EXPECT_TRUE(AllBytesAre(results[1], 0xCC));
+ // A short reply is retried for the remainder, here with no bytes left.
+ ASSERT_EQ(process->m_reads.size(), 2u);
+ EXPECT_EQ(process->m_reads[0].first, 0xB000u);
+ EXPECT_EQ(process->m_reads[0].second, 64u);
+ 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));
}
{ // A range ReadRanges fetched is cached, so asking for it again serves it
@@ -577,9 +594,9 @@ TEST_F(MemoryTest, TestReadRangesFromCaches) {
TEST_F(MemoryTest, TestReadRequestShape) {
CacheTestProcess proc;
- ASSERT_TRUE(proc.process());
- DummyProcess *process = proc.process();
- const lldb::addr_t line = proc.line();
+ ASSERT_TRUE(proc.GetProcess());
+ DummyProcess *process = proc.GetProcess();
+ const lldb::addr_t line = proc.GetLine();
{ // A read longer than a line that L1 cannot serve whole goes to the inferior
// as one request for the whole range.
@@ -615,11 +632,13 @@ TEST_F(MemoryTest, TestReadRequestShape) {
}
// 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.process());
- DummyProcess *process = proc.process();
- const lldb::addr_t line = proc.line();
+ ASSERT_TRUE(proc.GetProcess());
+ DummyProcess *process = proc.GetProcess();
+ const lldb::addr_t line = proc.GetLine();
const lldb::addr_t top_line = UINT64_MAX - line + 1;
// Only L2 is walked line by line, so seed it by reading. The line at 0 is
>From 4ca4eb6158a4581e9d316d93c52417b37c36e82d Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Fri, 4 Sep 2026 14:49:04 +0100
Subject: [PATCH 5/6] Fix compile error in TestCacheCopiesRawBytes
---
lldb/unittests/Target/MemoryTest.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp
index 568de7cd8f59b..5c5297284368a 100644
--- a/lldb/unittests/Target/MemoryTest.cpp
+++ b/lldb/unittests/Target/MemoryTest.cpp
@@ -660,8 +660,8 @@ TEST_F(MemoryTest, TestFlushAtTheTopOfTheAddressSpace) {
// The cache copies raw bytes, which have no buffer behind them to retain.
TEST_F(MemoryTest, TestCacheCopiesRawBytes) {
CacheTestProcess proc;
- ASSERT_TRUE(proc.process());
- DummyProcess *process = proc.process();
+ ASSERT_TRUE(proc.GetProcess());
+ DummyProcess *process = proc.GetProcess();
Status error;
TestMemoryCache cache(*process);
std::vector<uint8_t> raw(16, 0xAA);
>From 95f1ad8999f2960587d1d214163fd0f8078c1d86 Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Wed, 9 Sep 2026 08:42:03 +0100
Subject: [PATCH 6/6] Rename CacheTestProcess::GetLine to GetLineSize
`addr_t` suits a virtual address, and this is a byte count, so return
`uint64_t` and name the locals `line_size`.
---
lldb/unittests/Target/MemoryTest.cpp | 27 ++++++++++++++-------------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp
index 5c5297284368a..7891be4698c6c 100644
--- a/lldb/unittests/Target/MemoryTest.cpp
+++ b/lldb/unittests/Target/MemoryTest.cpp
@@ -182,7 +182,7 @@ class CacheTestProcess {
}
DummyProcess *GetProcess() const { return m_process; }
- lldb::addr_t GetLine() const { return m_process->GetMemoryCacheLineSize(); }
+ uint64_t GetLineSize() const { return m_process->GetMemoryCacheLineSize(); }
private:
ArchSpec m_arch;
@@ -500,7 +500,7 @@ TEST_F(MemoryTest, TestReadRangesFromCaches) {
CacheTestProcess proc;
ASSERT_TRUE(proc.GetProcess());
DummyProcess *process = proc.GetProcess();
- const lldb::addr_t line = proc.GetLine();
+ const uint64_t line_size = proc.GetLineSize();
{ // 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.
@@ -543,7 +543,7 @@ TEST_F(MemoryTest, TestReadRangesFromCaches) {
{ // A range ReadRanges fetched is cached, so asking for it again serves it
// without going to the inferior.
TestMemoryCache cache(*process);
- process->SetMaxReadSize(4 * line);
+ process->SetMaxReadSize(4 * line_size);
process->SetFiller(0xBB);
process->m_reads.clear();
llvm::SmallVector<uint8_t, 0> buffer(24, 0);
@@ -571,7 +571,7 @@ TEST_F(MemoryTest, TestReadRangesFromCaches) {
TestMemoryCache cache(*process);
AddCacheChunk(cache, 0x17000, 8, 0xAA);
cache.AddInvalidRange(0x17100, 8);
- process->SetMaxReadSize(4 * line);
+ process->SetMaxReadSize(4 * line_size);
process->SetFiller(0xBB);
process->m_reads.clear();
llvm::SmallVector<uint8_t, 0> buffer(24, 0);
@@ -596,22 +596,23 @@ TEST_F(MemoryTest, TestReadRequestShape) {
CacheTestProcess proc;
ASSERT_TRUE(proc.GetProcess());
DummyProcess *process = proc.GetProcess();
- const lldb::addr_t line = proc.GetLine();
+ 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.
- // v base v base + line
+ // v base v base + line_size
// cache: |AAAAAAAAAAAAAA|AA|
// process:|BBBBBBBBBBBBBB|BBBBBBBBBBBB|BBBBBBBBBBBB|
// buf: |BBBBBBBBBBBBBB|BBBBBBBBBBBB|BBBBBBBBBBBB|
TestMemoryCache cache(*process);
Status error;
const lldb::addr_t base = 0x15000;
- AddCacheChunk(cache, base, line + 8, 0xAA); // a whole line plus a remainder
- process->SetMaxReadSize(4 * line);
+ // A whole line plus a remainder.
+ AddCacheChunk(cache, base, line_size + 8, 0xAA);
+ process->SetMaxReadSize(4 * line_size);
process->SetFiller(0xBB);
process->m_reads.clear();
- std::vector<uint8_t> buf(3 * line, 0);
+ 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.
@@ -623,7 +624,7 @@ TEST_F(MemoryTest, TestReadRequestShape) {
// returns the same bytes.
process->SetMaxReadSize(0);
process->m_reads.clear();
- std::vector<uint8_t> again(3 * line, 0);
+ std::vector<uint8_t> again(3 * line_size, 0);
EXPECT_EQ(cache.Read(base, again.data(), again.size(), error),
again.size());
EXPECT_EQ(again, buf);
@@ -638,14 +639,14 @@ TEST_F(MemoryTest, TestFlushAtTheTopOfTheAddressSpace) {
CacheTestProcess proc;
ASSERT_TRUE(proc.GetProcess());
DummyProcess *process = proc.GetProcess();
- const lldb::addr_t line = proc.GetLine();
- const lldb::addr_t top_line = UINT64_MAX - line + 1;
+ const uint64_t line_size = proc.GetLineSize();
+ const lldb::addr_t top_line = UINT64_MAX - line_size + 1;
// 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);
+ process->SetMaxReadSize(4 * line_size);
std::vector<uint8_t> buf(8, 0);
cache.Read(top_line, buf.data(), buf.size(), error);
cache.Read(0, buf.data(), buf.size(), error);
More information about the lldb-commits
mailing list