diff --git a/.github/workflows/vixl_simulator.yml b/.github/workflows/vixl_simulator.yml index 331fae4ac..0d9c3b083 100644 --- a/.github/workflows/vixl_simulator.yml +++ b/.github/workflows/vixl_simulator.yml @@ -65,7 +65,7 @@ jobs: # Note the current convention is to use the -S and -B options here to specify source # and build directories, but this is only available with CMake 3.13 and higher. # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja -DENABLE_VIXL_SIMULATOR=True -DENABLE_LTO=False -DENABLE_ASSERTIONS=True -DENABLE_X86_HOST_DEBUG=True + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja -DENABLE_VIXL_SIMULATOR=True -DENABLE_VIXL_DISASSEMBLER=True -DENABLE_LTO=False -DENABLE_ASSERTIONS=True -DENABLE_X86_HOST_DEBUG=True - name: Build working-directory: ${{runner.workspace}}/build @@ -112,6 +112,18 @@ jobs: working-directory: ${{runner.workspace}}/build run: mv ${{runner.workspace}}/build/Testing/Temporary/LastTest.log ${{runner.workspace}}/build/Testing/Temporary/LastTest_IR.log || true + - name: Instruction Count Tests + working-directory: ${{runner.workspace}}/build + shell: bash + # Execute the unit tests + run: cmake --build . --config $BUILD_TYPE --target instcountci_tests + + - name: Instruction Count Test Results move + if: ${{ always() }} + shell: bash + working-directory: ${{runner.workspace}}/build + run: mv ${{runner.workspace}}/build/Testing/Temporary/LastTest.log ${{runner.workspace}}/build/Testing/Temporary/LastTest_InstCountCI.log || true + - name: Truncate test results if: ${{ always() }} shell: bash diff --git a/Scripts/InstructionCountParser.py b/Scripts/InstructionCountParser.py new file mode 100755 index 000000000..d2b83b4e4 --- /dev/null +++ b/Scripts/InstructionCountParser.py @@ -0,0 +1,199 @@ +#!/usr/bin/python3 +import base64 +from dataclasses import dataclass, field +from enum import Flag +import json +import struct +import sys +import subprocess +import os +import logging +logger = logging.getLogger() +logger.setLevel(logging.ERROR) + +@dataclass +class TestData: + name: str + optimal: int + expectedinstructioncount: int + code: bytes + def __init__(self, Name, Optimal, ExpectedInstructionCount, Code): + self.name = Name + self.expectedinstructioncount = ExpectedInstructionCount + self.optimal = Optimal + self.code = Code + + @property + def Name(self): + return self.name + + @property + def Optimal(self): + return self.optimal + + @property + def ExpectedInstructionCount(self): + return self.expectedinstructioncount + + @property + def Code(self): + return self.code + +TestDataMap = {} +class HostFeatures(Flag) : + FEATURE_ANY = 0 + FEATURE_SVE128 = (1 << 0) + FEATURE_SVE256 = (1 << 1) + +HostFeaturesLookup = { + "SVE128" : HostFeatures.FEATURE_SVE128, + "SVE256" : HostFeatures.FEATURE_SVE256, +} + +def GetHostFeatures(data): + HostFeaturesData = HostFeatures.FEATURE_ANY + if not (type(data) is list): + sys.exit("Features value must be list of features") + + for data_key in data: + data_key = data_key.upper() + if not (data_key in HostFeaturesLookup): + sys.exit("Invalid host feature") + + HostFeaturesData |= HostFeaturesLookup[data_key] + return HostFeaturesData + +def parse_json_data(json_data, output_binary_path): + Bitness = 64 + EnabledHostFeatures = HostFeatures.FEATURE_ANY + DisabledHostFeatures = HostFeatures.FEATURE_ANY + + if "Features" in json_data: + items = json_data["Features"] + if ("Bitness" in items): + Bitness = int(items["Bitness"]) + + if ("EnabledHostFeatures" in items): + EnabledHostFeatures = GetHostFeatures(items["EnabledHostFeatures"]) + + if ("DisabledHostFeatures" in items): + DisabledHostFeatures = GetHostFeatures(items["DisabledHostFeatures"]) + + for key, items in json_data["Instructions"].items(): + ExpectedInstructionCount = 0 + Optimal = 0 + if ("ExpectedInstructionCount" in items): + ExpectedInstructionCount = int(items["ExpectedInstructionCount"]) + + if ("Optimal" in items): + if items["Optimal"].upper() == "YES": + Optimal = 1 + + TestName = base64.b64encode(key.encode("ascii")).decode("ascii") + tmp_asm = "/tmp/{}.asm".format(TestName) + tmp_asm_out = "/tmp/{}.asm.o".format(TestName) + logging.info("'{}' -> '{}' -> '{}'".format(key, tmp_asm, tmp_asm_out)) + + with open(tmp_asm, "w") as tmp_asm_file: + tmp_asm_file.write("BITS 64;\n") + tmp_asm_file.write("{}\n".format(key)) + + Process = subprocess.Popen(["nasm", tmp_asm, "-o", tmp_asm_out]) + Process.wait() + ResultCode = Process.returncode + + if ResultCode != 0: + os.remove(tmp_asm) + logging.error("Nasm failed to execute") + logging.error("Couldn't compile: '{}'".format(key)) + return ResultCode + + if not os.path.exists(tmp_asm_out): + logging.error("Nasm didn't emit code?") + os.remove(tmp_asm) + return 1 + + logging.info("Generated asm file") + + with open(tmp_asm_out, "rb") as tmp_asm_out_file: + binary_hex = tmp_asm_out_file.read() + + TestDataMap[TestName] = TestData(key, Optimal, ExpectedInstructionCount, binary_hex) + + os.remove(tmp_asm) + os.remove(tmp_asm_out) + + # Output the test data as follows + # struct TestInfo; + # struct DataHeader { + # uint64_t Bitness; + # uint64_t NumTests; + # uint64_t EnabledHostFeatures; + # uint64_t DisabledHostFeatures; + # TestInfo Tests[NumTests]; + # }; + # struct TestInfo { + # char InstName[32]; + # uint64_t Optimal; + # int64_t ExpectedInstructionCount; + # uint64_t CodeSize; + # uint32_t Cookie; + # uint8_t Code[CodeSize]; + # }; + + MemData = bytes() + # Add the header + MemData += struct.pack('Q', Bitness) + MemData += struct.pack('Q', len(TestDataMap)) + MemData += struct.pack('Q', EnabledHostFeatures.value) + MemData += struct.pack('Q', DisabledHostFeatures.value) + + # Add each test + for key, item in TestDataMap.items(): + MemData += struct.pack('32s', item.Name.encode("ascii")) + MemData += struct.pack('Q', item.Optimal) + MemData += struct.pack('q', item.ExpectedInstructionCount) + MemData += struct.pack('Q', len(item.Code)) + MemData += struct.pack('I', 0x41424344) + MemData += item.Code + + logging.info("Code goign to {}".format(output_binary_path)) + with open(output_binary_path, "wb") as output_binary_file: + output_binary_file.write(MemData) + + return 0 + +def main(): + if sys.version_info[0] < 3: + logging.critical ("Python 3 or a more recent version is required.") + + if (len(sys.argv) < 3): + logging.critical ("usage: %s " % (sys.argv[0])) + + json_path = sys.argv[1] + output_binary_path = sys.argv[2] + + try: + with open(json_path) as json_file: + json_text = json_file.read() + except IOError: + logging.error("IOError!") + return 1 + + try: + json_data = json.loads(json_text) + if not isinstance(json_data, dict): + raise TypeError('JSON data must be a dict') + + return parse_json_data(json_data, output_binary_path) + + except ValueError as ve: + logging.error(f'JSON error: {ve}') + + return 1 + + return 0 + +if __name__ == "__main__": +# execute only if run as a script + sys.exit(main()) diff --git a/Scripts/UpdateInstructionCountJson.py b/Scripts/UpdateInstructionCountJson.py new file mode 100755 index 000000000..41a303a05 --- /dev/null +++ b/Scripts/UpdateInstructionCountJson.py @@ -0,0 +1,65 @@ +#!/usr/bin/python3 +import json +import logging +import sys +logger = logging.getLogger() +logger.setLevel(logging.ERROR) + +def update_performance_numbers(performance_json_path, performance_json, new_json_numbers): + for key, items in new_json_numbers.items(): + if len(key) == 0: + continue + + if not key in performance_json["Instructions"]: + logging.error("{} didn't exist in performance json file?".format(key)) + return 1 + performance_json["Instructions"][key]["ExpectedInstructionCount"] = items + + # Output to the original file. + with open(performance_json_path, "w") as json_file: + json.dump(performance_json, json_file, indent=2) + +def main(): + if sys.version_info[0] < 3: + logging.critical ("Python 3 or a more recent version is required.") + + if (len(sys.argv) < 3): + logging.critical ("usage: %s " % (sys.argv[0])) + + performance_json_path = sys.argv[1] + new_json_numbers = sys.argv[2] + + try: + with open(new_json_numbers) as json_file: + new_json_numbers_text = json_file.read() + except IOError: + # If there isn't any new json numbers for this file, then it is safe to skip. + return 0 + + try: + with open(performance_json_path) as json_file: + performance_json_text = json_file.read() + except IOError: + logging.error("IOError!") + return 1 + + try: + performance_json_data = json.loads(performance_json_text) + if not isinstance(performance_json_data, dict): + raise TypeError('JSON data must be a dict') + + new_json_numbers_data = json.loads(new_json_numbers_text) + if not isinstance(new_json_numbers_data, dict): + raise TypeError('JSON data must be a dict') + + return update_performance_numbers(performance_json_path, performance_json_data, new_json_numbers_data) + except ValueError as ve: + logging.error(f'JSON error: {ve}') + return 1 + + return 0 + +if __name__ == "__main__": +# execute only if run as a script + sys.exit(main()) + diff --git a/Source/Tools/CMakeLists.txt b/Source/Tools/CMakeLists.txt index 59d7d73f2..ab2c5fcde 100644 --- a/Source/Tools/CMakeLists.txt +++ b/Source/Tools/CMakeLists.txt @@ -25,6 +25,8 @@ if (NOT MINGW_BUILD) target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/) target_link_libraries(${NAME} FEXCore Common pthread) + + add_subdirectory(CodeSizeValidation/) endif() add_subdirectory(FEXLoader/) diff --git a/Source/Tools/CodeSizeValidation/CMakeLists.txt b/Source/Tools/CodeSizeValidation/CMakeLists.txt new file mode 100644 index 000000000..9f1ef665c --- /dev/null +++ b/Source/Tools/CodeSizeValidation/CMakeLists.txt @@ -0,0 +1,14 @@ +list(APPEND LIBS FEXCore Common CommonTools) + +set (SRCS Main.cpp) +add_executable(CodeSizeValidation ${SRCS}) +target_include_directories(CodeSizeValidation + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/Source/ + ${CMAKE_BINARY_DIR}/generated +) +target_link_libraries(CodeSizeValidation + PRIVATE + ${LIBS} + ${PTHREAD_LIB} +) diff --git a/Source/Tools/CodeSizeValidation/Main.cpp b/Source/Tools/CodeSizeValidation/Main.cpp new file mode 100644 index 000000000..758320fcd --- /dev/null +++ b/Source/Tools/CodeSizeValidation/Main.cpp @@ -0,0 +1,432 @@ +#include "DummyHandlers.h" +#include "FEXCore/Core/Context.h" +#include "FEXCore/Debug/InternalThreadState.h" +#include +#include +#include +#include +#include + +namespace CodeSize { + class CodeSizeValidation final { + public: + struct InstructionStats { + uint64_t GuestCodeInstructions{}; + uint64_t HostCodeInstructions{}; + + uint64_t HeaderSize{}; + uint64_t TailSize{}; + }; + + using CodeLines = fextl::vector; + using InstructionData = std::pair; + + bool ParseMessage(char const *Message); + + InstructionData *GetDataForRIP(uint64_t RIP) { + return &RIPToStats[RIP]; + } + + bool InfoPrintingDisabled() const { + return SetupInfoDisabled; + } + + void CalculateBaseStats(FEXCore::Context::Context *CTX, FEXCore::Core::InternalThreadState *Thread); + private: + void ClearStats() { + RIPToStats.clear(); + } + + void SetBaseStats(InstructionStats const &NewBase) { + BaseStats = NewBase; + } + + void CalculateDifferenceBetweenStats(InstructionData *Nop, InstructionData *Fence); + + uint64_t CurrentRIPParse{}; + bool ConsumingDisassembly{}; + InstructionData *CurrentStats{}; + InstructionStats BaseStats{}; + + fextl::unordered_map RIPToStats; + bool SetupInfoDisabled{}; + }; + + constexpr std::string_view RIPMessage = "RIP: 0x"; + constexpr std::string_view GuestCodeMessage = "Guest Code instructions: "; + constexpr std::string_view HostCodeMessage = "Host Code instructions: "; + constexpr std::string_view DisassembleBeginMessage = "Disassemble Begin"; + constexpr std::string_view DisassembleEndMessage = "Disassemble End"; + constexpr std::string_view BlowUpMsg = "Blow-up Amt: "; + + bool CodeSizeValidation::ParseMessage(char const *Message) { + // std::string_view doesn't have contains until c++23. + std::string_view MessageView {Message}; + if (MessageView.find(RIPMessage) != MessageView.npos) { + // New RIP found + std::string_view RIPView = std::string_view{Message + RIPMessage.size()}; + std::from_chars(RIPView.data(), RIPView.end(), CurrentRIPParse, 16); + CurrentStats = &RIPToStats[CurrentRIPParse]; + return false; + } + + if (MessageView.find(GuestCodeMessage) != MessageView.npos) { + std::string_view CodeSizeView = std::string_view{Message + GuestCodeMessage.size()}; + std::from_chars(CodeSizeView.data(), CodeSizeView.end(), CurrentStats->first.GuestCodeInstructions); + return false; + } + if (MessageView.find(HostCodeMessage) != MessageView.npos) { + std::string_view CodeSizeView = std::string_view{Message + HostCodeMessage.size()}; + std::from_chars(CodeSizeView.data(), CodeSizeView.end(), CurrentStats->first.HostCodeInstructions); + + CurrentStats->first.HostCodeInstructions -= BaseStats.HostCodeInstructions; + return false; + } + if (MessageView.find(DisassembleBeginMessage) != MessageView.npos) { + ConsumingDisassembly = true; + // Just so the output isn't a mess. + return false; + } + if (MessageView.find(DisassembleEndMessage) != MessageView.npos) { + ConsumingDisassembly = false; + // Just so the output isn't a mess. + + // Remove the header and tails. + if (BaseStats.HeaderSize) { + CurrentStats->second.erase(CurrentStats->second.begin(), CurrentStats->second.begin() + BaseStats.HeaderSize); + } + if (BaseStats.TailSize) { + CurrentStats->second.erase(CurrentStats->second.end() - BaseStats.TailSize, CurrentStats->second.end()); + } + return false; + } + + if (MessageView.find(BlowUpMsg) != MessageView.npos) { + return false; + } + + if (ConsumingDisassembly) { + // Currently consuming disassembly. Each line will be a single line of disassembly. + CurrentStats->second.push_back(Message); + return false; + } + + return true; + } + + void CodeSizeValidation::CalculateDifferenceBetweenStats(InstructionData *Nop, InstructionData *Fence) { + // Expected format. + // adr x0, #-0x4 (addr 0x7fffe9880054) + // str x0, [x28, #184] + // dmb sy + // ldr x0, pc+8 (addr 0x7fffe988006c) + // blr x0 + // unallocated (Unallocated) + // udf #0x7fff + // unallocated (Unallocated) + // udf #0x0 + // + // First two lines are the header. + // Next comes the implementation (0 instruction size for nop, 1 instruction for fence) + // After that is the tail. + + const auto &NOPCode = Nop->second; + const auto &FENCECode = Fence->second; + + LOGMAN_THROW_A_FMT(NOPCode.size() < FENCECode.size(), "NOP code must be smaller than fence!"); + for (size_t i = 0; i < NOPCode.size(); ++i) { + const auto &NOPLine = NOPCode.at(i); + const auto &FENCELine = FENCECode.at(i); + + const auto NOPmnemonic = std::string_view(NOPLine.data(), NOPLine.find(' ')); + const auto FENCEmnemonic = std::string_view(FENCELine.data(), FENCELine.find(' ')); + + if (NOPmnemonic != FENCEmnemonic) { + // Headersize of a block is now `i` number of instructions. + Nop->first.HeaderSize = i; + + // Tail size is going to be the remaining size + Nop->first.TailSize = NOPCode.size() - i; + break; + } + } + + SetBaseStats(Nop->first); + } + + void CodeSizeValidation::CalculateBaseStats(FEXCore::Context::Context *CTX, FEXCore::Core::InternalThreadState *Thread) { + SetupInfoDisabled = true; + + // Known hardcoded instructions that will generate blocks of particular sizes. + // NOP will never generate any instructions. + constexpr static uint8_t NOP[] = { + 0x90, + }; + + // MFENCE will always generate a block with one instruction. + constexpr static uint8_t MFENCE[] = { + 0x0f, 0xae, 0xf0, + }; + + // Compile the NOP. + CTX->CompileRIP(Thread, (uint64_t)NOP); + // Gather the stats for the NOP. + auto NOPStats = GetDataForRIP((uint64_t)NOP); + + // Compile MFence + CTX->CompileRIP(Thread, (uint64_t)MFENCE); + + // Get MFence stats. + auto MFENCEStats = GetDataForRIP((uint64_t)MFENCE); + + // Now scan the difference in disasembly between NOP and MFENCE to remove the header and tail. + // Just searching for first instruction change. + + CalculateDifferenceBetweenStats(NOPStats, MFENCEStats); + // Now that the stats have been cleared. Clear our currentStats. + ClearStats(); + + // Invalidate the code ranges to be safe. + CTX->InvalidateGuestCodeRange(Thread, (uint64_t)NOP, sizeof(NOP)); + CTX->InvalidateGuestCodeRange(Thread, (uint64_t)MFENCE, sizeof(MFENCE)); + SetupInfoDisabled = false; + } + + static CodeSizeValidation Validation{}; +} + +void MsgHandler(LogMan::DebugLevels Level, char const *Message) { + const char *CharLevel{nullptr}; + + switch (Level) { + case LogMan::NONE: + CharLevel = "NONE"; + break; + case LogMan::ASSERT: + CharLevel = "ASSERT"; + break; + case LogMan::ERROR: + CharLevel = "ERROR"; + break; + case LogMan::DEBUG: + CharLevel = "DEBUG"; + break; + case LogMan::INFO: + CharLevel = "Info"; + // Disassemble information is sent through the Info log level. + if (!CodeSize::Validation.ParseMessage(Message)) { + return; + } + if (CodeSize::Validation.InfoPrintingDisabled()) { + return; + } + break; + default: + CharLevel = "???"; + break; + } + fextl::fmt::print("[{}] {}\n", CharLevel, Message); +} + +void AssertHandler(char const *Message) { + fextl::fmt::print("[ASSERT] {}\n", Message); + + // make sure buffers are flushed + fflush(nullptr); +} + +struct TestInfo { + char TestInst[32]; + uint64_t Optimal; + int64_t ExpectedInstructionCount; + uint64_t CodeSize; + uint32_t Cookie; + uint8_t Code[]; +}; + +struct TestHeader { + uint64_t Bitness; + uint64_t NumTests{}; + uint64_t EnabledHostFeatures; + uint64_t DisabledHostFeatures; + TestInfo Tests[]; +}; + +static fextl::vector TestData; +static TestHeader const *TestHeaderData{}; + +static bool TestInstructions(FEXCore::Context::Context *CTX, FEXCore::Core::InternalThreadState *Thread, const char *UpdatedInstructionCountsPath) { + LogMan::Msg::IFmt("Compiling code"); + + // Tell FEXCore to compile all the instructions upfront. + TestInfo const *CurrentTest = &TestHeaderData->Tests[0]; + for (size_t i = 0; i < TestHeaderData->NumTests; ++i) { + uint64_t CodeRIP = (uint64_t)&CurrentTest->Code[0]; + // Compile the INST. + CTX->CompileRIP(Thread, CodeRIP); + + // Go to the next test. + CurrentTest = reinterpret_cast(&CurrentTest->Code[CurrentTest->CodeSize]); + } + + bool TestsPassed {true}; + bool InstructionCountChanged {}; + + // Get all the data for the instructions compiled. + CurrentTest = &TestHeaderData->Tests[0]; + for (size_t i = 0; i < TestHeaderData->NumTests; ++i) { + uint64_t CodeRIP = (uint64_t)CurrentTest->Code; + // Get the instruction stats. + auto INSTStats = CodeSize::Validation.GetDataForRIP(CodeRIP); + + LogMan::Msg::IFmt("Testing instruction '{}': {} host instructions", CurrentTest->TestInst, INSTStats->first.HostCodeInstructions); + + // Show the code if we know the implementation isn't optimal or if the count of instructions changed to something we didn't expect. + bool ShouldShowCode = CurrentTest->Optimal == 0 || + INSTStats->first.HostCodeInstructions != CurrentTest->ExpectedInstructionCount; + + if (ShouldShowCode) { + for (auto Line : INSTStats->second) { + LogMan::Msg::EFmt("\t{}", Line); + } + } + + if (INSTStats->first.HostCodeInstructions != CurrentTest->ExpectedInstructionCount) { + LogMan::Msg::EFmt("Fail: '{}': {} host instructions", CurrentTest->TestInst, INSTStats->first.HostCodeInstructions); + LogMan::Msg::EFmt("Fail: Test took {} instructions but we expected {} instructions!", INSTStats->first.HostCodeInstructions, CurrentTest->ExpectedInstructionCount); + InstructionCountChanged = true; + + if (CurrentTest->Optimal) { + // Don't count the test as a failure if it's known non-optimal. + TestsPassed = false; + } + } + + // Go to the next test. + CurrentTest = reinterpret_cast(&CurrentTest->Code[CurrentTest->CodeSize]); + } + + if (UpdatedInstructionCountsPath) { + // Unlink the file. + unlink(UpdatedInstructionCountsPath); + + if (!InstructionCountChanged) { + // If no instruction count changed then just return the results. + return TestsPassed; + } + + FEXCore::File::File FD(UpdatedInstructionCountsPath, FEXCore::File::FileModes::WRITE | FEXCore::File::FileModes::CREATE | FEXCore::File::FileModes::TRUNCATE); + + if (!FD.IsValid()) { + // If we couldn't open the file then early exit this. + LogMan::Msg::EFmt("Couldn't open {} for updating instruction counts", UpdatedInstructionCountsPath); + return TestsPassed; + } + + FD.Write("{\n", 2); + + CurrentTest = &TestHeaderData->Tests[0]; + for (size_t i = 0; i < TestHeaderData->NumTests; ++i) { + uint64_t CodeRIP = (uint64_t)CurrentTest->Code; + // Get the instruction stats. + auto INSTStats = CodeSize::Validation.GetDataForRIP(CodeRIP); + + if (INSTStats->first.HostCodeInstructions != CurrentTest->ExpectedInstructionCount) { + FD.Write(fextl::fmt::format("\t\"{}\": {},\n", CurrentTest->TestInst, INSTStats->first.HostCodeInstructions)); + } + + // Go to the next test. + CurrentTest = reinterpret_cast(&CurrentTest->Code[CurrentTest->CodeSize]); + } + + // Print a null member + FD.Write(fextl::fmt::format("\t\"\": \"\"")); + + FD.Write("}\n", 2); + } + return TestsPassed; +} + +bool LoadTests(const char *Path) { + if (!FEXCore::FileLoading::LoadFile(TestData, Path)) { + return false; + } + + TestHeaderData = reinterpret_cast(TestData.data()); + return true; +} + +int main(int argc, char **argv, char **const envp) { + FEXCore::Allocator::GLIBCScopedFault GLIBFaultScope; + LogMan::Throw::InstallHandler(AssertHandler); + LogMan::Msg::InstallHandler(MsgHandler); + FEXCore::Config::Initialize(); + FEXCore::Config::Load(); + FEXCore::Config::ReloadMetaLayer(); + + if (argc < 2) { + LogMan::Msg::EFmt("Usage: {} [Changed instruction count.json]", argv[0]); + return 1; + } + + if (!LoadTests(argv[1])) { + LogMan::Msg::EFmt("Couldn't load tests from {}", argv[1]); + return 1; + } + + // Setup configurations that this tool needs + // Maximum one instruction. + FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_MAXINST, "1"); + // IRJIT. Only works on JITs. + FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_CORE, fextl::fmt::format("{}", static_cast(FEXCore::Config::CONFIG_IRJIT))); + // Enable block disassembly. + FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_DISASSEMBLE, fextl::fmt::format("{}", static_cast(FEXCore::Config::Disassemble::BLOCKS | FEXCore::Config::Disassemble::STATS))); + // Choose bitness. + FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_IS64BIT_MODE, TestHeaderData->Bitness == 64 ? "1" : 0); + + // Host feature override. Only supports overriding SVE width. + enum HostFeatures { + FEATURE_SVE128 = (1U << 0), + FEATURE_SVE256 = (1U << 1), + }; + + uint64_t SVEWidth = 0; + uint64_t HostFeatureControl{}; + if (TestHeaderData->EnabledHostFeatures & FEATURE_SVE128) { + HostFeatureControl |= static_cast(FEXCore::Config::HostFeatures::ENABLESVE); + SVEWidth = 128; + } + if (TestHeaderData->EnabledHostFeatures & FEATURE_SVE256) { + HostFeatureControl |= static_cast(FEXCore::Config::HostFeatures::ENABLEAVX); + SVEWidth = 256; + } + + if (TestHeaderData->DisabledHostFeatures & FEATURE_SVE128) { + HostFeatureControl |= static_cast(FEXCore::Config::HostFeatures::DISABLESVE); + } + if (TestHeaderData->DisabledHostFeatures & FEATURE_SVE256) { + HostFeatureControl |= static_cast(FEXCore::Config::HostFeatures::DISABLEAVX); + } + FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_HOSTFEATURES, fextl::fmt::format("{}", HostFeatureControl)); + FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_FORCESVEWIDTH, fextl::fmt::format("{}", SVEWidth)); + + // Initialize static tables. + FEXCore::Context::InitializeStaticTables(TestHeaderData->Bitness == 64 ? FEXCore::Context::MODE_64BIT : FEXCore::Context::MODE_32BIT); + + // Create FEXCore context. + auto CTX = FEXCore::Context::Context::CreateNewContext(); + + CTX->InitializeContext(); + auto SignalDelegation = FEX::DummyHandlers::CreateSignalDelegator(); + auto SyscallHandler = FEX::DummyHandlers::CreateSyscallHandler(); + + CTX->SetSignalDelegator(SignalDelegation.get()); + CTX->SetSyscallHandler(SyscallHandler.get()); + auto ParentThread = CTX->InitCore(0, 0); + + // Calculate the base stats for instruction testing. + CodeSize::Validation.CalculateBaseStats(CTX.get(), ParentThread); + + // Test all the instructions. + return TestInstructions(CTX.get(), ParentThread, argc >= 2 ? argv[2] : nullptr) ? 0 : 1; +} diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 416b549e0..99269289e 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -19,3 +19,4 @@ endif() add_subdirectory(ASM/) add_subdirectory(32Bit_ASM/) +add_subdirectory(InstructionCountCI/) diff --git a/unittests/InstructionCountCI/CMakeLists.txt b/unittests/InstructionCountCI/CMakeLists.txt new file mode 100644 index 000000000..b837057e4 --- /dev/null +++ b/unittests/InstructionCountCI/CMakeLists.txt @@ -0,0 +1,64 @@ +# Careful. Globbing can't see changes to the contents of files +# Need to do a fresh clean to see changes +file(GLOB_RECURSE JSON_SOURCES CONFIGURE_DEPENDS *.json) + +set(JSON_DEPENDS "") +set(JSON_UPDATE_DEPENDS "") + +foreach(JSON_SRC ${JSON_SOURCES}) + file(RELATIVE_PATH REL_JSON ${CMAKE_SOURCE_DIR} ${JSON_SRC}) + file(RELATIVE_PATH REL_TEST_JSON ${CMAKE_CURRENT_SOURCE_DIR} ${JSON_SRC}) + get_filename_component(JSON_NAME ${JSON_SRC} NAME) + get_filename_component(JSON_DIR "${REL_JSON}" DIRECTORY) + set(OUTPUT_JSON_FOLDER "${CMAKE_BINARY_DIR}/${JSON_DIR}") + + # Generate build directory + file(MAKE_DIRECTORY "${OUTPUT_JSON_FOLDER}") + set(OUTPUT_JSON_NAME "${OUTPUT_JSON_FOLDER}/${JSON_NAME}.instcountci") + set(OUTPUT_JSON_NEWNUMBERS_NAME "${OUTPUT_JSON_FOLDER}/${JSON_NAME}.instcountci.json") + + add_custom_command(OUTPUT ${OUTPUT_JSON_NAME} + DEPENDS "${JSON_SRC}" + DEPENDS "${CMAKE_SOURCE_DIR}/Scripts/InstructionCountParser.py" + COMMAND "python3" ARGS "${CMAKE_SOURCE_DIR}/Scripts/InstructionCountParser.py" "${JSON_SRC}" "${OUTPUT_JSON_NAME}") + + list(APPEND JSON_DEPENDS "${OUTPUT_JSON_NAME}") + + if (NOT MINGW_BUILD) + set (LAUNCH_PROGRAM "${CMAKE_BINARY_DIR}/Bin/CodeSizeValidation") + else() + set (LAUNCH_PROGRAM "wine" "${CMAKE_BINARY_DIR}/Bin/CodeSizeValidation.exe") + endif() + + set(TEST_NAME "InstCountCI/Test_${JSON_NAME}.instcountci") + set(TEST_NAME_UPDATE_NUMBERS "InstCountCI/Test_${JSON_NAME}.new_numbers") + + add_test(NAME ${TEST_NAME} + COMMAND ${LAUNCH_PROGRAM} "${OUTPUT_JSON_NAME}" "${OUTPUT_JSON_NEWNUMBERS_NAME}") + + add_test(NAME ${TEST_NAME_UPDATE_NUMBERS} + COMMAND "python3" "${CMAKE_SOURCE_DIR}/Scripts/UpdateInstructionCountJson.py" "${JSON_SRC}" "${OUTPUT_JSON_NEWNUMBERS_NAME}") + + set_property(TEST ${TEST_NAME} APPEND PROPERTY DEPENDS "${CMAKE_BINARY_DIR}/Bin/CodeSizeValidation") + set_property(TEST ${TEST_NAME} APPEND PROPERTY DEPENDS "${OUTPUT_JSON_NAME}") + + set_property(TEST ${TEST_NAME_UPDATE_NUMBERS} APPEND PROPERTY DEPENDS "${TEST_NAME}") +endforeach() + +add_custom_target(instcountci_test_files ALL + DEPENDS "${JSON_DEPENDS}") + +execute_process(COMMAND "nproc" OUTPUT_VARIABLE CORES) +string(STRIP ${CORES} CORES) + +add_custom_target( + instcountci_tests + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + USES_TERMINAL + COMMAND "ctest" "--timeout" "302" "-j${CORES}" "-R" "InstCountCI/\.*.instcountci$$") + +add_custom_target( + instcountci_update_tests + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + USES_TERMINAL + COMMAND "ctest" "--timeout" "302" "-j${CORES}" "-R" "InstCountCI/\.*new_numbers$$") diff --git a/unittests/InstructionCountCI/SecondaryGroup.json b/unittests/InstructionCountCI/SecondaryGroup.json new file mode 100644 index 000000000..27cbbb673 --- /dev/null +++ b/unittests/InstructionCountCI/SecondaryGroup.json @@ -0,0 +1,649 @@ +{ + "Features": { + "Bitness": 64, + "EnabledHostFeatures": [], + "DisabledHostFeatures": [ + "SVE128", + "SVE256" + ] + }, + "Instructions": { + "sgdt [rax]": { + "ExpectedInstructionCount": 4, + "Optimal": "No", + "Comment": "GROUP7 0x0F 0x1 /0" + }, + "bt ax, 0": { + "ExpectedInstructionCount": 7, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /4" + }, + "bt eax, 0": { + "ExpectedInstructionCount": 7, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /4" + }, + "bt rax, 0": { + "ExpectedInstructionCount": 6, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /4" + }, + "bt ax, 15": { + "ExpectedInstructionCount": 8, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /4" + }, + "bt eax, 31": { + "ExpectedInstructionCount": 8, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /4" + }, + "bt rax, 63": { + "ExpectedInstructionCount": 7, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /4" + }, + "bts ax, 0": { + "ExpectedInstructionCount": 9, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /5" + }, + "bts eax, 0": { + "ExpectedInstructionCount": 9, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /5" + }, + "bts rax, 0": { + "ExpectedInstructionCount": 8, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /5" + }, + "bts ax, 15": { + "ExpectedInstructionCount": 10, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /5" + }, + "bts eax, 31": { + "ExpectedInstructionCount": 10, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /5" + }, + "bts rax, 63": { + "ExpectedInstructionCount": 8, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /5" + }, + "btr ax, 0": { + "ExpectedInstructionCount": 10, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /6" + }, + "btr eax, 0": { + "ExpectedInstructionCount": 9, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /6" + }, + "btr rax, 0": { + "ExpectedInstructionCount": 9, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /6" + }, + "btr ax, 15": { + "ExpectedInstructionCount": 11, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /6" + }, + "btr eax, 31": { + "ExpectedInstructionCount": 10, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /6" + }, + "btr rax, 63": { + "ExpectedInstructionCount": 9, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /6" + }, + "btc ax, 0": { + "ExpectedInstructionCount": 9, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /7" + }, + "btc eax, 0": { + "ExpectedInstructionCount": 9, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /7" + }, + "btc rax, 0": { + "ExpectedInstructionCount": 8, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /7" + }, + "btc ax, 15": { + "ExpectedInstructionCount": 10, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /7" + }, + "btc eax, 31": { + "ExpectedInstructionCount": 10, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /7" + }, + "btc rax, 63": { + "ExpectedInstructionCount": 8, + "Optimal": "No", + "Comment": "GROUP8 0x0F 0xBA /7" + }, + "cmpxchg8b [rbp]": { + "ExpectedInstructionCount": 25, + "Optimal": "No", + "Comment": "GROUP9 0x0F 0xC7 /1" + }, + "cmpxchg16b [rbp]": { + "ExpectedInstructionCount": 21, + "Optimal": "No", + "Comment": "GROUP9 0x0F 0xC7 /1" + }, + "rdrand ax": { + "ExpectedInstructionCount": 10, + "Optimal": "No", + "Comment": "GROUP9 0x0F 0xC7 /6" + }, + "rdrand eax": { + "ExpectedInstructionCount": 10, + "Optimal": "No", + "Comment": "GROUP9 0x0F 0xC7 /6" + }, + "rdrand rax": { + "ExpectedInstructionCount": 9, + "Optimal": "No", + "Comment": "GROUP9 0x0F 0xC7 /6" + }, + "rdseed ax": { + "ExpectedInstructionCount": 10, + "Optimal": "No", + "Comment": "GROUP9 0x0F 0xC7 /7" + }, + "rdseed eax": { + "ExpectedInstructionCount": 10, + "Optimal": "No", + "Comment": "GROUP9 0x0F 0xC7 /7" + }, + "rdseed rax": { + "ExpectedInstructionCount": 9, + "Optimal": "No", + "Comment": "GROUP9 0x0F 0xC7 /7" + }, + "psrlw mm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP12 0x0F 0xC7 /2" + }, + "psrlw mm0, 15": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "Yes", + "Comment": "GROUP12 0x0F 0xC7 /2" + }, + "psrlw mm0, 16": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP12 0x0F 0xC7 /2" + }, + "psrlw xmm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP12 0x0F 0xC7 /2" + }, + "psrlw xmm0, 15": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "Yes", + "Comment": "GROUP12 0x0F 0xC7 /2" + }, + "psrlw xmm0, 16": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP12 0x0F 0xC7 /2" + }, + "psraw mm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP12 0x0F 0xC7 /3" + }, + "psraw mm0, 15": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "Yes", + "Comment": "GROUP12 0x0F 0xC7 /3" + }, + "psraw mm0, 16": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "Yes", + "Comment": "GROUP12 0x0F 0xC7 /3" + }, + "psraw xmm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP12 0x0F 0xC7 /3" + }, + "psraw xmm0, 15": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "Yes", + "Comment": "GROUP12 0x0F 0xC7 /3" + }, + "psraw xmm0, 16": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "Yes", + "Comment": "GROUP12 0x0F 0xC7 /3" + }, + "psllw mm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP12 0x0F 0xC7 /6" + }, + "psllw mm0, 15": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "Yes", + "Comment": "GROUP12 0x0F 0xC7 /6" + }, + "psllw mm0, 16": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP12 0x0F 0xC7 /6" + }, + "psllw xmm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP12 0x0F 0xC7 /6" + }, + "psllw xmm0, 15": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "Yes", + "Comment": "GROUP12 0x0F 0xC7 /6" + }, + "psllw xmm0, 16": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP12 0x0F 0xC7 /6" + }, + "psrld mm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP13 0x0F 0xC7 /2" + }, + "psrld mm0, 31": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "Yes", + "Comment": "GROUP13 0x0F 0xC7 /2" + }, + "psrld mm0, 32": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP13 0x0F 0xC7 /2" + }, + "psrld xmm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP13 0x0F 0xC7 /2" + }, + "psrld xmm0, 31": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "Yes", + "Comment": "GROUP13 0x0F 0xC7 /2" + }, + "psrld xmm0, 32": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP13 0x0F 0xC7 /2" + }, + "psrad mm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP13 0x0F 0xC7 /3" + }, + "psrad mm0, 31": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "Yes", + "Comment": "GROUP13 0x0F 0xC7 /3" + }, + "psrad mm0, 32": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "Yes", + "Comment": "GROUP13 0x0F 0xC7 /3" + }, + "psrad xmm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP13 0x0F 0xC7 /3" + }, + "psrad xmm0, 31": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "Yes", + "Comment": "GROUP13 0x0F 0xC7 /3" + }, + "psrad xmm0, 32": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "Yes", + "Comment": "GROUP13 0x0F 0xC7 /3" + }, + "pslld mm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP13 0x0F 0xC7 /6" + }, + "pslld mm0, 31": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "Yes", + "Comment": "GROUP13 0x0F 0xC7 /6" + }, + "pslld mm0, 32": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP13 0x0F 0xC7 /6" + }, + "pslld xmm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP13 0x0F 0xC7 /6" + }, + "pslld xmm0, 31": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "Yes", + "Comment": "GROUP13 0x0F 0xC7 /6" + }, + "pslld xmm0, 32": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP13 0x0F 0xC7 /6" + }, + "psrlq mm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /2" + }, + "psrlq mm0, 63": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /2" + }, + "psrlq mm0, 64": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /2" + }, + "psrlq xmm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /2" + }, + "psrlq xmm0, 63": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "Yes", + "Comment": "GROUP14 0x0F 0xC7 /2" + }, + "psrlq xmm0, 64": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /2" + }, + "psrldq xmm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /3" + }, + "psrldq xmm0, 15": { + "ExpectedInstructionCount": 2, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /3" + }, + "psrldq xmm0, 16": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /3" + }, + "psllq mm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /6" + }, + "psllq mm0, 63": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "Yes", + "Comment": "GROUP14 0x0F 0xC7 /6" + }, + "psllq mm0, 64": { + "ExpectedInstructionCount": 3, + "Type": "MMX", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /6" + }, + "psllq xmm0, 0": { + "ExpectedInstructionCount": 2, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /6" + }, + "psllq xmm0, 63": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "Yes", + "Comment": "GROUP14 0x0F 0xC7 /6" + }, + "psllq xmm0, 64": { + "ExpectedInstructionCount": 1, + "Type": "SSE", + "Optimal": "No", + "Comment": "GROUP14 0x0F 0xC7 /6" + }, + "fxsave [rax]": { + "ExpectedInstructionCount": 73, + "Optimal": "No", + "Comment": "GROUP15 0x0F 0xAE /0" + }, + "rdfsbase eax": { + "ExpectedInstructionCount": 1, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /0" + }, + "rdfsbase rax": { + "ExpectedInstructionCount": 1, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /0" + }, + "fxrstor [rax]": { + "ExpectedInstructionCount": 106, + "Optimal": "No", + "Comment": "GROUP15 0x0F 0xAE /1" + }, + "rdgsbase eax": { + "ExpectedInstructionCount": 1, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /1" + }, + "rdgsbase rax": { + "ExpectedInstructionCount": 1, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /1" + }, + "ldmxcsr [rax]": { + "ExpectedInstructionCount": 16, + "Optimal": "No", + "Comment": "GROUP15 0x0F 0xAE /2" + }, + "wrfsbase eax": { + "ExpectedInstructionCount": 2, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /2" + }, + "wrfsbase rax": { + "ExpectedInstructionCount": 1, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /2" + }, + "stmxcsr [rax]": { + "ExpectedInstructionCount": 17, + "Optimal": "No", + "Comment": "GROUP15 0x0F 0xAE /3" + }, + "wrgsbase eax": { + "ExpectedInstructionCount": 2, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /3" + }, + "wrgsbase rax": { + "ExpectedInstructionCount": 1, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /3" + }, + "xsave [rax]": { + "ExpectedInstructionCount": 86, + "Optimal": "No", + "Comment": "GROUP15 0x0F 0xAE /4" + }, + "lfence": { + "ExpectedInstructionCount": 1, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /5" + }, + "xrstor [rax]": { + "ExpectedInstructionCount": 198, + "Optimal": "No", + "Comment": "GROUP15 0x0F 0xAE /5" + }, + "mfence": { + "ExpectedInstructionCount": 1, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /6" + }, + "clwb [rax]": { + "ExpectedInstructionCount": 3, + "Optimal": "No", + "Comment": "GROUP15 0x0F 0xAE /6" + }, + "sfence": { + "ExpectedInstructionCount": 1, + "Optimal": "Yes", + "Comment": "GROUP15 0x0F 0xAE /7" + }, + "clflush [rax]": { + "ExpectedInstructionCount": 4, + "Optimal": "No", + "Comment": "GROUP15 0x0F 0xAE /7" + }, + "clflushopt [rax]": { + "ExpectedInstructionCount": 3, + "Optimal": "No", + "Comment": "GROUP15 0x0F 0xAE /7" + }, + "prefetchnta [rax]": { + "ExpectedInstructionCount": 0, + "Optimal": "Yes", + "Comment": [ + "GROUP16 0x0F 0x18 /0", + "NOP implementation" + ] + }, + "prefetcht0 [rax]": { + "ExpectedInstructionCount": 0, + "Optimal": "Yes", + "Comment": [ + "GROUP16 0x0F 0x18 /1", + "NOP implementation" + ] + }, + "prefetcht1 [rax]": { + "ExpectedInstructionCount": 0, + "Optimal": "Yes", + "Comment": [ + "GROUP16 0x0F 0x18 /2", + "NOP implementation" + ] + }, + "prefetcht2 [rax]": { + "ExpectedInstructionCount": 0, + "Optimal": "Yes", + "Comment": [ + "GROUP16 0x0F 0x18 /3", + "NOP implementation" + ] + }, + "db 0x0f, 0x18, 0x20;": { + "ExpectedInstructionCount": 0, + "Optimal": "Yes", + "Comment": [ + "GROUP16 0x0F 0x18 /4", + "nop dword [rax]", + "NOP implementation" + ] + }, + "db 0x0f, 0x0d, 0x00": { + "ExpectedInstructionCount": 0, + "Optimal": "Yes", + "Comment": [ + "GROUPP 0x0F 0x0D /0", + "prefetch_exclusive [rax]", + "NOP implementation" + ] + }, + "prefetchw [rax]": { + "ExpectedInstructionCount": 0, + "Optimal": "Yes", + "Comment": [ + "GROUPP 0x0F 0x0D /1", + "NOP implementation" + ] + }, + "prefetchwt1 [rax]": { + "ExpectedInstructionCount": 0, + "Optimal": "Yes", + "Comment": [ + "GROUPP 0x0F 0x0D /2", + "NOP implementation" + ] + } + } +} \ No newline at end of file