Skip to content

Commit

Permalink
LLVM: Slice PPU executable memory
Browse files Browse the repository at this point in the history
  • Loading branch information
elad335 committed Jan 15, 2025
1 parent 71af800 commit c6b6b47
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 119 deletions.
4 changes: 2 additions & 2 deletions Utilities/JIT.h
Original file line number Diff line number Diff line change
Expand Up @@ -514,8 +514,8 @@ class jit_compiler final
atomic_t<usz> m_disk_space = umax;

public:
jit_compiler(const std::unordered_map<std::string, u64>& _link, const std::string& _cpu, u32 flags = 0);
~jit_compiler();
jit_compiler(const std::unordered_map<std::string, u64>& _link, const std::string& _cpu, u32 flags = 0, std::function<u64(const std::string&)> symbols_cement = {}) noexcept;
~jit_compiler() noexcept;

// Get LLVM context
auto& get_context()
Expand Down
124 changes: 92 additions & 32 deletions Utilities/JITLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ static u64 make_null_function(const std::string& name)

if (res.ec == std::errc() && res.ptr == name.c_str() + name.size() && addr < 0x8000'0000)
{
// Point the garbage to reserved, non-executable memory
return reinterpret_cast<u64>(vm::g_sudo_addr + addr);
fmt::throw_exception("Unhandled symbols cementing! (name='%s'", name);
}
}

