mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-07 01:00:17 +02:00
It turns out that pure SSA isn't a great choice for the sort of emulation we do.
On one hand, it discards information from the guest binary's register allocation
that would let us skip stuff. On the other hand, it doesn't have nearly as many
benefits in this setting as in a traditional compiler... We really *don't* want
to do global RA or really any global optimization. We assume the guest optimizer
did its job for x86, we just need to clean up the mess left from going x86 ->
arm. So we just need enough SSA to peephole optimize.
My concrete IR proposals are that:
* SSA values must be killed in the same block that they are defined.
* Explicit LoadGPR/StoreGPR instructions can be used for global persistence.
* LoadGPR/StoreGPR are eliminated in favour of SSA within a block.
This has a lot of nice properties for our setting:
* Except for some internal REP instruction emulation (etc), we already have
registers for everything that escapes block boundaries, so this form is very
easy to go into -- straightforward local value numbering, not a full into
SSA pass.
* Spilling is entirely local (if it happens at all), since everything is in
registers at block boundaries. This is excellent, because Belady's algorithm
lets us spill nearly optimally in linear-time for individual blocks. (And
the global version of Belady's algorithm is massively more complicated...)
A nice fit for a JIT.
Relatedly, it turns out allowing spilling is probably a decent decision,
since the same spiller code can be used to rematerialize constants in a
straightforward way. This is an issue with the current RA.
* Register assignment is entirely local. For the same reason, we can assign
registers "optimally" in linear time & memory (e.g. with linear scan). And
the impl is massively simpler than a full blown SSA-based tree scan RA. For
example, we don't have to worry about parallel copies or coalescing phis or
anything. Massively nicer algorithm to deal with.
* SSA value names can be block local which makes the validation implicit :~)
It also has remarkably few drawbacks, because we didn't want to do CFG global
optimization anyway given our time budget and the diminishng returns. The few
global optimizations we might want (flag escape analysis?) don't necessarily
benefit from pure SSA anyway.
Anyway, we explicitly don't want phi nodes in any of this. They're currently
unused. Let's just remove them so nobody gets the bright idea of changing that.
Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
125 lines
3.9 KiB
C++
125 lines
3.9 KiB
C++
/*
|
|
$info$
|
|
meta: ir|opts ~ IR to IR Optimization
|
|
tags: ir|opts
|
|
desc: Defines which passes are run, and runs them
|
|
$end_info$
|
|
*/
|
|
|
|
#include "Interface/Context/Context.h"
|
|
#include "Interface/IR/PassManager.h"
|
|
#include "Interface/IR/Passes.h"
|
|
#include "Interface/IR/Passes/RegisterAllocationPass.h"
|
|
|
|
#include <FEXCore/Config/Config.h>
|
|
#include <FEXCore/Utils/Profiler.h>
|
|
|
|
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::BEFOREOPT) {
|
|
// Insert at the start.
|
|
it = InsertAt(it, Debug::CreateIRDumper());
|
|
++it; // Skip what we inserted.
|
|
}
|
|
|
|
if ((PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::BEFOREPASS) ||
|
|
(PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::AFTERPASS)) {
|
|
|
|
bool SkipFirstBefore = PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::BEFOREOPT;
|
|
for (; it != Passes.end();) {
|
|
if (PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::BEFOREPASS) {
|
|
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::AFTERPASS) {
|
|
// Insert after
|
|
it = InsertAt(it, Debug::CreateIRDumper());
|
|
++it; // Skip what we inserted.
|
|
}
|
|
}
|
|
}
|
|
if (PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::AFTEROPT) {
|
|
if (!(PassManagerDumpIR() & FEXCore::Config::PassManagerDumpIR::AFTERPASS)) {
|
|
// Insert final IRDumper.
|
|
InsertAt(Passes.end(), Debug::CreateIRDumper());
|
|
}
|
|
}
|
|
}
|
|
|
|
void PassManager::AddDefaultPasses(FEXCore::Context::ContextImpl *ctx, bool InlineConstants, bool StaticRegisterAllocation) {
|
|
FEX_CONFIG_OPT(DisablePasses, O0);
|
|
|
|
if (!DisablePasses()) {
|
|
InsertPass(CreateContextLoadStoreElimination(ctx->HostFeatures.SupportsAVX));
|
|
|
|
if (Is64BitMode()) {
|
|
// This needs to run after RCLSE
|
|
// This only matters for 64-bit code since these instructions don't exist in 32-bit
|
|
InsertPass(CreateLongDivideEliminationPass());
|
|
}
|
|
|
|
InsertPass(CreateDeadStoreElimination(ctx->HostFeatures.SupportsAVX));
|
|
InsertPass(CreatePassDeadCodeElimination());
|
|
InsertPass(CreateConstProp(InlineConstants, ctx->HostFeatures.SupportsTSOImm9));
|
|
|
|
////// InsertPass(CreateDeadFlagCalculationEliminination());
|
|
|
|
InsertPass(CreateSyscallOptimization());
|
|
InsertPass(CreatePassDeadCodeElimination());
|
|
}
|
|
|
|
// If the IR is compacted post-RA then the node indexing gets messed up and the backend isn't able to find the register assigned to a node
|
|
// Compact before IR, don't worry about RA generating spills/fills
|
|
InsertPass(CreateIRCompaction(ctx->OpDispatcherAllocator), "Compaction");
|
|
}
|
|
|
|
void PassManager::AddDefaultValidationPasses() {
|
|
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
|
|
InsertValidationPass(Validation::CreateIRValidation(), "IRValidation");
|
|
InsertValidationPass(Validation::CreateRAValidation());
|
|
InsertValidationPass(Validation::CreateValueDominanceValidation());
|
|
#endif
|
|
}
|
|
|
|
void PassManager::InsertRegisterAllocationPass(bool OptimizeSRA, bool SupportsAVX) {
|
|
InsertPass(IR::CreateRegisterAllocationPass(GetPass("Compaction"), OptimizeSRA, SupportsAVX), "RA");
|
|
}
|
|
|
|
bool PassManager::Run(IREmitter *IREmit) {
|
|
FEXCORE_PROFILE_SCOPED("PassManager::Run");
|
|
|
|
bool Changed = false;
|
|
for (auto const &Pass : Passes) {
|
|
Changed |= Pass->Run(IREmit);
|
|
}
|
|
|
|
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
|
|
for (auto const &Pass : ValidationPasses) {
|
|
Changed |= Pass->Run(IREmit);
|
|
}
|
|
#endif
|
|
|
|
return Changed;
|
|
}
|
|
|
|
}
|