diff --git a/External/FEXCore/Source/CMakeLists.txt b/External/FEXCore/Source/CMakeLists.txt index b5ad295bb..3fd60a4b1 100644 --- a/External/FEXCore/Source/CMakeLists.txt +++ b/External/FEXCore/Source/CMakeLists.txt @@ -133,6 +133,7 @@ set (SRCS Interface/IR/Passes/DeadCodeElimination.cpp Interface/IR/Passes/DeadContextStoreElimination.cpp Interface/IR/Passes/IRCompaction.cpp + Interface/IR/Passes/IRDumperPass.cpp Interface/IR/Passes/IRValidation.cpp Interface/IR/Passes/RAValidation.cpp Interface/IR/Passes/LongDivideRemovalPass.cpp diff --git a/External/FEXCore/Source/Interface/Config/Config.json.in b/External/FEXCore/Source/Interface/Config/Config.json.in index 9a80069f4..83d4e11d1 100644 --- a/External/FEXCore/Source/Interface/Config/Config.json.in +++ b/External/FEXCore/Source/Interface/Config/Config.json.in @@ -198,6 +198,24 @@ "[no, stdout, stderr, ]" ] }, + "PassManagerDumpIR": { + "Type": "strenum", + "Default": "FEXCore::Config::PassManagerDumpIR::OFF", + "Enums": { + "BEFOREOPT": "beforeopt", + "AFTEROPT": "afteropt", + "BEFORE": "beforepass", + "AFTER": "afterpass" + }, + "Desc": [ + "Allows controlling when FEX dumps its IR.", + "\toff: IR dumping will be disabled", + "\tbeforeopt: Dump IR before any optimizations", + "\tafteropt: Dump IR after all optimizations", + "\tbeforepass: Dump IR before every optimization pass", + "\tafterpass: Dump IR after every optimization pass" + ] + }, "DumpGPRs": { "Type": "bool", "Default": "false", diff --git a/External/FEXCore/Source/Interface/Context/Context.h b/External/FEXCore/Source/Interface/Context/Context.h index 7bac3c17e..0b50d28b6 100644 --- a/External/FEXCore/Source/Interface/Context/Context.h +++ b/External/FEXCore/Source/Interface/Context/Context.h @@ -230,7 +230,6 @@ namespace FEXCore::Context { FEX_CONFIG_OPT(ThunkHostLibsPath, THUNKHOSTLIBS); FEX_CONFIG_OPT(ThunkHostLibsPath32, THUNKHOSTLIBS32); FEX_CONFIG_OPT(ThunkConfigFile, THUNKCONFIG); - FEX_CONFIG_OPT(DumpIR, DUMPIR); FEX_CONFIG_OPT(StaticRegisterAllocation, SRA); FEX_CONFIG_OPT(GlobalJITNaming, GLOBALJITNAMING); FEX_CONFIG_OPT(LibraryJITNaming, LIBRARYJITNAMING); diff --git a/External/FEXCore/Source/Interface/Core/Core.cpp b/External/FEXCore/Source/Interface/Core/Core.cpp index cf7d05cd6..a49005c56 100644 --- a/External/FEXCore/Source/Interface/Core/Core.cpp +++ b/External/FEXCore/Source/Interface/Core/Core.cpp @@ -603,6 +603,8 @@ namespace FEXCore::Context { ERROR_AND_DIE_FMT("Unknown core configuration"); break; } + + Thread->PassManager->Finalize(); } FEXCore::Core::InternalThreadState* ContextImpl::CreateThread(FEXCore::Core::CPUState *NewThreadState, uint64_t ParentTID) { @@ -729,30 +731,11 @@ namespace FEXCore::Context { } static void IRDumper(FEXCore::Core::InternalThreadState *Thread, IR::IREmitter *IREmitter, uint64_t GuestRIP, IR::RegisterAllocationData* RA) { - FEXCore::File::File FD; - const auto DumpIRStr = static_cast(Thread->CTX)->Config.DumpIR(); - - // DumpIRStr might be no if not dumping but ShouldDump is set in OpDisp - if (DumpIRStr =="stderr" || DumpIRStr =="no") { - FD = FEXCore::File::File::GetStdERR(); - } - else if (DumpIRStr =="stdout") { - FD = FEXCore::File::File::GetStdOUT(); - } - else { - const auto fileName = fextl::fmt::format("{}/{:x}{}", DumpIRStr, GuestRIP, RA ? "-post.ir" : "-pre.ir"); - FD = FEXCore::File::File(fileName.c_str(), - FEXCore::File::FileModes::WRITE | - FEXCore::File::FileModes::CREATE | - FEXCore::File::FileModes::TRUNCATE); - } - - if (FD.IsValid()) { - fextl::stringstream out; - auto NewIR = IREmitter->ViewIR(); - FEXCore::IR::Dump(&out, &NewIR, RA); - fextl::fmt::print(FD, "IR-{} 0x{:x}:\n{}\n@@@@@\n", RA ? "post" : "pre", GuestRIP, out.str()); - } + FEXCore::File::File FD = FEXCore::File::File::GetStdERR(); + fextl::stringstream out; + auto NewIR = IREmitter->ViewIR(); + FEXCore::IR::Dump(&out, &NewIR, RA); + fextl::fmt::print(FD, "IR-ShouldDump-{} 0x{:x}:\n{}\n@@@@@\n", RA ? "post" : "pre", GuestRIP, out.str()); }; static void ValidateIR(ContextImpl *ctx, IR::IREmitter *IREmitter) { @@ -920,7 +903,7 @@ namespace FEXCore::Context { IR::IREmitter *IREmitter = Thread->OpDispatcher.get(); - auto ShouldDump = static_cast(Thread->CTX)->Config.DumpIR() != "no" || Thread->OpDispatcher->ShouldDumpIR(); + auto ShouldDump = Thread->OpDispatcher->ShouldDumpIR(); // Debug { if (ShouldDump) { diff --git a/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp b/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp index 5470b3d86..0a42971a5 100644 --- a/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp +++ b/External/FEXCore/Source/Interface/Core/OpcodeDispatcher.cpp @@ -4734,7 +4734,7 @@ void OpDispatchBuilder::CreateJumpBlocks(fextl::vector const *Blocks) { Entry = RIP; - auto IRHeader = _IRHeader(InvalidNode, 0); + auto IRHeader = _IRHeader(InvalidNode, RIP, 0); CreateJumpBlocks(Blocks); auto Block = GetNewJumpBlock(RIP); diff --git a/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp b/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp index 362904b40..779f9983d 100644 --- a/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp +++ b/External/FEXCore/Source/Interface/HLE/Thunks/Thunks.cpp @@ -208,7 +208,7 @@ namespace FEXCore { auto Result = Thread->CTX->AddCustomIREntrypoint( args->original_callee, [CTX, GuestThunkEntrypoint = args->target_addr](uintptr_t Entrypoint, FEXCore::IR::IREmitter *emit) { - auto IRHeader = emit->_IRHeader(emit->Invalid(), 0); + auto IRHeader = emit->_IRHeader(emit->Invalid(), Entrypoint, 0); auto Block = emit->CreateCodeNode(); IRHeader.first->Blocks = emit->WrapNode(Block); emit->SetCurrentCodeBlock(Block); diff --git a/External/FEXCore/Source/Interface/IR/IR.json b/External/FEXCore/Source/Interface/IR/IR.json index b3f31a7c1..9fada7479 100644 --- a/External/FEXCore/Source/Interface/IR/IR.json +++ b/External/FEXCore/Source/Interface/IR/IR.json @@ -160,7 +160,7 @@ "HasSideEffects": true, "SwitchGen": false }, - "IRHeader SSA:$Blocks, u32:$BlockCount": { + "IRHeader SSA:$Blocks, u64:$OriginalRIP, u32:$BlockCount": { "SwitchGen": false }, "CodeBlock SSA:$Begin, SSA:$Last": { diff --git a/External/FEXCore/Source/Interface/IR/IRParser.cpp b/External/FEXCore/Source/Interface/IR/IRParser.cpp index 40b883172..3f0be4b68 100644 --- a/External/FEXCore/Source/Interface/IR/IRParser.cpp +++ b/External/FEXCore/Source/Interface/IR/IRParser.cpp @@ -533,11 +533,15 @@ class IRParser: public FEXCore::IR::IREmitter { return false; } + auto OriginalRIP = DecodeValue(Def.Args[1]); + + if (!CheckPrintError(Def, OriginalRIP.first)) return false; + auto CodeBlockCount = DecodeValue(Def.Args[1]); if (!CheckPrintError(Def, CodeBlockCount.first)) return false; - IRHeader = _IRHeader(InvalidNode, CodeBlockCount.second); + IRHeader = _IRHeader(InvalidNode, OriginalRIP.second, CodeBlockCount.second); } SetWriteCursor(nullptr); // isolate the header from everything following diff --git a/External/FEXCore/Source/Interface/IR/PassManager.cpp b/External/FEXCore/Source/Interface/IR/PassManager.cpp index 41464f7f4..1d9b35e75 100644 --- a/External/FEXCore/Source/Interface/IR/PassManager.cpp +++ b/External/FEXCore/Source/Interface/IR/PassManager.cpp @@ -17,6 +17,54 @@ $end_info$ namespace FEXCore::IR { class IREmitter; +void PassManager::Finalize() { + if (!PassManagerDumpIR()) { + // Not configured to dump any IR, just return. + return; + } + + auto it = Passes.begin(); + // Walk the passes and add them where asked. + if (PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::BEFORE) { + // Insert at the start. + it = InsertAt(it, Debug::CreateIRDumper()); + ++it; // Skip what we inserted. + } + + if ((PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::BEFOREOPT) || + (PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::AFTEROPT)) { + + bool SkipFirstBefore = PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::BEFORE; + for (; it != Passes.end();) { + if (PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::BEFOREOPT) { + if (SkipFirstBefore) { + // If we need to skip the first one, then continue. + SkipFirstBefore = false; + ++it; + continue; + } + + // Insert before + it = InsertAt(it, Debug::CreateIRDumper()); + ++it; // Skip what we inserted. + } + + ++it; // Skip current pass. + if (PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::AFTEROPT) { + // Insert after + it = InsertAt(it, Debug::CreateIRDumper()); + ++it; // Skip what we inserted. + } + } + } + if (PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::AFTER) { + if (!(PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::AFTEROPT)) { + // Insert final IRDumper. + it = InsertAt(it, Debug::CreateIRDumper()); + } + } +} + void PassManager::AddDefaultPasses(FEXCore::Context::ContextImpl *ctx, bool InlineConstants, bool StaticRegisterAllocation) { FEX_CONFIG_OPT(DisablePasses, O0); diff --git a/External/FEXCore/Source/Interface/IR/PassManager.h b/External/FEXCore/Source/Interface/IR/PassManager.h index 704f800f5..8bbc97442 100644 --- a/External/FEXCore/Source/Interface/IR/PassManager.h +++ b/External/FEXCore/Source/Interface/IR/PassManager.h @@ -49,8 +49,7 @@ public: void AddDefaultPasses(FEXCore::Context::ContextImpl *ctx, bool InlineConstants, bool StaticRegisterAllocation); void AddDefaultValidationPasses(); Pass* InsertPass(fextl::unique_ptr Pass, fextl::string Name = "") { - Pass->RegisterPassManager(this); - auto PassPtr = Passes.emplace_back(std::move(Pass)).get(); + auto PassPtr = InsertAt(Passes.end(), std::move(Pass))->get(); if (!Name.empty()) { NameToPassMaping[Name] = PassPtr; @@ -83,12 +82,19 @@ public: SyscallHandler = Handler; } + void Finalize(); + protected: ShouldExitHandler ExitHandler; FEXCore::HLE::SyscallHandler *SyscallHandler; private: - fextl::vector> Passes; + using PassArrayType = fextl::vector>; + PassArrayType::iterator InsertAt(PassArrayType::iterator pos, fextl::unique_ptr Pass) { + Pass->RegisterPassManager(this); + return Passes.insert(pos, std::move(Pass)); + } + PassArrayType Passes; fextl::unordered_map NameToPassMaping; #if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED @@ -104,6 +110,7 @@ private: #endif FEX_CONFIG_OPT(Is64BitMode, IS64BIT_MODE); + FEX_CONFIG_OPT(PassManagerDumpIR, PASSMANAGERDUMPIR); }; } diff --git a/External/FEXCore/Source/Interface/IR/Passes.h b/External/FEXCore/Source/Interface/IR/Passes.h index d258b0fc3..26d0b91d5 100644 --- a/External/FEXCore/Source/Interface/IR/Passes.h +++ b/External/FEXCore/Source/Interface/IR/Passes.h @@ -29,5 +29,9 @@ fextl::unique_ptr CreateRAValidation(); fextl::unique_ptr CreatePhiValidation(); fextl::unique_ptr CreateValueDominanceValidation(); } + +namespace Debug { +fextl::unique_ptr CreateIRDumper(); +} } diff --git a/External/FEXCore/Source/Interface/IR/Passes/IRCompaction.cpp b/External/FEXCore/Source/Interface/IR/Passes/IRCompaction.cpp index adf1ffea7..fc5539c9d 100644 --- a/External/FEXCore/Source/Interface/IR/Passes/IRCompaction.cpp +++ b/External/FEXCore/Source/Interface/IR/Passes/IRCompaction.cpp @@ -98,7 +98,8 @@ bool IRCompaction::Run(IREmitter *IREmit) { // Zero is always zero(invalid) OldToNewRemap[0].NodeID.Invalidate(); - auto LocalHeaderOp = LocalBuilder._IRHeader(OrderedNodeWrapper::WrapOffset(0).GetNode(ListBegin), HeaderOp->BlockCount); + auto LocalHeaderOp = LocalBuilder._IRHeader(OrderedNodeWrapper::WrapOffset(0).GetNode(ListBegin), HeaderOp->OriginalRIP, HeaderOp->BlockCount); + OldToNewRemap[CurrentIR.GetID(HeaderNode).Value].NodeID = LocalIR.GetID(LocalHeaderOp.Node); { diff --git a/External/FEXCore/Source/Interface/IR/Passes/IRDumperPass.cpp b/External/FEXCore/Source/Interface/IR/Passes/IRDumperPass.cpp new file mode 100644 index 000000000..89d8151b2 --- /dev/null +++ b/External/FEXCore/Source/Interface/IR/Passes/IRDumperPass.cpp @@ -0,0 +1,84 @@ +/* +$info$ +tags: ir|debug +desc: Prints IR +$end_info$ +*/ + +#include "Interface/IR/PassManager.h" +#include "Interface/IR/Passes/RegisterAllocationPass.h" +#include "Interface/Core/OpcodeDispatcher.h" + +#include + +namespace FEXCore::IR::Debug { +class IRDumper final : public FEXCore::IR::Pass { +public: + IRDumper(); + bool Run(IREmitter *IREmit) override; + +private: + FEX_CONFIG_OPT(DumpIR, DUMPIR); + bool DumpToFile{}; + bool DumpToLog{}; +}; + +IRDumper::IRDumper() { + const auto DumpIRStr = DumpIR(); + if (DumpIRStr == "stderr" || DumpIRStr == "stdout" || DumpIRStr == "no") { + // Intentionally do nothing + } + else if (DumpIRStr == "server") { + DumpToLog = true; + } + else { + DumpToFile = true; + } +} + +bool IRDumper::Run(IREmitter *IREmit) { + auto RAPass = Manager->GetPass("RA"); + IR::RegisterAllocationData* RA{}; + if (RAPass) { + RA = RAPass->GetAllocationData(); + } + + FEXCore::File::File FD{}; + if (DumpIR() == "stderr") { + FD = FEXCore::File::File::GetStdERR(); + } + else if (DumpIR() == "stdout") { + FD = FEXCore::File::File::GetStdOUT(); + } + + auto IR = IREmit->ViewIR(); + auto HeaderOp = IR.GetHeader(); + LOGMAN_THROW_AA_FMT(HeaderOp->Header.Op == OP_IRHEADER, "First op wasn't IRHeader"); + + // DumpIRStr might be no if not dumping but ShouldDump is set in OpDisp + if (DumpToFile) { + const auto fileName = fextl::fmt::format("{}/{:x}{}", DumpIR(), HeaderOp->OriginalRIP, RA ? "-post.ir" : "-pre.ir"); + FD = FEXCore::File::File(fileName.c_str(), + FEXCore::File::FileModes::WRITE | + FEXCore::File::FileModes::CREATE | + FEXCore::File::FileModes::TRUNCATE); + } + + if (FD.IsValid() || DumpToLog) { + fextl::stringstream out; + FEXCore::IR::Dump(&out, &IR, RA); + if (FD.IsValid()) { + fextl::fmt::print(FD, "IR-{} 0x{:x}:\n{}\n@@@@@\n", RA ? "post" : "pre", HeaderOp->OriginalRIP, out.str()); + } + else { + LogMan::Msg::IFmt("IR-{} 0x{:x}:\n{}\n@@@@@\n", RA ? "post" : "pre", HeaderOp->OriginalRIP, out.str()); + } + } + + return false; +} + +fextl::unique_ptr CreateIRDumper() { + return fextl::make_unique(); +} +}