Expand Down Expand Up @@ -174,18 +173,34 @@ struct JITAnnouncer : llvm::JITEventListener
struct MemoryManager1 : llvm::RTDyldMemoryManager
{
// 256 MiB for code or data
static constexpr u64 c_max_size = 0x20000000 / 2;
static constexpr u64 c_max_size = 0x1000'0000;

// Allocation unit (2M)
static constexpr u64 c_page_size = 2 * 1024 * 1024;

// Reserve 512 MiB
u8* const ptr = static_cast<u8*>(utils::memory_reserve(c_max_size * 2));
// Reserve 256 MiB blocks
void* m_code_mems = nullptr;
void* m_data_ro_mems = nullptr;
void* m_data_rw_mems = nullptr;

u64 code_ptr = 0;
u64 data_ptr = c_max_size;
u64 data_ro_ptr = 0;
u64 data_rw_ptr = 0;

MemoryManager1() = default;
// First fallback for non-existing symbols
// May be a memory container internally
std::function<u64(const std::string&)> m_symbols_cement;

MemoryManager1(std::function<u64(const std::string&)> symbols_cement = {}) noexcept
: m_symbols_cement(std::move(symbols_cement))
{
auto ptr = reinterpret_cast<u8*>(utils::memory_reserve(c_max_size * 3));
m_code_mems = ptr;
// ptr += c_max_size;
// m_data_ro_mems = ptr;
ptr += c_max_size;
m_data_rw_mems = ptr;
}

MemoryManager1(const MemoryManager1&) = delete;

Expand All @@ -194,13 +209,20 @@ struct MemoryManager1 : llvm::RTDyldMemoryManager
~MemoryManager1() override
{
// Hack: don't release to prevent reuse of address space, see jit_announce
utils::memory_decommit(ptr, c_max_size * 2);
utils::memory_decommit(m_code_mems, utils::align(code_ptr % c_max_size, c_page_size));
utils::memory_decommit(m_data_ro_mems, utils::align(data_ro_ptr % c_max_size, c_page_size));
utils::memory_decommit(m_data_rw_mems, utils::align(data_rw_ptr % c_max_size, c_page_size));
}

llvm::JITSymbol findSymbol(const std::string& name) override
{
u64 addr = RTDyldMemoryManager::getSymbolAddress(name);

if (!addr && m_symbols_cement)
{
addr = m_symbols_cement(name);
}

if (!addr)
{
addr = make_null_function(name);
Expand All @@ -214,45 +236,71 @@ struct MemoryManager1 : llvm::RTDyldMemoryManager
return {addr, llvm::JITSymbolFlags::Exported};
}

u8* allocate(u64& oldp, uptr size, uint align, utils::protection prot)
u8* allocate(u64& alloc_pos, void* block, uptr size, u64 align, utils::protection prot)
{
if (align > c_page_size)
align = align ? align : 16;

const u64 sizea = utils::align(size, align);

if (!size || align > c_page_size || sizea > c_max_size || sizea < size)
{
jit_log.fatal("Unsupported alignment (size=0x%x, align=0x%x)", size, align);
jit_log.fatal("Unsupported size/alignment (size=0x%x, align=0x%x)", size, align);
return nullptr;
}

const u64 olda = utils::align(oldp, align);
const u64 newp = utils::align(olda + size, align);
const u64 oldp = alloc_pos;

u64 olda = utils::align(oldp, align);

if ((newp - 1) / c_max_size != oldp / c_max_size)
ensure(olda >= oldp);
ensure(olda < ~sizea);

u64 newp = olda + sizea;

if ((newp - 1) / c_max_size != (oldp - 1) / c_max_size)
{
jit_log.fatal("Out of memory (size=0x%x, align=0x%x)", size, align);
return nullptr;
if ((newp - 1) / c_max_size > 1)
{
// Does not work for relocations, needs more robust solution
fmt::throw_exception("Out of memory (size=0x%x, align=0x%x)", size, align);
}

olda = utils::align(oldp, c_max_size);

ensure(olda >= oldp);
ensure(olda < ~sizea);

newp = olda + sizea;
}

if ((oldp - 1) / c_page_size != (newp - 1) / c_page_size)
// Update allocation counter
alloc_pos = newp;

if ((newp - 1) / c_page_size != (oldp - 1) / c_page_size)
{
// Allocate pages on demand
const u64 pagea = utils::align(oldp, c_page_size);
const u64 pagea = utils::align(olda, c_page_size);
const u64 psize = utils::align(newp - pagea, c_page_size);
utils::memory_commit(this->ptr + pagea, psize, prot);
utils::memory_commit(reinterpret_cast<u8*>(block) + (pagea % c_max_size), psize, prot);
}

// Update allocation counter
oldp = newp;

return this->ptr + olda;
return reinterpret_cast<u8*>(block) + (olda % c_max_size);
}

u8* allocateCodeSection(uptr size, uint align, uint /*sec_id*/, llvm::StringRef /*sec_name*/) override
{
return allocate(code_ptr, size, align, utils::protection::wx);
return allocate(code_ptr, m_code_mems, size, align, utils::protection::wx);
}

u8* allocateDataSection(uptr size, uint align, uint /*sec_id*/, llvm::StringRef /*sec_name*/, bool /*is_ro*/) override
u8* allocateDataSection(uptr size, uint align, uint /*sec_id*/, llvm::StringRef /*sec_name*/, bool is_ro) override
{
return allocate(data_ptr, size, align, utils::protection::rw);
if (is_ro)
{
// Disabled
//return allocate(data_ro_ptr, m_data_ro_mems, size, align, utils::protection::rw);
}

return allocate(data_rw_ptr, m_data_rw_mems, size, align, utils::protection::rw);
}

bool finalizeMemory(std::string* = nullptr) override
Expand All @@ -272,7 +320,14 @@ struct MemoryManager1 : llvm::RTDyldMemoryManager
// Simple memory manager
struct MemoryManager2 : llvm::RTDyldMemoryManager
{
MemoryManager2() = default;
// First fallback for non-existing symbols
// May be a memory container internally
std::function<u64(const std::string&)> m_symbols_cement;

MemoryManager2(std::function<u64(const std::string&)> symbols_cement = {}) noexcept
: m_symbols_cement(std::move(symbols_cement))
{
}

~MemoryManager2() override
{
Expand All @@ -282,6 +337,11 @@ struct MemoryManager2 : llvm::RTDyldMemoryManager
{
u64 addr = RTDyldMemoryManager::getSymbolAddress(name);

if (!addr && m_symbols_cement)
{
addr = m_symbols_cement(name);
}

if (!addr)
{
addr = make_null_function(name);
Expand Down Expand Up @@ -561,7 +621,7 @@ bool jit_compiler::add_sub_disk_space(ssz space)
}).second;
}

jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, const std::string& _cpu, u32 flags)
jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, const std::string& _cpu, u32 flags, std::function<u64(const std::string&)> symbols_cement) noexcept
: m_context(new llvm::LLVMContext)
, m_cpu(cpu(_cpu))
{
Expand Down Expand Up @@ -589,17 +649,17 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, co
// Auxiliary JIT (does not use custom memory manager, only writes the objects)
if (flags & 0x1)
{
mem = std::make_unique<MemoryManager1>();
mem = std::make_unique<MemoryManager1>(std::move(symbols_cement));
}
else
{
mem = std::make_unique<MemoryManager2>();
mem = std::make_unique<MemoryManager2>(std::move(symbols_cement));
null_mod->setTargetTriple(jit_compiler::triple2());
}
}
else
{
mem = std::make_unique<MemoryManager1>();
mem = std::make_unique<MemoryManager1>(std::move(symbols_cement));
}

{
Expand Down Expand Up @@ -648,7 +708,7 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, co
}
}

