mirror of
https://github.com/FEX-Emu/FEX.git
synced 2026-10-06 08:00:15 +02:00
Have it treated as a block terminator IR and instead of doing syscall+exitfunction, just do syscall and jump in to the dispatcher. This is necessary for code invalidation and reuse for code that is stuck in long running syscalls. We'll need to do something similar for thunks later, although the recursive nature of thunk+callback can make that a little squirrely.
195 lines
5.1 KiB
C++
195 lines
5.1 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 <cstdint>
|
|
#include <cstring>
|
|
|
|
namespace FEXCore::IR {
|
|
|
|
static bool IsFragmentExit(FEXCore::IR::IROps Op) {
|
|
switch (Op) {
|
|
case OP_SYSCALL:
|
|
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);
|
|
}
|
|
}
|
|
|
|
RegClass IREmitter::WalkFindRegClass(Ref Node) const {
|
|
auto Class = GetOpRegClass(Node);
|
|
switch (Class) {
|
|
case RegClass::GPR:
|
|
case RegClass::FPR:
|
|
case RegClass::GPRFixed:
|
|
case RegClass::FPRFixed:
|
|
case RegClass::Invalid: return Class;
|
|
default: break;
|
|
}
|
|
|
|
// Complex case, needs to be handled on an op by op basis
|
|
const uintptr_t DataBegin = DualListData.DataBegin();
|
|
const auto* 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 RegClass::Invalid;
|
|
}
|
|
|
|
void IREmitter::ResetWorkingList() {
|
|
DualListData.Reset();
|
|
CodeBlocks.clear();
|
|
CurrentWriteCursor = nullptr;
|
|
// This is necessary since we do "null" pointer checks
|
|
InvalidNode = reinterpret_cast<Ref>(DualListData.ListAllocate(sizeof(OrderedNode)));
|
|
memset(InvalidNode, 0, sizeof(OrderedNode));
|
|
CurrentCodeBlock = nullptr;
|
|
}
|
|
|
|
void IREmitter::ReplaceAllUsesWithRange(Ref Node, Ref 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(Ref Node, uint8_t Arg, Ref NewArg) {
|
|
uintptr_t ListBegin = DualListData.ListBegin();
|
|
uintptr_t DataBegin = DualListData.DataBegin();
|
|
|
|
FEXCore::IR::IROp_Header* IROp = Node->Op(DataBegin);
|
|
OrderedNodeWrapper OldArgWrapper = IROp->Args[Arg];
|
|
Ref OldArg = OldArgWrapper.GetNode(ListBegin);
|
|
OldArg->RemoveUse();
|
|
NewArg->AddUse();
|
|
IROp->Args[Arg].NodeOffset = NewArg->Wrapped(ListBegin).NodeOffset;
|
|
}
|
|
|
|
void IREmitter::RemoveArgUses(Ref 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::RemovePostRA(Ref Node) {
|
|
Node->Unlink(DualListData.ListBegin());
|
|
}
|
|
|
|
void IREmitter::Remove(Ref Node) {
|
|
RemoveArgUses(Node);
|
|
|
|
Node->Unlink(DualListData.ListBegin());
|
|
}
|
|
|
|
IREmitter::IRPair<IROp_CodeBlock> IREmitter::CreateNewCodeBlockAfter(Ref insertAfter) {
|
|
auto OldCursor = GetWriteCursor();
|
|
|
|
auto CodeNode = CreateCodeNode();
|
|
|
|
if (insertAfter) {
|
|
LinkCodeBlocks(insertAfter, CodeNode);
|
|
} else {
|
|
LOGMAN_THROW_A_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(Ref 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()));
|
|
|
|
// Constants are pooled only within a single block.
|
|
NrConstants = 0;
|
|
}
|
|
|
|
} // namespace FEXCore::IR
|