rpcs3/Utilities/JIT.h

425 lines
9.6 KiB
C
Raw Normal View History

#pragma once
#include "util/types.hpp"
2019-11-29 23:11:28 +00:00
// Include asmjit with warnings ignored
2018-06-12 18:03:53 +00:00
#define ASMJIT_EMBED
2021-12-28 19:25:36 +00:00
#define ASMJIT_STATIC
#define ASMJIT_BUILD_DEBUG
#undef Bool
2019-11-29 23:11:28 +00:00
#ifdef _MSC_VER
#pragma warning(push, 0)
#include <asmjit/asmjit.h>
#pragma warning(pop)
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wall"
#pragma GCC diagnostic ignored "-Wextra"
#pragma GCC diagnostic ignored "-Wold-style-cast"
2021-03-05 19:05:37 +00:00
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
2021-03-23 19:32:50 +00:00
#pragma GCC diagnostic ignored "-Wredundant-decls"
2021-04-03 16:38:02 +00:00
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
2021-03-30 15:31:46 +00:00
#pragma GCC diagnostic ignored "-Weffc++"
#ifdef __clang__
#pragma GCC diagnostic ignored "-Wdeprecated-anon-enum-enum-conversion"
#pragma GCC diagnostic ignored "-Wcast-qual"
#else
2021-03-13 15:03:08 +00:00
#pragma GCC diagnostic ignored "-Wduplicated-branches"
#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion"
2021-03-13 15:03:08 +00:00
#endif
2018-06-12 18:03:53 +00:00
#include <asmjit/asmjit.h>
#if defined(ARCH_ARM64)
#include <asmjit/a64.h>
#endif
2019-11-29 23:11:28 +00:00
#pragma GCC diagnostic pop
#endif
#include <array>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#if defined(ARCH_X64)
using native_asm = asmjit::x86::Assembler;
using native_args = std::array<asmjit::x86::Gp, 4>;
#elif defined(ARCH_ARM64)
using native_asm = asmjit::a64::Assembler;
using native_args = std::array<asmjit::a64::Gp, 4>;
#endif
void jit_announce(uptr func, usz size, std::string_view name);
void jit_announce(auto* func, usz size, std::string_view name)
{
jit_announce(uptr(func), size, name);
}
enum class jit_class
{
ppu_code,
ppu_data,
spu_code,
spu_data,
};
2021-12-28 19:25:36 +00:00
struct jit_runtime_base
{
jit_runtime_base() noexcept = default;
virtual ~jit_runtime_base() = default;
jit_runtime_base(const jit_runtime_base&) = delete;
jit_runtime_base& operator=(const jit_runtime_base&) = delete;
const asmjit::Environment& environment() const noexcept;
void* _add(asmjit::CodeHolder* code) noexcept;
virtual uchar* _alloc(usz size, usz align) noexcept = 0;
std::string_view dump_name;
2021-12-28 19:25:36 +00:00
};
// ASMJIT runtime for emitting code in a single 2G region
2021-12-28 19:25:36 +00:00
struct jit_runtime final : jit_runtime_base
{
jit_runtime();
~jit_runtime() override;
// Allocate executable memory
2021-12-28 19:25:36 +00:00
uchar* _alloc(usz size, usz align) noexcept override;
// Allocate memory
2020-12-18 07:39:54 +00:00
static u8* alloc(usz size, uint align, bool exec = true) noexcept;
// Should be called at least once after global initialization
static void initialize();
// Deallocate all memory
static void finalize() noexcept;
};
namespace asmjit
{
// Should only be used to build global functions
2021-12-28 19:25:36 +00:00
jit_runtime_base& get_global_runtime();
// Don't use directly
2021-12-28 19:25:36 +00:00
class inline_runtime : public jit_runtime_base
{
uchar* m_data;
usz m_size;
public:
2021-12-28 19:25:36 +00:00
inline_runtime(uchar* data, usz size);
~inline_runtime();
2021-12-28 19:25:36 +00:00
uchar* _alloc(usz size, usz align) noexcept override;
};
// Emit xbegin and adjacent loop, return label at xbegin (don't use xabort please)
template <typename F>
2021-12-28 19:25:36 +00:00
[[nodiscard]] inline asmjit::Label build_transaction_enter(asmjit::x86::Assembler& c, asmjit::Label fallback, F func)
{
Label fall = c.newLabel();
Label begin = c.newLabel();
c.jmp(begin);
c.bind(fall);
// Don't repeat on zero status (may indicate syscall or interrupt)
c.test(x86::eax, x86::eax);
c.jz(fallback);
// First invoked after failure (can fallback to proceed, or jump anywhere else)
func();
// Other bad statuses are ignored regardless of repeat flag (TODO)
2021-12-28 19:25:36 +00:00
c.align(AlignMode::kCode, 16);
c.bind(begin);
return fall;
// xbegin should be issued manually, allows to add more check before entering transaction
}
// Helper to spill RDX (EDX) register for RDTSC
2021-12-28 19:25:36 +00:00
inline void build_swap_rdx_with(asmjit::x86::Assembler& c, std::array<x86::Gp, 4>& args, const asmjit::x86::Gp& with)
{
#ifdef _WIN32
c.xchg(args[1], with);
args[1] = with;
#else
c.xchg(args[2], with);
args[2] = with;
#endif
}
// Get full RDTSC value into chosen register (clobbers rax/rdx or saves only rax with other target)
2021-12-28 19:25:36 +00:00
inline void build_get_tsc(asmjit::x86::Assembler& c, const asmjit::x86::Gp& to = asmjit::x86::rax)
{
if (&to != &x86::rax && &to != &x86::rdx)
{
// Swap to save its contents
c.xchg(x86::rax, to);
}
c.rdtsc();
c.shl(x86::rdx, 32);
if (&to == &x86::rax)
{
c.or_(x86::rax, x86::rdx);
}
else if (&to == &x86::rdx)
{
c.or_(x86::rdx, x86::rax);
}
else
{
// Swap back, maybe there is more effective way to do it
c.xchg(x86::rax, to);
c.mov(to.r32(), to.r32());
c.or_(to.r64(), x86::rdx);
}
}
2021-12-28 19:25:36 +00:00
inline void build_init_args_from_ghc(native_asm& c, native_args& args)
{
#if defined(ARCH_X64)
// TODO: handle case when args don't overlap with r13/rbp/r12/rbx
c.mov(args[0], x86::r13);
c.mov(args[1], x86::rbp);
c.mov(args[2], x86::r12);
c.mov(args[3], x86::rbx);
#else
static_cast<void>(c);
static_cast<void>(args);
#endif
}
inline void build_init_ghc_args(native_asm& c, native_args& args)
{
#if defined(ARCH_X64)
// TODO: handle case when args don't overlap with r13/rbp/r12/rbx
c.mov(x86::r13, args[0]);
c.mov(x86::rbp, args[1]);
c.mov(x86::r12, args[2]);
c.mov(x86::rbx, args[3]);
#else
static_cast<void>(c);
static_cast<void>(args);
#endif
}
2021-12-28 19:25:36 +00:00
using imm_ptr = Imm;
}
// Build runtime function with asmjit::X86Assembler
template <typename FT, typename Asm = native_asm, typename F>
inline FT build_function_asm(std::string_view name, F&& builder)
{
using namespace asmjit;
auto& rt = get_global_runtime();
CodeHolder code;
2021-12-28 19:25:36 +00:00
code.init(rt.environment());
#if defined(ARCH_X64)
native_args args;
#ifdef _WIN32
args[0] = x86::rcx;
args[1] = x86::rdx;
args[2] = x86::r8;
args[3] = x86::r9;
#else
args[0] = x86::rdi;
args[1] = x86::rsi;
args[2] = x86::rdx;
args[3] = x86::rcx;
#endif
#elif defined(ARCH_ARM64)
native_args args;
args[0] = a64::x0;
args[1] = a64::x1;
args[2] = a64::x2;
args[3] = a64::x3;
#endif
Asm compiler(&code);
2021-12-28 19:25:36 +00:00
compiler.addEncodingOptions(EncodingOptions::kOptimizedAlign);
if constexpr (std::is_invocable_v<F, Asm&, native_args&>)
builder(compiler, args);
else
builder(compiler);
rt.dump_name = name;
2021-12-28 19:25:36 +00:00
const auto result = rt._add(&code);
jit_announce(result, code.codeSize(), name);
return reinterpret_cast<FT>(uptr(result));
}
#if !defined(ARCH_X64) || defined(__APPLE__)
template <typename FT, usz = 4096>
class built_function
{
FT m_func;
public:
built_function(const built_function&) = delete;
built_function& operator=(const built_function&) = delete;
template <typename F> requires (std::is_invocable_v<F, native_asm&, native_args&>)
built_function(std::string_view name, F&& builder,
u32 line = __builtin_LINE(),
u32 col = __builtin_COLUMN(),
const char* file = __builtin_FILE(),
const char* func = __builtin_FUNCTION())
: m_func(ensure(build_function_asm<FT>(name, std::forward<F>(builder)), const_str(), line, col, file, func))
{
}
template <typename F> requires (std::is_invocable_v<F>)
built_function(std::string_view, F&& getter,
u32 line = __builtin_LINE(),
u32 col = __builtin_COLUMN(),
const char* file = __builtin_FILE(),
const char* func = __builtin_FUNCTION())
: m_func(ensure(getter(), const_str(), line, col, file, func))
{
}
operator FT() const noexcept
{
return m_func;
}
template <typename... Args>
auto operator()(Args&&... args) const noexcept
{
return m_func(std::forward<Args>(args)...);
}
};
#else
template <typename FT, usz Size = 4096>
class built_function
{
alignas(4096) uchar m_data[Size];
public:
built_function(const built_function&) = delete;
built_function& operator=(const built_function&) = delete;
template <typename F>
built_function(std::string_view name, F&& builder)
{
using namespace asmjit;
inline_runtime rt(m_data, Size);
CodeHolder code;
2021-12-28 19:25:36 +00:00
code.init(rt.environment());
#if defined(ARCH_X64)
native_args args;
#ifdef _WIN32
args[0] = x86::rcx;
args[1] = x86::rdx;
args[2] = x86::r8;
args[3] = x86::r9;
#else
args[0] = x86::rdi;
args[1] = x86::rsi;
args[2] = x86::rdx;
args[3] = x86::rcx;
#endif
#elif defined(ARCH_ARM64)
native_args args;
args[0] = a64::x0;
args[1] = a64::x1;
args[2] = a64::x2;
args[3] = a64::x3;
#endif
native_asm compiler(&code);
2021-12-28 19:25:36 +00:00
compiler.addEncodingOptions(EncodingOptions::kOptimizedAlign);
builder(compiler, args);
rt.dump_name = name;
2021-12-28 19:25:36 +00:00
jit_announce(rt._add(&code), code.codeSize(), name);
}
operator FT() const noexcept
{
return FT(+m_data);
}
template <typename... Args>
auto operator()(Args&&... args) const noexcept
{
return FT(+m_data)(std::forward<Args>(args)...);
}
};
#endif
#ifdef LLVM_AVAILABLE
namespace llvm
{
class LLVMContext;
class ExecutionEngine;
class Module;
}
// Temporary compiler interface
class jit_compiler final
{
2017-06-24 15:36:49 +00:00
// Local LLVM context
std::unique_ptr<llvm::LLVMContext> m_context{};
2017-06-24 15:36:49 +00:00
// Execution instance
2021-04-03 16:38:02 +00:00
std::unique_ptr<llvm::ExecutionEngine> m_engine{};
2017-02-26 15:56:31 +00:00
// Arch
2021-04-03 16:38:02 +00:00
std::string m_cpu{};
2017-02-26 15:56:31 +00:00
public:
jit_compiler(const std::unordered_map<std::string, u64>& _link, const std::string& _cpu, u32 flags = 0);
~jit_compiler();
2017-06-24 15:36:49 +00:00
// Get LLVM context
auto& get_context()
{
return *m_context;
2017-06-24 15:36:49 +00:00
}
2018-05-01 10:20:36 +00:00
auto& get_engine() const
{
return *m_engine;
}
2017-07-15 09:20:40 +00:00
// Add module (path to obj cache dir)
void add(std::unique_ptr<llvm::Module> _module, const std::string& path);
2018-05-01 10:20:36 +00:00
// Add module (not cached)
void add(std::unique_ptr<llvm::Module> _module);
2018-05-01 10:20:36 +00:00
2017-07-15 09:20:40 +00:00
// Add object (path to obj file)
void add(const std::string& path);
// Check object file
static bool check(const std::string& path);
// Finalize
2017-06-24 15:36:49 +00:00
void fin();
2017-02-26 15:56:31 +00:00
// Get compiled function address
2017-06-24 15:36:49 +00:00
u64 get(const std::string& name);
2017-02-26 15:56:31 +00:00
// Get CPU info
static std::string cpu(const std::string& _cpu);
};
#endif