mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 18:00:17 +02:00
now that the algebraic/folding opts are gone, we can do this in one pass for a
2.5% speedup:
N Min Max Median Avg Stddev
x 50 0.44474704 0.46750433 0.45455258 0.45446569 0.0044727894
+ 50 0.43149892 0.45173984 0.44252267 0.44295575 0.0045621814
Difference at 95.0% confidence
-0.0115099 +/- 0.00179263
-2.53263% +/- 0.394447%
(Student's t, pooled s = 0.00451771)
Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
402 lines
13 KiB
C++
402 lines
13 KiB
C++
// SPDX-License-Identifier: MIT
|
|
/*
|
|
$info$
|
|
tags: ir|opts
|
|
desc: ConstProp, ZExt elim, const pooling, fcmp reduction, const inlining
|
|
$end_info$
|
|
*/
|
|
#include <CodeEmitter/Emitter.h>
|
|
|
|
#include "Interface/IR/IREmitter.h"
|
|
#include "Interface/IR/PassManager.h"
|
|
|
|
#include <FEXCore/IR/IR.h>
|
|
#include <FEXCore/HLE/SyscallHandler.h>
|
|
#include <FEXCore/Utils/LogManager.h>
|
|
#include <FEXCore/Utils/Profiler.h>
|
|
#include <FEXCore/fextl/map.h>
|
|
#include <FEXCore/fextl/unordered_map.h>
|
|
|
|
#include <cstdint>
|
|
#include <string.h>
|
|
|
|
namespace FEXCore::IR {
|
|
|
|
// aarch64 heuristics
|
|
static bool IsImmLogical(uint64_t imm, unsigned width) {
|
|
if (width < 32) {
|
|
width = 32;
|
|
}
|
|
return ARMEmitter::Emitter::IsImmLogical(imm, width);
|
|
}
|
|
|
|
class ConstProp final : public FEXCore::IR::Pass {
|
|
public:
|
|
explicit ConstProp(bool SupportsTSOImm9)
|
|
: SupportsTSOImm9 {SupportsTSOImm9} {}
|
|
|
|
void Run(IREmitter* IREmit) override;
|
|
|
|
private:
|
|
void ConstantPropagation(IREmitter* IREmit, const IRListView& CurrentIR, Ref CodeNode, IROp_Header* IROp);
|
|
|
|
bool SupportsTSOImm9 {};
|
|
|
|
template<class F>
|
|
bool InlineIf(IREmitter* IREmit, const IRListView& CurrentIR, Ref CodeNode, IROp_Header* IROp, unsigned Index, F Filter) {
|
|
uint64_t Constant;
|
|
if (!IREmit->IsValueConstant(IROp->Args[Index], &Constant) || !Filter(Constant)) {
|
|
return false;
|
|
}
|
|
|
|
IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[Index]));
|
|
IREmit->ReplaceNodeArgument(CodeNode, Index, IREmit->_InlineConstant(Constant));
|
|
return true;
|
|
}
|
|
|
|
bool Inline(IREmitter* IREmit, const IRListView& CurrentIR, Ref CodeNode, IROp_Header* IROp, unsigned Index) {
|
|
return InlineIf(IREmit, CurrentIR, CodeNode, IROp, Index, [](uint64_t _) { return true; });
|
|
}
|
|
|
|
bool InlineIfZero(IREmitter* IREmit, const IRListView& CurrentIR, Ref CodeNode, IROp_Header* IROp, unsigned Index) {
|
|
return InlineIf(IREmit, CurrentIR, CodeNode, IROp, Index, [](uint64_t X) { return X == 0; });
|
|
}
|
|
|
|
bool InlineIfLargeAddSub(IREmitter* IREmit, const IRListView& CurrentIR, Ref CodeNode, IROp_Header* IROp, unsigned Index) {
|
|
// We don't allow 8/16-bit operations to have constants, since no
|
|
// constant would be in bounds after the JIT's 24/16 shift.
|
|
auto Filter = [&IROp](uint64_t X) {
|
|
return ARMEmitter::IsImmAddSub(X) && IROp->Size >= OpSize::i32Bit;
|
|
};
|
|
|
|
return InlineIf(IREmit, CurrentIR, CodeNode, IROp, Index, Filter);
|
|
}
|
|
|
|
void InlineMemImmediate(IREmitter* IREmit, const IRListView& IR, Ref CodeNode, IR::RegisterClassType RegisterClass, IROp_Header* IROp,
|
|
OrderedNodeWrapper Offset, MemOffsetType OffsetType, const size_t Offset_Index, uint8_t& OffsetScale, bool TSO) {
|
|
uint64_t Imm {};
|
|
if (OffsetType != MEM_OFFSET_SXTX || !IREmit->IsValueConstant(Offset, &Imm)) {
|
|
return;
|
|
}
|
|
|
|
// The immediate may be scaled in the IR, we need to correct for that.
|
|
Imm *= OffsetScale;
|
|
|
|
// Signed immediate unscaled 9-bit range for both regular and LRCPC2 ops.
|
|
bool IsSIMM9 = ((int64_t)Imm >= -256) && ((int64_t)Imm <= 255);
|
|
IsSIMM9 &= (SupportsTSOImm9 || !TSO);
|
|
|
|
// Extended offsets for regular loadstore only.
|
|
LOGMAN_THROW_A_FMT(IROp->Size >= IR::OpSize::i8Bit && IROp->Size <= (RegisterClass == GPRClass ? IR::OpSize::i64Bit : IR::OpSize::i256Bit),
|
|
"Invalid "
|
|
"size");
|
|
bool IsExtended = (Imm & (IR::OpSizeToSize(IROp->Size) - 1)) == 0 && Imm / IR::OpSizeToSize(IROp->Size) <= 4095;
|
|
IsExtended &= !TSO;
|
|
|
|
if (IsSIMM9 || IsExtended) {
|
|
IREmit->SetWriteCursor(IR.GetNode(Offset));
|
|
IREmit->ReplaceNodeArgument(CodeNode, Offset_Index, IREmit->_InlineConstant(Imm));
|
|
OffsetScale = 1;
|
|
}
|
|
}
|
|
};
|
|
|
|
// constprop + some more per instruction logic
|
|
void ConstProp::ConstantPropagation(IREmitter* IREmit, const IRListView& CurrentIR, Ref CodeNode, IROp_Header* IROp) {
|
|
switch (IROp->Op) {
|
|
case OP_ADD:
|
|
case OP_SUB:
|
|
case OP_ADDWITHFLAGS:
|
|
case OP_SUBWITHFLAGS: {
|
|
auto Op = IROp->C<IR::IROp_Add>();
|
|
uint64_t Constant1 {};
|
|
uint64_t Constant2 {};
|
|
bool IsConstant2 = IREmit->IsValueConstant(IROp->Args[1], &Constant2);
|
|
|
|
/* IsImmAddSub assumes the constants are sign-extended, take care of that
|
|
* here so we get the optimization for 32-bit adds too.
|
|
*/
|
|
if (Op->Header.Size == OpSize::i32Bit) {
|
|
Constant1 = (int64_t)(int32_t)Constant1;
|
|
Constant2 = (int64_t)(int32_t)Constant2;
|
|
}
|
|
|
|
if (IsConstant2 && !ARMEmitter::IsImmAddSub(Constant2) && ARMEmitter::IsImmAddSub(-Constant2)) {
|
|
// If the second argument is constant, the immediate is not ImmAddSub, but when negated is.
|
|
// So, negate the operation to negate (and inline) the constant.
|
|
if (IROp->Op == OP_ADD) {
|
|
IROp->Op = OP_SUB;
|
|
} else if (IROp->Op == OP_SUB) {
|
|
IROp->Op = OP_ADD;
|
|
} else if (IROp->Op == OP_ADDWITHFLAGS) {
|
|
IROp->Op = OP_SUBWITHFLAGS;
|
|
} else if (IROp->Op == OP_SUBWITHFLAGS) {
|
|
IROp->Op = OP_ADDWITHFLAGS;
|
|
}
|
|
|
|
IREmit->SetWriteCursorBefore(CodeNode);
|
|
|
|
// Negate the constant.
|
|
auto NegConstant = IREmit->_Constant(-Constant2);
|
|
|
|
// Replace the second source with the negated constant.
|
|
IREmit->ReplaceNodeArgument(CodeNode, Op->Src2_Index, NegConstant);
|
|
}
|
|
|
|
if (!InlineIfLargeAddSub(IREmit, CurrentIR, CodeNode, IROp, 1) && (IROp->Op == OP_SUB || IROp->Op == OP_SUBWITHFLAGS)) {
|
|
// TODO: Generalize this
|
|
InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, 0);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case OP_ADDNZCV: {
|
|
InlineIfLargeAddSub(IREmit, CurrentIR, CodeNode, IROp, 1);
|
|
break;
|
|
}
|
|
case OP_SUBNZCV: {
|
|
if (!InlineIfLargeAddSub(IREmit, CurrentIR, CodeNode, IROp, 1)) {
|
|
// TODO: Generalize this
|
|
InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, 0);
|
|
}
|
|
break;
|
|
}
|
|
case OP_AND:
|
|
case OP_OR:
|
|
case OP_XOR: {
|
|
InlineIf(IREmit, CurrentIR, CodeNode, IROp, 1, [&IROp](uint64_t X) { return IsImmLogical(X, IR::OpSizeAsBits(IROp->Size)); });
|
|
break;
|
|
}
|
|
case OP_ANDWITHFLAGS:
|
|
case OP_ANDN:
|
|
case OP_TESTNZ: {
|
|
InlineIf(IREmit, CurrentIR, CodeNode, IROp, 1, [&IROp](uint64_t X) { return IsImmLogical(X, IR::OpSizeAsBits(IROp->Size)); });
|
|
break;
|
|
}
|
|
case OP_ASHR:
|
|
case OP_ROR: {
|
|
Inline(IREmit, CurrentIR, CodeNode, IROp, 1);
|
|
break;
|
|
}
|
|
case OP_LSHL: {
|
|
Inline(IREmit, CurrentIR, CodeNode, IROp, 1);
|
|
break;
|
|
}
|
|
case OP_LSHR: {
|
|
Inline(IREmit, CurrentIR, CodeNode, IROp, 1);
|
|
break;
|
|
}
|
|
case OP_ADC:
|
|
case OP_ADCWITHFLAGS:
|
|
case OP_RMIFNZCV: {
|
|
InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, 0);
|
|
break;
|
|
}
|
|
case OP_STORECONTEXT: {
|
|
// For i128Bit, we won't see a normal Constant to inline, but as a special
|
|
// case we can replace with a 2x64-bit store which can use inline zeroes.
|
|
if (IROp->Size == OpSize::i128Bit) {
|
|
auto Op = IROp->C<IR::IROp_StoreContext>();
|
|
auto Header = IREmit->GetOpHeader(IROp->Args[0]);
|
|
const auto MAX_STP_OFFSET = (252 * 4);
|
|
|
|
if (Op->Offset <= MAX_STP_OFFSET && Header->Op == OP_LOADNAMEDVECTORCONSTANT) {
|
|
auto Const = Header->C<IR::IROp_LoadNamedVectorConstant>();
|
|
|
|
if (Const->Constant == IR::NamedVectorConstant::NAMED_VECTOR_ZERO) {
|
|
IREmit->SetWriteCursor(CodeNode);
|
|
Ref Zero = IREmit->_Constant(0);
|
|
Ref STP = IREmit->_StoreContextPair(IR::OpSize::i64Bit, GPRClass, Zero, Zero, Op->Offset);
|
|
IREmit->Remove(CodeNode);
|
|
|
|
// XXX: This works around InlineConstant not having an associated
|
|
// register class, else we'd just do InlineConstant above.
|
|
Ref InlineZero = IREmit->_InlineConstant(0);
|
|
IREmit->ReplaceNodeArgument(STP, 0, InlineZero);
|
|
IREmit->ReplaceNodeArgument(STP, 1, InlineZero);
|
|
}
|
|
}
|
|
} else {
|
|
InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, 0);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_CONDADDNZCV:
|
|
case OP_CONDSUBNZCV: {
|
|
InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, 0);
|
|
InlineIf(IREmit, CurrentIR, CodeNode, IROp, 1, ARMEmitter::IsImmAddSub);
|
|
break;
|
|
}
|
|
case OP_SELECT: {
|
|
InlineIf(IREmit, CurrentIR, CodeNode, IROp, 1, ARMEmitter::IsImmAddSub);
|
|
|
|
uint64_t AllOnes = IROp->Size == OpSize::i64Bit ? 0xffff'ffff'ffff'ffffull : 0xffff'ffffull;
|
|
|
|
uint64_t Constant2 {};
|
|
uint64_t Constant3 {};
|
|
if (IREmit->IsValueConstant(IROp->Args[2], &Constant2) && IREmit->IsValueConstant(IROp->Args[3], &Constant3) &&
|
|
(Constant2 == 1 || Constant2 == AllOnes) && Constant3 == 0) {
|
|
IREmit->SetWriteCursor(CurrentIR.GetNode(IROp->Args[2]));
|
|
|
|
IREmit->ReplaceNodeArgument(CodeNode, 2, IREmit->_InlineConstant(Constant2));
|
|
IREmit->ReplaceNodeArgument(CodeNode, 3, IREmit->_InlineConstant(Constant3));
|
|
}
|
|
|
|
break;
|
|
}
|
|
case OP_NZCVSELECT: {
|
|
// We always allow source 1 to be zero, but source 0 can only be a
|
|
// special 1/~0 constant if source 1 is 0.
|
|
if (InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, 1)) {
|
|
uint64_t AllOnes = IROp->Size == OpSize::i64Bit ? 0xffff'ffff'ffff'ffffull : 0xffff'ffffull;
|
|
InlineIf(IREmit, CurrentIR, CodeNode, IROp, 0, [&AllOnes](uint64_t X) { return X == 1 || X == AllOnes; });
|
|
}
|
|
break;
|
|
}
|
|
case OP_CONDJUMP: {
|
|
InlineIf(IREmit, CurrentIR, CodeNode, IROp, 1, ARMEmitter::IsImmAddSub);
|
|
break;
|
|
}
|
|
case OP_EXITFUNCTION: {
|
|
auto Op = IROp->C<IR::IROp_ExitFunction>();
|
|
|
|
if (!Inline(IREmit, CurrentIR, CodeNode, IROp, Op->NewRIP_Index)) {
|
|
auto NewRIP = IREmit->GetOpHeader(Op->NewRIP);
|
|
if (NewRIP->Op == OP_ENTRYPOINTOFFSET) {
|
|
auto EO = NewRIP->C<IR::IROp_EntrypointOffset>();
|
|
IREmit->SetWriteCursor(CurrentIR.GetNode(Op->NewRIP));
|
|
|
|
IREmit->ReplaceNodeArgument(CodeNode, 0, IREmit->_InlineEntrypointOffset(EO->Header.Size, EO->Offset));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case OP_LOADMEM: {
|
|
auto Op = IROp->CW<IR::IROp_LoadMem>();
|
|
InlineMemImmediate(IREmit, CurrentIR, CodeNode, Op->Class, IROp, Op->Offset, Op->OffsetType, Op->Offset_Index, Op->OffsetScale, false);
|
|
break;
|
|
}
|
|
case OP_STOREMEM: {
|
|
auto Op = IROp->CW<IR::IROp_StoreMem>();
|
|
InlineMemImmediate(IREmit, CurrentIR, CodeNode, Op->Class, IROp, Op->Offset, Op->OffsetType, Op->Offset_Index, Op->OffsetScale, false);
|
|
InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, Op->Value_Index);
|
|
break;
|
|
}
|
|
case OP_PREFETCH: {
|
|
auto Op = IROp->CW<IR::IROp_Prefetch>();
|
|
InlineMemImmediate(IREmit, CurrentIR, CodeNode, GPRClass, IROp, Op->Offset, Op->OffsetType, Op->Offset_Index, Op->OffsetScale, false);
|
|
break;
|
|
}
|
|
case OP_LOADMEMTSO: {
|
|
auto Op = IROp->CW<IR::IROp_LoadMemTSO>();
|
|
InlineMemImmediate(IREmit, CurrentIR, CodeNode, Op->Class, IROp, Op->Offset, Op->OffsetType, Op->Offset_Index, Op->OffsetScale, true);
|
|
break;
|
|
}
|
|
case OP_STOREMEMTSO: {
|
|
auto Op = IROp->CW<IR::IROp_StoreMemTSO>();
|
|
InlineMemImmediate(IREmit, CurrentIR, CodeNode, Op->Class, IROp, Op->Offset, Op->OffsetType, Op->Offset_Index, Op->OffsetScale, true);
|
|
InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, Op->Value_Index);
|
|
break;
|
|
}
|
|
case OP_STOREMEMPAIR: {
|
|
auto Op = IROp->CW<IR::IROp_StoreMemPair>();
|
|
InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, Op->Value1_Index);
|
|
InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, Op->Value2_Index);
|
|
break;
|
|
}
|
|
case OP_MEMCPY: {
|
|
auto Op = IROp->CW<IR::IROp_MemCpy>();
|
|
Inline(IREmit, CurrentIR, CodeNode, IROp, Op->Direction_Index);
|
|
break;
|
|
}
|
|
case OP_MEMSET: {
|
|
auto Op = IROp->CW<IR::IROp_MemSet>();
|
|
Inline(IREmit, CurrentIR, CodeNode, IROp, Op->Direction_Index);
|
|
InlineIfZero(IREmit, CurrentIR, CodeNode, IROp, Op->Value_Index);
|
|
break;
|
|
}
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
void ConstProp::Run(IREmitter* IREmit) {
|
|
FEXCORE_PROFILE_SCOPED("PassManager::ConstProp");
|
|
|
|
auto CurrentIR = IREmit->ViewIR();
|
|
const uint32_t SSACount = CurrentIR.GetSSACount();
|
|
|
|
// Allocation/initialization deferred until first use, since many multiblocks
|
|
// don't have constants leftover after all inlining.
|
|
fextl::vector<Ref> Remap {};
|
|
|
|
struct Entry {
|
|
int64_t Value;
|
|
Ref R;
|
|
};
|
|
|
|
fextl::vector<Entry> Pool {};
|
|
|
|
for (auto [BlockNode, BlockIROp] : CurrentIR.GetBlocks()) {
|
|
Pool.clear();
|
|
|
|
for (auto [CodeNode, IROp] : CurrentIR.GetCode(BlockNode)) {
|
|
if (IROp->Op == OP_CONSTANT) {
|
|
auto Op = IROp->C<IR::IROp_Constant>();
|
|
bool Found = false;
|
|
|
|
// Search for the constant. This is O(n^2) but n is small since it's
|
|
// local and most constants are inlined. In practice, it ends up much
|
|
// faster than a hash table.
|
|
for (auto K : Pool) {
|
|
if (K.Value == Op->Constant) {
|
|
uint32_t Value = CurrentIR.GetID(CodeNode).Value;
|
|
if (Value < SSACount) {
|
|
if (Remap.empty()) {
|
|
Remap.resize(SSACount, nullptr);
|
|
}
|
|
|
|
Remap[Value] = K.R;
|
|
}
|
|
Found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!Found) {
|
|
Pool.push_back({.Value = Op->Constant, .R = CodeNode});
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
ConstantPropagation(IREmit, CurrentIR, CodeNode, IROp);
|
|
|
|
if (!Remap.empty()) {
|
|
const uint8_t NumArgs = IR::GetArgs(IROp->Op);
|
|
for (uint8_t i = 0; i < NumArgs; ++i) {
|
|
if (IROp->Args[i].IsInvalid()) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t Value = IROp->Args[i].ID().Value;
|
|
if (Value < SSACount) {
|
|
Ref New = Remap[Value];
|
|
if (New) {
|
|
IREmit->ReplaceNodeArgument(CodeNode, i, New);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fextl::unique_ptr<FEXCore::IR::Pass> CreateConstProp(bool SupportsTSOImm9) {
|
|
return fextl::make_unique<ConstProp>(SupportsTSOImm9);
|
|
}
|
|
} // namespace FEXCore::IR
|