mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 19:00:17 +02:00
When the result of an atomic fetch operation is unused then we can safely convert it to a non-fetching version of the operation. This happens hundreds of times per process as far as I can tell. No idea if this actually helps any hardware, but theoretically it can allow CPUs to not stall waiting for writeback of the atomic operation. Couldn't detect any performance improvements in the various little things I was poking at least. Very trivial to support so add it. Unaligned variants are already handled in our unaligned fault handler since the only difference is the acquire semantic is dropped and the destination register is the zero register.
132 lines
3.5 KiB
C++
132 lines
3.5 KiB
C++
// SPDX-License-Identifier: MIT
|
|
/*
|
|
$info$
|
|
tags: ir|opts
|
|
$end_info$
|
|
*/
|
|
|
|
#include "Interface/IR/PassManager.h"
|
|
|
|
#include <FEXCore/IR/IR.h>
|
|
#include <FEXCore/IR/IREmitter.h>
|
|
#include <FEXCore/IR/IntrusiveIRList.h>
|
|
#include <FEXCore/Utils/Profiler.h>
|
|
|
|
#include <memory>
|
|
|
|
namespace FEXCore::IR {
|
|
|
|
class DeadCodeElimination final : public FEXCore::IR::Pass {
|
|
bool Run(IREmitter *IREmit) override;
|
|
|
|
private:
|
|
void markUsed(OrderedNodeWrapper *CodeOp, IROp_Header *IROp);
|
|
};
|
|
|
|
bool DeadCodeElimination::Run(IREmitter *IREmit) {
|
|
FEXCORE_PROFILE_SCOPED("PassManager::DCE");
|
|
auto CurrentIR = IREmit->ViewIR();
|
|
bool Changed = false;
|
|
|
|
for (auto [BlockNode, BlockHeader] : CurrentIR.GetBlocks()) {
|
|
|
|
// Reverse iteration is not yet working with the iterators
|
|
auto BlockIROp = BlockHeader->CW<FEXCore::IR::IROp_CodeBlock>();
|
|
|
|
// We grab these nodes this way so we can iterate easily
|
|
auto CodeBegin = CurrentIR.at(BlockIROp->Begin);
|
|
auto CodeLast = CurrentIR.at(BlockIROp->Last);
|
|
|
|
while (1) {
|
|
auto [CodeNode, IROp] = CodeLast();
|
|
|
|
bool HasSideEffects = IR::HasSideEffects(IROp->Op);
|
|
|
|
switch (IROp->Op) {
|
|
case OP_SYSCALL:
|
|
case OP_INLINESYSCALL: {
|
|
FEXCore::IR::SyscallFlags Flags{};
|
|
if (IROp->Op == OP_SYSCALL) {
|
|
auto Op = IROp->C<IR::IROp_Syscall>();
|
|
Flags = Op->Flags;
|
|
}
|
|
else {
|
|
auto Op = IROp->C<IR::IROp_InlineSyscall>();
|
|
Flags = Op->Flags;
|
|
}
|
|
|
|
if ((Flags & FEXCore::IR::SyscallFlags::NOSIDEEFFECTS) == FEXCore::IR::SyscallFlags::NOSIDEEFFECTS) {
|
|
HasSideEffects = false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case OP_ATOMICFETCHADD:
|
|
case OP_ATOMICFETCHSUB:
|
|
case OP_ATOMICFETCHAND:
|
|
case OP_ATOMICFETCHCLR:
|
|
case OP_ATOMICFETCHOR:
|
|
case OP_ATOMICFETCHXOR:
|
|
case OP_ATOMICFETCHNEG: {
|
|
// If the result of the atomic fetch is completely unused, convert it to a non-fetching atomic operation.
|
|
if (CodeNode->GetUses() == 0) {
|
|
switch (IROp->Op) {
|
|
case OP_ATOMICFETCHADD:
|
|
IROp->Op = OP_ATOMICADD;
|
|
break;
|
|
case OP_ATOMICFETCHSUB:
|
|
IROp->Op = OP_ATOMICSUB;
|
|
break;
|
|
case OP_ATOMICFETCHAND:
|
|
IROp->Op = OP_ATOMICAND;
|
|
break;
|
|
case OP_ATOMICFETCHCLR:
|
|
IROp->Op = OP_ATOMICCLR;
|
|
break;
|
|
case OP_ATOMICFETCHOR:
|
|
IROp->Op = OP_ATOMICOR;
|
|
break;
|
|
case OP_ATOMICFETCHXOR:
|
|
IROp->Op = OP_ATOMICXOR;
|
|
break;
|
|
case OP_ATOMICFETCHNEG:
|
|
IROp->Op = OP_ATOMICNEG;
|
|
break;
|
|
default: FEX_UNREACHABLE;
|
|
}
|
|
Changed = true;
|
|
}
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
// Skip over anything that has side effects
|
|
// Use count tracking can't safely remove anything with side effects
|
|
if (!HasSideEffects) {
|
|
if (CodeNode->GetUses() == 0) {
|
|
Changed = true;
|
|
IREmit->Remove(CodeNode);
|
|
}
|
|
}
|
|
|
|
if (CodeLast == CodeBegin) {
|
|
break;
|
|
}
|
|
--CodeLast;
|
|
}
|
|
}
|
|
|
|
return Changed;
|
|
}
|
|
|
|
void DeadCodeElimination::markUsed(OrderedNodeWrapper *CodeOp, IROp_Header *IROp) {
|
|
|
|
}
|
|
|
|
fextl::unique_ptr<FEXCore::IR::Pass> CreatePassDeadCodeElimination() {
|
|
return fextl::make_unique<DeadCodeElimination>();
|
|
}
|
|
|
|
}
|