Files
FEX-Emu--FEX/Scripts/json_ir_generator.py
T
Ryan Houdek 40cad15da9 Cleanup old IR emitter functions
Argumentless IR emitter functions were prone to generating invalid code.
Remove them from the python emitter and change the branch instructions
that were using them to a new version instead.
Adds NumUse tracking as well.
2020-03-06 07:48:41 +02:00

339 lines
13 KiB
Python

import json
import sys
# Print out enum values
def print_enums(ops, defines):
output_file.write("#ifdef IROP_ENUM\n")
output_file.write("enum IROps : uint8_t {\n")
for op_key, op_vals in ops.items():
output_file.write("\t\tOP_%s,\n" % op_key.upper())
output_file.write("};\n")
output_file.write("#undef IROP_ENUM\n")
output_file.write("#endif\n\n")
# Print out struct definitions
def print_ir_structs(ops, defines):
output_file.write("#ifdef IROP_STRUCTS\n")
# Print out defines here
for op_val in defines:
output_file.write("\t%s;\n" % op_val)
output_file.write("// Default structs\n")
output_file.write("struct __attribute__((packed)) IROp_Header {\n")
output_file.write("\tvoid* Data[0];\n")
output_file.write("\tIROps Op;\n\n")
output_file.write("\tuint8_t Size;\n")
output_file.write("\tuint8_t NumArgs;\n")
output_file.write("\tuint8_t Elements : 7;\n")
output_file.write("\tbool HasDest : 1;\n")
output_file.write("\ttemplate<typename T>\n")
output_file.write("\tT const* C() const { return reinterpret_cast<T const*>(Data); }\n")
output_file.write("\ttemplate<typename T>\n")
output_file.write("\tT* CW() { return reinterpret_cast<T*>(Data); }\n")
output_file.write("\tNodeWrapper Args[0];\n")
output_file.write("};\n\n");
output_file.write("struct __attribute__((packed)) IROp_Empty {\n")
output_file.write("\tIROp_Header Header;\n")
output_file.write("};\n\n")
output_file.write("// User defined IR Op structs\n")
for op_key, op_vals in ops.items():
SSAArgs = 0
HasArgs = False
if ("SSAArgs" in op_vals):
SSAArgs = int(op_vals["SSAArgs"])
if ("Args" in op_vals and len(op_vals["Args"]) != 0):
HasArgs = True
if (HasArgs or SSAArgs != 0):
output_file.write("struct __attribute__((packed)) IROp_%s {\n" % op_key)
output_file.write("\tIROp_Header Header;\n\n")
# SSA arguments have a hard requirement to appear after the header
if (SSAArgs != 0):
output_file.write("private:\n")
for i in range(0, SSAArgs):
output_file.write("\tuint64_t : (sizeof(NodeWrapper) * 8);\n");
output_file.write("public:\n")
if (HasArgs):
output_file.write("\t// User defined data\n")
# Print out arguments in IR Op
for i in range(0, len(op_vals["Args"]), 2):
data_type = op_vals["Args"][i]
data_name = op_vals["Args"][i+1]
output_file.write("\t%s %s;\n" % (data_type, data_name))
output_file.write("};\n")
else:
output_file.write("using IROp_%s = IROp_Empty;\n" % op_key)
# Add a static assert that the IR ops must be pod
output_file.write("static_assert(std::is_pod<IROp_%s>::value);\n\n" % op_key)
output_file.write("#undef IROP_STRUCTS\n")
output_file.write("#endif\n\n")
# Print out const expression to calculate IR Op sizes
def print_ir_sizes(ops, defines):
output_file.write("#ifdef IROP_SIZES\n")
output_file.write("constexpr std::array<size_t, IROps::OP_LAST + 1> IRSizes = {\n")
for op_key, op_vals in ops.items():
if ("Last" in op_vals):
output_file.write("\t-1ULL,\n")
else:
output_file.write("\tsizeof(IROp_%s),\n" % op_key)
output_file.write("};\n\n")
output_file.write("// Make sure our array maps directly to the IROps enum\n")
output_file.write("static_assert(IRSizes[IROps::OP_LAST] == -1ULL);\n\n")
output_file.write("[[maybe_unused]] static size_t GetSize(IROps Op) { return IRSizes[Op]; }\n\n")
output_file.write("std::string_view const& GetName(IROps Op);\n")
output_file.write("#undef IROP_SIZES\n")
output_file.write("#endif\n\n")
# Print out the name printer implementation
def print_ir_getname(ops, defines):
output_file.write("#ifdef IROP_GETNAME_IMPL\n")
output_file.write("constexpr std::array<std::string_view const, OP_LAST + 1> IRNames = {\n")
for op_key, op_vals in ops.items():
output_file.write("\t\"%s\",\n" % op_key)
output_file.write("};\n\n")
output_file.write("static_assert(IRNames[OP_LAST] == \"Last\");\n\n")
output_file.write("std::string_view const& GetName(IROps Op) {\n")
output_file.write(" return IRNames[Op];\n")
output_file.write("}\n")
output_file.write("#undef IROP_GETNAME_IMPL\n")
output_file.write("#endif\n\n")
# Print out IR argument printing
def print_ir_arg_printer(ops, defines):
output_file.write("#ifdef IROP_ARGPRINTER_HELPER\n")
output_file.write("switch (IROp->Op) {\n")
for op_key, op_vals in ops.items():
if not ("Last" in op_vals):
SSAArgs = 0
HasArgs = False
# Does this not want a printer?
if ("ArgPrinter" in op_vals and op_vals["ArgPrinter"] == False):
continue
if ("SSAArgs" in op_vals):
SSAArgs = int(op_vals["SSAArgs"])
if ("Args" in op_vals and len(op_vals["Args"]) != 0):
HasArgs = True
output_file.write("case IROps::OP_%s: {\n" % op_key.upper())
if (HasArgs or SSAArgs != 0):
output_file.write("\tauto Op = IROp->C<IR::IROp_%s>();\n" % op_key)
output_file.write("\t*out << \" \";\n")
# Print SSA args first
if (SSAArgs != 0):
for i in range(0, SSAArgs):
LastArg = (SSAArgs - i - 1) == 0 and not HasArgs
output_file.write("\tPrintArg(out, IR, Op->Header.Args[%d]);\n" % i)
if not (LastArg):
output_file.write("\t*out << \", \";\n")
# Now print user defined arguments
if (HasArgs):
ArgCount = len(op_vals["Args"])
for i in range(0, len(op_vals["Args"]), 2):
data_name = op_vals["Args"][i+1]
LastArg = (ArgCount - i - 2) == 0
CondArg2 = (", ", "")
output_file.write("\tPrintArg(out, IR, Op->%s);\n" % data_name)
if not (LastArg):
output_file.write("\t*out << \", \";\n")
output_file.write("break;\n")
output_file.write("}\n")
output_file.write("#undef IROP_ARGPRINTER_HELPER\n")
output_file.write("#endif\n")
# Print out IR allocator helpers
def print_ir_allocator_helpers(ops, defines):
output_file.write("#ifdef IROP_ALLOCATE_HELPERS\n")
output_file.write("\ttemplate <class T>\n")
output_file.write("\tusing IRPair = FEXCore::IR::Wrapper<T>;\n\n")
output_file.write("\tIRPair<IROp_Header> AllocateRawOp(size_t HeaderSize) {\n")
output_file.write("\t\tauto Op = reinterpret_cast<IROp_Header*>(Data.Allocate(HeaderSize));\n")
output_file.write("\t\tmemset(Op, 0, HeaderSize);\n")
output_file.write("\t\tOp->Op = IROps::OP_DUMMY;\n")
output_file.write("\t\treturn FEXCore::IR::Wrapper<IROp_Header>{Op, CreateNode(Op)};\n")
output_file.write("\t}\n\n")
output_file.write("\ttemplate<class T, IROps T2>\n")
output_file.write("\tIRPair<T> AllocateOp() {\n")
output_file.write("\t\tsize_t Size = FEXCore::IR::GetSize(T2);\n")
output_file.write("\t\tauto Op = reinterpret_cast<T*>(Data.Allocate(Size));\n")
output_file.write("\t\tmemset(Op, 0, Size);\n")
output_file.write("\t\tOp->Header.Op = T2;\n")
output_file.write("\t\treturn FEXCore::IR::Wrapper<T>{Op, CreateNode(&Op->Header)};\n")
output_file.write("\t}\n\n")
output_file.write("\tuint8_t GetOpSize(OrderedNode *Op) const {\n")
output_file.write("\t\tauto HeaderOp = reinterpret_cast<IROp_Header const*>(Op->Header.Value.GetPtr(Data.Begin()));\n")
output_file.write("\t\tLogMan::Throw::A(HeaderOp->HasDest, \"Op %s has no dest\\n\", GetName(HeaderOp->Op));\n")
output_file.write("\t\treturn HeaderOp->Size;\n")
output_file.write("\t}\n\n")
output_file.write("\tuint8_t GetOpElements(OrderedNode *Op) const {\n")
output_file.write("\t\tauto HeaderOp = reinterpret_cast<IROp_Header const*>(Op->Header.Value.GetPtr(Data.Begin()));\n")
output_file.write("\t\tLogMan::Throw::A(HeaderOp->HasDest, \"Op %s has no dest\\n\", GetName(HeaderOp->Op));\n")
output_file.write("\t\treturn HeaderOp->Elements;\n")
output_file.write("\t}\n\n")
output_file.write("\tbool OpHasDest(OrderedNode *Op) const {\n")
output_file.write("\t\tauto HeaderOp = reinterpret_cast<IROp_Header const*>(Op->Header.Value.GetPtr(Data.Begin()));\n")
output_file.write("\t\treturn HeaderOp->HasDest;\n")
output_file.write("\t}\n\n")
# Generate helpers with operands
for op_key, op_vals in ops.items():
if not ("Last" in op_vals):
SSAArgs = 0
HasArgs = False
HasDest = False
HasFixedDestSize = False
FixedDestSize = 0
HasDestSize = False;
NumElements = 1
DestSize = ""
if ("SSAArgs" in op_vals):
SSAArgs = int(op_vals["SSAArgs"])
if ("Args" in op_vals and len(op_vals["Args"]) != 0):
HasArgs = True
if ("HelperGen" in op_vals and op_vals["HelperGen"] == False):
continue;
if ("HasDest" in op_vals and op_vals["HasDest"] == True):
HasDest = True
if ("FixedDestSize" in op_vals):
HasFixedDestSize = True
FixedDestSize = int(op_vals["FixedDestSize"])
if ("DestSize" in op_vals):
HasDestSize = True
DestSize = op_vals["DestSize"]
if ("NumElements" in op_vals):
NumElements = int(op_vals["NumElements"])
output_file.write("\tIRPair<IROp_%s> _%s(" % (op_key, op_key))
# Output SSA args first
if (SSAArgs != 0):
for i in range(0, SSAArgs):
LastArg = (SSAArgs - i - 1) == 0 and not HasArgs
CondArg2 = (", ", "")
output_file.write("OrderedNode *ssa%d%s" % (i, CondArg2[LastArg]))
if (HasArgs):
ArgCount = len(op_vals["Args"])
for i in range(0, len(op_vals["Args"]), 2):
data_type = op_vals["Args"][i]
data_name = op_vals["Args"][i+1]
LastArg = (ArgCount - i - 2) == 0
CondArg2 = (", ", "")
output_file.write("%s %s%s" % (data_type, data_name, CondArg2[LastArg]))
output_file.write(") {\n")
output_file.write("\t\tauto Op = AllocateOp<IROp_%s, IROps::OP_%s>();\n" % (op_key, op_key.upper()))
output_file.write("\t\tOp.first->Header.NumArgs = %d;\n" % (SSAArgs))
if (SSAArgs != 0):
for i in range(0, SSAArgs):
output_file.write("\t\tOp.first->Header.Args[%d] = ssa%d->Wrapped(ListData.Begin());\n" % (i, i))
output_file.write("\t\tssa%d->AddUse();\n" % (i))
if (HasArgs):
for i in range(1, len(op_vals["Args"]), 2):
data_name = op_vals["Args"][i]
output_file.write("\t\tOp.first->%s = %s;\n" % (data_name, data_name))
if (HasDest):
if (HasFixedDestSize):
output_file.write("\t\tOp.first->Header.Size = %d;\n" % FixedDestSize)
if (HasDestSize):
output_file.write("\t\tOp.first->Header.Size = %s;\n" % DestSize)
output_file.write("\t\tOp.first->Header.Elements = %s;\n" % NumElements)
if not (HasFixedDestSize or HasDestSize):
# We need to infer destination size
output_file.write("\t\tuint8_t InferSize = 0;\n")
if (SSAArgs != 0):
for i in range(0, SSAArgs):
output_file.write("\t\tuint8_t Size%d = GetOpSize(ssa%s);\n" % (i, i))
output_file.write("\t\tInferSize = std::max(InferSize, Size%d);\n" % (i))
output_file.write("\t\tOp.first->Header.Size = InferSize;\n")
output_file.write("\t\tOp.first->Header.HasDest = true;\n")
output_file.write("\t\treturn Op;\n")
output_file.write("\t}\n\n")
output_file.write("#undef IROP_ALLOCATE_HELPERS\n")
output_file.write("#endif\n")
if (len(sys.argv) < 3):
sys.exit()
output_filename = sys.argv[2]
json_file = open(sys.argv[1], "r")
json_text = json_file.read()
json_file.close()
json_object = json.loads(json_text)
json_object = {k.upper(): v for k, v in json_object.items()}
ops = json_object["OPS"]
defines = json_object["DEFINES"]
output_file = open(output_filename, "w")
print_enums(ops, defines)
print_ir_structs(ops, defines)
print_ir_sizes(ops, defines)
print_ir_getname(ops, defines)
print_ir_arg_printer(ops, defines)
print_ir_allocator_helpers(ops, defines)
output_file.close()