mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-11 20:00:39 +02:00
It has been a long time coming that FEX no longer needed to leak IR implementation details to the frontend, this was legacy due to IR CI and various other problems. Now that the last bits of IR leaking has been removed, move everything that we can internally to the implementation. We still have a couple of minor details in the exposed IR.h to the frontend, but these are limited to a few enums and some thunking struct information rather than all the implementation details. No functional change with this, just moving headers around.
220 lines
5.8 KiB
C++
220 lines
5.8 KiB
C++
// SPDX-License-Identifier: MIT
|
|
/*
|
|
$info$
|
|
meta: ir|emitter ~ C++ Functions to generate IR. See IR.json for spec.
|
|
tags: ir|emitter
|
|
$end_info$
|
|
*/
|
|
|
|
#include "Interface/IR/IREmitter.h"
|
|
|
|
#include <FEXCore/IR/IR.h>
|
|
#include <FEXCore/Utils/EnumUtils.h>
|
|
#include <FEXCore/Utils/LogManager.h>
|
|
|
|
#include <array>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
namespace FEXCore::IR {
|
|
|
|
bool IsFragmentExit(FEXCore::IR::IROps Op) {
|
|
switch (Op) {
|
|
case OP_EXITFUNCTION:
|
|
case OP_BREAK:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IsBlockExit(FEXCore::IR::IROps Op) {
|
|
switch(Op) {
|
|
case OP_JUMP:
|
|
case OP_CONDJUMP:
|
|
return true;
|
|
default:
|
|
return IsFragmentExit(Op);
|
|
}
|
|
}
|
|
|
|
FEXCore::IR::RegisterClassType IREmitter::WalkFindRegClass(OrderedNode *Node) {
|
|
auto Class = GetOpRegClass(Node);
|
|
switch (Class) {
|
|
case GPRClass:
|
|
case GPRPairClass:
|
|
case FPRClass:
|
|
case GPRFixedClass:
|
|
case FPRFixedClass:
|
|
case InvalidClass:
|
|
return Class;
|
|
default: break;
|
|
}
|
|
|
|
// Complex case, needs to be handled on an op by op basis
|
|
uintptr_t DataBegin = DualListData.DataBegin();
|
|
|
|
FEXCore::IR::IROp_Header *IROp = Node->Op(DataBegin);
|
|
|
|
switch (IROp->Op) {
|
|
case IROps::OP_LOADREGISTER: {
|
|
auto Op = IROp->C<IROp_LoadRegister>();
|
|
return Op->Class;
|
|
break;
|
|
}
|
|
case IROps::OP_LOADCONTEXT: {
|
|
auto Op = IROp->C<IROp_LoadContext>();
|
|
return Op->Class;
|
|
break;
|
|
}
|
|
case IROps::OP_LOADCONTEXTINDEXED: {
|
|
auto Op = IROp->C<IROp_LoadContextIndexed>();
|
|
return Op->Class;
|
|
break;
|
|
}
|
|
case IROps::OP_FILLREGISTER: {
|
|
auto Op = IROp->C<IROp_FillRegister>();
|
|
return Op->Class;
|
|
break;
|
|
}
|
|
case IROps::OP_LOADMEM: {
|
|
auto Op = IROp->C<IROp_LoadMem>();
|
|
return Op->Class;
|
|
break;
|
|
}
|
|
case IROps::OP_LOADMEMTSO: {
|
|
auto Op = IROp->C<IROp_LoadMemTSO>();
|
|
return Op->Class;
|
|
break;
|
|
}
|
|
default:
|
|
LOGMAN_MSG_A_FMT("Unhandled op type: {} {} in argument class validation",
|
|
ToUnderlying(IROp->Op), GetOpName(Node));
|
|
break;
|
|
}
|
|
return InvalidClass;
|
|
}
|
|
|
|
void IREmitter::ResetWorkingList() {
|
|
DualListData.Reset();
|
|
CodeBlocks.clear();
|
|
CurrentWriteCursor = nullptr;
|
|
// This is necessary since we do "null" pointer checks
|
|
InvalidNode = reinterpret_cast<OrderedNode*>(DualListData.ListAllocate(sizeof(OrderedNode)));
|
|
memset(InvalidNode, 0, sizeof(OrderedNode));
|
|
CurrentCodeBlock = nullptr;
|
|
}
|
|
|
|
void IREmitter::ReplaceAllUsesWithRange(OrderedNode *Node, OrderedNode *NewNode, AllNodesIterator Begin, AllNodesIterator End) {
|
|
uintptr_t ListBegin = DualListData.ListBegin();
|
|
auto NodeId = Node->Wrapped(ListBegin).ID();
|
|
|
|
while (Begin != End) {
|
|
auto [RealNode, IROp] = Begin();
|
|
|
|
const uint8_t NumArgs = IR::GetArgs(IROp->Op);
|
|
for (uint8_t i = 0; i < NumArgs; ++i) {
|
|
if (IROp->Args[i].ID() == NodeId) {
|
|
Node->RemoveUse();
|
|
NewNode->AddUse();
|
|
IROp->Args[i].NodeOffset = NewNode->Wrapped(ListBegin).NodeOffset;
|
|
|
|
// We can stop searching once all uses of the node are gone.
|
|
if (Node->NumUses == 0) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
++Begin;
|
|
}
|
|
}
|
|
|
|
void IREmitter::ReplaceNodeArgument(OrderedNode *Node, uint8_t Arg, OrderedNode *NewArg) {
|
|
uintptr_t ListBegin = DualListData.ListBegin();
|
|
uintptr_t DataBegin = DualListData.DataBegin();
|
|
|
|
FEXCore::IR::IROp_Header *IROp = Node->Op(DataBegin);
|
|
OrderedNodeWrapper OldArgWrapper = IROp->Args[Arg];
|
|
OrderedNode *OldArg = OldArgWrapper.GetNode(ListBegin);
|
|
OldArg->RemoveUse();
|
|
NewArg->AddUse();
|
|
IROp->Args[Arg].NodeOffset = NewArg->Wrapped(ListBegin).NodeOffset;
|
|
}
|
|
|
|
void IREmitter::RemoveArgUses(OrderedNode *Node) {
|
|
uintptr_t ListBegin = DualListData.ListBegin();
|
|
uintptr_t DataBegin = DualListData.DataBegin();
|
|
|
|
FEXCore::IR::IROp_Header *IROp = Node->Op(DataBegin);
|
|
|
|
const uint8_t NumArgs = IR::GetArgs(IROp->Op);
|
|
for (uint8_t i = 0; i < NumArgs; ++i) {
|
|
auto ArgNode = IROp->Args[i].GetNode(ListBegin);
|
|
ArgNode->RemoveUse();
|
|
}
|
|
}
|
|
|
|
void IREmitter::Remove(OrderedNode *Node) {
|
|
RemoveArgUses(Node);
|
|
|
|
Node->Unlink(DualListData.ListBegin());
|
|
}
|
|
|
|
IREmitter::IRPair<IROp_CodeBlock> IREmitter::CreateNewCodeBlockAfter(OrderedNode* insertAfter) {
|
|
auto OldCursor = GetWriteCursor();
|
|
|
|
auto CodeNode = CreateCodeNode();
|
|
|
|
if (insertAfter) {
|
|
LinkCodeBlocks(insertAfter, CodeNode);
|
|
} else {
|
|
LOGMAN_THROW_AA_FMT(CurrentCodeBlock != nullptr, "CurrentCodeBlock must not be null here");
|
|
|
|
// Find last block
|
|
auto LastBlock = CurrentCodeBlock;
|
|
|
|
while (LastBlock->Header.Next.GetNode(DualListData.ListBegin()) != InvalidNode)
|
|
LastBlock = LastBlock->Header.Next.GetNode(DualListData.ListBegin());
|
|
|
|
// Append it after the last block
|
|
LinkCodeBlocks(LastBlock, CodeNode);
|
|
}
|
|
|
|
SetWriteCursor(OldCursor);
|
|
|
|
return CodeNode;
|
|
}
|
|
|
|
void IREmitter::SetCurrentCodeBlock(OrderedNode *Node) {
|
|
CurrentCodeBlock = Node;
|
|
LOGMAN_THROW_A_FMT(Node->Op(DualListData.DataBegin())->Op == OP_CODEBLOCK, "Node wasn't codeblock. It was '{}'", IR::GetName(Node->Op(DualListData.DataBegin())->Op));
|
|
SetWriteCursor(Node->Op(DualListData.DataBegin())->CW<IROp_CodeBlock>()->Begin.GetNode(DualListData.ListBegin()));
|
|
}
|
|
|
|
void IREmitter::ReplaceWithConstant(OrderedNode *Node, uint64_t Value) {
|
|
auto Header = Node->Op(DualListData.DataBegin());
|
|
|
|
if (IRSizes[Header->Op] >= sizeof(IROp_Constant)) {
|
|
// Unlink any arguments the node currently has
|
|
RemoveArgUses(Node);
|
|
|
|
// Overwrite data with the new constant op
|
|
Header->Op = OP_CONSTANT;
|
|
auto Const = Header->CW<IROp_Constant>();
|
|
Const->Constant = Value;
|
|
} else {
|
|
// Fallback path for when the node to overwrite is too small
|
|
auto cursor = GetWriteCursor();
|
|
SetWriteCursor(Node);
|
|
|
|
auto NewNode = _Constant(Value);
|
|
ReplaceAllUsesWith(Node, NewNode);
|
|
|
|
SetWriteCursor(cursor);
|
|
}
|
|
}
|
|
|
|
}
|
|
|