From 79f7dcbaa58ad86012e2d1dbd60493897e2e22a7 Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Mon, 17 Jul 2023 01:58:49 -0700 Subject: [PATCH] FEXCore/Config: Adds support for enum mask configuration array Allows us to consume an array of strings and convert it to an mask of enum values. This is a quality of life change that allows us to specify a mask of options. The first configuration option added to support this is to control the vixl disassembler. Now by default the vixl disassembler doesn't disassemble any blocks and needs to be enabled individually. eg: ``` FEXLoader --disassemble=blocks FEXLoader --disassemble=dispatcher FEXLoader --disassemble=blocks,dispatcher ``` Has the additional convenience option of just passing in numbers as well. ``` FEXLoader --disassemble=2 FEXLoader --disassemble=1 FEXLoader --disassemble=3 ``` Also of course all of this works through environment variables. ``` FEX_DISASSEMBLE=blocks FEXInterpreter FEX_DISASSEMBLE=dispatcher FEXInterpreter FEX_DISASSEMBLE=blocks,dispatcher FEXInterpreter ``` While only used fairly sparingly now, this is likely to have some additional configurations using this in the future. Since we already have some configs that are basically using enums, but just by doing string comparisons. This was asked for by a developer, so I figured I would throw it together quick. --- External/FEXCore/Scripts/config_generator.py | 75 +++++++++++++++++-- .../Source/Interface/Config/Config.cpp | 2 + .../Source/Interface/Config/Config.json.in | 14 ++++ .../Interface/Core/ArchHelpers/Arm64Emitter.h | 1 + .../Core/Dispatcher/Arm64Dispatcher.cpp | 6 +- .../Source/Interface/Core/JIT/Arm64/JIT.cpp | 6 +- .../FEXCore/include/FEXCore/Config/Config.h | 43 +++++++++++ Source/Tools/FEXConfig/Main.cpp | 5 ++ 8 files changed, 143 insertions(+), 9 deletions(-) diff --git a/External/FEXCore/Scripts/config_generator.py b/External/FEXCore/Scripts/config_generator.py index c662a62a5..a050ff148 100644 --- a/External/FEXCore/Scripts/config_generator.py +++ b/External/FEXCore/Scripts/config_generator.py @@ -27,7 +27,9 @@ def print_header(): #ifndef OPT_STRARRAY #define OPT_STRARRAY(group, enum, json, default) OPT_BASE(fextl::string, group, enum, json, default) #endif - +#ifndef OPT_STRENUM +#define OPT_STRENUM(group, enum, json, default) OPT_BASE(uint64_t, group, enum, json, default) +#endif ''' output_file.write(header) @@ -40,6 +42,7 @@ def print_tail(): #undef OPT_UINT64 #undef OPT_STR #undef OPT_STRARRAY +#undef OPT_STRENUM ''' output_file.write(tail) @@ -127,12 +130,13 @@ def print_man_options(options): short = op_vals["ShortArg"] default = op_vals["Default"] + value_type = op_vals["Type"] # Textual default rather than enum based if ("TextDefault" in op_vals): default = op_vals["TextDefault"] - if (op_vals["Type"] == "str" or op_vals["Type"] == "strarray"): + if (value_type == "str" or value_type == "strarray" or value_type == "strenum"): # Wrap the string argument in quotes default = "'" + default + "'" print_man_option( @@ -141,6 +145,12 @@ def print_man_options(options): op_vals["Desc"], default ) + if (value_type == "strenum"): + Enums = op_vals["Enums"] + output_man.write("\\fBAvailable Options:\\fR\n") + for enum_op_key, enum_op_vals in Enums.items(): + output_man.write("{}, ".format(enum_op_vals)) + output_man.write("\n") output_man.write(".El\n") @@ -150,12 +160,13 @@ def print_man_environment(options): for op_group, group_vals in options.items(): for op_key, op_vals in group_vals.items(): default = op_vals["Default"] + value_type = op_vals["Type"] # Textual default rather than enum based if ("TextDefault" in op_vals): default = op_vals["TextDefault"] - if (op_vals["Type"] == "str" or op_vals["Type"] == "strarray"): + if (value_type == "str" or value_type == "strarray" or value_type == "strenum"): # Wrap the string argument in quotes default = "'" + default + "'" print_man_env_option( @@ -165,6 +176,13 @@ def print_man_environment(options): False ) + if (value_type == "strenum"): + Enums = op_vals["Enums"] + output_man.write("\\fBAvailable Options:\\fR\n") + for enum_op_key, enum_op_vals in Enums.items(): + output_man.write("{}, ".format(enum_op_vals)) + output_man.write("\n") + print_man_environment_tail() output_man.write(".El\n") @@ -334,7 +352,7 @@ def print_argloader_options(options): for op_key, op_vals in group_vals.items(): default = op_vals["Default"] - if (op_vals["Type"] == "str" or op_vals["Type"] == "strarray"): + if (op_vals["Type"] == "str" or op_vals["Type"] == "strarray" or op_vals["Type"] == "strenum"): # Wrap the string argument in quotes default = "\"" + default + "\"" @@ -382,7 +400,10 @@ def print_parse_argloader_options(options): # boolean values need a decimal specifier. Otherwise fmt prints strings. conversion_func = "fextl::fmt::format(\"{:d}\", " - if (value_type == "strarray"): + if (value_type == "strenum"): + output_argloader.write("\tfextl::string UserValue = Options[\"{0}\"];\n".format(op_key)) + output_argloader.write("\tSet(FEXCore::Config::ConfigOption::CONFIG_{}, FEXCore::Config::EnumParser(FEXCore::Config::{}_EnumPairs, UserValue));\n".format(op_key.upper(), op_key, op_key)) + elif (value_type == "strarray"): # these need a bit more help output_argloader.write("\tauto Array = Options.all(\"{0}\");\n".format(op_key)) output_argloader.write("\tfor (auto iter = Array.begin(); iter != Array.end(); ++iter) {\n") @@ -407,6 +428,12 @@ def print_parse_envloader_options(options): for op_group, group_vals in options.items(): for op_key, op_vals in group_vals.items(): + value_type = op_vals["Type"] + if (value_type == "strenum"): + output_argloader.write("else if (Key == \"FEX_{0}\") {{\n".format(op_key.upper())) + output_argloader.write("Value = FEXCore::Config::EnumParser(FEXCore::Config::{}_EnumPairs, Value);\n".format(op_key, op_key)) + output_argloader.write("}\n") + if ("ArgumentHandler" in op_vals): conversion_func = "FEXCore::Config::Handler::{0}".format(op_vals["ArgumentHandler"]) output_argloader.write("else if (Key == \"FEX_{0}\") {{\n".format(op_key.upper())) @@ -414,6 +441,41 @@ def print_parse_envloader_options(options): output_argloader.write("}\n") output_argloader.write("#endif\n") +def print_parse_enum_options(options): + output_argloader.write("#ifdef ENUMDEFINES\n") + output_argloader.write("#undef ENUMDEFINES\n") + for op_group, group_vals in options.items(): + for op_key, op_vals in group_vals.items(): + if (op_vals["Type"] == "strenum"): + output_argloader.write("enum {} : uint64_t {{\n".format(op_key)) + Enums = op_vals["Enums"] + i = 0 + # Always have an OFF. + output_argloader.write("\tOFF = 0,\n") + for enum_op_key, enum_op_vals in Enums.items(): + output_argloader.write("\t{} = 1ULL << {},\n".format(enum_op_key.upper(), i)) + i += 1 + + output_argloader.write("};\n") + + for op_group, group_vals in options.items(): + for op_key, op_vals in group_vals.items(): + if (op_vals["Type"] == "strenum"): + Enums = op_vals["Enums"] + + output_argloader.write("using {}ConfigPair = std::pair;\n".format(op_key, op_key)) + output_argloader.write("constexpr static std::array<{}ConfigPair, {}> {}_EnumPairs = {{{{\n".format(op_key, len(Enums) + 1, op_key)) + i = 0 + # Always have an OFF. + output_argloader.write("\t{{ \"off\", FEXCore::Config::{}::OFF }},\n".format(op_key)) + for enum_op_key, enum_op_vals in Enums.items(): + output_argloader.write("\t{{ \"{}\", FEXCore::Config::{}::{} }},\n".format(enum_op_vals, op_key, enum_op_key.upper())) + i += 1 + + output_argloader.write("}};\n") + + output_argloader.write("#endif\n") + def check_for_duplicate_options(options): short_map = [] long_map = [] @@ -492,4 +554,7 @@ print_parse_argloader_options(options); # Generate environment loader code print_parse_envloader_options(options); +# Generate enum variable options +print_parse_enum_options(options); + output_argloader.close() diff --git a/External/FEXCore/Source/Interface/Config/Config.cpp b/External/FEXCore/Source/Interface/Config/Config.cpp index 152c96b34..0e4258db4 100644 --- a/External/FEXCore/Source/Interface/Config/Config.cpp +++ b/External/FEXCore/Source/Interface/Config/Config.cpp @@ -1,5 +1,6 @@ #include "Common/StringConv.h" #include "Common/StringUtils.h" +#include "FEXCore/Utils/EnumUtils.h" #include #include @@ -37,6 +38,7 @@ namespace DefaultValues { #define OPT_BASE(type, group, enum, json, default) const P(type) P(enum) = P(default); #define OPT_STR(group, enum, json, default) const std::string_view P(enum) = P(default); #define OPT_STRARRAY(group, enum, json, default) OPT_STR(group, enum, json, default) +#define OPT_STRENUM(group, enum, json, default) const uint64_t P(enum) = FEXCore::ToUnderlying(P(default)); #include } diff --git a/External/FEXCore/Source/Interface/Config/Config.json.in b/External/FEXCore/Source/Interface/Config/Config.json.in index 6f3c98d1f..5e872fc54 100644 --- a/External/FEXCore/Source/Interface/Config/Config.json.in +++ b/External/FEXCore/Source/Interface/Config/Config.json.in @@ -251,6 +251,20 @@ "Set this in an application configuration for injecting in to only specific applications.", "\tNote: If x86/x86_64 libSegFault.so isn't installed then this option won't work." ] + }, + "Disassemble": { + "Type": "strenum", + "Default": "FEXCore::Config::Disassemble::OFF", + "Enums": { + "DISPATCHER": "dispatcher", + "BLOCKS": "blocks" + }, + "Desc": [ + "Allows controlling of the vixl disassembler.", + "\toff: No disassembly will be output", + "\tdispatcher: Will enable disassembly of the JIT dispatcher loop", + "\tblocks: Will enable disassembly of the translated instruction code blocks" + ] } }, "Logging": { diff --git a/External/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.h b/External/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.h index 4a3a45cc6..545851552 100644 --- a/External/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.h +++ b/External/FEXCore/Source/Interface/Core/ArchHelpers/Arm64Emitter.h @@ -175,6 +175,7 @@ protected: #endif #ifdef VIXL_DISASSEMBLER vixl::aarch64::PrintDisassembler Disasm {stderr}; + FEX_CONFIG_OPT(Disassemble, DISASSEMBLE); #endif FEX_CONFIG_OPT(StaticRegisterAllocation, SRA); }; diff --git a/External/FEXCore/Source/Interface/Core/Dispatcher/Arm64Dispatcher.cpp b/External/FEXCore/Source/Interface/Core/Dispatcher/Arm64Dispatcher.cpp index db326ccd4..a5ee66ff7 100644 --- a/External/FEXCore/Source/Interface/Core/Dispatcher/Arm64Dispatcher.cpp +++ b/External/FEXCore/Source/Interface/Core/Dispatcher/Arm64Dispatcher.cpp @@ -503,8 +503,10 @@ void Arm64Dispatcher::EmitDispatcher() { } #ifdef VIXL_DISASSEMBLER - const auto DisasmEnd = GetCursorAddress(); - Disasm.DisassembleBuffer(DisasmBegin, DisasmEnd); + if (Disassemble() & FEXCore::Config::Disassemble::DISPATCHER) { + const auto DisasmEnd = GetCursorAddress(); + Disasm.DisassembleBuffer(DisasmBegin, DisasmEnd); + } #endif } diff --git a/External/FEXCore/Source/Interface/Core/JIT/Arm64/JIT.cpp b/External/FEXCore/Source/Interface/Core/JIT/Arm64/JIT.cpp index f7654cad2..9e9da877e 100644 --- a/External/FEXCore/Source/Interface/Core/JIT/Arm64/JIT.cpp +++ b/External/FEXCore/Source/Interface/Core/JIT/Arm64/JIT.cpp @@ -1142,8 +1142,10 @@ CPUBackend::CompiledCode Arm64JITCore::CompileCode(uint64_t Entry, ClearICache(CodeData.BlockBegin, CodeOnlySize); #ifdef VIXL_DISASSEMBLER - const auto DisasmEnd = reinterpret_cast(JITBlockTailLocation); - Disasm.DisassembleBuffer(DisasmBegin, DisasmEnd); + if (Disassemble() & FEXCore::Config::Disassemble::BLOCKS) { + const auto DisasmEnd = reinterpret_cast(JITBlockTailLocation); + Disasm.DisassembleBuffer(DisasmBegin, DisasmEnd); + } #endif if (DebugData) { diff --git a/External/FEXCore/include/FEXCore/Config/Config.h b/External/FEXCore/include/FEXCore/Config/Config.h index 0cd4348cf..6bf77bbdc 100644 --- a/External/FEXCore/include/FEXCore/Config/Config.h +++ b/External/FEXCore/include/FEXCore/Config/Config.h @@ -2,12 +2,16 @@ #include #include +#include +#include #include +#include #include #include #include #include +#include #include #include @@ -52,6 +56,9 @@ namespace Handler { #include }; +#define ENUMDEFINES +#include + enum ConfigCore { CONFIG_INTERPRETER, CONFIG_IRJIT, @@ -83,6 +90,42 @@ namespace Handler { LAYER_TOP, }; + template + static inline fextl::string EnumParser(PairTypes const &EnumPairs, std::string_view const View) { + uint64_t EnumMask{}; + auto Results = std::from_chars(View.data(), View.data() + View.size(), EnumMask); + if (Results.ec == std::errc()) { + // If the data is a valid number, just pass it through. + return View.data(); + } + + auto Begin = 0; + auto End = View.find_first_of(','); + std::string_view Option = View.substr(Begin, End); + while (Option.size() != 0) { + auto EnumValue = std::find_if(EnumPairs.begin(), EnumPairs.end(), + [Option](const DisassembleConfigPair &Value) -> bool { + return Value.first == Option; + }); + + if (EnumValue == EnumPairs.end()) { + LogMan::Msg::IFmt("Skipping Unknown option: {}", Option); + } + else { + EnumMask |= FEXCore::ToUnderlying(EnumValue->second); + } + + if (End == std::string::npos) { + break; + } + Begin = End + 1; + End = View.find_first_of(',', Begin); + Option = View.substr(Begin, End); + } + + return fextl::fmt::format("{}", EnumMask); + } + namespace DefaultValues { #define P(x) x #define OPT_BASE(type, group, enum, json, default) extern const P(type) P(enum); diff --git a/Source/Tools/FEXConfig/Main.cpp b/Source/Tools/FEXConfig/Main.cpp index 5f76e4a2f..fc8140a33 100644 --- a/Source/Tools/FEXConfig/Main.cpp +++ b/Source/Tools/FEXConfig/Main.cpp @@ -1,6 +1,7 @@ #include "Common/Config.h" #include "Common/FileFormatCheck.h" #include "FEXCore/Config/Config.h" +#include "FEXCore/Utils/EnumUtils.h" #include "Tools/CommonGUI/IMGui.h" #include @@ -75,6 +76,8 @@ namespace { #define OPT_STR(group, enum, json, default) \ LoadedConfig->Set(FEXCore::Config::ConfigOption::CONFIG_##enum, default); #define OPT_STRARRAY(group, enum, json, default) // Do nothing +#define OPT_STRENUM(group, enum, json, default) \ + LoadedConfig->Set(FEXCore::Config::ConfigOption::CONFIG_##enum, std::to_string(FEXCore::ToUnderlying(default))); #include // Erase unnamed options which shouldn't be set @@ -108,6 +111,8 @@ namespace { #define OPT_STR(group, enum, json, default) \ if (!LoadedConfig->OptionExists(FEXCore::Config::ConfigOption::CONFIG_##enum)) { LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_##enum, default); } #define OPT_STRARRAY(group, enum, json, default) // Do nothing +#define OPT_STRENUM(group, enum, json, default) \ + if (!LoadedConfig->OptionExists(FEXCore::Config::ConfigOption::CONFIG_##enum)) { LoadedConfig->EraseSet(FEXCore::Config::ConfigOption::CONFIG_##enum, std::to_string(FEXCore::ToUnderlying(default))); } #include // Erase unnamed options which shouldn't be set