mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 11:00:19 +02:00
631 lines
23 KiB
C++
631 lines
23 KiB
C++
// SPDX-License-Identifier: MIT
|
|
|
|
#include "FEXCore/Config/Config.h"
|
|
#include "FEXCore/fextl/string.h"
|
|
#include "FEXHeaderUtils/Filesystem.h"
|
|
#include "FEXCore/Core/DiskCache.h"
|
|
#include "FEXCore/Utils/LogManager.h"
|
|
#include "Interface/Context/Context.h"
|
|
#include "FEXCore/HLE/SyscallHandler.h"
|
|
#include "FEXCore/Utils/File.h"
|
|
#include "FEXCore/fextl/memory.h"
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <charconv>
|
|
|
|
namespace FEXCore {
|
|
|
|
namespace DiskCache {
|
|
|
|
namespace MesaFOZ {
|
|
|
|
enum { FOSSILIZE_COMPRESSION_NONE = 1, FOSSILIZE_COMPRESSION_DEFLATE = 2 };
|
|
|
|
enum { FOSSILIZE_FORMAT_VERSION = 6, FOSSILIZE_FORMAT_MIN_COMPAT_VERSION = 5 };
|
|
|
|
#define FOZ_REF_MAGIC_SIZE 16
|
|
|
|
static const uint8_t stream_reference_magic_and_version[FOZ_REF_MAGIC_SIZE] = {
|
|
0x81, 'F', 'O', 'S', 'S', 'I', 'L', 'I', 'Z', 'E', 'D', 'B', 0, 0, 0, FOSSILIZE_FORMAT_VERSION, /* 4 bytes to use for versioning. */
|
|
};
|
|
|
|
struct __attribute__((packed)) mesa_index_db_file_entry {
|
|
uint64_t hash;
|
|
uint32_t size;
|
|
uint64_t last_access_time;
|
|
uint64_t cache_db_file_offset;
|
|
};
|
|
|
|
} // namespace MesaFOZ
|
|
|
|
bool FOZFile::Open(const fextl::string& FOZFileName, bool ReadOnly) {
|
|
FileName = FOZFileName;
|
|
this->ReadOnly = ReadOnly;
|
|
|
|
File::FileModes Modes = File::FileModes::READ;
|
|
if (!ReadOnly) {
|
|
Modes = Modes | File::FileModes::WRITE | File::FileModes::CREATE;
|
|
}
|
|
FD = fextl::make_unique<File::File>(FileName.c_str(), Modes, false);
|
|
if (!FD->IsValid()) {
|
|
FD.reset();
|
|
return false;
|
|
}
|
|
|
|
bool Valid = false;
|
|
bool TookLock = false;
|
|
ssize_t Size = FD->Size();
|
|
|
|
if (Size < FOZ_REF_MAGIC_SIZE && !ReadOnly) {
|
|
if (!FD->Lock(OPEN_LOCK_TIMEOUT_MS)) {
|
|
FD.reset();
|
|
return false;
|
|
}
|
|
TookLock = true;
|
|
// check size again in case someone else made it while we waited
|
|
Size = FD->Size();
|
|
}
|
|
|
|
if (Size == 0 && !ReadOnly) {
|
|
Valid = FD->PWrite(MesaFOZ::stream_reference_magic_and_version, FOZ_REF_MAGIC_SIZE, 0) == FOZ_REF_MAGIC_SIZE;
|
|
} else {
|
|
uint8_t magic[FOZ_REF_MAGIC_SIZE];
|
|
if (FD->PRead(magic, FOZ_REF_MAGIC_SIZE, 0) == FOZ_REF_MAGIC_SIZE &&
|
|
memcmp(magic, MesaFOZ::stream_reference_magic_and_version, FOZ_REF_MAGIC_SIZE - 1) == 0) {
|
|
int version = magic[FOZ_REF_MAGIC_SIZE - 1];
|
|
Valid = version <= MesaFOZ::FOSSILIZE_FORMAT_VERSION && version >= MesaFOZ::FOSSILIZE_FORMAT_MIN_COMPAT_VERSION;
|
|
}
|
|
}
|
|
|
|
if (TookLock) {
|
|
FD->Unlock();
|
|
}
|
|
|
|
if (!Valid) {
|
|
FD.reset();
|
|
}
|
|
return Valid;
|
|
}
|
|
|
|
ssize_t FOZFile::Size() {
|
|
return FD ? FD->Size() : -1;
|
|
}
|
|
|
|
bool FOZFile::ReadAll(fextl::vector<uint8_t>& Out) {
|
|
ssize_t FileSize = Size();
|
|
if (FileSize < FOZ_REF_MAGIC_SIZE) {
|
|
return false;
|
|
}
|
|
Out.resize((size_t)FileSize - FOZ_REF_MAGIC_SIZE);
|
|
return FD->PRead(Out.data(), Out.size(), FOZ_REF_MAGIC_SIZE) == (ssize_t)Out.size();
|
|
}
|
|
|
|
bool FOZFile::ReadBlob(uint64_t Offset, std::span<uint8_t> OutBlob) {
|
|
if (FD->PRead(OutBlob.data(), OutBlob.size(), Offset) != (ssize_t)OutBlob.size()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FOZFile::WriteBlob(const MesaFOZ::foz_payload_key& Key, std::span<const std::span<const uint8_t>> BlobChunks, uint64_t& OutBlobOffset) {
|
|
ssize_t FileSize = FD->Size();
|
|
if (FileSize < 0) {
|
|
return false;
|
|
}
|
|
uint64_t WriteOffset = (uint64_t)FileSize;
|
|
|
|
if (FD->PWrite(Key.bytes, sizeof(Key.bytes), WriteOffset) != sizeof(Key.bytes)) {
|
|
return false;
|
|
}
|
|
WriteOffset += sizeof(Key.bytes);
|
|
|
|
uint64_t TotalBlobSize = 0;
|
|
for (const std::span<const uint8_t>& Chunk : BlobChunks) {
|
|
TotalBlobSize += Chunk.size();
|
|
}
|
|
|
|
MesaFOZ::foz_payload_header ScratchHeader {.payload_size = (uint32_t)TotalBlobSize,
|
|
.format = MesaFOZ::FOSSILIZE_COMPRESSION_NONE,
|
|
.crc = 0, // todo? maybe
|
|
.uncompressed_size = (uint32_t)TotalBlobSize};
|
|
|
|
if (FD->PWrite(&ScratchHeader, sizeof(ScratchHeader), WriteOffset) != sizeof(ScratchHeader)) {
|
|
return false;
|
|
}
|
|
WriteOffset += sizeof(ScratchHeader);
|
|
|
|
OutBlobOffset = WriteOffset;
|
|
|
|
for (const std::span<const uint8_t>& Chunk : BlobChunks) {
|
|
if (Chunk.size() == 0) {
|
|
continue;
|
|
}
|
|
if (FD->PWrite(Chunk.data(), Chunk.size(), WriteOffset) != (ssize_t)Chunk.size()) {
|
|
return false;
|
|
}
|
|
WriteOffset += Chunk.size();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IndexedDB::Open(const fextl::string& CacheDBName, bool ReadOnly) {
|
|
if (!CacheFOZ.Open(CacheDBName + ".foz", ReadOnly)) {
|
|
return false;
|
|
}
|
|
if (!IndexFOZ.Open(CacheDBName + "_idx.foz", ReadOnly)) {
|
|
return false;
|
|
}
|
|
|
|
this->ReadOnly = ReadOnly;
|
|
return true;
|
|
}
|
|
|
|
void IndexedDB::PopulateIndex(Index& CacheIndex, bool& FoundMetadata) {
|
|
fextl::vector<uint8_t> Data;
|
|
if (!IndexFOZ.ReadAll(Data)) {
|
|
return;
|
|
}
|
|
|
|
ssize_t CacheFOZSize = CacheFOZ.Size();
|
|
if (CacheFOZSize < 0) {
|
|
return;
|
|
}
|
|
|
|
const uint8_t* IndexDataStart = Data.data();
|
|
const size_t IndexDataSize = Data.size();
|
|
size_t ReadOffset = 0;
|
|
while (ReadOffset + sizeof(MesaFOZ::foz_payload_key) + sizeof(MesaFOZ::foz_payload_header) <= IndexDataSize) {
|
|
const auto* FOZKey = reinterpret_cast<const MesaFOZ::foz_payload_key*>(IndexDataStart + ReadOffset);
|
|
ReadOffset += sizeof(MesaFOZ::foz_payload_key);
|
|
const auto* FOZHeader = reinterpret_cast<const MesaFOZ::foz_payload_header*>(IndexDataStart + ReadOffset);
|
|
ReadOffset += sizeof(MesaFOZ::foz_payload_header);
|
|
|
|
if (FOZHeader->payload_size != sizeof(MesaFOZ::mesa_index_db_file_entry) || ReadOffset + FOZHeader->payload_size > IndexDataSize) {
|
|
break;
|
|
}
|
|
const auto* IndexBlobPayload = reinterpret_cast<const MesaFOZ::mesa_index_db_file_entry*>(IndexDataStart + ReadOffset);
|
|
ReadOffset += FOZHeader->payload_size;
|
|
|
|
// skip corrupt (carefully) so we don't have to figure that out in the hot path later
|
|
if (IndexBlobPayload->cache_db_file_offset > (uint64_t)CacheFOZSize ||
|
|
IndexBlobPayload->size > (uint64_t)CacheFOZSize - IndexBlobPayload->cache_db_file_offset) {
|
|
continue;
|
|
}
|
|
if (FOZKey->bytes[39] != 0xFF) {
|
|
uint64_t Key;
|
|
std::from_chars(reinterpret_cast<const char*>(FOZKey->bytes), reinterpret_cast<const char*>(&FOZKey->bytes[16]), Key, 16);
|
|
if (IndexBlobPayload->hash != Key) {
|
|
continue;
|
|
}
|
|
CacheIndex.insert({IndexBlobPayload->hash, {this, IndexBlobPayload->cache_db_file_offset, IndexBlobPayload->size}});
|
|
} else {
|
|
FoundMetadata = true;
|
|
}
|
|
}
|
|
// could truncate/delete index if we don't end up perfectly at end here
|
|
}
|
|
|
|
bool IndexedDB::ReadCacheBlob(uint64_t Offset, std::span<uint8_t> OutBlob) {
|
|
return CacheFOZ.ReadBlob(Offset, OutBlob);
|
|
}
|
|
|
|
bool IndexedDB::StoreCacheBlob(const MesaFOZ::foz_payload_key& Key, std::span<const uint8_t> Blob, Index& Index, std::mutex& IndexMutex) {
|
|
if (ReadOnly) {
|
|
// shouldn't happen
|
|
return false;
|
|
}
|
|
uint64_t Hash;
|
|
if (Key.bytes[39] != 0xFF) {
|
|
std::from_chars(reinterpret_cast<const char*>(Key.bytes), reinterpret_cast<const char*>(&Key.bytes[16]), Hash, 16);
|
|
} else {
|
|
Hash = ~0;
|
|
}
|
|
{
|
|
std::lock_guard Guard(IndexMutex);
|
|
if (Index.contains(Hash)) {
|
|
// shouldn't really happen.. assert or something?
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!CacheFOZ.Lock(STORE_LOCK_TIMEOUT_MS) || !IndexFOZ.Lock(STORE_LOCK_TIMEOUT_MS)) {
|
|
CacheFOZ.Unlock();
|
|
IndexFOZ.Unlock();
|
|
return false;
|
|
}
|
|
|
|
// write cache side first so we get offset for index
|
|
std::span<const uint8_t> BlobChunks[] = {Blob};
|
|
uint64_t BlobOffset = 0;
|
|
if (!CacheFOZ.WriteBlob(Key, BlobChunks, BlobOffset)) {
|
|
CacheFOZ.Unlock();
|
|
IndexFOZ.Unlock();
|
|
return false;
|
|
}
|
|
|
|
MesaFOZ::mesa_index_db_file_entry IndexEntry {.hash = Hash,
|
|
.size = (uint32_t)Blob.size(),
|
|
.last_access_time = 0, // todo..
|
|
.cache_db_file_offset = BlobOffset};
|
|
|
|
std::span<const uint8_t> IndexBlobChunks[] = {{(const uint8_t*)&IndexEntry, sizeof(IndexEntry)}};
|
|
uint64_t UnusedIndexBlobOffset = 0;
|
|
if (!IndexFOZ.WriteBlob(Key, IndexBlobChunks, UnusedIndexBlobOffset)) {
|
|
CacheFOZ.Unlock();
|
|
IndexFOZ.Unlock();
|
|
return false;
|
|
}
|
|
|
|
CacheFOZ.Unlock();
|
|
IndexFOZ.Unlock();
|
|
|
|
std::lock_guard Guard(IndexMutex);
|
|
Index[Hash] = {this, BlobOffset, (uint32_t)Blob.size()};
|
|
return true;
|
|
}
|
|
|
|
bool DiskCache::OpenCacheDB(const fextl::string& CacheDBName, bool ReadOnly) {
|
|
fextl::unique_ptr<IndexedDB> CurDB;
|
|
|
|
if (!ReadOnly && RWCacheDB) {
|
|
// rw already opened, just support one
|
|
return false;
|
|
}
|
|
|
|
CurDB = fextl::make_unique<IndexedDB>();
|
|
if (!CurDB) {
|
|
return false;
|
|
}
|
|
|
|
if (!CurDB->Open(CacheDBName, ReadOnly)) {
|
|
CurDB.reset();
|
|
return false;
|
|
}
|
|
|
|
CurDB->PopulateIndex(Index, FoundMetadata);
|
|
|
|
if (ReadOnly) {
|
|
ROCacheDBs.push_back(std::move(CurDB));
|
|
} else {
|
|
RWCacheDB = std::move(CurDB);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DiskCache::Init(FEXCore::Context::ContextImpl* CTX) {
|
|
this->CTX = CTX;
|
|
|
|
if (!EnableDiskCache) {
|
|
return;
|
|
}
|
|
|
|
fextl::string SerializedConfig = FEXCore::Config::SerializeForCache();
|
|
|
|
struct __attribute__((packed)) {
|
|
uint8_t FormatVersion;
|
|
uint8_t Is64BitMode;
|
|
uint64_t HostFeaturesHash;
|
|
} BucketHeader = {FormatVersion, CTX->Config.Is64BitMode, CTX->HostFeatures.HashForCaching()};
|
|
|
|
fextl::vector<uint8_t> BucketBytes;
|
|
BucketBytes.resize(sizeof(BucketHeader) + SerializedConfig.size());
|
|
memcpy(BucketBytes.data(), &BucketHeader, sizeof(BucketHeader));
|
|
memcpy(BucketBytes.data() + sizeof(BucketHeader), SerializedConfig.data(), SerializedConfig.size());
|
|
BucketHash = XXH3_128bits(BucketBytes.data(), BucketBytes.size());
|
|
|
|
fextl::string BasePath = BasePathOverride();
|
|
if (BasePath.empty()) {
|
|
BasePath = FEXCore::Config::GetCacheDirectory() + "DiskCache/";
|
|
BasePath += fextl::fmt::format("{:016x}{:016x}", BucketHash.high64, BucketHash.low64) + "/";
|
|
}
|
|
FHU::Filesystem::CreateDirectories(BasePath);
|
|
|
|
fextl::string RWDBBasePath = BasePath + "RWCacheDB";
|
|
OpenCacheDB(RWDBBasePath, false);
|
|
|
|
if (RWCacheDB && !FoundMetadata) {
|
|
// we just opened a fresh cache, add a metadata blob
|
|
MesaFOZ::foz_payload_key MetadataKey;
|
|
memset(MetadataKey.bytes, 0xFF, sizeof(MetadataKey));
|
|
RWCacheDB->StoreCacheBlob(MetadataKey, {BucketBytes.data(), BucketBytes.size()}, Index, IndexLock);
|
|
Index.clear();
|
|
}
|
|
|
|
std::string_view RONames = RODBNames();
|
|
while (!RONames.empty()) {
|
|
const auto Delim = RONames.find(',');
|
|
const std::string_view ROName = RONames.substr(0, Delim);
|
|
if (!ROName.empty()) {
|
|
fextl::string RODBBasePath = BasePath;
|
|
RODBBasePath += ROName;
|
|
OpenCacheDB(RODBBasePath, true);
|
|
}
|
|
if (Delim == std::string_view::npos) {
|
|
break;
|
|
}
|
|
// advance to next
|
|
RONames.remove_prefix(Delim + 1);
|
|
}
|
|
|
|
if (IsWritingDiskCache()) {
|
|
Writer = fextl::make_unique<WorkQueueThread>(true);
|
|
}
|
|
}
|
|
|
|
uint64_t DiskCache::MakeBlobKey(const uint64_t ModuleOffset) {
|
|
struct {
|
|
uint64_t ModuleOffset;
|
|
XXH128_hash_t BucketHash;
|
|
} BlobKeyBytes = {ModuleOffset, BucketHash};
|
|
|
|
return XXH3_64bits(&BlobKeyBytes, sizeof(BlobKeyBytes));
|
|
}
|
|
|
|
std::optional<CodeHitData> DiskCache::Lookup(Core::InternalThreadState* Thread, const ExecutableFileSectionInfo& Region, uint64_t GuestRIP) {
|
|
if (!IsReadingDiskCache()) {
|
|
return std::nullopt;
|
|
}
|
|
uint64_t ModuleOffset = GuestRIP - Region.FileStartVA;
|
|
|
|
uint64_t Hash = MakeBlobKey(ModuleOffset);
|
|
|
|
IndexEntry Entry;
|
|
{
|
|
std::lock_guard Guard(IndexLock);
|
|
auto It = Index.find(Hash);
|
|
if (It == Index.end()) {
|
|
// definite miss
|
|
return std::nullopt;
|
|
}
|
|
// we can't hold onto the iterator, the map may shift while we don't hold the lock
|
|
Entry = It->second;
|
|
}
|
|
// found a key hash match, could still be a miss, read the blob and verify more
|
|
CodeHitData HitData;
|
|
HitData.Blob.resize(Entry.Size);
|
|
if (!Entry.DB->ReadCacheBlob(Entry.Offset, HitData.Blob)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (Entry.Size < sizeof(BlobFixedHeader)) {
|
|
return std::nullopt;
|
|
}
|
|
BlobFixedHeader Header;
|
|
memcpy(&Header, HitData.Blob.data(), sizeof(Header));
|
|
|
|
// do we have enough room in our live code to even hash GuestSize worth?
|
|
auto RangeInfo = CTX->SyscallHandler->QueryGuestExecutableRange(Thread, GuestRIP);
|
|
if (RangeInfo.Size == 0 || RangeInfo.Base > GuestRIP) {
|
|
return std::nullopt;
|
|
}
|
|
uint64_t Available = RangeInfo.Base + RangeInfo.Size - GuestRIP;
|
|
if (Available < Header.GuestSize) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
XXH128_hash_t LiveGuestHash = XXH3_128bits(reinterpret_cast<void*>(GuestRIP), Header.GuestSize);
|
|
if (std::memcmp(&LiveGuestHash, &Header.GuestHash, sizeof(Header.GuestHash)) != 0) {
|
|
// LogMan::Msg::IFmt("hash mismatch! length {:d}", Header.GuestSize);
|
|
return std::nullopt;
|
|
}
|
|
// LogMan::Msg::IFmt("hash ok! length {:d}", Header.GuestSize);
|
|
|
|
// this seems to be a full hit, lastly, check the entry is big enough to have everything (except maybe GuestCode)
|
|
uint32_t SizeNeeded = sizeof(Header) + Header.HostSize + Header.EntryPointCount * sizeof(BlobEntryPoint);
|
|
SizeNeeded += Header.SmallRelocCount * sizeof(BlobSmallRelocation) + Header.ThunkRelocCount * sizeof(BlobThunkRelocation) +
|
|
Header.TouchedGuestPagesCount * sizeof(int64_t);
|
|
if (Entry.Size < SizeNeeded) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
HitData.HostCode = {HitData.Blob.data() + sizeof(Header), Header.HostSize};
|
|
HitData.EntryPoints = {reinterpret_cast<const BlobEntryPoint*>(HitData.Blob.data() + sizeof(Header) + Header.HostSize), Header.EntryPointCount};
|
|
|
|
auto* SmallRelocs = reinterpret_cast<const BlobSmallRelocation*>(
|
|
HitData.Blob.data() + sizeof(Header) + Header.HostSize + Header.EntryPointCount * sizeof(BlobEntryPoint));
|
|
auto* ThunkRelocs = reinterpret_cast<const BlobThunkRelocation*>(
|
|
reinterpret_cast<const uint8_t*>(SmallRelocs) + Header.SmallRelocCount * sizeof(BlobSmallRelocation));
|
|
|
|
HitData.Relocations.reserve(Header.SmallRelocCount + Header.ThunkRelocCount);
|
|
for (uint32_t i = 0; i < Header.SmallRelocCount; ++i) {
|
|
const auto& SmallReloc = SmallRelocs[i];
|
|
FEXCore::CPU::Relocation Reloc = FEXCore::CPU::Relocation::Default();
|
|
Reloc.Header.Type = (CPU::RelocationTypes)SmallReloc.Type;
|
|
Reloc.Header.Offset = SmallReloc.Offset;
|
|
switch (SmallReloc.Type) {
|
|
case uint8_t(CPU::RelocationTypes::RELOC_NAMED_SYMBOL_LITERAL):
|
|
Reloc.NamedSymbolLiteral.Symbol = CPU::RelocNamedSymbolLiteral::NamedSymbol(SmallReloc.Named.Symbol);
|
|
break;
|
|
case uint8_t(CPU::RelocationTypes::RELOC_GUEST_RIP_LITERAL): Reloc.GuestRIP.GuestRIP = SmallReloc.RIPLiteral.GuestRIP; break;
|
|
case uint8_t(CPU::RelocationTypes::RELOC_GUEST_RIP_MOVE):
|
|
Reloc.GuestRIP.RegisterIndex = SmallReloc.RIPMove.RegisterIndex;
|
|
Reloc.GuestRIP.GuestRIP = SmallReloc.RIPMove.GuestRIP;
|
|
break;
|
|
default: return std::nullopt;
|
|
}
|
|
HitData.Relocations.push_back(Reloc);
|
|
}
|
|
for (uint32_t i = 0; i < Header.ThunkRelocCount; ++i) {
|
|
const auto& BigReloc = ThunkRelocs[i];
|
|
FEXCore::CPU::Relocation Reloc = FEXCore::CPU::Relocation::Default();
|
|
Reloc.NamedThunkMove.Header.Offset = BigReloc.Offset;
|
|
Reloc.NamedThunkMove.Header.Type = CPU::RelocationTypes::RELOC_NAMED_THUNK_MOVE;
|
|
Reloc.NamedThunkMove.RegisterIndex = BigReloc.RegisterIndex;
|
|
memcpy(&Reloc.NamedThunkMove.Symbol, BigReloc.SymbolHash, sizeof(BigReloc.SymbolHash));
|
|
HitData.Relocations.push_back(Reloc);
|
|
}
|
|
|
|
auto* PageOffsets =
|
|
reinterpret_cast<const int64_t*>(reinterpret_cast<const uint8_t*>(ThunkRelocs) + Header.ThunkRelocCount * sizeof(BlobThunkRelocation));
|
|
HitData.GuestPages.reserve(Header.TouchedGuestPagesCount);
|
|
for (uint32_t i = 0; i < Header.TouchedGuestPagesCount; ++i) {
|
|
HitData.GuestPages.push_back(GuestRIP + PageOffsets[i]);
|
|
}
|
|
|
|
return HitData;
|
|
}
|
|
|
|
struct DiskCache::CacheStoreWorkItem final : WorkQueueThread::WorkItem {
|
|
DiskCache* Self;
|
|
IndexedDB* DB;
|
|
MesaFOZ::foz_payload_key Key;
|
|
fextl::vector<uint8_t> Blob;
|
|
CacheStoreWorkItem(DiskCache* Self, IndexedDB* DB, const MesaFOZ::foz_payload_key& Key, fextl::vector<uint8_t>&& Blob)
|
|
: Self(Self)
|
|
, DB(DB)
|
|
, Key(Key)
|
|
, Blob(std::move(Blob)) {}
|
|
void Run() override {
|
|
DB->StoreCacheBlob(Key, Blob, Self->Index, Self->IndexLock);
|
|
}
|
|
};
|
|
|
|
bool DiskCache::Store(Core::InternalThreadState* Thread, const ExecutableFileSectionInfo& Region, uint64_t GuestRIP,
|
|
std::span<const uint8_t> GuestCode, const CPU::CPUBackend::CompiledCode& CompiledCode,
|
|
std::span<const FEXCore::CPU::Relocation> Relocations, const Frontend::Decoder::DecodedBlockInformation* DecodedBlockInfo) {
|
|
if (!IsWritingDiskCache()) {
|
|
return false;
|
|
}
|
|
if (!DecodedBlockInfo) {
|
|
return false;
|
|
}
|
|
|
|
// check for any reloc targets outside of our jurisdiction
|
|
// todo what are they exactly? caching those blocks is great when it works, so need to figure this out and make finer-grained if we can
|
|
if (RelocationFilter) {
|
|
for (const auto& Reloc : Relocations) {
|
|
if (Reloc.Header.Type != CPU::RelocationTypes::RELOC_GUEST_RIP_LITERAL && Reloc.Header.Type != CPU::RelocationTypes::RELOC_GUEST_RIP_MOVE) {
|
|
continue;
|
|
}
|
|
uint64_t Target = Reloc.GuestRIP.GuestRIP;
|
|
if (Target >= Region.BeginVA && Target < Region.EndVA) {
|
|
continue;
|
|
}
|
|
auto TargetSection = CTX->SyscallHandler->LookupExecutableFileSection(Thread, Target);
|
|
if (!TargetSection || TargetSection->FileInfo.FileId != Region.FileInfo.FileId) {
|
|
// we don't know where it's pointing, so we don't know how to encode the offset, so we can't cache atm
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t SmallRelocCount = 0;
|
|
uint32_t ThunkRelocCount = 0;
|
|
for (const auto& Reloc : Relocations) {
|
|
// relocs aren't cleared every time if IsGeneratingCache, so filter just in case
|
|
if (Reloc.Header.Type == CPU::RelocationTypes::RELOC_NAMED_THUNK_MOVE) {
|
|
ThunkRelocCount++;
|
|
} else {
|
|
SmallRelocCount++;
|
|
}
|
|
}
|
|
|
|
const uint32_t EntryPointCount = (uint32_t)CompiledCode.EntryPoints.size();
|
|
const uint32_t TouchedGuestPagesCount = DecodedBlockInfo ? (uint32_t)DecodedBlockInfo->CodePages.size() : 0;
|
|
|
|
const size_t HeaderOffset = 0;
|
|
const size_t HostCodeOffset = HeaderOffset + sizeof(BlobFixedHeader);
|
|
const size_t EntryPointsOffset = HostCodeOffset + CompiledCode.Size;
|
|
const size_t SmallRelocsOffset = EntryPointsOffset + EntryPointCount * sizeof(BlobEntryPoint);
|
|
const size_t ThunkRelocsOffset = SmallRelocsOffset + SmallRelocCount * sizeof(BlobSmallRelocation);
|
|
const size_t TouchedGuestPagesOffset = ThunkRelocsOffset + ThunkRelocCount * sizeof(BlobThunkRelocation);
|
|
const size_t GuestCodeOffset = TouchedGuestPagesOffset + TouchedGuestPagesCount * sizeof(int64_t);
|
|
const size_t TotalSize = GuestCodeOffset + GuestCode.size();
|
|
|
|
// we'll copy everything into here and pass it to the Writer, then return to caller quickly
|
|
fextl::vector<uint8_t> Blob;
|
|
Blob.resize(TotalSize);
|
|
uint8_t* BlobData = Blob.data();
|
|
|
|
uint64_t ModuleOffset = GuestRIP - Region.FileStartVA;
|
|
|
|
uint64_t BlobKey = MakeBlobKey(ModuleOffset);
|
|
MesaFOZ::foz_payload_key Key = {};
|
|
fextl::string BlobName = fextl::fmt::format("{:016x}", BlobKey);
|
|
memcpy(Key.bytes, BlobName.data(), BlobName.size());
|
|
|
|
BlobFixedHeader Header {
|
|
.GuestSize = (uint32_t)GuestCode.size(),
|
|
.HostSize = (uint32_t)CompiledCode.Size,
|
|
.EntryPointCount = EntryPointCount,
|
|
.SmallRelocCount = SmallRelocCount,
|
|
.ThunkRelocCount = ThunkRelocCount,
|
|
.TouchedGuestPagesCount = TouchedGuestPagesCount,
|
|
.GuestHash = XXH3_128bits(GuestCode.data(), GuestCode.size()),
|
|
};
|
|
memcpy(BlobData + HeaderOffset, &Header, sizeof(Header));
|
|
memcpy(BlobData + HostCodeOffset, CompiledCode.BlockBegin, CompiledCode.Size);
|
|
|
|
// pack and relocate entrypoints
|
|
auto* EntryPoints = reinterpret_cast<BlobEntryPoint*>(BlobData + EntryPointsOffset);
|
|
uint32_t EntryIdx = 0;
|
|
for (auto [GuestAddr, HostAddr] : CompiledCode.EntryPoints) {
|
|
EntryPoints[EntryIdx++] = {GuestAddr - Region.FileStartVA, uint32_t(HostAddr - CompiledCode.BlockBegin)};
|
|
}
|
|
|
|
// pack relocations
|
|
auto* SmallRelocs = reinterpret_cast<BlobSmallRelocation*>(BlobData + SmallRelocsOffset);
|
|
auto* ThunkRelocs = reinterpret_cast<BlobThunkRelocation*>(BlobData + ThunkRelocsOffset);
|
|
uint32_t SmallIdx = 0;
|
|
uint32_t ThunkIdx = 0;
|
|
for (const auto& Reloc : Relocations) {
|
|
switch (Reloc.Header.Type) {
|
|
// it's important to zero-init the element completely so we don't have garbage in unused fields
|
|
// this way, the caches stay deterministic across machines
|
|
case CPU::RelocationTypes::RELOC_NAMED_SYMBOL_LITERAL: {
|
|
BlobSmallRelocation SmallReloc = {};
|
|
SmallReloc.Offset = Reloc.Header.Offset;
|
|
SmallReloc.Type = uint8_t(Reloc.Header.Type);
|
|
SmallReloc.Named.Symbol = uint32_t(Reloc.NamedSymbolLiteral.Symbol);
|
|
SmallRelocs[SmallIdx++] = SmallReloc;
|
|
break;
|
|
}
|
|
case CPU::RelocationTypes::RELOC_GUEST_RIP_LITERAL: {
|
|
BlobSmallRelocation SmallReloc = {};
|
|
SmallReloc.Offset = Reloc.Header.Offset;
|
|
SmallReloc.Type = uint8_t(Reloc.Header.Type);
|
|
SmallReloc.RIPLiteral.GuestRIP = Reloc.GuestRIP.GuestRIP - GuestRIP;
|
|
SmallRelocs[SmallIdx++] = SmallReloc;
|
|
break;
|
|
}
|
|
case CPU::RelocationTypes::RELOC_GUEST_RIP_MOVE: {
|
|
BlobSmallRelocation SmallReloc = {};
|
|
SmallReloc.Offset = Reloc.Header.Offset;
|
|
SmallReloc.Type = uint8_t(Reloc.Header.Type);
|
|
SmallReloc.RIPMove.RegisterIndex = Reloc.GuestRIP.RegisterIndex;
|
|
SmallReloc.RIPMove.GuestRIP = Reloc.GuestRIP.GuestRIP - GuestRIP;
|
|
SmallRelocs[SmallIdx++] = SmallReloc;
|
|
break;
|
|
}
|
|
case CPU::RelocationTypes::RELOC_NAMED_THUNK_MOVE: {
|
|
BlobThunkRelocation BigReloc = {};
|
|
BigReloc.Offset = Reloc.Header.Offset;
|
|
BigReloc.RegisterIndex = Reloc.NamedThunkMove.RegisterIndex;
|
|
memcpy(BigReloc.SymbolHash, &Reloc.NamedThunkMove.Symbol, sizeof(BigReloc.SymbolHash));
|
|
ThunkRelocs[ThunkIdx++] = BigReloc;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// relocate touched pages relative to GuestRIP
|
|
// in theory we could save some size here, unlikely we need all 64bits
|
|
auto* PageOffsets = reinterpret_cast<int64_t*>(BlobData + TouchedGuestPagesOffset);
|
|
uint32_t PageIdx = 0;
|
|
for (auto GuestPage : DecodedBlockInfo->CodePages) {
|
|
PageOffsets[PageIdx++] = GuestPage - GuestRIP;
|
|
}
|
|
|
|
memcpy(BlobData + GuestCodeOffset, GuestCode.data(), GuestCode.size());
|
|
|
|
// hand the rest off to the writer thread
|
|
Writer->QueueWork(fextl::make_unique<CacheStoreWorkItem>(this, RWCacheDB.get(), Key, std::move(Blob)));
|
|
return true;
|
|
}
|
|
|
|
} // namespace DiskCache
|
|
|
|
} // namespace FEXCore
|