[CPU/Backend] Refactor shared backend logic into CRTP base
This commit is contained in:
383
src/xenia/cpu/backend/code_cache_base.h
Normal file
383
src/xenia/cpu/backend/code_cache_base.h
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2026 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_CPU_BACKEND_CODE_CACHE_BASE_H_
|
||||||
|
#define XENIA_CPU_BACKEND_CODE_CACHE_BASE_H_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/clock.h"
|
||||||
|
#include "xenia/base/literals.h"
|
||||||
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/math.h"
|
||||||
|
#include "xenia/base/memory.h"
|
||||||
|
#include "xenia/base/mutex.h"
|
||||||
|
#include "xenia/cpu/backend/code_cache.h"
|
||||||
|
#include "xenia/cpu/function.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace cpu {
|
||||||
|
namespace backend {
|
||||||
|
|
||||||
|
struct EmitFunctionInfo {
|
||||||
|
struct _code_size {
|
||||||
|
size_t prolog;
|
||||||
|
size_t body;
|
||||||
|
size_t epilog;
|
||||||
|
size_t tail;
|
||||||
|
size_t total;
|
||||||
|
} code_size;
|
||||||
|
size_t prolog_stack_alloc_offset;
|
||||||
|
size_t stack_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
// CRTP base class for JIT code caches. Contains all platform-independent
|
||||||
|
// logic for memory management, indirection tables, code placement, and
|
||||||
|
// function lookup. Derived classes provide architecture-specific hooks:
|
||||||
|
//
|
||||||
|
// void FillCode(void* address, size_t size)
|
||||||
|
// Fill unused code regions with trap instructions (0xCC / BRK).
|
||||||
|
//
|
||||||
|
// void FlushCodeRange(void* address, size_t size)
|
||||||
|
// Flush I-cache after writing code (no-op on x86, required on ARM64).
|
||||||
|
//
|
||||||
|
// UnwindReservation RequestUnwindReservation(uint8_t* entry_address)
|
||||||
|
// Reserve space for platform-specific unwind info.
|
||||||
|
//
|
||||||
|
// void PlaceCode(uint32_t guest_address, void* machine_code,
|
||||||
|
// const EmitFunctionInfo& func_info,
|
||||||
|
// void* code_execute_address,
|
||||||
|
// UnwindReservation unwind_reservation)
|
||||||
|
// Register unwind info and perform platform-specific post-placement.
|
||||||
|
//
|
||||||
|
// void OnCodePlaced(uint32_t guest_address, GuestFunction* function_info,
|
||||||
|
// void* code_execute_address, size_t code_size)
|
||||||
|
// Optional hook called after code is placed outside the critical section
|
||||||
|
// (used for VTune integration on x64). Default is no-op.
|
||||||
|
template <typename Derived>
|
||||||
|
class CodeCacheBase : public CodeCache {
|
||||||
|
public:
|
||||||
|
~CodeCacheBase() override {
|
||||||
|
if (indirection_table_base_) {
|
||||||
|
xe::memory::DeallocFixed(indirection_table_base_, kIndirectionTableSize,
|
||||||
|
xe::memory::DeallocationType::kRelease);
|
||||||
|
}
|
||||||
|
if (mapping_ != xe::memory::kFileMappingHandleInvalid) {
|
||||||
|
if (generated_code_write_base_ &&
|
||||||
|
generated_code_write_base_ != generated_code_execute_base_) {
|
||||||
|
xe::memory::UnmapFileView(mapping_, generated_code_write_base_,
|
||||||
|
kGeneratedCodeSize);
|
||||||
|
}
|
||||||
|
if (generated_code_execute_base_) {
|
||||||
|
xe::memory::UnmapFileView(mapping_, generated_code_execute_base_,
|
||||||
|
kGeneratedCodeSize);
|
||||||
|
}
|
||||||
|
xe::memory::CloseFileMappingHandle(mapping_, file_name_);
|
||||||
|
mapping_ = xe::memory::kFileMappingHandleInvalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path& file_name() const override { return file_name_; }
|
||||||
|
uintptr_t execute_base_address() const override {
|
||||||
|
return kGeneratedCodeExecuteBase;
|
||||||
|
}
|
||||||
|
size_t total_size() const override { return kGeneratedCodeSize; }
|
||||||
|
|
||||||
|
bool has_indirection_table() { return indirection_table_base_ != nullptr; }
|
||||||
|
|
||||||
|
void set_indirection_default(uint32_t default_value) {
|
||||||
|
indirection_default_value_ = default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddIndirection(uint32_t guest_address, uint32_t host_address) {
|
||||||
|
if (!indirection_table_base_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint32_t* indirection_slot = reinterpret_cast<uint32_t*>(
|
||||||
|
indirection_table_base_ + (guest_address - kIndirectionTableBase));
|
||||||
|
*indirection_slot = host_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommitExecutableRange(uint32_t guest_low, uint32_t guest_high) {
|
||||||
|
if (!indirection_table_base_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xe::memory::AllocFixed(
|
||||||
|
indirection_table_base_ + (guest_low - kIndirectionTableBase),
|
||||||
|
guest_high - guest_low, xe::memory::AllocationType::kCommit,
|
||||||
|
xe::memory::PageAccess::kReadWrite);
|
||||||
|
uint32_t* p = reinterpret_cast<uint32_t*>(indirection_table_base_);
|
||||||
|
for (uint32_t address = guest_low; address < guest_high; ++address) {
|
||||||
|
p[(address - kIndirectionTableBase) / 4] = indirection_default_value_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaceHostCode(uint32_t guest_address, void* machine_code,
|
||||||
|
const EmitFunctionInfo& func_info,
|
||||||
|
void*& code_execute_address_out,
|
||||||
|
void*& code_write_address_out) {
|
||||||
|
PlaceGuestCode(guest_address, machine_code, func_info, nullptr,
|
||||||
|
code_execute_address_out, code_write_address_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
||||||
|
const EmitFunctionInfo& func_info,
|
||||||
|
GuestFunction* function_info,
|
||||||
|
void*& code_execute_address_out,
|
||||||
|
void*& code_write_address_out) {
|
||||||
|
using namespace xe::literals;
|
||||||
|
uint8_t* code_execute_address;
|
||||||
|
{
|
||||||
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
|
code_execute_address =
|
||||||
|
generated_code_execute_base_ + generated_code_offset_;
|
||||||
|
code_execute_address_out = code_execute_address;
|
||||||
|
uint8_t* code_write_address =
|
||||||
|
generated_code_write_base_ + generated_code_offset_;
|
||||||
|
code_write_address_out = code_write_address;
|
||||||
|
generated_code_offset_ += xe::round_up(func_info.code_size.total, 16);
|
||||||
|
|
||||||
|
auto tail_write_address =
|
||||||
|
generated_code_write_base_ + generated_code_offset_;
|
||||||
|
|
||||||
|
auto unwind_reservation = self().RequestUnwindReservation(
|
||||||
|
generated_code_write_base_ + generated_code_offset_);
|
||||||
|
generated_code_offset_ += xe::round_up(unwind_reservation.data_size, 16);
|
||||||
|
|
||||||
|
auto end_write_address =
|
||||||
|
generated_code_write_base_ + generated_code_offset_;
|
||||||
|
|
||||||
|
size_t high_mark = generated_code_offset_;
|
||||||
|
|
||||||
|
generated_code_map_.emplace_back(
|
||||||
|
(uint64_t(code_execute_address - generated_code_execute_base_)
|
||||||
|
<< 32) |
|
||||||
|
generated_code_offset_,
|
||||||
|
function_info);
|
||||||
|
|
||||||
|
// Commit memory if needed.
|
||||||
|
EnsureCommitted(high_mark);
|
||||||
|
|
||||||
|
// Copy code.
|
||||||
|
std::memcpy(code_write_address, machine_code, func_info.code_size.total);
|
||||||
|
|
||||||
|
// Fill unused tail/unwind gap with arch-specific trap instructions.
|
||||||
|
self().FillCode(
|
||||||
|
tail_write_address,
|
||||||
|
static_cast<size_t>(end_write_address - tail_write_address));
|
||||||
|
|
||||||
|
// Flush I-cache for code and fill regions.
|
||||||
|
self().FlushCodeRange(code_write_address, func_info.code_size.total);
|
||||||
|
if (tail_write_address < end_write_address) {
|
||||||
|
self().FlushCodeRange(
|
||||||
|
tail_write_address,
|
||||||
|
static_cast<size_t>(end_write_address - tail_write_address));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform-specific unwind registration.
|
||||||
|
self().PlaceCode(guest_address, machine_code, func_info,
|
||||||
|
code_execute_address, unwind_reservation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post-placement hook (e.g. VTune notification).
|
||||||
|
self().OnCodePlaced(guest_address, function_info, code_execute_address,
|
||||||
|
func_info.code_size.total);
|
||||||
|
|
||||||
|
// Fix up indirection table.
|
||||||
|
if (guest_address && indirection_table_base_) {
|
||||||
|
uint32_t* indirection_slot = reinterpret_cast<uint32_t*>(
|
||||||
|
indirection_table_base_ + (guest_address - kIndirectionTableBase));
|
||||||
|
*indirection_slot =
|
||||||
|
uint32_t(reinterpret_cast<uint64_t>(code_execute_address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t PlaceData(const void* data, size_t length) {
|
||||||
|
size_t high_mark;
|
||||||
|
uint8_t* data_address = nullptr;
|
||||||
|
{
|
||||||
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
data_address = generated_code_write_base_ + generated_code_offset_;
|
||||||
|
generated_code_offset_ += xe::round_up(length, 16);
|
||||||
|
high_mark = generated_code_offset_;
|
||||||
|
}
|
||||||
|
EnsureCommitted(high_mark);
|
||||||
|
std::memcpy(data_address, data, length);
|
||||||
|
return uint32_t(uintptr_t(data_address));
|
||||||
|
}
|
||||||
|
|
||||||
|
GuestFunction* LookupFunction(uint64_t host_pc) override {
|
||||||
|
uint32_t key = uint32_t(host_pc - kGeneratedCodeExecuteBase);
|
||||||
|
void* fn_entry = std::bsearch(
|
||||||
|
&key, generated_code_map_.data(), generated_code_map_.size(),
|
||||||
|
sizeof(std::pair<uint32_t, Function*>),
|
||||||
|
[](const void* key_ptr, const void* element_ptr) {
|
||||||
|
auto key = *reinterpret_cast<const uint32_t*>(key_ptr);
|
||||||
|
auto element =
|
||||||
|
reinterpret_cast<const std::pair<uint64_t, GuestFunction*>*>(
|
||||||
|
element_ptr);
|
||||||
|
if (key < (element->first >> 32)) {
|
||||||
|
return -1;
|
||||||
|
} else if (key > uint32_t(element->first)) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (fn_entry) {
|
||||||
|
return reinterpret_cast<const std::pair<uint64_t, GuestFunction*>*>(
|
||||||
|
fn_entry)
|
||||||
|
->second;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static constexpr size_t kIndirectionTableSize = 0x1FFFFFFF;
|
||||||
|
static constexpr uintptr_t kIndirectionTableBase = 0x80000000;
|
||||||
|
static constexpr size_t kGeneratedCodeSize = 0x0FFFFFFF;
|
||||||
|
static constexpr uintptr_t kGeneratedCodeExecuteBase = 0xA0000000;
|
||||||
|
static const uintptr_t kGeneratedCodeWriteBase =
|
||||||
|
kGeneratedCodeExecuteBase + kGeneratedCodeSize + 1;
|
||||||
|
static constexpr size_t kMaximumFunctionCount = 1000000;
|
||||||
|
|
||||||
|
struct UnwindReservation {
|
||||||
|
size_t data_size = 0;
|
||||||
|
size_t table_slot = 0;
|
||||||
|
uint8_t* entry_address = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeCacheBase() = default;
|
||||||
|
|
||||||
|
bool Initialize() {
|
||||||
|
indirection_table_base_ = reinterpret_cast<uint8_t*>(xe::memory::AllocFixed(
|
||||||
|
reinterpret_cast<void*>(kIndirectionTableBase), kIndirectionTableSize,
|
||||||
|
xe::memory::AllocationType::kReserve,
|
||||||
|
xe::memory::PageAccess::kReadWrite));
|
||||||
|
if (!indirection_table_base_) {
|
||||||
|
XELOGE("Unable to allocate code cache indirection table");
|
||||||
|
XELOGE(
|
||||||
|
"This is likely because the {:X}-{:X} range is in use by some "
|
||||||
|
"other system DLL",
|
||||||
|
static_cast<uint64_t>(kIndirectionTableBase),
|
||||||
|
kIndirectionTableBase + kIndirectionTableSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_name_ =
|
||||||
|
fmt::format("xenia_code_cache_{}", Clock::QueryHostTickCount());
|
||||||
|
mapping_ = xe::memory::CreateFileMappingHandle(
|
||||||
|
file_name_, kGeneratedCodeSize,
|
||||||
|
xe::memory::PageAccess::kExecuteReadWrite, false);
|
||||||
|
if (mapping_ == xe::memory::kFileMappingHandleInvalid) {
|
||||||
|
XELOGE("Unable to create code cache mmap");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xe::memory::IsWritableExecutableMemoryPreferred()) {
|
||||||
|
generated_code_execute_base_ =
|
||||||
|
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
|
||||||
|
mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
|
||||||
|
kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite,
|
||||||
|
0));
|
||||||
|
generated_code_write_base_ = generated_code_execute_base_;
|
||||||
|
if (!generated_code_execute_base_ || !generated_code_write_base_) {
|
||||||
|
XELOGE("Unable to allocate code cache generated code storage");
|
||||||
|
XELOGE(
|
||||||
|
"This is likely because the {:X}-{:X} range is in use by some "
|
||||||
|
"other system DLL",
|
||||||
|
uint64_t(kGeneratedCodeExecuteBase),
|
||||||
|
uint64_t(kGeneratedCodeExecuteBase + kGeneratedCodeSize));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
generated_code_execute_base_ =
|
||||||
|
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
|
||||||
|
mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
|
||||||
|
kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadOnly, 0));
|
||||||
|
generated_code_write_base_ =
|
||||||
|
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
|
||||||
|
mapping_, reinterpret_cast<void*>(kGeneratedCodeWriteBase),
|
||||||
|
kGeneratedCodeSize, xe::memory::PageAccess::kReadWrite, 0));
|
||||||
|
if (!generated_code_execute_base_ || !generated_code_write_base_) {
|
||||||
|
XELOGE("Unable to allocate code cache generated code storage");
|
||||||
|
XELOGE(
|
||||||
|
"This is likely because the {:X}-{:X} and {:X}-{:X} ranges are "
|
||||||
|
"in use by some other system DLL",
|
||||||
|
uint64_t(kGeneratedCodeExecuteBase),
|
||||||
|
uint64_t(kGeneratedCodeExecuteBase + kGeneratedCodeSize),
|
||||||
|
uint64_t(kGeneratedCodeWriteBase),
|
||||||
|
uint64_t(kGeneratedCodeWriteBase + kGeneratedCodeSize));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generated_code_map_.reserve(kMaximumFunctionCount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default no-op for the OnCodePlaced hook.
|
||||||
|
void OnCodePlaced(uint32_t guest_address, GuestFunction* function_info,
|
||||||
|
void* code_execute_address, size_t code_size) {}
|
||||||
|
|
||||||
|
std::filesystem::path file_name_;
|
||||||
|
xe::memory::FileMappingHandle mapping_ =
|
||||||
|
xe::memory::kFileMappingHandleInvalid;
|
||||||
|
xe::global_critical_region global_critical_region_;
|
||||||
|
uint32_t indirection_default_value_ = 0xFEEDF00D;
|
||||||
|
uint8_t* indirection_table_base_ = nullptr;
|
||||||
|
uint8_t* generated_code_execute_base_ = nullptr;
|
||||||
|
uint8_t* generated_code_write_base_ = nullptr;
|
||||||
|
size_t generated_code_offset_ = 0;
|
||||||
|
std::atomic<size_t> generated_code_commit_mark_ = {0};
|
||||||
|
std::vector<std::pair<uint64_t, GuestFunction*>> generated_code_map_;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Derived& self() { return static_cast<Derived&>(*this); }
|
||||||
|
|
||||||
|
void EnsureCommitted(size_t high_mark) {
|
||||||
|
using namespace xe::literals;
|
||||||
|
size_t old_commit_mark, new_commit_mark;
|
||||||
|
do {
|
||||||
|
old_commit_mark = generated_code_commit_mark_;
|
||||||
|
if (high_mark <= old_commit_mark) break;
|
||||||
|
new_commit_mark = old_commit_mark + 16_MiB;
|
||||||
|
if (generated_code_execute_base_ == generated_code_write_base_) {
|
||||||
|
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
||||||
|
xe::memory::AllocationType::kCommit,
|
||||||
|
xe::memory::PageAccess::kExecuteReadWrite);
|
||||||
|
} else {
|
||||||
|
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
||||||
|
xe::memory::AllocationType::kCommit,
|
||||||
|
xe::memory::PageAccess::kExecuteReadOnly);
|
||||||
|
xe::memory::AllocFixed(generated_code_write_base_, new_commit_mark,
|
||||||
|
xe::memory::AllocationType::kCommit,
|
||||||
|
xe::memory::PageAccess::kReadWrite);
|
||||||
|
}
|
||||||
|
} while (generated_code_commit_mark_.compare_exchange_weak(
|
||||||
|
old_commit_mark, new_commit_mark));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace backend
|
||||||
|
} // namespace cpu
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_CPU_BACKEND_CODE_CACHE_BASE_H_
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
#include "xenia/cpu/backend/x64/x64_code_cache.h"
|
#include "xenia/cpu/backend/x64/x64_code_cache.h"
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#if ENABLE_VTUNE
|
#if ENABLE_VTUNE
|
||||||
@@ -17,254 +16,36 @@
|
|||||||
#pragma comment(lib, "../third_party/vtune/lib64/jitprofiling.lib")
|
#pragma comment(lib, "../third_party/vtune/lib64/jitprofiling.lib")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if ENABLE_VTUNE
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
#include "xenia/base/assert.h"
|
|
||||||
#include "xenia/base/clock.h"
|
|
||||||
#include "xenia/base/literals.h"
|
|
||||||
#include "xenia/base/logging.h"
|
|
||||||
#include "xenia/base/math.h"
|
|
||||||
#include "xenia/base/memory.h"
|
|
||||||
#include "xenia/cpu/function.h"
|
|
||||||
#include "xenia/cpu/module.h"
|
#include "xenia/cpu/module.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
namespace backend {
|
namespace backend {
|
||||||
namespace x64 {
|
namespace x64 {
|
||||||
|
|
||||||
using namespace xe::literals;
|
bool X64CodeCache::Initialize() { return CodeCacheBase::Initialize(); }
|
||||||
|
|
||||||
X64CodeCache::X64CodeCache() = default;
|
void X64CodeCache::FillCode(void* write_address, size_t size) {
|
||||||
|
std::memset(write_address, 0xCC, size);
|
||||||
X64CodeCache::~X64CodeCache() {
|
|
||||||
if (indirection_table_base_) {
|
|
||||||
xe::memory::DeallocFixed(indirection_table_base_, kIndirectionTableSize,
|
|
||||||
xe::memory::DeallocationType::kRelease);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmap all views and close mapping.
|
|
||||||
if (mapping_ != xe::memory::kFileMappingHandleInvalid) {
|
|
||||||
if (generated_code_write_base_ &&
|
|
||||||
generated_code_write_base_ != generated_code_execute_base_) {
|
|
||||||
xe::memory::UnmapFileView(mapping_, generated_code_write_base_,
|
|
||||||
kGeneratedCodeSize);
|
|
||||||
}
|
|
||||||
if (generated_code_execute_base_) {
|
|
||||||
xe::memory::UnmapFileView(mapping_, generated_code_execute_base_,
|
|
||||||
kGeneratedCodeSize);
|
|
||||||
}
|
|
||||||
xe::memory::CloseFileMappingHandle(mapping_, file_name_);
|
|
||||||
mapping_ = xe::memory::kFileMappingHandleInvalid;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool X64CodeCache::Initialize() {
|
void X64CodeCache::FlushCodeRange(void* address, size_t size) {
|
||||||
indirection_table_base_ = reinterpret_cast<uint8_t*>(xe::memory::AllocFixed(
|
// x86-64 has coherent I/D caches; no flush needed.
|
||||||
reinterpret_cast<void*>(kIndirectionTableBase), kIndirectionTableSize,
|
|
||||||
xe::memory::AllocationType::kReserve,
|
|
||||||
xe::memory::PageAccess::kReadWrite));
|
|
||||||
if (!indirection_table_base_) {
|
|
||||||
XELOGE("Unable to allocate code cache indirection table");
|
|
||||||
XELOGE(
|
|
||||||
"This is likely because the {:X}-{:X} range is in use by some other "
|
|
||||||
"system DLL",
|
|
||||||
static_cast<uint64_t>(kIndirectionTableBase),
|
|
||||||
kIndirectionTableBase + kIndirectionTableSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create mmap file. This allows us to share the code cache with the debugger.
|
|
||||||
file_name_ = fmt::format("xenia_code_cache_{}", Clock::QueryHostTickCount());
|
|
||||||
mapping_ = xe::memory::CreateFileMappingHandle(
|
|
||||||
file_name_, kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite,
|
|
||||||
false);
|
|
||||||
if (mapping_ == xe::memory::kFileMappingHandleInvalid) {
|
|
||||||
XELOGE("Unable to create code cache mmap");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map generated code region into the file. Pages are committed as required.
|
|
||||||
if (xe::memory::IsWritableExecutableMemoryPreferred()) {
|
|
||||||
generated_code_execute_base_ =
|
|
||||||
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
|
|
||||||
mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
|
|
||||||
kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadWrite, 0));
|
|
||||||
generated_code_write_base_ = generated_code_execute_base_;
|
|
||||||
if (!generated_code_execute_base_ || !generated_code_write_base_) {
|
|
||||||
XELOGE("Unable to allocate code cache generated code storage");
|
|
||||||
XELOGE(
|
|
||||||
"This is likely because the {:X}-{:X} range is in use by some other "
|
|
||||||
"system DLL",
|
|
||||||
uint64_t(kGeneratedCodeExecuteBase),
|
|
||||||
uint64_t(kGeneratedCodeExecuteBase + kGeneratedCodeSize));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
generated_code_execute_base_ =
|
|
||||||
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
|
|
||||||
mapping_, reinterpret_cast<void*>(kGeneratedCodeExecuteBase),
|
|
||||||
kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadOnly, 0));
|
|
||||||
generated_code_write_base_ =
|
|
||||||
reinterpret_cast<uint8_t*>(xe::memory::MapFileView(
|
|
||||||
mapping_, reinterpret_cast<void*>(kGeneratedCodeWriteBase),
|
|
||||||
kGeneratedCodeSize, xe::memory::PageAccess::kReadWrite, 0));
|
|
||||||
if (!generated_code_execute_base_ || !generated_code_write_base_) {
|
|
||||||
XELOGE("Unable to allocate code cache generated code storage");
|
|
||||||
XELOGE(
|
|
||||||
"This is likely because the {:X}-{:X} and {:X}-{:X} ranges are in "
|
|
||||||
"use by some other system DLL",
|
|
||||||
uint64_t(kGeneratedCodeExecuteBase),
|
|
||||||
uint64_t(kGeneratedCodeExecuteBase + kGeneratedCodeSize),
|
|
||||||
uint64_t(kGeneratedCodeWriteBase),
|
|
||||||
uint64_t(kGeneratedCodeWriteBase + kGeneratedCodeSize));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preallocate the function map to a large, reasonable size.
|
|
||||||
generated_code_map_.reserve(kMaximumFunctionCount);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void X64CodeCache::set_indirection_default(uint32_t default_value) {
|
void X64CodeCache::OnCodePlaced(uint32_t guest_address,
|
||||||
indirection_default_value_ = default_value;
|
GuestFunction* function_info,
|
||||||
}
|
void* code_execute_address, size_t code_size) {
|
||||||
|
|
||||||
void X64CodeCache::AddIndirection(uint32_t guest_address,
|
|
||||||
uint32_t host_address) {
|
|
||||||
if (!indirection_table_base_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t* indirection_slot = reinterpret_cast<uint32_t*>(
|
|
||||||
indirection_table_base_ + (guest_address - kIndirectionTableBase));
|
|
||||||
*indirection_slot = host_address;
|
|
||||||
}
|
|
||||||
|
|
||||||
void X64CodeCache::CommitExecutableRange(uint32_t guest_low,
|
|
||||||
uint32_t guest_high) {
|
|
||||||
if (!indirection_table_base_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit the memory.
|
|
||||||
xe::memory::AllocFixed(
|
|
||||||
indirection_table_base_ + (guest_low - kIndirectionTableBase),
|
|
||||||
guest_high - guest_low, xe::memory::AllocationType::kCommit,
|
|
||||||
xe::memory::PageAccess::kReadWrite);
|
|
||||||
|
|
||||||
// Fill memory with the default value.
|
|
||||||
uint32_t* p = reinterpret_cast<uint32_t*>(indirection_table_base_);
|
|
||||||
for (uint32_t address = guest_low; address < guest_high; ++address) {
|
|
||||||
p[(address - kIndirectionTableBase) / 4] = indirection_default_value_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void X64CodeCache::PlaceHostCode(uint32_t guest_address, void* machine_code,
|
|
||||||
const EmitFunctionInfo& func_info,
|
|
||||||
void*& code_execute_address_out,
|
|
||||||
void*& code_write_address_out) {
|
|
||||||
// Same for now. We may use different pools or whatnot later on, like when
|
|
||||||
// we only want to place guest code in a serialized cache on disk.
|
|
||||||
PlaceGuestCode(guest_address, machine_code, func_info, nullptr,
|
|
||||||
code_execute_address_out, code_write_address_out);
|
|
||||||
}
|
|
||||||
|
|
||||||
void X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
|
||||||
const EmitFunctionInfo& func_info,
|
|
||||||
GuestFunction* function_info,
|
|
||||||
void*& code_execute_address_out,
|
|
||||||
void*& code_write_address_out) {
|
|
||||||
// Hold a lock while we bump the pointers up. This is important as the
|
|
||||||
// unwind table requires entries AND code to be sorted in order.
|
|
||||||
size_t low_mark;
|
|
||||||
size_t high_mark;
|
|
||||||
uint8_t* code_execute_address;
|
|
||||||
UnwindReservation unwind_reservation;
|
|
||||||
{
|
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
low_mark = generated_code_offset_;
|
|
||||||
|
|
||||||
// Reserve code.
|
|
||||||
// Always move the code to land on 16b alignment.
|
|
||||||
code_execute_address =
|
|
||||||
generated_code_execute_base_ + generated_code_offset_;
|
|
||||||
code_execute_address_out = code_execute_address;
|
|
||||||
uint8_t* code_write_address =
|
|
||||||
generated_code_write_base_ + generated_code_offset_;
|
|
||||||
code_write_address_out = code_write_address;
|
|
||||||
generated_code_offset_ += xe::round_up(func_info.code_size.total, 16);
|
|
||||||
|
|
||||||
auto tail_write_address =
|
|
||||||
generated_code_write_base_ + generated_code_offset_;
|
|
||||||
|
|
||||||
// Reserve unwind info.
|
|
||||||
// We go on the high size of the unwind info as we don't know how big we
|
|
||||||
// need it, and a few extra bytes of padding isn't the worst thing.
|
|
||||||
unwind_reservation = RequestUnwindReservation(generated_code_write_base_ +
|
|
||||||
generated_code_offset_);
|
|
||||||
generated_code_offset_ += xe::round_up(unwind_reservation.data_size, 16);
|
|
||||||
|
|
||||||
auto end_write_address =
|
|
||||||
generated_code_write_base_ + generated_code_offset_;
|
|
||||||
|
|
||||||
high_mark = generated_code_offset_;
|
|
||||||
|
|
||||||
// Store in map. It is maintained in sorted order of host PC dependent on
|
|
||||||
// us also being append-only.
|
|
||||||
generated_code_map_.emplace_back(
|
|
||||||
(uint64_t(code_execute_address - generated_code_execute_base_) << 32) |
|
|
||||||
generated_code_offset_,
|
|
||||||
function_info);
|
|
||||||
|
|
||||||
// TODO(DrChat): The following code doesn't really need to be under the
|
|
||||||
// global lock except for PlaceCode (but it depends on the previous code
|
|
||||||
// already being ran)
|
|
||||||
|
|
||||||
// If we are going above the high water mark of committed memory, commit
|
|
||||||
// some more. It's ok if multiple threads do this, as redundant commits
|
|
||||||
// aren't harmful.
|
|
||||||
size_t old_commit_mark, new_commit_mark;
|
|
||||||
do {
|
|
||||||
old_commit_mark = generated_code_commit_mark_;
|
|
||||||
if (high_mark <= old_commit_mark) break;
|
|
||||||
|
|
||||||
new_commit_mark = old_commit_mark + 16_MiB;
|
|
||||||
if (generated_code_execute_base_ == generated_code_write_base_) {
|
|
||||||
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
|
||||||
xe::memory::AllocationType::kCommit,
|
|
||||||
xe::memory::PageAccess::kExecuteReadWrite);
|
|
||||||
} else {
|
|
||||||
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
|
||||||
xe::memory::AllocationType::kCommit,
|
|
||||||
xe::memory::PageAccess::kExecuteReadOnly);
|
|
||||||
xe::memory::AllocFixed(generated_code_write_base_, new_commit_mark,
|
|
||||||
xe::memory::AllocationType::kCommit,
|
|
||||||
xe::memory::PageAccess::kReadWrite);
|
|
||||||
}
|
|
||||||
} while (generated_code_commit_mark_.compare_exchange_weak(
|
|
||||||
old_commit_mark, new_commit_mark));
|
|
||||||
|
|
||||||
// Copy code.
|
|
||||||
std::memcpy(code_write_address, machine_code, func_info.code_size.total);
|
|
||||||
|
|
||||||
// Fill unused slots with 0xCC
|
|
||||||
std::memset(tail_write_address, 0xCC,
|
|
||||||
static_cast<size_t>(end_write_address - tail_write_address));
|
|
||||||
|
|
||||||
// Notify subclasses of placed code.
|
|
||||||
PlaceCode(guest_address, machine_code, func_info, code_execute_address,
|
|
||||||
unwind_reservation);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if ENABLE_VTUNE
|
#if ENABLE_VTUNE
|
||||||
if (iJIT_IsProfilingActive() == iJIT_SAMPLING_ON) {
|
if (iJIT_IsProfilingActive() == iJIT_SAMPLING_ON) {
|
||||||
std::string method_name;
|
std::string method_name;
|
||||||
if (function_info && function_info->name().size() != 0) {
|
if (function_info && function_info->name().size() != 0) {
|
||||||
method_name = function_info->name();
|
method_name = function_info->name();
|
||||||
} else {
|
} else {
|
||||||
method_name = xe::format_string("sub_%.8X", guest_address);
|
method_name = fmt::format("sub_{:08X}", guest_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
iJIT_Method_Load_V2 method = {0};
|
iJIT_Method_Load_V2 method = {0};
|
||||||
@@ -278,88 +59,6 @@ void X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
|||||||
iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V2, (void*)&method);
|
iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED_V2, (void*)&method);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Now that everything is ready, fix up the indirection table.
|
|
||||||
// Note that we do support code that doesn't have an indirection fixup, so
|
|
||||||
// ignore those when we see them.
|
|
||||||
if (guest_address && indirection_table_base_) {
|
|
||||||
uint32_t* indirection_slot = reinterpret_cast<uint32_t*>(
|
|
||||||
indirection_table_base_ + (guest_address - kIndirectionTableBase));
|
|
||||||
*indirection_slot =
|
|
||||||
uint32_t(reinterpret_cast<uint64_t>(code_execute_address));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
|
|
||||||
// Hold a lock while we bump the pointers up.
|
|
||||||
size_t high_mark;
|
|
||||||
uint8_t* data_address = nullptr;
|
|
||||||
{
|
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
|
||||||
|
|
||||||
// Reserve code.
|
|
||||||
// Always move the code to land on 16b alignment.
|
|
||||||
data_address = generated_code_write_base_ + generated_code_offset_;
|
|
||||||
generated_code_offset_ += xe::round_up(length, 16);
|
|
||||||
|
|
||||||
high_mark = generated_code_offset_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are going above the high water mark of committed memory, commit some
|
|
||||||
// more. It's ok if multiple threads do this, as redundant commits aren't
|
|
||||||
// harmful.
|
|
||||||
size_t old_commit_mark, new_commit_mark;
|
|
||||||
do {
|
|
||||||
old_commit_mark = generated_code_commit_mark_;
|
|
||||||
if (high_mark <= old_commit_mark) break;
|
|
||||||
|
|
||||||
new_commit_mark = old_commit_mark + 16_MiB;
|
|
||||||
if (generated_code_execute_base_ == generated_code_write_base_) {
|
|
||||||
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
|
||||||
xe::memory::AllocationType::kCommit,
|
|
||||||
xe::memory::PageAccess::kExecuteReadWrite);
|
|
||||||
} else {
|
|
||||||
xe::memory::AllocFixed(generated_code_execute_base_, new_commit_mark,
|
|
||||||
xe::memory::AllocationType::kCommit,
|
|
||||||
xe::memory::PageAccess::kExecuteReadOnly);
|
|
||||||
xe::memory::AllocFixed(generated_code_write_base_, new_commit_mark,
|
|
||||||
xe::memory::AllocationType::kCommit,
|
|
||||||
xe::memory::PageAccess::kReadWrite);
|
|
||||||
}
|
|
||||||
} while (generated_code_commit_mark_.compare_exchange_weak(old_commit_mark,
|
|
||||||
new_commit_mark));
|
|
||||||
|
|
||||||
// Copy code.
|
|
||||||
std::memcpy(data_address, data, length);
|
|
||||||
|
|
||||||
return uint32_t(uintptr_t(data_address));
|
|
||||||
}
|
|
||||||
|
|
||||||
GuestFunction* X64CodeCache::LookupFunction(uint64_t host_pc) {
|
|
||||||
uint32_t key = uint32_t(host_pc - kGeneratedCodeExecuteBase);
|
|
||||||
void* fn_entry = std::bsearch(
|
|
||||||
&key, generated_code_map_.data(), generated_code_map_.size(),
|
|
||||||
sizeof(std::pair<uint32_t, Function*>),
|
|
||||||
[](const void* key_ptr, const void* element_ptr) {
|
|
||||||
auto key = *reinterpret_cast<const uint32_t*>(key_ptr);
|
|
||||||
auto element =
|
|
||||||
reinterpret_cast<const std::pair<uint64_t, GuestFunction*>*>(
|
|
||||||
element_ptr);
|
|
||||||
if (key < (element->first >> 32)) {
|
|
||||||
return -1;
|
|
||||||
} else if (key > uint32_t(element->first)) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (fn_entry) {
|
|
||||||
return reinterpret_cast<const std::pair<uint64_t, GuestFunction*>*>(
|
|
||||||
fn_entry)
|
|
||||||
->second;
|
|
||||||
} else {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace x64
|
} // namespace x64
|
||||||
|
|||||||
@@ -10,101 +10,32 @@
|
|||||||
#ifndef XENIA_CPU_BACKEND_X64_X64_CODE_CACHE_H_
|
#ifndef XENIA_CPU_BACKEND_X64_X64_CODE_CACHE_H_
|
||||||
#define XENIA_CPU_BACKEND_X64_X64_CODE_CACHE_H_
|
#define XENIA_CPU_BACKEND_X64_X64_CODE_CACHE_H_
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/cpu/backend/code_cache_base.h"
|
||||||
#include "xenia/base/mutex.h"
|
|
||||||
#include "xenia/cpu/backend/code_cache.h"
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
namespace backend {
|
namespace backend {
|
||||||
namespace x64 {
|
namespace x64 {
|
||||||
|
|
||||||
struct EmitFunctionInfo {
|
class X64CodeCache : public CodeCacheBase<X64CodeCache> {
|
||||||
struct _code_size {
|
|
||||||
size_t prolog;
|
|
||||||
size_t body;
|
|
||||||
size_t epilog;
|
|
||||||
size_t tail;
|
|
||||||
size_t total;
|
|
||||||
} code_size;
|
|
||||||
size_t prolog_stack_alloc_offset; // offset of instruction after stack alloc
|
|
||||||
size_t stack_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
class X64CodeCache : public CodeCache {
|
|
||||||
public:
|
public:
|
||||||
~X64CodeCache() override;
|
~X64CodeCache() override = default;
|
||||||
|
|
||||||
static std::unique_ptr<X64CodeCache> Create();
|
static std::unique_ptr<X64CodeCache> Create();
|
||||||
|
|
||||||
virtual bool Initialize();
|
virtual bool Initialize();
|
||||||
|
|
||||||
const std::filesystem::path& file_name() const override { return file_name_; }
|
void* LookupUnwindInfo(uint64_t host_pc) override { return nullptr; }
|
||||||
uintptr_t execute_base_address() const override {
|
|
||||||
return kGeneratedCodeExecuteBase;
|
|
||||||
}
|
|
||||||
size_t total_size() const override { return kGeneratedCodeSize; }
|
|
||||||
|
|
||||||
// TODO(benvanik): ELF serialization/etc
|
// CRTP hooks for CodeCacheBase.
|
||||||
// TODO(benvanik): keep track of code blocks
|
void FillCode(void* write_address, size_t size);
|
||||||
// TODO(benvanik): padding/guards/etc
|
void FlushCodeRange(void* address, size_t size);
|
||||||
|
void OnCodePlaced(uint32_t guest_address, GuestFunction* function_info,
|
||||||
bool has_indirection_table() { return indirection_table_base_ != nullptr; }
|
void* code_execute_address, size_t code_size);
|
||||||
void set_indirection_default(uint32_t default_value);
|
|
||||||
void AddIndirection(uint32_t guest_address, uint32_t host_address);
|
|
||||||
|
|
||||||
void CommitExecutableRange(uint32_t guest_low, uint32_t guest_high);
|
|
||||||
|
|
||||||
void PlaceHostCode(uint32_t guest_address, void* machine_code,
|
|
||||||
const EmitFunctionInfo& func_info,
|
|
||||||
void*& code_execute_address_out,
|
|
||||||
void*& code_write_address_out);
|
|
||||||
void PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
|
||||||
const EmitFunctionInfo& func_info,
|
|
||||||
GuestFunction* function_info,
|
|
||||||
void*& code_execute_address_out,
|
|
||||||
void*& code_write_address_out);
|
|
||||||
uint32_t PlaceData(const void* data, size_t length);
|
|
||||||
|
|
||||||
GuestFunction* LookupFunction(uint64_t host_pc) override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// All executable code falls within 0x80000000 to 0x9FFFFFFF, so we can
|
|
||||||
// only map enough for lookups within that range.
|
|
||||||
static constexpr size_t kIndirectionTableSize = 0x1FFFFFFF;
|
|
||||||
static constexpr uintptr_t kIndirectionTableBase = 0x80000000;
|
|
||||||
// The code range is 512MB, but we know the total code games will have is
|
|
||||||
// pretty small (dozens of mb at most) and our expansion is reasonablish
|
|
||||||
// so 256MB should be more than enough.
|
|
||||||
static constexpr size_t kGeneratedCodeSize = 0x0FFFFFFF;
|
|
||||||
static constexpr uintptr_t kGeneratedCodeExecuteBase = 0xA0000000;
|
|
||||||
// Used for writing when PageAccess::kExecuteReadWrite is not supported.
|
|
||||||
static const uintptr_t kGeneratedCodeWriteBase =
|
|
||||||
kGeneratedCodeExecuteBase + kGeneratedCodeSize + 1;
|
|
||||||
|
|
||||||
// This is picked to be high enough to cover whatever we can reasonably
|
|
||||||
// expect. If we hit issues with this it probably means some corner case
|
|
||||||
// in analysis triggering.
|
|
||||||
// chrispy: raised this, some games that were compiled with low optimization
|
|
||||||
// levels can exceed this
|
|
||||||
static constexpr size_t kMaximumFunctionCount = 1000000;
|
|
||||||
|
|
||||||
struct UnwindReservation {
|
|
||||||
size_t data_size = 0;
|
|
||||||
size_t table_slot = 0;
|
|
||||||
uint8_t* entry_address = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
X64CodeCache();
|
|
||||||
|
|
||||||
|
// Virtual for platform-specific overrides (_win.cc / _posix.cc).
|
||||||
virtual UnwindReservation RequestUnwindReservation(uint8_t* entry_address) {
|
virtual UnwindReservation RequestUnwindReservation(uint8_t* entry_address) {
|
||||||
return UnwindReservation();
|
return UnwindReservation();
|
||||||
}
|
}
|
||||||
@@ -113,36 +44,8 @@ class X64CodeCache : public CodeCache {
|
|||||||
void* code_execute_address,
|
void* code_execute_address,
|
||||||
UnwindReservation unwind_reservation) {}
|
UnwindReservation unwind_reservation) {}
|
||||||
|
|
||||||
std::filesystem::path file_name_;
|
protected:
|
||||||
xe::memory::FileMappingHandle mapping_ =
|
X64CodeCache() = default;
|
||||||
xe::memory::kFileMappingHandleInvalid;
|
|
||||||
|
|
||||||
// NOTE: the global critical region must be held when manipulating the offsets
|
|
||||||
// or counts of anything, to keep the tables consistent and ordered.
|
|
||||||
xe::global_critical_region global_critical_region_;
|
|
||||||
|
|
||||||
// Value that the indirection table will be initialized with upon commit.
|
|
||||||
uint32_t indirection_default_value_ = 0xFEEDF00D;
|
|
||||||
|
|
||||||
// Fixed at kIndirectionTableBase in host space, holding 4 byte pointers into
|
|
||||||
// the generated code table that correspond to the PPC functions in guest
|
|
||||||
// space.
|
|
||||||
uint8_t* indirection_table_base_ = nullptr;
|
|
||||||
// Fixed at kGeneratedCodeExecuteBase and holding all generated code, growing
|
|
||||||
// as needed.
|
|
||||||
uint8_t* generated_code_execute_base_ = nullptr;
|
|
||||||
// View of the memory that backs generated_code_execute_base_ when
|
|
||||||
// PageAccess::kExecuteReadWrite is not supported, for writing the generated
|
|
||||||
// code. Equals to generated_code_execute_base_ when it's supported.
|
|
||||||
uint8_t* generated_code_write_base_ = nullptr;
|
|
||||||
// Current offset to empty space in generated code.
|
|
||||||
size_t generated_code_offset_ = 0;
|
|
||||||
// Current high water mark of COMMITTED code.
|
|
||||||
std::atomic<size_t> generated_code_commit_mark_ = {0};
|
|
||||||
// Sorted map by host PC base offsets to source function info.
|
|
||||||
// This can be used to bsearch on host PC to find the guest function.
|
|
||||||
// The key is [start address | end address].
|
|
||||||
std::vector<std::pair<uint64_t, GuestFunction*>> generated_code_map_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace x64
|
} // namespace x64
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/arena.h"
|
#include "xenia/base/arena.h"
|
||||||
|
#include "xenia/cpu/backend/code_cache_base.h"
|
||||||
#include "xenia/cpu/function.h"
|
#include "xenia/cpu/function.h"
|
||||||
#include "xenia/cpu/function_trace_data.h"
|
#include "xenia/cpu/function_trace_data.h"
|
||||||
#include "xenia/cpu/hir/hir_builder.h"
|
#include "xenia/cpu/hir/hir_builder.h"
|
||||||
@@ -38,8 +39,6 @@ using namespace amd64;
|
|||||||
class X64Backend;
|
class X64Backend;
|
||||||
class X64CodeCache;
|
class X64CodeCache;
|
||||||
|
|
||||||
struct EmitFunctionInfo;
|
|
||||||
|
|
||||||
enum RegisterFlags {
|
enum RegisterFlags {
|
||||||
REG_DEST = (1 << 0),
|
REG_DEST = (1 << 0),
|
||||||
REG_ABCD = (1 << 1),
|
REG_ABCD = (1 << 1),
|
||||||
|
|||||||
Reference in New Issue
Block a user