jit_compiler::~jit_compiler()
jit_compiler::~jit_compiler() noexcept
{
}

Expand Down
2 changes: 1 addition & 1 deletion Utilities/Thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ class named_thread_group final
}

// Move the context (if movable)
new (static_cast<void*>(m_threads + m_count - 1)) Thread(std::string(name) + std::to_string(m_count - 1), std::forward<Context>(f));
new (static_cast<void*>(m_threads + m_count - 1)) Thread(std::string(name) + std::to_string(m_count), std::forward<Context>(f));
}

// Constructor with a function performed before adding more threads
Expand Down
80 changes: 67 additions & 13 deletions rpcs3/Emu/Cell/PPUAnalyser.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <map>
#include <set>
#include <deque>
#include <span>
#include "util/types.hpp"
#include "util/endian.hpp"
#include "util/asm.hpp"
Expand Down Expand Up @@ -38,7 +39,41 @@ struct ppu_function
std::map<u32, u32> blocks{}; // Basic blocks: addr -> size
std::set<u32> calls{}; // Set of called functions
std::set<u32> callers{};
std::string name{}; // Function name
mutable std::string name{}; // Function name

struct iterator
{
const ppu_function* _this;
usz index = 0;

std::pair<const u32, u32> operator*() const
{
return _this->blocks.empty() ? std::pair<const u32, u32>(_this->addr, _this->size) : *std::next(_this->blocks.begin(), index);
}

iterator& operator++()
{
index++;
return *this;
}

bool operator==(const iterator& rhs) const noexcept
{
return rhs.index == index;
}

bool operator!=(const iterator& rhs) const noexcept = default;
};

iterator begin() const
{
return iterator{this};
}

iterator end() const
{
return iterator{this, std::max<usz>(1, blocks.size())};
}
};

// PPU Relocation Information
Expand Down Expand Up @@ -87,30 +122,49 @@ struct ppu_module : public Type

ppu_module& operator=(ppu_module&&) noexcept = default;

uchar sha1[20]{};
std::string name{};
std::string path{};
uchar sha1[20]{}; // Hash
std::string name{}; // Filename
std::string path{}; // Filepath
s64 offset = 0; // Offset of file
std::string cache{};
std::vector<ppu_reloc> relocs{};
std::vector<ppu_segment> segs{};
std::vector<ppu_segment> secs{};
std::vector<ppu_function> funcs{};
std::vector<u32> applied_patches;
std::deque<std::shared_ptr<void>> allocations;
std::map<u32, u32> addr_to_seg_index;
mutable bs_t<ppu_attr> attr{}; // Shared module attributes
std::string cache{}; // Cache file path
std::vector<ppu_reloc> relocs{}; // Relocations
std::vector<ppu_segment> segs{}; // Segments
std::vector<ppu_segment> secs{}; // Segment sections
std::vector<ppu_function> funcs{}; // Function list
std::vector<u32> applied_patches; // Patch addresses
std::deque<std::shared_ptr<void>> allocations; // Segment memory allocations
std::map<u32, u32> addr_to_seg_index; // address->segment ordered translator map
ppu_module* parent = nullptr;
std::pair<u32, u32> local_bounds{u32{umax}, 0}; // Module addresses range
std::shared_ptr<std::pair<u32, u32>> jit_bounds; // JIT instance modules addresses range

auto& get_funcs()
{
return parent ? parent->funcs : funcs;
}

const auto& get_funcs() const
{
return parent ? parent->funcs : funcs;
}

const auto& get_relocs() const
{
return parent ? parent->relocs : relocs;
}

// Copy info without functions
void copy_part(const ppu_module& info)
{
std::memcpy(sha1, info.sha1, sizeof(sha1));
name = info.name;
path = info.path;
relocs = info.relocs;
segs = info.segs;
secs = info.secs;
allocations = info.allocations;
addr_to_seg_index = info.addr_to_seg_index;
parent = const_cast<ppu_module*>(&info);
}

bool analyse(u32 lib_toc, u32 entry, u32 end, const std::vector<u32>& applied, const std::vector<u32>& exported_funcs = std::vector<u32>{}, std::function<bool()> check_aborted = {});
Expand Down
Loading

0 comments on commit c6b6b47

Please sign in to comment.