Ranged cache invalidation

This commit is contained in:
Lynn 2017-02-16 18:18:29 +00:00 committed by Merry
parent d9c69ad997
commit fd068ed6b8
11 changed files with 188 additions and 31 deletions

View file

@ -8,8 +8,8 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <string>
#include <memory> #include <memory>
#include <string>
#include <dynarmic/callbacks.h> #include <dynarmic/callbacks.h>
@ -38,6 +38,13 @@ public:
*/ */
void ClearCache(); void ClearCache();
/**
* Invalidate the code cache at a range of addresses.
* @param start_address The starting address of the range to invalidate.
* @param length The length (in bytes) of the range to invalidate.
*/
void InvalidateCacheRange(std::uint32_t start_address, std::size_t length);
/** /**
* Reset CPU state to state at startup. Does not clear code cache. * Reset CPU state to state at startup. Does not clear code cache.
* Cannot be called from a callback. * Cannot be called from a callback.

View file

@ -39,6 +39,7 @@ set(HEADERS
../include/dynarmic/coprocessor_util.h ../include/dynarmic/coprocessor_util.h
../include/dynarmic/disassembler.h ../include/dynarmic/disassembler.h
../include/dynarmic/dynarmic.h ../include/dynarmic/dynarmic.h
common/address_range.h
common/assert.h common/assert.h
common/bit_util.h common/bit_util.h
common/common_types.h common/common_types.h

View file

@ -12,8 +12,10 @@
#include "backend_x64/block_of_code.h" #include "backend_x64/block_of_code.h"
#include "backend_x64/emit_x64.h" #include "backend_x64/emit_x64.h"
#include "backend_x64/jitstate.h" #include "backend_x64/jitstate.h"
#include "common/address_range.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/bit_util.h" #include "common/bit_util.h"
#include "common/common_types.h"
#include "common/variant_util.h" #include "common/variant_util.h"
#include "frontend/arm/types.h" #include "frontend/arm/types.h"
#include "frontend/ir/basic_block.h" #include "frontend/ir/basic_block.h"
@ -74,8 +76,9 @@ EmitX64::EmitX64(BlockOfCode* code, UserCallbacks cb, Jit* jit_interface)
EmitX64::BlockDescriptor EmitX64::Emit(IR::Block& block) { EmitX64::BlockDescriptor EmitX64::Emit(IR::Block& block) {
code->align(); code->align();
const u8* const emitted_code_start_ptr = code->getCurr(); const u8* const entrypoint = code->getCurr();
// Start emitting.
EmitCondPrelude(block); EmitCondPrelude(block);
RegAlloc reg_alloc{code}; RegAlloc reg_alloc{code};
@ -108,11 +111,12 @@ EmitX64::BlockDescriptor EmitX64::Emit(IR::Block& block) {
code->int3(); code->int3();
const IR::LocationDescriptor descriptor = block.Location(); const IR::LocationDescriptor descriptor = block.Location();
Patch(descriptor, emitted_code_start_ptr); Patch(descriptor, entrypoint);
const size_t size = static_cast<size_t>(code->getCurr() - entrypoint);
EmitX64::BlockDescriptor block_desc{entrypoint, size, block.Location(), block.EndLocation().PC()};
block_descriptors.emplace(descriptor.UniqueHash(), block_desc);
EmitX64::BlockDescriptor& block_desc = block_descriptors[descriptor.UniqueHash()];
size_t emitted_code_size = static_cast<size_t>(code->getCurr() - emitted_code_start_ptr);
block_desc = {emitted_code_start_ptr, emitted_code_size};
return block_desc; return block_desc;
} }
@ -459,7 +463,7 @@ void EmitX64::EmitPushRSB(RegAlloc& reg_alloc, IR::Block&, IR::Inst* inst) {
auto iter = block_descriptors.find(unique_hash_of_target); auto iter = block_descriptors.find(unique_hash_of_target);
CodePtr target_code_ptr = iter != block_descriptors.end() CodePtr target_code_ptr = iter != block_descriptors.end()
? iter->second.code_ptr ? iter->second.entrypoint
: code->GetReturnFromRunCodeAddress(); : code->GetReturnFromRunCodeAddress();
Xbyak::Reg64 code_ptr_reg = reg_alloc.ScratchGpr({HostLoc::RCX}); Xbyak::Reg64 code_ptr_reg = reg_alloc.ScratchGpr({HostLoc::RCX});
@ -3345,7 +3349,7 @@ void EmitX64::EmitTerminal(IR::Term::LinkBlock terminal, IR::LocationDescriptor
patch_information[terminal.next.UniqueHash()].jg.emplace_back(code->getCurr()); patch_information[terminal.next.UniqueHash()].jg.emplace_back(code->getCurr());
if (auto next_bb = GetBasicBlock(terminal.next)) { if (auto next_bb = GetBasicBlock(terminal.next)) {
EmitPatchJg(next_bb->code_ptr); EmitPatchJg(next_bb->entrypoint);
} else { } else {
EmitPatchJg(); EmitPatchJg();
} }
@ -3374,7 +3378,7 @@ void EmitX64::EmitTerminal(IR::Term::LinkBlockFast terminal, IR::LocationDescrip
patch_information[terminal.next.UniqueHash()].jmp.emplace_back(code->getCurr()); patch_information[terminal.next.UniqueHash()].jmp.emplace_back(code->getCurr());
if (auto next_bb = GetBasicBlock(terminal.next)) { if (auto next_bb = GetBasicBlock(terminal.next)) {
EmitPatchJmp(terminal.next, next_bb->code_ptr); EmitPatchJmp(terminal.next, next_bb->entrypoint);
} else { } else {
EmitPatchJmp(terminal.next); EmitPatchJmp(terminal.next);
} }
@ -3475,5 +3479,34 @@ void EmitX64::ClearCache() {
patch_information.clear(); patch_information.clear();
} }
void EmitX64::InvalidateCacheRange(const Common::AddressRange& range) {
// Remove cached block descriptors and patch information overlapping with the given range.
switch (range.which()) {
case 0: // FullAddressRange
ClearCache();
break;
case 1: // AddressInterval
auto interval = boost::get<Common::AddressInterval>(range);
for (auto it = std::begin(block_descriptors); it != std::end(block_descriptors);) {
const IR::LocationDescriptor& descriptor = it->second.start_location;
u32 start = descriptor.PC();
u32 end = it->second.end_location_pc;
if (interval.Overlaps(start, end)) {
it = block_descriptors.erase(it);
auto patch_it = patch_information.find(descriptor.UniqueHash());
if (patch_it != patch_information.end()) {
Unpatch(descriptor);
}
} else {
++it;
}
}
break;
}
}
} // namespace BackendX64 } // namespace BackendX64
} // namespace Dynarmic } // namespace Dynarmic

View file

@ -14,6 +14,7 @@
#include <xbyak_util.h> #include <xbyak_util.h>
#include "backend_x64/reg_alloc.h" #include "backend_x64/reg_alloc.h"
#include "common/address_range.h"
#include "dynarmic/callbacks.h" #include "dynarmic/callbacks.h"
#include "frontend/ir/location_descriptor.h" #include "frontend/ir/location_descriptor.h"
#include "frontend/ir/terminal.h" #include "frontend/ir/terminal.h"
@ -34,8 +35,11 @@ class BlockOfCode;
class EmitX64 final { class EmitX64 final {
public: public:
struct BlockDescriptor { struct BlockDescriptor {
CodePtr code_ptr; ///< Entrypoint of emitted code CodePtr entrypoint; // Entrypoint of emitted code
size_t size; ///< Length in bytes of emitted code size_t size; // Length in bytes of emitted code
IR::LocationDescriptor start_location;
u32 end_location_pc;
}; };
EmitX64(BlockOfCode* code, UserCallbacks cb, Jit* jit_interface); EmitX64(BlockOfCode* code, UserCallbacks cb, Jit* jit_interface);
@ -49,9 +53,16 @@ public:
/// Looks up an emitted host block in the cache. /// Looks up an emitted host block in the cache.
boost::optional<BlockDescriptor> GetBasicBlock(IR::LocationDescriptor descriptor) const; boost::optional<BlockDescriptor> GetBasicBlock(IR::LocationDescriptor descriptor) const;
/// Empties the cache. /// Empties the entire cache.
void ClearCache(); void ClearCache();
/**
* Invalidate the cache at a range of addresses.
* @param start_address The starting address of the range to invalidate.
* @param length The length (in bytes) of the range to invalidate.
*/
void InvalidateCacheRange(const Common::AddressRange& range);
private: private:
// Microinstruction emitters // Microinstruction emitters
#define OPCODE(name, type, ...) void Emit##name(RegAlloc& reg_alloc, IR::Block& block, IR::Inst* inst); #define OPCODE(name, type, ...) void Emit##name(RegAlloc& reg_alloc, IR::Block& block, IR::Inst* inst);

