From 6561ddae41648f63413dabde8852949958552e56 Mon Sep 17 00:00:00 2001 From: Nekotekina Date: Sat, 24 Jun 2017 18:36:49 +0300 Subject: [PATCH] PPU LLVM: multithread compilation --- Utilities/JIT.cpp | 165 +++++++++++++++++++----------- Utilities/JIT.h | 40 ++++---- Utilities/Thread.cpp | 31 ++++++ Utilities/Thread.h | 3 + rpcs3/Emu/CPU/CPUThread.cpp | 31 ------ rpcs3/Emu/CPU/CPUThread.h | 4 - rpcs3/Emu/Cell/PPUThread.cpp | 192 +++++++++++++++++++++-------------- rpcs3/Emu/Cell/SPUThread.cpp | 4 +- rpcs3/Emu/System.cpp | 5 + 9 files changed, 285 insertions(+), 190 deletions(-) diff --git a/Utilities/JIT.cpp b/Utilities/JIT.cpp index 21886630bf..0b5160e748 100644 --- a/Utilities/JIT.cpp +++ b/Utilities/JIT.cpp @@ -11,6 +11,7 @@ #include "StrFmt.h" #include "File.h" #include "Log.h" +#include "mutex.h" #include "VirtualMemory.h" #ifdef _MSC_VER @@ -32,8 +33,8 @@ #include "JIT.h" -// Global LLVM context (thread-unsafe) -llvm::LLVMContext g_llvm_ctx; +// Memory manager mutex +shared_mutex s_mutex; // Size of virtual memory area reserved: 512 MB static const u64 s_memory_size = 0x20000000; @@ -41,6 +42,10 @@ static const u64 s_memory_size = 0x20000000; // Try to reserve a portion of virtual memory in the first 2 GB address space beforehand, if possible. static void* const s_memory = []() -> void* { + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + LLVMLinkInMCJIT(); + for (u64 addr = 0x10000000; addr <= 0x80000000 - s_memory_size; addr += 0x1000000) { if (auto ptr = utils::memory_reserve(s_memory_size, (void*)addr)) @@ -52,28 +57,47 @@ 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 void* s_next = s_memory; #ifdef _WIN32 static std::deque> s_unwater; static std::vector> s_unwind; // .pdata #endif +// Reset memory manager +extern void jit_finalize() +{ +#ifdef _WIN32 + for (auto&& unwind : s_unwind) + { + if (!RtlDeleteFunctionTable(unwind.data())) + { + LOG_FATAL(GENERAL, "RtlDeleteFunctionTable() failed! Error %u", GetLastError()); + } + } + + s_unwind.clear(); +#else + // TODO: unregister EH frames if necessary +#endif + + utils::memory_decommit(s_memory, s_memory_size); + + s_next = s_memory; +} + // Helper class -struct MemoryManager final : llvm::RTDyldMemoryManager +struct MemoryManager : llvm::RTDyldMemoryManager { std::unordered_map& m_link; - std::array* m_tramps; + std::array* m_tramps{}; + + u8* m_code_addr{}; // TODO MemoryManager(std::unordered_map& table) : m_link(table) - , m_tramps(nullptr) { - s_next = s_memory; } [[noreturn]] static void null() @@ -104,6 +128,9 @@ struct MemoryManager final : llvm::RTDyldMemoryManager // Verify address for small code model if ((u64)s_memory > 0x80000000 - s_memory_size ? (u64)addr - (u64)s_memory >= s_memory_size : addr >= 0x80000000) { + // Lock memory manager + writer_lock lock(s_mutex); + // Allocate memory for trampolines if (!m_tramps) { @@ -137,6 +164,9 @@ struct MemoryManager final : llvm::RTDyldMemoryManager virtual u8* allocateCodeSection(std::uintptr_t size, uint align, uint sec_id, llvm::StringRef sec_name) override { + // Lock memory manager + writer_lock lock(s_mutex); + // Simple allocation const u64 next = ::align((u64)s_next + size, 4096); @@ -147,7 +177,7 @@ struct MemoryManager final : llvm::RTDyldMemoryManager } utils::memory_commit(s_next, size, utils::protection::wx); - s_code_addr = (u8*)s_next; + m_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(), s_next, size, align); return (u8*)std::exchange(s_next, (void*)next); @@ -155,6 +185,9 @@ struct MemoryManager final : llvm::RTDyldMemoryManager virtual u8* allocateDataSection(std::uintptr_t size, uint align, uint sec_id, llvm::StringRef sec_name, bool is_ro) override { + // Lock memory manager + writer_lock lock(s_mutex); + // Simple allocation const u64 next = ::align((u64)s_next + size, 4096); @@ -177,6 +210,9 @@ struct MemoryManager final : llvm::RTDyldMemoryManager virtual bool finalizeMemory(std::string* = nullptr) override { + // Lock memory manager + writer_lock lock(s_mutex); + // TODO: make only read-only sections read-only //#ifdef _WIN32 // DWORD op; @@ -192,6 +228,9 @@ struct MemoryManager final : llvm::RTDyldMemoryManager virtual void registerEHFrames(u8* addr, u64 load_addr, std::size_t size) override { #ifdef _WIN32 + // Lock memory manager + writer_lock lock(s_mutex); + // Use s_memory as a BASE, compute the difference const u64 unwind_diff = (u64)addr - (u64)s_memory; @@ -224,30 +263,18 @@ struct MemoryManager final : llvm::RTDyldMemoryManager return RTDyldMemoryManager::deregisterEHFrames(addr, load_addr, size); } - - ~MemoryManager() - { -#ifdef _WIN32 - for (auto&& unwind : s_unwind) - { - if (!RtlDeleteFunctionTable(unwind.data())) - { - LOG_FATAL(GENERAL, "RtlDeleteFunctionTable() failed! Error %u", GetLastError()); - } - } - - s_unwind.clear(); -#else - // TODO: unregister EH frames if necessary -#endif - - utils::memory_decommit(s_memory, s_memory_size); - } }; // Helper class -struct EventListener final : llvm::JITEventListener +struct EventListener : llvm::JITEventListener { + MemoryManager& m_mem; + + EventListener(MemoryManager& mem) + : m_mem(mem) + { + } + virtual void NotifyObjectEmitted(const llvm::object::ObjectFile& obj, const llvm::RuntimeDyld::LoadedObjectInfo& inf) override { #ifdef _WIN32 @@ -275,8 +302,11 @@ struct EventListener final : llvm::JITEventListener } } + // Lock memory manager + writer_lock lock(s_mutex); + // Use s_memory as a BASE, compute the difference - const u64 code_diff = (u64)s_code_addr - (u64)s_memory; + const u64 code_diff = (u64)m_mem.m_code_addr - (u64)s_memory; // Fix RUNTIME_FUNCTION records (.pdata section) for (auto& rf : rfs) @@ -292,8 +322,6 @@ struct EventListener final : llvm::JITEventListener } }; -static EventListener s_listener; - // Helper class class ObjectCache final : public llvm::ObjectCache { @@ -334,15 +362,10 @@ public: } }; -jit_compiler::jit_compiler(std::unordered_map init_linkage_info, std::string _cpu) - : m_link(std::move(init_linkage_info)) +jit_compiler::jit_compiler(const std::unordered_map& _link, std::string _cpu) + : m_link(std::move(_link)) , m_cpu(std::move(_cpu)) { - // Initialization - llvm::InitializeNativeTarget(); - llvm::InitializeNativeTargetAsmPrinter(); - LLVMLinkInMCJIT(); - if (m_cpu.empty()) { m_cpu = llvm::sys::getHostCPUName(); @@ -350,22 +373,42 @@ jit_compiler::jit_compiler(std::unordered_map init_ std::string result; - m_engine.reset(llvm::EngineBuilder(std::make_unique("", g_llvm_ctx)) - .setErrorStr(&result) - .setMCJITMemoryManager(std::make_unique(m_link)) - .setOptLevel(llvm::CodeGenOpt::Aggressive) - .setCodeModel(llvm::CodeModel::Small) - .setMCPU(m_cpu) - .create()); + if (m_link.empty()) + { + m_engine.reset(llvm::EngineBuilder(std::make_unique("null_", m_context)) + .setErrorStr(&result) + .setOptLevel(llvm::CodeGenOpt::Aggressive) + .setCodeModel(llvm::CodeModel::Small) + .setMCPU(m_cpu) + .create()); + } + else + { + auto mem = std::make_unique(m_link); + m_jit_el = std::make_unique(*mem); + + m_engine.reset(llvm::EngineBuilder(std::make_unique("null", m_context)) + .setErrorStr(&result) + .setMCJITMemoryManager(std::move(mem)) + .setOptLevel(llvm::CodeGenOpt::Aggressive) + .setCodeModel(llvm::CodeModel::Small) + .setMCPU(m_cpu) + .create()); + + if (m_engine) + { + m_engine->RegisterJITEventListener(m_jit_el.get()); + } + } if (!m_engine) { fmt::throw_exception("LLVM: Failed to create ExecutionEngine: %s", result); } +} - m_engine->RegisterJITEventListener(&s_listener); - - LOG_SUCCESS(GENERAL, "LLVM: JIT initialized (%s)", m_cpu); +jit_compiler::~jit_compiler() +{ } void jit_compiler::add(std::unique_ptr module, const std::string& path) @@ -385,13 +428,23 @@ void jit_compiler::add(std::unique_ptr module, const std::string& } } -void jit_compiler::fin(const std::string& path) +void jit_compiler::fin() { m_engine->finalizeObject(); } -void jit_compiler::add(std::unordered_map data) +u64 jit_compiler::get(const std::string & name) { + return m_engine->getFunctionAddress(name); +} + +std::unordered_map jit_compiler::add(std::unordered_map data) +{ + // Lock memory manager + writer_lock lock(s_mutex); + + std::unordered_map result; + std::size_t size = 0; for (auto&& pair : data) @@ -405,15 +458,13 @@ void jit_compiler::add(std::unordered_map data) for (auto&& pair : data) { std::memcpy(s_next, pair.second.data(), pair.second.size()); - m_link.emplace(pair.first, (u64)s_next); + result.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() -{ + return result; } #endif diff --git a/Utilities/JIT.h b/Utilities/JIT.h index eeb3a2a7c0..3c2f940687 100644 --- a/Utilities/JIT.h +++ b/Utilities/JIT.h @@ -7,6 +7,7 @@ #include #include "types.h" +#include "mutex.h" #include "restore_new.h" #ifdef _MSC_VER @@ -20,48 +21,45 @@ #endif #include "define_new_memleakdetect.h" -extern llvm::LLVMContext g_llvm_ctx; - // Temporary compiler interface class jit_compiler final { + // Local LLVM context + llvm::LLVMContext m_context; + + // JIT Event Listener + std::unique_ptr m_jit_el; + // Execution instance std::unique_ptr m_engine; - // Linkage cache + // Link table std::unordered_map m_link; - // Compiled functions - std::unordered_map m_map; - // Arch std::string m_cpu; public: - jit_compiler(std::unordered_map, std::string _cpu); + jit_compiler(const std::unordered_map& _link, std::string _cpu); ~jit_compiler(); + // Get LLVM context + auto& get_context() + { + return m_context; + } + // Add module void add(std::unique_ptr module, const std::string& path); // Finalize - void fin(const std::string& path); - - // Add functions directly (name -> code) - void add(std::unordered_map); + void fin(); // Get compiled function address - u64 get(const std::string& name) const - { - const auto found = m_map.find(name); - - if (found != m_map.end()) - { - return found->second; - } + u64 get(const std::string& name); - return m_engine->getFunctionAddress(name); - } + // Add functions directly to the memory manager (name -> code) + static std::unordered_map add(std::unordered_map); // Get CPU info const std::string& cpu() const diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 7043e6f8ec..262028e0ea 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -1819,6 +1819,37 @@ void thread_ctrl::test() } } +void thread_ctrl::set_native_priority(int priority) +{ +#ifdef _WIN32 + HANDLE _this_thread = GetCurrentThread(); + INT native_priority = THREAD_PRIORITY_NORMAL; + + switch (priority) + { + default: + case 0: + break; + case 1: + native_priority = THREAD_PRIORITY_ABOVE_NORMAL; + break; + case -1: + native_priority = THREAD_PRIORITY_BELOW_NORMAL; + break; + } + + SetThreadPriority(_this_thread, native_priority); +#endif +} + +void thread_ctrl::set_ideal_processor_core(int core) +{ +#ifdef _WIN32 + HANDLE _this_thread = GetCurrentThread(); + SetThreadIdealProcessor(_this_thread, core); +#endif +} + named_thread::named_thread() { diff --git a/Utilities/Thread.h b/Utilities/Thread.h index 3844a99052..a3bd00980d 100644 --- a/Utilities/Thread.h +++ b/Utilities/Thread.h @@ -230,6 +230,9 @@ public: thread_ctrl::start(out, std::forward(func)); } + + static void set_native_priority(int priority); + static void set_ideal_processor_core(int core); }; class named_thread diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index 1b0f7fff8e..5d4371277b 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -182,34 +182,3 @@ std::string cpu_thread::dump() const { return fmt::format("Type: %s\n" "State: %s\n", typeid(*this).name(), state.load()); } - -void cpu_thread::set_native_priority(int priority) -{ -#ifdef _WIN32 - HANDLE _this_thread = GetCurrentThread(); - INT native_priority = THREAD_PRIORITY_NORMAL; - - switch (priority) - { - default: - case 0: - break; - case 1: - native_priority = THREAD_PRIORITY_ABOVE_NORMAL; - break; - case -1: - native_priority = THREAD_PRIORITY_BELOW_NORMAL; - break; - } - - SetThreadPriority(_this_thread, native_priority); -#endif // _WIN32 -} - -void cpu_thread::set_ideal_processor_core(int core) -{ -#ifdef _WIN32 - HANDLE _this_thread = GetCurrentThread(); - SetThreadIdealProcessor(_this_thread, core); -#endif -} \ No newline at end of file diff --git a/rpcs3/Emu/CPU/CPUThread.h b/rpcs3/Emu/CPU/CPUThread.h index fed3cf0f9f..fa69f2d421 100644 --- a/rpcs3/Emu/CPU/CPUThread.h +++ b/rpcs3/Emu/CPU/CPUThread.h @@ -65,10 +65,6 @@ public: // Callback for cpu_flag::suspend virtual void cpu_sleep() {} - - //native scheduler tweaks - void set_native_priority(int priority); - void set_ideal_processor_core(int core); }; inline cpu_thread* get_current_cpu_thread() noexcept diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index 0dd858f106..759c22db05 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -49,6 +49,7 @@ #include "Modules/cellMsgDialog.h" #endif +#include #include #include "Utilities/GSL.h" @@ -102,7 +103,7 @@ const ppu_decoder s_ppu_interpreter_fast; extern void ppu_initialize(); extern void ppu_initialize(const ppu_module& info); -static void ppu_initialize2(const ppu_module& info); +static void ppu_initialize2(class jit_compiler& jit, const ppu_module& module_part, const std::string& cache_path, const std::string& obj_name); extern void ppu_execute_syscall(ppu_thread& ppu, u64 code); // Get pointer to executable cache @@ -1059,11 +1060,8 @@ extern void ppu_initialize(const ppu_module& info) return; } -#ifdef LLVM_AVAILABLE - using namespace llvm; - - // Initialize JIT compiler - if (!fxm::check()) + // Link table + static const std::unordered_map s_link_table = []() { std::unordered_map link_table { @@ -1098,15 +1096,32 @@ extern void ppu_initialize(const ppu_module& info) } } - fxm::make(std::move(link_table), g_cfg.core.llvm_cpu); + return link_table; + }(); + +#ifdef LLVM_AVAILABLE + // Initialize compiler + jit_compiler jit(s_link_table, g_cfg.core.llvm_cpu); + + // Compiler mutex + semaphore<> jmutex; + + // Initialize semaphore with the max number of threads + semaphore jcores(std::thread::hardware_concurrency()); + + if (!jcores.get()) + { + // Min value 1 + jcores.post(); } -#endif + + // Worker threads + std::vector jthreads; // Split module into fragments <= 1 MiB std::size_t fpos = 0; ppu_module part; - part.funcs.reserve(65536); while (fpos < info.funcs.size()) { @@ -1115,12 +1130,13 @@ extern void ppu_initialize(const ppu_module& info) std::size_t bsize = 0; part.funcs.clear(); + part.funcs.reserve(16000); while (fpos < info.funcs.size()) { auto& func = info.funcs[fpos]; - if (bsize + func.size > 1024 * 1024 && bsize) + if (bsize + func.size > 256 * 1024 && bsize) { break; } @@ -1158,13 +1174,90 @@ extern void ppu_initialize(const ppu_module& info) part.name.append("+0"); } - ppu_initialize2(part); + // Compute module hash + std::string obj_name; + { + sha1_context ctx; + u8 output[20]; + sha1_starts(&ctx); + + for (const auto& func : part.funcs) + { + if (func.size == 0) + { + continue; + } + + const be_t addr = func.addr; + const be_t size = func.size; + sha1_update(&ctx, reinterpret_cast(&addr), sizeof(addr)); + sha1_update(&ctx, reinterpret_cast(&size), sizeof(size)); + + for (const auto& block : func.blocks) + { + if (block.second == 0) + { + continue; + } + + sha1_update(&ctx, vm::ps3::_ptr(block.first), block.second); + } + + sha1_update(&ctx, vm::ps3::_ptr(func.addr), func.size); + } + + sha1_finish(&ctx, output); + + // Version, module name and hash: vX-liblv2.sprx-0123456789ABCDEF.obj + fmt::append(obj_name, "v1%s-%016X-%s.obj", part.name, reinterpret_cast&>(output), jit.cpu()); + } + + if (Emu.IsStopped()) + { + break; + } + + // Check object file + if (fs::is_file(Emu.GetCachePath() + obj_name)) + { + semaphore_lock lock(jmutex); + ppu_initialize2(jit, part, Emu.GetCachePath(), obj_name); + continue; + } + + // Create worker thread for compilation + jthreads.emplace_back([&jit, &jmutex, &jcores, obj_name = obj_name, part = std::move(part)]() + { + // Set low priority + thread_ctrl::set_native_priority(-1); + + // Allocate "core" + { + semaphore_lock jlock(jcores); + + if (Emu.IsStopped()) + { + return; + } + + // Use another JIT instance + jit_compiler jit2({}, g_cfg.core.llvm_cpu); + ppu_initialize2(jit2, part, Emu.GetCachePath(), obj_name); + } + + // Proceed with original JIT instance + semaphore_lock lock(jmutex); + ppu_initialize2(jit, part, Emu.GetCachePath(), obj_name); + }); } -#ifdef LLVM_AVAILABLE - const auto jit = fxm::check_unlocked(); + // Join worker threads + for (auto& thread : jthreads) + { + thread.join(); + } - jit->fin(Emu.GetCachePath()); + jit.fin(); // Get and install function addresses for (const auto& func : info.funcs) @@ -1175,74 +1268,27 @@ extern void ppu_initialize(const ppu_module& info) { if (block.second) { - ppu_ref(block.first) = ::narrow(jit->get(fmt::format("__0x%x", block.first))); + ppu_ref(block.first) = ::narrow(jit.get(fmt::format("__0x%x", block.first))); } } } -#endif } -static void ppu_initialize2(const ppu_module& module_part) +static void ppu_initialize2(jit_compiler& jit, const ppu_module& module_part, const std::string& cache_path, const std::string& obj_name) { - if (Emu.IsStopped()) - { - return; - } - - // Compute module hash - std::string obj_name; - { - sha1_context ctx; - u8 output[20]; - sha1_starts(&ctx); - - for (const auto& func : module_part.funcs) - { - if (func.size == 0) - { - continue; - } - - const be_t addr = func.addr; - const be_t size = func.size; - sha1_update(&ctx, reinterpret_cast(&addr), sizeof(addr)); - sha1_update(&ctx, reinterpret_cast(&size), sizeof(size)); - - for (const auto& block : func.blocks) - { - if (block.second == 0) - { - continue; - } - - sha1_update(&ctx, vm::ps3::_ptr(block.first), block.second); - } - - sha1_update(&ctx, vm::ps3::_ptr(func.addr), func.size); - } - - sha1_finish(&ctx, output); - - // Version, module name and hash: vX-liblv2.sprx-0123456789ABCDEF.obj - fmt::append(obj_name, "b1%s-%016X.obj", module_part.name, reinterpret_cast&>(output)); - } - -#ifdef LLVM_AVAILABLE using namespace llvm; - const auto jit = fxm::get(); - // Create LLVM module - std::unique_ptr module = std::make_unique(obj_name, g_llvm_ctx); + std::unique_ptr module = std::make_unique(obj_name, jit.get_context()); // Initialize target module->setTargetTriple(Triple::normalize(sys::getProcessTriple())); // Initialize translator - std::unique_ptr translator = std::make_unique(g_llvm_ctx, module.get(), 0); + std::unique_ptr translator = std::make_unique(jit.get_context(), module.get(), 0); // Define some types - const auto _void = Type::getVoidTy(g_llvm_ctx); + const auto _void = Type::getVoidTy(jit.get_context()); const auto _func = FunctionType::get(_void, {translator->GetContextType()->getPointerTo()}, false); // Initialize function list @@ -1258,7 +1304,7 @@ static void ppu_initialize2(const ppu_module& module_part) std::shared_ptr dlg; // Check cached file - if (!fs::is_file(Emu.GetCachePath() + obj_name)) + if (!fs::is_file(cache_path + obj_name)) { legacy::FunctionPassManager pm(module.get()); @@ -1404,7 +1450,7 @@ static void ppu_initialize2(const ppu_module& module_part) if (g_cfg.core.llvm_logs) { out << *module; // print IR - fs::file(Emu.GetCachePath() + obj_name + ".log", fs::rewrite).write(out.str()); + fs::file(cache_path + obj_name + ".log", fs::rewrite).write(out.str()); result.clear(); } @@ -1418,11 +1464,7 @@ static void ppu_initialize2(const ppu_module& module_part) LOG_NOTICE(PPU, "LLVM: %zu functions generated", module->getFunctionList().size()); } - // Access JIT compiler - if (const auto jit = fxm::check_unlocked()) - { - // Load or compile module - jit->add(std::move(module), Emu.GetCachePath()); - } -#endif + // Load or compile module + jit.add(std::move(module), cache_path); +#endif // LLVM_AVAILABLE } diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 7ec0c91942..9bd1d22d86 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -147,13 +147,13 @@ void SPUThread::on_spawn() auto half_count = core_count / 2; auto assigned_secondary_core = ((g_num_spu_threads % half_count) * 2) + 1; - set_ideal_processor_core(assigned_secondary_core); + thread_ctrl::set_ideal_processor_core(assigned_secondary_core); } } if (g_cfg.core.lower_spu_priority) { - set_native_priority(-1); + thread_ctrl::set_native_priority(-1); } g_num_spu_threads++; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 65783a1429..1b98768acc 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -681,6 +681,11 @@ void Emulator::Stop() { Init(); } + +#ifdef LLVM_AVAILABLE + extern void jit_finalize(); + jit_finalize(); +#endif } s32 error_code::error_report(const fmt_type_info* sup, u64 arg)