Files
FEX-Emu--FEX/Source/Tools/IRLoader/IRLoader.cpp
T
Ryan Houdek 58f2693954 FEXLoader: Moves thread management to the frontend
Lots going on here.

This moves OS thread object lifetime management and internal thread
state lifetime management to the frontend. This causes a bunch of thread
handling to move from the FEXCore Context to the frontend.

Looking at `FEXCore/include/FEXCore/Core/Context.h` really shows how
much of the API has moved to the frontend that FEXCore no longer needs
to manage. Primarily this makes FEXCore itself no longer need to care
about most of the management of the emulation state.

A large amount of the behaviour moved wholesale from Core.cpp to
LinuxEmulation's ThreadManager.cpp. Which this manages the lifetimes of
both the OS threads and the FEXCore thread state objects.

One feature lost was the instruction capability, but this was already
buggy and is going to be rewritten/fixed when gdbserver work continues.

Now that all of this management is moved to the frontend, the gdbserver
can start improving since it can start managing all thread state
directly.
2023-12-19 17:43:04 -08:00

230 lines
6.6 KiB
C++

// SPDX-License-Identifier: MIT
/*
$info$
tags: Bin|IRLoader
desc: Used to run IR Tests
$end_info$
*/
#include "Common/ArgumentLoader.h"
#include "LinuxSyscalls/SignalDelegator.h"
#include <FEXCore/Config/Config.h>
#include <FEXCore/Core/CodeLoader.h>
#include <FEXCore/Core/Context.h>
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/Core/HostFeatures.h>
#include <FEXCore/Utils/Allocator.h>
#include <FEXCore/Utils/EnumUtils.h>
#include <FEXCore/Utils/FileLoading.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/HLE/SyscallHandler.h>
#include <FEXCore/IR/IREmitter.h>
#include <FEXCore/fextl/fmt.h>
#include <FEXCore/fextl/sstream.h>
#include <csetjmp>
#include <functional>
#include <memory>
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#include <vector>
#include "HarnessHelpers.h"
namespace FEXCore::Context {
class Context;
}
void MsgHandler(LogMan::DebugLevels Level, char const *Message)
{
fextl::fmt::print("[{}] {}\n", LogMan::DebugLevelStr(Level), Message);
fflush(stdout);
}
void AssertHandler(char const *Message)
{
fextl::fmt::print("[ASSERT] {}\n", Message);
fflush(stdout);
}
using namespace FEXCore::IR;
class IRCodeLoader final {
public:
IRCodeLoader(fextl::string const &Filename, fextl::string const &ConfigFilename) {
Config.Init(ConfigFilename);
fextl::string IRFile;
if (!FEXCore::FileLoading::LoadFile(IRFile, Filename)) {
LogMan::Msg::EFmt("Couldn't open IR file '{}'", Filename);
return;
}
fextl::stringstream IRStream(IRFile);
ParsedCode = FEXCore::IR::Parse(Allocator, IRStream);
if (ParsedCode) {
EntryRIP = 0x40000;
fextl::stringstream out;
auto IR = ParsedCode->ViewIR();
FEXCore::IR::Dump(&out, &IR, nullptr);
fextl::fmt::print("IR:\n{}\n@@@@@\n", out.str());
for (auto &[region, size] : Config.GetMemoryRegions()) {
FEXCore::Allocator::mmap(reinterpret_cast<void *>(region), size, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}
Config.LoadMemory();
}
}
bool LoadIR(FEXCore::Context::Context *CTX) const {
if (!ParsedCode) {
return false;
}
return !!CTX->AddCustomIREntrypoint(EntryRIP, [ParsedCodePtr = ParsedCode.get()](uintptr_t Entrypoint, FEXCore::IR::IREmitter *emit) {
emit->CopyData(*ParsedCodePtr);
});
}
bool CompareStates(FEXCore::Core::CPUState const *State, bool SupportsAVX) {
return Config.CompareStates(State, nullptr, SupportsAVX);
}
uint64_t GetStackPointer()
{
return reinterpret_cast<uint64_t>(FEXCore::Allocator::mmap(nullptr, STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
}
uint64_t DefaultRIP() const
{
return EntryRIP;
}
bool RequiresAVX() const {
return Config.RequiresAVX();
}
private:
uint64_t EntryRIP{};
fextl::unique_ptr<IREmitter> ParsedCode;
FEXCore::Utils::PooledAllocatorMalloc Allocator;
FEX::HarnessHelper::ConfigLoader Config;
constexpr static uint64_t STACK_SIZE = 8 * 1024 * 1024;
};
class DummySyscallHandler: public FEXCore::HLE::SyscallHandler, public FEXCore::Allocator::FEXAllocOperators {
public:
uint64_t HandleSyscall(FEXCore::Core::CpuStateFrame *Frame, FEXCore::HLE::SyscallArguments *Args) override {
LOGMAN_MSG_A_FMT("Syscalls not implemented");
return 0;
}
FEXCore::HLE::SyscallABI GetSyscallABI(uint64_t Syscall) override {
LOGMAN_MSG_A_FMT("Syscalls not implemented");
return {0, false, 0 };
}
// These are no-ops implementations of the SyscallHandler API
FEXCore::HLE::AOTIRCacheEntryLookupResult LookupAOTIRCacheEntry(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestAddr) override {
return {0, 0};
}
};
int main(int argc, char **argv, char **const envp)
{
FEXCore::Allocator::GLIBCScopedFault GLIBFaultScope;
LogMan::Throw::InstallHandler(AssertHandler);
LogMan::Msg::InstallHandler(MsgHandler);
FEXCore::Config::Initialize();
FEXCore::Config::AddLayer(fextl::make_unique<FEX::ArgLoader::ArgLoader>(argc, argv));
FEXCore::Config::AddLayer(FEX::Config::CreateEnvironmentLayer(envp));
FEXCore::Config::Load();
// Ensure the IRLoader runs in 64-bit mode.
// This is to ensure that static register allocation in the JIT
// is configured correctly for accesses to the top 8 GPRs and 8 XMM registers.
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_IS64BIT_MODE, "1");
#ifdef VIXL_SIMULATOR
// If running under the vixl simulator, ensure that indirect runtime calls are enabled.
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_DISABLE_VIXL_INDIRECT_RUNTIME_CALLS, "0");
#endif
auto Args = FEX::ArgLoader::Get();
auto ParsedArgs = FEX::ArgLoader::GetParsedArgs();
LOGMAN_THROW_A_FMT(Args.size() > 1, "Not enough arguments");
FEXCore::Context::InitializeStaticTables();
auto CTX = FEXCore::Context::Context::CreateNewContext();
auto SignalDelegation = FEX::HLE::CreateSignalDelegator(CTX.get(), {});
CTX->SetSignalDelegator(SignalDelegation.get());
CTX->SetSyscallHandler(new DummySyscallHandler());
IRCodeLoader Loader(Args[0], Args[1]);
// Skip tests that require AVX on hosts that don't support it.
const bool SupportsAVX = CTX->GetHostFeatures().SupportsAVX;
if (!SupportsAVX && Loader.RequiresAVX()) {
return 0;
}
int Return{};
if (Loader.LoadIR(CTX.get()))
{
if (!CTX->InitCore()) {
return -1;
}
auto ParentThread = CTX->CreateThread(Loader.DefaultRIP(), Loader.GetStackPointer());
auto ShutdownReason = FEXCore::Context::ExitReason::EXIT_SHUTDOWN;
// There might already be an exit handler, leave it installed
if (!CTX->GetExitHandler())
{
CTX->SetExitHandler([&](uint64_t thread, FEXCore::Context::ExitReason reason) {
if (reason != FEXCore::Context::ExitReason::EXIT_DEBUG)
{
ShutdownReason = reason;
}
});
}
jmp_buf LongJump{};
int LongJumpVal{};
SignalDelegation->RegisterFrontendHostSignalHandler(SIGSEGV, [&LongJump](FEXCore::Core::InternalThreadState *Thread, int Signal, void *info, void *ucontext) {
longjmp(LongJump, 1);
return false;
}, true);
LongJumpVal = setjmp(LongJump);
if (!LongJumpVal) {
CTX->RunUntilExit(ParentThread);
}
LogMan::Msg::DFmt("Reason we left VM: {}", FEXCore::ToUnderlying(ShutdownReason));
// Just re-use compare state. It also checks against the expected values in config.
const bool Passed = Loader.CompareStates(&ParentThread->CurrentFrame->State, SupportsAVX);
LogMan::Msg::IFmt("Passed? {}\n", Passed ? "Yes" : "No");
Return = Passed ? 0 : -1;
CTX->DestroyThread(ParentThread);
}
else
{
LogMan::Msg::EFmt("Couldn't load IR");
Return = -1;
}
return Return;
}