Files
FEX-Emu--FEX/FEXCore/Source/Interface/IR/IREmitter.h
T
Alyssa Rosenzweig 1338a99add IR: cap constant pool
If we have more constants than registers, something will be rematerialized. Use
a simple round-robin heuristic to pick instead of the better-but-slower approach
with RA. This is a heuristic to reduce JIT time with minimal impact on code
quality. In Instcountci, the only impact is a block in oblivion only increasing
instruction count by 0.2%. And moves of constants are free for cycles at least
on Firestorm, so this isn't where we want to spend piles of JIT time anyway.

Difference at 95.0% confidence
        -0.00138911 +/- 0.00104724
        -0.418608% +/- 0.315587%

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
2025-07-26 11:42:55 -04:00

354 lines
12 KiB
C++

// SPDX-License-Identifier: MIT
#pragma once
#include "Interface/IR/IR.h"
#include "Interface/IR/IntrusiveIRList.h"
#include <FEXCore/Core/CoreState.h>
#include <FEXCore/IR/IR.h>
#include <FEXCore/Utils/LogManager.h>
#include <FEXCore/fextl/vector.h>
#include <algorithm>
#include <stdint.h>
#include <string.h>
namespace FEXCore::IR {
class Pass;
class PassManager;
class IREmitter {
friend class FEXCore::IR::Pass;
friend class FEXCore::IR::PassManager;
public:
IREmitter(FEXCore::Utils::IntrusivePooledAllocator& ThreadAllocator)
: DualListData {ThreadAllocator, 8 * 1024 * 1024} {
ReownOrClaimBuffer();
ResetWorkingList();
}
virtual ~IREmitter() = default;
void ReownOrClaimBuffer() {
DualListData.ReownOrClaimBuffer();
}
void DelayedDisownBuffer() {
DualListData.DelayedDisownBuffer();
}
IRListView ViewIR() {
return IRListView(&DualListData);
}
void ResetWorkingList();
/**
* @name IR allocation routines
*
* @{ */
FEXCore::IR::RegisterClassType WalkFindRegClass(Ref Node);
// These handlers add cost to the constructor and destructor
// If it becomes an issue then blow them away
// GCC also generates some pretty atrocious code around these
// Use Clang!
#define IROP_ALLOCATE_HELPERS
#define IROP_DISPATCH_HELPERS
#include <FEXCore/IR/IRDefines.inc>
IRPair<IROp_Jump> _Jump() {
return _Jump(InvalidNode);
}
IRPair<IROp_CondJump> _CondJump(Ref ssa0, CondClassType cond = {COND_NEQ}) {
return _CondJump(ssa0, _Constant(0), InvalidNode, InvalidNode, cond, GetOpSize(ssa0));
}
IRPair<IROp_CondJump> _CondJump(Ref ssa0, Ref ssa1, Ref ssa2, CondClassType cond = {COND_NEQ}) {
return _CondJump(ssa0, _Constant(0), ssa1, ssa2, cond, GetOpSize(ssa0));
}
// TODO: Work to remove this implicit sized Select implementation.
IRPair<IROp_Select> _Select(uint8_t Cond, Ref ssa0, Ref ssa1, Ref ssa2, Ref ssa3, IR::OpSize CompareSize = OpSize::iUnsized) {
if (CompareSize == OpSize::iUnsized) {
CompareSize = std::max(OpSize::i32Bit, std::max(GetOpSize(ssa0), GetOpSize(ssa1)));
}
return _Select(std::max(OpSize::i32Bit, std::max(GetOpSize(ssa2), GetOpSize(ssa3))), CompareSize, CondClassType {Cond}, ssa0, ssa1, ssa2, ssa3);
}
IRPair<IROp_LoadMem> _LoadMem(FEXCore::IR::RegisterClassType Class, IR::OpSize Size, Ref ssa0, IR::OpSize Align = OpSize::i8Bit) {
return _LoadMem(Class, Size, ssa0, Invalid(), Align, MEM_OFFSET_SXTX, 1);
}
IRPair<IROp_StoreMem> _StoreMem(FEXCore::IR::RegisterClassType Class, IR::OpSize Size, Ref Addr, Ref Value, IR::OpSize Align = OpSize::i8Bit) {
return _StoreMem(Class, Size, Value, Addr, Invalid(), Align, MEM_OFFSET_SXTX, 1);
}
int64_t Constants[32];
Ref ConstantRefs[32];
uint32_t NrConstants;
Ref Constant(int64_t Value) {
// Search for the constant in the pool.
for (unsigned i = 0; i < std::min(NrConstants, 32u); ++i) {
if (Constants[i] == Value) {
return ConstantRefs[i];
}
}
// Otherwise, materialize a fresh constant and pool it.
Ref R = _Constant(Value);
unsigned i = (NrConstants++) & 31;
Constants[i] = Value;
ConstantRefs[i] = R;
return R;
}
Ref Invalid() {
return InvalidNode;
}
void SetJumpTarget(IR::IROp_Jump* Op, Ref Target) {
LOGMAN_THROW_A_FMT(Target->Op(DualListData.DataBegin())->Op == OP_CODEBLOCK, "Tried setting Jump target to %{} {}",
Target->Wrapped(DualListData.ListBegin()).ID(), IR::GetName(Target->Op(DualListData.DataBegin())->Op));
Op->Header.Args[0].NodeOffset = Target->Wrapped(DualListData.ListBegin()).NodeOffset;
}
void SetTrueJumpTarget(IR::IROp_CondJump* Op, Ref Target) {
LOGMAN_THROW_A_FMT(Target->Op(DualListData.DataBegin())->Op == OP_CODEBLOCK, "Tried setting CondJump target to %{} {}",
Target->Wrapped(DualListData.ListBegin()).ID(), IR::GetName(Target->Op(DualListData.DataBegin())->Op));
Op->TrueBlock.NodeOffset = Target->Wrapped(DualListData.ListBegin()).NodeOffset;
}
void SetFalseJumpTarget(IR::IROp_CondJump* Op, Ref Target) {
LOGMAN_THROW_A_FMT(Target->Op(DualListData.DataBegin())->Op == OP_CODEBLOCK, "Tried setting CondJump target to %{} {}",
Target->Wrapped(DualListData.ListBegin()).ID(), IR::GetName(Target->Op(DualListData.DataBegin())->Op));
Op->FalseBlock.NodeOffset = Target->Wrapped(DualListData.ListBegin()).NodeOffset;
}
void SetJumpTarget(IRPair<IROp_Jump> Op, Ref Target) {
LOGMAN_THROW_A_FMT(Target->Op(DualListData.DataBegin())->Op == OP_CODEBLOCK, "Tried setting Jump target to %{} {}",
Target->Wrapped(DualListData.ListBegin()).ID(), IR::GetName(Target->Op(DualListData.DataBegin())->Op));
Op.first->Header.Args[0].NodeOffset = Target->Wrapped(DualListData.ListBegin()).NodeOffset;
}
void SetTrueJumpTarget(IRPair<IROp_CondJump> Op, Ref Target) {
LOGMAN_THROW_A_FMT(Target->Op(DualListData.DataBegin())->Op == OP_CODEBLOCK, "Tried setting CondJump target to %{} {}",
Target->Wrapped(DualListData.ListBegin()).ID(), IR::GetName(Target->Op(DualListData.DataBegin())->Op));
Op.first->TrueBlock.NodeOffset = Target->Wrapped(DualListData.ListBegin()).NodeOffset;
}
void SetFalseJumpTarget(IRPair<IROp_CondJump> Op, Ref Target) {
LOGMAN_THROW_A_FMT(Target->Op(DualListData.DataBegin())->Op == OP_CODEBLOCK, "Tried setting CondJump target to %{} {}",
Target->Wrapped(DualListData.ListBegin()).ID(), IR::GetName(Target->Op(DualListData.DataBegin())->Op));
Op.first->FalseBlock.NodeOffset = Target->Wrapped(DualListData.ListBegin()).NodeOffset;
}
/** @} */
FEXCore::IR::RegisterClassType WalkFindRegClass(OrderedNodeWrapper ssa) {
Ref RealNode = ssa.GetNode(DualListData.ListBegin());
return WalkFindRegClass(RealNode);
}
bool IsValueConstant(OrderedNodeWrapper ssa, uint64_t* Constant = nullptr) {
Ref RealNode = ssa.GetNode(DualListData.ListBegin());
FEXCore::IR::IROp_Header* IROp = RealNode->Op(DualListData.DataBegin());
if (IROp->Op == OP_CONSTANT) {
auto Op = IROp->C<IR::IROp_Constant>();
if (Constant) {
*Constant = Op->Constant;
}
return true;
}
return false;
}
bool IsValueInlineConstant(OrderedNodeWrapper ssa) {
Ref RealNode = ssa.GetNode(DualListData.ListBegin());
FEXCore::IR::IROp_Header* IROp = RealNode->Op(DualListData.DataBegin());
if (IROp->Op == OP_INLINECONSTANT) {
return true;
}
return false;
}
FEXCore::IR::IROp_Header* GetOpHeader(OrderedNodeWrapper ssa) {
Ref RealNode = ssa.GetNode(DualListData.ListBegin());
return RealNode->Op(DualListData.DataBegin());
}
Ref UnwrapNode(OrderedNodeWrapper ssa) {
return ssa.GetNode(DualListData.ListBegin());
}
OrderedNodeWrapper WrapNode(Ref node) {
return node->Wrapped(DualListData.ListBegin());
}
NodeIterator GetIterator(OrderedNodeWrapper wrapper) {
return NodeIterator(DualListData.ListBegin(), DualListData.DataBegin(), wrapper);
}
void ReplaceAllUsesWithRange(Ref Node, Ref NewNode, AllNodesIterator Begin, AllNodesIterator End);
void ReplaceUsesWithAfter(Ref Node, Ref NewNode, AllNodesIterator After) {
++After;
ReplaceAllUsesWithRange(Node, NewNode, After, AllNodesIterator(DualListData.ListBegin(), DualListData.DataBegin()));
}
void ReplaceUsesWithAfter(Ref Node, Ref NewNode, Ref After) {
auto Wrapped = After->Wrapped(DualListData.ListBegin());
AllNodesIterator It = AllNodesIterator(DualListData.ListBegin(), DualListData.DataBegin(), Wrapped);
ReplaceUsesWithAfter(Node, NewNode, It);
}
void ReplaceNodeArgument(Ref Node, uint8_t Arg, Ref NewArg);
void Remove(Ref Node);
void RemovePostRA(Ref Node);
void SetPackedRFLAG(bool Lower8, Ref Src);
Ref GetPackedRFLAG(bool Lower8);
void CopyData(const IREmitter& rhs) {
LOGMAN_THROW_A_FMT(rhs.DualListData.DataBackingSize() <= DualListData.DataBackingSize(), "Trying to take ownership of data that is too "
"large");
LOGMAN_THROW_A_FMT(rhs.DualListData.ListBackingSize() <= DualListData.ListBackingSize(), "Trying to take ownership of data that is too "
"large");
DualListData.CopyData(rhs.DualListData);
InvalidNode = rhs.InvalidNode->Wrapped(rhs.DualListData.ListBegin()).GetNode(DualListData.ListBegin());
CurrentWriteCursor = rhs.CurrentWriteCursor;
CodeBlocks = rhs.CodeBlocks;
for (auto& CodeBlock : CodeBlocks) {
CodeBlock = CodeBlock->Wrapped(rhs.DualListData.ListBegin()).GetNode(DualListData.ListBegin());
}
}
void SetWriteCursor(Ref Node) {
CurrentWriteCursor = Node;
}
// Set cursor to write before Node
void SetWriteCursorBefore(Ref Node) {
auto IR = ViewIR();
auto Before = IR.at(Node);
--Before;
SetWriteCursor((*Before).Node);
}
Ref GetWriteCursor() {
return CurrentWriteCursor;
}
Ref GetCurrentBlock() {
return CurrentCodeBlock;
}
/**
* @brief This creates an orphaned code node
* The IROp backing is in the correct list but the OrderedNode lives outside of the list
*
* XXX: This is because we don't want code blocks to interleave with current instruction IR ops currently
* We can change this behaviour once we remove the old BeginBlock/EndBlock types
*
* @return OrderedNode
*/
IRPair<IROp_CodeBlock> CreateCodeNode(bool EntryPoint = false, uint32_t GuestEntryOffset = 0) {
SetWriteCursor(nullptr); // Orphan from any previous nodes
auto ID = ViewIR().GetHeader()->BlockCount++;
auto CodeNode = _CodeBlock(InvalidNode, InvalidNode, ID, EntryPoint, GuestEntryOffset);
CodeBlocks.emplace_back(CodeNode);
SetWriteCursor(nullptr); // Orphan from any future nodes
auto Begin = _BeginBlock(CodeNode);
CodeNode.first->Begin = Begin.Node->Wrapped(DualListData.ListBegin());
auto EndBlock = _EndBlock(CodeNode);
CodeNode.first->Last = EndBlock.Node->Wrapped(DualListData.ListBegin());
return CodeNode;
}
/**
* @name Links codeblocks together
* Codeblocks are singly linked so we need to walk the list forward if the linked block isn't isn't the last
*
* eq.
* CodeNode->Next -> Next
* to
* CodeNode->Next -> New -> Next
*
* @{ */
/** @} */
void LinkCodeBlocks(Ref CodeNode, Ref Next) {
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
FEXCore::IR::IROp_CodeBlock* CurrentIROp =
#endif
CodeNode->Op(DualListData.DataBegin())->CW<FEXCore::IR::IROp_CodeBlock>();
LOGMAN_THROW_A_FMT(CurrentIROp->Header.Op == IROps::OP_CODEBLOCK, "Invalid");
CodeNode->append(DualListData.ListBegin(), Next);
}
IRPair<IROp_CodeBlock> CreateNewCodeBlockAtEnd() {
return CreateNewCodeBlockAfter(nullptr);
}
IRPair<IROp_CodeBlock> CreateNewCodeBlockAfter(Ref insertAfter);
void SetCurrentCodeBlock(Ref Node);
protected:
void RemoveArgUses(Ref Node);
Ref CreateNode(IROp_Header* Op) {
uintptr_t ListBegin = DualListData.ListBegin();
size_t Size = sizeof(OrderedNode);
void* Ptr = DualListData.ListAllocate(Size);
Ref Node = new (Ptr) OrderedNode();
Node->Header.Value.SetOffset(DualListData.DataBegin(), reinterpret_cast<uintptr_t>(Op));
if (CurrentWriteCursor) {
CurrentWriteCursor->append(ListBegin, Node);
}
CurrentWriteCursor = Node;
return Node;
}
Ref GetNode(uint32_t SSANode) {
uintptr_t ListBegin = DualListData.ListBegin();
Ref Node = reinterpret_cast<Ref>(ListBegin + SSANode * sizeof(OrderedNode));
return Node;
}
Ref EmplaceOrphanedNode(Ref OldNode) {
size_t Size = sizeof(OrderedNode);
Ref Ptr = reinterpret_cast<Ref>(DualListData.ListAllocate(Size));
memcpy(Ptr, OldNode, Size);
return Ptr;
}
// MMX State can be either MMX (for 64bit) or x87 FPU (for 80bit)
enum { MMXState_MMX, MMXState_X87 } MMXState = MMXState_MMX;
// Overriden by dispatcher, stubbed for IR tests
virtual void RecordX87Use() {}
virtual void ChgStateX87_MMX() {}
virtual void ChgStateMMX_X87() {}
virtual void SaveNZCV(IROps Op) {}
Ref CurrentWriteCursor = nullptr;
// These could be combined with a little bit of work to be more efficient with memory usage. Isn't a big deal
DualIntrusiveAllocatorThreadPool DualListData;
Ref InvalidNode {};
Ref CurrentCodeBlock {};
fextl::vector<Ref> CodeBlocks;
uint64_t Entry {};
};
} // namespace FEXCore::IR