FEXCore/docs/IR.md
The IR for the FEXCore is an SSA based IR that is generated from the incoming x86-64 assembly. SSA is quite nice to work with when translating the x86-64 code to the IR, when optimizing that code with custom optimization passes, and also passing that IR to our CPU backends.
Print Op for printing out values for debug viewingThe first SSA node is a special case node that is considered invalid. This means %0 will always be invalid for "null" node checks The first real SSA node also has to be a IRHeader node. This means it is safe to assume that %1 will always be an IRHeader.
(%%1) IRHeader 0x41a9a0, %%2, 5
The header provides information about that function like the entry point address.
Additionally it also points to the first CodeBlock IROp
(%%2) CodeBlock %%7, %%168, %%3
CodeBlock Op is a jump target and must be treated as if it'll be jumped to from other blocks
(%%3) CodeBlock %%169, %%173, %%4
(%%169) BeginBlock %3
%170 i64 = Constant 0x41a9e1
(%%171) StoreContext %170 i64, 0x8, 0x0
(%%172) ExitFunction
(%%173) EndBlock %3
The in-memory representation of the IR may be a bit confusing when initially viewed and once dealing with optimizations then it may be confusing as well.
Currently the IR Generation is tied to the OpDispatchBuilder class. This class handles translating decoded x86 to our IR representation.
When generating IR inside of the OpDispatchBuilder it is straight forward, just call the IR generation ops.
This is an intrusive allocator that is used by the OpDispatchBuilder for storing IR data. It is a simple linear arena allocator without resizing capabilities.
OpDispatchBuilder provides IRListView ViewIR() for handling the IR outside of the class:
This class uses two IntrusiveAllocator objects for tracking IR data. ListData and Data are the object names.
ListData is for tracking the doubly linked list of nodes
FEXCore::IR::OrderedNode objectsAllocationOffset / sizeof(OrderedNode)This is a doubly linked list of all of our IR nodes. This allows us to walk forward or backward over the IR and they must be ordered correctly to ensure dominance of SSA values.
OrderedNodeHeader
OpNodeWrapper Value
IROp_Header backing op for this SSA nodeOrderedNodeWrapper Next
OrderedNodeOrderedNodeWrapper Previous
OrderedNodeIROp_Header *Op(uintptr_t Base)
OrderedNodeHeader and OpNodeWrapperusing OpNodeWrapper = NodeWrapperBase<IROp_Header>using OrderedNodeWrapper = NodeWrapperBase<OrderedNode>GetNode(uintptr_t Base) allows you to pass in the base pointer from the backing Intrusive allocator and get the object
GetNode(ListDataBegin) with OrderedNodeWrapperOrderedNode* from GetNode, Use the Op(IRDataBegin) function to get the IR data.GetNode directly from OpNodeWrapper as it is VERY easy to mess it upProvides a fairly straightforward interface that allows easily walking the IR nodes with C++ increment and decrement operations. Only iterates over a single block
IR::NodeIterator After = ...;
IR::NodeIterator End = ...;
while (After != End) {
// NodeIterator() returns a pair of pointers to the OrderedNode and IROp data
// You can unpack the result with structured bindings
auto [CodeNode, IROp] = After();
// IROp_Header contains a bunch of information about the IR object
// We can convert it with the object's C<typename Type> or CW<typename Type> functions
switch(IROp->Op) {
case IR::OP_ADD: {
FEXCore::IR::IROp_Add const *Op = IROp->C<FEXCore::IR::IROp_Add>();
/* We can now access members inside of IROp_Add that were previously unavailable
You can still access the header definitions from Op->Header */
break;
}
/* ... */
}
// Go to the next IR Op
++After;
}
This is like NodeIterator, except that it will cross block boundaries.
Provides a range for easy iterating over all the blocks in a multi-block with NodeIterator
for (auto [BlockNode, BlockHeader] : CurrentIR.GetBlocks()) {
// Do stuff for each block
}
Provides a range for easy iterating over all the code in a block
for (auto [CodeNode, IROp] : CurrentIR.GetCode(BlockNode)) {
// Do stuff for each op
switch(IROp->Op) {
case IR::OP_ADD: {
FEXCore::IR::IROp_Add const *Op = IROp->C<FEXCore::IR::IROp_Add>();
// Do stuff for each Add op.
break;
}
}
}
Like GetCode, except it uses AllNodesIterator to allow easy iterating over every single op in the entire Multiblock
for (auto [CodeNode, IROp] : CurrentIR.GetAllCode()) {
// Do stuff for each op
}
An example of what the IR json looks like
"StoreContext": {
"SSAArgs": "1",
"Args": [
"uint8_t", "Size",
"uint32_t", "Offset"
]
},
The json entry name will be the name of the IR op and the dispatcher function.
This means you'll get a _Add(...) dispatcher function generated
HasDest
SSAArgs
SSANames
Args
FixedDestSize
DestSize
RAOverride
HelperGen
Last