diff --git a/src/xenia/cpu/backend/code_cache_base.h b/src/xenia/cpu/backend/code_cache_base.h new file mode 100644 index 000000000..e3e526998 --- /dev/null +++ b/src/xenia/cpu/backend/code_cache_base.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +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( + 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(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(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(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( + indirection_table_base_ + (guest_address - kIndirectionTableBase)); + *indirection_slot = + uint32_t(reinterpret_cast(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), + [](const void* key_ptr, const void* element_ptr) { + auto key = *reinterpret_cast(key_ptr); + auto element = + reinterpret_cast*>( + 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*>( + 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(xe::memory::AllocFixed( + reinterpret_cast(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(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(xe::memory::MapFileView( + mapping_, reinterpret_cast(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(xe::memory::MapFileView( + mapping_, reinterpret_cast(kGeneratedCodeExecuteBase), + kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadOnly, 0)); + generated_code_write_base_ = + reinterpret_cast(xe::memory::MapFileView( + mapping_, reinterpret_cast(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 generated_code_commit_mark_ = {0}; + std::vector> generated_code_map_; + + private: + Derived& self() { return static_cast(*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_ diff --git a/src/xenia/cpu/backend/x64/x64_code_cache.cc b/src/xenia/cpu/backend/x64/x64_code_cache.cc index c386d15ca..24defe124 100644 --- a/src/xenia/cpu/backend/x64/x64_code_cache.cc +++ b/src/xenia/cpu/backend/x64/x64_code_cache.cc @@ -9,7 +9,6 @@ #include "xenia/cpu/backend/x64/x64_code_cache.h" -#include #include #if ENABLE_VTUNE @@ -17,254 +16,36 @@ #pragma comment(lib, "../third_party/vtune/lib64/jitprofiling.lib") #endif +#if ENABLE_VTUNE #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" +#endif namespace xe { namespace cpu { namespace backend { namespace x64 { -using namespace xe::literals; +bool X64CodeCache::Initialize() { return CodeCacheBase::Initialize(); } -X64CodeCache::X64CodeCache() = default; - -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; - } +void X64CodeCache::FillCode(void* write_address, size_t size) { + std::memset(write_address, 0xCC, size); } -bool X64CodeCache::Initialize() { - indirection_table_base_ = reinterpret_cast(xe::memory::AllocFixed( - reinterpret_cast(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(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(xe::memory::MapFileView( - mapping_, reinterpret_cast(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(xe::memory::MapFileView( - mapping_, reinterpret_cast(kGeneratedCodeExecuteBase), - kGeneratedCodeSize, xe::memory::PageAccess::kExecuteReadOnly, 0)); - generated_code_write_base_ = - reinterpret_cast(xe::memory::MapFileView( - mapping_, reinterpret_cast(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::FlushCodeRange(void* address, size_t size) { + // x86-64 has coherent I/D caches; no flush needed. } -void X64CodeCache::set_indirection_default(uint32_t default_value) { - indirection_default_value_ = default_value; -} - -void X64CodeCache::AddIndirection(uint32_t guest_address, - uint32_t host_address) { - if (!indirection_table_base_) { - return; - } - - uint32_t* indirection_slot = reinterpret_cast( - 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(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(end_write_address - tail_write_address)); - - // Notify subclasses of placed code. - PlaceCode(guest_address, machine_code, func_info, code_execute_address, - unwind_reservation); - } - +void X64CodeCache::OnCodePlaced(uint32_t guest_address, + GuestFunction* function_info, + void* code_execute_address, size_t code_size) { #if ENABLE_VTUNE if (iJIT_IsProfilingActive() == iJIT_SAMPLING_ON) { std::string method_name; if (function_info && function_info->name().size() != 0) { method_name = function_info->name(); } else { - method_name = xe::format_string("sub_%.8X", guest_address); + method_name = fmt::format("sub_{:08X}", guest_address); } 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); } #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( - indirection_table_base_ + (guest_address - kIndirectionTableBase)); - *indirection_slot = - uint32_t(reinterpret_cast(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), - [](const void* key_ptr, const void* element_ptr) { - auto key = *reinterpret_cast(key_ptr); - auto element = - reinterpret_cast*>( - 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*>( - fn_entry) - ->second; - } else { - return nullptr; - } } } // namespace x64 diff --git a/src/xenia/cpu/backend/x64/x64_code_cache.h b/src/xenia/cpu/backend/x64/x64_code_cache.h index a85fbf777..c1e971d27 100644 --- a/src/xenia/cpu/backend/x64/x64_code_cache.h +++ b/src/xenia/cpu/backend/x64/x64_code_cache.h @@ -10,101 +10,32 @@ #ifndef XENIA_CPU_BACKEND_X64_X64_CODE_CACHE_H_ #define XENIA_CPU_BACKEND_X64_X64_CODE_CACHE_H_ -#include -#include -#include #include -#include -#include -#include -#include "xenia/base/memory.h" -#include "xenia/base/mutex.h" -#include "xenia/cpu/backend/code_cache.h" +#include "xenia/cpu/backend/code_cache_base.h" namespace xe { namespace cpu { namespace backend { namespace x64 { -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; // offset of instruction after stack alloc - size_t stack_size; -}; - -class X64CodeCache : public CodeCache { +class X64CodeCache : public CodeCacheBase { public: - ~X64CodeCache() override; + ~X64CodeCache() override = default; static std::unique_ptr Create(); virtual bool Initialize(); - 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; } + void* LookupUnwindInfo(uint64_t host_pc) override { return nullptr; } - // TODO(benvanik): ELF serialization/etc - // TODO(benvanik): keep track of code blocks - // TODO(benvanik): padding/guards/etc - - bool has_indirection_table() { return indirection_table_base_ != nullptr; } - 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(); + // CRTP hooks for CodeCacheBase. + void FillCode(void* write_address, size_t size); + void FlushCodeRange(void* address, size_t size); + void OnCodePlaced(uint32_t guest_address, GuestFunction* function_info, + void* code_execute_address, size_t code_size); + // Virtual for platform-specific overrides (_win.cc / _posix.cc). virtual UnwindReservation RequestUnwindReservation(uint8_t* entry_address) { return UnwindReservation(); } @@ -113,36 +44,8 @@ class X64CodeCache : public CodeCache { void* code_execute_address, UnwindReservation unwind_reservation) {} - std::filesystem::path file_name_; - xe::memory::FileMappingHandle mapping_ = - 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 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> generated_code_map_; + protected: + X64CodeCache() = default; }; } // namespace x64 diff --git a/src/xenia/cpu/backend/x64/x64_emitter.h b/src/xenia/cpu/backend/x64/x64_emitter.h index 63d1a7b2a..ad29c9ced 100644 --- a/src/xenia/cpu/backend/x64/x64_emitter.h +++ b/src/xenia/cpu/backend/x64/x64_emitter.h @@ -13,6 +13,7 @@ #include #include "xenia/base/arena.h" +#include "xenia/cpu/backend/code_cache_base.h" #include "xenia/cpu/function.h" #include "xenia/cpu/function_trace_data.h" #include "xenia/cpu/hir/hir_builder.h" @@ -38,8 +39,6 @@ using namespace amd64; class X64Backend; class X64CodeCache; -struct EmitFunctionInfo; - enum RegisterFlags { REG_DEST = (1 << 0), REG_ABCD = (1 << 1),