View file

@ -5,6 +5,7 @@
*/ */
#include <memory> #include <memory>
#include <queue>
#include <fmt/format.h> #include <fmt/format.h>
@ -35,6 +36,7 @@ struct Jit::Impl {
, jit_state() , jit_state()
, emitter(&block_of_code, callbacks, jit) , emitter(&block_of_code, callbacks, jit)
, callbacks(callbacks) , callbacks(callbacks)
, jit_interface(jit)
{} {}
BlockOfCode block_of_code; BlockOfCode block_of_code;
@ -42,20 +44,21 @@ struct Jit::Impl {
EmitX64 emitter; EmitX64 emitter;
const UserCallbacks callbacks; const UserCallbacks callbacks;
bool clear_cache_required = false; // Requests made during execution to invalidate the cache are queued up here.
std::queue<Common::AddressRange> invalid_cache_ranges;
size_t Execute(size_t cycle_count) { size_t Execute(size_t cycle_count) {
u32 pc = jit_state.Reg[15]; u32 pc = jit_state.Reg[15];
IR::LocationDescriptor descriptor{pc, Arm::PSR{jit_state.Cpsr}, Arm::FPSCR{jit_state.FPSCR_mode}}; IR::LocationDescriptor descriptor{pc, Arm::PSR{jit_state.Cpsr}, Arm::FPSCR{jit_state.FPSCR_mode}};
CodePtr code_ptr = GetBasicBlock(descriptor).code_ptr; CodePtr entrypoint = GetBasicBlock(descriptor).entrypoint;
return block_of_code.RunCode(&jit_state, code_ptr, cycle_count); return block_of_code.RunCode(&jit_state, entrypoint, cycle_count);
} }
std::string Disassemble(const IR::LocationDescriptor& descriptor) { std::string Disassemble(const IR::LocationDescriptor& descriptor) {
auto block = GetBasicBlock(descriptor); auto block = GetBasicBlock(descriptor);
std::string result = fmt::format("address: {}\nsize: {} bytes\n", block.code_ptr, block.size); std::string result = fmt::format("address: {}\nsize: {} bytes\n", block.entrypoint, block.size);
#ifdef DYNARMIC_USE_LLVM #ifdef DYNARMIC_USE_LLVM
LLVMInitializeX86TargetInfo(); LLVMInitializeX86TargetInfo();
@ -64,7 +67,7 @@ struct Jit::Impl {
LLVMDisasmContextRef llvm_ctx = LLVMCreateDisasm("x86_64", nullptr, 0, nullptr, nullptr); LLVMDisasmContextRef llvm_ctx = LLVMCreateDisasm("x86_64", nullptr, 0, nullptr, nullptr);
LLVMSetDisasmOptions(llvm_ctx, LLVMDisassembler_Option_AsmPrinterVariant); LLVMSetDisasmOptions(llvm_ctx, LLVMDisassembler_Option_AsmPrinterVariant);
const u8* pos = static_cast<const u8*>(block.code_ptr); const u8* pos = static_cast<const u8*>(block.entrypoint);
const u8* end = pos + block.size; const u8* end = pos + block.size;
size_t remaining = block.size; size_t remaining = block.size;
@ -91,14 +94,31 @@ struct Jit::Impl {
return result; return result;
} }
void ClearCache() { void PerformCacheInvalidation() {
block_of_code.ClearCache(); if (invalid_cache_ranges.empty()) {
emitter.ClearCache(); return;
}
jit_state.ResetRSB(); jit_state.ResetRSB();
clear_cache_required = false; block_of_code.ClearCache();
while (!invalid_cache_ranges.empty()) {
emitter.InvalidateCacheRange(invalid_cache_ranges.front());
invalid_cache_ranges.pop();
}
}
void HandleNewCacheRange() {
if (jit_interface->is_executing) {
jit_state.halt_requested = true;
return;
}
PerformCacheInvalidation();
} }
private: private:
Jit* jit_interface;
EmitX64::BlockDescriptor GetBasicBlock(IR::LocationDescriptor descriptor) { EmitX64::BlockDescriptor GetBasicBlock(IR::LocationDescriptor descriptor) {
auto block = emitter.GetBasicBlock(descriptor); auto block = emitter.GetBasicBlock(descriptor);
if (block) if (block)
@ -130,21 +150,19 @@ size_t Jit::Run(size_t cycle_count) {
cycles_executed += impl->Execute(cycle_count - cycles_executed); cycles_executed += impl->Execute(cycle_count - cycles_executed);
} }
if (impl->clear_cache_required) { impl->PerformCacheInvalidation();
impl->ClearCache();
}
return cycles_executed; return cycles_executed;
} }
void Jit::ClearCache() { void Jit::ClearCache() {
if (is_executing) { impl->invalid_cache_ranges.push(Common::FullAddressRange{});
impl->jit_state.halt_requested = true; impl->HandleNewCacheRange();
impl->clear_cache_required = true;
return;
} }
impl->ClearCache(); void Jit::InvalidateCacheRange(std::uint32_t start_address, std::size_t length) {
impl->invalid_cache_ranges.push(Common::AddressInterval{start_address, length});
impl->HandleNewCacheRange();
} }
void Jit::Reset() { void Jit::Reset() {

View file

@ -0,0 +1,33 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2016 MerryMage
* This software may be used and distributed according to the terms of the GNU
* General Public License version 2 or any later version.
*/
#pragma once
#include <cstddef>
#include <boost/variant.hpp>
#include "common/common_types.h"
namespace Dynarmic {
namespace Common {
struct FullAddressRange {};
struct AddressInterval {
u32 start_address;
std::size_t length;
// Does this interval overlap with [from, to)?
bool Overlaps(u32 from, u32 to) const {
return start_address <= to && from <= start_address + length;
}
};
using AddressRange = boost::variant<FullAddressRange, AddressInterval>;
} // namespace Common
} // namespace Dynarmic

View file

@ -35,6 +35,14 @@ LocationDescriptor Block::Location() const {
return location; return location;
} }
LocationDescriptor Block::EndLocation() const {
return end_location;
}
void Block::SetEndLocation(const LocationDescriptor& descriptor) {
end_location = descriptor;
}
Arm::Cond Block::GetCondition() const { Arm::Cond Block::GetCondition() const {
return cond; return cond;
} }

View file

@ -40,7 +40,8 @@ public:
using reverse_iterator = InstructionList::reverse_iterator; using reverse_iterator = InstructionList::reverse_iterator;
using const_reverse_iterator = InstructionList::const_reverse_iterator; using const_reverse_iterator = InstructionList::const_reverse_iterator;
explicit Block(const LocationDescriptor& location) : location(location) {} explicit Block(const LocationDescriptor& location)
: location(location), end_location(location) {}
bool empty() const { return instructions.empty(); } bool empty() const { return instructions.empty(); }
size_type size() const { return instructions.size(); } size_type size() const { return instructions.size(); }
@ -78,6 +79,10 @@ public:
/// Gets the starting location for this basic block. /// Gets the starting location for this basic block.
LocationDescriptor Location() const; LocationDescriptor Location() const;
/// Gets the end location for this basic block.
LocationDescriptor EndLocation() const;
/// Sets the end location for this basic block.
void SetEndLocation(const LocationDescriptor& descriptor);
/// Gets the condition required to pass in order to execute this block. /// Gets the condition required to pass in order to execute this block.
Arm::Cond GetCondition() const; Arm::Cond GetCondition() const;
@ -116,6 +121,8 @@ public:
private: private:
/// Description of the starting location of this block /// Description of the starting location of this block
LocationDescriptor location; LocationDescriptor location;
/// Description of the end location of this block
LocationDescriptor end_location;
/// Conditional to pass in order to execute this block /// Conditional to pass in order to execute this block
Arm::Cond cond = Arm::Cond::AL; Arm::Cond cond = Arm::Cond::AL;
/// Block to execute next if `cond` did not pass. /// Block to execute next if `cond` did not pass.

View file

@ -60,6 +60,8 @@ IR::Block TranslateArm(IR::LocationDescriptor descriptor, MemoryReadCodeFuncType
ASSERT_MSG(visitor.ir.block.HasTerminal(), "Terminal has not been set"); ASSERT_MSG(visitor.ir.block.HasTerminal(), "Terminal has not been set");
visitor.ir.block.SetEndLocation(visitor.ir.current_location);
return std::move(visitor.ir.block); return std::move(visitor.ir.block);
} }

View file

@ -909,6 +909,8 @@ IR::Block TranslateThumb(IR::LocationDescriptor descriptor, MemoryReadCodeFuncTy
visitor.ir.block.CycleCount()++; visitor.ir.block.CycleCount()++;
} }
visitor.ir.block.SetEndLocation(visitor.ir.current_location);
return std::move(visitor.ir.block); return std::move(visitor.ir.block);
} }

