diff options
| -rw-r--r-- | build/Android.gtest.mk | 3 | ||||
| -rw-r--r-- | compiler/utils/assembler_test.h | 687 | ||||
| -rw-r--r-- | compiler/utils/x86_64/assembler_x86_64.cc | 82 | ||||
| -rw-r--r-- | compiler/utils/x86_64/assembler_x86_64.h | 30 | ||||
| -rw-r--r-- | compiler/utils/x86_64/assembler_x86_64_test.cc | 173 |
5 files changed, 961 insertions, 14 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 23583915ff..f25217f3ca 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -105,7 +105,8 @@ COMPILER_GTEST_TARGET_SRC_FILES := \ COMPILER_GTEST_HOST_SRC_FILES := \ $(COMPILER_GTEST_COMMON_SRC_FILES) \ - compiler/utils/x86/assembler_x86_test.cc + compiler/utils/x86/assembler_x86_test.cc \ + compiler/utils/x86_64/assembler_x86_64_test.cc ART_HOST_GTEST_EXECUTABLES := ART_TARGET_GTEST_EXECUTABLES$(ART_PHONY_TEST_TARGET_SUFFIX) := diff --git a/compiler/utils/assembler_test.h b/compiler/utils/assembler_test.h new file mode 100644 index 0000000000..ce1c4de2fa --- /dev/null +++ b/compiler/utils/assembler_test.h @@ -0,0 +1,687 @@ +/* + * 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 "gtest/gtest.h" + +#include <cstdio> +#include <cstdlib> +#include <fstream> +#include <iostream> +#include <iterator> +#include <sys/stat.h> + +namespace art { + +template<typename Ass, typename Reg, typename Imm> +class AssemblerTest : public testing::Test { + public: + Ass* GetAssembler() { + return assembler_.get(); + } + + typedef std::string (*TestFn)(Ass* assembler); + + void DriverFn(TestFn f, std::string test_name) { + Driver(f(assembler_.get()), test_name); + } + + // This driver assumes the assembler has already been called. + void DriverStr(std::string assembly_string, std::string test_name) { + Driver(assembly_string, test_name); + } + + std::string RepeatR(void (Ass::*f)(Reg), std::string fmt) { + const std::vector<Reg*> registers = GetRegisters(); + std::string str; + for (auto reg : registers) { + (assembler_.get()->*f)(*reg); + std::string base = fmt; + + size_t reg_index = base.find("{reg}"); + if (reg_index != std::string::npos) { + std::ostringstream sreg; + sreg << *reg; + std::string reg_string = sreg.str(); + base.replace(reg_index, 5, reg_string); + } + + if (str.size() > 0) { + str += "\n"; + } + str += base; + } + // Add a newline at the end. + str += "\n"; + return str; + } + + std::string RepeatRR(void (Ass::*f)(Reg, Reg), std::string fmt) { + const std::vector<Reg*> registers = GetRegisters(); + std::string str; + for (auto reg1 : registers) { + for (auto reg2 : registers) { + (assembler_.get()->*f)(*reg1, *reg2); + std::string base = fmt; + + size_t reg1_index = base.find("{reg1}"); + if (reg1_index != std::string::npos) { + std::ostringstream sreg; + sreg << *reg1; + std::string reg_string = sreg.str(); + base.replace(reg1_index, 6, reg_string); + } + + size_t reg2_index = base.find("{reg2}"); + if (reg2_index != std::string::npos) { + std::ostringstream sreg; + sreg << *reg2; + std::string reg_string = sreg.str(); + base.replace(reg2_index, 6, reg_string); + } + + if (str.size() > 0) { + str += "\n"; + } + str += base; + } + } + // Add a newline at the end. + str += "\n"; + return str; + } + + std::string RepeatRI(void (Ass::*f)(Reg, const Imm&), size_t imm_bytes, std::string fmt) { + const std::vector<Reg*> registers = GetRegisters(); + std::string str; + std::vector<int64_t> imms = CreateImmediateValues(imm_bytes); + for (auto reg : registers) { + for (int64_t imm : imms) { + Imm* new_imm = CreateImmediate(imm); + (assembler_.get()->*f)(*reg, *new_imm); + delete new_imm; + std::string base = fmt; + + size_t reg_index = base.find("{reg}"); + if (reg_index != std::string::npos) { + std::ostringstream sreg; + sreg << *reg; + std::string reg_string = sreg.str(); + base.replace(reg_index, 5, reg_string); + } + + size_t imm_index = base.find("{imm}"); + if (imm_index != std::string::npos) { + std::ostringstream sreg; + sreg << imm; + std::string imm_string = sreg.str(); + base.replace(imm_index, 5, imm_string); + } + + if (str.size() > 0) { + str += "\n"; + } + str += base; + } + } + // Add a newline at the end. + str += "\n"; + return str; + } + + std::string RepeatI(void (Ass::*f)(const Imm&), size_t imm_bytes, std::string fmt) { + std::string str; + std::vector<int64_t> imms = CreateImmediateValues(imm_bytes); + for (int64_t imm : imms) { + Imm* new_imm = CreateImmediate(imm); + (assembler_.get()->*f)(*new_imm); + delete new_imm; + std::string base = fmt; + + size_t imm_index = base.find("{imm}"); + if (imm_index != std::string::npos) { + std::ostringstream sreg; + sreg << imm; + std::string imm_string = sreg.str(); + base.replace(imm_index, 5, 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() { + if (!FileExists(GetAssemblerCommand())) { + return false; + } + LOG(INFO) << "Chosen assembler command: " << GetAssemblerCommand(); + + if (!FileExists(GetObjdumpCommand())) { + return false; + } + LOG(INFO) << "Chosen objdump command: " << GetObjdumpCommand(); + + // Disassembly is optional. + std::string disassembler = GetDisassembleCommand(); + if (disassembler.length() != 0) { + if (!FileExists(disassembler)) { + return false; + } + LOG(INFO) << "Chosen disassemble command: " << GetDisassembleCommand(); + } else { + LOG(INFO) << "No disassembler given."; + } + + return true; + } + + protected: + void SetUp() OVERRIDE { + assembler_.reset(new Ass()); + + SetUpHelpers(); + } + + // Override this to set up any architecture-specific things, e.g., register vectors. + virtual void SetUpHelpers() {} + + virtual std::vector<Reg*> GetRegisters() = 0; + + // 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 ""; + } + + // Return the host assembler command for this test. + virtual std::string GetAssemblerCommand() { + // Already resolved it once? + if (resolved_assembler_cmd_.length() != 0) { + return resolved_assembler_cmd_; + } + + std::string line = FindTool(GetAssemblerCmdName()); + if (line.length() == 0) { + return line; + } + + resolved_assembler_cmd_ = line + GetAssemblerParameters(); + + return line; + } + + // 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"; + } + + // Return the host objdump command for this test. + virtual std::string GetObjdumpCommand() { + // Already resolved it once? + if (resolved_objdump_cmd_.length() != 0) { + return resolved_objdump_cmd_; + } + + std::string line = FindTool(GetObjdumpCmdName()); + if (line.length() == 0) { + return line; + } + + resolved_objdump_cmd_ = line + GetObjdumpParameters(); + + return line; + } + + // 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; + + // Return the host disassembler command for this test. + virtual std::string GetDisassembleCommand() { + // Already resolved it once? + if (resolved_disassemble_cmd_.length() != 0) { + return resolved_disassemble_cmd_; + } + + std::string line = FindTool(GetDisassembleCmdName()); + if (line.length() == 0) { + return line; + } + + resolved_disassemble_cmd_ = line + GetDisassembleParameters(); + + return line; + } + + // Create a couple of immediate values up to the number of bytes given. + virtual std::vector<int64_t> CreateImmediateValues(size_t imm_bytes) { + std::vector<int64_t> res; + res.push_back(0); + res.push_back(-1); + res.push_back(0x12); + if (imm_bytes >= 2) { + res.push_back(0x1234); + res.push_back(-0x1234); + if (imm_bytes >= 4) { + res.push_back(0x12345678); + res.push_back(-0x12345678); + if (imm_bytes >= 6) { + res.push_back(0x123456789ABC); + res.push_back(-0x123456789ABC); + if (imm_bytes >= 8) { + res.push_back(0x123456789ABCDEF0); + res.push_back(-0x123456789ABCDEF0); + } + } + } + } + return res; + } + + // Create an immediate from the specific value. + virtual Imm* CreateImmediate(int64_t imm_value) = 0; + + private: + // Driver() assembles and compares the results. If the results are not equal and we have a + // disassembler, disassemble both and check whether they have the same mnemonics (in which case + // we just warn). + void Driver(std::string assembly_text, std::string test_name) { + EXPECT_NE(assembly_text.length(), 0U) << "Empty assembly"; + + NativeAssemblerResult res; + Compile(assembly_text, &res, test_name); + + EXPECT_TRUE(res.ok) << res.error_msg; + if (!res.ok) { + // No way of continuing. + return; + } + + size_t cs = assembler_->CodeSize(); + UniquePtr<std::vector<uint8_t> > data(new std::vector<uint8_t>(cs)); + MemoryRegion code(&(*data)[0], data->size()); + assembler_->FinalizeInstructions(code); + + if (*data == *res.code) { + Clean(&res); + } else { + if (DisassembleBinaries(*data, *res.code, test_name)) { + if (data->size() > res.code->size()) { + LOG(WARNING) << "Assembly code is not identical, but disassembly of machine code is " + "equal: this implies sub-optimal encoding! Our code size=" << data->size() << + ", gcc size=" << res.code->size(); + } else { + LOG(INFO) << "GCC chose a different encoding than ours, but the overall length is the " + "same."; + } + } else { + // This will output the assembly. + EXPECT_EQ(*data, *res.code) << "Outputs (and disassembly) not identical."; + } + } + } + + // Structure to store intermediates and results. + struct NativeAssemblerResult { + bool ok; + std::string error_msg; + std::string base_name; + UniquePtr<std::vector<uint8_t>> code; + uintptr_t length; + }; + + // Compile the assembly file from_file to a binary file to_file. Returns true on success. + bool Assemble(const char* from_file, const char* to_file, std::string* error_msg) { + bool have_assembler = FileExists(GetAssemblerCommand()); + EXPECT_TRUE(have_assembler) << "Cannot find assembler:" << GetAssemblerCommand(); + if (!have_assembler) { + return false; + } + + std::vector<std::string> args; + + args.push_back(GetAssemblerCommand()); + args.push_back("-o"); + args.push_back(to_file); + args.push_back(from_file); + + return Exec(args, error_msg); + } + + // Runs objdump -h on the binary file and extracts the first line with .text. + // Returns "" on failure. + std::string Objdump(std::string file) { + bool have_objdump = FileExists(GetObjdumpCommand()); + EXPECT_TRUE(have_objdump) << "Cannot find objdump: " << GetObjdumpCommand(); + if (!have_objdump) { + return ""; + } + + std::string error_msg; + std::vector<std::string> args; + + args.push_back(GetObjdumpCommand()); + args.push_back(file); + args.push_back(">"); + args.push_back(file+".dump"); + std::string cmd = Join(args, ' '); + + args.clear(); + args.push_back("/bin/sh"); + args.push_back("-c"); + args.push_back(cmd); + + if (!Exec(args, &error_msg)) { + EXPECT_TRUE(false) << error_msg; + } + + std::ifstream dump(file+".dump"); + + std::string line; + bool found = false; + while (std::getline(dump, line)) { + if (line.find(".text") != line.npos) { + found = true; + break; + } + } + + dump.close(); + + if (found) { + return line; + } else { + return ""; + } + } + + // Disassemble both binaries and compare the text. + bool DisassembleBinaries(std::vector<uint8_t>& data, std::vector<uint8_t>& as, + std::string test_name) { + std::string disassembler = GetDisassembleCommand(); + if (disassembler.length() == 0) { + LOG(WARNING) << "No dissassembler command."; + return false; + } + + std::string data_name = WriteToFile(data, test_name + ".ass"); + std::string error_msg; + if (!DisassembleBinary(data_name, &error_msg)) { + LOG(INFO) << "Error disassembling: " << error_msg; + std::remove(data_name.c_str()); + return false; + } + + std::string as_name = WriteToFile(as, test_name + ".gcc"); + if (!DisassembleBinary(as_name, &error_msg)) { + LOG(INFO) << "Error disassembling: " << error_msg; + std::remove(data_name.c_str()); + std::remove((data_name + ".dis").c_str()); + std::remove(as_name.c_str()); + return false; + } + + bool result = CompareFiles(data_name + ".dis", as_name + ".dis"); + + if (result) { + std::remove(data_name.c_str()); + std::remove(as_name.c_str()); + std::remove((data_name + ".dis").c_str()); + std::remove((as_name + ".dis").c_str()); + } + + return result; + } + + bool DisassembleBinary(std::string file, std::string* error_msg) { + std::vector<std::string> args; + + args.push_back(GetDisassembleCommand()); + args.push_back(file); + args.push_back("| sed -n \'/<.data>/,$p\' | sed -e \'s/.*://\'"); + args.push_back(">"); + args.push_back(file+".dis"); + std::string cmd = Join(args, ' '); + + args.clear(); + args.push_back("/bin/sh"); + args.push_back("-c"); + args.push_back(cmd); + + return Exec(args, error_msg); + } + + std::string WriteToFile(std::vector<uint8_t>& buffer, std::string test_name) { + std::string file_name = GetTmpnam() + std::string("---") + test_name; + const char* data = reinterpret_cast<char*>(buffer.data()); + std::ofstream s_out(file_name + ".o"); + s_out.write(data, buffer.size()); + s_out.close(); + return file_name + ".o"; + } + + bool CompareFiles(std::string f1, std::string f2) { + std::ifstream f1_in(f1); + std::ifstream f2_in(f2); + + bool result = std::equal(std::istreambuf_iterator<char>(f1_in), + std::istreambuf_iterator<char>(), + std::istreambuf_iterator<char>(f2_in)); + + f1_in.close(); + f2_in.close(); + + return result; + } + + // Compile the given assembly code and extract the binary, if possible. Put result into res. + bool Compile(std::string assembly_code, NativeAssemblerResult* res, std::string test_name) { + res->ok = false; + res->code.reset(nullptr); + + res->base_name = GetTmpnam() + std::string("---") + test_name; + + // TODO: Lots of error checking. + + std::ofstream s_out(res->base_name + ".S"); + s_out << assembly_code; + s_out.close(); + + if (!Assemble((res->base_name + ".S").c_str(), (res->base_name + ".o").c_str(), + &res->error_msg)) { + res->error_msg = "Could not compile."; + return false; + } + + std::string odump = Objdump(res->base_name + ".o"); + if (odump.length() == 0) { + res->error_msg = "Objdump failed."; + return false; + } + + std::istringstream iss(odump); + std::istream_iterator<std::string> start(iss); + std::istream_iterator<std::string> end; + std::vector<std::string> tokens(start, end); + + if (tokens.size() < OBJDUMP_SECTION_LINE_MIN_TOKENS) { + res->error_msg = "Objdump output not recognized: too few tokens."; + return false; + } + + if (tokens[1] != ".text") { + res->error_msg = "Objdump output not recognized: .text not second token."; + return false; + } + + std::string lengthToken = "0x" + tokens[2]; + std::istringstream(lengthToken) >> std::hex >> res->length; + + std::string offsetToken = "0x" + tokens[5]; + uintptr_t offset; + std::istringstream(offsetToken) >> std::hex >> offset; + + std::ifstream obj(res->base_name + ".o"); + obj.seekg(offset); + res->code.reset(new std::vector<uint8_t>(res->length)); + obj.read(reinterpret_cast<char*>(&(*res->code)[0]), res->length); + obj.close(); + + res->ok = true; + return true; + } + + // Remove temporary files. + void Clean(const NativeAssemblerResult* res) { + std::remove((res->base_name + ".S").c_str()); + std::remove((res->base_name + ".o").c_str()); + std::remove((res->base_name + ".o.dump").c_str()); + } + + // Check whether file exists. Is used for commands, so strips off any parameters: anything after + // the first space. We skip to the last slash for this, so it should work with directories with + // spaces. + static bool FileExists(std::string file) { + if (file.length() == 0) { + return false; + } + + // Need to strip any options. + size_t last_slash = file.find_last_of('/'); + if (last_slash == std::string::npos) { + // No slash, start looking at the start. + last_slash = 0; + } + size_t space_index = file.find(' ', last_slash); + + if (space_index == std::string::npos) { + std::ifstream infile(file.c_str()); + return infile.good(); + } else { + std::string copy = file.substr(0, space_index - 1); + + struct stat buf; + return stat(copy.c_str(), &buf) == 0; + } + } + + static std::string GetGCCRootPath() { + return "prebuilts/gcc/linux-x86"; + } + + static std::string GetRootPath() { + // 1) Check ANDROID_BUILD_TOP + char* build_top = getenv("ANDROID_BUILD_TOP"); + if (build_top != nullptr) { + return std::string(build_top) + "/"; + } + + // 2) Do cwd + char temp[1024]; + return getcwd(temp, 1024) ? std::string(temp) + "/" : std::string(""); + } + + std::string FindTool(std::string tool_name) { + // Find the current tool. Wild-card pattern is "arch-string*tool-name". + std::string gcc_path = GetRootPath() + GetGCCRootPath(); + std::vector<std::string> args; + args.push_back("find"); + args.push_back(gcc_path); + args.push_back("-name"); + args.push_back(GetArchitectureString() + "*" + tool_name); + args.push_back("|"); + args.push_back("sort"); + args.push_back("|"); + args.push_back("tail"); + args.push_back("-n"); + args.push_back("1"); + std::string tmp_file = GetTmpnam(); + args.push_back(">"); + args.push_back(tmp_file); + std::string sh_args = Join(args, ' '); + + args.clear(); + args.push_back("/bin/sh"); + args.push_back("-c"); + args.push_back(sh_args); + + std::string error_msg; + if (!Exec(args, &error_msg)) { + EXPECT_TRUE(false) << error_msg; + return ""; + } + + std::ifstream in(tmp_file.c_str()); + std::string line; + if (!std::getline(in, line)) { + in.close(); + std::remove(tmp_file.c_str()); + return ""; + } + in.close(); + std::remove(tmp_file.c_str()); + return line; + } + + // Use a consistent tmpnam, so store it. + std::string GetTmpnam() { + if (tmpnam_.length() == 0) { + tmpnam_ = std::string(tmpnam(nullptr)); + } + return tmpnam_; + } + + UniquePtr<Ass> assembler_; + + std::string resolved_assembler_cmd_; + std::string resolved_objdump_cmd_; + std::string resolved_disassemble_cmd_; + std::string tmpnam_; + + static constexpr size_t OBJDUMP_SECTION_LINE_MIN_TOKENS = 6; +}; + +} // namespace art + +#endif // ART_COMPILER_UTILS_ASSEMBLER_TEST_H_ diff --git a/compiler/utils/x86_64/assembler_x86_64.cc b/compiler/utils/x86_64/assembler_x86_64.cc index 9507e1207a..8eaeae17ac 100644 --- a/compiler/utils/x86_64/assembler_x86_64.cc +++ b/compiler/utils/x86_64/assembler_x86_64.cc @@ -77,6 +77,7 @@ void X86_64Assembler::pushq(const Address& address) { void X86_64Assembler::pushq(const Immediate& imm) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); + CHECK(imm.is_int32()); // pushq only supports 32b immediate. if (imm.is_int8()) { EmitUint8(0x6A); EmitUint8(imm.value() & 0xFF); @@ -104,9 +105,17 @@ void X86_64Assembler::popq(const Address& address) { void X86_64Assembler::movq(CpuRegister dst, const Immediate& imm) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); - EmitRex64(dst); - EmitUint8(0xB8 + dst.LowBits()); - EmitImmediate(imm); + if (imm.is_int32()) { + // 32 bit. Note: sign-extends. + EmitRex64(dst); + EmitUint8(0xC7); + EmitRegisterOperand(0, dst.LowBits()); + EmitInt32(static_cast<int32_t>(imm.value())); + } else { + EmitRex64(dst); + EmitUint8(0xB8 + dst.LowBits()); + EmitInt64(imm.value()); + } } @@ -120,7 +129,8 @@ void X86_64Assembler::movl(CpuRegister dst, const Immediate& imm) { void X86_64Assembler::movq(CpuRegister dst, CpuRegister src) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); - EmitRex64(dst, src); + // 0x89 is movq r/m64 <- r64, with op1 in r/m and op2 in reg: so reverse EmitRex64 + EmitRex64(src, dst); EmitUint8(0x89); EmitRegisterOperand(src.LowBits(), dst.LowBits()); } @@ -843,6 +853,14 @@ void X86_64Assembler::cmpl(CpuRegister reg, const Address& address) { } +void X86_64Assembler::cmpq(CpuRegister reg0, CpuRegister reg1) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitRex64(reg0, reg1); + EmitUint8(0x3B); + EmitOperand(reg0.LowBits(), Operand(reg1)); +} + + void X86_64Assembler::addl(CpuRegister dst, CpuRegister src) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitOptionalRex32(dst, src); @@ -945,6 +963,14 @@ void X86_64Assembler::xorl(CpuRegister dst, CpuRegister src) { EmitOperand(dst.LowBits(), Operand(src)); } + +void X86_64Assembler::xorq(CpuRegister dst, const Immediate& imm) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + CHECK(imm.is_int32()); // xorq only supports 32b immediate. + EmitRex64(dst); + EmitComplex(6, Operand(dst), imm); +} + #if 0 void X86_64Assembler::rex(bool force, bool w, Register* r, Register* x, Register* b) { // REX.WRXB @@ -1007,11 +1033,21 @@ void X86_64Assembler::addl(CpuRegister reg, const Immediate& imm) { void X86_64Assembler::addq(CpuRegister reg, const Immediate& imm) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); + CHECK(imm.is_int32()); // addq only supports 32b immediate. EmitRex64(reg); EmitComplex(0, Operand(reg), imm); } +void X86_64Assembler::addq(CpuRegister dst, CpuRegister src) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + // 0x01 is addq r/m64 <- r/m64 + r64, with op1 in r/m and op2 in reg: so reverse EmitRex64 + EmitRex64(src, dst); + EmitUint8(0x01); + EmitRegisterOperand(src.LowBits(), dst.LowBits()); +} + + void X86_64Assembler::addl(const Address& address, CpuRegister reg) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitOptionalRex32(reg, address); @@ -1042,6 +1078,22 @@ void X86_64Assembler::subl(CpuRegister reg, const Immediate& imm) { } +void X86_64Assembler::subq(CpuRegister reg, const Immediate& imm) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + CHECK(imm.is_int32()); // subq only supports 32b immediate. + EmitRex64(reg); + EmitComplex(5, Operand(reg), imm); +} + + +void X86_64Assembler::subq(CpuRegister dst, CpuRegister src) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitRex64(dst, src); + EmitUint8(0x2B); + EmitRegisterOperand(dst.LowBits(), src.LowBits()); +} + + void X86_64Assembler::subl(CpuRegister reg, const Address& address) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitOptionalRex32(reg, address); @@ -1299,13 +1351,15 @@ void X86_64Assembler::mfence() { EmitUint8(0xF0); } + X86_64Assembler* X86_64Assembler::gs() { - // TODO: fs is a prefix and not an instruction + // TODO: gs is a prefix and not an instruction AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x65); return this; } + void X86_64Assembler::AddImmediate(CpuRegister reg, const Immediate& imm) { int value = imm.value(); if (value != 0) { @@ -1318,6 +1372,18 @@ void X86_64Assembler::AddImmediate(CpuRegister reg, const Immediate& imm) { } +void X86_64Assembler::setcc(Condition condition, CpuRegister dst) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + // RSP, RBP, RDI, RSI need rex prefix (else the pattern encodes ah/bh/ch/dh). + if (dst.NeedsRex() || dst.AsRegister() > 3) { + EmitOptionalRex(true, false, false, false, dst.NeedsRex()); + } + EmitUint8(0x0F); + EmitUint8(0x90 + condition); + EmitUint8(0xC0 + dst.LowBits()); +} + + void X86_64Assembler::LoadDoubleConstant(XmmRegister dst, double value) { // TODO: Need to have a code constants table. int64_t constant = bit_cast<int64_t, double>(value); @@ -1398,7 +1464,11 @@ void X86_64Assembler::EmitOperand(uint8_t reg_or_opcode, const Operand& operand) void X86_64Assembler::EmitImmediate(const Immediate& imm) { - EmitInt32(imm.value()); + if (imm.is_int32()) { + EmitInt32(static_cast<int32_t>(imm.value())); + } else { + EmitInt64(imm.value()); + } } diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h index 4738dcb486..87fb359112 100644 --- a/compiler/utils/x86_64/assembler_x86_64.h +++ b/compiler/utils/x86_64/assembler_x86_64.h @@ -31,16 +31,21 @@ namespace x86_64 { class Immediate { public: - explicit Immediate(int32_t value) : value_(value) {} + explicit Immediate(int64_t value) : value_(value) {} - int32_t value() const { return value_; } + int64_t value() const { return value_; } bool is_int8() const { return IsInt(8, value_); } bool is_uint8() const { return IsUint(8, value_); } bool is_uint16() const { return IsUint(16, value_); } + bool is_int32() const { + // This does not work on 32b machines: return IsInt(32, value_); + int64_t limit = static_cast<int64_t>(1) << 31; + return (-limit <= value_) && (value_ < limit); + } private: - const int32_t value_; + const int64_t value_; DISALLOW_COPY_AND_ASSIGN(Immediate); }; @@ -368,10 +373,11 @@ class X86_64Assembler FINAL : public Assembler { void cmpl(CpuRegister reg, const Immediate& imm); void cmpl(CpuRegister reg0, CpuRegister reg1); void cmpl(CpuRegister reg, const Address& address); - void cmpl(const Address& address, CpuRegister reg); void cmpl(const Address& address, const Immediate& imm); + void cmpq(CpuRegister reg0, CpuRegister reg1); + void testl(CpuRegister reg1, CpuRegister reg2); void testl(CpuRegister reg, const Immediate& imm); @@ -382,19 +388,24 @@ class X86_64Assembler FINAL : public Assembler { void orl(CpuRegister dst, CpuRegister src); void xorl(CpuRegister dst, CpuRegister src); + void xorq(CpuRegister dst, const Immediate& imm); void addl(CpuRegister dst, CpuRegister src); - void addq(CpuRegister reg, const Immediate& imm); void addl(CpuRegister reg, const Immediate& imm); void addl(CpuRegister reg, const Address& address); - void addl(const Address& address, CpuRegister reg); void addl(const Address& address, const Immediate& imm); + void addq(CpuRegister reg, const Immediate& imm); + void addq(CpuRegister dst, CpuRegister src); + void subl(CpuRegister dst, CpuRegister src); void subl(CpuRegister reg, const Immediate& imm); void subl(CpuRegister reg, const Address& address); + void subq(CpuRegister reg, const Immediate& imm); + void subq(CpuRegister dst, CpuRegister src); + void cdq(); void idivl(CpuRegister reg); @@ -442,6 +453,8 @@ class X86_64Assembler FINAL : public Assembler { X86_64Assembler* gs(); + void setcc(Condition condition, CpuRegister dst); + // // Macros for High-level operations. // @@ -586,6 +599,7 @@ class X86_64Assembler FINAL : public Assembler { private: void EmitUint8(uint8_t value); void EmitInt32(int32_t value); + void EmitInt64(int64_t value); void EmitRegisterOperand(uint8_t rm, uint8_t reg); void EmitXmmRegisterOperand(uint8_t rm, XmmRegister reg); void EmitFixup(AssemblerFixup* fixup); @@ -634,6 +648,10 @@ inline void X86_64Assembler::EmitInt32(int32_t value) { buffer_.Emit<int32_t>(value); } +inline void X86_64Assembler::EmitInt64(int64_t value) { + buffer_.Emit<int64_t>(value); +} + inline void X86_64Assembler::EmitRegisterOperand(uint8_t rm, uint8_t reg) { CHECK_GE(rm, 0); CHECK_LT(rm, 8); diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc index df0d14e9c8..7201d04292 100644 --- a/compiler/utils/x86_64/assembler_x86_64_test.cc +++ b/compiler/utils/x86_64/assembler_x86_64_test.cc @@ -16,7 +16,7 @@ #include "assembler_x86_64.h" -#include "gtest/gtest.h" +#include "utils/assembler_test.h" namespace art { @@ -29,4 +29,175 @@ TEST(AssemblerX86_64, CreateBuffer) { ASSERT_EQ(static_cast<size_t>(5), buffer.Size()); } +class AssemblerX86_64Test : public AssemblerTest<x86_64::X86_64Assembler, x86_64::CpuRegister, + x86_64::Immediate> { + protected: + // Get the typically used name for this architecture, e.g., aarch64, x86-64, ... + std::string GetArchitectureString() OVERRIDE { + return "x86_64"; + } + + std::string GetDisassembleParameters() OVERRIDE { + return " -D -bbinary -mi386:x86-64 -Mx86-64,addr64,data32 --no-show-raw-insn"; + } + + void SetUpHelpers() OVERRIDE { + if (registers_.size() == 0) { + registers_.push_back(new x86_64::CpuRegister(x86_64::RAX)); + registers_.push_back(new x86_64::CpuRegister(x86_64::RBX)); + registers_.push_back(new x86_64::CpuRegister(x86_64::RCX)); + registers_.push_back(new x86_64::CpuRegister(x86_64::RDX)); + registers_.push_back(new x86_64::CpuRegister(x86_64::RBP)); + registers_.push_back(new x86_64::CpuRegister(x86_64::RSP)); + registers_.push_back(new x86_64::CpuRegister(x86_64::RSI)); + registers_.push_back(new x86_64::CpuRegister(x86_64::RDI)); + registers_.push_back(new x86_64::CpuRegister(x86_64::R8)); + registers_.push_back(new x86_64::CpuRegister(x86_64::R9)); + registers_.push_back(new x86_64::CpuRegister(x86_64::R10)); + registers_.push_back(new x86_64::CpuRegister(x86_64::R11)); + registers_.push_back(new x86_64::CpuRegister(x86_64::R12)); + registers_.push_back(new x86_64::CpuRegister(x86_64::R13)); + registers_.push_back(new x86_64::CpuRegister(x86_64::R14)); + registers_.push_back(new x86_64::CpuRegister(x86_64::R15)); + } + } + + std::vector<x86_64::CpuRegister*> GetRegisters() OVERRIDE { + return registers_; + } + + x86_64::Immediate* CreateImmediate(int64_t imm_value) OVERRIDE { + return new x86_64::Immediate(imm_value); + } + + private: + std::vector<x86_64::CpuRegister*> registers_; +}; + + +TEST_F(AssemblerX86_64Test, Toolchain) { + EXPECT_TRUE(CheckTools()); +} + + +TEST_F(AssemblerX86_64Test, PushqRegs) { + DriverStr(RepeatR(&x86_64::X86_64Assembler::pushq, "pushq %{reg}"), "pushq"); +} + +TEST_F(AssemblerX86_64Test, PushqImm) { + DriverStr(RepeatI(&x86_64::X86_64Assembler::pushq, 4U, "pushq ${imm}"), "pushqi"); +} + + +TEST_F(AssemblerX86_64Test, MovqRegs) { + DriverStr(RepeatRR(&x86_64::X86_64Assembler::movq, "movq %{reg2}, %{reg1}"), "movq"); +} + +TEST_F(AssemblerX86_64Test, MovqImm) { + DriverStr(RepeatRI(&x86_64::X86_64Assembler::movq, 8U, "movq ${imm}, %{reg}"), "movqi"); +} + + +TEST_F(AssemblerX86_64Test, AddqRegs) { + DriverStr(RepeatRR(&x86_64::X86_64Assembler::addq, "addq %{reg2}, %{reg1}"), "addq"); +} + +TEST_F(AssemblerX86_64Test, AddqImm) { + DriverStr(RepeatRI(&x86_64::X86_64Assembler::addq, 4U, "addq ${imm}, %{reg}"), "addqi"); +} + + +TEST_F(AssemblerX86_64Test, SubqRegs) { + DriverStr(RepeatRR(&x86_64::X86_64Assembler::subq, "subq %{reg2}, %{reg1}"), "subq"); +} + +TEST_F(AssemblerX86_64Test, SubqImm) { + DriverStr(RepeatRI(&x86_64::X86_64Assembler::subq, 4U, "subq ${imm}, %{reg}"), "subqi"); +} + + +TEST_F(AssemblerX86_64Test, CmpqRegs) { + DriverStr(RepeatRR(&x86_64::X86_64Assembler::cmpq, "cmpq %{reg2}, %{reg1}"), "cmpq"); +} + + +TEST_F(AssemblerX86_64Test, XorqImm) { + DriverStr(RepeatRI(&x86_64::X86_64Assembler::xorq, 4U, "xorq ${imm}, %{reg}"), "xorqi"); +} + + +std::string setcc_test_fn(x86_64::X86_64Assembler* assembler) { + // From Condition + /* + kOverflow = 0, + kNoOverflow = 1, + kBelow = 2, + kAboveEqual = 3, + kEqual = 4, + kNotEqual = 5, + kBelowEqual = 6, + kAbove = 7, + kSign = 8, + kNotSign = 9, + kParityEven = 10, + kParityOdd = 11, + kLess = 12, + kGreaterEqual = 13, + kLessEqual = 14, + */ + std::string suffixes[15] = { "o", "no", "b", "ae", "e", "ne", "be", "a", "s", "ns", "pe", "po", + "l", "ge", "le" }; + + std::vector<x86_64::CpuRegister*> registers; + registers.push_back(new x86_64::CpuRegister(x86_64::RAX)); + registers.push_back(new x86_64::CpuRegister(x86_64::RBX)); + registers.push_back(new x86_64::CpuRegister(x86_64::RCX)); + registers.push_back(new x86_64::CpuRegister(x86_64::RDX)); + registers.push_back(new x86_64::CpuRegister(x86_64::RBP)); + registers.push_back(new x86_64::CpuRegister(x86_64::RSP)); + registers.push_back(new x86_64::CpuRegister(x86_64::RSI)); + registers.push_back(new x86_64::CpuRegister(x86_64::RDI)); + registers.push_back(new x86_64::CpuRegister(x86_64::R8)); + registers.push_back(new x86_64::CpuRegister(x86_64::R9)); + registers.push_back(new x86_64::CpuRegister(x86_64::R10)); + registers.push_back(new x86_64::CpuRegister(x86_64::R11)); + registers.push_back(new x86_64::CpuRegister(x86_64::R12)); + registers.push_back(new x86_64::CpuRegister(x86_64::R13)); + registers.push_back(new x86_64::CpuRegister(x86_64::R14)); + registers.push_back(new x86_64::CpuRegister(x86_64::R15)); + + std::string byte_regs[16]; + byte_regs[x86_64::RAX] = "al"; + byte_regs[x86_64::RBX] = "bl"; + byte_regs[x86_64::RCX] = "cl"; + byte_regs[x86_64::RDX] = "dl"; + byte_regs[x86_64::RBP] = "bpl"; + byte_regs[x86_64::RSP] = "spl"; + byte_regs[x86_64::RSI] = "sil"; + byte_regs[x86_64::RDI] = "dil"; + byte_regs[x86_64::R8] = "r8b"; + byte_regs[x86_64::R9] = "r9b"; + byte_regs[x86_64::R10] = "r10b"; + byte_regs[x86_64::R11] = "r11b"; + byte_regs[x86_64::R12] = "r12b"; + byte_regs[x86_64::R13] = "r13b"; + byte_regs[x86_64::R14] = "r14b"; + byte_regs[x86_64::R15] = "r15b"; + + std::ostringstream str; + + for (auto reg : registers) { + for (size_t i = 0; i < 15; ++i) { + assembler->setcc(static_cast<x86_64::Condition>(i), *reg); + str << "set" << suffixes[i] << " %" << byte_regs[reg->AsRegister()] << "\n"; + } + } + + return str.str(); +} + +TEST_F(AssemblerX86_64Test, SetCC) { + DriverFn(&setcc_test_fn, "setcc"); +} + } // namespace art |