Files
llvm-tms34010/lldb/include/lldb/Target/Memory.h
T
Yao Qi 80e68c79c6 [lldb] Serve MemoryCache::ReadRanges from the L2 cache as well as L1 (#216318)
`MemoryCache::Read` fetches a whole L2 cache line for any read that fits
in one,
so reading a few bytes caches the line around them. `ReadRanges` probed
only L1,
and re-fetched ranges that line already held. Callers hit this whenever
they
read an array's header and then batch the elements that follow it in the
same
line, as `AppleObjCRuntimeV2::SharedCacheImageHeaders` and
`ClassDescriptorV2::method_list_t` both do.  #201166 uses MemoryCache in
`Process::ReadRangesFromMemory`, but I didn't see why is L1 used only.

Add `FindL2CacheEntry`, a lookup that never reads from the inferior, and
consult
it after L1. When it serves every range in a batch, `ReadRanges` returns
without
calling `Process::DoReadMemoryRanges`, so no packet is sent. As in the
L1 lookup
a range spanning two lines is a miss, and a partially read line is used
only up
to what it holds.

Over the region
`TestObjCMethodsNSError.test_runtime_types_efficient_memreads`
brackets, `MultiMemRead` drops from 190 packets to 107 and the ranges
they carry
from 7004 to 6411, with the `m`/`x` count unchanged at 856. That test
now also
requires no read range to be contained in one an earlier packet already
read,
which counted 593 ranges before this change and none after.

`TestReadMemoryRangesUsesL2Cache` covers the lookup directly.

(cherry picked from commit 797a057bccd4f0e96f2775a55d2e6401e46e0275)
2026-08-20 20:57:18 +00:00

180 lines
6.2 KiB
C++

//===-- Memory.h ------------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_TARGET_MEMORY_H
#define LLDB_TARGET_MEMORY_H
#include "lldb/Utility/RangeMap.h"
#include "lldb/lldb-private.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include <map>
#include <mutex>
#include <vector>
namespace lldb_private {
// A class to track memory that was read from a live process between
// runs.
class MemoryCache {
public:
// Constructors and Destructors
MemoryCache(Process &process);
~MemoryCache();
void Clear(bool clear_invalid_ranges = false);
void Flush(lldb::addr_t addr, size_t size);
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.
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; }
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);
void AddL1CacheData(lldb::addr_t addr, llvm::ArrayRef<uint8_t> src) {
if (!src.empty())
AddL1CacheData(addr, src.data(), src.size());
}
void AddL1CacheData(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)
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.
// Caller must hold m_mutex.
const uint8_t *FindCacheEntry(lldb::addr_t addr, size_t len) const;
};
class AllocatedBlock {
public:
AllocatedBlock(lldb::addr_t addr, uint32_t byte_size, uint32_t permissions,
uint32_t chunk_size);
~AllocatedBlock();
lldb::addr_t ReserveBlock(uint32_t size);
bool FreeBlock(lldb::addr_t addr);
lldb::addr_t GetBaseAddress() const { return m_range.GetRangeBase(); }
uint32_t GetByteSize() const { return m_range.GetByteSize(); }
uint32_t GetPermissions() const { return m_permissions; }
uint32_t GetChunkSize() const { return m_chunk_size; }
bool Contains(lldb::addr_t addr) const {
return m_range.Contains(addr);
}
protected:
uint32_t TotalChunks() const { return GetByteSize() / GetChunkSize(); }
uint32_t CalculateChunksNeededForSize(uint32_t size) const {
return (size + m_chunk_size - 1) / m_chunk_size;
}
// Base address of this block of memory 4GB of chunk should be enough.
Range<lldb::addr_t, uint32_t> m_range;
// Permissions for this memory (logical OR of lldb::Permissions bits)
const uint32_t m_permissions;
// The size of chunks that the memory at m_addr is divied up into.
const uint32_t m_chunk_size;
// A sorted list of free address ranges.
RangeVector<lldb::addr_t, uint32_t> m_free_blocks;
// A sorted list of reserved address.
RangeVector<lldb::addr_t, uint32_t> m_reserved_blocks;
};
// A class that can track allocated memory and give out allocated memory
// without us having to make an allocate/deallocate call every time we need
// some memory in a process that is being debugged.
class AllocatedMemoryCache {
public:
// Constructors and Destructors
AllocatedMemoryCache(Process &process);
~AllocatedMemoryCache();
void Clear(bool deallocate_memory);
lldb::addr_t AllocateMemory(size_t byte_size, uint32_t permissions,
Status &error);
bool DeallocateMemory(lldb::addr_t ptr);
bool IsInCache(lldb::addr_t addr) const;
protected:
typedef std::shared_ptr<AllocatedBlock> AllocatedBlockSP;
AllocatedBlockSP AllocatePage(uint32_t byte_size, uint32_t permissions,
uint32_t chunk_size, Status &error);
// Classes that inherit from MemoryCache can see and modify these
Process &m_process;
mutable std::recursive_mutex m_mutex;
typedef std::multimap<uint32_t, AllocatedBlockSP> PermissionsToBlockMap;
PermissionsToBlockMap m_memory_map;
private:
AllocatedMemoryCache(const AllocatedMemoryCache &) = delete;
const AllocatedMemoryCache &operator=(const AllocatedMemoryCache &) = delete;
};
} // namespace lldb_private
#endif // LLDB_TARGET_MEMORY_H