/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ART_COMPILER_UTILS_ASSEMBLER_TEST_H_ #define ART_COMPILER_UTILS_ASSEMBLER_TEST_H_ #include "assembler.h" #include "assembler_test_base.h" #include "common_runtime_test.h" // For ScratchFile #include #include #include #include #include namespace art { // Helper for a constexpr string length. constexpr size_t ConstexprStrLen(char const* str, size_t count = 0) { return ('\0' == str[0]) ? count : ConstexprStrLen(str+1, count+1); } enum class RegisterView { // private kUsePrimaryName, kUseSecondaryName, kUseTertiaryName, kUseQuaternaryName, }; template class AssemblerTest : public testing::Test { public: Ass* GetAssembler() { return assembler_.get(); } typedef std::string (*TestFn)(AssemblerTest* assembler_test, Ass* assembler); void DriverFn(TestFn f, std::string test_name) { DriverWrapper(f(this, assembler_.get()), test_name); } // This driver assumes the assembler has already been called. void DriverStr(std::string assembly_string, std::string test_name) { DriverWrapper(assembly_string, test_name); } std::string RepeatR(void (Ass::*f)(Reg), std::string fmt) { return RepeatTemplatedRegister(f, GetRegisters(), &AssemblerTest::GetRegName, fmt); } std::string Repeatr(void (Ass::*f)(Reg), std::string fmt) { return RepeatTemplatedRegister(f, GetRegisters(), &AssemblerTest::GetRegName, fmt); } std::string RepeatRR(void (Ass::*f)(Reg, Reg), std::string fmt) { return RepeatTemplatedRegisters(f, GetRegisters(), GetRegisters(), &AssemblerTest::GetRegName, &AssemblerTest::GetRegName, fmt); } std::string Repeatrr(void (Ass::*f)(Reg, Reg), std::string fmt) { return RepeatTemplatedRegisters(f, GetRegisters(), GetRegisters(), &AssemblerTest::GetRegName, &AssemblerTest::GetRegName, fmt); } std::string Repeatrb(void (Ass::*f)(Reg, Reg), std::string fmt) { return RepeatTemplatedRegisters(f, GetRegisters(), GetRegisters(), &AssemblerTest::GetRegName, &AssemblerTest::GetRegName, fmt); } std::string RepeatRr(void (Ass::*f)(Reg, Reg), std::string fmt) { return RepeatTemplatedRegisters(f, GetRegisters(), GetRegisters(), &AssemblerTest::GetRegName, &AssemblerTest::GetRegName, fmt); } std::string RepeatRI(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) { return RepeatRegisterImm(f, imm_bytes, fmt); } std::string Repeatri(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) { return RepeatRegisterImm(f, imm_bytes, fmt); } std::string RepeatFF(void (Ass::*f)(FPReg, FPReg), std::string fmt) { return RepeatTemplatedRegisters(f, GetFPRegisters(), GetFPRegisters(), &AssemblerTest::GetFPRegName, &AssemblerTest::GetFPRegName, fmt); } std::string RepeatFFI(void (Ass::*f)(FPReg, FPReg, const Imm&), size_t imm_bytes, std::string fmt) { return RepeatTemplatedRegistersImm(f, GetFPRegisters(), GetFPRegisters(), &AssemblerTest::GetFPRegName, &AssemblerTest::GetFPRegName, imm_bytes, fmt); } std::string RepeatFR(void (Ass::*f)(FPReg, Reg), std::string fmt) { return RepeatTemplatedRegisters(f, GetFPRegisters(), GetRegisters(), &AssemblerTest::GetFPRegName, &AssemblerTest::GetRegName, fmt); } std::string RepeatFr(void (Ass::*f)(FPReg, Reg), std::string fmt) { return RepeatTemplatedRegisters(f, GetFPRegisters(), GetRegisters(), &AssemblerTest::GetFPRegName, &AssemblerTest::GetRegName, fmt); } std::string RepeatRF(void (Ass::*f)(Reg, FPReg), std::string fmt) { return RepeatTemplatedRegisters(f, GetRegisters(), GetFPRegisters(), &AssemblerTest::GetRegName, &AssemblerTest::GetFPRegName, fmt); } std::string RepeatrF(void (Ass::*f)(Reg, FPReg), std::string fmt) { return RepeatTemplatedRegisters(f, GetRegisters(), GetFPRegisters(), &AssemblerTest::GetRegName, &AssemblerTest::GetFPRegName, fmt); } std::string RepeatI(void (Ass::*f)(const Imm&), size_t imm_bytes, std::string fmt, bool as_uint = false) { std::string str; std::vector imms = CreateImmediateValues(imm_bytes, as_uint); WarnOnCombinations(imms.size()); for (int64_t imm : imms) { Imm new_imm = CreateImmediate(imm); (assembler_.get()->*f)(new_imm); std::string base = fmt; size_t imm_index = base.find(IMM_TOKEN); if (imm_index != std::string::npos) { std::ostringstream sreg; sreg << imm; std::string imm_string = sreg.str(); base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string); } if (str.size() > 0) { str += "\n"; } str += base; } // Add a newline at the end. str += "\n"; return str; } // This is intended to be run as a test. bool CheckTools() { return test_helper_->CheckTools(); } // The following functions are public so that TestFn can use them... virtual std::vector GetRegisters() = 0; virtual std::vector GetFPRegisters() { UNIMPLEMENTED(FATAL) << "Architecture does not support floating-point registers"; UNREACHABLE(); } // Secondary register names are the secondary view on registers, e.g., 32b on 64b systems. virtual std::string GetSecondaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) { UNIMPLEMENTED(FATAL) << "Architecture does not support secondary registers"; UNREACHABLE(); } // Tertiary register names are the tertiary view on registers, e.g., 16b on 64b systems. virtual std::string GetTertiaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) { UNIMPLEMENTED(FATAL) << "Architecture does not support tertiary registers"; UNREACHABLE(); } // Quaternary register names are the quaternary view on registers, e.g., 8b on 64b systems. virtual std::string GetQuaternaryRegisterName(const Reg& reg ATTRIBUTE_UNUSED) { UNIMPLEMENTED(FATAL) << "Architecture does not support quaternary registers"; UNREACHABLE(); } std::string GetRegisterName(const Reg& reg) { return GetRegName(reg); } protected: explicit AssemblerTest() {} void SetUp() OVERRIDE { assembler_.reset(new Ass()); test_helper_.reset( new AssemblerTestInfrastructure(GetArchitectureString(), GetAssemblerCmdName(), GetAssemblerParameters(), GetObjdumpCmdName(), GetObjdumpParameters(), GetDisassembleCmdName(), GetDisassembleParameters(), GetAssemblyHeader())); SetUpHelpers(); } void TearDown() OVERRIDE { test_helper_.reset(); // Clean up the helper. } // Override this to set up any architecture-specific things, e.g., register vectors. virtual void SetUpHelpers() {} // Get the typically used name for this architecture, e.g., aarch64, x86_64, ... virtual std::string GetArchitectureString() = 0; // Get the name of the assembler, e.g., "as" by default. virtual std::string GetAssemblerCmdName() { return "as"; } // Switches to the assembler command. Default none. virtual std::string GetAssemblerParameters() { return ""; } // Get the name of the objdump, e.g., "objdump" by default. virtual std::string GetObjdumpCmdName() { return "objdump"; } // Switches to the objdump command. Default is " -h". virtual std::string GetObjdumpParameters() { return " -h"; } // Get the name of the objdump, e.g., "objdump" by default. virtual std::string GetDisassembleCmdName() { return "objdump"; } // Switches to the objdump command. As it's a binary, one needs to push the architecture and // such to objdump, so it's architecture-specific and there is no default. virtual std::string GetDisassembleParameters() = 0; // Create a couple of immediate values up to the number of bytes given. virtual std::vector CreateImmediateValues(size_t imm_bytes, bool as_uint = false) { std::vector res; res.push_back(0); if (!as_uint) { res.push_back(-1); } else { res.push_back(0xFF); } res.push_back(0x12); if (imm_bytes >= 2) { res.push_back(0x1234); if (!as_uint) { res.push_back(-0x1234); } else { res.push_back(0xFFFF); } if (imm_bytes >= 4) { res.push_back(0x12345678); if (!as_uint) { res.push_back(-0x12345678); } else { res.push_back(0xFFFFFFFF); } if (imm_bytes >= 6) { res.push_back(0x123456789ABC); if (!as_uint) { res.push_back(-0x123456789ABC); } if (imm_bytes >= 8) { res.push_back(0x123456789ABCDEF0); if (!as_uint) { res.push_back(-0x123456789ABCDEF0); } else { res.push_back(0xFFFFFFFFFFFFFFFF); } } } } } return res; } // Create an immediate from the specific value. virtual Imm CreateImmediate(int64_t imm_value) = 0; template std::string RepeatTemplatedRegister(void (Ass::*f)(RegType), const std::vector registers, std::string (AssemblerTest::*GetName)(const RegType&), std::string fmt) { std::string str; for (auto reg : registers) { (assembler_.get()->*f)(*reg); std::string base = fmt; std::string reg_string = (this->*GetName)(*reg); size_t reg_index; if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) { base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string); } if (str.size() > 0) { str += "\n"; } str += base; } // Add a newline at the end. str += "\n"; return str; } template std::string RepeatTemplatedRegisters(void (Ass::*f)(Reg1, Reg2), const std::vector reg1_registers, const std::vector reg2_registers, std::string (AssemblerTest::*GetName1)(const Reg1&), std::string (AssemblerTest::*GetName2)(const Reg2&), std::string fmt) { WarnOnCombinations(reg1_registers.size() * reg2_registers.size()); std::string str; for (auto reg1 : reg1_registers) { for (auto reg2 : reg2_registers) { (assembler_.get()->*f)(*reg1, *reg2); std::string base = fmt; std::string reg1_string = (this->*GetName1)(*reg1); size_t reg1_index; while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) { base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string); } std::string reg2_string = (this->*GetName2)(*reg2); size_t reg2_index; while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) { base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string); } if (str.size() > 0) { str += "\n"; } str += base; } } // Add a newline at the end. str += "\n"; return str; } template std::string RepeatTemplatedRegistersImm(void (Ass::*f)(Reg1, Reg2, const Imm&), const std::vector reg1_registers, const std::vector reg2_registers, std::string (AssemblerTest::*GetName1)(const Reg1&), std::string (AssemblerTest::*GetName2)(const Reg2&), size_t imm_bytes, std::string fmt) { std::vector imms = CreateImmediateValues(imm_bytes); WarnOnCombinations(reg1_registers.size() * reg2_registers.size() * imms.size()); std::string str; for (auto reg1 : reg1_registers) { for (auto reg2 : reg2_registers) { for (int64_t imm : imms) { Imm new_imm = CreateImmediate(imm); (assembler_.get()->*f)(*reg1, *reg2, new_imm); std::string base = fmt; std::string reg1_string = (this->*GetName1)(*reg1); size_t reg1_index; while ((reg1_index = base.find(REG1_TOKEN)) != std::string::npos) { base.replace(reg1_index, ConstexprStrLen(REG1_TOKEN), reg1_string); } std::string reg2_string = (this->*GetName2)(*reg2); size_t reg2_index; while ((reg2_index = base.find(REG2_TOKEN)) != std::string::npos) { base.replace(reg2_index, ConstexprStrLen(REG2_TOKEN), reg2_string); } size_t imm_index = base.find(IMM_TOKEN); if (imm_index != std::string::npos) { std::ostringstream sreg; sreg << imm; std::string imm_string = sreg.str(); base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string); } if (str.size() > 0) { str += "\n"; } str += base; } } } // Add a newline at the end. str += "\n"; return str; } template std::string GetRegName(const Reg& reg) { std::ostringstream sreg; switch (kRegView) { case RegisterView::kUsePrimaryName: sreg << reg; break; case RegisterView::kUseSecondaryName: sreg << GetSecondaryRegisterName(reg); break; case RegisterView::kUseTertiaryName: sreg << GetTertiaryRegisterName(reg); break; case RegisterView::kUseQuaternaryName: sreg << GetQuaternaryRegisterName(reg); break; } return sreg.str(); } std::string GetFPRegName(const FPReg& reg) { std::ostringstream sreg; sreg << reg; return sreg.str(); } // If the assembly file needs a header, return it in a sub-class. virtual const char* GetAssemblyHeader() { return nullptr; } void WarnOnCombinations(size_t count) { if (count > kWarnManyCombinationsThreshold) { GTEST_LOG_(WARNING) << "Many combinations (" << count << "), test generation might be slow."; } } static constexpr const char* REG_TOKEN = "{reg}"; static constexpr const char* REG1_TOKEN = "{reg1}"; static constexpr const char* REG2_TOKEN = "{reg2}"; static constexpr const char* IMM_TOKEN = "{imm}"; private: template std::string RepeatRegisterImm(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) { const std::vector registers = GetRegisters(); std::string str; std::vector imms = CreateImmediateValues(imm_bytes); WarnOnCombinations(registers.size() * imms.size()); for (auto reg : registers) { for (int64_t imm : imms) { Imm new_imm = CreateImmediate(imm); (assembler_.get()->*f)(*reg, new_imm); std::string base = fmt; std::string reg_string = GetRegName(*reg); size_t reg_index; while ((reg_index = base.find(REG_TOKEN)) != std::string::npos) { base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string); } size_t imm_index = base.find(IMM_TOKEN); if (imm_index != std::string::npos) { std::ostringstream sreg; sreg << imm; std::string imm_string = sreg.str(); base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string); } if (str.size() > 0) { str += "\n"; } str += base; } } // Add a newline at the end. str += "\n"; return str; } void DriverWrapper(std::string assembly_text, std::string test_name) { size_t cs = assembler_->CodeSize(); std::unique_ptr> data(new std::vector(cs)); MemoryRegion code(&(*data)[0], data->size()); assembler_->FinalizeInstructions(code); test_helper_->Driver(*data, assembly_text, test_name); } static constexpr size_t kWarnManyCombinationsThreshold = 500; std::unique_ptr assembler_; std::unique_ptr test_helper_; DISALLOW_COPY_AND_ASSIGN(AssemblerTest); }; } // namespace art #endif // ART_COMPILER_UTILS_ASSEMBLER_TEST_H_