View file

@ -1166,3 +1166,38 @@ TEST_CASE("Fuzz ARM packing instructions", "[JitX64]") {
}); });
} }
} }
TEST_CASE("arm: Test InvalidateCacheRange", "[arm]") {
Dynarmic::Jit jit{GetUserCallbacks()};
code_mem.fill({});
code_mem[0] = 0xe3a00005; // mov r0, #5
code_mem[1] = 0xe3a0100D; // mov r1, #13
code_mem[2] = 0xe0812000; // add r2, r1, r0
code_mem[3] = 0xeafffffe; // b +#0 (infinite loop)
jit.Regs() = {};
jit.Cpsr() = 0x000001d0; // User-mode
jit.Run(4);
REQUIRE(jit.Regs()[0] == 5);
REQUIRE(jit.Regs()[1] == 13);
REQUIRE(jit.Regs()[2] == 18);
REQUIRE(jit.Regs()[15] == 0x0000000c);
REQUIRE(jit.Cpsr() == 0x000001d0);
// Change the code
code_mem[1] = 0xe3a01007; // mov r1, #7
jit.InvalidateCacheRange(/*start_memory_location = */ 4, /* length_in_bytes = */ 4);
// Reset position of PC
jit.Regs()[15] = 0;
jit.Run(4);
REQUIRE(jit.Regs()[0] == 5);
REQUIRE(jit.Regs()[1] == 7);
REQUIRE(jit.Regs()[2] == 12);
REQUIRE(jit.Regs()[15] == 0x0000000c);
REQUIRE(jit.Cpsr() == 0x000001d0);
}