mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-03 13:31:27 +12:00
PPU LLVM: paradigm shift
For now, compile only one block at time Use tail calls to move between blocks Fully write PPU context (except CIA) This fixes many compatibility problems
This commit is contained in:
parent
a29d7d3962
commit
aea094730b
13 changed files with 1076 additions and 856 deletions
|
@ -5,6 +5,7 @@
|
|||
#include <unordered_set>
|
||||
#include <set>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
|
||||
#include "types.h"
|
||||
#include "StrFmt.h"
|
||||
|
@ -20,6 +21,7 @@
|
|||
#include "llvm/ExecutionEngine/ExecutionEngine.h"
|
||||
#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"
|
||||
#include "llvm/ExecutionEngine/JITEventListener.h"
|
||||
#include "llvm/ExecutionEngine/ObjectCache.h"
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
@ -50,11 +52,13 @@ static void* const s_memory = []() -> void*
|
|||
return utils::memory_reserve(s_memory_size);
|
||||
}();
|
||||
|
||||
static void* s_next;
|
||||
|
||||
// Code section
|
||||
static u8* s_code_addr;
|
||||
static u64 s_code_size;
|
||||
|
||||
#ifdef _WIN32
|
||||
static std::deque<std::vector<RUNTIME_FUNCTION>> s_unwater;
|
||||
static std::vector<std::vector<RUNTIME_FUNCTION>> s_unwind; // .pdata
|
||||
#endif
|
||||
|
||||
|
@ -67,9 +71,9 @@ struct MemoryManager final : llvm::RTDyldMemoryManager
|
|||
|
||||
MemoryManager(std::unordered_map<std::string, std::uintptr_t>& table)
|
||||
: m_link(table)
|
||||
, m_next(s_memory)
|
||||
, m_tramps(nullptr)
|
||||
{
|
||||
s_next = s_memory;
|
||||
}
|
||||
|
||||
[[noreturn]] static void null()
|
||||
|
@ -77,7 +81,7 @@ struct MemoryManager final : llvm::RTDyldMemoryManager
|
|||
fmt::throw_exception("Null function" HERE);
|
||||
}
|
||||
|
||||
virtual u64 getSymbolAddress(const std::string& name) override
|
||||
llvm::JITSymbol findSymbol(const std::string& name) override
|
||||
{
|
||||
auto& addr = m_link[name];
|
||||
|
||||
|
@ -92,7 +96,6 @@ struct MemoryManager final : llvm::RTDyldMemoryManager
|
|||
}
|
||||
else
|
||||
{
|
||||
// It's fine if some function is never called, for example.
|
||||
LOG_ERROR(GENERAL, "LLVM: Linkage failed: %s", name);
|
||||
addr = (u64)null;
|
||||
}
|
||||
|
@ -104,9 +107,9 @@ struct MemoryManager final : llvm::RTDyldMemoryManager
|
|||
// Allocate memory for trampolines
|
||||
if (!m_tramps)
|
||||
{
|
||||
m_tramps = reinterpret_cast<decltype(m_tramps)>(m_next);
|
||||
utils::memory_commit(m_next, 4096, utils::protection::wx);
|
||||
m_next = (u8*)((u64)m_next + 4096);
|
||||
m_tramps = reinterpret_cast<decltype(m_tramps)>(s_next);
|
||||
utils::memory_commit(s_next, 4096, utils::protection::wx);
|
||||
s_next = (u8*)((u64)s_next + 4096);
|
||||
}
|
||||
|
||||
// Create a trampoline
|
||||
|
@ -129,13 +132,13 @@ struct MemoryManager final : llvm::RTDyldMemoryManager
|
|||
}
|
||||
}
|
||||
|
||||
return addr;
|
||||
return {addr, llvm::JITSymbolFlags::Exported};
|
||||
}
|
||||
|
||||
virtual u8* allocateCodeSection(std::uintptr_t size, uint align, uint sec_id, llvm::StringRef sec_name) override
|
||||
{
|
||||
// Simple allocation
|
||||
const u64 next = ::align((u64)m_next + size, 4096);
|
||||
const u64 next = ::align((u64)s_next + size, 4096);
|
||||
|
||||
if (next > (u64)s_memory + s_memory_size)
|
||||
{
|
||||
|
@ -143,18 +146,17 @@ struct MemoryManager final : llvm::RTDyldMemoryManager
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
utils::memory_commit(m_next, size, utils::protection::wx);
|
||||
s_code_addr = (u8*)m_next;
|
||||
s_code_size = size;
|
||||
utils::memory_commit(s_next, size, utils::protection::wx);
|
||||
s_code_addr = (u8*)s_next;
|
||||
|
||||
LOG_NOTICE(GENERAL, "LLVM: Code section %u '%s' allocated -> %p (size=0x%llx, aligned 0x%x)", sec_id, sec_name.data(), m_next, size, align);
|
||||
return (u8*)std::exchange(m_next, (void*)next);
|
||||
LOG_NOTICE(GENERAL, "LLVM: Code section %u '%s' allocated -> %p (size=0x%llx, aligned 0x%x)", sec_id, sec_name.data(), s_next, size, align);
|
||||
return (u8*)std::exchange(s_next, (void*)next);
|
||||
}
|
||||
|
||||
virtual u8* allocateDataSection(std::uintptr_t size, uint align, uint sec_id, llvm::StringRef sec_name, bool is_ro) override
|
||||
{
|
||||
// Simple allocation
|
||||
const u64 next = ::align((u64)m_next + size, 4096);
|
||||
const u64 next = ::align((u64)s_next + size, 4096);
|
||||
|
||||
if (next > (u64)s_memory + s_memory_size)
|
||||
{
|
||||
|
@ -167,10 +169,10 @@ struct MemoryManager final : llvm::RTDyldMemoryManager
|
|||
LOG_ERROR(GENERAL, "LLVM: Writeable data section not supported!");
|
||||
}
|
||||
|
||||
utils::memory_commit(m_next, size);
|
||||
utils::memory_commit(s_next, size);
|
||||
|
||||
LOG_NOTICE(GENERAL, "LLVM: Data section %u '%s' allocated -> %p (size=0x%llx, aligned 0x%x, %s)", sec_id, sec_name.data(), m_next, size, align, is_ro ? "ro" : "rw");
|
||||
return (u8*)std::exchange(m_next, (void*)next);
|
||||
LOG_NOTICE(GENERAL, "LLVM: Data section %u '%s' allocated -> %p (size=0x%llx, aligned 0x%x, %s)", sec_id, sec_name.data(), s_next, size, align, is_ro ? "ro" : "rw");
|
||||
return (u8*)std::exchange(s_next, (void*)next);
|
||||
}
|
||||
|
||||
virtual bool finalizeMemory(std::string* = nullptr) override
|
||||
|
@ -191,17 +193,15 @@ struct MemoryManager final : llvm::RTDyldMemoryManager
|
|||
{
|
||||
#ifdef _WIN32
|
||||
// Use s_memory as a BASE, compute the difference
|
||||
const u64 code_diff = (u64)s_code_addr - (u64)s_memory;
|
||||
const u64 unwind_diff = (u64)addr - (u64)s_memory;
|
||||
|
||||
// Fix RUNTIME_FUNCTION records (.pdata section)
|
||||
auto& pdata = s_unwind.back();
|
||||
auto pdata = std::move(s_unwater.front());
|
||||
s_unwater.pop_front();
|
||||
|
||||
for (auto& rf : pdata)
|
||||
{
|
||||
rf.BeginAddress += static_cast<DWORD>(code_diff);
|
||||
rf.EndAddress += static_cast<DWORD>(code_diff);
|
||||
rf.UnwindData += static_cast<DWORD>(unwind_diff);
|
||||
rf.UnwindData += static_cast<DWORD>(unwind_diff);
|
||||
}
|
||||
|
||||
// Register .xdata UNWIND_INFO structs
|
||||
|
@ -209,6 +209,10 @@ struct MemoryManager final : llvm::RTDyldMemoryManager
|
|||
{
|
||||
LOG_ERROR(GENERAL, "RtlAddFunctionTable() failed! Error %u", GetLastError());
|
||||
}
|
||||
else
|
||||
{
|
||||
s_unwind.emplace_back(std::move(pdata));
|
||||
}
|
||||
#endif
|
||||
|
||||
return RTDyldMemoryManager::registerEHFrames(addr, load_addr, size);
|
||||
|
@ -239,24 +243,13 @@ struct MemoryManager final : llvm::RTDyldMemoryManager
|
|||
|
||||
utils::memory_decommit(s_memory, s_memory_size);
|
||||
}
|
||||
|
||||
private:
|
||||
void* m_next;
|
||||
};
|
||||
|
||||
// Helper class
|
||||
struct EventListener final : llvm::JITEventListener
|
||||
{
|
||||
std::string path;
|
||||
|
||||
virtual void NotifyObjectEmitted(const llvm::object::ObjectFile& obj, const llvm::RuntimeDyld::LoadedObjectInfo& inf) override
|
||||
{
|
||||
if (!path.empty())
|
||||
{
|
||||
const llvm::StringRef elf = obj.getData();
|
||||
fs::file(path, fs::rewrite).write(elf.data(), elf.size());
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
for (auto it = obj.section_begin(), end = obj.section_end(); it != end; ++it)
|
||||
{
|
||||
|
@ -282,7 +275,17 @@ struct EventListener final : llvm::JITEventListener
|
|||
}
|
||||
}
|
||||
|
||||
s_unwind.emplace_back(std::move(rfs));
|
||||
// Use s_memory as a BASE, compute the difference
|
||||
const u64 code_diff = (u64)s_code_addr - (u64)s_memory;
|
||||
|
||||
// Fix RUNTIME_FUNCTION records (.pdata section)
|
||||
for (auto& rf : rfs)
|
||||
{
|
||||
rf.BeginAddress += static_cast<DWORD>(code_diff);
|
||||
rf.EndAddress += static_cast<DWORD>(code_diff);
|
||||
}
|
||||
|
||||
s_unwater.emplace_back(std::move(rfs));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -291,6 +294,46 @@ struct EventListener final : llvm::JITEventListener
|
|||
|
||||
static EventListener s_listener;
|
||||
|
||||
// Helper class
|
||||
class ObjectCache final : public llvm::ObjectCache
|
||||
{
|
||||
const std::string& m_path;
|
||||
|
||||
public:
|
||||
ObjectCache(const std::string& path)
|
||||
: m_path(path)
|
||||
{
|
||||
}
|
||||
|
||||
~ObjectCache() override = default;
|
||||
|
||||
void notifyObjectCompiled(const llvm::Module* module, llvm::MemoryBufferRef obj) override
|
||||
{
|
||||
std::string name = m_path;
|
||||
name.append(module->getName());
|
||||
fs::file(name, fs::rewrite).write(obj.getBufferStart(), obj.getBufferSize());
|
||||
LOG_SUCCESS(GENERAL, "LLVM: Created module: %s", module->getName().data());
|
||||
}
|
||||
|
||||
std::unique_ptr<llvm::MemoryBuffer> getObject(const llvm::Module* module) override
|
||||
{
|
||||
std::string name = m_path;
|
||||
name.append(module->getName());
|
||||
|
||||
if (fs::file cached{name, fs::read})
|
||||
{
|
||||
auto buf = llvm::MemoryBuffer::getNewUninitMemBuffer(cached.size());
|
||||
cached.read(const_cast<char*>(buf->getBufferStart()), buf->getBufferSize());
|
||||
LOG_SUCCESS(GENERAL, "LLVM: Loaded module: %s", module->getName().data());
|
||||
return buf;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
jit_compiler::jit_compiler(std::unordered_map<std::string, std::uintptr_t> init_linkage_info, std::string _cpu)
|
||||
: m_link(std::move(init_linkage_info))
|
||||
, m_cpu(std::move(_cpu))
|
||||
|
@ -321,58 +364,54 @@ jit_compiler::jit_compiler(std::unordered_map<std::string, std::uintptr_t> init_
|
|||
}
|
||||
|
||||
m_engine->RegisterJITEventListener(&s_listener);
|
||||
|
||||
LOG_SUCCESS(GENERAL, "LLVM: JIT initialized (%s)", m_cpu);
|
||||
}
|
||||
|
||||
void jit_compiler::load(std::unique_ptr<llvm::Module> module, std::unique_ptr<llvm::object::ObjectFile> object)
|
||||
void jit_compiler::add(std::unique_ptr<llvm::Module> module, const std::string& path)
|
||||
{
|
||||
s_listener.path.clear();
|
||||
|
||||
auto* module_ptr = module.get();
|
||||
ObjectCache cache{path};
|
||||
m_engine->setObjectCache(&cache);
|
||||
|
||||
const auto ptr = module.get();
|
||||
m_engine->addModule(std::move(module));
|
||||
m_engine->addObjectFile(std::move(object));
|
||||
m_engine->finalizeObject();
|
||||
m_engine->generateCodeForModule(ptr);
|
||||
m_engine->setObjectCache(nullptr);
|
||||
|
||||
m_map.clear();
|
||||
|
||||
for (auto& func : module_ptr->functions())
|
||||
for (auto& func : ptr->functions())
|
||||
{
|
||||
const std::string& name = func.getName();
|
||||
|
||||
if (!m_link.count(name))
|
||||
{
|
||||
// Register compiled function
|
||||
m_map[name] = m_engine->getFunctionAddress(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void jit_compiler::make(std::unique_ptr<llvm::Module> module, std::string path)
|
||||
{
|
||||
s_listener.path = std::move(path);
|
||||
|
||||
auto* module_ptr = module.get();
|
||||
|
||||
m_engine->addModule(std::move(module));
|
||||
m_engine->finalizeObject();
|
||||
|
||||
m_map.clear();
|
||||
|
||||
for (auto& func : module_ptr->functions())
|
||||
{
|
||||
if (!func.empty())
|
||||
{
|
||||
const std::string& name = func.getName();
|
||||
|
||||
// Register compiled function
|
||||
m_map[name] = m_engine->getFunctionAddress(name);
|
||||
}
|
||||
|
||||
// Delete IR to lower memory consumption
|
||||
func.deleteBody();
|
||||
}
|
||||
}
|
||||
|
||||
void jit_compiler::fin(const std::string& path)
|
||||
{
|
||||
m_engine->finalizeObject();
|
||||
}
|
||||
|
||||
void jit_compiler::add(std::unordered_map<std::string, std::string> data)
|
||||
{
|
||||
std::size_t size = 0;
|
||||
|
||||
for (auto&& pair : data)
|
||||
{
|
||||
size += ::align(pair.second.size(), 16);
|
||||
}
|
||||
|
||||
utils::memory_commit(s_next, size, utils::protection::wx);
|
||||
std::memset(s_next, 0xc3, ::align(size, 4096));
|
||||
|
||||
for (auto&& pair : data)
|
||||
{
|
||||
std::memcpy(s_next, pair.second.data(), pair.second.size());
|
||||
m_link.emplace(pair.first, (u64)s_next);
|
||||
s_next = (void*)::align((u64)s_next + pair.second.size(), 16);
|
||||
}
|
||||
|
||||
s_next = (void*)::align((u64)s_next, 4096);
|
||||
}
|
||||
|
||||
jit_compiler::~jit_compiler()
|
||||
{
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue