diff --git a/FEXCore/Source/Interface/Context/Context.h b/FEXCore/Source/Interface/Context/Context.h index ec56e0177..ffabdbdae 100644 --- a/FEXCore/Source/Interface/Context/Context.h +++ b/FEXCore/Source/Interface/Context/Context.h @@ -132,7 +132,7 @@ public: // Same but on disk cache packed relocations [[nodiscard]] bool ApplyPackedCodeRelocations(uint64_t GuestDelta, std::span Code, std::span SmallRelocs, - std::span ThunkRelocs, bool ForStorage); + std::span ThunkRelocs); }; class ContextImpl final : public FEXCore::Context::Context, public CPU::SharedCodeBufferManager { diff --git a/FEXCore/Source/Interface/Core/CodeCache.cpp b/FEXCore/Source/Interface/Core/CodeCache.cpp index b3982e47d..5246c0247 100644 --- a/FEXCore/Source/Interface/Core/CodeCache.cpp +++ b/FEXCore/Source/Interface/Core/CodeCache.cpp @@ -538,16 +538,22 @@ ApplyRIPMoveRelocation(ContextImpl& CTX, uint64_t GuestRIP, uint8_t RegisterInde Emitter.LoadConstant(ARMEmitter::Size::i64Bit, ARMEmitter::Register(RegisterIndex), Pointer, CPU::Arm64Emitter::PadType::DOPAD); } +static inline void ApplyPatchableDataRelocation(uint64_t SiteAddress, uint8_t ValueSize, uint8_t RegisterIndex, CPU::Arm64Emitter& Emitter) { + uint64_t Value = 0; + memcpy(&Value, reinterpret_cast(SiteAddress), ValueSize); + Emitter.LoadConstant(ARMEmitter::Size::i64Bit, ARMEmitter::Register(RegisterIndex), Value, CPU::Arm64Emitter::PadType::DOPAD); +} + bool CodeCache::ApplyPackedCodeRelocations(uint64_t GuestEntry, std::span Code, std::span SmallRelocs, - std::span ThunkRelocs, bool ForStorage) { + std::span ThunkRelocs) { CPU::Arm64Emitter Emitter(&CTX, Code.data(), Code.size_bytes()); for (auto& Reloc : SmallRelocs) { LOGMAN_THROW_A_FMT(Reloc.Offset < Code.size_bytes(), "Invalid relocation offset"); Emitter.SetCursorOffset(Reloc.Offset); switch ((CPU::RelocationTypes)Reloc.Type) { case FEXCore::CPU::RelocationTypes::RELOC_NAMED_SYMBOL_LITERAL: { - ApplySymbolLiteralRelocation(CTX, (CPU::RelocNamedSymbolLiteral::NamedSymbol)Reloc.Named.Symbol, GuestEntry, Emitter, ForStorage); + ApplySymbolLiteralRelocation(CTX, (CPU::RelocNamedSymbolLiteral::NamedSymbol)Reloc.Named.Symbol, GuestEntry, Emitter, false); break; } case FEXCore::CPU::RelocationTypes::RELOC_GUEST_RIP_LITERAL: { @@ -558,13 +564,18 @@ bool CodeCache::ApplyPackedCodeRelocations(uint64_t GuestEntry, std::span(WriterThreadFlags); @@ -480,13 +483,23 @@ namespace DiskCache { return std::nullopt; } Thread->FrontendDecoder->DecodeLoop(reinterpret_cast(GuestRIP), AnonPrefixGuestBytes); + const auto* BlockInfo = Thread->FrontendDecoder->GetDecodedBlockInfo(); + XXH3_state_t HashState; XXH3_64bits_reset(&HashState); - for (auto& SubBlock : Thread->FrontendDecoder->GetDecodedBlockInfo()->Blocks) { - XXH3_64bits_update(&HashState, reinterpret_cast(SubBlock.Entry), SubBlock.Size); + for (auto& SubBlock : BlockInfo->Blocks) { if (SubBlock.BlockStatus != Frontend::Decoder::DecodedBlockStatus::SUCCESS) { return std::nullopt; } + uint64_t HashStart = SubBlock.Entry; + // skip over masked in-block data and data/etc gaps between blocks + for (auto& DataMask : SubBlock.DataMasks) { + XXH3_64bits_update(&HashState, reinterpret_cast(HashStart), DataMask.FieldAddress - HashStart); + HashStart = DataMask.FieldAddress + DataMask.ValueSize; + } + if (HashStart != SubBlock.Entry + SubBlock.Size) { + XXH3_64bits_update(&HashState, reinterpret_cast(HashStart), SubBlock.Size - (HashStart - SubBlock.Entry)); + } } GuestCodeKey = XXH3_64bits_digest(&HashState); // if (TotalSize < AnonPrefixGuestBytes) { @@ -635,7 +648,7 @@ namespace DiskCache { EntryPointRip += GuestRIP; } - if (!CTX->CodeCache.ApplyPackedCodeRelocations(GuestRIP, std::as_writable_bytes(HitData.HostCode), SmallRelocs, ThunkRelocs, false)) { + if (!CTX->CodeCache.ApplyPackedCodeRelocations(GuestRIP, std::as_writable_bytes(HitData.HostCode), SmallRelocs, ThunkRelocs)) { return std::nullopt; } @@ -737,6 +750,8 @@ namespace DiskCache { } } + fextl::set DataMaskAddresses; + fextl::vector ExactGuestCodeExtents; uint64_t CurStartExtent = 0, CurEndExtent = 0; const Frontend::Decoder::DecodedBlocks* LastBlock = nullptr; @@ -758,6 +773,16 @@ namespace DiskCache { CurEndExtent = SubBlock.Entry + SubBlock.Size; } } + // split extents according to data masks as well + for (auto& Mask : SubBlock.DataMasks) { + if (Mask.FieldAddress > CurStartExtent) { + ExactGuestCodeExtents.push_back(CurStartExtent - GuestRIP); + ExactGuestCodeExtents.push_back(Mask.FieldAddress - CurStartExtent); + } + CurStartExtent = Mask.FieldAddress + Mask.ValueSize; + + DataMaskAddresses.insert(Mask.FieldAddress); + } LastBlock = &SubBlock; } if (LastBlock && (CurStartExtent != GuestRIP || CurEndExtent != GuestRIP + GuestCode.size())) { @@ -860,6 +885,24 @@ namespace DiskCache { SmallRelocs[SmallIdx++] = SmallReloc; break; } + case CPU::RelocationTypes::RELOC_GUEST_PATCHABLE_DATA_MOVE: { + BlobSmallRelocation SmallReloc = {}; + SmallReloc.Offset = Reloc.Header.Offset; + SmallReloc.Type = uint8_t(Reloc.Header.Type); + SmallReloc.PatchableData.RegisterIndex = Reloc.GuestPatchableData.RegisterIndex; + SmallReloc.PatchableData.ValueSize = Reloc.GuestPatchableData.ValueSize; + SmallReloc.PatchableData.SiteOffset = uint32_t(Reloc.GuestPatchableData.SiteAddress - GuestRIP); + SmallRelocs[SmallIdx++] = SmallReloc; + + // mark the corresponding data mask consumed - we might not find one if they got removed due to the smc workaround + // the hash will just fail on lookup later + auto It = DataMaskAddresses.find(Reloc.GuestPatchableData.SiteAddress); + if (It != DataMaskAddresses.end()) { + DataMaskAddresses.erase(It); + } + + break; + } case CPU::RelocationTypes::RELOC_NAMED_THUNK_MOVE: { BlobThunkRelocation BigReloc = {}; BigReloc.Offset = Reloc.Header.Offset; @@ -871,6 +914,12 @@ namespace DiskCache { } } + if (!DataMaskAddresses.empty()) { + LogMan::Msg::IFmt("DiskCache: DataMask unaccounted for! {:x}", GuestCodeKey); + // this would mean we omitted contents in the hash that we're not going to patch, which would be loading corrupt code + return false; + } + memcpy(BlobData + GuestCodeOffset, GuestCode.data(), GuestCode.size()); fextl::vector IndexBlob; diff --git a/FEXCore/Source/Interface/Core/Frontend.cpp b/FEXCore/Source/Interface/Core/Frontend.cpp index 5829516de..92f5c4fce 100644 --- a/FEXCore/Source/Interface/Core/Frontend.cpp +++ b/FEXCore/Source/Interface/Core/Frontend.cpp @@ -140,6 +140,8 @@ std::pair Decoder::ReadData(uint8_t Size) { uint64_t Res = 0; uint64_t Address = reinterpret_cast(InstStream.InstStream + InstructionSize); + LastFieldReadOffset = (uint8_t)InstructionSize; + LastFieldReadSize = Size; if (CheckRangeExecutable(Address, Size)) { std::memcpy(&Res, &InstStream.AdjustedInstStream[InstructionSize], Size); } else { @@ -1334,6 +1336,13 @@ void Decoder::AddBranchTarget(uint64_t Target) { .BlockStatus = BlockIt->BlockStatus, }; + if (BlockIt->DataMasks.size()) { + auto MaskIt = std::lower_bound(BlockIt->DataMasks.begin(), BlockIt->DataMasks.end(), SplitAddr, + [](const DataMask& Mask, uint64_t Addr) { return Mask.FieldAddress < Addr; }); + SplitBlock.DataMasks.assign(MaskIt, BlockIt->DataMasks.end()); + BlockIt->DataMasks.erase(MaskIt, BlockIt->DataMasks.end()); + } + BlockIt->Size = SplitOffset; BlockIt->NumInstructions = SplitIdx; @@ -1386,7 +1395,48 @@ bool Decoder::CheckIfCacheable(FEXCore::Core::InternalThreadState& Thread, const return !Uncacheable; } +void Decoder::DetectDataMasks(uint64_t OpAddress, DecodedBlocks& Block) { + if (LastFieldReadSize < 4) { + return; + } + + FEXCore::X86Tables::DecodedOperand* LiteralToPatch = nullptr; + + // mov reg,imm + if (DecodeInst->OP >= 0xB8 && DecodeInst->OP <= 0xBF) { + for (auto& Src : DecodeInst->Src) { + if (Src.IsLiteral()) { + LiteralToPatch = &Src; + break; + } + } + + // we could filter to certain high values that are more likely to be pointers/etc? + // const uint64_t Value = Lit->Data.Literal.Value; + // if (LiteralToPatch && Value < 0x1000000ULL) { + // LiteralToPatch = nullptr; + // } + } + + // todo add a bunch more + + if (LiteralToPatch) { + Block.DataMasks.push_back({OpAddress + LastFieldReadOffset, LastFieldReadSize}); + + LiteralToPatch->Type = X86Tables::DecodedOperand::OpType::LiteralPatchable; + LiteralToPatch->Data.LiteralPatchable.FieldOffset = LastFieldReadOffset; + LiteralToPatch->Data.LiteralPatchable.Width = LastFieldReadSize; + } +} + void Decoder::DecodeLoop(const uint8_t* _InstStream, uint64_t GuestSizePause) { + // counter-intuitively, the masks are also needed for lookup on anon prefix decodes, not just stores + bool WantsDataMasks = CTX->DiskCache.IsReadingDiskCache() || CTX->DiskCache.IsWritingDiskCache(); + // remove this if we ever fixup ValidateCode crc constant after relocations + if (CTX->Config.SMCChecks == FEXCore::Config::CONFIG_SMC_FULL) { + WantsDataMasks = false; + } + while (!FinalInstruction && (Paused || !BlocksToDecode.empty())) { bool Pausing = false; fextl::vector::iterator BlockIt; @@ -1461,6 +1511,7 @@ void Decoder::DecodeLoop(const uint8_t* _InstStream, uint64_t GuestSizePause) { BlockInfo.CodePages.insert(CurrentCodePage); } + LastFieldReadSize = 0; BlockIt->BlockStatus = DecodeInstruction(OpAddress); if (HitBadRelocation) { BlockInfo.TotalInstructionCount = 0; @@ -1485,6 +1536,11 @@ void Decoder::DecodeLoop(const uint8_t* _InstStream, uint64_t GuestSizePause) { ++BlockIt->NumInstructions; BlockIt->Size += DecodeInst->InstSize; + // if we weren't provided relocations (guest JIT), try to detect what we can + if (WantsDataMasks && BlockIt->BlockStatus == DecodedBlockStatus::SUCCESS && BlockInfo.Is64BitMode && !Relocations) { + DetectDataMasks(OpAddress, *BlockIt); + } + // Can not continue this block at all on invalid instruction if (BlockIt->BlockStatus != DecodedBlockStatus::SUCCESS) [[unlikely]] { if (!EntryBlock && BlockIt->BlockStatus != DecodedBlockStatus::BAD_RELOCATION) { @@ -1531,7 +1587,12 @@ void Decoder::DecodeLoop(const uint8_t* _InstStream, uint64_t GuestSizePause) { // If the branch target is within our multiblock range then we can keep going on // We don't want to short circuit this since we want to calculate our ranges still // NOTE: This will invalidate BlockIt, this is fine as we immediately break from the loop and EraseBlock cannot be true - BlockIt->ForceFullSMCDetection = CTX->AreMonoHacksActive() && IsBranchMonoTailcall(BlockIt->NumInstructions); + if (CTX->AreMonoHacksActive() && IsBranchMonoTailcall(BlockIt->NumInstructions)) { + BlockIt->ForceFullSMCDetection = true; + // todo abandon patching this for now, as the crc will fail and it will lock up redoing it over and over + // we should fix the crc at relocation if this is important + BlockIt->DataMasks.clear(); + } BranchTargetInMultiblockRange(); } diff --git a/FEXCore/Source/Interface/Core/Frontend.h b/FEXCore/Source/Interface/Core/Frontend.h index 15683fced..5a3ec199f 100644 --- a/FEXCore/Source/Interface/Core/Frontend.h +++ b/FEXCore/Source/Interface/Core/Frontend.h @@ -32,6 +32,11 @@ public: UNIMPLEMENTED_INST, }; + struct DataMask final { + uint64_t FieldAddress; + uint8_t ValueSize; + }; + // New Frontend decoding struct DecodedBlocks final { uint64_t Entry {}; @@ -41,6 +46,7 @@ public: DecodedBlockStatus BlockStatus; bool IsEntryPoint {}; bool ForceFullSMCDetection {}; + fextl::vector DataMasks; }; struct DecodedBlockInformation final { @@ -103,6 +109,8 @@ private: void AddBranchTarget(uint64_t Target); + void DetectDataMasks(uint64_t OpAddress, DecodedBlocks& Block); + bool CheckRangeExecutable(uint64_t Address, uint64_t Size); uint8_t ReadByte(); @@ -133,6 +141,9 @@ private: uint64_t BlockStartOffset {}; bool EraseBlock {}; + uint8_t LastFieldReadOffset; + uint8_t LastFieldReadSize; + uint64_t ExecutableRangeBase {}; uint64_t ExecutableRangeEnd {}; bool ExecutableRangeWritable {}; diff --git a/FEXCore/Source/Interface/Core/JIT/ALUOps.cpp b/FEXCore/Source/Interface/Core/JIT/ALUOps.cpp index 7946ccdd2..3691bcab8 100644 --- a/FEXCore/Source/Interface/Core/JIT/ALUOps.cpp +++ b/FEXCore/Source/Interface/Core/JIT/ALUOps.cpp @@ -64,6 +64,11 @@ DEF_OP(EntrypointOffset) { InsertGuestRIPMove(GetReg(Node), Constant & Mask); } +DEF_OP(PatchableGuestData) { + auto Op = IROp->C(); + InsertGuestPatchableDataMove(GetReg(Node), Op->Value, Op->SiteAddress, (uint8_t)Op->SiteSize); +} + DEF_OP(InlineConstant) { // nop } diff --git a/FEXCore/Source/Interface/Core/JIT/Arm64Relocations.cpp b/FEXCore/Source/Interface/Core/JIT/Arm64Relocations.cpp index 50b8628e2..47c4f1159 100644 --- a/FEXCore/Source/Interface/Core/JIT/Arm64Relocations.cpp +++ b/FEXCore/Source/Interface/Core/JIT/Arm64Relocations.cpp @@ -102,6 +102,18 @@ void Arm64JITCore::InsertGuestRIPMove(ARMEmitter::Register Reg, uint64_t Constan Relocations.emplace_back(MoveABI); } +void Arm64JITCore::InsertGuestPatchableDataMove(ARMEmitter::Register Reg, uint64_t Value, uint64_t SiteAddress, uint8_t ValueSize) { + Relocation MoveABI = Relocation::Default(); + MoveABI.GuestPatchableData.Header = {.Offset = GetCursorOffset(), .Type = FEXCore::CPU::RelocationTypes::RELOC_GUEST_PATCHABLE_DATA_MOVE}; + MoveABI.GuestPatchableData.RegisterIndex = Reg.Idx(); + MoveABI.GuestPatchableData.ValueSize = ValueSize; + MoveABI.GuestPatchableData.SiteAddress = SiteAddress; + + // this might get patched on disk cache load + LoadConstant(ARMEmitter::Size::i64Bit, Reg, Value, FEXCore::CPU::Arm64Emitter::PadType::DOPAD); + Relocations.emplace_back(MoveABI); +} + fextl::vector Arm64JITCore::TakeRelocations(uint64_t GuestBaseAddress) { // Rebase relocations to library base address for (auto& Relocation : Relocations) { diff --git a/FEXCore/Source/Interface/Core/JIT/JITClass.h b/FEXCore/Source/Interface/Core/JIT/JITClass.h index 9d91f6503..2a56a7efe 100644 --- a/FEXCore/Source/Interface/Core/JIT/JITClass.h +++ b/FEXCore/Source/Interface/Core/JIT/JITClass.h @@ -562,6 +562,8 @@ private: */ void InsertGuestRIPMove(ARMEmitter::Register Reg, uint64_t Constant); + void InsertGuestPatchableDataMove(ARMEmitter::Register Reg, uint64_t Value, uint64_t SiteAddress, uint8_t ValueSize); + /** * @brief Inserts a named symbol as a literal in memory * diff --git a/FEXCore/Source/Interface/Core/JIT/Relocations.h b/FEXCore/Source/Interface/Core/JIT/Relocations.h index 9faa8a781..ec268874d 100644 --- a/FEXCore/Source/Interface/Core/JIT/Relocations.h +++ b/FEXCore/Source/Interface/Core/JIT/Relocations.h @@ -25,6 +25,10 @@ enum class RelocationTypes : uint32_t { // 4 instruction constant generation // Aligned to struct RelocGuestRIP RELOC_GUEST_RIP_MOVE, + + // The frontend flagged those regions as patchable by the disk cache + // Aligned to struct RelocGuestPatchableData + RELOC_GUEST_PATCHABLE_DATA_MOVE, }; struct FEX_PACKED RelocationHeader final { @@ -73,6 +77,20 @@ struct RelocGuestRIP final { uint32_t pad2[6] {}; }; +struct RelocGuestPatchableData final { + RelocationHeader Header {}; + + uint8_t RegisterIndex; + + uint8_t ValueSize; + + char Pad[2]; + + uint64_t SiteAddress; + + uint32_t pad2[6] {}; +}; + union Relocation { // Clang 16 Can't default-initialize this union static Relocation Default() { @@ -93,6 +111,8 @@ union Relocation { RelocNamedThunkMove NamedThunkMove; RelocGuestRIP GuestRIP; + + RelocGuestPatchableData GuestPatchableData; }; uint64_t GetNamedSymbolLiteral(FEXCore::Context::ContextImpl&, RelocNamedSymbolLiteral::NamedSymbol); diff --git a/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp b/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp index d2552f433..22c7e46b8 100644 --- a/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp +++ b/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp @@ -4357,6 +4357,9 @@ AddressMode OpDispatchBuilder::DecodeAddress(const X86Tables::DecodedOp& Op, con A.NonTSO |= IsNonTSOReg(AccessType, Operand.Data.SIB.Base) || IsNonTSOReg(AccessType, Operand.Data.SIB.Index); } else if (Operand.IsLiteralRelocation()) { A.Base = _EntrypointOffset(GPRSize, Operand.Data.LiteralRelocation.EntrypointOffset); + } else if (Operand.IsLiteralPatchable()) { + A.Base = _PatchableGuestData(OpSize::i64Bit, Operand.Data.LiteralPatchable.Value, Op->PC + Operand.Data.LiteralPatchable.FieldOffset, + static_cast(Operand.Data.LiteralPatchable.Width)); } else { LOGMAN_MSG_A_FMT("Unknown Src Type: {}\n", Operand.Type); } diff --git a/FEXCore/Source/Interface/Core/OpcodeDispatcher.h b/FEXCore/Source/Interface/Core/OpcodeDispatcher.h index 4a298a812..5a5737131 100644 --- a/FEXCore/Source/Interface/Core/OpcodeDispatcher.h +++ b/FEXCore/Source/Interface/Core/OpcodeDispatcher.h @@ -1521,7 +1521,7 @@ private: [[nodiscard]] static bool IsOperandMem(const X86Tables::DecodedOperand& Operand, bool Load) { // Literals are immediates as sources but memory addresses as destinations. - return !(Load && (Operand.IsLiteral() || Operand.IsLiteralRelocation())) && !Operand.IsGPR(); + return !(Load && (Operand.IsLiteral() || Operand.IsLiteralRelocation() || Operand.IsLiteralPatchable())) && !Operand.IsGPR(); } [[nodiscard]] diff --git a/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h b/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h index 8bf90e6fd..9ab0cfbaa 100644 --- a/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h +++ b/FEXCore/Source/Interface/Core/X86Tables/X86Tables.h @@ -128,6 +128,7 @@ struct DecodedOperand { RIPRelativeRelocation, Literal, LiteralRelocation, + LiteralPatchable, SIB, SIBRelocation }; @@ -159,6 +160,9 @@ struct DecodedOperand { bool IsLiteralRelocation() const { return Type == OpType::LiteralRelocation; } + bool IsLiteralPatchable() const { + return Type == OpType::LiteralPatchable; + } bool IsSIB() const { return Type == OpType::SIB; } @@ -196,6 +200,12 @@ struct DecodedOperand { int64_t EntrypointOffset; } LiteralRelocation; + struct { + uint64_t Value; + uint8_t Size; + uint8_t FieldOffset; + uint8_t Width; + } LiteralPatchable; struct { int64_t Offset; uint8_t Scale; diff --git a/FEXCore/Source/Interface/IR/IR.json b/FEXCore/Source/Interface/IR/IR.json index 5f5b8257d..61d0e0fd0 100644 --- a/FEXCore/Source/Interface/IR/IR.json +++ b/FEXCore/Source/Interface/IR/IR.json @@ -953,6 +953,13 @@ ] }, + "GPR = PatchableGuestData OpSize:#Size, i64:$Value, i64:$SiteAddress, i64:$SiteSize": { + "Desc": ["Loads Value in a patchable way", + "On disk cache load the value is patched from live guest bytes at SiteAddress" + ], + "DestSize": "Size" + }, + "GPR = Constant i64:$Constant, ConstPad:$Pad{IR::ConstPad::NoPad}, i32:$MaxBytes{0}": { "Desc": ["Generates a 64bit constant inside of a GPR", "Unsupported to create a constant in FPR" diff --git a/FEXCore/include/FEXCore/Core/DiskCache.h b/FEXCore/include/FEXCore/Core/DiskCache.h index 0b96bf781..4071a8572 100644 --- a/FEXCore/include/FEXCore/Core/DiskCache.h +++ b/FEXCore/include/FEXCore/Core/DiskCache.h @@ -85,6 +85,11 @@ namespace DiskCache { uint8_t RegisterIndex; uint64_t GuestRIP; } RIPMove; + struct __attribute__((packed)) { + uint8_t RegisterIndex; + uint8_t ValueSize; + uint32_t SiteOffset; + } PatchableData; }; }; @@ -176,10 +181,10 @@ namespace DiskCache { std::span Relocations, const Frontend::Decoder::DecodedBlockInformation* DecodedBlockInfo); bool IsWritingDiskCache() const { - return (bool)RWCacheDB; + return WritingDiskCache; } bool IsReadingDiskCache() const { - return !ROCacheDBs.empty() || RWCacheDB != nullptr; + return ReadingDiskCache; } bool IsValidating() const { return Validation; @@ -189,6 +194,8 @@ namespace DiskCache { bool OpenCacheDB(const fextl::string& CacheDBName, bool ReadOnly); uint64_t MakeBlobKey(Core::InternalThreadState* Thread, const uint64_t ModuleOffset, bool Writable, bool MonoBackpatcher); + bool ReadingDiskCache {}; + bool WritingDiskCache {}; FEXCore::Context::ContextImpl* CTX; XXH128_hash_t BucketHash; fextl::vector> ROCacheDBs; @@ -214,7 +221,7 @@ namespace DiskCache { // TODO: This header is in global installed header path, but uses internal headers. // Migrate this once that is fixed. - static constexpr uint16_t FormatVersion = 16; + static constexpr uint16_t FormatVersion = 17; FEX_DEFAULT_VISIBILITY uint16_t GetFormatVersion(); } // namespace DiskCache