From 35e97ec9bcda1978a5672940f4883c8a4fac8e09 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Wed, 9 Aug 2023 03:34:43 -0700 Subject: [PATCH] IR: Adds Option to run the IRDumper with more configurations This is incredibly useful and I find myself hacking this feature in every time I am optimizing IR. Adds a new configuration option which allows dumping IR at various times. Before any optimization passes has happened After all optimizations passes have happened Before and After each IRPass to see what is breaking something. Needs #2864 merged first --- External/FEXCore/Source/CMakeLists.txt | 1 + .../Source/Interface/Config/Config.json.in | 18 ++++ .../Source/Interface/Context/Context.h | 1 - .../FEXCore/Source/Interface/Core/Core.cpp | 33 ++------ .../Interface/Core/OpcodeDispatcher.cpp | 2 +- .../Source/Interface/HLE/Thunks/Thunks.cpp | 2 +- External/FEXCore/Source/Interface/IR/IR.json | 2 +- .../FEXCore/Source/Interface/IR/IRParser.cpp | 6 +- .../Source/Interface/IR/PassManager.cpp | 48 +++++++++++ .../FEXCore/Source/Interface/IR/PassManager.h | 13 ++- External/FEXCore/Source/Interface/IR/Passes.h | 4 + .../Interface/IR/Passes/IRCompaction.cpp | 3 +- .../Interface/IR/Passes/IRDumperPass.cpp | 84 +++++++++++++++++++ 13 files changed, 183 insertions(+), 34 deletions(-) create mode 100644 External/FEXCore/Source/Interface/IR/Passes/IRDumperPass.cpp 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(); +} +}