/* * 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. */ #include <dirent.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <fstream> #include <map> #include "gtest/gtest.h" #include "jni/quick/calling_convention.h" #include "utils/arm/jni_macro_assembler_arm_vixl.h" #include "base/hex_dump.h" #include "base/malloc_arena_pool.h" #include "common_runtime_test.h" namespace art { namespace arm { // Include results file (generated manually) #include "assembler_thumb_test_expected.cc.inc" #ifndef ART_TARGET_ANDROID // This controls whether the results are printed to the // screen or compared against the expected output. // To generate new expected output, set this to true and // copy the output into the .cc.inc file in the form // of the other results. // // When this is false, the results are not printed to the // output, but are compared against the expected results // in the .cc.inc file. static constexpr bool kPrintResults = false; #endif void SetAndroidData() { const char* data = getenv("ANDROID_DATA"); if (data == nullptr) { setenv("ANDROID_DATA", "/tmp", 1); } } int CompareIgnoringSpace(const char* s1, const char* s2) { while (*s1 != '\0') { while (isspace(*s1)) ++s1; while (isspace(*s2)) ++s2; if (*s1 == '\0' || *s1 != *s2) { break; } ++s1; ++s2; } return *s1 - *s2; } void InitResults() { if (test_results.empty()) { setup_results(); } } std::string GetToolsDir() { #ifndef ART_TARGET_ANDROID // This will only work on the host. There is no as, objcopy or objdump on the device. static std::string toolsdir; if (toolsdir.empty()) { setup_results(); toolsdir = CommonRuntimeTest::GetAndroidTargetToolsDir(InstructionSet::kThumb2); SetAndroidData(); } return toolsdir; #else return std::string(); #endif } void DumpAndCheck(std::vector<uint8_t>& code, const char* testname, const char* const* results) { #ifndef ART_TARGET_ANDROID static std::string toolsdir = GetToolsDir(); ScratchFile file; const char* filename = file.GetFilename().c_str(); std::ofstream out(filename); if (out) { out << ".section \".text\"\n"; out << ".syntax unified\n"; out << ".arch armv7-a\n"; out << ".thumb\n"; out << ".thumb_func\n"; out << ".type " << testname << ", #function\n"; out << ".global " << testname << "\n"; out << testname << ":\n"; out << ".fnstart\n"; for (uint32_t i = 0 ; i < code.size(); ++i) { out << ".byte " << (static_cast<int>(code[i]) & 0xff) << "\n"; } out << ".fnend\n"; out << ".size " << testname << ", .-" << testname << "\n"; } out.close(); char cmd[1024]; // Assemble the .S snprintf(cmd, sizeof(cmd), "%sas %s -o %s.o", toolsdir.c_str(), filename, filename); int cmd_result = system(cmd); ASSERT_EQ(cmd_result, 0) << cmd << strerror(errno); // Disassemble. snprintf(cmd, sizeof(cmd), "%sobjdump -D -M force-thumb --section=.text %s.o | grep '^ *[0-9a-f][0-9a-f]*:'", toolsdir.c_str(), filename); if (kPrintResults) { // Print the results only, don't check. This is used to generate new output for inserting // into the .inc file, so let's add the appropriate prefix/suffix needed in the C++ code. strcat(cmd, " | sed '-es/^/ \"/' | sed '-es/$/\\\\n\",/'"); int cmd_result3 = system(cmd); ASSERT_EQ(cmd_result3, 0) << strerror(errno); } else { // Check the results match the appropriate results in the .inc file. FILE *fp = popen(cmd, "r"); ASSERT_TRUE(fp != nullptr); uint32_t lineindex = 0; while (!feof(fp)) { char testline[256]; char *s = fgets(testline, sizeof(testline), fp); if (s == nullptr) { break; } if (CompareIgnoringSpace(results[lineindex], testline) != 0) { LOG(FATAL) << "Output is not as expected at line: " << lineindex << results[lineindex] << "/" << testline << ", test name: " << testname; } ++lineindex; } // Check that we are at the end. ASSERT_TRUE(results[lineindex] == nullptr); fclose(fp); } char buf[FILENAME_MAX]; snprintf(buf, sizeof(buf), "%s.o", filename); unlink(buf); #endif // ART_TARGET_ANDROID } class ArmVIXLAssemblerTest : public ::testing::Test { public: ArmVIXLAssemblerTest() : pool(), allocator(&pool), assembler(&allocator) { } MallocArenaPool pool; ArenaAllocator allocator; ArmVIXLJNIMacroAssembler assembler; }; #define __ assembler-> void EmitAndCheck(ArmVIXLJNIMacroAssembler* assembler, const char* testname, const char* const* results) { __ FinalizeCode(); size_t cs = __ CodeSize(); std::vector<uint8_t> managed_code(cs); MemoryRegion code(&managed_code[0], managed_code.size()); __ FinalizeInstructions(code); DumpAndCheck(managed_code, testname, results); } void EmitAndCheck(ArmVIXLJNIMacroAssembler* assembler, const char* testname) { InitResults(); std::map<std::string, const char* const*>::iterator results = test_results.find(testname); ASSERT_NE(results, test_results.end()); EmitAndCheck(assembler, testname, results->second); } #undef __ #define __ assembler. TEST_F(ArmVIXLAssemblerTest, VixlJniHelpers) { // Run the test only with Baker read barriers, as the expected // generated code contains a Marking Register refresh instruction. TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS(); const bool is_static = true; const bool is_synchronized = false; const bool is_critical_native = false; const char* shorty = "IIFII"; std::unique_ptr<JniCallingConvention> jni_conv( JniCallingConvention::Create(&allocator, is_static, is_synchronized, is_critical_native, shorty, InstructionSet::kThumb2)); std::unique_ptr<ManagedRuntimeCallingConvention> mr_conv( ManagedRuntimeCallingConvention::Create( &allocator, is_static, is_synchronized, shorty, InstructionSet::kThumb2)); const int frame_size(jni_conv->FrameSize()); ArrayRef<const ManagedRegister> callee_save_regs = jni_conv->CalleeSaveRegisters(); const ManagedRegister method_register = ArmManagedRegister::FromCoreRegister(R0); const ManagedRegister scratch_register = ArmManagedRegister::FromCoreRegister(R12); __ BuildFrame(frame_size, mr_conv->MethodRegister(), callee_save_regs, mr_conv->EntrySpills()); __ IncreaseFrameSize(32); // Loads __ IncreaseFrameSize(4096); __ Load(method_register, FrameOffset(32), 4); __ Load(method_register, FrameOffset(124), 4); __ Load(method_register, FrameOffset(132), 4); __ Load(method_register, FrameOffset(1020), 4); __ Load(method_register, FrameOffset(1024), 4); __ Load(scratch_register, FrameOffset(4092), 4); __ Load(scratch_register, FrameOffset(4096), 4); __ LoadRawPtrFromThread(scratch_register, ThreadOffset32(512)); __ LoadRef(method_register, scratch_register, MemberOffset(128), /* unpoison_reference= */ false); // Stores __ Store(FrameOffset(32), method_register, 4); __ Store(FrameOffset(124), method_register, 4); __ Store(FrameOffset(132), method_register, 4); __ Store(FrameOffset(1020), method_register, 4); __ Store(FrameOffset(1024), method_register, 4); __ Store(FrameOffset(4092), scratch_register, 4); __ Store(FrameOffset(4096), scratch_register, 4); __ StoreImmediateToFrame(FrameOffset(48), 0xFF, scratch_register); __ StoreImmediateToFrame(FrameOffset(48), 0xFFFFFF, scratch_register); __ StoreRawPtr(FrameOffset(48), scratch_register); __ StoreRef(FrameOffset(48), scratch_register); __ StoreSpanning(FrameOffset(48), method_register, FrameOffset(48), scratch_register); __ StoreStackOffsetToThread(ThreadOffset32(512), FrameOffset(4096), scratch_register); __ StoreStackPointerToThread(ThreadOffset32(512)); // Other __ Call(method_register, FrameOffset(48), scratch_register); __ Copy(FrameOffset(48), FrameOffset(44), scratch_register, 4); __ CopyRawPtrFromThread(FrameOffset(44), ThreadOffset32(512), scratch_register); __ CopyRef(FrameOffset(48), FrameOffset(44), scratch_register); __ GetCurrentThread(method_register); __ GetCurrentThread(FrameOffset(48), scratch_register); __ Move(scratch_register, method_register, 4); __ VerifyObject(scratch_register, false); __ CreateHandleScopeEntry(scratch_register, FrameOffset(48), scratch_register, true); __ CreateHandleScopeEntry(scratch_register, FrameOffset(48), scratch_register, false); __ CreateHandleScopeEntry(method_register, FrameOffset(48), scratch_register, true); __ CreateHandleScopeEntry(FrameOffset(48), FrameOffset(64), scratch_register, true); __ CreateHandleScopeEntry(method_register, FrameOffset(0), scratch_register, true); __ CreateHandleScopeEntry(method_register, FrameOffset(1025), scratch_register, true); __ CreateHandleScopeEntry(scratch_register, FrameOffset(1025), scratch_register, true); __ ExceptionPoll(scratch_register, 0); // Push the target out of range of branch emitted by ExceptionPoll. for (int i = 0; i < 64; i++) { __ Store(FrameOffset(2047), scratch_register, 4); } __ DecreaseFrameSize(4096); __ DecreaseFrameSize(32); __ RemoveFrame(frame_size, callee_save_regs, /* may_suspend= */ true); EmitAndCheck(&assembler, "VixlJniHelpers"); } #undef __ // TODO: Avoid these macros. #define R0 vixl::aarch32::r0 #define R2 vixl::aarch32::r2 #define R4 vixl::aarch32::r4 #define R12 vixl::aarch32::r12 #define __ assembler.asm_. TEST_F(ArmVIXLAssemblerTest, VixlLoadFromOffset) { __ LoadFromOffset(kLoadWord, R2, R4, 12); __ LoadFromOffset(kLoadWord, R2, R4, 0xfff); __ LoadFromOffset(kLoadWord, R2, R4, 0x1000); __ LoadFromOffset(kLoadWord, R2, R4, 0x1000a4); __ LoadFromOffset(kLoadWord, R2, R4, 0x101000); __ LoadFromOffset(kLoadWord, R4, R4, 0x101000); __ LoadFromOffset(kLoadUnsignedHalfword, R2, R4, 12); __ LoadFromOffset(kLoadUnsignedHalfword, R2, R4, 0xfff); __ LoadFromOffset(kLoadUnsignedHalfword, R2, R4, 0x1000); __ LoadFromOffset(kLoadUnsignedHalfword, R2, R4, 0x1000a4); __ LoadFromOffset(kLoadUnsignedHalfword, R2, R4, 0x101000); __ LoadFromOffset(kLoadUnsignedHalfword, R4, R4, 0x101000); __ LoadFromOffset(kLoadWordPair, R2, R4, 12); __ LoadFromOffset(kLoadWordPair, R2, R4, 0x3fc); __ LoadFromOffset(kLoadWordPair, R2, R4, 0x400); __ LoadFromOffset(kLoadWordPair, R2, R4, 0x400a4); __ LoadFromOffset(kLoadWordPair, R2, R4, 0x40400); __ LoadFromOffset(kLoadWordPair, R4, R4, 0x40400); vixl::aarch32::UseScratchRegisterScope temps(assembler.asm_.GetVIXLAssembler()); temps.Exclude(R12); __ LoadFromOffset(kLoadWord, R0, R12, 12); // 32-bit because of R12. temps.Include(R12); __ LoadFromOffset(kLoadWord, R2, R4, 0xa4 - 0x100000); __ LoadFromOffset(kLoadSignedByte, R2, R4, 12); __ LoadFromOffset(kLoadUnsignedByte, R2, R4, 12); __ LoadFromOffset(kLoadSignedHalfword, R2, R4, 12); EmitAndCheck(&assembler, "VixlLoadFromOffset"); } TEST_F(ArmVIXLAssemblerTest, VixlStoreToOffset) { __ StoreToOffset(kStoreWord, R2, R4, 12); __ StoreToOffset(kStoreWord, R2, R4, 0xfff); __ StoreToOffset(kStoreWord, R2, R4, 0x1000); __ StoreToOffset(kStoreWord, R2, R4, 0x1000a4); __ StoreToOffset(kStoreWord, R2, R4, 0x101000); __ StoreToOffset(kStoreWord, R4, R4, 0x101000); __ StoreToOffset(kStoreHalfword, R2, R4, 12); __ StoreToOffset(kStoreHalfword, R2, R4, 0xfff); __ StoreToOffset(kStoreHalfword, R2, R4, 0x1000); __ StoreToOffset(kStoreHalfword, R2, R4, 0x1000a4); __ StoreToOffset(kStoreHalfword, R2, R4, 0x101000); __ StoreToOffset(kStoreHalfword, R4, R4, 0x101000); __ StoreToOffset(kStoreWordPair, R2, R4, 12); __ StoreToOffset(kStoreWordPair, R2, R4, 0x3fc); __ StoreToOffset(kStoreWordPair, R2, R4, 0x400); __ StoreToOffset(kStoreWordPair, R2, R4, 0x400a4); __ StoreToOffset(kStoreWordPair, R2, R4, 0x40400); __ StoreToOffset(kStoreWordPair, R4, R4, 0x40400); vixl::aarch32::UseScratchRegisterScope temps(assembler.asm_.GetVIXLAssembler()); temps.Exclude(R12); __ StoreToOffset(kStoreWord, R0, R12, 12); // 32-bit because of R12. temps.Include(R12); __ StoreToOffset(kStoreWord, R2, R4, 0xa4 - 0x100000); __ StoreToOffset(kStoreByte, R2, R4, 12); EmitAndCheck(&assembler, "VixlStoreToOffset"); } #undef __ } // namespace arm } // namespace art