blob: ac8ed474d51aa1601f7f07d9e23e80ec47f45d10 [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cheriot/memory_use_profiler.h"
#include <cstdint>
#include <cstring>
#include <ostream>
#include <utility>
#include "absl/container/btree_map.h"
#include "absl/numeric/bits.h"
#include "absl/strings/str_format.h"
#include "mpact/sim/util/memory/memory_interface.h"
#include "mpact/sim/util/memory/memory_watcher.h"
#include "mpact/sim/util/memory/tagged_memory_interface.h"
namespace mpact::sim::cheriot {
namespace internal {
MemoryUseTracker::~MemoryUseTracker() {
for (auto &[unused, bits] : memory_use_map_) {
delete[] bits;
}
memory_use_map_.clear();
}
// Static helper function.
static inline void MarkUsedBits(uint64_t byte_offset, int mask, uint8_t *bits) {
// Shift right by two, so that every byte offset becomes a word offset.
uint64_t word_offset = byte_offset >> 2;
// Byte offs
uint64_t byte_index = word_offset >> 3;
int bit_index = word_offset & 0b111;
bits[byte_index] |= (mask << bit_index);
}
void MemoryUseTracker::MarkUsed(uint64_t address, int size) {
// The profiling is done on a word boundary, so word or smaller is marked by
// a single bit, double words by 2 bits.
uint8_t mask = size == 8 ? 0b11 : 0b1;
if ((address >= last_start_) && (address + size - 1 <= last_end_)) {
return MarkUsedBits(address - last_start_, mask, last_used_);
}
auto it = memory_use_map_.find(AddressRange{address, address + size - 1});
if (it == memory_use_map_.end()) {
// Compute new base and top addresses.
uint64_t start = address & ~kBaseMask;
uint64_t end = start + kSegmentSize - 1;
auto *bits = new uint8_t[kBitsSize];
std::memset(bits, 0, kBitsSize);
it = memory_use_map_.insert(std::make_pair(AddressRange{start, end}, bits))
.first;
}
last_start_ = it->first.start;
last_end_ = it->first.end;
last_used_ = it->second;
MarkUsedBits(address - last_start_, mask, it->second);
}
// Write out ranges of words that have been used.
void MemoryUseTracker::WriteUseProfile(std::ostream &os) const {
// Current range info.
uint64_t range_start = 0;
uint64_t range_end = 0;
bool range_started = false;
for (auto const &[range, bits] : memory_use_map_) {
auto base = range.start;
int byte_index = 0;
uint8_t byte = bits[byte_index];
while (byte_index < kBitsSize) {
if (range_started) {
// If we have a range started and the accesses are contiguous, increment
// the byte index and continue.
if (byte == 0xff) {
byte_index++;
if (byte_index < kBitsSize) byte = bits[byte_index];
continue;
}
// Compute the end of the current range by counting the number of
// consecutive ones starting from the lsb.
int bit_indx = absl::countr_one(byte) - 1;
range_end = base + (byte_index * 8 + bit_indx) * kGranularity;
// Output range.
os << absl::StrFormat("0x%llx,0x%llx,%llu\n", range_start, range_end,
range_end - range_start + 4);
// Clear those bits from the current byte, then we start looking for
// a new range.
byte &= ~((1 << (bit_indx + 1)) - 1);
range_started = false;
}
// If we are here, range_started is false.
if (byte == 0) {
byte_index++;
if (byte_index < kBitsSize) byte = bits[byte_index];
continue;
}
// At this point byte != 0, and we need to start a new range.
int bit_indx = absl::countr_zero(byte);
range_start = base + (byte_index * 8 + bit_indx) * kGranularity;
// Set the low bits in the current byte so that we can determine the end
// of this range, in case it is contained in this byte.
byte |= ((1 << bit_indx) - 1);
range_started = true;
}
}
}
} // namespace internal
MemoryUseProfiler::MemoryUseProfiler() : MemoryUseProfiler(nullptr) {}
MemoryUseProfiler::MemoryUseProfiler(MemoryInterface *memory)
: memory_(memory) {}
void MemoryUseProfiler::Load(uint64_t address, DataBuffer *db,
Instruction *inst, ReferenceCount *context) {
if (is_enabled_) tracker_.MarkUsed(address, db->size<uint8_t>());
if (memory_) memory_->Load(address, db, inst, context);
}
void MemoryUseProfiler::Load(DataBuffer *address_db, DataBuffer *mask_db,
int el_size, DataBuffer *db, Instruction *inst,
ReferenceCount *context) {
if (is_enabled_) {
for (int i = 0; i < address_db->size<uint64_t>(); ++i) {
if (mask_db->Get<uint8_t>(i)) {
tracker_.MarkUsed(address_db->Get<uint64_t>(i), el_size);
}
}
}
if (memory_) memory_->Load(address_db, mask_db, el_size, db, inst, context);
}
void MemoryUseProfiler::Store(uint64_t address, DataBuffer *db) {
if (is_enabled_) tracker_.MarkUsed(address, db->size<uint8_t>());
if (memory_) memory_->Store(address, db);
}
void MemoryUseProfiler::Store(DataBuffer *address_db, DataBuffer *mask_db,
int el_size, DataBuffer *db) {
if (is_enabled_) {
for (int i = 0; i < address_db->size<uint64_t>(); ++i) {
if (mask_db->Get<uint8_t>(i)) {
tracker_.MarkUsed(address_db->Get<uint64_t>(i), el_size);
}
}
}
if (memory_) memory_->Store(address_db, mask_db, el_size, db);
}
TaggedMemoryUseProfiler::TaggedMemoryUseProfiler()
: TaggedMemoryUseProfiler(nullptr) {}
TaggedMemoryUseProfiler::TaggedMemoryUseProfiler(
TaggedMemoryInterface *tagged_memory)
: tagged_memory_(tagged_memory) {}
void TaggedMemoryUseProfiler::Load(uint64_t address, DataBuffer *db,
Instruction *inst, ReferenceCount *context) {
if (is_enabled_) tracker_.MarkUsed(address, db->size<uint8_t>());
if (tagged_memory_) tagged_memory_->Load(address, db, inst, context);
}
void TaggedMemoryUseProfiler::Load(DataBuffer *address_db, DataBuffer *mask_db,
int el_size, DataBuffer *db,
Instruction *inst, ReferenceCount *context) {
if (is_enabled_) {
for (int i = 0; i < address_db->size<uint64_t>(); ++i) {
if (mask_db->Get<uint8_t>(i)) {
tracker_.MarkUsed(address_db->Get<uint64_t>(i), el_size);
}
}
}
if (tagged_memory_)
tagged_memory_->Load(address_db, mask_db, el_size, db, inst, context);
}
void TaggedMemoryUseProfiler::Load(uint64_t address, DataBuffer *db,
DataBuffer *tags, Instruction *inst,
ReferenceCount *context) {
if ((db != nullptr) && is_enabled_)
tracker_.MarkUsed(address, db->size<uint8_t>());
if (tagged_memory_) tagged_memory_->Load(address, db, tags, inst, context);
}
void TaggedMemoryUseProfiler::Store(uint64_t address, DataBuffer *db) {
if (is_enabled_) tracker_.MarkUsed(address, db->size<uint8_t>());
if (tagged_memory_) tagged_memory_->Store(address, db);
}
void TaggedMemoryUseProfiler::Store(DataBuffer *address_db, DataBuffer *mask_db,
int el_size, DataBuffer *db) {
if (is_enabled_) {
for (int i = 0; i < address_db->size<uint64_t>(); ++i) {
if (mask_db->Get<uint8_t>(i)) {
tracker_.MarkUsed(address_db->Get<uint64_t>(i), el_size);
}
}
}
if (tagged_memory_) tagged_memory_->Store(address_db, mask_db, el_size, db);
}
void TaggedMemoryUseProfiler::Store(uint64_t address, DataBuffer *db,
DataBuffer *tags) {
if ((db != nullptr) && is_enabled_)
tracker_.MarkUsed(address, db->size<uint8_t>());
if (tagged_memory_) tagged_memory_->Store(address, db, tags);
}
} // namespace mpact::sim::cheriot