From acc7f2fa8fa156593b270e0d941e0a1d8deee71c Mon Sep 17 00:00:00 2001 From: Ryan Houdek Date: Thu, 10 Aug 2023 04:47:44 -0700 Subject: [PATCH] FEX: Adds instruction count CI Implements CI for tracking instruction counts for generate blocks of code when transforming from x86 to ARM64 assembly. This will end up encompassing every instruction in our instruction tables similarly to how our assembly tests try to test everything in our instruction tables. Incidentally, the data for this CI is generated using our assembly tests. By enabling disassembly and instruction stats when executing a suite of instructions, this gives the stats that can be added to a json file. The current implementation only implements the SecondGroup table of instructions because it is a relatively small table and has known inefficiencies in the instruction implementations. As this gets merged I will be adding more tables of instructions to additional json files for testing. These JSON files will support adjusting CPU features regardless of the host features so it can test implementations depending on different CPU features. This will let us test things like one instruction having different "optimal" implementations depending on if it supports SVE128, SVE256, SVEI8MM, etc. This initial instruction auditing is what found the bug in our vector shift instructions by size of zero. If inspecting the result of the CI run, you can tell that these instructions still aren't "optimal" because they are doing loads and stores that can be eliminated. The "Optimal" in the JSON is purely for human readable and grepping ability to see what is optimal versus not. Same with the "Comment" section. According to my auditing spreadsheet, the total number of instructions that will end up in these json files will be about 1000, but we will likely end up with more since there will be edge cases that can be more optimal depending on arguments. --- .github/workflows/vixl_simulator.yml | 14 +- Scripts/InstructionCountParser.py | 199 ++++++ Scripts/UpdateInstructionCountJson.py | 65 ++ Source/Tools/CMakeLists.txt | 2 + .../Tools/CodeSizeValidation/CMakeLists.txt | 14 + Source/Tools/CodeSizeValidation/Main.cpp | 432 ++++++++++++ unittests/CMakeLists.txt | 1 + unittests/InstructionCountCI/CMakeLists.txt | 64 ++ .../InstructionCountCI/SecondaryGroup.json | 649 ++++++++++++++++++ 9 files changed, 1439 insertions(+), 1 deletion(-) create mode 100755 Scripts/InstructionCountParser.py create mode 100755 Scripts/UpdateInstructionCountJson.py create mode 100644 Source/Tools/CodeSizeValidation/CMakeLists.txt create mode 100644 Source/Tools/CodeSizeValidation/Main.cpp create mode 100644 unittests/InstructionCountCI/CMakeLists.txt create mode 100644 unittests/InstructionCountCI/SecondaryGroup.json 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