diff options
205 files changed, 6670 insertions, 2127 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 6295e1527b..ff41736a35 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -29,6 +29,7 @@ GTEST_DEX_DIRECTORIES := \ GetMethodSignature \ Instrumentation \ Interfaces \ + Lookup \ Main \ MultiDex \ MultiDexModifiedSecondary \ @@ -78,6 +79,7 @@ ART_GTEST_proxy_test_DEX_DEPS := Interfaces ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods ART_GTEST_stub_test_DEX_DEPS := AllFields ART_GTEST_transaction_test_DEX_DEPS := Transaction +ART_GTEST_type_lookup_table_test_DEX_DEPS := Lookup # The elf writer test has dependencies on core.oat. ART_GTEST_elf_writer_test_HOST_DEPS := $(HOST_CORE_IMAGE_default_no-pic_64) $(HOST_CORE_IMAGE_default_no-pic_32) @@ -220,6 +222,7 @@ RUNTIME_GTEST_COMMON_SRC_FILES := \ runtime/reference_table_test.cc \ runtime/thread_pool_test.cc \ runtime/transaction_test.cc \ + runtime/type_lookup_table_test.cc \ runtime/utf_test.cc \ runtime/utils_test.cc \ runtime/verifier/method_verifier_test.cc \ @@ -230,6 +233,7 @@ COMPILER_GTEST_COMMON_SRC_FILES := \ runtime/jni_internal_test.cc \ runtime/proxy_test.cc \ runtime/reflection_test.cc \ + compiler/compiled_method_test.cc \ compiler/dex/gvn_dead_code_elimination_test.cc \ compiler/dex/global_value_numbering_test.cc \ compiler/dex/local_value_numbering_test.cc \ @@ -237,6 +241,7 @@ COMPILER_GTEST_COMMON_SRC_FILES := \ compiler/dex/mir_optimization_test.cc \ compiler/dex/type_inference_test.cc \ compiler/dwarf/dwarf_test.cc \ + compiler/driver/compiled_method_storage_test.cc \ compiler/driver/compiler_driver_test.cc \ compiler/elf_writer_test.cc \ compiler/image_test.cc \ diff --git a/compiler/Android.mk b/compiler/Android.mk index 17f9d122a7..e74a68f608 100644 --- a/compiler/Android.mk +++ b/compiler/Android.mk @@ -54,6 +54,7 @@ LIBART_COMPILER_SRC_FILES := \ dex/verification_results.cc \ dex/vreg_analysis.cc \ dex/quick_compiler_callbacks.cc \ + driver/compiled_method_storage.cc \ driver/compiler_driver.cc \ driver/compiler_options.cc \ driver/dex_compilation_unit.cc \ @@ -88,6 +89,7 @@ LIBART_COMPILER_SRC_FILES := \ optimizing/primitive_type_propagation.cc \ optimizing/reference_type_propagation.cc \ optimizing/register_allocator.cc \ + optimizing/sharpening.cc \ optimizing/side_effects_analysis.cc \ optimizing/ssa_builder.cc \ optimizing/ssa_liveness_analysis.cc \ @@ -153,6 +155,7 @@ LIBART_COMPILER_SRC_FILES_mips := \ dex/quick/mips/utility_mips.cc \ jni/quick/mips/calling_convention_mips.cc \ optimizing/code_generator_mips.cc \ + optimizing/intrinsics_mips.cc \ utils/mips/assembler_mips.cc \ utils/mips/managed_register_mips.cc \ diff --git a/compiler/cfi_test.h b/compiler/cfi_test.h index 5e345dbf61..6fd457599f 100644 --- a/compiler/cfi_test.h +++ b/compiler/cfi_test.h @@ -51,7 +51,7 @@ class CFITest : public dwarf::DwarfTest { dwarf::WriteDebugFrameCIE(is64bit, dwarf::DW_EH_PE_absptr, dwarf::Reg(8), initial_opcodes, kCFIFormat, &debug_frame_data_); std::vector<uintptr_t> debug_frame_patches; - dwarf::WriteDebugFrameFDE(is64bit, 0, 0, actual_asm.size(), &actual_cfi, + dwarf::WriteDebugFrameFDE(is64bit, 0, 0, actual_asm.size(), ArrayRef<const uint8_t>(actual_cfi), kCFIFormat, &debug_frame_data_, &debug_frame_patches); ReformatCfi(Objdump(false, "-W"), &lines); // Pretty-print assembly. diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc index 58a2f96cd9..c37cecaeac 100644 --- a/compiler/common_compiler_test.cc +++ b/compiler/common_compiler_test.cc @@ -54,22 +54,22 @@ void CommonCompilerTest::MakeExecutable(ArtMethod* method) { method->GetDexMethodIndex())); } if (compiled_method != nullptr) { - const SwapVector<uint8_t>* code = compiled_method->GetQuickCode(); - uint32_t code_size = code->size(); + ArrayRef<const uint8_t> code = compiled_method->GetQuickCode(); + uint32_t code_size = code.size(); CHECK_NE(0u, code_size); - const SwapVector<uint8_t>* vmap_table = compiled_method->GetVmapTable(); - uint32_t vmap_table_offset = vmap_table->empty() ? 0u - : sizeof(OatQuickMethodHeader) + vmap_table->size(); - const SwapVector<uint8_t>* mapping_table = compiled_method->GetMappingTable(); - bool mapping_table_used = mapping_table != nullptr && !mapping_table->empty(); - size_t mapping_table_size = mapping_table_used ? mapping_table->size() : 0U; + ArrayRef<const uint8_t> vmap_table = compiled_method->GetVmapTable(); + uint32_t vmap_table_offset = vmap_table.empty() ? 0u + : sizeof(OatQuickMethodHeader) + vmap_table.size(); + ArrayRef<const uint8_t> mapping_table = compiled_method->GetMappingTable(); + bool mapping_table_used = !mapping_table.empty(); + size_t mapping_table_size = mapping_table.size(); uint32_t mapping_table_offset = !mapping_table_used ? 0u - : sizeof(OatQuickMethodHeader) + vmap_table->size() + mapping_table_size; - const SwapVector<uint8_t>* gc_map = compiled_method->GetGcMap(); - bool gc_map_used = gc_map != nullptr && !gc_map->empty(); - size_t gc_map_size = gc_map_used ? gc_map->size() : 0U; + : sizeof(OatQuickMethodHeader) + vmap_table.size() + mapping_table_size; + ArrayRef<const uint8_t> gc_map = compiled_method->GetGcMap(); + bool gc_map_used = !gc_map.empty(); + size_t gc_map_size = gc_map.size(); uint32_t gc_map_offset = !gc_map_used ? 0u - : sizeof(OatQuickMethodHeader) + vmap_table->size() + mapping_table_size + gc_map_size; + : sizeof(OatQuickMethodHeader) + vmap_table.size() + mapping_table_size + gc_map_size; OatQuickMethodHeader method_header(mapping_table_offset, vmap_table_offset, gc_map_offset, compiled_method->GetFrameSizeInBytes(), compiled_method->GetCoreSpillMask(), @@ -77,25 +77,30 @@ void CommonCompilerTest::MakeExecutable(ArtMethod* method) { header_code_and_maps_chunks_.push_back(std::vector<uint8_t>()); std::vector<uint8_t>* chunk = &header_code_and_maps_chunks_.back(); - size_t size = sizeof(method_header) + code_size + vmap_table->size() + mapping_table_size + - gc_map_size; - size_t code_offset = compiled_method->AlignCode(size - code_size); - size_t padding = code_offset - (size - code_size); - chunk->reserve(padding + size); + const size_t max_padding = GetInstructionSetAlignment(compiled_method->GetInstructionSet()); + const size_t size = + gc_map_size + mapping_table_size + vmap_table.size() + sizeof(method_header) + code_size; + chunk->reserve(size + max_padding); chunk->resize(sizeof(method_header)); memcpy(&(*chunk)[0], &method_header, sizeof(method_header)); - chunk->insert(chunk->begin(), vmap_table->begin(), vmap_table->end()); + chunk->insert(chunk->begin(), vmap_table.begin(), vmap_table.end()); if (mapping_table_used) { - chunk->insert(chunk->begin(), mapping_table->begin(), mapping_table->end()); + chunk->insert(chunk->begin(), mapping_table.begin(), mapping_table.end()); } if (gc_map_used) { - chunk->insert(chunk->begin(), gc_map->begin(), gc_map->end()); + chunk->insert(chunk->begin(), gc_map.begin(), gc_map.end()); } + chunk->insert(chunk->end(), code.begin(), code.end()); + CHECK_EQ(chunk->size(), size); + const void* unaligned_code_ptr = chunk->data() + (size - code_size); + size_t offset = dchecked_integral_cast<size_t>(reinterpret_cast<uintptr_t>(unaligned_code_ptr)); + size_t padding = compiled_method->AlignCode(offset) - offset; + // Make sure no resizing takes place. + CHECK_GE(chunk->capacity(), chunk->size() + padding); chunk->insert(chunk->begin(), padding, 0); - chunk->insert(chunk->end(), code->begin(), code->end()); - CHECK_EQ(padding + size, chunk->size()); - const void* code_ptr = &(*chunk)[code_offset]; - MakeExecutable(code_ptr, code->size()); + const void* code_ptr = reinterpret_cast<const uint8_t*>(unaligned_code_ptr) + padding; + CHECK_EQ(code_ptr, static_cast<const void*>(chunk->data() + (chunk->size() - code_size))); + MakeExecutable(code_ptr, code.size()); const void* method_code = CompiledMethod::CodePointer(code_ptr, compiled_method->GetInstructionSet()); LOG(INFO) << "MakeExecutable " << PrettyMethod(method) << " code=" << method_code; diff --git a/compiler/compiled_method.cc b/compiler/compiled_method.cc index 74ef35e740..9551d2298b 100644 --- a/compiler/compiled_method.cc +++ b/compiler/compiled_method.cc @@ -15,27 +15,22 @@ */ #include "compiled_method.h" + +#include "driver/compiled_method_storage.h" #include "driver/compiler_driver.h" +#include "utils/swap_space.h" namespace art { CompiledCode::CompiledCode(CompilerDriver* compiler_driver, InstructionSet instruction_set, - const ArrayRef<const uint8_t>& quick_code, bool owns_code_array) - : compiler_driver_(compiler_driver), instruction_set_(instruction_set), - owns_code_array_(owns_code_array), quick_code_(nullptr) { - if (owns_code_array_) { - // If we are supposed to own the code, don't deduplicate it. - quick_code_ = new SwapVector<uint8_t>(quick_code.begin(), quick_code.end(), - compiler_driver_->GetSwapSpaceAllocator()); - } else { - quick_code_ = compiler_driver_->DeduplicateCode(quick_code); - } + const ArrayRef<const uint8_t>& quick_code) + : compiler_driver_(compiler_driver), + instruction_set_(instruction_set), + quick_code_(compiler_driver_->GetCompiledMethodStorage()->DeduplicateCode(quick_code)) { } CompiledCode::~CompiledCode() { - if (owns_code_array_) { - delete quick_code_; - } + compiler_driver_->GetCompiledMethodStorage()->ReleaseCode(quick_code_); } bool CompiledCode::operator==(const CompiledCode& rhs) const { @@ -104,59 +99,28 @@ const void* CompiledCode::CodePointer(const void* code_pointer, } } -const std::vector<uint32_t>& CompiledCode::GetOatdataOffsetsToCompliledCodeOffset() const { - CHECK_NE(0U, oatdata_offsets_to_compiled_code_offset_.size()); - return oatdata_offsets_to_compiled_code_offset_; -} - -void CompiledCode::AddOatdataOffsetToCompliledCodeOffset(uint32_t offset) { - oatdata_offsets_to_compiled_code_offset_.push_back(offset); -} - CompiledMethod::CompiledMethod(CompilerDriver* driver, InstructionSet instruction_set, const ArrayRef<const uint8_t>& quick_code, const size_t frame_size_in_bytes, const uint32_t core_spill_mask, const uint32_t fp_spill_mask, - DefaultSrcMap* src_mapping_table, + const ArrayRef<const SrcMapElem>& src_mapping_table, const ArrayRef<const uint8_t>& mapping_table, const ArrayRef<const uint8_t>& vmap_table, const ArrayRef<const uint8_t>& native_gc_map, const ArrayRef<const uint8_t>& cfi_info, const ArrayRef<const LinkerPatch>& patches) - : CompiledCode(driver, instruction_set, quick_code, !driver->DedupeEnabled()), - owns_arrays_(!driver->DedupeEnabled()), + : CompiledCode(driver, instruction_set, quick_code), frame_size_in_bytes_(frame_size_in_bytes), core_spill_mask_(core_spill_mask), fp_spill_mask_(fp_spill_mask), - patches_(patches.begin(), patches.end(), driver->GetSwapSpaceAllocator()) { - if (owns_arrays_) { - if (src_mapping_table == nullptr) { - src_mapping_table_ = new SwapSrcMap(driver->GetSwapSpaceAllocator()); - } else { - src_mapping_table_ = new SwapSrcMap(src_mapping_table->begin(), src_mapping_table->end(), - driver->GetSwapSpaceAllocator()); - } - mapping_table_ = mapping_table.empty() ? - nullptr : new SwapVector<uint8_t>(mapping_table.begin(), mapping_table.end(), - driver->GetSwapSpaceAllocator()); - vmap_table_ = new SwapVector<uint8_t>(vmap_table.begin(), vmap_table.end(), - driver->GetSwapSpaceAllocator()); - gc_map_ = native_gc_map.empty() ? nullptr : - new SwapVector<uint8_t>(native_gc_map.begin(), native_gc_map.end(), - driver->GetSwapSpaceAllocator()); - cfi_info_ = cfi_info.empty() ? nullptr : - new SwapVector<uint8_t>(cfi_info.begin(), cfi_info.end(), driver->GetSwapSpaceAllocator()); - } else { - src_mapping_table_ = src_mapping_table == nullptr ? - driver->DeduplicateSrcMappingTable(ArrayRef<SrcMapElem>()) : - driver->DeduplicateSrcMappingTable(ArrayRef<SrcMapElem>(*src_mapping_table)); - mapping_table_ = mapping_table.empty() ? - nullptr : driver->DeduplicateMappingTable(mapping_table); - vmap_table_ = driver->DeduplicateVMapTable(vmap_table); - gc_map_ = native_gc_map.empty() ? nullptr : driver->DeduplicateGCMap(native_gc_map); - cfi_info_ = cfi_info.empty() ? nullptr : driver->DeduplicateCFIInfo(cfi_info); - } + src_mapping_table_( + driver->GetCompiledMethodStorage()->DeduplicateSrcMappingTable(src_mapping_table)), + mapping_table_(driver->GetCompiledMethodStorage()->DeduplicateMappingTable(mapping_table)), + vmap_table_(driver->GetCompiledMethodStorage()->DeduplicateVMapTable(vmap_table)), + gc_map_(driver->GetCompiledMethodStorage()->DeduplicateGCMap(native_gc_map)), + cfi_info_(driver->GetCompiledMethodStorage()->DeduplicateCFIInfo(cfi_info)), + patches_(driver->GetCompiledMethodStorage()->DeduplicateLinkerPatches(patches)) { } CompiledMethod* CompiledMethod::SwapAllocCompiledMethod( @@ -166,13 +130,13 @@ CompiledMethod* CompiledMethod::SwapAllocCompiledMethod( const size_t frame_size_in_bytes, const uint32_t core_spill_mask, const uint32_t fp_spill_mask, - DefaultSrcMap* src_mapping_table, + const ArrayRef<const SrcMapElem>& src_mapping_table, const ArrayRef<const uint8_t>& mapping_table, const ArrayRef<const uint8_t>& vmap_table, const ArrayRef<const uint8_t>& native_gc_map, const ArrayRef<const uint8_t>& cfi_info, const ArrayRef<const LinkerPatch>& patches) { - SwapAllocator<CompiledMethod> alloc(driver->GetSwapSpaceAllocator()); + SwapAllocator<CompiledMethod> alloc(driver->GetCompiledMethodStorage()->GetSwapSpaceAllocator()); CompiledMethod* ret = alloc.allocate(1); alloc.construct(ret, driver, instruction_set, quick_code, frame_size_in_bytes, core_spill_mask, fp_spill_mask, src_mapping_table, mapping_table, vmap_table, native_gc_map, @@ -180,22 +144,20 @@ CompiledMethod* CompiledMethod::SwapAllocCompiledMethod( return ret; } - - void CompiledMethod::ReleaseSwapAllocatedCompiledMethod(CompilerDriver* driver, CompiledMethod* m) { - SwapAllocator<CompiledMethod> alloc(driver->GetSwapSpaceAllocator()); + SwapAllocator<CompiledMethod> alloc(driver->GetCompiledMethodStorage()->GetSwapSpaceAllocator()); alloc.destroy(m); alloc.deallocate(m, 1); } CompiledMethod::~CompiledMethod() { - if (owns_arrays_) { - delete src_mapping_table_; - delete mapping_table_; - delete vmap_table_; - delete gc_map_; - delete cfi_info_; - } + CompiledMethodStorage* storage = GetCompilerDriver()->GetCompiledMethodStorage(); + storage->ReleaseLinkerPatches(patches_); + storage->ReleaseCFIInfo(cfi_info_); + storage->ReleaseGCMap(gc_map_); + storage->ReleaseVMapTable(vmap_table_); + storage->ReleaseMappingTable(mapping_table_); + storage->ReleaseSrcMappingTable(src_mapping_table_); } } // namespace art diff --git a/compiler/compiled_method.h b/compiler/compiled_method.h index a4d2387030..15a4ba0f6f 100644 --- a/compiler/compiled_method.h +++ b/compiler/compiled_method.h @@ -23,19 +23,20 @@ #include "arch/instruction_set.h" #include "base/bit_utils.h" +#include "length_prefixed_array.h" #include "method_reference.h" #include "utils/array_ref.h" -#include "utils/swap_space.h" namespace art { class CompilerDriver; +class CompiledMethodStorage; class CompiledCode { public: // For Quick to supply an code blob CompiledCode(CompilerDriver* compiler_driver, InstructionSet instruction_set, - const ArrayRef<const uint8_t>& quick_code, bool owns_code_array); + const ArrayRef<const uint8_t>& quick_code); virtual ~CompiledCode(); @@ -43,8 +44,8 @@ class CompiledCode { return instruction_set_; } - const SwapVector<uint8_t>* GetQuickCode() const { - return quick_code_; + ArrayRef<const uint8_t> GetQuickCode() const { + return GetArray(quick_code_); } bool operator==(const CompiledCode& rhs) const; @@ -66,40 +67,45 @@ class CompiledCode { static const void* CodePointer(const void* code_pointer, InstructionSet instruction_set); - const std::vector<uint32_t>& GetOatdataOffsetsToCompliledCodeOffset() const; - void AddOatdataOffsetToCompliledCodeOffset(uint32_t offset); + protected: + template <typename T> + static ArrayRef<const T> GetArray(const LengthPrefixedArray<T>* array) { + if (array == nullptr) { + return ArrayRef<const T>(); + } + DCHECK_NE(array->size(), 0u); + return ArrayRef<const T>(&array->At(0), array->size()); + } + + CompilerDriver* GetCompilerDriver() { + return compiler_driver_; + } private: CompilerDriver* const compiler_driver_; const InstructionSet instruction_set_; - // If we own the code array (means that we free in destructor). - const bool owns_code_array_; - // Used to store the PIC code for Quick. - SwapVector<uint8_t>* quick_code_; - - // There are offsets from the oatdata symbol to where the offset to - // the compiled method will be found. These are computed by the - // OatWriter and then used by the ElfWriter to add relocations so - // that MCLinker can update the values to the location in the linked .so. - std::vector<uint32_t> oatdata_offsets_to_compiled_code_offset_; + const LengthPrefixedArray<uint8_t>* const quick_code_; }; class SrcMapElem { public: uint32_t from_; int32_t to_; +}; - // Lexicographical compare. - bool operator<(const SrcMapElem& other) const { - if (from_ != other.from_) { - return from_ < other.from_; - } - return to_ < other.to_; +inline bool operator<(const SrcMapElem& lhs, const SrcMapElem& rhs) { + if (lhs.from_ != rhs.from_) { + return lhs.from_ < rhs.from_; } -}; + return lhs.to_ < rhs.to_; +} + +inline bool operator==(const SrcMapElem& lhs, const SrcMapElem& rhs) { + return lhs.from_ == rhs.from_ && lhs.to_ == rhs.to_; +} template <class Allocator> class SrcMap FINAL : public std::vector<SrcMapElem, Allocator> { @@ -151,7 +157,6 @@ class SrcMap FINAL : public std::vector<SrcMapElem, Allocator> { }; using DefaultSrcMap = SrcMap<std::allocator<SrcMapElem>>; -using SwapSrcMap = SrcMap<SwapAllocator<SrcMapElem>>; enum LinkerPatchType { @@ -273,6 +278,9 @@ class LinkerPatch { uint32_t method_idx_; // Method index for Call/Method patches. uint32_t type_idx_; // Type index for Type patches. uint32_t element_offset_; // Element offset in the dex cache arrays. + static_assert(sizeof(method_idx_) == sizeof(cmp1_), "needed by relational operators"); + static_assert(sizeof(type_idx_) == sizeof(cmp1_), "needed by relational operators"); + static_assert(sizeof(element_offset_) == sizeof(cmp1_), "needed by relational operators"); }; union { uint32_t cmp2_; // Used for relational operators. @@ -313,7 +321,7 @@ class CompiledMethod FINAL : public CompiledCode { const size_t frame_size_in_bytes, const uint32_t core_spill_mask, const uint32_t fp_spill_mask, - DefaultSrcMap* src_mapping_table, + const ArrayRef<const SrcMapElem>& src_mapping_table, const ArrayRef<const uint8_t>& mapping_table, const ArrayRef<const uint8_t>& vmap_table, const ArrayRef<const uint8_t>& native_gc_map, @@ -329,7 +337,7 @@ class CompiledMethod FINAL : public CompiledCode { const size_t frame_size_in_bytes, const uint32_t core_spill_mask, const uint32_t fp_spill_mask, - DefaultSrcMap* src_mapping_table, + const ArrayRef<const SrcMapElem>& src_mapping_table, const ArrayRef<const uint8_t>& mapping_table, const ArrayRef<const uint8_t>& vmap_table, const ArrayRef<const uint8_t>& native_gc_map, @@ -350,35 +358,31 @@ class CompiledMethod FINAL : public CompiledCode { return fp_spill_mask_; } - const SwapSrcMap& GetSrcMappingTable() const { - DCHECK(src_mapping_table_ != nullptr); - return *src_mapping_table_; + ArrayRef<const SrcMapElem> GetSrcMappingTable() const { + return GetArray(src_mapping_table_); } - SwapVector<uint8_t> const* GetMappingTable() const { - return mapping_table_; + ArrayRef<const uint8_t> GetMappingTable() const { + return GetArray(mapping_table_); } - const SwapVector<uint8_t>* GetVmapTable() const { - DCHECK(vmap_table_ != nullptr); - return vmap_table_; + ArrayRef<const uint8_t> GetVmapTable() const { + return GetArray(vmap_table_); } - SwapVector<uint8_t> const* GetGcMap() const { - return gc_map_; + ArrayRef<const uint8_t> GetGcMap() const { + return GetArray(gc_map_); } - const SwapVector<uint8_t>* GetCFIInfo() const { - return cfi_info_; + ArrayRef<const uint8_t> GetCFIInfo() const { + return GetArray(cfi_info_); } ArrayRef<const LinkerPatch> GetPatches() const { - return ArrayRef<const LinkerPatch>(patches_); + return GetArray(patches_); } private: - // Whether or not the arrays are owned by the compiled method or dedupe sets. - const bool owns_arrays_; // For quick code, the size of the activation used by the code. const size_t frame_size_in_bytes_; // For quick code, a bit mask describing spilled GPR callee-save registers. @@ -386,19 +390,19 @@ class CompiledMethod FINAL : public CompiledCode { // For quick code, a bit mask describing spilled FPR callee-save registers. const uint32_t fp_spill_mask_; // For quick code, a set of pairs (PC, DEX) mapping from native PC offset to DEX offset. - SwapSrcMap* src_mapping_table_; + const LengthPrefixedArray<SrcMapElem>* const src_mapping_table_; // For quick code, a uleb128 encoded map from native PC offset to dex PC aswell as dex PC to // native PC offset. Size prefixed. - SwapVector<uint8_t>* mapping_table_; + const LengthPrefixedArray<uint8_t>* const mapping_table_; // For quick code, a uleb128 encoded map from GPR/FPR register to dex register. Size prefixed. - SwapVector<uint8_t>* vmap_table_; + const LengthPrefixedArray<uint8_t>* const vmap_table_; // For quick code, a map keyed by native PC indices to bitmaps describing what dalvik registers // are live. - SwapVector<uint8_t>* gc_map_; + const LengthPrefixedArray<uint8_t>* const gc_map_; // For quick code, a FDE entry for the debug_frame section. - SwapVector<uint8_t>* cfi_info_; + const LengthPrefixedArray<uint8_t>* const cfi_info_; // For quick code, linker patches needed by the method. - const SwapVector<LinkerPatch> patches_; + const LengthPrefixedArray<LinkerPatch>* const patches_; }; } // namespace art diff --git a/compiler/compiled_method_test.cc b/compiler/compiled_method_test.cc new file mode 100644 index 0000000000..99ee875da2 --- /dev/null +++ b/compiler/compiled_method_test.cc @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> + +#include "compiled_method.h" + +namespace art { + +TEST(CompiledMethod, SrcMapElemOperators) { + SrcMapElem elems[] = { + { 1u, -1 }, + { 1u, 0 }, + { 1u, 1 }, + { 2u, -1 }, + { 2u, 0 }, // Index 4. + { 2u, 1 }, + { 2u, 0u }, // Index 6: Arbitrarily add identical SrcMapElem with index 4. + }; + + for (size_t i = 0; i != arraysize(elems); ++i) { + for (size_t j = 0; j != arraysize(elems); ++j) { + bool expected = (i != 6u ? i : 4u) == (j != 6u ? j : 4u); + EXPECT_EQ(expected, elems[i] == elems[j]) << i << " " << j; + } + } + + for (size_t i = 0; i != arraysize(elems); ++i) { + for (size_t j = 0; j != arraysize(elems); ++j) { + bool expected = (i != 6u ? i : 4u) < (j != 6u ? j : 4u); + EXPECT_EQ(expected, elems[i] < elems[j]) << i << " " << j; + } + } +} + +TEST(CompiledMethod, LinkerPatchOperators) { + const DexFile* dex_file1 = reinterpret_cast<const DexFile*>(1); + const DexFile* dex_file2 = reinterpret_cast<const DexFile*>(2); + LinkerPatch patches[] = { + LinkerPatch::MethodPatch(16u, dex_file1, 1000u), + LinkerPatch::MethodPatch(16u, dex_file1, 1001u), + LinkerPatch::MethodPatch(16u, dex_file2, 1000u), + LinkerPatch::MethodPatch(16u, dex_file2, 1001u), // Index 3. + LinkerPatch::CodePatch(16u, dex_file1, 1000u), + LinkerPatch::CodePatch(16u, dex_file1, 1001u), + LinkerPatch::CodePatch(16u, dex_file2, 1000u), + LinkerPatch::CodePatch(16u, dex_file2, 1001u), + LinkerPatch::RelativeCodePatch(16u, dex_file1, 1000u), + LinkerPatch::RelativeCodePatch(16u, dex_file1, 1001u), + LinkerPatch::RelativeCodePatch(16u, dex_file2, 1000u), + LinkerPatch::RelativeCodePatch(16u, dex_file2, 1001u), + LinkerPatch::TypePatch(16u, dex_file1, 1000u), + LinkerPatch::TypePatch(16u, dex_file1, 1001u), + LinkerPatch::TypePatch(16u, dex_file2, 1000u), + LinkerPatch::TypePatch(16u, dex_file2, 1001u), + LinkerPatch::DexCacheArrayPatch(16u, dex_file1, 3000u, 2000u), + LinkerPatch::DexCacheArrayPatch(16u, dex_file1, 3001u, 2000u), + LinkerPatch::DexCacheArrayPatch(16u, dex_file1, 3000u, 2001u), + LinkerPatch::DexCacheArrayPatch(16u, dex_file1, 3001u, 2001u), + LinkerPatch::DexCacheArrayPatch(16u, dex_file2, 3000u, 2000u), + LinkerPatch::DexCacheArrayPatch(16u, dex_file2, 3001u, 2000u), + LinkerPatch::DexCacheArrayPatch(16u, dex_file2, 3000u, 2001u), + LinkerPatch::DexCacheArrayPatch(16u, dex_file2, 3001u, 2001u), + LinkerPatch::MethodPatch(32u, dex_file1, 1000u), + LinkerPatch::MethodPatch(32u, dex_file1, 1001u), + LinkerPatch::MethodPatch(32u, dex_file2, 1000u), + LinkerPatch::MethodPatch(32u, dex_file2, 1001u), + LinkerPatch::CodePatch(32u, dex_file1, 1000u), + LinkerPatch::CodePatch(32u, dex_file1, 1001u), + LinkerPatch::CodePatch(32u, dex_file2, 1000u), + LinkerPatch::CodePatch(32u, dex_file2, 1001u), + LinkerPatch::RelativeCodePatch(32u, dex_file1, 1000u), + LinkerPatch::RelativeCodePatch(32u, dex_file1, 1001u), + LinkerPatch::RelativeCodePatch(32u, dex_file2, 1000u), + LinkerPatch::RelativeCodePatch(32u, dex_file2, 1001u), + LinkerPatch::TypePatch(32u, dex_file1, 1000u), + LinkerPatch::TypePatch(32u, dex_file1, 1001u), + LinkerPatch::TypePatch(32u, dex_file2, 1000u), + LinkerPatch::TypePatch(32u, dex_file2, 1001u), + LinkerPatch::DexCacheArrayPatch(32u, dex_file1, 3000u, 2000u), + LinkerPatch::DexCacheArrayPatch(32u, dex_file1, 3001u, 2000u), + LinkerPatch::DexCacheArrayPatch(32u, dex_file1, 3000u, 2001u), + LinkerPatch::DexCacheArrayPatch(32u, dex_file1, 3001u, 2001u), + LinkerPatch::DexCacheArrayPatch(32u, dex_file2, 3000u, 2000u), + LinkerPatch::DexCacheArrayPatch(32u, dex_file2, 3001u, 2000u), + LinkerPatch::DexCacheArrayPatch(32u, dex_file2, 3000u, 2001u), + LinkerPatch::DexCacheArrayPatch(32u, dex_file2, 3001u, 2001u), + LinkerPatch::MethodPatch(16u, dex_file2, 1001u), // identical with patch as index 3. + }; + constexpr size_t last_index = arraysize(patches) - 1u; + + for (size_t i = 0; i != arraysize(patches); ++i) { + for (size_t j = 0; j != arraysize(patches); ++j) { + bool expected = (i != last_index ? i : 3u) == (j != last_index ? j : 3u); + EXPECT_EQ(expected, patches[i] == patches[j]) << i << " " << j; + } + } + + for (size_t i = 0; i != arraysize(patches); ++i) { + for (size_t j = 0; j != arraysize(patches); ++j) { + bool expected = (i != last_index ? i : 3u) < (j != last_index ? j : 3u); + EXPECT_EQ(expected, patches[i] < patches[j]) << i << " " << j; + } + } +} + +} // namespace art diff --git a/compiler/dex/dex_to_dex_compiler.cc b/compiler/dex/dex_to_dex_compiler.cc index ff7ddc18c6..4836041ce0 100644 --- a/compiler/dex/dex_to_dex_compiler.cc +++ b/compiler/dex/dex_to_dex_compiler.cc @@ -356,7 +356,7 @@ CompiledMethod* ArtCompileDEX( 0, 0, 0, - nullptr, // src_mapping_table + ArrayRef<const SrcMapElem>(), // src_mapping_table ArrayRef<const uint8_t>(), // mapping_table ArrayRef<const uint8_t>(builder.GetData()), // vmap_table ArrayRef<const uint8_t>(), // gc_map diff --git a/compiler/dex/quick/codegen_util.cc b/compiler/dex/quick/codegen_util.cc index cde99b3fae..d68835a9cf 100644 --- a/compiler/dex/quick/codegen_util.cc +++ b/compiler/dex/quick/codegen_util.cc @@ -22,6 +22,7 @@ #endif #include "base/bit_vector-inl.h" +#include "base/stringprintf.h" #include "dex/mir_graph.h" #include "driver/compiler_driver.h" #include "driver/compiler_options.h" @@ -1165,7 +1166,7 @@ CompiledMethod* Mir2Lir::GetCompiledMethod() { cu_->compiler_driver, cu_->instruction_set, ArrayRef<const uint8_t>(code_buffer_), frame_size_, core_spill_mask_, fp_spill_mask_, - &src_mapping_table_, + ArrayRef<const SrcMapElem>(src_mapping_table_), ArrayRef<const uint8_t>(encoded_mapping_table_), ArrayRef<const uint8_t>(vmap_encoder.GetData()), ArrayRef<const uint8_t>(native_gc_map_), diff --git a/compiler/dex/quick/dex_file_method_inliner.cc b/compiler/dex/quick/dex_file_method_inliner.cc index e1a2838f3e..eaf2408763 100644 --- a/compiler/dex/quick/dex_file_method_inliner.cc +++ b/compiler/dex/quick/dex_file_method_inliner.cc @@ -756,14 +756,7 @@ uint32_t DexFileMethodInliner::FindClassIndex(const DexFile* dex_file, IndexCach return *class_index; } - const DexFile::StringId* string_id = dex_file->FindStringId(kClassCacheNames[index]); - if (string_id == nullptr) { - *class_index = kIndexNotFound; - return *class_index; - } - uint32_t string_index = dex_file->GetIndexForStringId(*string_id); - - const DexFile::TypeId* type_id = dex_file->FindTypeId(string_index); + const DexFile::TypeId* type_id = dex_file->FindTypeId(kClassCacheNames[index]); if (type_id == nullptr) { *class_index = kIndexNotFound; return *class_index; diff --git a/compiler/dex/quick/quick_cfi_test.cc b/compiler/dex/quick/quick_cfi_test.cc index 18c2e55700..24daf2f15f 100644 --- a/compiler/dex/quick/quick_cfi_test.cc +++ b/compiler/dex/quick/quick_cfi_test.cc @@ -67,7 +67,6 @@ class QuickCFITest : public CFITest { false, false, nullptr, - new PassManagerOptions(), nullptr, false); VerificationResults verification_results(&compiler_options); diff --git a/compiler/dex/quick/ralloc_util.cc b/compiler/dex/quick/ralloc_util.cc index d9d0434e8a..dceb118d1e 100644 --- a/compiler/dex/quick/ralloc_util.cc +++ b/compiler/dex/quick/ralloc_util.cc @@ -18,6 +18,7 @@ #include "mir_to_lir-inl.h" +#include "base/stringprintf.h" #include "dex/compiler_ir.h" #include "dex/dataflow_iterator-inl.h" #include "dex/mir_graph.h" diff --git a/compiler/dex/quick/x86/quick_assemble_x86_test.cc b/compiler/dex/quick/x86/quick_assemble_x86_test.cc index d9571c5f26..e977ebf722 100644 --- a/compiler/dex/quick/x86/quick_assemble_x86_test.cc +++ b/compiler/dex/quick/x86/quick_assemble_x86_test.cc @@ -50,7 +50,6 @@ class QuickAssembleX86TestBase : public testing::Test { false, false, nullptr, - new PassManagerOptions(), nullptr, false)); verification_results_.reset(new VerificationResults(compiler_options_.get())); diff --git a/compiler/driver/compiled_method_storage.cc b/compiler/driver/compiled_method_storage.cc new file mode 100644 index 0000000000..bc5c6cab87 --- /dev/null +++ b/compiler/driver/compiled_method_storage.cc @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2015 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 <algorithm> +#include <ostream> + +#include "compiled_method_storage.h" + +#include "base/logging.h" +#include "compiled_method.h" +#include "thread-inl.h" +#include "utils.h" +#include "utils/dedupe_set-inl.h" +#include "utils/swap_space.h" + +namespace art { + +namespace { // anonymous namespace + +template <typename T> +const LengthPrefixedArray<T>* CopyArray(SwapSpace* swap_space, const ArrayRef<const T>& array) { + DCHECK(!array.empty()); + SwapAllocator<uint8_t> allocator(swap_space); + void* storage = allocator.allocate(LengthPrefixedArray<T>::ComputeSize(array.size())); + LengthPrefixedArray<T>* array_copy = new(storage) LengthPrefixedArray<T>(array.size()); + std::copy(array.begin(), array.end(), array_copy->begin()); + return array_copy; +} + +template <typename T> +void ReleaseArray(SwapSpace* swap_space, const LengthPrefixedArray<T>* array) { + SwapAllocator<uint8_t> allocator(swap_space); + size_t size = LengthPrefixedArray<T>::ComputeSize(array->size()); + array->~LengthPrefixedArray<T>(); + allocator.deallocate(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(array)), size); +} + +} // anonymous namespace + +template <typename T, typename DedupeSetType> +inline const LengthPrefixedArray<T>* CompiledMethodStorage::AllocateOrDeduplicateArray( + const ArrayRef<const T>& data, + DedupeSetType* dedupe_set) { + if (data.empty()) { + return nullptr; + } else if (!DedupeEnabled()) { + return CopyArray(swap_space_.get(), data); + } else { + return dedupe_set->Add(Thread::Current(), data); + } +} + +template <typename T> +inline void CompiledMethodStorage::ReleaseArrayIfNotDeduplicated( + const LengthPrefixedArray<T>* array) { + if (array != nullptr && !DedupeEnabled()) { + ReleaseArray(swap_space_.get(), array); + } +} + +template <typename ContentType> +class CompiledMethodStorage::DedupeHashFunc { + private: + static constexpr bool kUseMurmur3Hash = true; + + public: + size_t operator()(const ArrayRef<ContentType>& array) const { + const uint8_t* data = reinterpret_cast<const uint8_t*>(array.data()); + // TODO: More reasonable assertion. + // static_assert(IsPowerOfTwo(sizeof(ContentType)), + // "ContentType is not power of two, don't know whether array layout is as assumed"); + uint32_t len = sizeof(ContentType) * array.size(); + if (kUseMurmur3Hash) { + static constexpr uint32_t c1 = 0xcc9e2d51; + static constexpr uint32_t c2 = 0x1b873593; + static constexpr uint32_t r1 = 15; + static constexpr uint32_t r2 = 13; + static constexpr uint32_t m = 5; + static constexpr uint32_t n = 0xe6546b64; + + uint32_t hash = 0; + + const int nblocks = len / 4; + typedef __attribute__((__aligned__(1))) uint32_t unaligned_uint32_t; + const unaligned_uint32_t *blocks = reinterpret_cast<const uint32_t*>(data); + int i; + for (i = 0; i < nblocks; i++) { + uint32_t k = blocks[i]; + k *= c1; + k = (k << r1) | (k >> (32 - r1)); + k *= c2; + + hash ^= k; + hash = ((hash << r2) | (hash >> (32 - r2))) * m + n; + } + + const uint8_t *tail = reinterpret_cast<const uint8_t*>(data + nblocks * 4); + uint32_t k1 = 0; + + switch (len & 3) { + case 3: + k1 ^= tail[2] << 16; + FALLTHROUGH_INTENDED; + case 2: + k1 ^= tail[1] << 8; + FALLTHROUGH_INTENDED; + case 1: + k1 ^= tail[0]; + + k1 *= c1; + k1 = (k1 << r1) | (k1 >> (32 - r1)); + k1 *= c2; + hash ^= k1; + } + + hash ^= len; + hash ^= (hash >> 16); + hash *= 0x85ebca6b; + hash ^= (hash >> 13); + hash *= 0xc2b2ae35; + hash ^= (hash >> 16); + + return hash; + } else { + size_t hash = 0x811c9dc5; + for (uint32_t i = 0; i < len; ++i) { + hash = (hash * 16777619) ^ data[i]; + } + hash += hash << 13; + hash ^= hash >> 7; + hash += hash << 3; + hash ^= hash >> 17; + hash += hash << 5; + return hash; + } + } +}; + +template <typename T> +class CompiledMethodStorage::LengthPrefixedArrayAlloc { + public: + explicit LengthPrefixedArrayAlloc(SwapSpace* swap_space) + : swap_space_(swap_space) { + } + + const LengthPrefixedArray<T>* Copy(const ArrayRef<const T>& array) { + return CopyArray(swap_space_, array); + } + + void Destroy(const LengthPrefixedArray<T>* array) { + ReleaseArray(swap_space_, array); + } + + private: + SwapSpace* const swap_space_; +}; + +CompiledMethodStorage::CompiledMethodStorage(int swap_fd) + : swap_space_(swap_fd == -1 ? nullptr : new SwapSpace(swap_fd, 10 * MB)), + dedupe_enabled_(true), + dedupe_code_("dedupe code", LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())), + dedupe_src_mapping_table_("dedupe source mapping table", + LengthPrefixedArrayAlloc<SrcMapElem>(swap_space_.get())), + dedupe_mapping_table_("dedupe mapping table", + LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())), + dedupe_vmap_table_("dedupe vmap table", + LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())), + dedupe_gc_map_("dedupe gc map", LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())), + dedupe_cfi_info_("dedupe cfi info", LengthPrefixedArrayAlloc<uint8_t>(swap_space_.get())), + dedupe_linker_patches_("dedupe cfi info", + LengthPrefixedArrayAlloc<LinkerPatch>(swap_space_.get())) { +} + +CompiledMethodStorage::~CompiledMethodStorage() { + // All done by member destructors. +} + +void CompiledMethodStorage::DumpMemoryUsage(std::ostream& os, bool extended) const { + if (swap_space_.get() != nullptr) { + os << " swap=" << PrettySize(swap_space_->GetSize()); + } + if (extended) { + Thread* self = Thread::Current(); + os << "\nCode dedupe: " << dedupe_code_.DumpStats(self); + os << "\nMapping table dedupe: " << dedupe_mapping_table_.DumpStats(self); + os << "\nVmap table dedupe: " << dedupe_vmap_table_.DumpStats(self); + os << "\nGC map dedupe: " << dedupe_gc_map_.DumpStats(self); + os << "\nCFI info dedupe: " << dedupe_cfi_info_.DumpStats(self); + } +} + +const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateCode( + const ArrayRef<const uint8_t>& code) { + return AllocateOrDeduplicateArray(code, &dedupe_code_); +} + +void CompiledMethodStorage::ReleaseCode(const LengthPrefixedArray<uint8_t>* code) { + ReleaseArrayIfNotDeduplicated(code); +} + +const LengthPrefixedArray<SrcMapElem>* CompiledMethodStorage::DeduplicateSrcMappingTable( + const ArrayRef<const SrcMapElem>& src_map) { + return AllocateOrDeduplicateArray(src_map, &dedupe_src_mapping_table_); +} + +void CompiledMethodStorage::ReleaseSrcMappingTable(const LengthPrefixedArray<SrcMapElem>* src_map) { + ReleaseArrayIfNotDeduplicated(src_map); +} + +const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateMappingTable( + const ArrayRef<const uint8_t>& table) { + return AllocateOrDeduplicateArray(table, &dedupe_mapping_table_); +} + +void CompiledMethodStorage::ReleaseMappingTable(const LengthPrefixedArray<uint8_t>* table) { + ReleaseArrayIfNotDeduplicated(table); +} + +const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateVMapTable( + const ArrayRef<const uint8_t>& table) { + return AllocateOrDeduplicateArray(table, &dedupe_vmap_table_); +} + +void CompiledMethodStorage::ReleaseVMapTable(const LengthPrefixedArray<uint8_t>* table) { + ReleaseArrayIfNotDeduplicated(table); +} + +const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateGCMap( + const ArrayRef<const uint8_t>& gc_map) { + return AllocateOrDeduplicateArray(gc_map, &dedupe_gc_map_); +} + +void CompiledMethodStorage::ReleaseGCMap(const LengthPrefixedArray<uint8_t>* gc_map) { + ReleaseArrayIfNotDeduplicated(gc_map); +} + +const LengthPrefixedArray<uint8_t>* CompiledMethodStorage::DeduplicateCFIInfo( + const ArrayRef<const uint8_t>& cfi_info) { + return AllocateOrDeduplicateArray(cfi_info, &dedupe_cfi_info_); +} + +void CompiledMethodStorage::ReleaseCFIInfo(const LengthPrefixedArray<uint8_t>* cfi_info) { + ReleaseArrayIfNotDeduplicated(cfi_info); +} + +const LengthPrefixedArray<LinkerPatch>* CompiledMethodStorage::DeduplicateLinkerPatches( + const ArrayRef<const LinkerPatch>& linker_patches) { + return AllocateOrDeduplicateArray(linker_patches, &dedupe_linker_patches_); +} + +void CompiledMethodStorage::ReleaseLinkerPatches( + const LengthPrefixedArray<LinkerPatch>* linker_patches) { + ReleaseArrayIfNotDeduplicated(linker_patches); +} + +} // namespace art diff --git a/compiler/driver/compiled_method_storage.h b/compiler/driver/compiled_method_storage.h new file mode 100644 index 0000000000..ef10b6768b --- /dev/null +++ b/compiler/driver/compiled_method_storage.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2015 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_DRIVER_COMPILED_METHOD_STORAGE_H_ +#define ART_COMPILER_DRIVER_COMPILED_METHOD_STORAGE_H_ + +#include <iosfwd> +#include <memory> + +#include "base/macros.h" +#include "length_prefixed_array.h" +#include "utils/array_ref.h" +#include "utils/dedupe_set.h" +#include "utils/swap_space.h" + +namespace art { + +class LinkerPatch; +class SrcMapElem; + +class CompiledMethodStorage { + public: + explicit CompiledMethodStorage(int swap_fd); + ~CompiledMethodStorage(); + + void DumpMemoryUsage(std::ostream& os, bool extended) const; + + void SetDedupeEnabled(bool dedupe_enabled) { + dedupe_enabled_ = dedupe_enabled; + } + bool DedupeEnabled() const { + return dedupe_enabled_; + } + + SwapAllocator<void> GetSwapSpaceAllocator() { + return SwapAllocator<void>(swap_space_.get()); + } + + const LengthPrefixedArray<uint8_t>* DeduplicateCode(const ArrayRef<const uint8_t>& code); + void ReleaseCode(const LengthPrefixedArray<uint8_t>* code); + + const LengthPrefixedArray<SrcMapElem>* DeduplicateSrcMappingTable( + const ArrayRef<const SrcMapElem>& src_map); + void ReleaseSrcMappingTable(const LengthPrefixedArray<SrcMapElem>* src_map); + + const LengthPrefixedArray<uint8_t>* DeduplicateMappingTable(const ArrayRef<const uint8_t>& table); + void ReleaseMappingTable(const LengthPrefixedArray<uint8_t>* table); + + const LengthPrefixedArray<uint8_t>* DeduplicateVMapTable(const ArrayRef<const uint8_t>& table); + void ReleaseVMapTable(const LengthPrefixedArray<uint8_t>* table); + + const LengthPrefixedArray<uint8_t>* DeduplicateGCMap(const ArrayRef<const uint8_t>& gc_map); + void ReleaseGCMap(const LengthPrefixedArray<uint8_t>* gc_map); + + const LengthPrefixedArray<uint8_t>* DeduplicateCFIInfo(const ArrayRef<const uint8_t>& cfi_info); + void ReleaseCFIInfo(const LengthPrefixedArray<uint8_t>* cfi_info); + + const LengthPrefixedArray<LinkerPatch>* DeduplicateLinkerPatches( + const ArrayRef<const LinkerPatch>& linker_patches); + void ReleaseLinkerPatches(const LengthPrefixedArray<LinkerPatch>* linker_patches); + + private: + template <typename T, typename DedupeSetType> + const LengthPrefixedArray<T>* AllocateOrDeduplicateArray(const ArrayRef<const T>& data, + DedupeSetType* dedupe_set); + + template <typename T> + void ReleaseArrayIfNotDeduplicated(const LengthPrefixedArray<T>* array); + + // DeDuplication data structures. + template <typename ContentType> + class DedupeHashFunc; + + template <typename T> + class LengthPrefixedArrayAlloc; + + template <typename T> + using ArrayDedupeSet = DedupeSet<ArrayRef<const T>, + LengthPrefixedArray<T>, + LengthPrefixedArrayAlloc<T>, + size_t, + DedupeHashFunc<const T>, + 4>; + + // Swap pool and allocator used for native allocations. May be file-backed. Needs to be first + // as other fields rely on this. + std::unique_ptr<SwapSpace> swap_space_; + + bool dedupe_enabled_; + + ArrayDedupeSet<uint8_t> dedupe_code_; + ArrayDedupeSet<SrcMapElem> dedupe_src_mapping_table_; + ArrayDedupeSet<uint8_t> dedupe_mapping_table_; + ArrayDedupeSet<uint8_t> dedupe_vmap_table_; + ArrayDedupeSet<uint8_t> dedupe_gc_map_; + ArrayDedupeSet<uint8_t> dedupe_cfi_info_; + ArrayDedupeSet<LinkerPatch> dedupe_linker_patches_; + + DISALLOW_COPY_AND_ASSIGN(CompiledMethodStorage); +}; + +} // namespace art + +#endif // ART_COMPILER_DRIVER_COMPILED_METHOD_STORAGE_H_ diff --git a/compiler/driver/compiled_method_storage_test.cc b/compiler/driver/compiled_method_storage_test.cc new file mode 100644 index 0000000000..c6dbd24bf8 --- /dev/null +++ b/compiler/driver/compiled_method_storage_test.cc @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> + +#include "compiled_method_storage.h" +#include "compiled_method.h" +#include "compiler_driver.h" +#include "compiler_options.h" +#include "dex/verification_results.h" +#include "dex/quick/dex_file_to_method_inliner_map.h" + +namespace art { + +TEST(CompiledMethodStorage, Deduplicate) { + CompilerOptions compiler_options; + VerificationResults verification_results(&compiler_options); + DexFileToMethodInlinerMap method_inliner_map; + CompilerDriver driver(&compiler_options, + &verification_results, + &method_inliner_map, + Compiler::kOptimizing, kNone, + nullptr, + false, + nullptr, + nullptr, + nullptr, + 1u, + false, + false, + "", + false, + nullptr, + -1, + ""); + CompiledMethodStorage* storage = driver.GetCompiledMethodStorage(); + + ASSERT_TRUE(storage->DedupeEnabled()); // The default. + + const uint8_t raw_code1[] = { 1u, 2u, 3u }; + const uint8_t raw_code2[] = { 4u, 3u, 2u, 1u }; + ArrayRef<const uint8_t> code[] = { + ArrayRef<const uint8_t>(raw_code1), + ArrayRef<const uint8_t>(raw_code2), + }; + const SrcMapElem raw_src_map1[] = { { 1u, 2u }, { 3u, 4u }, { 5u, 6u } }; + const SrcMapElem raw_src_map2[] = { { 8u, 7u }, { 6u, 5u }, { 4u, 3u }, { 2u, 1u } }; + ArrayRef<const SrcMapElem> src_map[] = { + ArrayRef<const SrcMapElem>(raw_src_map1), + ArrayRef<const SrcMapElem>(raw_src_map2), + }; + const uint8_t raw_mapping_table1[] = { 5, 6, 7 }; + const uint8_t raw_mapping_table2[] = { 7, 6, 5, 4 }; + ArrayRef<const uint8_t> mapping_table[] = { + ArrayRef<const uint8_t>(raw_mapping_table1), + ArrayRef<const uint8_t>(raw_mapping_table2), + }; + const uint8_t raw_vmap_table1[] = { 2, 4, 6 }; + const uint8_t raw_vmap_table2[] = { 7, 5, 3, 1 }; + ArrayRef<const uint8_t> vmap_table[] = { + ArrayRef<const uint8_t>(raw_vmap_table1), + ArrayRef<const uint8_t>(raw_vmap_table2), + }; + const uint8_t raw_gc_map1[] = { 9, 8, 7 }; + const uint8_t raw_gc_map2[] = { 6, 7, 8, 9 }; + ArrayRef<const uint8_t> gc_map[] = { + ArrayRef<const uint8_t>(raw_gc_map1), + ArrayRef<const uint8_t>(raw_gc_map2), + }; + const uint8_t raw_cfi_info1[] = { 1, 3, 5 }; + const uint8_t raw_cfi_info2[] = { 8, 6, 4, 2 }; + ArrayRef<const uint8_t> cfi_info[] = { + ArrayRef<const uint8_t>(raw_cfi_info1), + ArrayRef<const uint8_t>(raw_cfi_info2), + }; + const LinkerPatch raw_patches1[] = { + LinkerPatch::CodePatch(0u, nullptr, 1u), + LinkerPatch::MethodPatch(4u, nullptr, 1u), + }; + const LinkerPatch raw_patches2[] = { + LinkerPatch::CodePatch(0u, nullptr, 1u), + LinkerPatch::MethodPatch(4u, nullptr, 2u), + }; + ArrayRef<const LinkerPatch> patches[] = { + ArrayRef<const LinkerPatch>(raw_patches1), + ArrayRef<const LinkerPatch>(raw_patches2), + }; + + std::vector<CompiledMethod*> compiled_methods; + compiled_methods.reserve(1u << 7); + for (auto&& c : code) { + for (auto&& s : src_map) { + for (auto&& m : mapping_table) { + for (auto&& v : vmap_table) { + for (auto&& g : gc_map) { + for (auto&& f : cfi_info) { + for (auto&& p : patches) { + compiled_methods.push_back(CompiledMethod::SwapAllocCompiledMethod( + &driver, kNone, c, 0u, 0u, 0u, s, m, v, g, f, p)); + } + } + } + } + } + } + } + constexpr size_t code_bit = 1u << 6; + constexpr size_t src_map_bit = 1u << 5; + constexpr size_t mapping_table_bit = 1u << 4; + constexpr size_t vmap_table_bit = 1u << 3; + constexpr size_t gc_map_bit = 1u << 2; + constexpr size_t cfi_info_bit = 1u << 1; + constexpr size_t patches_bit = 1u << 0; + CHECK_EQ(compiled_methods.size(), 1u << 7); + for (size_t i = 0; i != compiled_methods.size(); ++i) { + for (size_t j = 0; j != compiled_methods.size(); ++j) { + CompiledMethod* lhs = compiled_methods[i]; + CompiledMethod* rhs = compiled_methods[j]; + bool same_code = ((i ^ j) & code_bit) == 0u; + bool same_src_map = ((i ^ j) & src_map_bit) == 0u; + bool same_mapping_table = ((i ^ j) & mapping_table_bit) == 0u; + bool same_vmap_table = ((i ^ j) & vmap_table_bit) == 0u; + bool same_gc_map = ((i ^ j) & gc_map_bit) == 0u; + bool same_cfi_info = ((i ^ j) & cfi_info_bit) == 0u; + bool same_patches = ((i ^ j) & patches_bit) == 0u; + ASSERT_EQ(same_code, lhs->GetQuickCode().data() == rhs->GetQuickCode().data()) + << i << " " << j; + ASSERT_EQ(same_src_map, lhs->GetSrcMappingTable().data() == rhs->GetSrcMappingTable().data()) + << i << " " << j; + ASSERT_EQ(same_mapping_table, lhs->GetMappingTable().data() == rhs->GetMappingTable().data()) + << i << " " << j; + ASSERT_EQ(same_vmap_table, lhs->GetVmapTable().data() == rhs->GetVmapTable().data()) + << i << " " << j; + ASSERT_EQ(same_gc_map, lhs->GetGcMap().data() == rhs->GetGcMap().data()) + << i << " " << j; + ASSERT_EQ(same_cfi_info, lhs->GetCFIInfo().data() == rhs->GetCFIInfo().data()) + << i << " " << j; + ASSERT_EQ(same_patches, lhs->GetPatches().data() == rhs->GetPatches().data()) + << i << " " << j; + } + } + for (CompiledMethod* method : compiled_methods) { + CompiledMethod::ReleaseSwapAllocatedCompiledMethod(&driver, method); + } +} + +} // namespace art diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h index 1a7dbe3a9f..14ba81d193 100644 --- a/compiler/driver/compiler_driver-inl.h +++ b/compiler/driver/compiler_driver-inl.h @@ -187,15 +187,11 @@ inline std::pair<bool, bool> CompilerDriver::IsClassOfStaticMemberAvailableToRef // Search dex file for localized ssb index, may fail if member's class is a parent // of the class mentioned in the dex file and there is no dex cache entry. std::string temp; - const DexFile::StringId* string_id = - dex_file->FindStringId(resolved_member->GetDeclaringClass()->GetDescriptor(&temp)); - if (string_id != nullptr) { - const DexFile::TypeId* type_id = - dex_file->FindTypeId(dex_file->GetIndexForStringId(*string_id)); - if (type_id != nullptr) { - // medium path, needs check of static storage base being initialized - storage_idx = dex_file->GetIndexForTypeId(*type_id); - } + const DexFile::TypeId* type_id = + dex_file->FindTypeId(resolved_member->GetDeclaringClass()->GetDescriptor(&temp)); + if (type_id != nullptr) { + // medium path, needs check of static storage base being initialized + storage_idx = dex_file->GetIndexForTypeId(*type_id); } } if (storage_idx != DexFile::kDexNoIndex) { diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index b9565846ce..d055b37ea7 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -348,9 +348,8 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, const std::string& dump_cfg_file_name, bool dump_cfg_append, CumulativeLogger* timer, int swap_fd, const std::string& profile_file) - : swap_space_(swap_fd == -1 ? nullptr : new SwapSpace(swap_fd, 10 * MB)), - swap_space_allocator_(new SwapAllocator<void>(swap_space_.get())), - profile_present_(false), compiler_options_(compiler_options), + : profile_present_(false), + compiler_options_(compiler_options), verification_results_(verification_results), method_inliner_map_(method_inliner_map), compiler_(Compiler::Create(this, compiler_kind)), @@ -369,7 +368,6 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, had_hard_verifier_failure_(false), thread_count_(thread_count), stats_(new AOTCompilationStats), - dedupe_enabled_(true), dump_stats_(dump_stats), dump_passes_(dump_passes), dump_cfg_file_name_(dump_cfg_file_name), @@ -377,12 +375,8 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, timings_logger_(timer), compiler_context_(nullptr), support_boot_image_fixup_(instruction_set != kMips && instruction_set != kMips64), - dedupe_code_("dedupe code", *swap_space_allocator_), - dedupe_src_mapping_table_("dedupe source mapping table", *swap_space_allocator_), - dedupe_mapping_table_("dedupe mapping table", *swap_space_allocator_), - dedupe_vmap_table_("dedupe vmap table", *swap_space_allocator_), - dedupe_gc_map_("dedupe gc map", *swap_space_allocator_), - dedupe_cfi_info_("dedupe cfi info", *swap_space_allocator_) { + dex_files_for_oat_file_(nullptr), + compiled_method_storage_(swap_fd) { DCHECK(compiler_options_ != nullptr); DCHECK(verification_results_ != nullptr); DCHECK(method_inliner_map_ != nullptr); @@ -402,36 +396,6 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, } } -SwapVector<uint8_t>* CompilerDriver::DeduplicateCode(const ArrayRef<const uint8_t>& code) { - DCHECK(dedupe_enabled_); - return dedupe_code_.Add(Thread::Current(), code); -} - -SwapSrcMap* CompilerDriver::DeduplicateSrcMappingTable(const ArrayRef<SrcMapElem>& src_map) { - DCHECK(dedupe_enabled_); - return dedupe_src_mapping_table_.Add(Thread::Current(), src_map); -} - -SwapVector<uint8_t>* CompilerDriver::DeduplicateMappingTable(const ArrayRef<const uint8_t>& code) { - DCHECK(dedupe_enabled_); - return dedupe_mapping_table_.Add(Thread::Current(), code); -} - -SwapVector<uint8_t>* CompilerDriver::DeduplicateVMapTable(const ArrayRef<const uint8_t>& code) { - DCHECK(dedupe_enabled_); - return dedupe_vmap_table_.Add(Thread::Current(), code); -} - -SwapVector<uint8_t>* CompilerDriver::DeduplicateGCMap(const ArrayRef<const uint8_t>& code) { - DCHECK(dedupe_enabled_); - return dedupe_gc_map_.Add(Thread::Current(), code); -} - -SwapVector<uint8_t>* CompilerDriver::DeduplicateCFIInfo(const ArrayRef<const uint8_t>& cfi_info) { - DCHECK(dedupe_enabled_); - return dedupe_cfi_info_.Add(Thread::Current(), cfi_info); -} - CompilerDriver::~CompilerDriver() { Thread* self = Thread::Current(); { @@ -447,6 +411,7 @@ CompilerDriver::~CompilerDriver() { compiler_->UnInit(); } + #define CREATE_TRAMPOLINE(type, abi, offset) \ if (Is64BitInstructionSet(instruction_set_)) { \ return CreateTrampoline64(instruction_set_, abi, \ @@ -732,6 +697,9 @@ void CompilerDriver::CompileOne(Thread* self, ArtMethod* method, TimingLogger* t } CompiledMethod* CompilerDriver::CompileArtMethod(Thread* self, ArtMethod* method) { + DCHECK_EQ(method, + method->GetInterfaceMethodIfProxy( + Runtime::Current()->GetClassLinker()->GetImagePointerSize())); const uint32_t method_idx = method->GetDexMethodIndex(); const uint32_t access_flags = method->GetAccessFlags(); const InvokeType invoke_type = method->GetInvokeType(); @@ -1407,8 +1375,7 @@ uint32_t CompilerDriver::GetReferenceDisableFlagOffset() const { } DexCacheArraysLayout CompilerDriver::GetDexCacheArraysLayout(const DexFile* dex_file) { - // Currently only image dex caches have fixed array layout. - return IsImage() && GetSupportBootImageFixup() + return ContainsElement(GetDexFilesForOatFile(), dex_file) ? DexCacheArraysLayout(GetInstructionSetPointerSize(instruction_set_), dex_file) : DexCacheArraysLayout(); } @@ -2642,16 +2609,7 @@ std::string CompilerDriver::GetMemoryUsageString(bool extended) const { oss << " native alloc=" << PrettySize(allocated_space) << " free=" << PrettySize(free_space); #endif - if (swap_space_.get() != nullptr) { - oss << " swap=" << PrettySize(swap_space_->GetSize()); - } - if (extended) { - oss << "\nCode dedupe: " << dedupe_code_.DumpStats(); - oss << "\nMapping table dedupe: " << dedupe_mapping_table_.DumpStats(); - oss << "\nVmap table dedupe: " << dedupe_vmap_table_.DumpStats(); - oss << "\nGC map dedupe: " << dedupe_gc_map_.DumpStats(); - oss << "\nCFI info dedupe: " << dedupe_cfi_info_.DumpStats(); - } + compiled_method_storage_.DumpMemoryUsage(oss, extended); return oss.str(); } diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 0dc8261aac..4ed4dc60d2 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -30,6 +30,7 @@ #include "class_reference.h" #include "compiler.h" #include "dex_file.h" +#include "driver/compiled_method_storage.h" #include "invoke_type.h" #include "method_reference.h" #include "mirror/class.h" // For mirror::Class::Status. @@ -39,9 +40,7 @@ #include "safe_map.h" #include "thread_pool.h" #include "utils/array_ref.h" -#include "utils/dedupe_set.h" #include "utils/dex_cache_arrays_layout.h" -#include "utils/swap_space.h" namespace art { @@ -80,8 +79,6 @@ enum EntryPointCallingConvention { kQuickAbi }; -static constexpr bool kUseMurmur3Hash = true; - class CompilerDriver { public: // Create a compiler targeting the requested "instruction_set". @@ -105,7 +102,20 @@ class CompilerDriver { ~CompilerDriver(); - void CompileAll(jobject class_loader, const std::vector<const DexFile*>& dex_files, + // Set dex files that will be stored in the oat file after being compiled. + void SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files) { + dex_files_for_oat_file_ = &dex_files; + } + + // Get dex file that will be stored in the oat file after being compiled. + ArrayRef<const DexFile* const> GetDexFilesForOatFile() const { + return (dex_files_for_oat_file_ != nullptr) + ? ArrayRef<const DexFile* const>(*dex_files_for_oat_file_) + : ArrayRef<const DexFile* const>(); + } + + void CompileAll(jobject class_loader, + const std::vector<const DexFile*>& dex_files, TimingLogger* timings) REQUIRES(!Locks::mutator_lock_, !compiled_classes_lock_); @@ -388,10 +398,6 @@ class CompilerDriver { support_boot_image_fixup_ = support_boot_image_fixup; } - SwapAllocator<void>& GetSwapSpaceAllocator() { - return *swap_space_allocator_.get(); - } - bool WriteElf(const std::string& android_root, bool is_host, const std::vector<const DexFile*>& dex_files, @@ -431,10 +437,10 @@ class CompilerDriver { } void SetDedupeEnabled(bool dedupe_enabled) { - dedupe_enabled_ = dedupe_enabled; + compiled_method_storage_.SetDedupeEnabled(dedupe_enabled); } bool DedupeEnabled() const { - return dedupe_enabled_; + return compiled_method_storage_.DedupeEnabled(); } // Checks if class specified by type_idx is one of the image_classes_ @@ -455,13 +461,6 @@ class CompilerDriver { uint16_t class_def_idx, const DexFile& dex_file) const; - SwapVector<uint8_t>* DeduplicateCode(const ArrayRef<const uint8_t>& code); - SwapSrcMap* DeduplicateSrcMappingTable(const ArrayRef<SrcMapElem>& src_map); - SwapVector<uint8_t>* DeduplicateMappingTable(const ArrayRef<const uint8_t>& code); - SwapVector<uint8_t>* DeduplicateVMapTable(const ArrayRef<const uint8_t>& code); - SwapVector<uint8_t>* DeduplicateGCMap(const ArrayRef<const uint8_t>& code); - SwapVector<uint8_t>* DeduplicateCFIInfo(const ArrayRef<const uint8_t>& cfi_info); - // Should the compiler run on this method given profile information? bool SkipCompilation(const std::string& method_name); @@ -479,6 +478,10 @@ class CompilerDriver { return compiler_kind_; } + CompiledMethodStorage* GetCompiledMethodStorage() { + return &compiled_method_storage_; + } + private: // Return whether the declaring class of `resolved_member` is // available to `referrer_class` for read or write access using two @@ -599,11 +602,6 @@ class CompilerDriver { ThreadPool* thread_pool, TimingLogger* timings) REQUIRES(!Locks::mutator_lock_); - // Swap pool and allocator used for native allocations. May be file-backed. Needs to be first - // as other fields rely on this. - std::unique_ptr<SwapSpace> swap_space_; - std::unique_ptr<SwapAllocator<void> > swap_space_allocator_; - ProfileFile profile_file_; bool profile_present_; @@ -663,7 +661,6 @@ class CompilerDriver { class AOTCompilationStats; std::unique_ptr<AOTCompilationStats> stats_; - bool dedupe_enabled_; bool dump_stats_; const bool dump_passes_; const std::string dump_cfg_file_name_; @@ -678,93 +675,10 @@ class CompilerDriver { bool support_boot_image_fixup_; - // DeDuplication data structures, these own the corresponding byte arrays. - template <typename ContentType> - class DedupeHashFunc { - public: - size_t operator()(const ArrayRef<ContentType>& array) const { - const uint8_t* data = reinterpret_cast<const uint8_t*>(array.data()); - static_assert(IsPowerOfTwo(sizeof(ContentType)), - "ContentType is not power of two, don't know whether array layout is as assumed"); - uint32_t len = sizeof(ContentType) * array.size(); - if (kUseMurmur3Hash) { - static constexpr uint32_t c1 = 0xcc9e2d51; - static constexpr uint32_t c2 = 0x1b873593; - static constexpr uint32_t r1 = 15; - static constexpr uint32_t r2 = 13; - static constexpr uint32_t m = 5; - static constexpr uint32_t n = 0xe6546b64; - - uint32_t hash = 0; - - const int nblocks = len / 4; - typedef __attribute__((__aligned__(1))) uint32_t unaligned_uint32_t; - const unaligned_uint32_t *blocks = reinterpret_cast<const uint32_t*>(data); - int i; - for (i = 0; i < nblocks; i++) { - uint32_t k = blocks[i]; - k *= c1; - k = (k << r1) | (k >> (32 - r1)); - k *= c2; - - hash ^= k; - hash = ((hash << r2) | (hash >> (32 - r2))) * m + n; - } - - const uint8_t *tail = reinterpret_cast<const uint8_t*>(data + nblocks * 4); - uint32_t k1 = 0; - - switch (len & 3) { - case 3: - k1 ^= tail[2] << 16; - FALLTHROUGH_INTENDED; - case 2: - k1 ^= tail[1] << 8; - FALLTHROUGH_INTENDED; - case 1: - k1 ^= tail[0]; - - k1 *= c1; - k1 = (k1 << r1) | (k1 >> (32 - r1)); - k1 *= c2; - hash ^= k1; - } - - hash ^= len; - hash ^= (hash >> 16); - hash *= 0x85ebca6b; - hash ^= (hash >> 13); - hash *= 0xc2b2ae35; - hash ^= (hash >> 16); - - return hash; - } else { - size_t hash = 0x811c9dc5; - for (uint32_t i = 0; i < len; ++i) { - hash = (hash * 16777619) ^ data[i]; - } - hash += hash << 13; - hash ^= hash >> 7; - hash += hash << 3; - hash ^= hash >> 17; - hash += hash << 5; - return hash; - } - } - }; + // List of dex files that will be stored in the oat file. + const std::vector<const DexFile*>* dex_files_for_oat_file_; - DedupeSet<ArrayRef<const uint8_t>, - SwapVector<uint8_t>, size_t, DedupeHashFunc<const uint8_t>, 4> dedupe_code_; - DedupeSet<ArrayRef<SrcMapElem>, - SwapSrcMap, size_t, DedupeHashFunc<SrcMapElem>, 4> dedupe_src_mapping_table_; - DedupeSet<ArrayRef<const uint8_t>, - SwapVector<uint8_t>, size_t, DedupeHashFunc<const uint8_t>, 4> dedupe_mapping_table_; - DedupeSet<ArrayRef<const uint8_t>, - SwapVector<uint8_t>, size_t, DedupeHashFunc<const uint8_t>, 4> dedupe_vmap_table_; - DedupeSet<ArrayRef<const uint8_t>, - SwapVector<uint8_t>, size_t, DedupeHashFunc<const uint8_t>, 4> dedupe_gc_map_; - DedupeSet<ArrayRef<const uint8_t>, - SwapVector<uint8_t>, size_t, DedupeHashFunc<const uint8_t>, 4> dedupe_cfi_info_; + CompiledMethodStorage compiled_method_storage_; friend class CompileClassVisitor; DISALLOW_COPY_AND_ASSIGN(CompilerDriver); diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc index 3f5a1eabb6..a24c8a3347 100644 --- a/compiler/driver/compiler_options.cc +++ b/compiler/driver/compiler_options.cc @@ -16,6 +16,8 @@ #include "compiler_options.h" +#include <fstream> + #include "dex/pass_manager.h" namespace art { @@ -27,8 +29,8 @@ CompilerOptions::CompilerOptions() small_method_threshold_(kDefaultSmallMethodThreshold), tiny_method_threshold_(kDefaultTinyMethodThreshold), num_dex_methods_threshold_(kDefaultNumDexMethodsThreshold), - inline_depth_limit_(kDefaultInlineDepthLimit), - inline_max_code_units_(kDefaultInlineMaxCodeUnits), + inline_depth_limit_(kUnsetInlineDepthLimit), + inline_max_code_units_(kUnsetInlineMaxCodeUnits), include_patch_information_(kDefaultIncludePatchInformation), top_k_profile_threshold_(kDefaultTopKProfileThreshold), debuggable_(false), @@ -38,7 +40,7 @@ CompilerOptions::CompilerOptions() implicit_suspend_checks_(false), compile_pic_(false), verbose_methods_(nullptr), - pass_manager_options_(new PassManagerOptions), + pass_manager_options_(), abort_on_hard_verifier_failure_(false), init_failure_output_(nullptr) { } @@ -65,7 +67,6 @@ CompilerOptions::CompilerOptions(CompilerFilter compiler_filter, bool implicit_suspend_checks, bool compile_pic, const std::vector<std::string>* verbose_methods, - PassManagerOptions* pass_manager_options, std::ostream* init_failure_output, bool abort_on_hard_verifier_failure ) : // NOLINT(whitespace/parens) @@ -86,9 +87,155 @@ CompilerOptions::CompilerOptions(CompilerFilter compiler_filter, implicit_suspend_checks_(implicit_suspend_checks), compile_pic_(compile_pic), verbose_methods_(verbose_methods), - pass_manager_options_(pass_manager_options), + pass_manager_options_(), abort_on_hard_verifier_failure_(abort_on_hard_verifier_failure), init_failure_output_(init_failure_output) { } +void CompilerOptions::ParseHugeMethodMax(const StringPiece& option, UsageFn Usage) { + ParseUintOption(option, "--huge-method-max", &huge_method_threshold_, Usage); +} + +void CompilerOptions::ParseLargeMethodMax(const StringPiece& option, UsageFn Usage) { + ParseUintOption(option, "--large-method-max", &large_method_threshold_, Usage); +} + +void CompilerOptions::ParseSmallMethodMax(const StringPiece& option, UsageFn Usage) { + ParseUintOption(option, "--small-method-max", &small_method_threshold_, Usage); +} + +void CompilerOptions::ParseTinyMethodMax(const StringPiece& option, UsageFn Usage) { + ParseUintOption(option, "--tiny-method-max", &tiny_method_threshold_, Usage); +} + +void CompilerOptions::ParseNumDexMethods(const StringPiece& option, UsageFn Usage) { + ParseUintOption(option, "--num-dex-methods", &num_dex_methods_threshold_, Usage); +} + +void CompilerOptions::ParseInlineDepthLimit(const StringPiece& option, UsageFn Usage) { + ParseUintOption(option, "--inline-depth-limit", &inline_depth_limit_, Usage); +} + +void CompilerOptions::ParseInlineMaxCodeUnits(const StringPiece& option, UsageFn Usage) { + ParseUintOption(option, "--inline-max-code-units=", &inline_max_code_units_, Usage); +} + +void CompilerOptions::ParseDisablePasses(const StringPiece& option, + UsageFn Usage ATTRIBUTE_UNUSED) { + DCHECK(option.starts_with("--disable-passes=")); + const std::string disable_passes = option.substr(strlen("--disable-passes=")).data(); + pass_manager_options_.SetDisablePassList(disable_passes); +} + +void CompilerOptions::ParsePrintPasses(const StringPiece& option, + UsageFn Usage ATTRIBUTE_UNUSED) { + DCHECK(option.starts_with("--print-passes=")); + const std::string print_passes = option.substr(strlen("--print-passes=")).data(); + pass_manager_options_.SetPrintPassList(print_passes); +} + +void CompilerOptions::ParseDumpCfgPasses(const StringPiece& option, + UsageFn Usage ATTRIBUTE_UNUSED) { + DCHECK(option.starts_with("--dump-cfg-passes=")); + const std::string dump_passes_string = option.substr(strlen("--dump-cfg-passes=")).data(); + pass_manager_options_.SetDumpPassList(dump_passes_string); +} + +void CompilerOptions::ParsePassOptions(const StringPiece& option, + UsageFn Usage ATTRIBUTE_UNUSED) { + DCHECK(option.starts_with("--pass-options=")); + const std::string pass_options = option.substr(strlen("--pass-options=")).data(); + pass_manager_options_.SetOverriddenPassOptions(pass_options); +} + +void CompilerOptions::ParseDumpInitFailures(const StringPiece& option, + UsageFn Usage ATTRIBUTE_UNUSED) { + DCHECK(option.starts_with("--dump-init-failures=")); + std::string file_name = option.substr(strlen("--dump-init-failures=")).data(); + init_failure_output_.reset(new std::ofstream(file_name)); + if (init_failure_output_.get() == nullptr) { + LOG(ERROR) << "Failed to allocate ofstream"; + } else if (init_failure_output_->fail()) { + LOG(ERROR) << "Failed to open " << file_name << " for writing the initialization " + << "failures."; + init_failure_output_.reset(); + } +} + +bool CompilerOptions::ParseCompilerOption(const StringPiece& option, UsageFn Usage) { + if (option.starts_with("--compiler-filter=")) { + const char* compiler_filter_string = option.substr(strlen("--compiler-filter=")).data(); + if (strcmp(compiler_filter_string, "verify-none") == 0) { + compiler_filter_ = CompilerOptions::kVerifyNone; + } else if (strcmp(compiler_filter_string, "interpret-only") == 0) { + compiler_filter_ = CompilerOptions::kInterpretOnly; + } else if (strcmp(compiler_filter_string, "verify-at-runtime") == 0) { + compiler_filter_ = CompilerOptions::kVerifyAtRuntime; + } else if (strcmp(compiler_filter_string, "space") == 0) { + compiler_filter_ = CompilerOptions::kSpace; + } else if (strcmp(compiler_filter_string, "balanced") == 0) { + compiler_filter_ = CompilerOptions::kBalanced; + } else if (strcmp(compiler_filter_string, "speed") == 0) { + compiler_filter_ = CompilerOptions::kSpeed; + } else if (strcmp(compiler_filter_string, "everything") == 0) { + compiler_filter_ = CompilerOptions::kEverything; + } else if (strcmp(compiler_filter_string, "time") == 0) { + compiler_filter_ = CompilerOptions::kTime; + } else { + Usage("Unknown --compiler-filter value %s", compiler_filter_string); + } + } else if (option == "--compile-pic") { + compile_pic_ = true; + } else if (option.starts_with("--huge-method-max=")) { + ParseHugeMethodMax(option, Usage); + } else if (option.starts_with("--large-method-max=")) { + ParseLargeMethodMax(option, Usage); + } else if (option.starts_with("--small-method-max=")) { + ParseSmallMethodMax(option, Usage); + } else if (option.starts_with("--tiny-method-max=")) { + ParseTinyMethodMax(option, Usage); + } else if (option.starts_with("--num-dex-methods=")) { + ParseNumDexMethods(option, Usage); + } else if (option.starts_with("--inline-depth-limit=")) { + ParseInlineDepthLimit(option, Usage); + } else if (option.starts_with("--inline-max-code-units=")) { + ParseInlineMaxCodeUnits(option, Usage); + } else if (option == "--generate-debug-info" || option == "-g") { + generate_debug_info_ = true; + } else if (option == "--no-generate-debug-info") { + generate_debug_info_ = false; + } else if (option == "--debuggable") { + debuggable_ = true; + generate_debug_info_ = true; + } else if (option.starts_with("--top-k-profile-threshold=")) { + ParseDouble(option.data(), '=', 0.0, 100.0, &top_k_profile_threshold_, Usage); + } else if (option == "--include-patch-information") { + include_patch_information_ = true; + } else if (option == "--no-include-patch-information") { + include_patch_information_ = false; + } else if (option == "--abort-on-hard-verifier-error") { + abort_on_hard_verifier_failure_ = true; + } else if (option == "--print-pass-names") { + pass_manager_options_.SetPrintPassNames(true); + } else if (option.starts_with("--disable-passes=")) { + ParseDisablePasses(option, Usage); + } else if (option.starts_with("--print-passes=")) { + ParsePrintPasses(option, Usage); + } else if (option == "--print-all-passes") { + pass_manager_options_.SetPrintAllPasses(); + } else if (option.starts_with("--dump-cfg-passes=")) { + ParseDumpCfgPasses(option, Usage); + } else if (option == "--print-pass-options") { + pass_manager_options_.SetPrintPassOptions(true); + } else if (option.starts_with("--pass-options=")) { + ParsePassOptions(option, Usage); + } else if (option.starts_with("--dump-init-failures=")) { + ParseDumpInitFailures(option, Usage); + } else { + // Option not recognized. + return false; + } + return true; +} + } // namespace art diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index 18f215d165..e6acab42f2 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -22,12 +22,12 @@ #include <vector> #include "base/macros.h" +#include "dex/pass_manager.h" #include "globals.h" +#include "utils.h" namespace art { -class PassManagerOptions; - class CompilerOptions FINAL { public: enum CompilerFilter { @@ -53,6 +53,8 @@ class CompilerOptions FINAL { static const bool kDefaultIncludePatchInformation = false; static const size_t kDefaultInlineDepthLimit = 3; static const size_t kDefaultInlineMaxCodeUnits = 20; + static constexpr size_t kUnsetInlineDepthLimit = -1; + static constexpr size_t kUnsetInlineMaxCodeUnits = -1; // Default inlining settings when the space filter is used. static constexpr size_t kSpaceFilterInlineDepthLimit = 3; @@ -78,7 +80,6 @@ class CompilerOptions FINAL { bool implicit_suspend_checks, bool compile_pic, const std::vector<std::string>* verbose_methods, - PassManagerOptions* pass_manager_options, std::ostream* init_failure_output, bool abort_on_hard_verifier_failure); @@ -200,47 +201,64 @@ class CompilerOptions FINAL { } std::ostream* GetInitFailureOutput() const { - return init_failure_output_; + return init_failure_output_.get(); } const PassManagerOptions* GetPassManagerOptions() const { - return pass_manager_options_.get(); + return &pass_manager_options_; } bool AbortOnHardVerifierFailure() const { return abort_on_hard_verifier_failure_; } + bool ParseCompilerOption(const StringPiece& option, UsageFn Usage); + private: + void ParseDumpInitFailures(const StringPiece& option, UsageFn Usage); + void ParsePassOptions(const StringPiece& option, UsageFn Usage); + void ParseDumpCfgPasses(const StringPiece& option, UsageFn Usage); + void ParsePrintPasses(const StringPiece& option, UsageFn Usage); + void ParseDisablePasses(const StringPiece& option, UsageFn Usage); + void ParseInlineMaxCodeUnits(const StringPiece& option, UsageFn Usage); + void ParseInlineDepthLimit(const StringPiece& option, UsageFn Usage); + void ParseNumDexMethods(const StringPiece& option, UsageFn Usage); + void ParseTinyMethodMax(const StringPiece& option, UsageFn Usage); + void ParseSmallMethodMax(const StringPiece& option, UsageFn Usage); + void ParseLargeMethodMax(const StringPiece& option, UsageFn Usage); + void ParseHugeMethodMax(const StringPiece& option, UsageFn Usage); + CompilerFilter compiler_filter_; - const size_t huge_method_threshold_; - const size_t large_method_threshold_; - const size_t small_method_threshold_; - const size_t tiny_method_threshold_; - const size_t num_dex_methods_threshold_; - const size_t inline_depth_limit_; - const size_t inline_max_code_units_; - const bool include_patch_information_; + size_t huge_method_threshold_; + size_t large_method_threshold_; + size_t small_method_threshold_; + size_t tiny_method_threshold_; + size_t num_dex_methods_threshold_; + size_t inline_depth_limit_; + size_t inline_max_code_units_; + bool include_patch_information_; // When using a profile file only the top K% of the profiled samples will be compiled. - const double top_k_profile_threshold_; - const bool debuggable_; - const bool generate_debug_info_; - const bool implicit_null_checks_; - const bool implicit_so_checks_; - const bool implicit_suspend_checks_; - const bool compile_pic_; + double top_k_profile_threshold_; + bool debuggable_; + bool generate_debug_info_; + bool implicit_null_checks_; + bool implicit_so_checks_; + bool implicit_suspend_checks_; + bool compile_pic_; // Vector of methods to have verbose output enabled for. - const std::vector<std::string>* const verbose_methods_; + const std::vector<std::string>* verbose_methods_; - std::unique_ptr<PassManagerOptions> pass_manager_options_; + PassManagerOptions pass_manager_options_; // Abort compilation with an error if we find a class that fails verification with a hard // failure. - const bool abort_on_hard_verifier_failure_; + bool abort_on_hard_verifier_failure_; // Log initialization of initialization failures to this stream if not null. - std::ostream* const init_failure_output_; + std::unique_ptr<std::ostream> init_failure_output_; + + friend class Dex2Oat; DISALLOW_COPY_AND_ASSIGN(CompilerOptions); }; diff --git a/compiler/dwarf/dwarf_test.cc b/compiler/dwarf/dwarf_test.cc index a07d27c1d2..3ba380e9db 100644 --- a/compiler/dwarf/dwarf_test.cc +++ b/compiler/dwarf/dwarf_test.cc @@ -126,7 +126,7 @@ TEST_F(DwarfTest, DebugFrame) { initial_opcodes, kCFIFormat, &debug_frame_data_); std::vector<uintptr_t> debug_frame_patches; std::vector<uintptr_t> expected_patches { 28 }; // NOLINT - WriteDebugFrameFDE(is64bit, 0, 0x01000000, 0x01000000, opcodes.data(), + WriteDebugFrameFDE(is64bit, 0, 0x01000000, 0x01000000, ArrayRef<const uint8_t>(*opcodes.data()), kCFIFormat, &debug_frame_data_, &debug_frame_patches); EXPECT_EQ(expected_patches, debug_frame_patches); @@ -142,7 +142,8 @@ TEST_F(DwarfTest, DebugFrame64) { std::vector<uintptr_t> debug_frame_patches; std::vector<uintptr_t> expected_patches { 32 }; // NOLINT WriteDebugFrameFDE(is64bit, 0, 0x0100000000000000, 0x0200000000000000, - opcodes.data(), kCFIFormat, &debug_frame_data_, &debug_frame_patches); + ArrayRef<const uint8_t>(*opcodes.data()), + kCFIFormat, &debug_frame_data_, &debug_frame_patches); DW_CHECK("FDE cie=00000000 pc=100000000000000..300000000000000"); EXPECT_EQ(expected_patches, debug_frame_patches); @@ -179,7 +180,8 @@ TEST_F(DwarfTest, x86_64_RegisterMapping) { initial_opcodes, kCFIFormat, &debug_frame_data_); std::vector<uintptr_t> debug_frame_patches; WriteDebugFrameFDE(is64bit, 0, 0x0100000000000000, 0x0200000000000000, - opcodes.data(), kCFIFormat, &debug_frame_data_, &debug_frame_patches); + ArrayRef<const uint8_t>(*opcodes.data()), + kCFIFormat, &debug_frame_data_, &debug_frame_patches); CheckObjdumpOutput(is64bit, "-W"); } diff --git a/compiler/dwarf/headers.h b/compiler/dwarf/headers.h index b7eff19bed..f3fba4b1fa 100644 --- a/compiler/dwarf/headers.h +++ b/compiler/dwarf/headers.h @@ -25,6 +25,7 @@ #include "dwarf/dwarf_constants.h" #include "dwarf/register.h" #include "dwarf/writer.h" +#include "utils/array_ref.h" namespace art { namespace dwarf { @@ -70,21 +71,19 @@ void WriteDebugFrameCIE(bool is64bit, writer.PushUint8(DW_EH_PE_absptr | DW_EH_PE_udata4); // R: Pointer encoding. } } - writer.PushData(opcodes.data()); + writer.PushData(*opcodes.data()); writer.Pad(is64bit ? 8 : 4); writer.UpdateUint32(cie_header_start_, writer.data()->size() - cie_header_start_ - 4); } // Write frame description entry (FDE) to .debug_frame or .eh_frame section. -template<typename Vector> +inline void WriteDebugFrameFDE(bool is64bit, size_t cie_offset, uint64_t initial_address, uint64_t address_range, - const Vector* opcodes, + const ArrayRef<const uint8_t>& opcodes, CFIFormat format, std::vector<uint8_t>* debug_frame, std::vector<uintptr_t>* debug_frame_patches) { - static_assert(std::is_same<typename Vector::value_type, uint8_t>::value, "Invalid value type"); - Writer<> writer(debug_frame); size_t fde_header_start = writer.data()->size(); writer.PushUint32(0); // Length placeholder. @@ -125,7 +124,7 @@ void WriteDebugInfoCU(uint32_t debug_abbrev_offset, writer.PushUint32(debug_abbrev_offset); writer.PushUint8(entries.Is64bit() ? 8 : 4); size_t entries_offset = writer.data()->size(); - writer.PushData(entries.data()); + writer.PushData(*entries.data()); writer.UpdateUint32(start, writer.data()->size() - start - 4); // Copy patch locations and make them relative to .debug_info section. for (uintptr_t patch_location : entries.GetPatchLocations()) { @@ -181,7 +180,7 @@ void WriteDebugLineTable(const std::vector<std::string>& include_directories, writer.PushUint8(0); // Terminate file list. writer.UpdateUint32(header_length_pos, writer.data()->size() - header_length_pos - 4); size_t opcodes_offset = writer.data()->size(); - writer.PushData(opcodes.data()); + writer.PushData(*opcodes.data()); writer.UpdateUint32(header_start, writer.data()->size() - header_start - 4); // Copy patch locations and make them relative to .debug_line section. for (uintptr_t patch_location : opcodes.GetPatchLocations()) { diff --git a/compiler/dwarf/writer.h b/compiler/dwarf/writer.h index 42c32c4303..00b9dfa303 100644 --- a/compiler/dwarf/writer.h +++ b/compiler/dwarf/writer.h @@ -17,6 +17,7 @@ #ifndef ART_COMPILER_DWARF_WRITER_H_ #define ART_COMPILER_DWARF_WRITER_H_ +#include <type_traits> #include <vector> #include "base/bit_utils.h" #include "base/logging.h" @@ -119,9 +120,10 @@ class Writer { } template<typename Vector2> - void PushData(const Vector2* buffer) { - static_assert(std::is_same<typename Vector2::value_type, uint8_t>::value, "Invalid value type"); - data_->insert(data_->end(), buffer->begin(), buffer->end()); + void PushData(const Vector2& buffer) { + static_assert(std::is_same<typename std::add_const<typename Vector::value_type>::type, + const uint8_t>::value, "Invalid value type"); + data_->insert(data_->end(), buffer.begin(), buffer.end()); } void UpdateUint32(size_t offset, uint32_t value) { diff --git a/compiler/elf_writer_debug.cc b/compiler/elf_writer_debug.cc index c10ffebbbc..3a9e312225 100644 --- a/compiler/elf_writer_debug.cc +++ b/compiler/elf_writer_debug.cc @@ -182,8 +182,8 @@ void WriteCFISection(const CompilerDriver* compiler, WriteDebugFrameCIE(isa, address_type, format, debug_frame); for (const OatWriter::DebugInfo& mi : method_infos) { if (!mi.deduped_) { // Only one FDE per unique address. - const SwapVector<uint8_t>* opcodes = mi.compiled_method_->GetCFIInfo(); - if (opcodes != nullptr) { + ArrayRef<const uint8_t> opcodes = mi.compiled_method_->GetCFIInfo(); + if (!opcodes.empty()) { address_to_fde_offset_map.emplace(mi.low_pc_, debug_frame->size()); WriteDebugFrameFDE(Is64BitInstructionSet(isa), cie_offset, mi.low_pc_, mi.high_pc_ - mi.low_pc_, diff --git a/compiler/image_test.cc b/compiler/image_test.cc index 7e31a7a08b..fd6cd82f7c 100644 --- a/compiler/image_test.cc +++ b/compiler/image_test.cc @@ -76,6 +76,7 @@ TEST_F(ImageTest, WriteRead) { for (const DexFile* dex_file : class_linker->GetBootClassPath()) { dex_file->EnableWrite(); } + compiler_driver_->SetDexFilesForOatFile(class_linker->GetBootClassPath()); compiler_driver_->CompileAll(class_loader, class_linker->GetBootClassPath(), &timings); t.NewTiming("WriteElf"); @@ -96,8 +97,10 @@ TEST_F(ImageTest, WriteRead) { ASSERT_TRUE(dup_oat.get() != nullptr); { - bool success_image = - writer->Write(image_file.GetFilename(), dup_oat->GetPath(), dup_oat->GetPath()); + bool success_image = writer->Write(kInvalidImageFd, + image_file.GetFilename(), + dup_oat->GetPath(), + dup_oat->GetPath()); ASSERT_TRUE(success_image); bool success_fixup = ElfWriter::Fixup(dup_oat.get(), writer->GetOatDataBegin()); ASSERT_TRUE(success_fixup); diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 4310be6464..af2a4f9426 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -122,7 +122,8 @@ bool ImageWriter::PrepareImageAddressSpace() { return true; } -bool ImageWriter::Write(const std::string& image_filename, +bool ImageWriter::Write(int image_fd, + const std::string& image_filename, const std::string& oat_filename, const std::string& oat_location) { CHECK(!image_filename.empty()); @@ -178,10 +179,13 @@ bool ImageWriter::Write(const std::string& image_filename, LOG(ERROR) << "Failed to flush and close oat file " << oat_filename << " for " << oat_location; return false; } - - std::unique_ptr<File> image_file(OS::CreateEmptyFile(image_filename.c_str())); - ImageHeader* image_header = reinterpret_cast<ImageHeader*>(image_->Begin()); - if (image_file.get() == nullptr) { + std::unique_ptr<File> image_file; + if (image_fd != kInvalidImageFd) { + image_file.reset(new File(image_fd, image_filename, unix_file::kCheckSafeUsage)); + } else { + image_file.reset(OS::CreateEmptyFile(image_filename.c_str())); + } + if (image_file == nullptr) { LOG(ERROR) << "Failed to open image file " << image_filename; return false; } @@ -192,6 +196,7 @@ bool ImageWriter::Write(const std::string& image_filename, } // Write out the image + fields + methods. + ImageHeader* const image_header = reinterpret_cast<ImageHeader*>(image_->Begin()); const auto write_count = image_header->GetImageSize(); if (!image_file->WriteFully(image_->Begin(), write_count)) { PLOG(ERROR) << "Failed to write image file " << image_filename; @@ -200,7 +205,8 @@ bool ImageWriter::Write(const std::string& image_filename, } // Write out the image bitmap at the page aligned start of the image end. - const ImageSection& bitmap_section = image_header->GetImageSection(ImageHeader::kSectionImageBitmap); + const ImageSection& bitmap_section = image_header->GetImageSection( + ImageHeader::kSectionImageBitmap); CHECK_ALIGNED(bitmap_section.Offset(), kPageSize); if (!image_file->Write(reinterpret_cast<char*>(image_bitmap_->Begin()), bitmap_section.Size(), bitmap_section.Offset())) { @@ -796,7 +802,7 @@ void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) { offset, kNativeObjectRelocationTypeArtFieldArray }); offset += header_size; // Forward individual fields so that we can quickly find where they belong. - for (size_t i = 0, count = cur_fields->Length(); i < count; ++i) { + for (size_t i = 0, count = cur_fields->size(); i < count; ++i) { // Need to forward arrays separate of fields. ArtField* field = &cur_fields->At(i); auto it2 = native_object_relocations_.find(field); diff --git a/compiler/image_writer.h b/compiler/image_writer.h index e235bc4553..7a2febcea1 100644 --- a/compiler/image_writer.h +++ b/compiler/image_writer.h @@ -41,6 +41,8 @@ namespace art { +static constexpr int kInvalidImageFd = -1; + // Write a Space built during compilation for use during execution. class ImageWriter FINAL { public: @@ -89,7 +91,11 @@ class ImageWriter FINAL { uint8_t* GetOatFileBegin() const; - bool Write(const std::string& image_filename, const std::string& oat_filename, + // If image_fd is not kInvalidImageFd, then we use that for the file. Otherwise we open + // image_filename. + bool Write(int image_fd, + const std::string& image_filename, + const std::string& oat_filename, const std::string& oat_location) REQUIRES(!Locks::mutator_lock_); diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index 3d1b42f51c..d520208d32 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -63,9 +63,18 @@ extern "C" bool jit_compile_method(void* handle, ArtMethod* method, Thread* self return jit_compiler->CompileMethod(self, method); } +// Callers of this method assume it has NO_RETURN. +NO_RETURN static void Usage(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + std::string error; + StringAppendV(&error, fmt, ap); + LOG(FATAL) << error; + va_end(ap); + exit(EXIT_FAILURE); +} + JitCompiler::JitCompiler() : total_time_(0) { - auto* pass_manager_options = new PassManagerOptions; - pass_manager_options->SetDisablePassList("GVN,DCE,GVNCleanup"); compiler_options_.reset(new CompilerOptions( CompilerOptions::kDefaultCompilerFilter, CompilerOptions::kDefaultHugeMethodThreshold, @@ -84,9 +93,11 @@ JitCompiler::JitCompiler() : total_time_(0) { /* implicit_suspend_checks */ false, /* pic */ true, // TODO: Support non-PIC in optimizing. /* verbose_methods */ nullptr, - pass_manager_options, /* init_failure_output */ nullptr, /* abort_on_hard_verifier_failure */ false)); + for (const std::string& argument : Runtime::Current()->GetCompilerOptions()) { + compiler_options_->ParseCompilerOption(argument, Usage); + } const InstructionSet instruction_set = kRuntimeISA; for (const StringPiece option : Runtime::Current()->GetCompilerOptions()) { VLOG(compiler) << "JIT compiler option " << option; @@ -160,7 +171,7 @@ bool JitCompiler::CompileMethod(Thread* self, ArtMethod* method) { Runtime* runtime = Runtime::Current(); // Check if the method is already compiled. - if (runtime->GetJit()->GetCodeCache()->ContainsMethod(method)) { + if (runtime->GetJit()->GetCodeCache()->ContainsPc(method->GetEntryPointFromQuickCompiledCode())) { VLOG(jit) << "Already compiled " << PrettyMethod(method); return true; } @@ -181,7 +192,10 @@ bool JitCompiler::CompileMethod(Thread* self, ArtMethod* method) { CompiledMethod* compiled_method = nullptr; { TimingLogger::ScopedTiming t2("Compiling", &logger); - compiled_method = compiler_driver_->CompileArtMethod(self, method); + // If we get a request to compile a proxy method, we pass the actual Java method + // of that proxy method, as the compiler does not expect a proxy method. + ArtMethod* method_to_compile = method->GetInterfaceMethodIfProxy(sizeof(void*)); + compiled_method = compiler_driver_->CompileArtMethod(self, method_to_compile); } // Trim maps to reduce memory usage. @@ -207,10 +221,7 @@ bool JitCompiler::CompileMethod(Thread* self, ArtMethod* method) { result = true; } else { TimingLogger::ScopedTiming t2("LinkCode", &logger); - OatFile::OatMethod oat_method(nullptr, 0); - if (AddToCodeCache(method, compiled_method, &oat_method)) { - oat_method.LinkMethod(method); - CHECK(runtime->GetJit()->GetCodeCache()->ContainsMethod(method)) << PrettyMethod(method); + if (AddToCodeCache(method, compiled_method)) { result = true; } } @@ -227,57 +238,57 @@ CompilerCallbacks* JitCompiler::GetCompilerCallbacks() const { } bool JitCompiler::AddToCodeCache(ArtMethod* method, - const CompiledMethod* compiled_method, - OatFile::OatMethod* out_method) { + const CompiledMethod* compiled_method) { Runtime* runtime = Runtime::Current(); JitCodeCache* const code_cache = runtime->GetJit()->GetCodeCache(); - const auto* quick_code = compiled_method->GetQuickCode(); - if (quick_code == nullptr) { + auto const quick_code = compiled_method->GetQuickCode(); + if (quick_code.empty()) { return false; } - const auto code_size = quick_code->size(); + const auto code_size = quick_code.size(); Thread* const self = Thread::Current(); - auto* const mapping_table = compiled_method->GetMappingTable(); - auto* const vmap_table = compiled_method->GetVmapTable(); - auto* const gc_map = compiled_method->GetGcMap(); + auto const mapping_table = compiled_method->GetMappingTable(); + auto const vmap_table = compiled_method->GetVmapTable(); + auto const gc_map = compiled_method->GetGcMap(); uint8_t* mapping_table_ptr = nullptr; uint8_t* vmap_table_ptr = nullptr; uint8_t* gc_map_ptr = nullptr; - if (mapping_table != nullptr) { + if (!mapping_table.empty()) { // Write out pre-header stuff. mapping_table_ptr = code_cache->AddDataArray( - self, mapping_table->data(), mapping_table->data() + mapping_table->size()); + self, mapping_table.data(), mapping_table.data() + mapping_table.size()); if (mapping_table_ptr == nullptr) { return false; // Out of data cache. } } - if (vmap_table != nullptr) { + if (!vmap_table.empty()) { vmap_table_ptr = code_cache->AddDataArray( - self, vmap_table->data(), vmap_table->data() + vmap_table->size()); + self, vmap_table.data(), vmap_table.data() + vmap_table.size()); if (vmap_table_ptr == nullptr) { return false; // Out of data cache. } } - if (gc_map != nullptr) { + if (!gc_map.empty()) { gc_map_ptr = code_cache->AddDataArray( - self, gc_map->data(), gc_map->data() + gc_map->size()); + self, gc_map.data(), gc_map.data() + gc_map.size()); if (gc_map_ptr == nullptr) { return false; // Out of data cache. } } uint8_t* const code = code_cache->CommitCode(self, + method, mapping_table_ptr, vmap_table_ptr, gc_map_ptr, compiled_method->GetFrameSizeInBytes(), compiled_method->GetCoreSpillMask(), compiled_method->GetFpSpillMask(), - compiled_method->GetQuickCode()->data(), - compiled_method->GetQuickCode()->size()); + compiled_method->GetQuickCode().data(), + compiled_method->GetQuickCode().size()); if (code == nullptr) { return false; @@ -285,13 +296,6 @@ bool JitCompiler::AddToCodeCache(ArtMethod* method, const size_t thumb_offset = compiled_method->CodeDelta(); const uint32_t code_offset = sizeof(OatQuickMethodHeader) + thumb_offset; - *out_method = OatFile::OatMethod(code, code_offset); - DCHECK_EQ(out_method->GetGcMap(), gc_map_ptr); - DCHECK_EQ(out_method->GetMappingTable(), mapping_table_ptr); - DCHECK_EQ(out_method->GetVmapTable(), vmap_table_ptr); - DCHECK_EQ(out_method->GetFrameSizeInBytes(), compiled_method->GetFrameSizeInBytes()); - DCHECK_EQ(out_method->GetCoreSpillMask(), compiled_method->GetCoreSpillMask()); - DCHECK_EQ(out_method->GetFpSpillMask(), compiled_method->GetFpSpillMask()); VLOG(jit) << "JIT added " << PrettyMethod(method) << "@" << method diff --git a/compiler/jit/jit_compiler.h b/compiler/jit/jit_compiler.h index 757f3f386a..913a6d00ae 100644 --- a/compiler/jit/jit_compiler.h +++ b/compiler/jit/jit_compiler.h @@ -59,8 +59,8 @@ class JitCompiler { // This is in the compiler since the runtime doesn't have access to the compiled method // structures. bool AddToCodeCache(ArtMethod* method, - const CompiledMethod* compiled_method, - OatFile::OatMethod* out_method) SHARED_REQUIRES(Locks::mutator_lock_); + const CompiledMethod* compiled_method) + SHARED_REQUIRES(Locks::mutator_lock_); DISALLOW_COPY_AND_ASSIGN(JitCompiler); }; diff --git a/compiler/jni/quick/jni_compiler.cc b/compiler/jni/quick/jni_compiler.cc index 34f0802444..52a238233b 100644 --- a/compiler/jni/quick/jni_compiler.cc +++ b/compiler/jni/quick/jni_compiler.cc @@ -487,7 +487,7 @@ CompiledMethod* ArtJniCompileMethodInternal(CompilerDriver* driver, frame_size, main_jni_conv->CoreSpillMask(), main_jni_conv->FpSpillMask(), - nullptr, // src_mapping_table. + ArrayRef<const SrcMapElem>(), ArrayRef<const uint8_t>(), // mapping_table. ArrayRef<const uint8_t>(), // vmap_table. ArrayRef<const uint8_t>(), // native_gc_map. diff --git a/compiler/linker/arm/relative_patcher_arm_base.cc b/compiler/linker/arm/relative_patcher_arm_base.cc index cb9ea38b1c..13754fdaa1 100644 --- a/compiler/linker/arm/relative_patcher_arm_base.cc +++ b/compiler/linker/arm/relative_patcher_arm_base.cc @@ -36,7 +36,8 @@ uint32_t ArmBaseRelativePatcher::ReserveSpaceEnd(uint32_t offset) { // of code. To avoid any alignment discrepancies for the final chunk, we always align the // offset after reserving of writing any chunk. uint32_t aligned_offset = CompiledMethod::AlignCode(offset, instruction_set_); - bool needs_thunk = ReserveSpaceProcessPatches(aligned_offset, MethodReference(nullptr, 0u), + bool needs_thunk = ReserveSpaceProcessPatches(aligned_offset, + MethodReference(nullptr, 0u), aligned_offset); if (needs_thunk) { thunk_locations_.push_back(aligned_offset); @@ -85,8 +86,7 @@ uint32_t ArmBaseRelativePatcher::ReserveSpaceInternal(uint32_t offset, const CompiledMethod* compiled_method, MethodReference method_ref, uint32_t max_extra_space) { - DCHECK(compiled_method->GetQuickCode() != nullptr); - uint32_t quick_code_size = compiled_method->GetQuickCode()->size(); + uint32_t quick_code_size = compiled_method->GetQuickCode().size(); uint32_t quick_code_offset = compiled_method->AlignCode(offset) + sizeof(OatQuickMethodHeader); uint32_t next_aligned_offset = compiled_method->AlignCode(quick_code_offset + quick_code_size); // Adjust for extra space required by the subclass. @@ -95,7 +95,8 @@ uint32_t ArmBaseRelativePatcher::ReserveSpaceInternal(uint32_t offset, // We need the MethodReference for that. if (!unprocessed_patches_.empty() && next_aligned_offset - unprocessed_patches_.front().second > max_positive_displacement_) { - bool needs_thunk = ReserveSpaceProcessPatches(quick_code_offset, method_ref, + bool needs_thunk = ReserveSpaceProcessPatches(quick_code_offset, + method_ref, next_aligned_offset); if (needs_thunk) { // A single thunk will cover all pending patches. @@ -157,7 +158,10 @@ bool ArmBaseRelativePatcher::ReserveSpaceProcessPatches(uint32_t quick_code_offs // If still unresolved, check if we have a thunk within range. if (thunk_locations_.empty() || patch_offset - thunk_locations_.back() > max_negative_displacement_) { - return next_aligned_offset - patch_offset > max_positive_displacement_; + // No thunk in range, we need a thunk if the next aligned offset + // is out of range, or if we're at the end of all code. + return (next_aligned_offset - patch_offset > max_positive_displacement_) || + (quick_code_offset == next_aligned_offset); // End of code. } } else { uint32_t target_offset = result.second - CompiledCode::CodeDelta(instruction_set_); diff --git a/compiler/linker/arm/relative_patcher_thumb2_test.cc b/compiler/linker/arm/relative_patcher_thumb2_test.cc index 551531314a..a259cda986 100644 --- a/compiler/linker/arm/relative_patcher_thumb2_test.cc +++ b/compiler/linker/arm/relative_patcher_thumb2_test.cc @@ -233,6 +233,36 @@ TEST_F(Thumb2RelativePatcherTest, CallTrampoline) { EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); } +TEST_F(Thumb2RelativePatcherTest, CallTrampolineTooFar) { + constexpr uint32_t missing_method_index = 1024u; + auto method3_raw_code = GenNopsAndBl(3u, kBlPlus0); + constexpr uint32_t bl_offset_in_method3 = 3u * 2u; // After NOPs. + ArrayRef<const uint8_t> method3_code(method3_raw_code); + ASSERT_EQ(bl_offset_in_method3 + 4u, method3_code.size()); + LinkerPatch method3_patches[] = { + LinkerPatch::RelativeCodePatch(bl_offset_in_method3, nullptr, missing_method_index), + }; + + constexpr uint32_t just_over_max_negative_disp = 16 * MB + 2 - 4u /* PC adjustment */; + bool thunk_in_gap = Create2MethodsWithGap(kNopCode, + ArrayRef<const LinkerPatch>(), + method3_code, + ArrayRef<const LinkerPatch>(method3_patches), + just_over_max_negative_disp - bl_offset_in_method3); + ASSERT_FALSE(thunk_in_gap); // There should be a thunk but it should be after the method2. + ASSERT_FALSE(method_offset_map_.FindMethodOffset(MethodRef(missing_method_index)).first); + + // Check linked code. + uint32_t method3_offset = GetMethodOffset(3u); + uint32_t thunk_offset = CompiledCode::AlignCode(method3_offset + method3_code.size(), kThumb2); + uint32_t diff = thunk_offset - (method3_offset + bl_offset_in_method3 + 4u /* PC adjustment */); + ASSERT_EQ(diff & 1u, 0u); + ASSERT_LT(diff >> 1, 1u << 8); // Simple encoding, (diff >> 1) fits into 8 bits. + auto expected_code = GenNopsAndBl(3u, kBlPlus0 | ((diff >> 1) & 0xffu)); + EXPECT_TRUE(CheckLinkedMethod(MethodRef(3u), ArrayRef<const uint8_t>(expected_code))); + EXPECT_TRUE(CheckThunk(thunk_offset)); +} + TEST_F(Thumb2RelativePatcherTest, CallOtherAlmostTooFarAfter) { auto method1_raw_code = GenNopsAndBl(3u, kBlPlus0); constexpr uint32_t bl_offset_in_method1 = 3u * 2u; // After NOPs. diff --git a/compiler/linker/arm64/relative_patcher_arm64.cc b/compiler/linker/arm64/relative_patcher_arm64.cc index 6f234a8367..57018af840 100644 --- a/compiler/linker/arm64/relative_patcher_arm64.cc +++ b/compiler/linker/arm64/relative_patcher_arm64.cc @@ -74,7 +74,7 @@ uint32_t Arm64RelativePatcher::ReserveSpace(uint32_t offset, // Now that we have the actual offset where the code will be placed, locate the ADRP insns // that actually require the thunk. uint32_t quick_code_offset = compiled_method->AlignCode(offset) + sizeof(OatQuickMethodHeader); - ArrayRef<const uint8_t> code(*compiled_method->GetQuickCode()); + ArrayRef<const uint8_t> code = compiled_method->GetQuickCode(); uint32_t thunk_offset = compiled_method->AlignCode(quick_code_offset + code.size()); DCHECK(compiled_method != nullptr); for (const LinkerPatch& patch : compiled_method->GetPatches()) { diff --git a/compiler/linker/arm64/relative_patcher_arm64_test.cc b/compiler/linker/arm64/relative_patcher_arm64_test.cc index 857d5842b5..0bfef5e6d3 100644 --- a/compiler/linker/arm64/relative_patcher_arm64_test.cc +++ b/compiler/linker/arm64/relative_patcher_arm64_test.cc @@ -237,7 +237,7 @@ class Arm64RelativePatcherTest : public RelativePatcherTest { CHECK(!compiled_method_refs_.empty()); CHECK_EQ(compiled_method_refs_[0].dex_method_index, 1u); CHECK_EQ(compiled_method_refs_.size(), compiled_methods_.size()); - uint32_t method1_size = compiled_methods_[0]->GetQuickCode()->size(); + uint32_t method1_size = compiled_methods_[0]->GetQuickCode().size(); uint32_t thunk_offset = CompiledCode::AlignCode(method1_offset + method1_size, kArm64); uint32_t b_diff = thunk_offset - (method1_offset + num_nops * 4u); ASSERT_EQ(b_diff & 3u, 0u); @@ -386,6 +386,39 @@ TEST_F(Arm64RelativePatcherTestDefault, CallTrampoline) { EXPECT_TRUE(CheckLinkedMethod(MethodRef(1u), ArrayRef<const uint8_t>(expected_code))); } +TEST_F(Arm64RelativePatcherTestDefault, CallTrampolineTooFar) { + constexpr uint32_t missing_method_index = 1024u; + auto last_method_raw_code = GenNopsAndBl(1u, kBlPlus0); + constexpr uint32_t bl_offset_in_last_method = 1u * 4u; // After NOPs. + ArrayRef<const uint8_t> last_method_code(last_method_raw_code); + ASSERT_EQ(bl_offset_in_last_method + 4u, last_method_code.size()); + LinkerPatch last_method_patches[] = { + LinkerPatch::RelativeCodePatch(bl_offset_in_last_method, nullptr, missing_method_index), + }; + + constexpr uint32_t just_over_max_negative_disp = 128 * MB + 4; + uint32_t last_method_idx = Create2MethodsWithGap( + kNopCode, ArrayRef<const LinkerPatch>(), last_method_code, + ArrayRef<const LinkerPatch>(last_method_patches), + just_over_max_negative_disp - bl_offset_in_last_method); + uint32_t method1_offset = GetMethodOffset(1u); + uint32_t last_method_offset = GetMethodOffset(last_method_idx); + ASSERT_EQ(method1_offset, + last_method_offset + bl_offset_in_last_method - just_over_max_negative_disp); + ASSERT_FALSE(method_offset_map_.FindMethodOffset(MethodRef(missing_method_index)).first); + + // Check linked code. + uint32_t thunk_offset = + CompiledCode::AlignCode(last_method_offset + last_method_code.size(), kArm64); + uint32_t diff = thunk_offset - (last_method_offset + bl_offset_in_last_method); + ASSERT_EQ(diff & 3u, 0u); + ASSERT_LT(diff, 128 * MB); + auto expected_code = GenNopsAndBl(1u, kBlPlus0 | (diff >> 2)); + EXPECT_TRUE(CheckLinkedMethod(MethodRef(last_method_idx), + ArrayRef<const uint8_t>(expected_code))); + EXPECT_TRUE(CheckThunk(thunk_offset)); +} + TEST_F(Arm64RelativePatcherTestDefault, CallOtherAlmostTooFarAfter) { auto method1_raw_code = GenNopsAndBl(1u, kBlPlus0); constexpr uint32_t bl_offset_in_method1 = 1u * 4u; // After NOPs. diff --git a/compiler/linker/relative_patcher_test.h b/compiler/linker/relative_patcher_test.h index e357662388..92cf8ca7ff 100644 --- a/compiler/linker/relative_patcher_test.h +++ b/compiler/linker/relative_patcher_test.h @@ -74,8 +74,8 @@ class RelativePatcherTest : public testing::Test { compiled_method_refs_.push_back(method_ref); compiled_methods_.emplace_back(new CompiledMethod( &driver_, instruction_set_, code, - 0u, 0u, 0u, nullptr, ArrayRef<const uint8_t>(), ArrayRef<const uint8_t>(), - ArrayRef<const uint8_t>(), ArrayRef<const uint8_t>(), + 0u, 0u, 0u, ArrayRef<const SrcMapElem>(), ArrayRef<const uint8_t>(), + ArrayRef<const uint8_t>(), ArrayRef<const uint8_t>(), ArrayRef<const uint8_t>(), patches)); } @@ -93,7 +93,7 @@ class RelativePatcherTest : public testing::Test { offset += sizeof(OatQuickMethodHeader); uint32_t quick_code_offset = offset + compiled_method->CodeDelta(); - const auto& code = *compiled_method->GetQuickCode(); + const auto code = compiled_method->GetQuickCode(); offset += code.size(); method_offset_map_.map.Put(compiled_method_refs_[idx], quick_code_offset); @@ -125,7 +125,7 @@ class RelativePatcherTest : public testing::Test { out_.WriteFully(dummy_header, sizeof(OatQuickMethodHeader)); offset += sizeof(OatQuickMethodHeader); - ArrayRef<const uint8_t> code(*compiled_method->GetQuickCode()); + ArrayRef<const uint8_t> code = compiled_method->GetQuickCode(); if (!compiled_method->GetPatches().empty()) { patched_code_.assign(code.begin(), code.end()); code = ArrayRef<const uint8_t>(patched_code_); @@ -164,7 +164,7 @@ class RelativePatcherTest : public testing::Test { ++idx; } CHECK_NE(idx, compiled_method_refs_.size()); - CHECK_EQ(compiled_methods_[idx]->GetQuickCode()->size(), expected_code.size()); + CHECK_EQ(compiled_methods_[idx]->GetQuickCode().size(), expected_code.size()); auto result = method_offset_map_.FindMethodOffset(method_ref); CHECK(result.first); // Must have been linked. diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc index 2d9d91a7ee..ea3cb667e2 100644 --- a/compiler/oat_test.cc +++ b/compiler/oat_test.cc @@ -63,9 +63,9 @@ class OatTest : public CommonCompilerTest { EXPECT_EQ(oat_method.GetFpSpillMask(), compiled_method->GetFpSpillMask()); uintptr_t oat_code_aligned = RoundDown(reinterpret_cast<uintptr_t>(quick_oat_code), 2); quick_oat_code = reinterpret_cast<const void*>(oat_code_aligned); - const SwapVector<uint8_t>* quick_code = compiled_method->GetQuickCode(); - EXPECT_TRUE(quick_code != nullptr); - size_t code_size = quick_code->size() * sizeof(quick_code[0]); + ArrayRef<const uint8_t> quick_code = compiled_method->GetQuickCode(); + EXPECT_FALSE(quick_code.empty()); + size_t code_size = quick_code.size() * sizeof(quick_code[0]); EXPECT_EQ(0, memcmp(quick_oat_code, &quick_code[0], code_size)) << PrettyMethod(method) << " " << code_size; CHECK_EQ(0, memcmp(quick_oat_code, &quick_code[0], code_size)); @@ -98,6 +98,7 @@ TEST_F(OatTest, WriteRead) { jobject class_loader = nullptr; if (kCompile) { TimingLogger timings2("OatTest::WriteRead", false, false); + compiler_driver_->SetDexFilesForOatFile(class_linker->GetBootClassPath()); compiler_driver_->CompileAll(class_loader, class_linker->GetBootClassPath(), &timings2); } diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index 640698b0e9..c7b8884214 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -33,6 +33,7 @@ #include "driver/compiler_options.h" #include "gc/space/image_space.h" #include "gc/space/space.h" +#include "handle_scope-inl.h" #include "image_writer.h" #include "linker/relative_patcher.h" #include "mirror/array.h" @@ -44,7 +45,7 @@ #include "output_stream.h" #include "safe_map.h" #include "scoped_thread_state_change.h" -#include "handle_scope-inl.h" +#include "type_lookup_table.h" #include "utils/dex_cache_arrays_layout-inl.h" #include "verifier/method_verifier.h" @@ -107,6 +108,9 @@ OatWriter::OatWriter(const std::vector<const DexFile*>& dex_files, size_oat_class_status_(0), size_oat_class_method_bitmaps_(0), size_oat_class_method_offsets_(0), + size_oat_lookup_table_alignment_(0), + size_oat_lookup_table_offset_(0), + size_oat_lookup_table_(0), method_offset_map_() { CHECK(key_value_store != nullptr); @@ -129,6 +133,10 @@ OatWriter::OatWriter(const std::vector<const DexFile*>& dex_files, offset = InitDexFiles(offset); } { + TimingLogger::ScopedTiming split("InitLookupTables", timings); + offset = InitLookupTables(offset); + } + { TimingLogger::ScopedTiming split("InitOatClasses", timings); offset = InitOatClasses(offset); } @@ -172,7 +180,7 @@ OatWriter::~OatWriter() { } struct OatWriter::GcMapDataAccess { - static const SwapVector<uint8_t>* GetData(const CompiledMethod* compiled_method) ALWAYS_INLINE { + static ArrayRef<const uint8_t> GetData(const CompiledMethod* compiled_method) ALWAYS_INLINE { return compiled_method->GetGcMap(); } @@ -194,7 +202,7 @@ struct OatWriter::GcMapDataAccess { }; struct OatWriter::MappingTableDataAccess { - static const SwapVector<uint8_t>* GetData(const CompiledMethod* compiled_method) ALWAYS_INLINE { + static ArrayRef<const uint8_t> GetData(const CompiledMethod* compiled_method) ALWAYS_INLINE { return compiled_method->GetMappingTable(); } @@ -216,7 +224,7 @@ struct OatWriter::MappingTableDataAccess { }; struct OatWriter::VmapTableDataAccess { - static const SwapVector<uint8_t>* GetData(const CompiledMethod* compiled_method) ALWAYS_INLINE { + static ArrayRef<const uint8_t> GetData(const CompiledMethod* compiled_method) ALWAYS_INLINE { return compiled_method->GetVmapTable(); } @@ -322,7 +330,8 @@ class OatWriter::InitOatClassesMethodVisitor : public DexMethodVisitor { return true; } - bool VisitMethod(size_t class_def_method_index ATTRIBUTE_UNUSED, const ClassDataItemIterator& it) { + bool VisitMethod(size_t class_def_method_index ATTRIBUTE_UNUSED, + const ClassDataItemIterator& it) { // Fill in the compiled_methods_ array for methods that have a // CompiledMethod. We track the number of non-null entries in // num_non_null_compiled_methods_ since we only want to allocate @@ -388,8 +397,8 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { // Derived from CompiledMethod. uint32_t quick_code_offset = 0; - const SwapVector<uint8_t>* quick_code = compiled_method->GetQuickCode(); - uint32_t code_size = quick_code->size() * sizeof(uint8_t); + ArrayRef<const uint8_t> quick_code = compiled_method->GetQuickCode(); + uint32_t code_size = quick_code.size() * sizeof(uint8_t); uint32_t thumb_offset = compiled_method->CodeDelta(); // Deduplicate code arrays if we are not producing debuggable code. @@ -428,7 +437,7 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { uint32_t vmap_table_offset = method_header->vmap_table_offset_; // If we don't have quick code, then we must have a vmap, as that is how the dex2dex // compiler records its transformations. - DCHECK(quick_code != nullptr || vmap_table_offset != 0); + DCHECK(!quick_code.empty() || vmap_table_offset != 0); uint32_t gc_map_offset = method_header->gc_map_offset_; // The code offset was 0 when the mapping/vmap table offset was set, so it's set // to 0-offset and we need to adjust it by code_offset. @@ -496,12 +505,12 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { } else { status = mirror::Class::kStatusNotReady; } - const SwapVector<uint8_t>* gc_map = compiled_method->GetGcMap(); - if (gc_map != nullptr) { - size_t gc_map_size = gc_map->size() * sizeof(gc_map[0]); + ArrayRef<const uint8_t> gc_map = compiled_method->GetGcMap(); + if (!gc_map.empty()) { + size_t gc_map_size = gc_map.size() * sizeof(gc_map[0]); bool is_native = it.MemberIsNative(); CHECK(gc_map_size != 0 || is_native || status < mirror::Class::kStatusVerified) - << gc_map << " " << gc_map_size << " " << (is_native ? "true" : "false") << " " + << gc_map_size << " " << (is_native ? "true" : "false") << " " << (status < mirror::Class::kStatusVerified) << " " << status << " " << PrettyMethod(it.GetMemberIndex(), *dex_file_); } @@ -519,30 +528,22 @@ class OatWriter::InitCodeMethodVisitor : public OatDexMethodVisitor { private: struct CodeOffsetsKeyComparator { bool operator()(const CompiledMethod* lhs, const CompiledMethod* rhs) const { - if (lhs->GetQuickCode() != rhs->GetQuickCode()) { - return lhs->GetQuickCode() < rhs->GetQuickCode(); + // Code is deduplicated by CompilerDriver, compare only data pointers. + if (lhs->GetQuickCode().data() != rhs->GetQuickCode().data()) { + return lhs->GetQuickCode().data() < rhs->GetQuickCode().data(); } // If the code is the same, all other fields are likely to be the same as well. - if (UNLIKELY(lhs->GetMappingTable() != rhs->GetMappingTable())) { - return lhs->GetMappingTable() < rhs->GetMappingTable(); + if (UNLIKELY(lhs->GetMappingTable().data() != rhs->GetMappingTable().data())) { + return lhs->GetMappingTable().data() < rhs->GetMappingTable().data(); } - if (UNLIKELY(lhs->GetVmapTable() != rhs->GetVmapTable())) { - return lhs->GetVmapTable() < rhs->GetVmapTable(); + if (UNLIKELY(lhs->GetVmapTable().data() != rhs->GetVmapTable().data())) { + return lhs->GetVmapTable().data() < rhs->GetVmapTable().data(); } - if (UNLIKELY(lhs->GetGcMap() != rhs->GetGcMap())) { - return lhs->GetGcMap() < rhs->GetGcMap(); + if (UNLIKELY(lhs->GetGcMap().data() != rhs->GetGcMap().data())) { + return lhs->GetGcMap().data() < rhs->GetGcMap().data(); } - const auto& lhs_patches = lhs->GetPatches(); - const auto& rhs_patches = rhs->GetPatches(); - if (UNLIKELY(lhs_patches.size() != rhs_patches.size())) { - return lhs_patches.size() < rhs_patches.size(); - } - auto rit = rhs_patches.begin(); - for (const LinkerPatch& lpatch : lhs_patches) { - if (UNLIKELY(!(lpatch == *rit))) { - return lpatch < *rit; - } - ++rit; + if (UNLIKELY(lhs->GetPatches().data() != rhs->GetPatches().data())) { + return lhs->GetPatches().data() < rhs->GetPatches().data(); } return false; } @@ -583,17 +584,17 @@ class OatWriter::InitMapMethodVisitor : public OatDexMethodVisitor { DCHECK_LT(method_offsets_index_, oat_class->method_offsets_.size()); DCHECK_EQ(DataAccess::GetOffset(oat_class, method_offsets_index_), 0u); - const SwapVector<uint8_t>* map = DataAccess::GetData(compiled_method); - uint32_t map_size = map == nullptr ? 0 : map->size() * sizeof((*map)[0]); + ArrayRef<const uint8_t> map = DataAccess::GetData(compiled_method); + uint32_t map_size = map.size() * sizeof(map[0]); if (map_size != 0u) { - auto lb = dedupe_map_.lower_bound(map); - if (lb != dedupe_map_.end() && !dedupe_map_.key_comp()(map, lb->first)) { + auto lb = dedupe_map_.lower_bound(map.data()); + if (lb != dedupe_map_.end() && !dedupe_map_.key_comp()(map.data(), lb->first)) { DataAccess::SetOffset(oat_class, method_offsets_index_, lb->second); } else { DataAccess::SetOffset(oat_class, method_offsets_index_, offset_); - dedupe_map_.PutBefore(lb, map, offset_); + dedupe_map_.PutBefore(lb, map.data(), offset_); offset_ += map_size; - writer_->oat_header_->UpdateChecksum(&(*map)[0], map_size); + writer_->oat_header_->UpdateChecksum(&map[0], map_size); } } ++method_offsets_index_; @@ -605,7 +606,7 @@ class OatWriter::InitMapMethodVisitor : public OatDexMethodVisitor { private: // Deduplication is already done on a pointer basis by the compiler driver, // so we can simply compare the pointers to find out if things are duplicated. - SafeMap<const SwapVector<uint8_t>*, uint32_t> dedupe_map_; + SafeMap<const uint8_t*, uint32_t> dedupe_map_; }; class OatWriter::InitImageMethodVisitor : public OatDexMethodVisitor { @@ -647,7 +648,7 @@ class OatWriter::InitImageMethodVisitor : public OatDexMethodVisitor { UNREACHABLE(); } - if (compiled_method != nullptr && compiled_method->GetQuickCode()->size() != 0) { + if (compiled_method != nullptr && compiled_method->GetQuickCode().size() != 0) { method->SetEntryPointFromQuickCompiledCodePtrSize( reinterpret_cast<void*>(offsets.code_offset_), pointer_size_); } @@ -713,10 +714,8 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { size_t file_offset = file_offset_; OutputStream* out = out_; - const SwapVector<uint8_t>* quick_code = compiled_method->GetQuickCode(); - // Need a wrapper if we create a copy for patching. - ArrayRef<const uint8_t> wrapped(*quick_code); - uint32_t code_size = quick_code->size() * sizeof(uint8_t); + ArrayRef<const uint8_t> quick_code = compiled_method->GetQuickCode(); + uint32_t code_size = quick_code.size() * sizeof(uint8_t); // Deduplicate code arrays. const OatMethodOffsets& method_offsets = oat_class->method_offsets_[method_offsets_index_]; @@ -753,8 +752,8 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { DCHECK_OFFSET_(); if (!compiled_method->GetPatches().empty()) { - patched_code_.assign(quick_code->begin(), quick_code->end()); - wrapped = ArrayRef<const uint8_t>(patched_code_); + patched_code_.assign(quick_code.begin(), quick_code.end()); + quick_code = ArrayRef<const uint8_t>(patched_code_); for (const LinkerPatch& patch : compiled_method->GetPatches()) { if (patch.Type() == kLinkerPatchCallRelative) { // NOTE: Relative calls across oat files are not supported. @@ -781,8 +780,8 @@ class OatWriter::WriteCodeMethodVisitor : public OatDexMethodVisitor { } } - writer_->oat_header_->UpdateChecksum(wrapped.data(), code_size); - if (!out->WriteFully(wrapped.data(), code_size)) { + writer_->oat_header_->UpdateChecksum(quick_code.data(), code_size); + if (!out->WriteFully(quick_code.data(), code_size)) { ReportWriteFailure("method code", it); return false; } @@ -947,14 +946,14 @@ class OatWriter::WriteMapMethodVisitor : public OatDexMethodVisitor { ++method_offsets_index_; // Write deduplicated map. - const SwapVector<uint8_t>* map = DataAccess::GetData(compiled_method); - size_t map_size = map == nullptr ? 0 : map->size() * sizeof((*map)[0]); + ArrayRef<const uint8_t> map = DataAccess::GetData(compiled_method); + size_t map_size = map.size() * sizeof(map[0]); DCHECK((map_size == 0u && map_offset == 0u) || (map_size != 0u && map_offset != 0u && map_offset <= offset_)) << map_size << " " << map_offset << " " << offset_ << " " << PrettyMethod(it.GetMemberIndex(), *dex_file_) << " for " << DataAccess::Name(); if (map_size != 0u && map_offset == offset_) { - if (UNLIKELY(!out->WriteFully(&(*map)[0], map_size))) { + if (UNLIKELY(!out->WriteFully(&map[0], map_size))) { ReportWriteFailure(it); return false; } @@ -1053,11 +1052,29 @@ size_t OatWriter::InitDexFiles(size_t offset) { oat_dex_files_[i]->dex_file_offset_ = offset; const DexFile* dex_file = (*dex_files_)[i]; + + // Initialize type lookup table + oat_dex_files_[i]->lookup_table_ = dex_file->GetTypeLookupTable(); + offset += dex_file->GetHeader().file_size_; } return offset; } +size_t OatWriter::InitLookupTables(size_t offset) { + for (OatDexFile* oat_dex_file : oat_dex_files_) { + if (oat_dex_file->lookup_table_ != nullptr) { + uint32_t aligned_offset = RoundUp(offset, 4); + oat_dex_file->lookup_table_offset_ = aligned_offset; + size_oat_lookup_table_alignment_ += aligned_offset - offset; + offset = aligned_offset + oat_dex_file->lookup_table_->RawDataLength(); + } else { + oat_dex_file->lookup_table_offset_ = 0; + } + } + return offset; +} + size_t OatWriter::InitOatClasses(size_t offset) { // calculate the offsets within OatDexFiles to OatClasses InitOatClassesMethodVisitor visitor(this, offset); @@ -1266,6 +1283,9 @@ bool OatWriter::WriteCode(OutputStream* out) { DO_STAT(size_oat_class_status_); DO_STAT(size_oat_class_method_bitmaps_); DO_STAT(size_oat_class_method_offsets_); + DO_STAT(size_oat_lookup_table_alignment_); + DO_STAT(size_oat_lookup_table_offset_); + DO_STAT(size_oat_lookup_table_); #undef DO_STAT VLOG(compiler) << "size_total=" << PrettySize(size_total) << " (" << size_total << "B)"; \ @@ -1319,6 +1339,9 @@ bool OatWriter::WriteTables(OutputStream* out, const size_t file_offset) { } size_dex_file_ += dex_file->GetHeader().file_size_; } + if (!WriteLookupTables(out, file_offset)) { + return false; + } for (size_t i = 0; i != oat_classes_.size(); ++i) { if (!oat_classes_[i]->Write(this, out, file_offset)) { PLOG(ERROR) << "Failed to write oat methods information to " << out->GetLocation(); @@ -1328,6 +1351,35 @@ bool OatWriter::WriteTables(OutputStream* out, const size_t file_offset) { return true; } +bool OatWriter::WriteLookupTables(OutputStream* out, const size_t file_offset) { + for (size_t i = 0; i < oat_dex_files_.size(); ++i) { + const uint32_t lookup_table_offset = oat_dex_files_[i]->lookup_table_offset_; + const TypeLookupTable* table = oat_dex_files_[i]->lookup_table_; + DCHECK_EQ(lookup_table_offset == 0, table == nullptr); + if (lookup_table_offset == 0) { + continue; + } + const uint32_t expected_offset = file_offset + lookup_table_offset; + off_t actual_offset = out->Seek(expected_offset, kSeekSet); + if (static_cast<uint32_t>(actual_offset) != expected_offset) { + const DexFile* dex_file = (*dex_files_)[i]; + PLOG(ERROR) << "Failed to seek to lookup table section. Actual: " << actual_offset + << " Expected: " << expected_offset << " File: " << dex_file->GetLocation(); + return false; + } + if (table != nullptr) { + if (!out->WriteFully(table->RawData(), table->RawDataLength())) { + const DexFile* dex_file = (*dex_files_)[i]; + PLOG(ERROR) << "Failed to write lookup table for " << dex_file->GetLocation() + << " to " << out->GetLocation(); + return false; + } + size_oat_lookup_table_ += table->RawDataLength(); + } + } + return true; +} + size_t OatWriter::WriteMaps(OutputStream* out, const size_t file_offset, size_t relative_offset) { #define VISIT(VisitorType) \ do { \ @@ -1435,6 +1487,7 @@ OatWriter::OatDexFile::OatDexFile(size_t offset, const DexFile& dex_file) { dex_file_location_data_ = reinterpret_cast<const uint8_t*>(location.data()); dex_file_location_checksum_ = dex_file.GetLocationChecksum(); dex_file_offset_ = 0; + lookup_table_offset_ = 0; methods_offsets_.resize(dex_file.NumClassDefs()); } @@ -1443,6 +1496,7 @@ size_t OatWriter::OatDexFile::SizeOf() const { + dex_file_location_size_ + sizeof(dex_file_location_checksum_) + sizeof(dex_file_offset_) + + sizeof(lookup_table_offset_) + (sizeof(methods_offsets_[0]) * methods_offsets_.size()); } @@ -1451,6 +1505,10 @@ void OatWriter::OatDexFile::UpdateChecksum(OatHeader* oat_header) const { oat_header->UpdateChecksum(dex_file_location_data_, dex_file_location_size_); oat_header->UpdateChecksum(&dex_file_location_checksum_, sizeof(dex_file_location_checksum_)); oat_header->UpdateChecksum(&dex_file_offset_, sizeof(dex_file_offset_)); + oat_header->UpdateChecksum(&lookup_table_offset_, sizeof(lookup_table_offset_)); + if (lookup_table_ != nullptr) { + oat_header->UpdateChecksum(lookup_table_->RawData(), lookup_table_->RawDataLength()); + } oat_header->UpdateChecksum(&methods_offsets_[0], sizeof(methods_offsets_[0]) * methods_offsets_.size()); } @@ -1479,6 +1537,11 @@ bool OatWriter::OatDexFile::Write(OatWriter* oat_writer, return false; } oat_writer->size_oat_dex_file_offset_ += sizeof(dex_file_offset_); + if (!out->WriteFully(&lookup_table_offset_, sizeof(lookup_table_offset_))) { + PLOG(ERROR) << "Failed to write lookup table offset to " << out->GetLocation(); + return false; + } + oat_writer->size_oat_lookup_table_offset_ += sizeof(lookup_table_offset_); if (!out->WriteFully(&methods_offsets_[0], sizeof(methods_offsets_[0]) * methods_offsets_.size())) { PLOG(ERROR) << "Failed to write methods offsets to " << out->GetLocation(); diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h index d6cb65bd64..f2fe048174 100644 --- a/compiler/oat_writer.h +++ b/compiler/oat_writer.h @@ -24,8 +24,8 @@ #include "linker/relative_patcher.h" // For linker::RelativePatcherTargetProvider. #include "mem_map.h" #include "method_reference.h" -#include "oat.h" #include "mirror/class.h" +#include "oat.h" #include "safe_map.h" namespace art { @@ -36,6 +36,7 @@ class CompilerDriver; class ImageWriter; class OutputStream; class TimingLogger; +class TypeLookupTable; // OatHeader variable length with count of D OatDexFiles // @@ -49,6 +50,11 @@ class TimingLogger; // ... // Dex[D] // +// TypeLookupTable[0] one descriptor to class def index hash table for each OatDexFile. +// TypeLookupTable[1] +// ... +// TypeLookupTable[D] +// // OatClass[0] one variable sized OatClass for each of C DexFile::ClassDefs // OatClass[1] contains OatClass entries with class status, offsets to code, etc. // ... @@ -168,6 +174,7 @@ class OatWriter { size_t InitOatHeader(); size_t InitOatDexFiles(size_t offset); + size_t InitLookupTables(size_t offset); size_t InitDexFiles(size_t offset); size_t InitOatClasses(size_t offset); size_t InitOatMaps(size_t offset); @@ -177,6 +184,7 @@ class OatWriter { SHARED_REQUIRES(Locks::mutator_lock_); bool WriteTables(OutputStream* out, const size_t file_offset); + bool WriteLookupTables(OutputStream* out, const size_t file_offset); size_t WriteMaps(OutputStream* out, const size_t file_offset, size_t relative_offset); size_t WriteCode(OutputStream* out, const size_t file_offset, size_t relative_offset); size_t WriteCodeDexFiles(OutputStream* out, const size_t file_offset, size_t relative_offset); @@ -199,6 +207,8 @@ class OatWriter { const uint8_t* dex_file_location_data_; uint32_t dex_file_location_checksum_; uint32_t dex_file_offset_; + uint32_t lookup_table_offset_; + TypeLookupTable* lookup_table_; // Owned by the dex file. std::vector<uint32_t> methods_offsets_; private: @@ -333,6 +343,9 @@ class OatWriter { uint32_t size_oat_class_status_; uint32_t size_oat_class_method_bitmaps_; uint32_t size_oat_class_method_offsets_; + uint32_t size_oat_lookup_table_alignment_; + uint32_t size_oat_lookup_table_offset_; + uint32_t size_oat_lookup_table_; std::unique_ptr<linker::RelativePatcher> relative_patcher_; diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc index 8ca352f573..ed193c7b61 100644 --- a/compiler/optimizing/builder.cc +++ b/compiler/optimizing/builder.cc @@ -774,11 +774,12 @@ bool HGraphBuilder::BuildInvoke(const Instruction& instruction, &string_init_offset); // Replace calls to String.<init> with StringFactory. if (is_string_init) { - HInvokeStaticOrDirect::DispatchInfo dispatch_info = ComputeDispatchInfo(is_string_init, - string_init_offset, - target_method, - direct_method, - direct_code); + HInvokeStaticOrDirect::DispatchInfo dispatch_info = { + HInvokeStaticOrDirect::MethodLoadKind::kStringInit, + HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod, + dchecked_integral_cast<uint64_t>(string_init_offset), + 0U + }; HInvoke* invoke = new (arena_) HInvokeStaticOrDirect( arena_, number_of_arguments - 1, @@ -841,11 +842,12 @@ bool HGraphBuilder::BuildInvoke(const Instruction& instruction, clinit_check = ProcessClinitCheckForInvoke(dex_pc, method_idx, &clinit_check_requirement); } - HInvokeStaticOrDirect::DispatchInfo dispatch_info = ComputeDispatchInfo(is_string_init, - string_init_offset, - target_method, - direct_method, - direct_code); + HInvokeStaticOrDirect::DispatchInfo dispatch_info = { + HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod, + HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod, + 0u, + 0U + }; invoke = new (arena_) HInvokeStaticOrDirect(arena_, number_of_arguments, return_type, @@ -958,77 +960,6 @@ HClinitCheck* HGraphBuilder::ProcessClinitCheckForInvoke( return clinit_check; } -HInvokeStaticOrDirect::DispatchInfo HGraphBuilder::ComputeDispatchInfo( - bool is_string_init, - int32_t string_init_offset, - MethodReference target_method, - uintptr_t direct_method, - uintptr_t direct_code) { - HInvokeStaticOrDirect::MethodLoadKind method_load_kind; - HInvokeStaticOrDirect::CodePtrLocation code_ptr_location; - uint64_t method_load_data = 0u; - uint64_t direct_code_ptr = 0u; - - if (is_string_init) { - // TODO: Use direct_method and direct_code for the appropriate StringFactory method. - method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kStringInit; - code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod; - method_load_data = string_init_offset; - } else if (target_method.dex_file == outer_compilation_unit_->GetDexFile() && - target_method.dex_method_index == outer_compilation_unit_->GetDexMethodIndex()) { - method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kRecursive; - code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallSelf; - } else { - if (direct_method != 0u) { // Should we use a direct pointer to the method? - if (direct_method != static_cast<uintptr_t>(-1)) { // Is the method pointer known now? - method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDirectAddress; - method_load_data = direct_method; - } else { // The direct pointer will be known at link time. - method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDirectAddressWithFixup; - } - } else { // Use dex cache. - DCHECK(target_method.dex_file == dex_compilation_unit_->GetDexFile()); - DexCacheArraysLayout layout = - compiler_driver_->GetDexCacheArraysLayout(target_method.dex_file); - if (layout.Valid()) { // Can we use PC-relative access to the dex cache arrays? - method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative; - method_load_data = layout.MethodOffset(target_method.dex_method_index); - } else { // We must go through the ArtMethod's pointer to resolved methods. - method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod; - } - } - if (direct_code != 0u) { // Should we use a direct pointer to the code? - if (direct_code != static_cast<uintptr_t>(-1)) { // Is the code pointer known now? - code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallDirect; - direct_code_ptr = direct_code; - } else if (compiler_driver_->IsImage() || - target_method.dex_file == dex_compilation_unit_->GetDexFile()) { - // Use PC-relative calls for invokes within a multi-dex oat file. - // TODO: Recognize when the target dex file is within the current oat file for - // app compilation. At the moment we recognize only the boot image as multi-dex. - // NOTE: This will require changing the ARM backend which currently falls - // through from kCallPCRelative to kDirectCodeFixup for different dex files. - code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative; - } else { // The direct pointer will be known at link time. - // NOTE: This is used for app->boot calls when compiling an app against - // a relocatable but not yet relocated image. - code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup; - } - } else { // We must use the code pointer from the ArtMethod. - code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod; - } - } - - if (graph_->IsDebuggable()) { - // For debuggable apps always use the code pointer from ArtMethod - // so that we don't circumvent instrumentation stubs if installed. - code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod; - } - - return HInvokeStaticOrDirect::DispatchInfo { - method_load_kind, code_ptr_location, method_load_data, direct_code_ptr }; -} - bool HGraphBuilder::SetupInvokeArguments(HInvoke* invoke, uint32_t number_of_vreg_arguments, uint32_t* args, diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h index 6910d5195c..9eaa4b62c5 100644 --- a/compiler/optimizing/builder.h +++ b/compiler/optimizing/builder.h @@ -276,12 +276,6 @@ class HGraphBuilder : public ValueObject { uint32_t dex_pc, HInvoke* invoke); - HInvokeStaticOrDirect::DispatchInfo ComputeDispatchInfo(bool is_string_init, - int32_t string_init_offset, - MethodReference target_method, - uintptr_t direct_method, - uintptr_t direct_code); - bool SetupInvokeArguments(HInvoke* invoke, uint32_t number_of_vreg_arguments, uint32_t* args, diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index 1c62dfa859..a1bb5e0838 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -379,13 +379,17 @@ void CodeGenerator::CreateCommonInvokeLocationSummary( if (invoke->IsInvokeStaticOrDirect()) { HInvokeStaticOrDirect* call = invoke->AsInvokeStaticOrDirect(); - if (call->IsStringInit()) { - locations->AddTemp(visitor->GetMethodLocation()); - } else if (call->IsRecursive()) { - locations->SetInAt(call->GetCurrentMethodInputIndex(), visitor->GetMethodLocation()); - } else { - locations->AddTemp(visitor->GetMethodLocation()); - locations->SetInAt(call->GetCurrentMethodInputIndex(), Location::RequiresRegister()); + switch (call->GetMethodLoadKind()) { + case HInvokeStaticOrDirect::MethodLoadKind::kRecursive: + locations->SetInAt(call->GetCurrentMethodInputIndex(), visitor->GetMethodLocation()); + break; + case HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod: + locations->AddTemp(visitor->GetMethodLocation()); + locations->SetInAt(call->GetCurrentMethodInputIndex(), Location::RequiresRegister()); + break; + default: + locations->AddTemp(visitor->GetMethodLocation()); + break; } } else { locations->AddTemp(visitor->GetMethodLocation()); diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h index b04dfc00b2..47b6f30450 100644 --- a/compiler/optimizing/code_generator.h +++ b/compiler/optimizing/code_generator.h @@ -172,6 +172,7 @@ class CodeGenerator { OptimizingCompilerStats* stats = nullptr); virtual ~CodeGenerator() {} + // Get the graph. This is the outermost graph, never the graph of a method being inlined. HGraph* GetGraph() const { return graph_; } HBasicBlock* GetNextBlockToEmit() const; @@ -431,6 +432,12 @@ class CodeGenerator { uint32_t dex_pc, SlowPathCode* slow_path) = 0; + // Check if the desired_dispatch_info is supported. If it is, return it, + // otherwise return a fall-back info that should be used instead. + virtual HInvokeStaticOrDirect::DispatchInfo GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method) = 0; + // Generate a call to a static or direct method. virtual void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) = 0; // Generate a call to a virtual method. diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 92a5878476..3dc3b7fba0 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -56,6 +56,8 @@ static constexpr SRegister kFpuCalleeSaves[] = // S registers. Therefore there is no need to block it. static constexpr DRegister DTMP = D31; +static constexpr uint32_t kPackedSwitchJumpTableThreshold = 6; + #define __ down_cast<ArmAssembler*>(codegen->GetAssembler())-> #define QUICK_ENTRY_POINT(x) QUICK_ENTRYPOINT_OFFSET(kArmWordSize, x).Int32Value() @@ -513,17 +515,6 @@ void CodeGeneratorARM::Finalize(CodeAllocator* allocator) { uint32_t new_position = __ GetAdjustedPosition(old_position); stack_map_stream_.SetStackMapNativePcOffset(i, new_position); } - // Adjust native pc offsets of block labels. - for (HBasicBlock* block : *block_order_) { - // Get the label directly from block_labels_ rather than through GetLabelOf() to avoid - // FirstNonEmptyBlock() which could lead to adjusting a label more than once. - DCHECK_LT(block->GetBlockId(), GetGraph()->GetBlocks().size()); - Label* block_label = &block_labels_[block->GetBlockId()]; - DCHECK_EQ(block_label->IsBound(), !block->IsSingleJump()); - if (block_label->IsBound()) { - __ AdjustLabelPosition(block_label); - } - } // Adjust pc offsets for the disassembly information. if (disasm_info_ != nullptr) { GeneratedCodeInterval* frame_entry_interval = disasm_info_->GetFrameEntryInterval(); @@ -538,10 +529,6 @@ void CodeGeneratorARM::Finalize(CodeAllocator* allocator) { it.code_interval.end = __ GetAdjustedPosition(it.code_interval.end); } } - // Adjust pc offsets for relative call patches. - for (MethodPatchInfo<Label>& info : relative_call_patches_) { - __ AdjustLabelPosition(&info.label); - } CodeGenerator::Finalize(allocator); } @@ -732,7 +719,8 @@ void CodeGeneratorARM::GenerateFrameExit() { } void CodeGeneratorARM::Bind(HBasicBlock* block) { - __ Bind(GetLabelOf(block)); + Label* label = GetLabelOf(block); + __ BindTrackedLabel(label); } Location CodeGeneratorARM::GetStackLocation(HLoadLocal* load) const { @@ -5155,26 +5143,51 @@ void InstructionCodeGeneratorARM::HandleBitwiseOperation(HBinaryOperation* instr } } +HInvokeStaticOrDirect::DispatchInfo CodeGeneratorARM::GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method) { + if (desired_dispatch_info.method_load_kind == + HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative) { + // TODO: Implement this type. For the moment, we fall back to kDexCacheViaMethod. + return HInvokeStaticOrDirect::DispatchInfo { + HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod, + HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod, + 0u, + 0u + }; + } + if (desired_dispatch_info.code_ptr_location == + HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative) { + const DexFile& outer_dex_file = GetGraph()->GetDexFile(); + if (&outer_dex_file != target_method.dex_file) { + // Calls across dex files are more likely to exceed the available BL range, + // so use absolute patch with fixup if available and kCallArtMethod otherwise. + HInvokeStaticOrDirect::CodePtrLocation code_ptr_location = + (desired_dispatch_info.method_load_kind == + HInvokeStaticOrDirect::MethodLoadKind::kDirectAddressWithFixup) + ? HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup + : HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod; + return HInvokeStaticOrDirect::DispatchInfo { + desired_dispatch_info.method_load_kind, + code_ptr_location, + desired_dispatch_info.method_load_data, + 0u + }; + } + } + return desired_dispatch_info; +} + void CodeGeneratorARM::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { // For better instruction scheduling we load the direct code pointer before the method pointer. - bool direct_code_loaded = false; switch (invoke->GetCodePtrLocation()) { - case HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative: - if (IsSameDexFile(*invoke->GetTargetMethod().dex_file, GetGraph()->GetDexFile())) { - break; - } - // Calls across dex files are more likely to exceed the available BL range, - // so use absolute patch by falling through to kDirectCodeFixup. - FALLTHROUGH_INTENDED; case HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup: // LR = code address from literal pool with link-time patch. __ LoadLiteral(LR, DeduplicateMethodCodeLiteral(invoke->GetTargetMethod())); - direct_code_loaded = true; break; case HInvokeStaticOrDirect::CodePtrLocation::kCallDirect: // LR = invoke->GetDirectCodePtr(); __ LoadImmediate(LR, invoke->GetDirectCodePtr()); - direct_code_loaded = true; break; default: break; @@ -5197,8 +5210,10 @@ void CodeGeneratorARM::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, DeduplicateMethodAddressLiteral(invoke->GetTargetMethod())); break; case HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative: - // TODO: Implement this type. For the moment, we fall back to kDexCacheViaMethod. - FALLTHROUGH_INTENDED; + // TODO: Implement this type. + // Currently filtered out by GetSupportedInvokeStaticOrDirectDispatch(). + LOG(FATAL) << "Unsupported"; + UNREACHABLE(); case HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod: { Location current_method = invoke->GetLocations()->InAt(invoke->GetCurrentMethodInputIndex()); Register method_reg; @@ -5227,20 +5242,14 @@ void CodeGeneratorARM::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, __ bl(GetFrameEntryLabel()); break; case HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative: - if (!direct_code_loaded) { - relative_call_patches_.emplace_back(invoke->GetTargetMethod()); - __ Bind(&relative_call_patches_.back().label); - Label label; - __ bl(&label); // Arbitrarily branch to the instruction after BL, override at link time. - __ Bind(&label); - break; - } - // If we loaded the direct code above, fall through. - FALLTHROUGH_INTENDED; + relative_call_patches_.emplace_back(invoke->GetTargetMethod()); + __ BindTrackedLabel(&relative_call_patches_.back().label); + // Arbitrarily branch to the BL itself, override at link time. + __ bl(&relative_call_patches_.back().label); + break; case HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup: case HInvokeStaticOrDirect::CodePtrLocation::kCallDirect: // LR prepared above for better instruction scheduling. - DCHECK(direct_code_loaded); // LR() __ blx(LR); break; @@ -5357,25 +5366,64 @@ void LocationsBuilderARM::VisitPackedSwitch(HPackedSwitch* switch_instr) { LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(switch_instr, LocationSummary::kNoCall); locations->SetInAt(0, Location::RequiresRegister()); + if (switch_instr->GetNumEntries() >= kPackedSwitchJumpTableThreshold && + codegen_->GetAssembler()->IsThumb()) { + locations->AddTemp(Location::RequiresRegister()); // We need a temp for the table base. + if (switch_instr->GetStartValue() != 0) { + locations->AddTemp(Location::RequiresRegister()); // We need a temp for the bias. + } + } } void InstructionCodeGeneratorARM::VisitPackedSwitch(HPackedSwitch* switch_instr) { int32_t lower_bound = switch_instr->GetStartValue(); - int32_t num_entries = switch_instr->GetNumEntries(); + uint32_t num_entries = switch_instr->GetNumEntries(); LocationSummary* locations = switch_instr->GetLocations(); Register value_reg = locations->InAt(0).AsRegister<Register>(); HBasicBlock* default_block = switch_instr->GetDefaultBlock(); - // Create a series of compare/jumps. - const ArenaVector<HBasicBlock*>& successors = switch_instr->GetBlock()->GetSuccessors(); - for (int32_t i = 0; i < num_entries; i++) { - GenerateCompareWithImmediate(value_reg, lower_bound + i); - __ b(codegen_->GetLabelOf(successors[i]), EQ); - } + if (num_entries < kPackedSwitchJumpTableThreshold || !codegen_->GetAssembler()->IsThumb()) { + // Create a series of compare/jumps. + const ArenaVector<HBasicBlock*>& successors = switch_instr->GetBlock()->GetSuccessors(); + for (uint32_t i = 0; i < num_entries; i++) { + GenerateCompareWithImmediate(value_reg, lower_bound + i); + __ b(codegen_->GetLabelOf(successors[i]), EQ); + } + + // And the default for any other value. + if (!codegen_->GoesToNextBlock(switch_instr->GetBlock(), default_block)) { + __ b(codegen_->GetLabelOf(default_block)); + } + } else { + // Create a table lookup. + Register temp_reg = locations->GetTemp(0).AsRegister<Register>(); + + // Materialize a pointer to the switch table + std::vector<Label*> labels(num_entries); + const ArenaVector<HBasicBlock*>& successors = switch_instr->GetBlock()->GetSuccessors(); + for (uint32_t i = 0; i < num_entries; i++) { + labels[i] = codegen_->GetLabelOf(successors[i]); + } + JumpTable* table = __ CreateJumpTable(std::move(labels), temp_reg); + + // Remove the bias. + Register key_reg; + if (lower_bound != 0) { + key_reg = locations->GetTemp(1).AsRegister<Register>(); + __ AddConstant(key_reg, value_reg, -lower_bound); + } else { + key_reg = value_reg; + } + + // Check whether the value is in the table, jump to default block if not. + __ CmpConstant(key_reg, num_entries - 1); + __ b(codegen_->GetLabelOf(default_block), Condition::HI); + + // Load the displacement from the table. + __ ldr(temp_reg, Address(temp_reg, key_reg, Shift::LSL, 2)); - // And the default for any other value. - if (!codegen_->GoesToNextBlock(switch_instr->GetBlock(), default_block)) { - __ b(codegen_->GetLabelOf(default_block)); + // Dispatch is a direct add to the PC (for Thumb2). + __ EmitJumpTableDispatch(table, temp_reg); } } diff --git a/compiler/optimizing/code_generator_arm.h b/compiler/optimizing/code_generator_arm.h index 6900933e87..cef1095c5d 100644 --- a/compiler/optimizing/code_generator_arm.h +++ b/compiler/optimizing/code_generator_arm.h @@ -362,6 +362,12 @@ class CodeGeneratorARM : public CodeGenerator { Label* GetFrameEntryLabel() { return &frame_entry_label_; } + // Check if the desired_dispatch_info is supported. If it is, return it, + // otherwise return a fall-back info that should be used instead. + HInvokeStaticOrDirect::DispatchInfo GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method) OVERRIDE; + void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE; diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 1773c06e0b..b0be446174 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -2826,6 +2826,13 @@ static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorARM64* codege return false; } +HInvokeStaticOrDirect::DispatchInfo CodeGeneratorARM64::GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method ATTRIBUTE_UNUSED) { + // On arm64 we support all dispatch types. + return desired_dispatch_info; +} + void CodeGeneratorARM64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { // For better instruction scheduling we load the direct code pointer before the method pointer. bool direct_code_loaded = false; diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h index 799f1bdcff..ab684ea538 100644 --- a/compiler/optimizing/code_generator_arm64.h +++ b/compiler/optimizing/code_generator_arm64.h @@ -388,6 +388,12 @@ class CodeGeneratorARM64 : public CodeGenerator { return false; } + // Check if the desired_dispatch_info is supported. If it is, return it, + // otherwise return a fall-back info that should be used instead. + HInvokeStaticOrDirect::DispatchInfo GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method) OVERRIDE; + void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE; diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index 8ba4556184..29d08beb97 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -19,10 +19,12 @@ #include "arch/mips/entrypoints_direct_mips.h" #include "arch/mips/instruction_set_features_mips.h" #include "art_method.h" +#include "code_generator_utils.h" #include "entrypoints/quick/quick_entrypoints.h" #include "entrypoints/quick/quick_entrypoints_enum.h" #include "gc/accounting/card_table.h" #include "intrinsics.h" +#include "intrinsics_mips.h" #include "mirror/array-inl.h" #include "mirror/class-inl.h" #include "offsets.h" @@ -1000,6 +1002,9 @@ void CodeGeneratorMIPS::Move(HInstruction* instruction, void CodeGeneratorMIPS::AddLocationAsTemp(Location location, LocationSummary* locations) { if (location.IsRegister()) { locations->AddTemp(location); + } else if (location.IsRegisterPair()) { + locations->AddTemp(Location::RegisterLocation(location.AsRegisterPairLow<Register>())); + locations->AddTemp(Location::RegisterLocation(location.AsRegisterPairHigh<Register>())); } else { UNIMPLEMENTED(FATAL) << "AddLocationAsTemp not implemented for location " << location; } @@ -2930,7 +2935,11 @@ void InstructionCodeGeneratorMIPS::VisitInvokeInterface(HInvokeInterface* invoke } void LocationsBuilderMIPS::VisitInvokeVirtual(HInvokeVirtual* invoke) { - // TODO: intrinsic function. + IntrinsicLocationsBuilderMIPS intrinsic(codegen_); + if (intrinsic.TryDispatch(invoke)) { + return; + } + HandleInvoke(invoke); } @@ -2939,18 +2948,54 @@ void LocationsBuilderMIPS::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invo // invokes must have been pruned by art::PrepareForRegisterAllocation. DCHECK(codegen_->IsBaseline() || !invoke->IsStaticWithExplicitClinitCheck()); - // TODO: intrinsic function. + IntrinsicLocationsBuilderMIPS intrinsic(codegen_); + if (intrinsic.TryDispatch(invoke)) { + return; + } + HandleInvoke(invoke); } -static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorMIPS* codegen ATTRIBUTE_UNUSED) { +static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorMIPS* codegen) { if (invoke->GetLocations()->Intrinsified()) { - // TODO: intrinsic function. + IntrinsicCodeGeneratorMIPS intrinsic(codegen); + intrinsic.Dispatch(invoke); return true; } return false; } +HInvokeStaticOrDirect::DispatchInfo CodeGeneratorMIPS::GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method ATTRIBUTE_UNUSED) { + switch (desired_dispatch_info.method_load_kind) { + case HInvokeStaticOrDirect::MethodLoadKind::kDirectAddressWithFixup: + case HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative: + // TODO: Implement these types. For the moment, we fall back to kDexCacheViaMethod. + return HInvokeStaticOrDirect::DispatchInfo { + HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod, + HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod, + 0u, + 0u + }; + default: + break; + } + switch (desired_dispatch_info.code_ptr_location) { + case HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup: + case HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative: + // TODO: Implement these types. For the moment, we fall back to kCallArtMethod. + return HInvokeStaticOrDirect::DispatchInfo { + desired_dispatch_info.method_load_kind, + HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod, + desired_dispatch_info.method_load_data, + 0u + }; + default: + return desired_dispatch_info; + } +} + void CodeGeneratorMIPS::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { // All registers are assumed to be correctly set up per the calling convention. @@ -2970,13 +3015,11 @@ void CodeGeneratorMIPS::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke __ LoadConst32(temp.AsRegister<Register>(), invoke->GetMethodAddress()); break; case HInvokeStaticOrDirect::MethodLoadKind::kDirectAddressWithFixup: - // TODO: Implement this type. (Needs literal support.) At the moment, the - // CompilerDriver will not direct the backend to use this type for MIPS. - LOG(FATAL) << "Unsupported!"; - UNREACHABLE(); case HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative: - // TODO: Implement this type. For the moment, we fall back to kDexCacheViaMethod. - FALLTHROUGH_INTENDED; + // TODO: Implement these types. + // Currently filtered out by GetSupportedInvokeStaticOrDirectDispatch(). + LOG(FATAL) << "Unsupported"; + UNREACHABLE(); case HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod: { Location current_method = invoke->GetLocations()->InAt(invoke->GetCurrentMethodInputIndex()); Register reg = temp.AsRegister<Register>(); @@ -3017,12 +3060,12 @@ void CodeGeneratorMIPS::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke __ Jalr(T9); __ Nop(); break; - case HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative: - // TODO: Implement kCallPCRelative. For the moment, we fall back to kMethodCode. - FALLTHROUGH_INTENDED; case HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup: - // TODO: Implement kDirectCodeFixup. For the moment, we fall back to kMethodCode. - FALLTHROUGH_INTENDED; + case HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative: + // TODO: Implement these types. + // Currently filtered out by GetSupportedInvokeStaticOrDirectDispatch(). + LOG(FATAL) << "Unsupported"; + UNREACHABLE(); case HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod: // T9 = callee_method->entry_point_from_quick_compiled_code_; __ LoadFromOffset(kLoadWord, @@ -3056,7 +3099,10 @@ void InstructionCodeGeneratorMIPS::VisitInvokeStaticOrDirect(HInvokeStaticOrDire } void InstructionCodeGeneratorMIPS::VisitInvokeVirtual(HInvokeVirtual* invoke) { - // TODO: Try to generate intrinsics code. + if (TryGenerateIntrinsicCode(invoke, codegen_)) { + return; + } + LocationSummary* locations = invoke->GetLocations(); Location receiver = locations->InAt(0); Register temp = invoke->GetLocations()->GetTemp(0).AsRegister<Register>(); @@ -3086,15 +3132,25 @@ void InstructionCodeGeneratorMIPS::VisitInvokeVirtual(HInvokeVirtual* invoke) { } void LocationsBuilderMIPS::VisitLoadClass(HLoadClass* cls) { - LocationSummary::CallKind call_kind = cls->CanCallRuntime() ? LocationSummary::kCallOnSlowPath - : LocationSummary::kNoCall; - LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(cls, call_kind); - locations->SetInAt(0, Location::RequiresRegister()); - locations->SetOut(Location::RequiresRegister()); + InvokeRuntimeCallingConvention calling_convention; + CodeGenerator::CreateLoadClassLocationSummary( + cls, + Location::RegisterLocation(calling_convention.GetRegisterAt(0)), + Location::RegisterLocation(V0)); } void InstructionCodeGeneratorMIPS::VisitLoadClass(HLoadClass* cls) { LocationSummary* locations = cls->GetLocations(); + if (cls->NeedsAccessCheck()) { + codegen_->MoveConstant(locations->GetTemp(0), cls->GetTypeIndex()); + codegen_->InvokeRuntime(QUICK_ENTRY_POINT(pInitializeTypeAndVerifyAccess), + cls, + cls->GetDexPc(), + nullptr, + IsDirectEntrypoint(kQuickInitializeTypeAndVerifyAccess)); + return; + } + Register out = locations->Out().AsRegister<Register>(); Register current_method = locations->InAt(0).AsRegister<Register>(); if (cls->IsReferrersClass()) { diff --git a/compiler/optimizing/code_generator_mips.h b/compiler/optimizing/code_generator_mips.h index a571e76933..059131dcfc 100644 --- a/compiler/optimizing/code_generator_mips.h +++ b/compiler/optimizing/code_generator_mips.h @@ -332,6 +332,12 @@ class CodeGeneratorMIPS : public CodeGenerator { return type == Primitive::kPrimLong; } + // Check if the desired_dispatch_info is supported. If it is, return it, + // otherwise return a fall-back info that should be used instead. + HInvokeStaticOrDirect::DispatchInfo GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method) OVERRIDE; + void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp); void GenerateVirtualCall(HInvokeVirtual* invoke ATTRIBUTE_UNUSED, Location temp ATTRIBUTE_UNUSED) OVERRIDE { diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index 5f78285b69..55efd5f9de 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -2528,6 +2528,37 @@ static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorMIPS64* codeg return false; } +HInvokeStaticOrDirect::DispatchInfo CodeGeneratorMIPS64::GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method ATTRIBUTE_UNUSED) { + switch (desired_dispatch_info.method_load_kind) { + case HInvokeStaticOrDirect::MethodLoadKind::kDirectAddressWithFixup: + case HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative: + // TODO: Implement these types. For the moment, we fall back to kDexCacheViaMethod. + return HInvokeStaticOrDirect::DispatchInfo { + HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod, + HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod, + 0u, + 0u + }; + default: + break; + } + switch (desired_dispatch_info.code_ptr_location) { + case HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup: + case HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative: + // TODO: Implement these types. For the moment, we fall back to kCallArtMethod. + return HInvokeStaticOrDirect::DispatchInfo { + desired_dispatch_info.method_load_kind, + HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod, + desired_dispatch_info.method_load_data, + 0u + }; + default: + return desired_dispatch_info; + } +} + void CodeGeneratorMIPS64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { // All registers are assumed to be correctly set up per the calling convention. @@ -2547,13 +2578,11 @@ void CodeGeneratorMIPS64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invo __ LoadConst64(temp.AsRegister<GpuRegister>(), invoke->GetMethodAddress()); break; case HInvokeStaticOrDirect::MethodLoadKind::kDirectAddressWithFixup: - // TODO: Implement this type. (Needs literal support.) At the moment, the - // CompilerDriver will not direct the backend to use this type for MIPS. - LOG(FATAL) << "Unsupported!"; - UNREACHABLE(); case HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative: - // TODO: Implement this type. For the moment, we fall back to kDexCacheViaMethod. - FALLTHROUGH_INTENDED; + // TODO: Implement these types. + // Currently filtered out by GetSupportedInvokeStaticOrDirectDispatch(). + LOG(FATAL) << "Unsupported"; + UNREACHABLE(); case HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod: { Location current_method = invoke->GetLocations()->InAt(invoke->GetCurrentMethodInputIndex()); GpuRegister reg = temp.AsRegister<GpuRegister>(); @@ -2593,12 +2622,12 @@ void CodeGeneratorMIPS64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invo // LR() __ Jalr(T9); break; - case HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative: - // TODO: Implement kCallPCRelative. For the moment, we fall back to kMethodCode. - FALLTHROUGH_INTENDED; case HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup: - // TODO: Implement kDirectCodeFixup. For the moment, we fall back to kMethodCode. - FALLTHROUGH_INTENDED; + case HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative: + // TODO: Implement these types. + // Currently filtered out by GetSupportedInvokeStaticOrDirectDispatch(). + LOG(FATAL) << "Unsupported"; + UNREACHABLE(); case HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod: // T9 = callee_method->entry_point_from_quick_compiled_code_; __ LoadFromOffset(kLoadDoubleword, diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h index df3fc0d1e9..9bbd02759a 100644 --- a/compiler/optimizing/code_generator_mips64.h +++ b/compiler/optimizing/code_generator_mips64.h @@ -326,6 +326,12 @@ class CodeGeneratorMIPS64 : public CodeGenerator { bool NeedsTwoRegisters(Primitive::Type type ATTRIBUTE_UNUSED) const { return false; } + // Check if the desired_dispatch_info is supported. If it is, return it, + // otherwise return a fall-back info that should be used instead. + HInvokeStaticOrDirect::DispatchInfo GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method) OVERRIDE; + void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; void GenerateVirtualCall(HInvokeVirtual* invoke ATTRIBUTE_UNUSED, Location temp ATTRIBUTE_UNUSED) OVERRIDE { diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 963eec2529..0df7e3b30a 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -3757,6 +3757,34 @@ void InstructionCodeGeneratorX86::GenerateMemoryBarrier(MemBarrierKind kind) { } } +HInvokeStaticOrDirect::DispatchInfo CodeGeneratorX86::GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method ATTRIBUTE_UNUSED) { + if (desired_dispatch_info.method_load_kind == + HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative) { + // TODO: Implement this type. For the moment, we fall back to kDexCacheViaMethod. + return HInvokeStaticOrDirect::DispatchInfo { + HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod, + HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod, + 0u, + 0u + }; + } + switch (desired_dispatch_info.code_ptr_location) { + case HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup: + case HInvokeStaticOrDirect::CodePtrLocation::kCallDirect: + // For direct code, we actually prefer to call via the code pointer from ArtMethod*. + // (Though the direct CALL ptr16:32 is available for consideration). + return HInvokeStaticOrDirect::DispatchInfo { + desired_dispatch_info.method_load_kind, + HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod, + desired_dispatch_info.method_load_data, + 0u + }; + default: + return desired_dispatch_info; + } +} void CodeGeneratorX86::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { Location callee_method = temp; // For all kinds except kRecursive, callee will be in temp. @@ -3777,8 +3805,10 @@ void CodeGeneratorX86::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, __ Bind(&method_patches_.back().label); // Bind the label at the end of the "movl" insn. break; case HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative: - // TODO: Implement this type. For the moment, we fall back to kDexCacheViaMethod. - FALLTHROUGH_INTENDED; + // TODO: Implement this type. + // Currently filtered out by GetSupportedInvokeStaticOrDirectDispatch(). + LOG(FATAL) << "Unsupported"; + UNREACHABLE(); case HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod: { Location current_method = invoke->GetLocations()->InAt(invoke->GetCurrentMethodInputIndex()); Register method_reg; @@ -3814,9 +3844,9 @@ void CodeGeneratorX86::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, } case HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup: case HInvokeStaticOrDirect::CodePtrLocation::kCallDirect: - // For direct code, we actually prefer to call via the code pointer from ArtMethod*. - // (Though the direct CALL ptr16:32 is available for consideration). - FALLTHROUGH_INTENDED; + // Filtered out by GetSupportedInvokeStaticOrDirectDispatch(). + LOG(FATAL) << "Unsupported"; + UNREACHABLE(); case HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod: // (callee_method + offset_of_quick_compiled_code)() __ call(Address(callee_method.AsRegister<Register>(), diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h index fdfc5ab69b..ac3d06c23d 100644 --- a/compiler/optimizing/code_generator_x86.h +++ b/compiler/optimizing/code_generator_x86.h @@ -333,6 +333,12 @@ class CodeGeneratorX86 : public CodeGenerator { // Helper method to move a 64bits value between two locations. void Move64(Location destination, Location source); + // Check if the desired_dispatch_info is supported. If it is, return it, + // otherwise return a fall-back info that should be used instead. + HInvokeStaticOrDirect::DispatchInfo GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method) OVERRIDE; + // Generate a call to a static or direct method. void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; // Generate a call to a virtual method. diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index ed2e4ca87c..5218d70995 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -473,6 +473,24 @@ inline Condition X86_64FPCondition(IfCondition cond) { UNREACHABLE(); } +HInvokeStaticOrDirect::DispatchInfo CodeGeneratorX86_64::GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method ATTRIBUTE_UNUSED) { + switch (desired_dispatch_info.code_ptr_location) { + case HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup: + case HInvokeStaticOrDirect::CodePtrLocation::kCallDirect: + // For direct code, we actually prefer to call via the code pointer from ArtMethod*. + return HInvokeStaticOrDirect::DispatchInfo { + desired_dispatch_info.method_load_kind, + HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod, + desired_dispatch_info.method_load_data, + 0u + }; + default: + return desired_dispatch_info; + } +} + void CodeGeneratorX86_64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { // All registers are assumed to be correctly set up. @@ -539,8 +557,9 @@ void CodeGeneratorX86_64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invo } case HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup: case HInvokeStaticOrDirect::CodePtrLocation::kCallDirect: - // For direct code, we actually prefer to call via the code pointer from ArtMethod*. - FALLTHROUGH_INTENDED; + // Filtered out by GetSupportedInvokeStaticOrDirectDispatch(). + LOG(FATAL) << "Unsupported"; + UNREACHABLE(); case HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod: // (callee_method + offset_of_quick_compiled_code)() __ call(Address(callee_method.AsRegister<CpuRegister>(), diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h index dc86a48ce7..fc485f5bb6 100644 --- a/compiler/optimizing/code_generator_x86_64.h +++ b/compiler/optimizing/code_generator_x86_64.h @@ -335,6 +335,12 @@ class CodeGeneratorX86_64 : public CodeGenerator { return false; } + // Check if the desired_dispatch_info is supported. If it is, return it, + // otherwise return a fall-back info that should be used instead. + HInvokeStaticOrDirect::DispatchInfo GetSupportedInvokeStaticOrDirectDispatch( + const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, + MethodReference target_method) OVERRIDE; + void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE; diff --git a/compiler/optimizing/constant_folding.cc b/compiler/optimizing/constant_folding.cc index e0aa4ff489..57452cc076 100644 --- a/compiler/optimizing/constant_folding.cc +++ b/compiler/optimizing/constant_folding.cc @@ -27,6 +27,11 @@ class InstructionWithAbsorbingInputSimplifier : public HGraphVisitor { private: void VisitShift(HBinaryOperation* shift); + void VisitAbove(HAbove* instruction) OVERRIDE; + void VisitAboveOrEqual(HAboveOrEqual* instruction) OVERRIDE; + void VisitBelow(HBelow* instruction) OVERRIDE; + void VisitBelowOrEqual(HBelowOrEqual* instruction) OVERRIDE; + void VisitAnd(HAnd* instruction) OVERRIDE; void VisitCompare(HCompare* instruction) OVERRIDE; void VisitMul(HMul* instruction) OVERRIDE; @@ -105,6 +110,54 @@ void InstructionWithAbsorbingInputSimplifier::VisitShift(HBinaryOperation* instr } } +void InstructionWithAbsorbingInputSimplifier::VisitAbove(HAbove* instruction) { + if (instruction->GetLeft()->IsConstant() && + instruction->GetLeft()->AsConstant()->IsZero()) { + // Replace code looking like + // ABOVE dst, 0, src // unsigned 0 > src is always false + // with + // CONSTANT false + instruction->ReplaceWith(GetGraph()->GetConstant(Primitive::kPrimBoolean, 0)); + instruction->GetBlock()->RemoveInstruction(instruction); + } +} + +void InstructionWithAbsorbingInputSimplifier::VisitAboveOrEqual(HAboveOrEqual* instruction) { + if (instruction->GetRight()->IsConstant() && + instruction->GetRight()->AsConstant()->IsZero()) { + // Replace code looking like + // ABOVE_OR_EQUAL dst, src, 0 // unsigned src >= 0 is always true + // with + // CONSTANT true + instruction->ReplaceWith(GetGraph()->GetConstant(Primitive::kPrimBoolean, 1)); + instruction->GetBlock()->RemoveInstruction(instruction); + } +} + +void InstructionWithAbsorbingInputSimplifier::VisitBelow(HBelow* instruction) { + if (instruction->GetRight()->IsConstant() && + instruction->GetRight()->AsConstant()->IsZero()) { + // Replace code looking like + // BELOW dst, src, 0 // unsigned src < 0 is always false + // with + // CONSTANT false + instruction->ReplaceWith(GetGraph()->GetConstant(Primitive::kPrimBoolean, 0)); + instruction->GetBlock()->RemoveInstruction(instruction); + } +} + +void InstructionWithAbsorbingInputSimplifier::VisitBelowOrEqual(HBelowOrEqual* instruction) { + if (instruction->GetLeft()->IsConstant() && + instruction->GetLeft()->AsConstant()->IsZero()) { + // Replace code looking like + // BELOW_OR_EQUAL dst, 0, src // unsigned 0 <= src is always true + // with + // CONSTANT true + instruction->ReplaceWith(GetGraph()->GetConstant(Primitive::kPrimBoolean, 1)); + instruction->GetBlock()->RemoveInstruction(instruction); + } +} + void InstructionWithAbsorbingInputSimplifier::VisitAnd(HAnd* instruction) { HConstant* input_cst = instruction->GetConstantRight(); if ((input_cst != nullptr) && input_cst->IsZero()) { diff --git a/compiler/optimizing/constant_folding_test.cc b/compiler/optimizing/constant_folding_test.cc index 2feb75cc9f..e469c8d6d0 100644 --- a/compiler/optimizing/constant_folding_test.cc +++ b/compiler/optimizing/constant_folding_test.cc @@ -29,50 +29,70 @@ namespace art { -static void TestCode(const uint16_t* data, - const std::string& expected_before, - const std::string& expected_after_cf, - const std::string& expected_after_dce, - std::function<void(HGraph*)> check_after_cf, - Primitive::Type return_type = Primitive::kPrimInt) { - ArenaPool pool; - ArenaAllocator allocator(&pool); - HGraph* graph = CreateCFG(&allocator, data, return_type); - ASSERT_NE(graph, nullptr); - - graph->TryBuildingSsa(); - - StringPrettyPrinter printer_before(graph); - printer_before.VisitInsertionOrder(); - std::string actual_before = printer_before.str(); - ASSERT_EQ(expected_before, actual_before); - - std::unique_ptr<const X86InstructionSetFeatures> features_x86( - X86InstructionSetFeatures::FromCppDefines()); - x86::CodeGeneratorX86 codegenX86(graph, *features_x86.get(), CompilerOptions()); - HConstantFolding(graph).Run(); - SSAChecker ssa_checker_cf(graph); - ssa_checker_cf.Run(); - ASSERT_TRUE(ssa_checker_cf.IsValid()); - - StringPrettyPrinter printer_after_cf(graph); - printer_after_cf.VisitInsertionOrder(); - std::string actual_after_cf = printer_after_cf.str(); - ASSERT_EQ(expected_after_cf, actual_after_cf); - - check_after_cf(graph); - - HDeadCodeElimination(graph).Run(); - SSAChecker ssa_checker_dce(graph); - ssa_checker_dce.Run(); - ASSERT_TRUE(ssa_checker_dce.IsValid()); - - StringPrettyPrinter printer_after_dce(graph); - printer_after_dce.VisitInsertionOrder(); - std::string actual_after_dce = printer_after_dce.str(); - ASSERT_EQ(expected_after_dce, actual_after_dce); -} - +/** + * Fixture class for the constant folding and dce tests. + */ +class ConstantFoldingTest : public testing::Test { + public: + ConstantFoldingTest() : pool_(), allocator_(&pool_) { + graph_ = CreateGraph(&allocator_); + } + + void TestCode(const uint16_t* data, + const std::string& expected_before, + const std::string& expected_after_cf, + const std::string& expected_after_dce, + std::function<void(HGraph*)> check_after_cf, + Primitive::Type return_type = Primitive::kPrimInt) { + graph_ = CreateCFG(&allocator_, data, return_type); + TestCodeOnReadyGraph(expected_before, + expected_after_cf, + expected_after_dce, + check_after_cf); + } + + void TestCodeOnReadyGraph(const std::string& expected_before, + const std::string& expected_after_cf, + const std::string& expected_after_dce, + std::function<void(HGraph*)> check_after_cf) { + ASSERT_NE(graph_, nullptr); + graph_->TryBuildingSsa(); + + StringPrettyPrinter printer_before(graph_); + printer_before.VisitInsertionOrder(); + std::string actual_before = printer_before.str(); + EXPECT_EQ(expected_before, actual_before); + + std::unique_ptr<const X86InstructionSetFeatures> features_x86( + X86InstructionSetFeatures::FromCppDefines()); + x86::CodeGeneratorX86 codegenX86(graph_, *features_x86.get(), CompilerOptions()); + HConstantFolding(graph_).Run(); + SSAChecker ssa_checker_cf(graph_); + ssa_checker_cf.Run(); + ASSERT_TRUE(ssa_checker_cf.IsValid()); + + StringPrettyPrinter printer_after_cf(graph_); + printer_after_cf.VisitInsertionOrder(); + std::string actual_after_cf = printer_after_cf.str(); + EXPECT_EQ(expected_after_cf, actual_after_cf); + + check_after_cf(graph_); + + HDeadCodeElimination(graph_).Run(); + SSAChecker ssa_checker_dce(graph_); + ssa_checker_dce.Run(); + ASSERT_TRUE(ssa_checker_dce.IsValid()); + + StringPrettyPrinter printer_after_dce(graph_); + printer_after_dce.VisitInsertionOrder(); + std::string actual_after_dce = printer_after_dce.str(); + EXPECT_EQ(expected_after_dce, actual_after_dce); + } + + ArenaPool pool_; + ArenaAllocator allocator_; + HGraph* graph_; +}; /** * Tiny three-register program exercising int constant folding on negation. @@ -84,7 +104,7 @@ static void TestCode(const uint16_t* data, * v1 <- -v0 1. neg-int v1, v0 * return v1 2. return v1 */ -TEST(ConstantFolding, IntConstantFoldingNegation) { +TEST_F(ConstantFoldingTest, IntConstantFoldingNegation) { const uint16_t data[] = TWO_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 0 << 8 | 1 << 12, Instruction::NEG_INT | 1 << 8 | 0 << 12, @@ -141,7 +161,7 @@ TEST(ConstantFolding, IntConstantFoldingNegation) { * (v2, v3) <- -(v0, v1) 1. neg-long v2, v0 * return (v2, v3) 2. return-wide v2 */ -TEST(ConstantFolding, LongConstantFoldingNegation) { +TEST_F(ConstantFoldingTest, LongConstantFoldingNegation) { const int64_t input = INT64_C(4294967296); // 2^32 const uint16_t word0 = Low16Bits(Low32Bits(input)); // LSW. const uint16_t word1 = High16Bits(Low32Bits(input)); @@ -205,7 +225,7 @@ TEST(ConstantFolding, LongConstantFoldingNegation) { * v2 <- v0 + v1 2. add-int v2, v0, v1 * return v2 4. return v2 */ -TEST(ConstantFolding, IntConstantFoldingOnAddition1) { +TEST_F(ConstantFoldingTest, IntConstantFoldingOnAddition1) { const uint16_t data[] = THREE_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 0 << 8 | 1 << 12, Instruction::CONST_4 | 1 << 8 | 2 << 12, @@ -271,7 +291,7 @@ TEST(ConstantFolding, IntConstantFoldingOnAddition1) { * v2 <- v0 + v1 6. add-int v2, v0, v1 * return v2 8. return v2 */ -TEST(ConstantFolding, IntConstantFoldingOnAddition2) { +TEST_F(ConstantFoldingTest, IntConstantFoldingOnAddition2) { const uint16_t data[] = THREE_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 0 << 8 | 1 << 12, Instruction::CONST_4 | 1 << 8 | 2 << 12, @@ -357,7 +377,7 @@ TEST(ConstantFolding, IntConstantFoldingOnAddition2) { * v2 <- v0 - v1 2. sub-int v2, v0, v1 * return v2 4. return v2 */ -TEST(ConstantFolding, IntConstantFoldingOnSubtraction) { +TEST_F(ConstantFoldingTest, IntConstantFoldingOnSubtraction) { const uint16_t data[] = THREE_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 0 << 8 | 3 << 12, Instruction::CONST_4 | 1 << 8 | 2 << 12, @@ -421,7 +441,7 @@ TEST(ConstantFolding, IntConstantFoldingOnSubtraction) { * (v0, v1) + (v1, v2) 4. add-long v4, v0, v2 * return (v4, v5) 6. return-wide v4 */ -TEST(ConstantFolding, LongConstantFoldingOnAddition) { +TEST_F(ConstantFoldingTest, LongConstantFoldingOnAddition) { const uint16_t data[] = SIX_REGISTERS_CODE_ITEM( Instruction::CONST_WIDE_16 | 0 << 8, 1, Instruction::CONST_WIDE_16 | 2 << 8, 2, @@ -486,7 +506,7 @@ TEST(ConstantFolding, LongConstantFoldingOnAddition) { * (v0, v1) - (v1, v2) 4. sub-long v4, v0, v2 * return (v4, v5) 6. return-wide v4 */ -TEST(ConstantFolding, LongConstantFoldingOnSubtraction) { +TEST_F(ConstantFoldingTest, LongConstantFoldingOnSubtraction) { const uint16_t data[] = SIX_REGISTERS_CODE_ITEM( Instruction::CONST_WIDE_16 | 0 << 8, 3, Instruction::CONST_WIDE_16 | 2 << 8, 2, @@ -560,7 +580,7 @@ TEST(ConstantFolding, LongConstantFoldingOnSubtraction) { * L3: v2 <- v1 + 8 11. add-int/lit16 v2, v1, #+8 * return v2 13. return v2 */ -TEST(ConstantFolding, IntConstantFoldingAndJumps) { +TEST_F(ConstantFoldingTest, IntConstantFoldingAndJumps) { const uint16_t data[] = THREE_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 0 << 8 | 1 << 12, Instruction::CONST_4 | 1 << 8 | 2 << 12, @@ -656,7 +676,6 @@ TEST(ConstantFolding, IntConstantFoldingAndJumps) { check_after_cf); } - /** * Three-register program with a constant (static) condition. * @@ -670,7 +689,7 @@ TEST(ConstantFolding, IntConstantFoldingAndJumps) { * L1: v2 <- v0 + v1 5. add-int v2, v0, v1 * return-void 7. return */ -TEST(ConstantFolding, ConstantCondition) { +TEST_F(ConstantFoldingTest, ConstantCondition) { const uint16_t data[] = THREE_REGISTERS_CODE_ITEM( Instruction::CONST_4 | 1 << 8 | 1 << 12, Instruction::CONST_4 | 0 << 8 | 0 << 12, @@ -732,4 +751,109 @@ TEST(ConstantFolding, ConstantCondition) { check_after_cf); } +/** + * Unsigned comparisons with zero. Since these instructions are not present + * in the bytecode, we need to set up the graph explicitly. + */ +TEST_F(ConstantFoldingTest, UnsignedComparisonsWithZero) { + graph_ = CreateGraph(&allocator_); + HBasicBlock* entry_block = new (&allocator_) HBasicBlock(graph_); + graph_->AddBlock(entry_block); + graph_->SetEntryBlock(entry_block); + HBasicBlock* block = new (&allocator_) HBasicBlock(graph_); + graph_->AddBlock(block); + HBasicBlock* exit_block = new (&allocator_) HBasicBlock(graph_); + graph_->AddBlock(exit_block); + graph_->SetExitBlock(exit_block); + entry_block->AddSuccessor(block); + block->AddSuccessor(exit_block); + + // Make various unsigned comparisons with zero against a parameter. + HInstruction* parameter = new (&allocator_) HParameterValue( + graph_->GetDexFile(), 0, 0, Primitive::kPrimInt, true); + entry_block->AddInstruction(parameter); + HInstruction* zero = graph_->GetIntConstant(0); + HInstruction* last; + block->AddInstruction(last = new (&allocator_) HAbove(zero, parameter)); + block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(last = new (&allocator_) HAbove(parameter, zero)); + block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(last = new (&allocator_) HAboveOrEqual(zero, parameter)); + block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(last = new (&allocator_) HAboveOrEqual(parameter, zero)); + block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(last = new (&allocator_) HBelow(zero, parameter)); + block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(last = new (&allocator_) HBelow(parameter, zero)); + block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(last = new (&allocator_) HBelowOrEqual(zero, parameter)); + block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + block->AddInstruction(last = new (&allocator_) HBelowOrEqual(parameter, zero)); + block->AddInstruction(new (&allocator_) HDeoptimize(last, 0)); + + entry_block->AddInstruction(new (&allocator_) HGoto()); + block->AddInstruction(new (&allocator_) HReturn(zero)); + exit_block->AddInstruction(new (&allocator_) HExit()); + + const std::string expected_before = + "BasicBlock 0, succ: 1\n" + " 0: ParameterValue [16, 14, 12, 10, 8, 6, 4, 2]\n" + " 1: IntConstant [19, 16, 14, 12, 10, 8, 6, 4, 2]\n" + " 18: Goto 1\n" + "BasicBlock 1, pred: 0, succ: 2\n" + " 2: Above(1, 0) [3]\n" + " 3: Deoptimize(2)\n" + " 4: Above(0, 1) [5]\n" + " 5: Deoptimize(4)\n" + " 6: AboveOrEqual(1, 0) [7]\n" + " 7: Deoptimize(6)\n" + " 8: AboveOrEqual(0, 1) [9]\n" + " 9: Deoptimize(8)\n" + " 10: Below(1, 0) [11]\n" + " 11: Deoptimize(10)\n" + " 12: Below(0, 1) [13]\n" + " 13: Deoptimize(12)\n" + " 14: BelowOrEqual(1, 0) [15]\n" + " 15: Deoptimize(14)\n" + " 16: BelowOrEqual(0, 1) [17]\n" + " 17: Deoptimize(16)\n" + " 19: Return(1)\n" + "BasicBlock 2, pred: 1\n" + " 20: Exit\n"; + + const std::string expected_after_cf = + "BasicBlock 0, succ: 1\n" + " 0: ParameterValue [16, 10, 6, 4]\n" + " 1: IntConstant [13, 3, 19, 16, 10, 6, 4]\n" + " 21: IntConstant [15, 9]\n" + " 18: Goto 1\n" + "BasicBlock 1, pred: 0, succ: 2\n" + " 3: Deoptimize(1)\n" + " 4: Above(0, 1) [5]\n" + " 5: Deoptimize(4)\n" + " 6: AboveOrEqual(1, 0) [7]\n" + " 7: Deoptimize(6)\n" + " 9: Deoptimize(21)\n" + " 10: Below(1, 0) [11]\n" + " 11: Deoptimize(10)\n" + " 13: Deoptimize(1)\n" + " 15: Deoptimize(21)\n" + " 16: BelowOrEqual(0, 1) [17]\n" + " 17: Deoptimize(16)\n" + " 19: Return(1)\n" + "BasicBlock 2, pred: 1\n" + " 20: Exit\n"; + + const std::string expected_after_dce = expected_after_cf; + + auto check_after_cf = [](HGraph* graph) { + CHECK(graph != nullptr); + }; + + TestCodeOnReadyGraph(expected_before, + expected_after_cf, + expected_after_dce, + check_after_cf); +} + } // namespace art diff --git a/compiler/optimizing/induction_var_analysis.cc b/compiler/optimizing/induction_var_analysis.cc index 8968a44da8..fdf8cc9c1f 100644 --- a/compiler/optimizing/induction_var_analysis.cc +++ b/compiler/optimizing/induction_var_analysis.cc @@ -20,19 +20,6 @@ namespace art { /** - * Returns true if instruction is invariant within the given loop. - */ -static bool IsLoopInvariant(HLoopInformation* loop, HInstruction* instruction) { - HLoopInformation* other_loop = instruction->GetBlock()->GetLoopInformation(); - if (other_loop != loop) { - // If instruction does not occur in same loop, it is invariant - // if it appears in an outer loop (including no loop at all). - return other_loop == nullptr || loop->IsIn(*other_loop); - } - return false; -} - -/** * Since graph traversal may enter a SCC at any position, an initial representation may be rotated, * along dependences, viz. any of (a, b, c, d), (d, a, b, c) (c, d, a, b), (b, c, d, a) assuming * a chain of dependences (mutual independent items may occur in arbitrary order). For proper @@ -601,15 +588,16 @@ void HInductionVarAnalysis::VisitTripCount(HLoopInformation* loop, // an unsigned entity, for example, as in the following loop that uses the full range: // for (int i = INT_MIN; i < INT_MAX; i++) // TC = UINT_MAX // (2) The TC is only valid if the loop is taken, otherwise TC = 0, as in: - // for (int i = 12; i < U; i++) // TC = 0 when U >= 12 + // for (int i = 12; i < U; i++) // TC = 0 when U < 12 // If this cannot be determined at compile-time, the TC is only valid within the - // loop-body proper, not the loop-header unless enforced with an explicit condition. + // loop-body proper, not the loop-header unless enforced with an explicit taken-test. // (3) The TC is only valid if the loop is finite, otherwise TC has no value, as in: // for (int i = 0; i <= U; i++) // TC = Inf when U = INT_MAX // If this cannot be determined at compile-time, the TC is only valid when enforced - // with an explicit condition. + // with an explicit finite-test. // (4) For loops which early-exits, the TC forms an upper bound, as in: // for (int i = 0; i < 10 && ....; i++) // TC <= 10 + InductionInfo* trip_count = upper_expr; const bool is_taken = IsTaken(lower_expr, upper_expr, cmp); const bool is_finite = IsFinite(upper_expr, stride_value, type, cmp); const bool cancels = (cmp == kCondLT || cmp == kCondGT) && std::abs(stride_value) == 1; @@ -617,26 +605,36 @@ void HInductionVarAnalysis::VisitTripCount(HLoopInformation* loop, // Convert exclusive integral inequality into inclusive integral inequality, // viz. condition i < U is i <= U - 1 and condition i > U is i >= U + 1. if (cmp == kCondLT) { - upper_expr = CreateInvariantOp(kSub, upper_expr, CreateConstant(1, type)); + trip_count = CreateInvariantOp(kSub, trip_count, CreateConstant(1, type)); } else if (cmp == kCondGT) { - upper_expr = CreateInvariantOp(kAdd, upper_expr, CreateConstant(1, type)); + trip_count = CreateInvariantOp(kAdd, trip_count, CreateConstant(1, type)); } // Compensate for stride. - upper_expr = CreateInvariantOp(kAdd, upper_expr, stride); + trip_count = CreateInvariantOp(kAdd, trip_count, stride); } - InductionInfo* trip_count - = CreateInvariantOp(kDiv, CreateInvariantOp(kSub, upper_expr, lower_expr), stride); + trip_count = CreateInvariantOp(kDiv, CreateInvariantOp(kSub, trip_count, lower_expr), stride); // Assign the trip-count expression to the loop control. Clients that use the information // should be aware that the expression is only valid under the conditions listed above. - InductionOp tcKind = kTripCountInBodyUnsafe; + InductionOp tcKind = kTripCountInBodyUnsafe; // needs both tests if (is_taken && is_finite) { - tcKind = kTripCountInLoop; + tcKind = kTripCountInLoop; // needs neither test } else if (is_finite) { - tcKind = kTripCountInBody; + tcKind = kTripCountInBody; // needs taken-test } else if (is_taken) { - tcKind = kTripCountInLoopUnsafe; + tcKind = kTripCountInLoopUnsafe; // needs finite-test } - AssignInfo(loop, loop->GetHeader()->GetLastInstruction(), CreateTripCount(tcKind, trip_count)); + InductionOp op = kNop; + switch (cmp) { + case kCondLT: op = kLT; break; + case kCondLE: op = kLE; break; + case kCondGT: op = kGT; break; + case kCondGE: op = kGE; break; + default: LOG(FATAL) << "CONDITION UNREACHABLE"; + } + InductionInfo* taken_test = CreateInvariantOp(op, lower_expr, upper_expr); + AssignInfo(loop, + loop->GetHeader()->GetLastInstruction(), + CreateTripCount(tcKind, trip_count, taken_test)); } bool HInductionVarAnalysis::IsTaken(InductionInfo* lower_expr, @@ -707,7 +705,7 @@ HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::LookupInfo(HLoopInf return loop_it->second; } } - if (IsLoopInvariant(loop, instruction)) { + if (loop->IsLoopInvariant(instruction, true)) { InductionInfo* info = CreateInvariantFetch(instruction); AssignInfo(loop, instruction, info); return info; @@ -829,12 +827,16 @@ std::string HInductionVarAnalysis::InductionToString(InductionInfo* info) { std::string inv = "("; inv += InductionToString(info->op_a); switch (info->operation) { - case kNop: inv += " @ "; break; - case kAdd: inv += " + "; break; + case kNop: inv += " @ "; break; + case kAdd: inv += " + "; break; case kSub: - case kNeg: inv += " - "; break; - case kMul: inv += " * "; break; - case kDiv: inv += " / "; break; + case kNeg: inv += " - "; break; + case kMul: inv += " * "; break; + case kDiv: inv += " / "; break; + case kLT: inv += " < "; break; + case kLE: inv += " <= "; break; + case kGT: inv += " > "; break; + case kGE: inv += " >= "; break; case kFetch: DCHECK(info->fetch); if (IsIntAndGet(info, &value)) { @@ -843,10 +845,10 @@ std::string HInductionVarAnalysis::InductionToString(InductionInfo* info) { inv += std::to_string(info->fetch->GetId()) + ":" + info->fetch->DebugName(); } break; - case kTripCountInLoop: inv += "TC-loop:"; break; - case kTripCountInBody: inv += "TC-body:"; break; - case kTripCountInLoopUnsafe: inv += "TC-loop-unsafe:"; break; - case kTripCountInBodyUnsafe: inv += "TC-body-unsafe:"; break; + case kTripCountInLoop: inv += " (TC-loop) "; break; + case kTripCountInBody: inv += " (TC-body) "; break; + case kTripCountInLoopUnsafe: inv += " (TC-loop-unsafe) "; break; + case kTripCountInBodyUnsafe: inv += " (TC-body-unsafe) "; break; } inv += InductionToString(info->op_b); return inv + ")"; diff --git a/compiler/optimizing/induction_var_analysis.h b/compiler/optimizing/induction_var_analysis.h index 7ab80cd676..cf354093f2 100644 --- a/compiler/optimizing/induction_var_analysis.h +++ b/compiler/optimizing/induction_var_analysis.h @@ -65,11 +65,16 @@ class HInductionVarAnalysis : public HOptimization { kMul, kDiv, kFetch, - // Trip counts (valid in full loop or only body proper; unsafe implies loop may be infinite). - kTripCountInLoop, - kTripCountInBody, - kTripCountInLoopUnsafe, - kTripCountInBodyUnsafe + // Trip-counts. + kTripCountInLoop, // valid in full loop; loop is finite + kTripCountInBody, // valid in body only; loop is finite + kTripCountInLoopUnsafe, // valid in full loop; loop may be infinite + kTripCountInBodyUnsafe, // valid in body only; loop may be infinite + // Comparisons for trip-count tests. + kLT, + kLE, + kGT, + kGE }; /** @@ -85,7 +90,7 @@ class HInductionVarAnalysis : public HOptimization { * (4) periodic * nop: a, then defined by b (repeated when exhausted) * (5) trip-count: - * tc: defined by b + * tc: defined by a, taken-test in b */ struct InductionInfo : public ArenaObject<kArenaAllocInductionVarAnalysis> { InductionInfo(InductionClass ic, @@ -119,8 +124,9 @@ class HInductionVarAnalysis : public HOptimization { return new (graph_->GetArena()) InductionInfo(kInvariant, kFetch, nullptr, nullptr, f); } - InductionInfo* CreateTripCount(InductionOp op, InductionInfo* b) { - return new (graph_->GetArena()) InductionInfo(kInvariant, op, nullptr, b, nullptr); + InductionInfo* CreateTripCount(InductionOp op, InductionInfo* a, InductionInfo* b) { + DCHECK(a != nullptr); + return new (graph_->GetArena()) InductionInfo(kInvariant, op, a, b, nullptr); } InductionInfo* CreateInduction(InductionClass ic, InductionInfo* a, InductionInfo* b) { diff --git a/compiler/optimizing/induction_var_analysis_test.cc b/compiler/optimizing/induction_var_analysis_test.cc index f16da2a3f7..b7262f6b29 100644 --- a/compiler/optimizing/induction_var_analysis_test.cc +++ b/compiler/optimizing/induction_var_analysis_test.cc @@ -234,7 +234,7 @@ TEST_F(InductionVarAnalysisTest, FindBasicInduction) { EXPECT_STREQ("((1) * i + (1))", GetInductionInfo(increment_[0], 0).c_str()); // Trip-count. - EXPECT_STREQ("(TC-loop:(100))", + EXPECT_STREQ("((100) (TC-loop) ((0) < (100)))", GetInductionInfo(loop_header_[0]->GetLastInstruction(), 0).c_str()); } @@ -552,7 +552,7 @@ TEST_F(InductionVarAnalysisTest, FindDeepLoopInduction) { } EXPECT_STREQ("((1) * i + (1))", GetInductionInfo(increment_[d], d).c_str()); // Trip-count. - EXPECT_STREQ("(TC-loop:(100))", + EXPECT_STREQ("((100) (TC-loop) ((0) < (100)))", GetInductionInfo(loop_header_[d]->GetLastInstruction(), d).c_str()); } } diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc index f4842f9696..5530d261d2 100644 --- a/compiler/optimizing/induction_var_range.cc +++ b/compiler/optimizing/induction_var_range.cc @@ -152,7 +152,7 @@ InductionVarRange::Value InductionVarRange::GetFetch(HInstruction* instruction, } } else if (is_min) { // Special case for finding minimum: minimum of trip-count in loop-body is 1. - if (trip != nullptr && in_body && instruction == trip->op_b->fetch) { + if (trip != nullptr && in_body && instruction == trip->op_a->fetch) { return Value(1); } } @@ -185,14 +185,14 @@ InductionVarRange::Value InductionVarRange::GetVal(HInductionVarAnalysis::Induct return GetFetch(info->fetch, trip, in_body, is_min); case HInductionVarAnalysis::kTripCountInLoop: if (!in_body && !is_min) { // one extra! - return GetVal(info->op_b, trip, in_body, is_min); + return GetVal(info->op_a, trip, in_body, is_min); } FALLTHROUGH_INTENDED; case HInductionVarAnalysis::kTripCountInBody: if (is_min) { return Value(0); } else if (in_body) { - return SubValue(GetVal(info->op_b, trip, in_body, is_min), Value(1)); + return SubValue(GetVal(info->op_a, trip, in_body, is_min), Value(1)); } break; default: @@ -428,7 +428,7 @@ bool InductionVarRange::GenerateCode(HInductionVarAnalysis::InductionInfo* info, return true; case HInductionVarAnalysis::kTripCountInLoop: if (!in_body && !is_min) { // one extra! - return GenerateCode(info->op_b, trip, graph, block, result, in_body, is_min); + return GenerateCode(info->op_a, trip, graph, block, result, in_body, is_min); } FALLTHROUGH_INTENDED; case HInductionVarAnalysis::kTripCountInBody: @@ -438,7 +438,7 @@ bool InductionVarRange::GenerateCode(HInductionVarAnalysis::InductionInfo* info, } return true; } else if (in_body) { - if (GenerateCode(info->op_b, trip, graph, block, &opb, in_body, is_min)) { + if (GenerateCode(info->op_a, trip, graph, block, &opb, in_body, is_min)) { if (graph != nullptr) { *result = Insert(block, new (graph->GetArena()) diff --git a/compiler/optimizing/induction_var_range_test.cc b/compiler/optimizing/induction_var_range_test.cc index 8fbc59fb4a..ce8926ad72 100644 --- a/compiler/optimizing/induction_var_range_test.cc +++ b/compiler/optimizing/induction_var_range_test.cc @@ -125,7 +125,7 @@ class InductionVarRangeTest : public testing::Test { /** Constructs a trip-count. */ HInductionVarAnalysis::InductionInfo* CreateTripCount(int32_t tc) { - return iva_->CreateTripCount(HInductionVarAnalysis::kTripCountInLoop, CreateConst(tc)); + return iva_->CreateTripCount(HInductionVarAnalysis::kTripCountInLoop, CreateConst(tc), nullptr); } /** Constructs a linear a * i + b induction. */ diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index e2aca3091f..353881e47a 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -32,6 +32,7 @@ #include "optimizing_compiler.h" #include "reference_type_propagation.h" #include "register_allocator.h" +#include "sharpening.h" #include "ssa_phi_elimination.h" #include "scoped_thread_state_change.h" #include "thread.h" @@ -396,12 +397,14 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, HDeadCodeElimination dce(callee_graph, stats_); HConstantFolding fold(callee_graph); ReferenceTypePropagation type_propagation(callee_graph, handles_); + HSharpening sharpening(callee_graph, codegen_, dex_compilation_unit, compiler_driver_); InstructionSimplifier simplify(callee_graph, stats_); IntrinsicsRecognizer intrinsics(callee_graph, compiler_driver_); HOptimization* optimizations[] = { &intrinsics, &type_propagation, + &sharpening, &simplify, &dce, &fold, @@ -415,6 +418,7 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, size_t number_of_instructions_budget = kMaximumNumberOfHInstructions; if (depth_ + 1 < compiler_driver_->GetCompilerOptions().GetInlineDepthLimit()) { HInliner inliner(callee_graph, + codegen_, outer_compilation_unit_, dex_compilation_unit, compiler_driver_, @@ -484,12 +488,32 @@ bool HInliner::TryBuildAndInline(ArtMethod* resolved_method, return false; } - if (!same_dex_file && current->NeedsDexCache()) { + if (!same_dex_file && current->NeedsDexCacheOfDeclaringClass()) { VLOG(compiler) << "Method " << PrettyMethod(method_index, callee_dex_file) << " could not be inlined because " << current->DebugName() << " it is in a different dex file and requires access to the dex cache"; return false; } + + if (current->IsNewInstance() && + (current->AsNewInstance()->GetEntrypoint() == kQuickAllocObjectWithAccessCheck)) { + // Allocation entrypoint does not handle inlined frames. + return false; + } + + if (current->IsNewArray() && + (current->AsNewArray()->GetEntrypoint() == kQuickAllocArrayWithAccessCheck)) { + // Allocation entrypoint does not handle inlined frames. + return false; + } + + if (current->IsUnresolvedStaticFieldGet() || + current->IsUnresolvedInstanceFieldGet() || + current->IsUnresolvedStaticFieldSet() || + current->IsUnresolvedInstanceFieldSet()) { + // Entrypoint for unresolved fields does not handle inlined frames. + return false; + } } } number_of_inlined_instructions_ += number_of_instructions; diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h index bce5915219..0f6a9453be 100644 --- a/compiler/optimizing/inliner.h +++ b/compiler/optimizing/inliner.h @@ -22,6 +22,7 @@ namespace art { +class CodeGenerator; class CompilerDriver; class DexCompilationUnit; class HGraph; @@ -31,6 +32,7 @@ class OptimizingCompilerStats; class HInliner : public HOptimization { public: HInliner(HGraph* outer_graph, + CodeGenerator* codegen, const DexCompilationUnit& outer_compilation_unit, const DexCompilationUnit& caller_compilation_unit, CompilerDriver* compiler_driver, @@ -40,6 +42,7 @@ class HInliner : public HOptimization { : HOptimization(outer_graph, kInlinerPassName, stats), outer_compilation_unit_(outer_compilation_unit), caller_compilation_unit_(caller_compilation_unit), + codegen_(codegen), compiler_driver_(compiler_driver), depth_(depth), number_of_inlined_instructions_(0), @@ -57,6 +60,7 @@ class HInliner : public HOptimization { const DexCompilationUnit& outer_compilation_unit_; const DexCompilationUnit& caller_compilation_unit_; + CodeGenerator* const codegen_; CompilerDriver* const compiler_driver_; const size_t depth_; size_t number_of_inlined_instructions_; diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc new file mode 100644 index 0000000000..5efcf4eadf --- /dev/null +++ b/compiler/optimizing/intrinsics_mips.cc @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2015 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 "intrinsics_mips.h" + +#include "arch/mips/instruction_set_features_mips.h" +#include "art_method.h" +#include "code_generator_mips.h" +#include "entrypoints/quick/quick_entrypoints.h" +#include "intrinsics.h" +#include "mirror/array-inl.h" +#include "mirror/string.h" +#include "thread.h" +#include "utils/mips/assembler_mips.h" +#include "utils/mips/constants_mips.h" + +namespace art { + +namespace mips { + +IntrinsicLocationsBuilderMIPS::IntrinsicLocationsBuilderMIPS(CodeGeneratorMIPS* codegen) + : arena_(codegen->GetGraph()->GetArena()) { +} + +MipsAssembler* IntrinsicCodeGeneratorMIPS::GetAssembler() { + return reinterpret_cast<MipsAssembler*>(codegen_->GetAssembler()); +} + +ArenaAllocator* IntrinsicCodeGeneratorMIPS::GetAllocator() { + return codegen_->GetGraph()->GetArena(); +} + +#define __ codegen->GetAssembler()-> + +static void MoveFromReturnRegister(Location trg, + Primitive::Type type, + CodeGeneratorMIPS* codegen) { + if (!trg.IsValid()) { + DCHECK_EQ(type, Primitive::kPrimVoid); + return; + } + + DCHECK_NE(type, Primitive::kPrimVoid); + + if (Primitive::IsIntegralType(type) || type == Primitive::kPrimNot) { + Register trg_reg = trg.AsRegister<Register>(); + if (trg_reg != V0) { + __ Move(V0, trg_reg); + } + } else { + FRegister trg_reg = trg.AsFpuRegister<FRegister>(); + if (trg_reg != F0) { + if (type == Primitive::kPrimFloat) { + __ MovS(F0, trg_reg); + } else { + __ MovD(F0, trg_reg); + } + } + } +} + +static void MoveArguments(HInvoke* invoke, CodeGeneratorMIPS* codegen) { + InvokeDexCallingConventionVisitorMIPS calling_convention_visitor; + IntrinsicVisitor::MoveArguments(invoke, codegen, &calling_convention_visitor); +} + +// Slow-path for fallback (calling the managed code to handle the +// intrinsic) in an intrinsified call. This will copy the arguments +// into the positions for a regular call. +// +// Note: The actual parameters are required to be in the locations +// given by the invoke's location summary. If an intrinsic +// modifies those locations before a slowpath call, they must be +// restored! +class IntrinsicSlowPathMIPS : public SlowPathCodeMIPS { + public: + explicit IntrinsicSlowPathMIPS(HInvoke* invoke) : invoke_(invoke) { } + + void EmitNativeCode(CodeGenerator* codegen_in) OVERRIDE { + CodeGeneratorMIPS* codegen = down_cast<CodeGeneratorMIPS*>(codegen_in); + + __ Bind(GetEntryLabel()); + + SaveLiveRegisters(codegen, invoke_->GetLocations()); + + MoveArguments(invoke_, codegen); + + if (invoke_->IsInvokeStaticOrDirect()) { + codegen->GenerateStaticOrDirectCall(invoke_->AsInvokeStaticOrDirect(), + Location::RegisterLocation(A0)); + codegen->RecordPcInfo(invoke_, invoke_->GetDexPc(), this); + } else { + UNIMPLEMENTED(FATAL) << "Non-direct intrinsic slow-path not yet implemented"; + UNREACHABLE(); + } + + // Copy the result back to the expected output. + Location out = invoke_->GetLocations()->Out(); + if (out.IsValid()) { + DCHECK(out.IsRegister()); // TODO: Replace this when we support output in memory. + DCHECK(!invoke_->GetLocations()->GetLiveRegisters()->ContainsCoreRegister(out.reg())); + MoveFromReturnRegister(out, invoke_->GetType(), codegen); + } + + RestoreLiveRegisters(codegen, invoke_->GetLocations()); + __ B(GetExitLabel()); + } + + const char* GetDescription() const OVERRIDE { return "IntrinsicSlowPathMIPS"; } + + private: + // The instruction where this slow path is happening. + HInvoke* const invoke_; + + DISALLOW_COPY_AND_ASSIGN(IntrinsicSlowPathMIPS); +}; + +#undef __ + +bool IntrinsicLocationsBuilderMIPS::TryDispatch(HInvoke* invoke) { + Dispatch(invoke); + LocationSummary* res = invoke->GetLocations(); + return res != nullptr && res->Intrinsified(); +} + +#define __ assembler-> + +// Unimplemented intrinsics. + +#define UNIMPLEMENTED_INTRINSIC(Name) \ +void IntrinsicLocationsBuilderMIPS::Visit ## Name(HInvoke* invoke ATTRIBUTE_UNUSED) { \ +} \ +void IntrinsicCodeGeneratorMIPS::Visit ## Name(HInvoke* invoke ATTRIBUTE_UNUSED) { \ +} + +UNIMPLEMENTED_INTRINSIC(IntegerReverse) +UNIMPLEMENTED_INTRINSIC(LongReverse) +UNIMPLEMENTED_INTRINSIC(ShortReverseBytes) +UNIMPLEMENTED_INTRINSIC(IntegerReverseBytes) +UNIMPLEMENTED_INTRINSIC(LongReverseBytes) +UNIMPLEMENTED_INTRINSIC(LongNumberOfLeadingZeros) +UNIMPLEMENTED_INTRINSIC(IntegerNumberOfLeadingZeros) +UNIMPLEMENTED_INTRINSIC(FloatIntBitsToFloat) +UNIMPLEMENTED_INTRINSIC(DoubleLongBitsToDouble) +UNIMPLEMENTED_INTRINSIC(FloatFloatToRawIntBits) +UNIMPLEMENTED_INTRINSIC(DoubleDoubleToRawLongBits) +UNIMPLEMENTED_INTRINSIC(MathAbsDouble) +UNIMPLEMENTED_INTRINSIC(MathAbsFloat) +UNIMPLEMENTED_INTRINSIC(MathAbsInt) +UNIMPLEMENTED_INTRINSIC(MathAbsLong) +UNIMPLEMENTED_INTRINSIC(MathMinDoubleDouble) +UNIMPLEMENTED_INTRINSIC(MathMinFloatFloat) +UNIMPLEMENTED_INTRINSIC(MathMaxDoubleDouble) +UNIMPLEMENTED_INTRINSIC(MathMaxFloatFloat) +UNIMPLEMENTED_INTRINSIC(MathMinIntInt) +UNIMPLEMENTED_INTRINSIC(MathMinLongLong) +UNIMPLEMENTED_INTRINSIC(MathMaxIntInt) +UNIMPLEMENTED_INTRINSIC(MathMaxLongLong) +UNIMPLEMENTED_INTRINSIC(MathSqrt) +UNIMPLEMENTED_INTRINSIC(MathCeil) +UNIMPLEMENTED_INTRINSIC(MathFloor) +UNIMPLEMENTED_INTRINSIC(MathRint) +UNIMPLEMENTED_INTRINSIC(MathRoundDouble) +UNIMPLEMENTED_INTRINSIC(MathRoundFloat) +UNIMPLEMENTED_INTRINSIC(MemoryPeekByte) +UNIMPLEMENTED_INTRINSIC(MemoryPeekIntNative) +UNIMPLEMENTED_INTRINSIC(MemoryPeekLongNative) +UNIMPLEMENTED_INTRINSIC(MemoryPeekShortNative) +UNIMPLEMENTED_INTRINSIC(MemoryPokeByte) +UNIMPLEMENTED_INTRINSIC(MemoryPokeIntNative) +UNIMPLEMENTED_INTRINSIC(MemoryPokeLongNative) +UNIMPLEMENTED_INTRINSIC(MemoryPokeShortNative) +UNIMPLEMENTED_INTRINSIC(ThreadCurrentThread) +UNIMPLEMENTED_INTRINSIC(UnsafeGet) +UNIMPLEMENTED_INTRINSIC(UnsafeGetVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafeGetLong) +UNIMPLEMENTED_INTRINSIC(UnsafeGetLongVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafeGetObject) +UNIMPLEMENTED_INTRINSIC(UnsafeGetObjectVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafePut) +UNIMPLEMENTED_INTRINSIC(UnsafePutOrdered) +UNIMPLEMENTED_INTRINSIC(UnsafePutVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafePutObject) +UNIMPLEMENTED_INTRINSIC(UnsafePutObjectOrdered) +UNIMPLEMENTED_INTRINSIC(UnsafePutObjectVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafePutLong) +UNIMPLEMENTED_INTRINSIC(UnsafePutLongOrdered) +UNIMPLEMENTED_INTRINSIC(UnsafePutLongVolatile) +UNIMPLEMENTED_INTRINSIC(UnsafeCASInt) +UNIMPLEMENTED_INTRINSIC(UnsafeCASLong) +UNIMPLEMENTED_INTRINSIC(UnsafeCASObject) +UNIMPLEMENTED_INTRINSIC(StringCharAt) +UNIMPLEMENTED_INTRINSIC(StringCompareTo) +UNIMPLEMENTED_INTRINSIC(StringEquals) +UNIMPLEMENTED_INTRINSIC(StringIndexOf) +UNIMPLEMENTED_INTRINSIC(StringIndexOfAfter) +UNIMPLEMENTED_INTRINSIC(StringNewStringFromBytes) +UNIMPLEMENTED_INTRINSIC(StringNewStringFromChars) +UNIMPLEMENTED_INTRINSIC(StringNewStringFromString) +UNIMPLEMENTED_INTRINSIC(LongRotateLeft) +UNIMPLEMENTED_INTRINSIC(LongRotateRight) +UNIMPLEMENTED_INTRINSIC(LongNumberOfTrailingZeros) +UNIMPLEMENTED_INTRINSIC(IntegerRotateLeft) +UNIMPLEMENTED_INTRINSIC(IntegerRotateRight) +UNIMPLEMENTED_INTRINSIC(IntegerNumberOfTrailingZeros) + +UNIMPLEMENTED_INTRINSIC(ReferenceGetReferent) +UNIMPLEMENTED_INTRINSIC(StringGetCharsNoCheck) +UNIMPLEMENTED_INTRINSIC(SystemArrayCopyChar) +UNIMPLEMENTED_INTRINSIC(SystemArrayCopy) + +#undef UNIMPLEMENTED_INTRINSIC + +#undef __ + +} // namespace mips +} // namespace art diff --git a/compiler/optimizing/intrinsics_mips.h b/compiler/optimizing/intrinsics_mips.h new file mode 100644 index 0000000000..c71b3c68b7 --- /dev/null +++ b/compiler/optimizing/intrinsics_mips.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 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_OPTIMIZING_INTRINSICS_MIPS_H_ +#define ART_COMPILER_OPTIMIZING_INTRINSICS_MIPS_H_ + +#include "intrinsics.h" + +namespace art { + +class ArenaAllocator; +class HInvokeStaticOrDirect; +class HInvokeVirtual; + +namespace mips { + +class CodeGeneratorMIPS; +class MipsAssembler; + +class IntrinsicLocationsBuilderMIPS FINAL : public IntrinsicVisitor { + public: + explicit IntrinsicLocationsBuilderMIPS(CodeGeneratorMIPS* codegen); + + // Define visitor methods. + +#define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache) \ + void Visit ## Name(HInvoke* invoke) OVERRIDE; +#include "intrinsics_list.h" +INTRINSICS_LIST(OPTIMIZING_INTRINSICS) +#undef INTRINSICS_LIST +#undef OPTIMIZING_INTRINSICS + + // Check whether an invoke is an intrinsic, and if so, create a location summary. Returns whether + // a corresponding LocationSummary with the intrinsified_ flag set was generated and attached to + // the invoke. + bool TryDispatch(HInvoke* invoke); + + private: + ArenaAllocator* arena_; + + DISALLOW_COPY_AND_ASSIGN(IntrinsicLocationsBuilderMIPS); +}; + +class IntrinsicCodeGeneratorMIPS FINAL : public IntrinsicVisitor { + public: + explicit IntrinsicCodeGeneratorMIPS(CodeGeneratorMIPS* codegen) : codegen_(codegen) {} + + // Define visitor methods. + +#define OPTIMIZING_INTRINSICS(Name, IsStatic, NeedsEnvironmentOrCache) \ + void Visit ## Name(HInvoke* invoke) OVERRIDE; +#include "intrinsics_list.h" +INTRINSICS_LIST(OPTIMIZING_INTRINSICS) +#undef INTRINSICS_LIST +#undef OPTIMIZING_INTRINSICS + + private: + MipsAssembler* GetAssembler(); + + ArenaAllocator* GetAllocator(); + + CodeGeneratorMIPS* codegen_; + + DISALLOW_COPY_AND_ASSIGN(IntrinsicCodeGeneratorMIPS); +}; + +} // namespace mips +} // namespace art + +#endif // ART_COMPILER_OPTIMIZING_INTRINSICS_MIPS_H_ diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc index 56c4177b29..05c7eb02d9 100644 --- a/compiler/optimizing/intrinsics_mips64.cc +++ b/compiler/optimizing/intrinsics_mips64.cc @@ -272,7 +272,9 @@ void IntrinsicCodeGeneratorMIPS64::VisitShortReverseBytes(HInvoke* invoke) { GenReverseBytes(invoke->GetLocations(), Primitive::kPrimShort, GetAssembler()); } -static void GenNumberOfLeadingZeroes(LocationSummary* locations, bool is64bit, Mips64Assembler* assembler) { +static void GenNumberOfLeadingZeroes(LocationSummary* locations, + bool is64bit, + Mips64Assembler* assembler) { GpuRegister in = locations->InAt(0).AsRegister<GpuRegister>(); GpuRegister out = locations->Out().AsRegister<GpuRegister>(); @@ -301,7 +303,9 @@ void IntrinsicCodeGeneratorMIPS64::VisitLongNumberOfLeadingZeros(HInvoke* invoke GenNumberOfLeadingZeroes(invoke->GetLocations(), true, GetAssembler()); } -static void GenNumberOfTrailingZeroes(LocationSummary* locations, bool is64bit, Mips64Assembler* assembler) { +static void GenNumberOfTrailingZeroes(LocationSummary* locations, + bool is64bit, + Mips64Assembler* assembler) { Location in = locations->InAt(0); Location out = locations->Out(); @@ -383,7 +387,7 @@ void IntrinsicCodeGeneratorMIPS64::VisitIntegerRotateRight(HInvoke* invoke) { GenRotateRight(invoke, Primitive::kPrimInt, GetAssembler()); } -// int java.lang.Long.rotateRight(long i, int distance) +// long java.lang.Long.rotateRight(long i, int distance) void IntrinsicLocationsBuilderMIPS64::VisitLongRotateRight(HInvoke* invoke) { LocationSummary* locations = new (arena_) LocationSummary(invoke, LocationSummary::kNoCall, @@ -446,7 +450,7 @@ void IntrinsicCodeGeneratorMIPS64::VisitIntegerRotateLeft(HInvoke* invoke) { GenRotateLeft(invoke, Primitive::kPrimInt, GetAssembler()); } -// int java.lang.Long.rotateLeft(long i, int distance) +// long java.lang.Long.rotateLeft(long i, int distance) void IntrinsicLocationsBuilderMIPS64::VisitLongRotateLeft(HInvoke* invoke) { LocationSummary* locations = new (arena_) LocationSummary(invoke, LocationSummary::kNoCall, @@ -754,17 +758,19 @@ void IntrinsicCodeGeneratorMIPS64::VisitMathSqrt(HInvoke* invoke) { __ SqrtD(out, in); } -static void CreateFPToFP(ArenaAllocator* arena, HInvoke* invoke) { +static void CreateFPToFP(ArenaAllocator* arena, + HInvoke* invoke, + Location::OutputOverlap overlaps = Location::kOutputOverlap) { LocationSummary* locations = new (arena) LocationSummary(invoke, LocationSummary::kNoCall, kIntrinsified); locations->SetInAt(0, Location::RequiresFpuRegister()); - locations->SetOut(Location::RequiresFpuRegister(), Location::kNoOutputOverlap); + locations->SetOut(Location::RequiresFpuRegister(), overlaps); } // double java.lang.Math.rint(double) void IntrinsicLocationsBuilderMIPS64::VisitMathRint(HInvoke* invoke) { - CreateFPToFP(arena_, invoke); + CreateFPToFP(arena_, invoke, Location::kNoOutputOverlap); } void IntrinsicCodeGeneratorMIPS64::VisitMathRint(HInvoke* invoke) { @@ -788,15 +794,22 @@ const constexpr uint16_t kFPLeaveUnchanged = kPositiveZero | kQuietNaN | kSignalingNaN; -void IntrinsicCodeGeneratorMIPS64::VisitMathFloor(HInvoke* invoke) { - LocationSummary* locations = invoke->GetLocations(); - Mips64Assembler* assembler = GetAssembler(); +enum FloatRoundingMode { + kFloor, + kCeil, +}; + +static void GenRoundingMode(LocationSummary* locations, + FloatRoundingMode mode, + Mips64Assembler* assembler) { FpuRegister in = locations->InAt(0).AsFpuRegister<FpuRegister>(); FpuRegister out = locations->Out().AsFpuRegister<FpuRegister>(); + DCHECK_NE(in, out); + Label done; - // double floor(double in) { + // double floor/ceil(double in) { // if in.isNaN || in.isInfinite || in.isZero { // return in; // } @@ -806,19 +819,23 @@ void IntrinsicCodeGeneratorMIPS64::VisitMathFloor(HInvoke* invoke) { __ MovD(out, in); __ Bnezc(AT, &done); - // Long outLong = floor(in); + // Long outLong = floor/ceil(in); // if outLong == Long.MAX_VALUE { - // // floor() has almost certainly returned a value which - // // can't be successfully represented as a signed 64-bit - // // number. Java expects that the input value will be - // // returned in these cases. - // // There is also a small probability that floor(in) - // // correctly truncates the input value to Long.MAX_VALUE. In - // // that case, this exception handling code still does the - // // correct thing. + // // floor()/ceil() has almost certainly returned a value + // // which can't be successfully represented as a signed + // // 64-bit number. Java expects that the input value will + // // be returned in these cases. + // // There is also a small probability that floor(in)/ceil(in) + // // correctly truncates/rounds up the input value to + // // Long.MAX_VALUE. In that case, this exception handling + // // code still does the correct thing. // return in; // } - __ FloorLD(out, in); + if (mode == kFloor) { + __ FloorLD(out, in); + } else if (mode == kCeil) { + __ CeilLD(out, in); + } __ Dmfc1(AT, out); __ MovD(out, in); __ LoadConst64(TMP, kPrimLongMax); @@ -832,53 +849,17 @@ void IntrinsicCodeGeneratorMIPS64::VisitMathFloor(HInvoke* invoke) { // } } +void IntrinsicCodeGeneratorMIPS64::VisitMathFloor(HInvoke* invoke) { + GenRoundingMode(invoke->GetLocations(), kFloor, GetAssembler()); +} + // double java.lang.Math.ceil(double) void IntrinsicLocationsBuilderMIPS64::VisitMathCeil(HInvoke* invoke) { CreateFPToFP(arena_, invoke); } void IntrinsicCodeGeneratorMIPS64::VisitMathCeil(HInvoke* invoke) { - LocationSummary* locations = invoke->GetLocations(); - Mips64Assembler* assembler = GetAssembler(); - FpuRegister in = locations->InAt(0).AsFpuRegister<FpuRegister>(); - FpuRegister out = locations->Out().AsFpuRegister<FpuRegister>(); - - Label done; - - // double ceil(double in) { - // if in.isNaN || in.isInfinite || in.isZero { - // return in; - // } - __ ClassD(out, in); - __ Dmfc1(AT, out); - __ Andi(AT, AT, kFPLeaveUnchanged); // +0.0 | +Inf | -0.0 | -Inf | qNaN | sNaN - __ MovD(out, in); - __ Bnezc(AT, &done); - - // Long outLong = ceil(in); - // if outLong == Long.MAX_VALUE { - // // ceil() has almost certainly returned a value which - // // can't be successfully represented as a signed 64-bit - // // number. Java expects that the input value will be - // // returned in these cases. - // // There is also a small probability that ceil(in) - // // correctly rounds up the input value to Long.MAX_VALUE. In - // // that case, this exception handling code still does the - // // correct thing. - // return in; - // } - __ CeilLD(out, in); - __ Dmfc1(AT, out); - __ MovD(out, in); - __ LoadConst64(TMP, kPrimLongMax); - __ Beqc(AT, TMP, &done); - - // double out = outLong; - // return out; - __ Dmtc1(AT, out); - __ Cvtdl(out, out); - __ Bind(&done); - // } + GenRoundingMode(invoke->GetLocations(), kCeil, GetAssembler()); } // byte libcore.io.Memory.peekByte(long address) @@ -1246,6 +1227,91 @@ void IntrinsicCodeGeneratorMIPS64::VisitUnsafePutLongVolatile(HInvoke* invoke) { GenUnsafePut(invoke->GetLocations(), Primitive::kPrimLong, true, false, codegen_); } +static void CreateIntIntIntIntIntToInt(ArenaAllocator* arena, HInvoke* invoke) { + LocationSummary* locations = new (arena) LocationSummary(invoke, + LocationSummary::kNoCall, + kIntrinsified); + locations->SetInAt(0, Location::NoLocation()); // Unused receiver. + locations->SetInAt(1, Location::RequiresRegister()); + locations->SetInAt(2, Location::RequiresRegister()); + locations->SetInAt(3, Location::RequiresRegister()); + locations->SetInAt(4, Location::RequiresRegister()); + + locations->SetOut(Location::RequiresRegister()); +} + +static void GenCas(LocationSummary* locations, Primitive::Type type, CodeGeneratorMIPS64* codegen) { + Mips64Assembler* assembler = codegen->GetAssembler(); + GpuRegister base = locations->InAt(1).AsRegister<GpuRegister>(); + GpuRegister offset = locations->InAt(2).AsRegister<GpuRegister>(); + GpuRegister expected = locations->InAt(3).AsRegister<GpuRegister>(); + GpuRegister value = locations->InAt(4).AsRegister<GpuRegister>(); + GpuRegister out = locations->Out().AsRegister<GpuRegister>(); + + DCHECK_NE(base, out); + DCHECK_NE(offset, out); + DCHECK_NE(expected, out); + + // do { + // tmp_value = [tmp_ptr] - expected; + // } while (tmp_value == 0 && failure([tmp_ptr] <- r_new_value)); + // result = tmp_value != 0; + + Label loop_head, exit_loop; + __ Daddu(TMP, base, offset); + __ Sync(0); + __ Bind(&loop_head); + if (type == Primitive::kPrimLong) { + __ Lld(out, TMP); + } else { + __ Ll(out, TMP); + } + __ Dsubu(out, out, expected); // If we didn't get the 'expected' + __ Sltiu(out, out, 1); // value, set 'out' to false, and + __ Beqzc(out, &exit_loop); // return. + __ Move(out, value); // Use 'out' for the 'store conditional' instruction. + // If we use 'value' directly, we would lose 'value' + // in the case that the store fails. Whether the + // store succeeds, or fails, it will load the + // correct boolean value into the 'out' register. + if (type == Primitive::kPrimLong) { + __ Scd(out, TMP); + } else { + __ Sc(out, TMP); + } + __ Beqzc(out, &loop_head); // If we couldn't do the read-modify-write + // cycle atomically then retry. + __ Bind(&exit_loop); + __ Sync(0); +} + +// boolean sun.misc.Unsafe.compareAndSwapInt(Object o, long offset, int expected, int x) +void IntrinsicLocationsBuilderMIPS64::VisitUnsafeCASInt(HInvoke* invoke) { + CreateIntIntIntIntIntToInt(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitUnsafeCASInt(HInvoke* invoke) { + GenCas(invoke->GetLocations(), Primitive::kPrimInt, codegen_); +} + +// boolean sun.misc.Unsafe.compareAndSwapLong(Object o, long offset, long expected, long x) +void IntrinsicLocationsBuilderMIPS64::VisitUnsafeCASLong(HInvoke* invoke) { + CreateIntIntIntIntIntToInt(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitUnsafeCASLong(HInvoke* invoke) { + GenCas(invoke->GetLocations(), Primitive::kPrimLong, codegen_); +} + +// boolean sun.misc.Unsafe.compareAndSwapObject(Object o, long offset, Object expected, Object x) +void IntrinsicLocationsBuilderMIPS64::VisitUnsafeCASObject(HInvoke* invoke) { + CreateIntIntIntIntIntToInt(arena_, invoke); +} + +void IntrinsicCodeGeneratorMIPS64::VisitUnsafeCASObject(HInvoke* invoke) { + GenCas(invoke->GetLocations(), Primitive::kPrimNot, codegen_); +} + // char java.lang.String.charAt(int index) void IntrinsicLocationsBuilderMIPS64::VisitStringCharAt(HInvoke* invoke) { LocationSummary* locations = new (arena_) LocationSummary(invoke, @@ -1521,9 +1587,6 @@ void IntrinsicCodeGeneratorMIPS64::Visit ## Name(HInvoke* invoke ATTRIBUTE_UNUSE UNIMPLEMENTED_INTRINSIC(MathRoundDouble) UNIMPLEMENTED_INTRINSIC(MathRoundFloat) -UNIMPLEMENTED_INTRINSIC(UnsafeCASInt) -UNIMPLEMENTED_INTRINSIC(UnsafeCASLong) -UNIMPLEMENTED_INTRINSIC(UnsafeCASObject) UNIMPLEMENTED_INTRINSIC(StringEquals) UNIMPLEMENTED_INTRINSIC(ReferenceGetReferent) diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc index 90f28e511e..6fbb6823d6 100644 --- a/compiler/optimizing/load_store_elimination.cc +++ b/compiler/optimizing/load_store_elimination.cc @@ -59,7 +59,7 @@ class ReferenceInfo : public ArenaObject<kArenaAllocMisc> { (use->IsInstanceFieldSet() && (reference_ == use->InputAt(1))) || (use->IsUnresolvedInstanceFieldSet() && (reference_ == use->InputAt(1))) || (use->IsStaticFieldSet() && (reference_ == use->InputAt(1))) || - (use->IsUnresolvedStaticFieldSet() && (reference_ == use->InputAt(1))) || + (use->IsUnresolvedStaticFieldSet() && (reference_ == use->InputAt(0))) || (use->IsArraySet() && (reference_ == use->InputAt(2)))) { // reference_ is merged to a phi, passed to a callee, or stored to heap. // reference_ isn't the only name that can refer to its value anymore. diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 98c3096cae..68fb0acf7f 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -574,6 +574,17 @@ bool HLoopInformation::IsIn(const HLoopInformation& other) const { return other.blocks_.IsBitSet(header_->GetBlockId()); } +bool HLoopInformation::IsLoopInvariant(HInstruction* instruction, bool must_dominate) const { + HLoopInformation* other_loop = instruction->GetBlock()->GetLoopInformation(); + if (other_loop != this && (other_loop == nullptr || !other_loop->IsIn(*this))) { + if (must_dominate) { + return instruction->GetBlock()->Dominates(GetHeader()); + } + return true; + } + return false; +} + size_t HLoopInformation::GetLifetimeEnd() const { size_t last_position = 0; for (HBasicBlock* back_edge : GetBackEdges()) { @@ -1641,7 +1652,8 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { // Update the meta information surrounding blocks: // (1) the graph they are now in, // (2) the reverse post order of that graph, - // (3) the potential loop information they are now in. + // (3) the potential loop information they are now in, + // (4) try block membership. // We don't add the entry block, the exit block, and the first block, which // has been merged with `at`. @@ -1657,41 +1669,47 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { size_t index_of_at = IndexOfElement(outer_graph->reverse_post_order_, at); MakeRoomFor(&outer_graph->reverse_post_order_, blocks_added, index_of_at); - // Do a reverse post order of the blocks in the callee and do (1), (2), - // and (3) to the blocks that apply. - HLoopInformation* info = at->GetLoopInformation(); + HLoopInformation* loop_info = at->GetLoopInformation(); + // Copy TryCatchInformation if `at` is a try block, not if it is a catch block. + TryCatchInformation* try_catch_info = at->IsTryBlock() ? at->GetTryCatchInformation() : nullptr; + + // Do a reverse post order of the blocks in the callee and do (1), (2), (3) + // and (4) to the blocks that apply. for (HReversePostOrderIterator it(*this); !it.Done(); it.Advance()) { HBasicBlock* current = it.Current(); if (current != exit_block_ && current != entry_block_ && current != first) { DCHECK(!current->IsInLoop()); + DCHECK(current->GetTryCatchInformation() == nullptr); DCHECK(current->GetGraph() == this); current->SetGraph(outer_graph); outer_graph->AddBlock(current); outer_graph->reverse_post_order_[++index_of_at] = current; - if (info != nullptr) { - current->SetLoopInformation(info); + if (loop_info != nullptr) { + current->SetLoopInformation(loop_info); for (HLoopInformationOutwardIterator loop_it(*at); !loop_it.Done(); loop_it.Advance()) { loop_it.Current()->Add(current); } } + current->SetTryCatchInformation(try_catch_info); } } - // Do (1), (2), and (3) to `to`. + // Do (1), (2), (3) and (4) to `to`. to->SetGraph(outer_graph); outer_graph->AddBlock(to); outer_graph->reverse_post_order_[++index_of_at] = to; - if (info != nullptr) { - to->SetLoopInformation(info); + if (loop_info != nullptr) { + to->SetLoopInformation(loop_info); for (HLoopInformationOutwardIterator loop_it(*at); !loop_it.Done(); loop_it.Advance()) { loop_it.Current()->Add(to); } - if (info->IsBackEdge(*at)) { + if (loop_info->IsBackEdge(*at)) { // Only `to` can become a back edge, as the inlined blocks // are predecessors of `to`. - info->ReplaceBackEdge(at, to); + loop_info->ReplaceBackEdge(at, to); } } + to->SetTryCatchInformation(try_catch_info); } // Update the next instruction id of the outer graph, so that instructions @@ -1911,8 +1929,8 @@ bool HInvoke::NeedsEnvironment() const { return !opt.GetDoesNotNeedEnvironment(); } -bool HInvokeStaticOrDirect::NeedsDexCache() const { - if (IsRecursive() || IsStringInit()) { +bool HInvokeStaticOrDirect::NeedsDexCacheOfDeclaringClass() const { + if (GetMethodLoadKind() != MethodLoadKind::kDexCacheViaMethod) { return false; } if (!IsIntrinsic()) { diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 7aa933ddf0..0f2c1cffee 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -551,6 +551,12 @@ class HLoopInformation : public ArenaObject<kArenaAllocLoopInfo> { // Note that `other` *must* be populated before entering this function. bool IsIn(const HLoopInformation& other) const; + // Returns true if instruction is not defined within this loop or any loop nested inside + // this loop. If must_dominate is set, only definitions that actually dominate the loop + // header can be invariant. Otherwise, any definition outside the loop, including + // definitions that appear after the loop, is invariant. + bool IsLoopInvariant(HInstruction* instruction, bool must_dominate) const; + const ArenaBitVector& GetBlocks() const { return blocks_; } void Add(HBasicBlock* block); @@ -1980,7 +1986,9 @@ class HInstruction : public ArenaObject<kArenaAllocInstruction> { return NeedsEnvironment() || IsLoadClass() || IsLoadString(); } - virtual bool NeedsDexCache() const { return false; } + // Returns whether the code generation of the instruction will require to have access + // to the dex cache of the current method's declaring class via the current method. + virtual bool NeedsDexCacheOfDeclaringClass() const { return false; } // Does this instruction have any use in an environment before // control flow hits 'other'? @@ -3368,15 +3376,15 @@ class HInvokeStaticOrDirect : public HInvoke { }; struct DispatchInfo { - const MethodLoadKind method_load_kind; - const CodePtrLocation code_ptr_location; + MethodLoadKind method_load_kind; + CodePtrLocation code_ptr_location; // The method load data holds // - thread entrypoint offset for kStringInit method if this is a string init invoke. // Note that there are multiple string init methods, each having its own offset. // - the method address for kDirectAddress // - the dex cache arrays offset for kDexCachePcRel. - const uint64_t method_load_data; - const uint64_t direct_code_ptr; + uint64_t method_load_data; + uint64_t direct_code_ptr; }; HInvokeStaticOrDirect(ArenaAllocator* arena, @@ -3405,6 +3413,10 @@ class HInvokeStaticOrDirect : public HInvoke { target_method_(target_method), dispatch_info_(dispatch_info) {} + void SetDispatchInfo(const DispatchInfo& dispatch_info) { + dispatch_info_ = dispatch_info; + } + bool CanDoImplicitNullCheckOn(HInstruction* obj ATTRIBUTE_UNUSED) const OVERRIDE { // We access the method via the dex cache so we can't do an implicit null check. // TODO: for intrinsics we can generate implicit null checks. @@ -3419,11 +3431,13 @@ class HInvokeStaticOrDirect : public HInvoke { MethodLoadKind GetMethodLoadKind() const { return dispatch_info_.method_load_kind; } CodePtrLocation GetCodePtrLocation() const { return dispatch_info_.code_ptr_location; } bool IsRecursive() const { return GetMethodLoadKind() == MethodLoadKind::kRecursive; } - bool NeedsDexCache() const OVERRIDE; + bool NeedsDexCacheOfDeclaringClass() const OVERRIDE; bool IsStringInit() const { return GetMethodLoadKind() == MethodLoadKind::kStringInit; } uint32_t GetCurrentMethodInputIndex() const { return GetNumberOfArguments(); } bool HasMethodAddress() const { return GetMethodLoadKind() == MethodLoadKind::kDirectAddress; } - bool HasPcRelDexCache() const { return GetMethodLoadKind() == MethodLoadKind::kDexCachePcRelative; } + bool HasPcRelDexCache() const { + return GetMethodLoadKind() == MethodLoadKind::kDexCachePcRelative; + } bool HasDirectCodePtr() const { return GetCodePtrLocation() == CodePtrLocation::kCallDirect; } MethodReference GetTargetMethod() const { return target_method_; } @@ -4736,6 +4750,9 @@ class HLoadClass : public HExpression<1> { return generate_clinit_check_; } void SetMustGenerateClinitCheck(bool generate_clinit_check) { + // The entrypoint the code generator is going to call does not do + // clinit of the class. + DCHECK(!NeedsAccessCheck()); generate_clinit_check_ = generate_clinit_check; } @@ -4765,7 +4782,7 @@ class HLoadClass : public HExpression<1> { const DexFile& GetDexFile() { return dex_file_; } - bool NeedsDexCache() const OVERRIDE { return !is_referrers_class_; } + bool NeedsDexCacheOfDeclaringClass() const OVERRIDE { return !is_referrers_class_; } static SideEffects SideEffectsForArchRuntimeCalls() { return SideEffects::CanTriggerGC(); @@ -4807,7 +4824,7 @@ class HLoadString : public HExpression<1> { // TODO: Can we deopt or debug when we resolve a string? bool NeedsEnvironment() const OVERRIDE { return false; } - bool NeedsDexCache() const OVERRIDE { return true; } + bool NeedsDexCacheOfDeclaringClass() const OVERRIDE { return true; } bool CanBeNull() const OVERRIDE { return false; } static SideEffects SideEffectsForArchRuntimeCalls() { diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index d6f2543890..8cb2cfc816 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -63,6 +63,7 @@ #include "prepare_for_register_allocation.h" #include "reference_type_propagation.h" #include "register_allocator.h" +#include "sharpening.h" #include "side_effects_analysis.h" #include "ssa_builder.h" #include "ssa_phi_elimination.h" @@ -378,6 +379,7 @@ static void RunOptimizations(HOptimization* optimizations[], } static void MaybeRunInliner(HGraph* graph, + CodeGenerator* codegen, CompilerDriver* driver, OptimizingCompilerStats* stats, const DexCompilationUnit& dex_compilation_unit, @@ -392,7 +394,7 @@ static void MaybeRunInliner(HGraph* graph, ArenaAllocator* arena = graph->GetArena(); HInliner* inliner = new (arena) HInliner( - graph, dex_compilation_unit, dex_compilation_unit, driver, handles, stats); + graph, codegen, dex_compilation_unit, dex_compilation_unit, driver, handles, stats); ReferenceTypePropagation* type_propagation = new (arena) ReferenceTypePropagation(graph, handles, "reference_type_propagation_after_inlining"); @@ -445,6 +447,7 @@ static void RunArchOptimizations(InstructionSet instruction_set, } static void RunOptimizations(HGraph* graph, + CodeGenerator* codegen, CompilerDriver* driver, OptimizingCompilerStats* stats, const DexCompilationUnit& dex_compilation_unit, @@ -467,6 +470,7 @@ static void RunOptimizations(HGraph* graph, BoundsCheckElimination* bce = new (arena) BoundsCheckElimination(graph, induction); ReferenceTypePropagation* type_propagation = new (arena) ReferenceTypePropagation(graph, handles); + HSharpening* sharpening = new (arena) HSharpening(graph, codegen, dex_compilation_unit, driver); InstructionSimplifier* simplify2 = new (arena) InstructionSimplifier( graph, stats, "instruction_simplifier_after_types"); InstructionSimplifier* simplify3 = new (arena) InstructionSimplifier( @@ -481,12 +485,15 @@ static void RunOptimizations(HGraph* graph, fold1, simplify1, type_propagation, + sharpening, dce1, simplify2 }; RunOptimizations(optimizations1, arraysize(optimizations1), pass_observer); + MaybeRunInliner(graph, codegen, driver, stats, dex_compilation_unit, pass_observer, handles); + // TODO: Update passes incompatible with try/catch so we have the same // pipeline for all methods. if (graph->HasTryCatch()) { @@ -502,8 +509,6 @@ static void RunOptimizations(HGraph* graph, RunOptimizations(optimizations2, arraysize(optimizations2), pass_observer); } else { - MaybeRunInliner(graph, driver, stats, dex_compilation_unit, pass_observer, handles); - HOptimization* optimizations2[] = { // BooleanSimplifier depends on the InstructionSimplifier removing // redundant suspend checks to recognize empty blocks. @@ -577,8 +582,13 @@ CompiledMethod* OptimizingCompiler::CompileOptimized(HGraph* graph, ScopedObjectAccess soa(Thread::Current()); StackHandleScopeCollection handles(soa.Self()); soa.Self()->TransitionFromRunnableToSuspended(kNative); - RunOptimizations(graph, compiler_driver, compilation_stats_.get(), - dex_compilation_unit, pass_observer, &handles); + RunOptimizations(graph, + codegen, + compiler_driver, + compilation_stats_.get(), + dex_compilation_unit, + pass_observer, + &handles); AllocateRegisters(graph, codegen, pass_observer); @@ -607,7 +617,7 @@ CompiledMethod* OptimizingCompiler::CompileOptimized(HGraph* graph, codegen->HasEmptyFrame() ? 0 : codegen->GetFrameSize(), codegen->GetCoreSpillMask(), codegen->GetFpuSpillMask(), - &src_mapping_table, + ArrayRef<const SrcMapElem>(src_mapping_table), ArrayRef<const uint8_t>(), // mapping_table. ArrayRef<const uint8_t>(stack_map), ArrayRef<const uint8_t>(), // native_gc_map. @@ -652,7 +662,7 @@ CompiledMethod* OptimizingCompiler::CompileBaseline( codegen->HasEmptyFrame() ? 0 : codegen->GetFrameSize(), codegen->GetCoreSpillMask(), codegen->GetFpuSpillMask(), - &src_mapping_table, + ArrayRef<const SrcMapElem>(src_mapping_table), AlignVectorSize(mapping_table), AlignVectorSize(vmap_table), AlignVectorSize(gc_map), diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc index 26a05da4cb..659da068a9 100644 --- a/compiler/optimizing/reference_type_propagation.cc +++ b/compiler/optimizing/reference_type_propagation.cc @@ -373,12 +373,18 @@ void RTPVisitor::SetClassAsTypeInfo(HInstruction* instr, if (instr->IsInvokeStaticOrDirect() && instr->AsInvokeStaticOrDirect()->IsStringInit()) { // Calls to String.<init> are replaced with a StringFactory. if (kIsDebugBuild) { - ScopedObjectAccess soa(Thread::Current()); + HInvoke* invoke = instr->AsInvoke(); ClassLinker* cl = Runtime::Current()->GetClassLinker(); - mirror::DexCache* dex_cache = cl->FindDexCache( - soa.Self(), instr->AsInvoke()->GetDexFile(), false); - ArtMethod* method = dex_cache->GetResolvedMethod( - instr->AsInvoke()->GetDexMethodIndex(), cl->GetImagePointerSize()); + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<2> hs(soa.Self()); + Handle<mirror::DexCache> dex_cache( + hs.NewHandle(cl->FindDexCache(soa.Self(), invoke->GetDexFile(), false))); + // Use a null loader. We should probably use the compiling method's class loader, + // but then we would need to pass it to RTPVisitor just for this debug check. Since + // the method is from the String class, the null loader is good enough. + Handle<mirror::ClassLoader> loader; + ArtMethod* method = cl->ResolveMethod( + invoke->GetDexFile(), invoke->GetDexMethodIndex(), dex_cache, loader, nullptr, kDirect); DCHECK(method != nullptr); mirror::Class* declaring_class = method->GetDeclaringClass(); DCHECK(declaring_class != nullptr); diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc new file mode 100644 index 0000000000..a128079cdb --- /dev/null +++ b/compiler/optimizing/sharpening.cc @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2015 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 "sharpening.h" + +#include "code_generator.h" +#include "utils/dex_cache_arrays_layout-inl.h" +#include "driver/compiler_driver.h" +#include "nodes.h" +#include "runtime.h" + +namespace art { + +void HSharpening::Run() { + // We don't care about the order of the blocks here. + for (HBasicBlock* block : graph_->GetReversePostOrder()) { + for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { + HInstruction* instruction = it.Current(); + if (instruction->IsInvokeStaticOrDirect()) { + ProcessInvokeStaticOrDirect(instruction->AsInvokeStaticOrDirect()); + } + // TODO: Move the sharpening of invoke-virtual/-interface/-super from HGraphBuilder + // here. Rewrite it to avoid the CompilerDriver's reliance on verifier data + // because we know the type better when inlining. + // TODO: HLoadClass, HLoadString - select PC relative dex cache array access if + // available. + } + } +} + +void HSharpening::ProcessInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) { + if (invoke->IsStringInit()) { + // Not using the dex cache arrays. But we could still try to use a better dispatch... + // TODO: Use direct_method and direct_code for the appropriate StringFactory method. + return; + } + + // TODO: Avoid CompilerDriver. + InvokeType invoke_type = invoke->GetOriginalInvokeType(); + MethodReference target_method(&graph_->GetDexFile(), invoke->GetDexMethodIndex()); + int vtable_idx; + uintptr_t direct_code, direct_method; + bool success = compiler_driver_->ComputeInvokeInfo( + &compilation_unit_, + invoke->GetDexPc(), + false /* update_stats: already updated in builder */, + true /* enable_devirtualization */, + &invoke_type, + &target_method, + &vtable_idx, + &direct_code, + &direct_method); + DCHECK(success); + DCHECK_EQ(invoke_type, invoke->GetInvokeType()); + DCHECK_EQ(target_method.dex_file, invoke->GetTargetMethod().dex_file); + DCHECK_EQ(target_method.dex_method_index, invoke->GetTargetMethod().dex_method_index); + + HInvokeStaticOrDirect::MethodLoadKind method_load_kind; + HInvokeStaticOrDirect::CodePtrLocation code_ptr_location; + uint64_t method_load_data = 0u; + uint64_t direct_code_ptr = 0u; + + HGraph* outer_graph = codegen_->GetGraph(); + if (target_method.dex_file == &outer_graph->GetDexFile() && + target_method.dex_method_index == outer_graph->GetMethodIdx()) { + method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kRecursive; + code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallSelf; + } else { + bool use_pc_relative_instructions = + ((direct_method == 0u || direct_code == static_cast<uintptr_t>(-1))) && + ContainsElement(compiler_driver_->GetDexFilesForOatFile(), target_method.dex_file); + if (direct_method != 0u) { // Should we use a direct pointer to the method? + // Note: For JIT, kDirectAddressWithFixup doesn't make sense at all and while + // kDirectAddress would be fine for image methods, we don't support it at the moment. + DCHECK(!Runtime::Current()->UseJit()); + if (direct_method != static_cast<uintptr_t>(-1)) { // Is the method pointer known now? + method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDirectAddress; + method_load_data = direct_method; + } else { // The direct pointer will be known at link time. + method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDirectAddressWithFixup; + } + } else { // Use dex cache. + DCHECK_EQ(target_method.dex_file, &graph_->GetDexFile()); + if (use_pc_relative_instructions) { // Can we use PC-relative access to the dex cache arrays? + DCHECK(!Runtime::Current()->UseJit()); + method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative; + DexCacheArraysLayout layout(GetInstructionSetPointerSize(codegen_->GetInstructionSet()), + &graph_->GetDexFile()); + method_load_data = layout.MethodOffset(target_method.dex_method_index); + } else { // We must go through the ArtMethod's pointer to resolved methods. + method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod; + } + } + if (direct_code != 0u) { // Should we use a direct pointer to the code? + // Note: For JIT, kCallPCRelative and kCallDirectWithFixup don't make sense at all and + // while kCallDirect would be fine for image methods, we don't support it at the moment. + DCHECK(!Runtime::Current()->UseJit()); + if (direct_code != static_cast<uintptr_t>(-1)) { // Is the code pointer known now? + code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallDirect; + direct_code_ptr = direct_code; + } else if (use_pc_relative_instructions) { + // Use PC-relative calls for invokes within a multi-dex oat file. + code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallPCRelative; + } else { // The direct pointer will be known at link time. + // NOTE: This is used for app->boot calls when compiling an app against + // a relocatable but not yet relocated image. + code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallDirectWithFixup; + } + } else { // We must use the code pointer from the ArtMethod. + code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod; + } + } + + if (graph_->IsDebuggable()) { + // For debuggable apps always use the code pointer from ArtMethod + // so that we don't circumvent instrumentation stubs if installed. + code_ptr_location = HInvokeStaticOrDirect::CodePtrLocation::kCallArtMethod; + } + + HInvokeStaticOrDirect::DispatchInfo desired_dispatch_info = { + method_load_kind, code_ptr_location, method_load_data, direct_code_ptr + }; + HInvokeStaticOrDirect::DispatchInfo dispatch_info = + codegen_->GetSupportedInvokeStaticOrDirectDispatch(desired_dispatch_info, + invoke->GetTargetMethod()); + invoke->SetDispatchInfo(dispatch_info); +} + +} // namespace art diff --git a/compiler/optimizing/sharpening.h b/compiler/optimizing/sharpening.h new file mode 100644 index 0000000000..adae7007dd --- /dev/null +++ b/compiler/optimizing/sharpening.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 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_OPTIMIZING_SHARPENING_H_ +#define ART_COMPILER_OPTIMIZING_SHARPENING_H_ + +#include "optimization.h" + +namespace art { + +class CodeGenerator; +class CompilerDriver; +class DexCompilationUnit; +class HInvokeStaticOrDirect; + +// Optimization that tries to improve the way we dispatch methods and access types, +// fields, etc. Besides actual method sharpening based on receiver type (for example +// virtual->direct), this includes selecting the best available dispatch for +// invoke-static/-direct based on code generator support. +class HSharpening : public HOptimization { + public: + HSharpening(HGraph* graph, + CodeGenerator* codegen, + const DexCompilationUnit& compilation_unit, + CompilerDriver* compiler_driver) + : HOptimization(graph, kSharpeningPassName), + codegen_(codegen), + compilation_unit_(compilation_unit), + compiler_driver_(compiler_driver) { } + + void Run() OVERRIDE; + + static constexpr const char* kSharpeningPassName = "sharpening"; + + private: + void ProcessInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke); + + CodeGenerator* codegen_; + const DexCompilationUnit& compilation_unit_; + CompilerDriver* compiler_driver_; +}; + +} // namespace art + +#endif // ART_COMPILER_OPTIMIZING_SHARPENING_H_ diff --git a/compiler/utils/arm/assembler_arm.cc b/compiler/utils/arm/assembler_arm.cc index 807bedaa04..68e39568bb 100644 --- a/compiler/utils/arm/assembler_arm.cc +++ b/compiler/utils/arm/assembler_arm.cc @@ -16,6 +16,8 @@ #include "assembler_arm.h" +#include <algorithm> + #include "base/bit_utils.h" #include "base/logging.h" #include "entrypoints/quick/quick_entrypoints.h" @@ -922,5 +924,24 @@ uint32_t ArmAssembler::ModifiedImmediate(uint32_t value) { return value | i << 26 | imm3 << 12 | a << 7; } +void ArmAssembler::FinalizeTrackedLabels() { + if (!tracked_labels_.empty()) { + // This array should be sorted, as assembly is generated in linearized order. It isn't + // technically required, but GetAdjustedPosition() used in AdjustLabelPosition() can take + // advantage of it. So ensure that it's actually the case. + DCHECK(std::is_sorted( + tracked_labels_.begin(), + tracked_labels_.end(), + [](const Label* lhs, const Label* rhs) { return lhs->Position() < rhs->Position(); })); + + Label* last_label = nullptr; // Track duplicates, we must not adjust twice. + for (Label* label : tracked_labels_) { + DCHECK_NE(label, last_label); + AdjustLabelPosition(label); + last_label = label; + } + } +} + } // namespace arm } // namespace art diff --git a/compiler/utils/arm/assembler_arm.h b/compiler/utils/arm/assembler_arm.h index d59bc6be40..4a6e6d7c3f 100644 --- a/compiler/utils/arm/assembler_arm.h +++ b/compiler/utils/arm/assembler_arm.h @@ -77,6 +77,45 @@ class Literal { DISALLOW_COPY_AND_ASSIGN(Literal); }; +// Jump table: table of labels emitted after the literals. Similar to literals. +class JumpTable { + public: + explicit JumpTable(std::vector<Label*>&& labels) + : label_(), anchor_label_(), labels_(std::move(labels)) { + } + + uint32_t GetSize() const { + return static_cast<uint32_t>(labels_.size()) * sizeof(uint32_t); + } + + const std::vector<Label*>& GetData() const { + return labels_; + } + + Label* GetLabel() { + return &label_; + } + + const Label* GetLabel() const { + return &label_; + } + + Label* GetAnchorLabel() { + return &anchor_label_; + } + + const Label* GetAnchorLabel() const { + return &anchor_label_; + } + + private: + Label label_; + Label anchor_label_; + std::vector<Label*> labels_; + + DISALLOW_COPY_AND_ASSIGN(JumpTable); +}; + class ShifterOperand { public: ShifterOperand() : type_(kUnknown), rm_(kNoRegister), rs_(kNoRegister), @@ -685,6 +724,8 @@ class ArmAssembler : public Assembler { AddConstant(rd, rd, value, cond, set_cc); } + virtual void CmpConstant(Register rn, int32_t value, Condition cond = AL) = 0; + // Load and Store. May clobber IP. virtual void LoadImmediate(Register rd, int32_t value, Condition cond = AL) = 0; void LoadSImmediate(SRegister sd, float value, Condition cond = AL) { @@ -996,11 +1037,43 @@ class ArmAssembler : public Assembler { b(label); } + // Jump table support. This is split into three functions: + // + // * CreateJumpTable creates the internal metadata to track the jump targets, and emits code to + // load the base address of the jump table. + // + // * EmitJumpTableDispatch emits the code to actually jump, assuming that the right table value + // has been loaded into a register already. + // + // * FinalizeTables emits the jump table into the literal pool. This can only be called after the + // labels for the jump targets have been finalized. + + // Create a jump table for the given labels that will be emitted when finalizing. Create a load + // sequence (or placeholder) that stores the base address into the given register. When the table + // is emitted, offsets will be relative to the location EmitJumpTableDispatch was called on (the + // anchor). + virtual JumpTable* CreateJumpTable(std::vector<Label*>&& labels, Register base_reg) = 0; + + // Emit the jump-table jump, assuming that the right value was loaded into displacement_reg. + virtual void EmitJumpTableDispatch(JumpTable* jump_table, Register displacement_reg) = 0; + + // Bind a Label that needs to be updated by the assembler in FinalizeCode() if its position + // changes due to branch/literal fixup. + void BindTrackedLabel(Label* label) { + Bind(label); + tracked_labels_.push_back(label); + } + protected: // Returns whether or not the given register is used for passing parameters. static int RegisterCompare(const Register* reg1, const Register* reg2) { return *reg1 - *reg2; } + + void FinalizeTrackedLabels(); + + // Tracked labels. Use a vector, as we need to sort before adjusting. + std::vector<Label*> tracked_labels_; }; // Slowpath entered when Thread::Current()->_exception is non-null diff --git a/compiler/utils/arm/assembler_arm32.cc b/compiler/utils/arm/assembler_arm32.cc index 6e7c828b4a..a7dbacd3a9 100644 --- a/compiler/utils/arm/assembler_arm32.cc +++ b/compiler/utils/arm/assembler_arm32.cc @@ -1385,6 +1385,21 @@ void Arm32Assembler::AddConstant(Register rd, Register rn, int32_t value, } } +void Arm32Assembler::CmpConstant(Register rn, int32_t value, Condition cond) { + ShifterOperand shifter_op; + if (ShifterOperandCanHoldArm32(value, &shifter_op)) { + cmp(rn, shifter_op, cond); + } else if (ShifterOperandCanHoldArm32(~value, &shifter_op)) { + cmn(rn, shifter_op, cond); + } else { + movw(IP, Low16Bits(value), cond); + uint16_t value_high = High16Bits(value); + if (value_high != 0) { + movt(IP, value_high, cond); + } + cmp(rn, ShifterOperand(IP), cond); + } +} void Arm32Assembler::LoadImmediate(Register rd, int32_t value, Condition cond) { ShifterOperand shifter_op; @@ -1584,6 +1599,23 @@ void Arm32Assembler::CompareAndBranchIfNonZero(Register r, Label* label) { b(label, NE); } +JumpTable* Arm32Assembler::CreateJumpTable(std::vector<Label*>&& labels ATTRIBUTE_UNUSED, + Register base_reg ATTRIBUTE_UNUSED) { + LOG(FATAL) << "CreateJumpTable is not supported on ARM32"; + UNREACHABLE(); +} + +void Arm32Assembler::EmitJumpTableDispatch(JumpTable* jump_table ATTRIBUTE_UNUSED, + Register displacement_reg ATTRIBUTE_UNUSED) { + LOG(FATAL) << "EmitJumpTableDispatch is not supported on ARM32"; + UNREACHABLE(); +} + +void Arm32Assembler::FinalizeCode() { + ArmAssembler::FinalizeCode(); + // Currently the arm32 assembler does not support fixups, and thus no tracking. We must not call + // FinalizeTrackedLabels(), which would lead to an abort. +} } // namespace arm } // namespace art diff --git a/compiler/utils/arm/assembler_arm32.h b/compiler/utils/arm/assembler_arm32.h index 4646538716..5233dcbbb0 100644 --- a/compiler/utils/arm/assembler_arm32.h +++ b/compiler/utils/arm/assembler_arm32.h @@ -261,6 +261,8 @@ class Arm32Assembler FINAL : public ArmAssembler { void AddConstant(Register rd, Register rn, int32_t value, Condition cond = AL, SetCc set_cc = kCcDontCare) OVERRIDE; + void CmpConstant(Register rn, int32_t value, Condition cond = AL) OVERRIDE; + // Load and Store. May clobber IP. void LoadImmediate(Register rd, int32_t value, Condition cond = AL) OVERRIDE; void MarkExceptionHandler(Label* label) OVERRIDE; @@ -308,6 +310,11 @@ class Arm32Assembler FINAL : public ArmAssembler { void MemoryBarrier(ManagedRegister scratch) OVERRIDE; + JumpTable* CreateJumpTable(std::vector<Label*>&& labels, Register base_reg) OVERRIDE; + void EmitJumpTableDispatch(JumpTable* jump_table, Register displacement_reg) OVERRIDE; + + void FinalizeCode() OVERRIDE; + private: void EmitType01(Condition cond, int type, diff --git a/compiler/utils/arm/assembler_thumb2.cc b/compiler/utils/arm/assembler_thumb2.cc index cc87856e82..fb3aa1ea85 100644 --- a/compiler/utils/arm/assembler_thumb2.cc +++ b/compiler/utils/arm/assembler_thumb2.cc @@ -92,7 +92,7 @@ void Thumb2Assembler::BindLabel(Label* label, uint32_t bound_pc) { label->BindTo(bound_pc); } -void Thumb2Assembler::BindLiterals() { +uint32_t Thumb2Assembler::BindLiterals() { // We don't add the padding here, that's done only after adjusting the Fixup sizes. uint32_t code_size = buffer_.Size(); for (Literal& lit : literals_) { @@ -100,6 +100,15 @@ void Thumb2Assembler::BindLiterals() { BindLabel(label, code_size); code_size += lit.GetSize(); } + return code_size; +} + +void Thumb2Assembler::BindJumpTables(uint32_t code_size) { + for (JumpTable& table : jump_tables_) { + Label* label = table.GetLabel(); + BindLabel(label, code_size); + code_size += table.GetSize(); + } } void Thumb2Assembler::AdjustFixupIfNeeded(Fixup* fixup, uint32_t* current_code_size, @@ -144,7 +153,7 @@ uint32_t Thumb2Assembler::AdjustFixups() { AdjustFixupIfNeeded(fixup, ¤t_code_size, &fixups_to_recalculate); } while (!fixups_to_recalculate.empty()); - if ((current_code_size & 2) != 0 && !literals_.empty()) { + if ((current_code_size & 2) != 0 && (!literals_.empty() || !jump_tables_.empty())) { // If we need to add padding before literals, this may just push some out of range, // so recalculate all load literals. This makes up for the fact that we don't mark // load literal as a dependency of all previous Fixups even though it actually is. @@ -173,6 +182,13 @@ uint32_t Thumb2Assembler::AdjustFixups() { label->Reinitialize(); label->BindTo(old_position + literals_adjustment); } + for (JumpTable& table : jump_tables_) { + Label* label = table.GetLabel(); + DCHECK(label->IsBound()); + int old_position = label->Position(); + label->Reinitialize(); + label->BindTo(old_position + literals_adjustment); + } } return current_code_size; @@ -229,6 +245,43 @@ void Thumb2Assembler::EmitLiterals() { } } +void Thumb2Assembler::EmitJumpTables() { + if (!jump_tables_.empty()) { + // Jump tables require 4 byte alignment. (We don't support byte and half-word jump tables.) + uint32_t code_size = buffer_.Size(); + DCHECK_ALIGNED(code_size, 2); + if ((code_size & 2u) != 0u) { + Emit16(0); + } + for (JumpTable& table : jump_tables_) { + // Bulk ensure capacity, as this may be large. + size_t orig_size = buffer_.Size(); + buffer_.ExtendCapacity(orig_size + table.GetSize()); +#ifndef NDEBUG + buffer_.has_ensured_capacity_ = true; +#endif + + DCHECK_EQ(static_cast<size_t>(table.GetLabel()->Position()), buffer_.Size()); + int32_t anchor_position = table.GetAnchorLabel()->Position() + 4; + + for (Label* target : table.GetData()) { + // Ensure that the label was tracked, so that it will have the right position. + DCHECK(std::find(tracked_labels_.begin(), tracked_labels_.end(), target) != + tracked_labels_.end()); + + int32_t offset = target->Position() - anchor_position; + buffer_.Emit<int32_t>(offset); + } + +#ifndef NDEBUG + buffer_.has_ensured_capacity_ = false; +#endif + size_t new_size = buffer_.Size(); + DCHECK_LE(new_size - orig_size, table.GetSize()); + } + } +} + inline int16_t Thumb2Assembler::BEncoding16(int32_t offset, Condition cond) { DCHECK_ALIGNED(offset, 2); int16_t encoding = B15 | B14; @@ -382,12 +435,34 @@ inline int32_t Thumb2Assembler::LdrRtRnImm12Encoding(Register rt, Register rn, i return B31 | B30 | B29 | B28 | B27 | B23 | B22 | B20 | (rn << 16) | (rt << 12) | offset; } +inline int16_t Thumb2Assembler::AdrEncoding16(Register rd, int32_t offset) { + DCHECK(IsUint<10>(offset)); + DCHECK(IsAligned<4>(offset)); + DCHECK(!IsHighRegister(rd)); + return B15 | B13 | (rd << 8) | (offset >> 2); +} + +inline int32_t Thumb2Assembler::AdrEncoding32(Register rd, int32_t offset) { + DCHECK(IsUint<12>(offset)); + // Bit 26: offset[11] + // Bits 14-12: offset[10-8] + // Bits 7-0: offset[7-0] + int32_t immediate_mask = + ((offset & (1 << 11)) << (26 - 11)) | + ((offset & (7 << 8)) << (12 - 8)) | + (offset & 0xFF); + return B31 | B30 | B29 | B28 | B25 | B19 | B18 | B17 | B16 | (rd << 8) | immediate_mask; +} + void Thumb2Assembler::FinalizeCode() { ArmAssembler::FinalizeCode(); - BindLiterals(); + uint32_t size_after_literals = BindLiterals(); + BindJumpTables(size_after_literals); uint32_t adjusted_code_size = AdjustFixups(); EmitFixups(adjusted_code_size); EmitLiterals(); + FinalizeTrackedLabels(); + EmitJumpTables(); } bool Thumb2Assembler::ShifterOperandCanAlwaysHold(uint32_t immediate) { @@ -1770,6 +1845,15 @@ inline size_t Thumb2Assembler::Fixup::SizeInBytes(Size size) { case kLiteralFar: return 14u; + case kLiteralAddr1KiB: + return 2u; + case kLiteralAddr4KiB: + return 4u; + case kLiteralAddr64KiB: + return 6u; + case kLiteralAddrFar: + return 10u; + case kLongOrFPLiteral1KiB: return 4u; case kLongOrFPLiteral256KiB: @@ -1831,6 +1915,8 @@ inline int32_t Thumb2Assembler::Fixup::GetOffset(uint32_t current_code_size) con case kLiteral1KiB: case kLiteral4KiB: case kLongOrFPLiteral1KiB: + case kLiteralAddr1KiB: + case kLiteralAddr4KiB: DCHECK(diff >= 0 || (GetSize() == kLiteral1KiB && diff == -2)); diff += LiteralPoolPaddingSize(current_code_size); // Load literal instructions round down the PC+4 to a multiple of 4, so if the PC @@ -1843,12 +1929,14 @@ inline int32_t Thumb2Assembler::Fixup::GetOffset(uint32_t current_code_size) con case kLiteral1MiB: case kLiteral64KiB: case kLongOrFPLiteral256KiB: + case kLiteralAddr64KiB: DCHECK_GE(diff, 4); // The target must be at least 4 bytes after the ADD rX, PC. diff -= 4; // One extra 32-bit MOV. diff += LiteralPoolPaddingSize(current_code_size); break; case kLiteralFar: case kLongOrFPLiteralFar: + case kLiteralAddrFar: DCHECK_GE(diff, 8); // The target must be at least 4 bytes after the ADD rX, PC. diff -= 8; // Extra MOVW+MOVT; both 32-bit. diff += LiteralPoolPaddingSize(current_code_size); @@ -1929,6 +2017,29 @@ uint32_t Thumb2Assembler::Fixup::AdjustSizeIfNeeded(uint32_t current_code_size) // This encoding can reach any target. break; + case kLiteralAddr1KiB: + DCHECK(!IsHighRegister(rn_)); + if (IsUint<10>(GetOffset(current_code_size))) { + break; + } + current_code_size += IncreaseSize(kLiteralAddr4KiB); + FALLTHROUGH_INTENDED; + case kLiteralAddr4KiB: + if (IsUint<12>(GetOffset(current_code_size))) { + break; + } + current_code_size += IncreaseSize(kLiteralAddr64KiB); + FALLTHROUGH_INTENDED; + case kLiteralAddr64KiB: + if (IsUint<16>(GetOffset(current_code_size))) { + break; + } + current_code_size += IncreaseSize(kLiteralAddrFar); + FALLTHROUGH_INTENDED; + case kLiteralAddrFar: + // This encoding can reach any target. + break; + case kLongOrFPLiteral1KiB: if (IsUint<10>(GetOffset(current_code_size))) { break; @@ -2055,6 +2166,42 @@ void Thumb2Assembler::Fixup::Emit(AssemblerBuffer* buffer, uint32_t code_size) c break; } + case kLiteralAddr1KiB: { + DCHECK(type_ == kLoadLiteralAddr); + int16_t encoding = AdrEncoding16(rn_, GetOffset(code_size)); + buffer->Store<int16_t>(location_, encoding); + break; + } + case kLiteralAddr4KiB: { + DCHECK(type_ == kLoadLiteralAddr); + int32_t encoding = AdrEncoding32(rn_, GetOffset(code_size)); + buffer->Store<int16_t>(location_, encoding >> 16); + buffer->Store<int16_t>(location_ + 2u, static_cast<int16_t>(encoding & 0xffff)); + break; + } + case kLiteralAddr64KiB: { + DCHECK(type_ == kLoadLiteralAddr); + int32_t mov_encoding = MovwEncoding32(rn_, GetOffset(code_size)); + int16_t add_pc_encoding = AddRdnRmEncoding16(rn_, PC); + buffer->Store<int16_t>(location_, mov_encoding >> 16); + buffer->Store<int16_t>(location_ + 2u, static_cast<int16_t>(mov_encoding & 0xffff)); + buffer->Store<int16_t>(location_ + 4u, add_pc_encoding); + break; + } + case kLiteralAddrFar: { + DCHECK(type_ == kLoadLiteralAddr); + int32_t offset = GetOffset(code_size); + int32_t movw_encoding = MovwEncoding32(rn_, offset & 0xffff); + int32_t movt_encoding = MovtEncoding32(rn_, offset & ~0xffff); + int16_t add_pc_encoding = AddRdnRmEncoding16(rn_, PC); + buffer->Store<int16_t>(location_, movw_encoding >> 16); + buffer->Store<int16_t>(location_ + 2u, static_cast<int16_t>(movw_encoding & 0xffff)); + buffer->Store<int16_t>(location_ + 4u, movt_encoding >> 16); + buffer->Store<int16_t>(location_ + 6u, static_cast<int16_t>(movt_encoding & 0xffff)); + buffer->Store<int16_t>(location_ + 8u, add_pc_encoding); + break; + } + case kLongOrFPLiteral1KiB: { int32_t encoding = LoadWideOrFpEncoding(PC, GetOffset(code_size)); // DCHECKs type_. buffer->Store<int16_t>(location_, encoding >> 16); @@ -3260,6 +3407,25 @@ void Thumb2Assembler::AddConstant(Register rd, Register rn, int32_t value, } } +void Thumb2Assembler::CmpConstant(Register rn, int32_t value, Condition cond) { + // We prefer to select the shorter code sequence rather than selecting add for + // positive values and sub for negatives ones, which would slightly improve + // the readability of generated code for some constants. + ShifterOperand shifter_op; + if (ShifterOperandCanHold(kNoRegister, rn, CMP, value, &shifter_op)) { + cmp(rn, shifter_op, cond); + } else if (ShifterOperandCanHold(kNoRegister, rn, CMN, ~value, &shifter_op)) { + cmn(rn, shifter_op, cond); + } else { + CHECK(rn != IP); + movw(IP, Low16Bits(value), cond); + uint16_t value_high = High16Bits(value); + if (value_high != 0) { + movt(IP, value_high, cond); + } + cmp(rn, ShifterOperand(IP), cond); + } +} void Thumb2Assembler::LoadImmediate(Register rd, int32_t value, Condition cond) { ShifterOperand shifter_op; @@ -3476,5 +3642,39 @@ void Thumb2Assembler::CompareAndBranchIfNonZero(Register r, Label* label) { b(label, NE); } } + +JumpTable* Thumb2Assembler::CreateJumpTable(std::vector<Label*>&& labels, Register base_reg) { + jump_tables_.emplace_back(std::move(labels)); + JumpTable* table = &jump_tables_.back(); + DCHECK(!table->GetLabel()->IsBound()); + + bool use32bit = IsForced32Bit() || IsHighRegister(base_reg); + uint32_t location = buffer_.Size(); + Fixup::Size size = use32bit ? Fixup::kLiteralAddr4KiB : Fixup::kLiteralAddr1KiB; + FixupId fixup_id = AddFixup(Fixup::LoadLiteralAddress(location, base_reg, size)); + Emit16(static_cast<uint16_t>(table->GetLabel()->position_)); + table->GetLabel()->LinkTo(fixup_id); + if (use32bit) { + Emit16(0); + } + DCHECK_EQ(location + GetFixup(fixup_id)->GetSizeInBytes(), buffer_.Size()); + + return table; +} + +void Thumb2Assembler::EmitJumpTableDispatch(JumpTable* jump_table, Register displacement_reg) { + CHECK(!IsForced32Bit()) << "Forced 32-bit dispatch not implemented yet"; + // 32-bit ADD doesn't support PC as an input, so we need a two-instruction sequence: + // SUB ip, ip, #0 + // ADD pc, ip, reg + // TODO: Implement. + + // The anchor's position needs to be fixed up before we can compute offsets - so make it a tracked + // label. + BindTrackedLabel(jump_table->GetAnchorLabel()); + + add(PC, PC, ShifterOperand(displacement_reg)); +} + } // namespace arm } // namespace art diff --git a/compiler/utils/arm/assembler_thumb2.h b/compiler/utils/arm/assembler_thumb2.h index 055b1379ad..38fd244087 100644 --- a/compiler/utils/arm/assembler_thumb2.h +++ b/compiler/utils/arm/assembler_thumb2.h @@ -18,6 +18,7 @@ #define ART_COMPILER_UTILS_ARM_ASSEMBLER_THUMB2_H_ #include <deque> +#include <utility> #include <vector> #include "base/logging.h" @@ -304,6 +305,8 @@ class Thumb2Assembler FINAL : public ArmAssembler { void AddConstant(Register rd, Register rn, int32_t value, Condition cond = AL, SetCc set_cc = kCcDontCare) OVERRIDE; + void CmpConstant(Register rn, int32_t value, Condition cond = AL) OVERRIDE; + // Load and Store. May clobber IP. void LoadImmediate(Register rd, int32_t value, Condition cond = AL) OVERRIDE; void MarkExceptionHandler(Label* label) OVERRIDE; @@ -358,6 +361,12 @@ class Thumb2Assembler FINAL : public ArmAssembler { force_32bit_ = true; } + // Emit an ADR (or a sequence of instructions) to load the jump table address into base_reg. This + // will generate a fixup. + JumpTable* CreateJumpTable(std::vector<Label*>&& labels, Register base_reg) OVERRIDE; + // Emit an ADD PC, X to dispatch a jump-table jump. This will generate a fixup. + void EmitJumpTableDispatch(JumpTable* jump_table, Register displacement_reg) OVERRIDE; + private: typedef uint16_t FixupId; @@ -399,6 +408,7 @@ class Thumb2Assembler FINAL : public ArmAssembler { kCompareAndBranchXZero, // cbz/cbnz. kLoadLiteralNarrow, // Load narrrow integer literal. kLoadLiteralWide, // Load wide integer literal. + kLoadLiteralAddr, // Load address of literal (used for jump table). kLoadFPLiteralSingle, // Load FP literal single. kLoadFPLiteralDouble, // Load FP literal double. }; @@ -429,6 +439,16 @@ class Thumb2Assembler FINAL : public ArmAssembler { // MOV rX, imm16 + MOVT rX, imm16 + ADD rX, pc + LDR rX, [rX]; any offset; 14 bytes. kLiteralFar, + // Load literal base addr. + // ADR rX, label; X < 8; 8 bit immediate, shifted to 10 bit. 2 bytes. + kLiteralAddr1KiB, + // ADR rX, label; 4KiB offset. 4 bytes. + kLiteralAddr4KiB, + // MOV rX, imm16 + ADD rX, pc; 64KiB offset. 6 bytes. + kLiteralAddr64KiB, + // MOV rX, imm16 + MOVT rX, imm16 + ADD rX, pc; any offset; 10 bytes. + kLiteralAddrFar, + // Load long or FP literal variants. // VLDR s/dX, label; 32-bit insn, up to 1KiB offset; 4 bytes. kLongOrFPLiteral1KiB, @@ -457,7 +477,7 @@ class Thumb2Assembler FINAL : public ArmAssembler { } // Load narrow literal. - static Fixup LoadNarrowLiteral(uint32_t location, Register rt, Size size = kLiteral1KiB) { + static Fixup LoadNarrowLiteral(uint32_t location, Register rt, Size size) { DCHECK(size == kLiteral1KiB || size == kLiteral4KiB || size == kLiteral64KiB || size == kLiteral1MiB || size == kLiteralFar); DCHECK(!IsHighRegister(rt) || (size != kLiteral1KiB && size != kLiteral64KiB)); @@ -493,6 +513,14 @@ class Thumb2Assembler FINAL : public ArmAssembler { AL, kLoadFPLiteralDouble, size, location); } + static Fixup LoadLiteralAddress(uint32_t location, Register rt, Size size) { + DCHECK(size == kLiteralAddr1KiB || size == kLiteralAddr4KiB || size == kLiteralAddr64KiB || + size == kLiteralAddrFar); + DCHECK(!IsHighRegister(rt) || size != kLiteralAddr1KiB); + return Fixup(rt, kNoRegister, kNoSRegister, kNoDRegister, + AL, kLoadLiteralAddr, size, location); + } + Type GetType() const { return type_; } @@ -756,12 +784,14 @@ class Thumb2Assembler FINAL : public ArmAssembler { } void BindLabel(Label* label, uint32_t bound_pc); - void BindLiterals(); + uint32_t BindLiterals(); + void BindJumpTables(uint32_t code_size); void AdjustFixupIfNeeded(Fixup* fixup, uint32_t* current_code_size, std::deque<FixupId>* fixups_to_recalculate); uint32_t AdjustFixups(); void EmitFixups(uint32_t adjusted_code_size); void EmitLiterals(); + void EmitJumpTables(); static int16_t BEncoding16(int32_t offset, Condition cond); static int32_t BEncoding32(int32_t offset, Condition cond); @@ -778,6 +808,8 @@ class Thumb2Assembler FINAL : public ArmAssembler { static int32_t VldrdEncoding32(DRegister dd, Register rn, int32_t offset); static int16_t LdrRtRnImm5Encoding16(Register rt, Register rn, int32_t offset); static int32_t LdrRtRnImm12Encoding(Register rt, Register rn, int32_t offset); + static int16_t AdrEncoding16(Register rd, int32_t offset); + static int32_t AdrEncoding32(Register rd, int32_t offset); std::vector<Fixup> fixups_; std::unique_ptr<FixupId[]> fixup_dependents_; @@ -786,6 +818,9 @@ class Thumb2Assembler FINAL : public ArmAssembler { // without invalidating pointers and references to existing elements. std::deque<Literal> literals_; + // Jump table list. + std::deque<JumpTable> jump_tables_; + // Data for AdjustedPosition(), see the description there. uint32_t last_position_adjustment_; uint32_t last_old_position_; diff --git a/compiler/utils/arm/assembler_thumb2_test.cc b/compiler/utils/arm/assembler_thumb2_test.cc index 9c08ce017e..cb4b20b5ba 100644 --- a/compiler/utils/arm/assembler_thumb2_test.cc +++ b/compiler/utils/arm/assembler_thumb2_test.cc @@ -17,6 +17,7 @@ #include "assembler_thumb2.h" #include "base/stl_util.h" +#include "base/stringprintf.h" #include "utils/assembler_test.h" namespace art { @@ -1011,6 +1012,315 @@ TEST_F(AssemblerThumb2Test, LoadLiteralBeyondMax1KiBDueToAlignmentOnSecondPass) __ GetAdjustedPosition(label.Position())); } +TEST_F(AssemblerThumb2Test, BindTrackedLabel) { + Label non_tracked, tracked, branch_target; + + // A few dummy loads on entry. + constexpr size_t kLdrR0R0Count = 5; + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + + // A branch that will need to be fixed up. + __ cbz(arm::R0, &branch_target); + + // Some more dummy loads. + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + + // Now insert tracked and untracked label. + __ Bind(&non_tracked); + __ BindTrackedLabel(&tracked); + + // A lot of dummy loads, to ensure the branch needs resizing. + constexpr size_t kLdrR0R0CountLong = 60; + for (size_t i = 0; i != kLdrR0R0CountLong; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + + // Bind the branch target. + __ Bind(&branch_target); + + // One more load. + __ ldr(arm::R0, arm::Address(arm::R0)); + + std::string expected = + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + "cmp r0, #0\n" // cbz r0, 1f + "beq.n 1f\n" + + RepeatInsn(kLdrR0R0Count + kLdrR0R0CountLong, "ldr r0, [r0]\n") + + "1:\n" + "ldr r0, [r0]\n"; + DriverStr(expected, "BindTrackedLabel"); + + // Expectation is that the tracked label should have moved. + EXPECT_LT(non_tracked.Position(), tracked.Position()); +} + +TEST_F(AssemblerThumb2Test, JumpTable) { + // The jump table. Use three labels. + Label label1, label2, label3; + std::vector<Label*> labels({ &label1, &label2, &label3 }); + + // A few dummy loads on entry, interspersed with 2 labels. + constexpr size_t kLdrR0R0Count = 5; + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label1); + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label2); + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + + // Create the jump table, emit the base load. + arm::JumpTable* jump_table = __ CreateJumpTable(std::move(labels), arm::R1); + + // Dummy computation, stand-in for the address. We're only testing the jump table here, not how + // it's being used. + __ ldr(arm::R0, arm::Address(arm::R0)); + + // Emit the jump + __ EmitJumpTableDispatch(jump_table, arm::R1); + + // Some more dummy instructions. + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label3); + for (size_t i = 0; i != kLdrR0R0Count; ++i) { // Note: odd so there's no alignment + __ ldr(arm::R0, arm::Address(arm::R0)); // necessary, as gcc as emits nops, + } // whereas we emit 0 != nop. + + static_assert((kLdrR0R0Count + 3) * 2 < 1 * KB, "Too much offset"); + + std::string expected = + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L1:\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L2:\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + "adr r1, .Ljump_table\n" + "ldr r0, [r0]\n" + ".Lbase:\n" + "add pc, r1\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L3:\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".align 2\n" + ".Ljump_table:\n" + ".4byte (.L1 - .Lbase - 4)\n" + ".4byte (.L2 - .Lbase - 4)\n" + ".4byte (.L3 - .Lbase - 4)\n"; + DriverStr(expected, "JumpTable"); +} + +// Test for >1K fixup. +TEST_F(AssemblerThumb2Test, JumpTable4K) { + // The jump table. Use three labels. + Label label1, label2, label3; + std::vector<Label*> labels({ &label1, &label2, &label3 }); + + // A few dummy loads on entry, interspersed with 2 labels. + constexpr size_t kLdrR0R0Count = 5; + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label1); + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label2); + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + + // Create the jump table, emit the base load. + arm::JumpTable* jump_table = __ CreateJumpTable(std::move(labels), arm::R1); + + // Dummy computation, stand-in for the address. We're only testing the jump table here, not how + // it's being used. + __ ldr(arm::R0, arm::Address(arm::R0)); + + // Emit the jump + __ EmitJumpTableDispatch(jump_table, arm::R1); + + // Some more dummy instructions. + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label3); + constexpr size_t kLdrR0R0Count2 = 600; // Note: even so there's no alignment + for (size_t i = 0; i != kLdrR0R0Count2; ++i) { // necessary, as gcc as emits nops, + __ ldr(arm::R0, arm::Address(arm::R0)); // whereas we emit 0 != nop. + } + + static_assert((kLdrR0R0Count + kLdrR0R0Count2 + 3) * 2 > 1 * KB, "Not enough offset"); + static_assert((kLdrR0R0Count + kLdrR0R0Count2 + 3) * 2 < 4 * KB, "Too much offset"); + + std::string expected = + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L1:\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L2:\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + "adr r1, .Ljump_table\n" + "ldr r0, [r0]\n" + ".Lbase:\n" + "add pc, r1\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L3:\n" + + RepeatInsn(kLdrR0R0Count2, "ldr r0, [r0]\n") + + ".align 2\n" + ".Ljump_table:\n" + ".4byte (.L1 - .Lbase - 4)\n" + ".4byte (.L2 - .Lbase - 4)\n" + ".4byte (.L3 - .Lbase - 4)\n"; + DriverStr(expected, "JumpTable4K"); +} + +// Test for >4K fixup. +TEST_F(AssemblerThumb2Test, JumpTable64K) { + // The jump table. Use three labels. + Label label1, label2, label3; + std::vector<Label*> labels({ &label1, &label2, &label3 }); + + // A few dummy loads on entry, interspersed with 2 labels. + constexpr size_t kLdrR0R0Count = 5; + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label1); + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label2); + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + + // Create the jump table, emit the base load. + arm::JumpTable* jump_table = __ CreateJumpTable(std::move(labels), arm::R1); + + // Dummy computation, stand-in for the address. We're only testing the jump table here, not how + // it's being used. + __ ldr(arm::R0, arm::Address(arm::R0)); + + // Emit the jump + __ EmitJumpTableDispatch(jump_table, arm::R1); + + // Some more dummy instructions. + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label3); + constexpr size_t kLdrR0R0Count2 = 2601; // Note: odd so there's no alignment + for (size_t i = 0; i != kLdrR0R0Count2; ++i) { // necessary, as gcc as emits nops, + __ ldr(arm::R0, arm::Address(arm::R0)); // whereas we emit 0 != nop. + } + + static_assert((kLdrR0R0Count + kLdrR0R0Count2 + 3) * 2 > 4 * KB, "Not enough offset"); + static_assert((kLdrR0R0Count + kLdrR0R0Count2 + 3) * 2 < 64 * KB, "Too much offset"); + + std::string expected = + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L1:\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L2:\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + // ~ adr r1, .Ljump_table, gcc as can't seem to fix up a large offset itself. + // (Note: have to use constants, as labels aren't accepted. + "movw r1, #(((3 + " + StringPrintf("%zu", kLdrR0R0Count + kLdrR0R0Count2) + + ") * 2 - 4) & 0xFFFF)\n" + "add r1, pc\n" + "ldr r0, [r0]\n" + ".Lbase:\n" + "add pc, r1\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L3:\n" + + RepeatInsn(kLdrR0R0Count2, "ldr r0, [r0]\n") + + ".align 2\n" + ".Ljump_table:\n" + ".4byte (.L1 - .Lbase - 4)\n" + ".4byte (.L2 - .Lbase - 4)\n" + ".4byte (.L3 - .Lbase - 4)\n"; + DriverStr(expected, "JumpTable64K"); +} + +// Test for >64K fixup. +TEST_F(AssemblerThumb2Test, JumpTableFar) { + // The jump table. Use three labels. + Label label1, label2, label3; + std::vector<Label*> labels({ &label1, &label2, &label3 }); + + // A few dummy loads on entry, interspersed with 2 labels. + constexpr size_t kLdrR0R0Count = 5; + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label1); + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label2); + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + + // Create the jump table, emit the base load. + arm::JumpTable* jump_table = __ CreateJumpTable(std::move(labels), arm::R1); + + // Dummy computation, stand-in for the address. We're only testing the jump table here, not how + // it's being used. + __ ldr(arm::R0, arm::Address(arm::R0)); + + // Emit the jump + __ EmitJumpTableDispatch(jump_table, arm::R1); + + // Some more dummy instructions. + for (size_t i = 0; i != kLdrR0R0Count; ++i) { + __ ldr(arm::R0, arm::Address(arm::R0)); + } + __ BindTrackedLabel(&label3); + constexpr size_t kLdrR0R0Count2 = 70001; // Note: odd so there's no alignment + for (size_t i = 0; i != kLdrR0R0Count2; ++i) { // necessary, as gcc as emits nops, + __ ldr(arm::R0, arm::Address(arm::R0)); // whereas we emit 0 != nop. + } + + static_assert((kLdrR0R0Count + kLdrR0R0Count2 + 3) * 2 > 64 * KB, "Not enough offset"); + + std::string expected = + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L1:\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L2:\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + // ~ adr r1, .Ljump_table, gcc as can't seem to fix up a large offset itself. + // (Note: have to use constants, as labels aren't accepted. + "movw r1, #(((3 + " + StringPrintf("%zu", kLdrR0R0Count + kLdrR0R0Count2) + + ") * 2 - 4) & 0xFFFF)\n" + "movt r1, #(((3 + " + StringPrintf("%zu", kLdrR0R0Count + kLdrR0R0Count2) + + ") * 2 - 4) >> 16)\n" + ".Lhelp:" + "add r1, pc\n" + "ldr r0, [r0]\n" + ".Lbase:\n" + "add pc, r1\n" + + RepeatInsn(kLdrR0R0Count, "ldr r0, [r0]\n") + + ".L3:\n" + + RepeatInsn(kLdrR0R0Count2, "ldr r0, [r0]\n") + + ".align 2\n" + ".Ljump_table:\n" + ".4byte (.L1 - .Lbase - 4)\n" + ".4byte (.L2 - .Lbase - 4)\n" + ".4byte (.L3 - .Lbase - 4)\n"; + DriverStr(expected, "JumpTableFar"); +} + TEST_F(AssemblerThumb2Test, Clz) { __ clz(arm::R0, arm::R1); diff --git a/compiler/utils/assembler.h b/compiler/utils/assembler.h index d97a2a40b2..dfe6babb25 100644 --- a/compiler/utils/assembler.h +++ b/compiler/utils/assembler.h @@ -227,6 +227,8 @@ class AssemblerBuffer { // Returns the position in the instruction stream. int GetPosition() { return cursor_ - contents_; } + void ExtendCapacity(size_t min_capacity = 0u); + private: // The limit is set to kMinimumGap bytes before the end of the data area. // This leaves enough space for the longest possible instruction and allows @@ -261,8 +263,6 @@ class AssemblerBuffer { return data + capacity - kMinimumGap; } - void ExtendCapacity(size_t min_capacity = 0u); - friend class AssemblerFixup; }; diff --git a/compiler/utils/dedupe_set-inl.h b/compiler/utils/dedupe_set-inl.h new file mode 100644 index 0000000000..ac5481336b --- /dev/null +++ b/compiler/utils/dedupe_set-inl.h @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2015 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_DEDUPE_SET_INL_H_ +#define ART_COMPILER_UTILS_DEDUPE_SET_INL_H_ + +#include "dedupe_set.h" + +#include <algorithm> +#include <inttypes.h> +#include <unordered_map> + +#include "base/mutex.h" +#include "base/hash_set.h" +#include "base/stl_util.h" +#include "base/stringprintf.h" +#include "base/time_utils.h" + +namespace art { + +template <typename InKey, + typename StoreKey, + typename Alloc, + typename HashType, + typename HashFunc, + HashType kShard> +struct DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::Stats { + size_t collision_sum = 0u; + size_t collision_max = 0u; + size_t total_probe_distance = 0u; + size_t total_size = 0u; +}; + +template <typename InKey, + typename StoreKey, + typename Alloc, + typename HashType, + typename HashFunc, + HashType kShard> +class DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::Shard { + public: + Shard(const Alloc& alloc, const std::string& lock_name) + : alloc_(alloc), + lock_name_(lock_name), + lock_(lock_name_.c_str()), + keys_() { + } + + ~Shard() { + for (const HashedKey<StoreKey>& key : keys_) { + DCHECK(key.Key() != nullptr); + alloc_.Destroy(key.Key()); + } + } + + const StoreKey* Add(Thread* self, size_t hash, const InKey& in_key) REQUIRES(!lock_) { + MutexLock lock(self, lock_); + HashedKey<InKey> hashed_in_key(hash, &in_key); + auto it = keys_.Find(hashed_in_key); + if (it != keys_.end()) { + DCHECK(it->Key() != nullptr); + return it->Key(); + } + const StoreKey* store_key = alloc_.Copy(in_key); + keys_.Insert(HashedKey<StoreKey> { hash, store_key }); + return store_key; + } + + void UpdateStats(Thread* self, Stats* global_stats) REQUIRES(!lock_) { + // HashSet<> doesn't keep entries ordered by hash, so we actually allocate memory + // for bookkeeping while collecting the stats. + std::unordered_map<HashType, size_t> stats; + { + MutexLock lock(self, lock_); + // Note: The total_probe_distance will be updated with the current state. + // It may have been higher before a re-hash. + global_stats->total_probe_distance += keys_.TotalProbeDistance(); + global_stats->total_size += keys_.Size(); + for (const HashedKey<StoreKey>& key : keys_) { + auto it = stats.find(key.Hash()); + if (it == stats.end()) { + stats.insert({key.Hash(), 1u}); + } else { + ++it->second; + } + } + } + for (const auto& entry : stats) { + size_t number_of_entries = entry.second; + if (number_of_entries > 1u) { + global_stats->collision_sum += number_of_entries - 1u; + global_stats->collision_max = std::max(global_stats->collision_max, number_of_entries); + } + } + } + + private: + template <typename T> + class HashedKey { + public: + HashedKey() : hash_(0u), key_(nullptr) { } + HashedKey(size_t hash, const T* key) : hash_(hash), key_(key) { } + + size_t Hash() const { + return hash_; + } + + const T* Key() const { + return key_; + } + + bool IsEmpty() const { + return Key() == nullptr; + } + + void MakeEmpty() { + key_ = nullptr; + } + + private: + size_t hash_; + const T* key_; + }; + + class ShardEmptyFn { + public: + bool IsEmpty(const HashedKey<StoreKey>& key) const { + return key.IsEmpty(); + } + + void MakeEmpty(HashedKey<StoreKey>& key) { + key.MakeEmpty(); + } + }; + + struct ShardHashFn { + template <typename T> + size_t operator()(const HashedKey<T>& key) const { + return key.Hash(); + } + }; + + struct ShardPred { + typename std::enable_if<!std::is_same<StoreKey, InKey>::value, bool>::type + operator()(const HashedKey<StoreKey>& lhs, const HashedKey<StoreKey>& rhs) const { + DCHECK(lhs.Key() != nullptr); + DCHECK(rhs.Key() != nullptr); + // Rehashing: stored keys are already deduplicated, so we can simply compare key pointers. + return lhs.Key() == rhs.Key(); + } + + template <typename LeftT, typename RightT> + bool operator()(const HashedKey<LeftT>& lhs, const HashedKey<RightT>& rhs) const { + DCHECK(lhs.Key() != nullptr); + DCHECK(rhs.Key() != nullptr); + return lhs.Hash() == rhs.Hash() && + lhs.Key()->size() == rhs.Key()->size() && + std::equal(lhs.Key()->begin(), lhs.Key()->end(), rhs.Key()->begin()); + } + }; + + Alloc alloc_; + const std::string lock_name_; + Mutex lock_; + HashSet<HashedKey<StoreKey>, ShardEmptyFn, ShardHashFn, ShardPred> keys_ GUARDED_BY(lock_); +}; + +template <typename InKey, + typename StoreKey, + typename Alloc, + typename HashType, + typename HashFunc, + HashType kShard> +const StoreKey* DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::Add( + Thread* self, const InKey& key) { + uint64_t hash_start; + if (kIsDebugBuild) { + hash_start = NanoTime(); + } + HashType raw_hash = HashFunc()(key); + if (kIsDebugBuild) { + uint64_t hash_end = NanoTime(); + hash_time_ += hash_end - hash_start; + } + HashType shard_hash = raw_hash / kShard; + HashType shard_bin = raw_hash % kShard; + return shards_[shard_bin]->Add(self, shard_hash, key); +} + +template <typename InKey, + typename StoreKey, + typename Alloc, + typename HashType, + typename HashFunc, + HashType kShard> +DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::DedupeSet(const char* set_name, + const Alloc& alloc) + : hash_time_(0) { + for (HashType i = 0; i < kShard; ++i) { + std::ostringstream oss; + oss << set_name << " lock " << i; + shards_[i].reset(new Shard(alloc, oss.str())); + } +} + +template <typename InKey, + typename StoreKey, + typename Alloc, + typename HashType, + typename HashFunc, + HashType kShard> +DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::~DedupeSet() { + // Everything done by member destructors. +} + +template <typename InKey, + typename StoreKey, + typename Alloc, + typename HashType, + typename HashFunc, + HashType kShard> +std::string DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::DumpStats( + Thread* self) const { + Stats stats; + for (HashType shard = 0; shard < kShard; ++shard) { + shards_[shard]->UpdateStats(self, &stats); + } + return StringPrintf("%zu collisions, %zu max hash collisions, " + "%zu/%zu probe distance, %" PRIu64 " ns hash time", + stats.collision_sum, + stats.collision_max, + stats.total_probe_distance, + stats.total_size, + hash_time_); +} + + +} // namespace art + +#endif // ART_COMPILER_UTILS_DEDUPE_SET_INL_H_ diff --git a/compiler/utils/dedupe_set.h b/compiler/utils/dedupe_set.h index 2c4a689096..b62f216842 100644 --- a/compiler/utils/dedupe_set.h +++ b/compiler/utils/dedupe_set.h @@ -17,151 +17,41 @@ #ifndef ART_COMPILER_UTILS_DEDUPE_SET_H_ #define ART_COMPILER_UTILS_DEDUPE_SET_H_ -#include <algorithm> -#include <inttypes.h> #include <memory> -#include <set> +#include <stdint.h> #include <string> -#include "base/mutex.h" -#include "base/stl_util.h" -#include "base/stringprintf.h" -#include "base/time_utils.h" -#include "utils/swap_space.h" +#include "base/macros.h" namespace art { +class Thread; + // A set of Keys that support a HashFunc returning HashType. Used to find duplicates of Key in the // Add method. The data-structure is thread-safe through the use of internal locks, it also // supports the lock being sharded. -template <typename InKey, typename StoreKey, typename HashType, typename HashFunc, +template <typename InKey, + typename StoreKey, + typename Alloc, + typename HashType, + typename HashFunc, HashType kShard = 1> class DedupeSet { - typedef std::pair<HashType, const InKey*> HashedInKey; - struct HashedKey { - StoreKey* store_ptr; - union { - HashType store_hash; // Valid if store_ptr != null. - const HashedInKey* in_key; // Valid if store_ptr == null. - }; - }; - - class Comparator { - public: - bool operator()(const HashedKey& a, const HashedKey& b) const { - HashType a_hash = (a.store_ptr != nullptr) ? a.store_hash : a.in_key->first; - HashType b_hash = (b.store_ptr != nullptr) ? b.store_hash : b.in_key->first; - if (a_hash != b_hash) { - return a_hash < b_hash; - } - if (a.store_ptr != nullptr && b.store_ptr != nullptr) { - return std::lexicographical_compare(a.store_ptr->begin(), a.store_ptr->end(), - b.store_ptr->begin(), b.store_ptr->end()); - } else if (a.store_ptr != nullptr && b.store_ptr == nullptr) { - return std::lexicographical_compare(a.store_ptr->begin(), a.store_ptr->end(), - b.in_key->second->begin(), b.in_key->second->end()); - } else if (a.store_ptr == nullptr && b.store_ptr != nullptr) { - return std::lexicographical_compare(a.in_key->second->begin(), a.in_key->second->end(), - b.store_ptr->begin(), b.store_ptr->end()); - } else { - return std::lexicographical_compare(a.in_key->second->begin(), a.in_key->second->end(), - b.in_key->second->begin(), b.in_key->second->end()); - } - } - }; - public: - StoreKey* Add(Thread* self, const InKey& key) { - uint64_t hash_start; - if (kIsDebugBuild) { - hash_start = NanoTime(); - } - HashType raw_hash = HashFunc()(key); - if (kIsDebugBuild) { - uint64_t hash_end = NanoTime(); - hash_time_ += hash_end - hash_start; - } - HashType shard_hash = raw_hash / kShard; - HashType shard_bin = raw_hash % kShard; - HashedInKey hashed_in_key(shard_hash, &key); - HashedKey hashed_key; - hashed_key.store_ptr = nullptr; - hashed_key.in_key = &hashed_in_key; - MutexLock lock(self, *lock_[shard_bin]); - auto it = keys_[shard_bin].find(hashed_key); - if (it != keys_[shard_bin].end()) { - DCHECK(it->store_ptr != nullptr); - return it->store_ptr; - } - hashed_key.store_ptr = CreateStoreKey(key); - hashed_key.store_hash = shard_hash; - keys_[shard_bin].insert(hashed_key); - return hashed_key.store_ptr; - } + // Add a new key to the dedupe set if not present. Return the equivalent deduplicated stored key. + const StoreKey* Add(Thread* self, const InKey& key); - DedupeSet(const char* set_name, SwapAllocator<void>& alloc) - : allocator_(alloc), hash_time_(0) { - for (HashType i = 0; i < kShard; ++i) { - std::ostringstream oss; - oss << set_name << " lock " << i; - lock_name_[i] = oss.str(); - lock_[i].reset(new Mutex(lock_name_[i].c_str())); - } - } + DedupeSet(const char* set_name, const Alloc& alloc); - ~DedupeSet() { - // Have to manually free all pointers. - for (auto& shard : keys_) { - for (const auto& hashed_key : shard) { - DCHECK(hashed_key.store_ptr != nullptr); - DeleteStoreKey(hashed_key.store_ptr); - } - } - } + ~DedupeSet(); - std::string DumpStats() const { - size_t collision_sum = 0; - size_t collision_max = 0; - for (HashType shard = 0; shard < kShard; ++shard) { - HashType last_hash = 0; - size_t collision_cur_max = 0; - for (const HashedKey& key : keys_[shard]) { - DCHECK(key.store_ptr != nullptr); - if (key.store_hash == last_hash) { - collision_cur_max++; - if (collision_cur_max > 1) { - collision_sum++; - if (collision_cur_max > collision_max) { - collision_max = collision_cur_max; - } - } - } else { - collision_cur_max = 1; - last_hash = key.store_hash; - } - } - } - return StringPrintf("%zu collisions, %zu max bucket size, %" PRIu64 " ns hash time", - collision_sum, collision_max, hash_time_); - } + std::string DumpStats(Thread* self) const; private: - StoreKey* CreateStoreKey(const InKey& key) { - StoreKey* ret = allocator_.allocate(1); - allocator_.construct(ret, key.begin(), key.end(), allocator_); - return ret; - } - - void DeleteStoreKey(StoreKey* key) { - SwapAllocator<StoreKey> alloc(allocator_); - alloc.destroy(key); - alloc.deallocate(key, 1); - } + struct Stats; + class Shard; - std::string lock_name_[kShard]; - std::unique_ptr<Mutex> lock_[kShard]; - std::set<HashedKey, Comparator> keys_[kShard]; - SwapAllocator<StoreKey> allocator_; + std::unique_ptr<Shard> shards_[kShard]; uint64_t hash_time_; DISALLOW_COPY_AND_ASSIGN(DedupeSet); diff --git a/compiler/utils/dedupe_set_test.cc b/compiler/utils/dedupe_set_test.cc index 637964e484..60a891d6a2 100644 --- a/compiler/utils/dedupe_set_test.cc +++ b/compiler/utils/dedupe_set_test.cc @@ -18,15 +18,18 @@ #include <algorithm> #include <cstdio> +#include <vector> +#include "dedupe_set-inl.h" #include "gtest/gtest.h" #include "thread-inl.h" +#include "utils/array_ref.h" namespace art { -class DedupeHashFunc { +class DedupeSetTestHashFunc { public: - size_t operator()(const std::vector<uint8_t>& array) const { + size_t operator()(const ArrayRef<const uint8_t>& array) const { size_t hash = 0; for (uint8_t c : array) { hash += c; @@ -36,46 +39,52 @@ class DedupeHashFunc { return hash; } }; + +class DedupeSetTestAlloc { + public: + const std::vector<uint8_t>* Copy(const ArrayRef<const uint8_t>& src) { + return new std::vector<uint8_t>(src.begin(), src.end()); + } + + void Destroy(const std::vector<uint8_t>* key) { + delete key; + } +}; + TEST(DedupeSetTest, Test) { Thread* self = Thread::Current(); - typedef std::vector<uint8_t> ByteArray; - SwapAllocator<void> swap(nullptr); - DedupeSet<ByteArray, SwapVector<uint8_t>, size_t, DedupeHashFunc> deduplicator("test", swap); - SwapVector<uint8_t>* array1; + DedupeSetTestAlloc alloc; + DedupeSet<ArrayRef<const uint8_t>, + std::vector<uint8_t>, + DedupeSetTestAlloc, + size_t, + DedupeSetTestHashFunc> deduplicator("test", alloc); + const std::vector<uint8_t>* array1; { - ByteArray test1; - test1.push_back(10); - test1.push_back(20); - test1.push_back(30); - test1.push_back(45); - + uint8_t raw_test1[] = { 10u, 20u, 30u, 45u }; + ArrayRef<const uint8_t> test1(raw_test1); array1 = deduplicator.Add(self, test1); ASSERT_NE(array1, nullptr); ASSERT_TRUE(std::equal(test1.begin(), test1.end(), array1->begin())); } - SwapVector<uint8_t>* array2; + const std::vector<uint8_t>* array2; { - ByteArray test1; - test1.push_back(10); - test1.push_back(20); - test1.push_back(30); - test1.push_back(45); - array2 = deduplicator.Add(self, test1); + uint8_t raw_test2[] = { 10u, 20u, 30u, 45u }; + ArrayRef<const uint8_t> test2(raw_test2); + array2 = deduplicator.Add(self, test2); ASSERT_EQ(array2, array1); - ASSERT_TRUE(std::equal(test1.begin(), test1.end(), array2->begin())); + ASSERT_TRUE(std::equal(test2.begin(), test2.end(), array2->begin())); } - SwapVector<uint8_t>* array3; + const std::vector<uint8_t>* array3; { - ByteArray test1; - test1.push_back(10); - test1.push_back(22); - test1.push_back(30); - test1.push_back(47); - array3 = deduplicator.Add(self, test1); + uint8_t raw_test3[] = { 10u, 22u, 30u, 47u }; + ArrayRef<const uint8_t> test3(raw_test3); + array3 = deduplicator.Add(self, test3); ASSERT_NE(array3, nullptr); - ASSERT_TRUE(std::equal(test1.begin(), test1.end(), array3->begin())); + ASSERT_NE(array3, array1); + ASSERT_TRUE(std::equal(test3.begin(), test3.end(), array3->begin())); } } diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc index 5347bf0302..d6caa3c338 100644 --- a/compiler/utils/x86/assembler_x86.cc +++ b/compiler/utils/x86/assembler_x86.cc @@ -720,6 +720,14 @@ void X86Assembler::ucomiss(XmmRegister a, XmmRegister b) { } +void X86Assembler::ucomiss(XmmRegister a, const Address& b) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitUint8(0x0F); + EmitUint8(0x2E); + EmitOperand(a, b); +} + + void X86Assembler::ucomisd(XmmRegister a, XmmRegister b) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); @@ -729,6 +737,15 @@ void X86Assembler::ucomisd(XmmRegister a, XmmRegister b) { } +void X86Assembler::ucomisd(XmmRegister a, const Address& b) { + AssemblerBuffer::EnsureCapacity ensured(&buffer_); + EmitUint8(0x66); + EmitUint8(0x0F); + EmitUint8(0x2E); + EmitOperand(a, b); +} + + void X86Assembler::roundsd(XmmRegister dst, XmmRegister src, const Immediate& imm) { AssemblerBuffer::EnsureCapacity ensured(&buffer_); EmitUint8(0x66); diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h index b50fda907a..655af9c184 100644 --- a/compiler/utils/x86/assembler_x86.h +++ b/compiler/utils/x86/assembler_x86.h @@ -417,7 +417,9 @@ class X86Assembler FINAL : public Assembler { void comiss(XmmRegister a, XmmRegister b); void comisd(XmmRegister a, XmmRegister b); void ucomiss(XmmRegister a, XmmRegister b); + void ucomiss(XmmRegister a, const Address& b); void ucomisd(XmmRegister a, XmmRegister b); + void ucomisd(XmmRegister a, const Address& b); void roundsd(XmmRegister dst, XmmRegister src, const Immediate& imm); void roundss(XmmRegister dst, XmmRegister src, const Immediate& imm); diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc index 16f9db43fb..a9b991c7a0 100644 --- a/compiler/utils/x86/assembler_x86_test.cc +++ b/compiler/utils/x86/assembler_x86_test.cc @@ -306,6 +306,19 @@ TEST_F(AssemblerX86Test, RollImm) { DriverStr(RepeatRI(&x86::X86Assembler::roll, 1U, "roll ${imm}, %{reg}"), "rolli"); } +TEST_F(AssemblerX86Test, UComissAddr) { + GetAssembler()->ucomiss(x86::XmmRegister(x86::XMM0), x86::Address(x86::EAX, 0)); + const char* expected = "ucomiss 0(%EAX), %xmm0\n"; + DriverStr(expected, "ucomiss"); +} + +TEST_F(AssemblerX86Test, UComisdAddr) { + GetAssembler()->ucomisd(x86::XmmRegister(x86::XMM0), x86::Address(x86::EAX, 0)); + const char* expected = "ucomisd 0(%EAX), %xmm0\n"; + DriverStr(expected, "ucomisd"); +} + + ///////////////// // Near labels // ///////////////// diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 384b8794c1..2653807369 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -126,11 +126,12 @@ static std::string StrippedCommandLine() { // However, we prefer to drop this when we saw --zip-fd. if (saw_zip_fd) { - // Drop anything --zip-X, --dex-X, --oat-X, --swap-X. + // Drop anything --zip-X, --dex-X, --oat-X, --swap-X, or --app-image-X if (StartsWith(original_argv[i], "--zip-") || StartsWith(original_argv[i], "--dex-") || StartsWith(original_argv[i], "--oat-") || - StartsWith(original_argv[i], "--swap-")) { + StartsWith(original_argv[i], "--swap-") || + StartsWith(original_argv[i], "--app-image-")) { continue; } } @@ -336,6 +337,12 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" --swap-fd=<file-descriptor>: specifies a file to use for swap (by descriptor)."); UsageError(" Example: --swap-fd=10"); UsageError(""); + UsageError(" --app-image-fd=<file-descriptor>: specify output file descriptor for app image."); + UsageError(" Example: --app-image-fd=10"); + UsageError(""); + UsageError(" --app-image-file=<file-name>: specify a file name for app image."); + UsageError(" Example: --app-image-file=/data/dalvik-cache/system@app@Calculator.apk.art"); + UsageError(""); std::cerr << "See log for usage error information\n"; exit(EXIT_FAILURE); } @@ -445,38 +452,6 @@ class WatchDog { pthread_t pthread_; }; -static void ParseStringAfterChar(const std::string& s, char c, std::string* parsed_value) { - std::string::size_type colon = s.find(c); - if (colon == std::string::npos) { - Usage("Missing char %c in option %s\n", c, s.c_str()); - } - // Add one to remove the char we were trimming until. - *parsed_value = s.substr(colon + 1); -} - -static void ParseDouble(const std::string& option, char after_char, double min, double max, - double* parsed_value) { - std::string substring; - ParseStringAfterChar(option, after_char, &substring); - bool sane_val = true; - double value; - if (false) { - // TODO: this doesn't seem to work on the emulator. b/15114595 - std::stringstream iss(substring); - iss >> value; - // Ensure that we have a value, there was no cruft after it and it satisfies a sensible range. - sane_val = iss.eof() && (value >= min) && (value <= max); - } else { - char* end = nullptr; - value = strtod(substring.c_str(), &end); - sane_val = *end == '\0' && value >= min && value <= max; - } - if (!sane_val) { - Usage("Invalid double value %s for option %s\n", substring.c_str(), option.c_str()); - } - *parsed_value = value; -} - static constexpr size_t kMinDexFilesForSwap = 2; static constexpr size_t kMinDexFileCumulativeSizeForSwap = 20 * MB; @@ -516,7 +491,8 @@ class Dex2Oat FINAL { compiled_classes_filename_(nullptr), compiled_methods_zip_filename_(nullptr), compiled_methods_filename_(nullptr), - image_(false), + app_image_(false), + boot_image_(false), is_host_(false), driver_(nullptr), dump_stats_(false), @@ -525,6 +501,7 @@ class Dex2Oat FINAL { dump_slow_timing_(kIsDebugBuild), dump_cfg_append_(false), swap_fd_(-1), + app_image_fd_(kInvalidImageFd), timings_(timings) {} ~Dex2Oat() { @@ -555,66 +532,21 @@ class Dex2Oat FINAL { struct ParserOptions { std::string oat_symbols; std::string boot_image_filename; - const char* compiler_filter_string = nullptr; - CompilerOptions::CompilerFilter compiler_filter = CompilerOptions::kDefaultCompilerFilter; - bool compile_pic = false; - int huge_method_threshold = CompilerOptions::kDefaultHugeMethodThreshold; - int large_method_threshold = CompilerOptions::kDefaultLargeMethodThreshold; - int small_method_threshold = CompilerOptions::kDefaultSmallMethodThreshold; - int tiny_method_threshold = CompilerOptions::kDefaultTinyMethodThreshold; - int num_dex_methods_threshold = CompilerOptions::kDefaultNumDexMethodsThreshold; - static constexpr int kUnsetInlineDepthLimit = -1; - int inline_depth_limit = kUnsetInlineDepthLimit; - static constexpr int kUnsetInlineMaxCodeUnits = -1; - int inline_max_code_units = kUnsetInlineMaxCodeUnits; - - // Profile file to use - double top_k_profile_threshold = CompilerOptions::kDefaultTopKProfileThreshold; - - bool debuggable = false; - bool include_patch_information = CompilerOptions::kDefaultIncludePatchInformation; - bool generate_debug_info = kIsDebugBuild; bool watch_dog_enabled = true; - bool abort_on_hard_verifier_error = false; bool requested_specific_compiler = false; - - bool implicit_null_checks = false; - bool implicit_so_checks = false; - bool implicit_suspend_checks = false; - - PassManagerOptions pass_manager_options; - std::string error_msg; }; - template <typename T> - static void ParseUintOption(const StringPiece& option, - const std::string& option_name, - T* out, - bool is_long_option = true) { - std::string option_prefix = option_name + (is_long_option ? "=" : ""); - DCHECK(option.starts_with(option_prefix)); - const char* value_string = option.substr(option_prefix.size()).data(); - int64_t parsed_integer_value; - if (!ParseInt(value_string, &parsed_integer_value)) { - Usage("Failed to parse %s '%s' as an integer", option_name.c_str(), value_string); - } - if (parsed_integer_value < 0) { - Usage("%s passed a negative value %d", option_name.c_str(), parsed_integer_value); - } - *out = dchecked_integral_cast<T>(parsed_integer_value); - } - void ParseZipFd(const StringPiece& option) { - ParseUintOption(option, "--zip-fd", &zip_fd_); + ParseUintOption(option, "--zip-fd", &zip_fd_, Usage); } void ParseOatFd(const StringPiece& option) { - ParseUintOption(option, "--oat-fd", &oat_fd_); + ParseUintOption(option, "--oat-fd", &oat_fd_, Usage); } void ParseJ(const StringPiece& option) { - ParseUintOption(option, "-j", &thread_count_, /* is_long_option */ false); + ParseUintOption(option, "-j", &thread_count_, Usage, /* is_long_option */ false); } void ParseBase(const StringPiece& option) { @@ -685,80 +617,17 @@ class Dex2Oat FINAL { } } - void ParseHugeMethodMax(const StringPiece& option, ParserOptions* parser_options) { - ParseUintOption(option, "--huge-method-max", &parser_options->huge_method_threshold); - } - - void ParseLargeMethodMax(const StringPiece& option, ParserOptions* parser_options) { - ParseUintOption(option, "--large-method-max", &parser_options->large_method_threshold); - } - - void ParseSmallMethodMax(const StringPiece& option, ParserOptions* parser_options) { - ParseUintOption(option, "--small-method-max", &parser_options->small_method_threshold); - } - - void ParseTinyMethodMax(const StringPiece& option, ParserOptions* parser_options) { - ParseUintOption(option, "--tiny-method-max", &parser_options->tiny_method_threshold); - } - - void ParseNumDexMethods(const StringPiece& option, ParserOptions* parser_options) { - ParseUintOption(option, "--num-dex-methods", &parser_options->num_dex_methods_threshold); - } - - void ParseInlineDepthLimit(const StringPiece& option, ParserOptions* parser_options) { - ParseUintOption(option, "--inline-depth-limit", &parser_options->inline_depth_limit); - } - - void ParseInlineMaxCodeUnits(const StringPiece& option, ParserOptions* parser_options) { - ParseUintOption(option, "--inline-max-code-units=", &parser_options->inline_max_code_units); - } - - void ParseDisablePasses(const StringPiece& option, ParserOptions* parser_options) { - DCHECK(option.starts_with("--disable-passes=")); - const std::string disable_passes = option.substr(strlen("--disable-passes=")).data(); - parser_options->pass_manager_options.SetDisablePassList(disable_passes); - } - - void ParsePrintPasses(const StringPiece& option, ParserOptions* parser_options) { - DCHECK(option.starts_with("--print-passes=")); - const std::string print_passes = option.substr(strlen("--print-passes=")).data(); - parser_options->pass_manager_options.SetPrintPassList(print_passes); - } - - void ParseDumpCfgPasses(const StringPiece& option, ParserOptions* parser_options) { - DCHECK(option.starts_with("--dump-cfg-passes=")); - const std::string dump_passes_string = option.substr(strlen("--dump-cfg-passes=")).data(); - parser_options->pass_manager_options.SetDumpPassList(dump_passes_string); - } - - void ParsePassOptions(const StringPiece& option, ParserOptions* parser_options) { - DCHECK(option.starts_with("--pass-options=")); - const std::string pass_options = option.substr(strlen("--pass-options=")).data(); - parser_options->pass_manager_options.SetOverriddenPassOptions(pass_options); - } + void ProcessOptions(ParserOptions* parser_options) { + boot_image_ = !image_filename_.empty(); + app_image_ = app_image_fd_ != -1 || !app_image_file_name_.empty(); - void ParseDumpInitFailures(const StringPiece& option) { - DCHECK(option.starts_with("--dump-init-failures=")); - std::string file_name = option.substr(strlen("--dump-init-failures=")).data(); - init_failure_output_.reset(new std::ofstream(file_name)); - if (init_failure_output_.get() == nullptr) { - LOG(ERROR) << "Failed to allocate ofstream"; - } else if (init_failure_output_->fail()) { - LOG(ERROR) << "Failed to open " << file_name << " for writing the initialization " - << "failures."; - init_failure_output_.reset(); + if (IsAppImage() && IsBootImage()) { + Usage("Can't have both --image and (--app-image-fd or --app-image-file)"); } - } - - void ParseSwapFd(const StringPiece& option) { - ParseUintOption(option, "--swap-fd", &swap_fd_); - } - void ProcessOptions(ParserOptions* parser_options) { - image_ = (!image_filename_.empty()); - if (image_) { + if (IsBootImage()) { // We need the boot image to always be debuggable. - parser_options->debuggable = true; + compiler_options_->debuggable_ = true; } if (oat_filename_.empty() && oat_fd_ == -1) { @@ -789,7 +658,7 @@ class Dex2Oat FINAL { android_root_ += android_root_env_var; } - if (!image_ && parser_options->boot_image_filename.empty()) { + if (!boot_image_ && parser_options->boot_image_filename.empty()) { parser_options->boot_image_filename += android_root_; parser_options->boot_image_filename += "/framework/boot.art"; } @@ -798,7 +667,7 @@ class Dex2Oat FINAL { boot_image_option_ += parser_options->boot_image_filename; } - if (image_classes_filename_ != nullptr && !image_) { + if (image_classes_filename_ != nullptr && !IsBootImage()) { Usage("--image-classes should only be used with --image"); } @@ -810,7 +679,7 @@ class Dex2Oat FINAL { Usage("--image-classes-zip should be used with --image-classes"); } - if (compiled_classes_filename_ != nullptr && !image_) { + if (compiled_classes_filename_ != nullptr && !IsBootImage()) { Usage("--compiled-classes should only be used with --image"); } @@ -882,44 +751,19 @@ class Dex2Oat FINAL { } } - if (parser_options->compiler_filter_string == nullptr) { - parser_options->compiler_filter_string = "speed"; - } - - CHECK(parser_options->compiler_filter_string != nullptr); - if (strcmp(parser_options->compiler_filter_string, "verify-none") == 0) { - parser_options->compiler_filter = CompilerOptions::kVerifyNone; - } else if (strcmp(parser_options->compiler_filter_string, "interpret-only") == 0) { - parser_options->compiler_filter = CompilerOptions::kInterpretOnly; - } else if (strcmp(parser_options->compiler_filter_string, "verify-at-runtime") == 0) { - parser_options->compiler_filter = CompilerOptions::kVerifyAtRuntime; - } else if (strcmp(parser_options->compiler_filter_string, "space") == 0) { - parser_options->compiler_filter = CompilerOptions::kSpace; - } else if (strcmp(parser_options->compiler_filter_string, "balanced") == 0) { - parser_options->compiler_filter = CompilerOptions::kBalanced; - } else if (strcmp(parser_options->compiler_filter_string, "speed") == 0) { - parser_options->compiler_filter = CompilerOptions::kSpeed; - } else if (strcmp(parser_options->compiler_filter_string, "everything") == 0) { - parser_options->compiler_filter = CompilerOptions::kEverything; - } else if (strcmp(parser_options->compiler_filter_string, "time") == 0) { - parser_options->compiler_filter = CompilerOptions::kTime; - } else { - Usage("Unknown --compiler-filter value %s", parser_options->compiler_filter_string); - } - // It they are not set, use default values for inlining settings. // TODO: We should rethink the compiler filter. We mostly save // time here, which is orthogonal to space. - if (parser_options->inline_depth_limit == ParserOptions::kUnsetInlineDepthLimit) { - parser_options->inline_depth_limit = - (parser_options->compiler_filter == CompilerOptions::kSpace) + if (compiler_options_->inline_depth_limit_ == CompilerOptions::kUnsetInlineDepthLimit) { + compiler_options_->inline_depth_limit_ = + (compiler_options_->compiler_filter_ == CompilerOptions::kSpace) // Implementation of the space filter: limit inlining depth. ? CompilerOptions::kSpaceFilterInlineDepthLimit : CompilerOptions::kDefaultInlineDepthLimit; } - if (parser_options->inline_max_code_units == ParserOptions::kUnsetInlineMaxCodeUnits) { - parser_options->inline_max_code_units = - (parser_options->compiler_filter == CompilerOptions::kSpace) + if (compiler_options_->inline_max_code_units_ == CompilerOptions::kUnsetInlineMaxCodeUnits) { + compiler_options_->inline_max_code_units_ = + (compiler_options_->compiler_filter_ == CompilerOptions::kSpace) // Implementation of the space filter: limit inlining max code units. ? CompilerOptions::kSpaceFilterInlineMaxCodeUnits : CompilerOptions::kDefaultInlineMaxCodeUnits; @@ -935,8 +779,8 @@ class Dex2Oat FINAL { case kX86_64: case kMips: case kMips64: - parser_options->implicit_null_checks = true; - parser_options->implicit_so_checks = true; + compiler_options_->implicit_null_checks_ = true; + compiler_options_->implicit_so_checks_ = true; break; default: @@ -944,29 +788,7 @@ class Dex2Oat FINAL { break; } - compiler_options_.reset(new CompilerOptions(parser_options->compiler_filter, - parser_options->huge_method_threshold, - parser_options->large_method_threshold, - parser_options->small_method_threshold, - parser_options->tiny_method_threshold, - parser_options->num_dex_methods_threshold, - parser_options->inline_depth_limit, - parser_options->inline_max_code_units, - parser_options->include_patch_information, - parser_options->top_k_profile_threshold, - parser_options->debuggable, - parser_options->generate_debug_info, - parser_options->implicit_null_checks, - parser_options->implicit_so_checks, - parser_options->implicit_suspend_checks, - parser_options->compile_pic, - verbose_methods_.empty() ? - nullptr : - &verbose_methods_, - new PassManagerOptions( - parser_options->pass_manager_options), - init_failure_output_.get(), - parser_options->abort_on_hard_verifier_error)); + compiler_options_->verbose_methods_ = verbose_methods_.empty() ? nullptr : &verbose_methods_; // Done with usage checks, enable watchdog if requested if (parser_options->watch_dog_enabled) { @@ -977,7 +799,7 @@ class Dex2Oat FINAL { key_value_store_.reset(new SafeMap<std::string, std::string>()); } - void InsertCompileOptions(int argc, char** argv, ParserOptions* parser_options) { + void InsertCompileOptions(int argc, char** argv) { std::ostringstream oss; for (int i = 0; i < argc; ++i) { if (i > 0) { @@ -991,10 +813,10 @@ class Dex2Oat FINAL { key_value_store_->Put(OatHeader::kDex2OatHostKey, oss.str()); key_value_store_->Put( OatHeader::kPicKey, - parser_options->compile_pic ? OatHeader::kTrueValue : OatHeader::kFalseValue); + compiler_options_->compile_pic_ ? OatHeader::kTrueValue : OatHeader::kFalseValue); key_value_store_->Put( OatHeader::kDebuggableKey, - parser_options->debuggable ? OatHeader::kTrueValue : OatHeader::kFalseValue); + compiler_options_->debuggable_ ? OatHeader::kTrueValue : OatHeader::kFalseValue); } // Parse the arguments from the command line. In case of an unrecognized option or impossible @@ -1015,6 +837,7 @@ class Dex2Oat FINAL { } std::unique_ptr<ParserOptions> parser_options(new ParserOptions()); + compiler_options_.reset(new CompilerOptions()); for (int i = 0; i < argc; i++) { const StringPiece option(argv[i]); @@ -1072,24 +895,11 @@ class Dex2Oat FINAL { ParseInstructionSetFeatures(option, parser_options.get()); } else if (option.starts_with("--compiler-backend=")) { ParseCompilerBackend(option, parser_options.get()); - } else if (option.starts_with("--compiler-filter=")) { - parser_options->compiler_filter_string = option.substr(strlen("--compiler-filter=")).data(); - } else if (option == "--compile-pic") { - parser_options->compile_pic = true; - } else if (option.starts_with("--huge-method-max=")) { - ParseHugeMethodMax(option, parser_options.get()); - } else if (option.starts_with("--large-method-max=")) { - ParseLargeMethodMax(option, parser_options.get()); - } else if (option.starts_with("--small-method-max=")) { - ParseSmallMethodMax(option, parser_options.get()); - } else if (option.starts_with("--tiny-method-max=")) { - ParseTinyMethodMax(option, parser_options.get()); - } else if (option.starts_with("--num-dex-methods=")) { - ParseNumDexMethods(option, parser_options.get()); - } else if (option.starts_with("--inline-depth-limit=")) { - ParseInlineDepthLimit(option, parser_options.get()); - } else if (option.starts_with("--inline-max-code-units=")) { - ParseInlineMaxCodeUnits(option, parser_options.get()); + } else if (option.starts_with("--profile-file=")) { + profile_file_ = option.substr(strlen("--profile-file=")).data(); + VLOG(compiler) << "dex2oat: profile file is " << profile_file_; + } else if (option == "--no-profile-file") { + // No profile } else if (option == "--host") { is_host_ = true; } else if (option == "--runtime-arg") { @@ -1110,52 +920,20 @@ class Dex2Oat FINAL { dump_cfg_append_ = true; } else if (option == "--dump-stats") { dump_stats_ = true; - } else if (option == "--generate-debug-info" || option == "-g") { - parser_options->generate_debug_info = true; - } else if (option == "--no-generate-debug-info") { - parser_options->generate_debug_info = false; - } else if (option == "--debuggable") { - parser_options->debuggable = true; - parser_options->generate_debug_info = true; - } else if (option.starts_with("--profile-file=")) { - profile_file_ = option.substr(strlen("--profile-file=")).data(); - VLOG(compiler) << "dex2oat: profile file is " << profile_file_; - } else if (option == "--no-profile-file") { - // No profile - } else if (option.starts_with("--top-k-profile-threshold=")) { - ParseDouble(option.data(), '=', 0.0, 100.0, &parser_options->top_k_profile_threshold); - } else if (option == "--print-pass-names") { - parser_options->pass_manager_options.SetPrintPassNames(true); - } else if (option.starts_with("--disable-passes=")) { - ParseDisablePasses(option, parser_options.get()); - } else if (option.starts_with("--print-passes=")) { - ParsePrintPasses(option, parser_options.get()); - } else if (option == "--print-all-passes") { - parser_options->pass_manager_options.SetPrintAllPasses(); - } else if (option.starts_with("--dump-cfg-passes=")) { - ParseDumpCfgPasses(option, parser_options.get()); - } else if (option == "--print-pass-options") { - parser_options->pass_manager_options.SetPrintPassOptions(true); - } else if (option.starts_with("--pass-options=")) { - ParsePassOptions(option, parser_options.get()); - } else if (option == "--include-patch-information") { - parser_options->include_patch_information = true; - } else if (option == "--no-include-patch-information") { - parser_options->include_patch_information = false; + } else if (option.starts_with("--swap-file=")) { + swap_file_name_ = option.substr(strlen("--swap-file=")).data(); + } else if (option.starts_with("--swap-fd=")) { + ParseUintOption(option, "--swap-fd", &swap_fd_, Usage); + } else if (option.starts_with("--app-image-file=")) { + app_image_file_name_ = option.substr(strlen("--app-image-file=")).data(); + } else if (option.starts_with("--app-image-fd=")) { + ParseUintOption(option, "--app-image-fd", &app_image_fd_, Usage); } else if (option.starts_with("--verbose-methods=")) { // TODO: rather than switch off compiler logging, make all VLOG(compiler) messages // conditional on having verbost methods. gLogVerbosity.compiler = false; Split(option.substr(strlen("--verbose-methods=")).ToString(), ',', &verbose_methods_); - } else if (option.starts_with("--dump-init-failures=")) { - ParseDumpInitFailures(option); - } else if (option.starts_with("--swap-file=")) { - swap_file_name_ = option.substr(strlen("--swap-file=")).data(); - } else if (option.starts_with("--swap-fd=")) { - ParseSwapFd(option); - } else if (option == "--abort-on-hard-verifier-error") { - parser_options->abort_on_hard_verifier_error = true; - } else { + } else if (!compiler_options_->ParseCompilerOption(option, Usage)) { Usage("Unknown argument %s", option.data()); } } @@ -1163,7 +941,7 @@ class Dex2Oat FINAL { ProcessOptions(parser_options.get()); // Insert some compiler things. - InsertCompileOptions(argc, argv, parser_options.get()); + InsertCompileOptions(argc, argv); } // Check whether the oat output file is writable, and open it for later. Also open a swap file, @@ -1211,7 +989,6 @@ class Dex2Oat FINAL { // released immediately. unlink(swap_file_name_.c_str()); } - return true; } @@ -1253,7 +1030,7 @@ class Dex2Oat FINAL { callbacks_.reset(new QuickCompilerCallbacks( verification_results_.get(), &method_inliner_map_, - image_ ? + IsBootImage() ? CompilerCallbacks::CallbackMode::kCompileBootImage : CompilerCallbacks::CallbackMode::kCompileApp)); runtime_options.push_back(std::make_pair("compilercallbacks", callbacks_.get())); @@ -1263,7 +1040,7 @@ class Dex2Oat FINAL { // Only allow no boot image for the runtime if we're compiling one. When we compile an app, // we don't want fallback mode, it will abort as we do not push a boot classpath (it might // have been stripped in preopting, anyways). - if (!image_) { + if (!IsBootImage()) { runtime_options.push_back(std::make_pair("-Xno-dex-file-fallback", nullptr)); } // Disable libsigchain. We don't don't need it during compilation and it prevents us @@ -1302,7 +1079,7 @@ class Dex2Oat FINAL { "': " << error_msg; return false; } - } else if (image_) { + } else if (IsBootImage()) { image_classes_.reset(new std::unordered_set<std::string>); } // If --compiled-classes was specified, calculate the full list of classes to compile in the @@ -1410,11 +1187,12 @@ class Dex2Oat FINAL { ScopedObjectAccess soa(self); dex_caches_.push_back(soa.AddLocalReference<jobject>( class_linker->RegisterDexFile(*dex_file, Runtime::Current()->GetLinearAlloc()))); + dex_file->CreateTypeLookupTable(); } // If we use a swap file, ensure we are above the threshold to make it necessary. if (swap_fd_ != -1) { - if (!UseSwap(image_, dex_files_)) { + if (!UseSwap(IsBootImage(), dex_files_)) { close(swap_fd_); swap_fd_ = -1; VLOG(compiler) << "Decided to run without swap."; @@ -1428,7 +1206,7 @@ class Dex2Oat FINAL { * If we're not in interpret-only or verify-none mode, go ahead and compile small applications. * Don't bother to check if we're doing the image. */ - if (!image_ && + if (!IsBootImage() && compiler_options_->IsCompilationEnabled() && compiler_kind_ == Compiler::kQuick) { size_t num_methods = 0; @@ -1482,7 +1260,7 @@ class Dex2Oat FINAL { compiler_kind_, instruction_set_, instruction_set_features_.get(), - image_, + IsBootImage(), image_classes_.release(), compiled_classes_.release(), nullptr, @@ -1495,6 +1273,7 @@ class Dex2Oat FINAL { swap_fd_, profile_file_)); + driver_->SetDexFilesForOatFile(dex_files_); driver_->CompileAll(class_loader, dex_files_, timings_); } @@ -1576,7 +1355,7 @@ class Dex2Oat FINAL { uint32_t image_file_location_oat_checksum = 0; uintptr_t image_file_location_oat_data_begin = 0; int32_t image_patch_delta = 0; - if (image_) { + if (IsImage()) { PrepareImageWriter(image_base_); } else { TimingLogger::ScopedTiming t3("Loading image checksum", timings_); @@ -1601,7 +1380,7 @@ class Dex2Oat FINAL { key_value_store_.get())); } - if (image_) { + if (IsImage()) { // The OatWriter constructor has already updated offsets in methods and we need to // prepare method offsets in the image address space for direct method patching. TimingLogger::ScopedTiming t2("dex2oat Prepare image address space", timings_); @@ -1626,7 +1405,7 @@ class Dex2Oat FINAL { // If we are compiling an image, invoke the image creation routine. Else just skip. bool HandleImage() { - if (image_) { + if (IsImage()) { TimingLogger::ScopedTiming t("dex2oat ImageWriter", timings_); if (!CreateImageFile()) { return false; @@ -1709,7 +1488,15 @@ class Dex2Oat FINAL { } bool IsImage() const { - return image_; + return IsAppImage() || IsBootImage(); + } + + bool IsAppImage() const { + return app_image_; + } + + bool IsBootImage() const { + return boot_image_; } bool IsHost() const { @@ -1811,7 +1598,10 @@ class Dex2Oat FINAL { bool CreateImageFile() REQUIRES(!Locks::mutator_lock_) { CHECK(image_writer_ != nullptr); - if (!image_writer_->Write(image_filename_, oat_unstripped_, oat_location_)) { + if (!image_writer_->Write(app_image_fd_, + IsBootImage() ? image_filename_ : app_image_file_name_, + oat_unstripped_, + oat_location_)) { LOG(ERROR) << "Failed to create image file " << image_filename_; return false; } @@ -1820,8 +1610,8 @@ class Dex2Oat FINAL { // Destroy ImageWriter before doing FixupElf. image_writer_.reset(); - // Do not fix up the ELF file if we are --compile-pic - if (!compiler_options_->GetCompilePic()) { + // Do not fix up the ELF file if we are --compile-pic or compiing the app image + if (!compiler_options_->GetCompilePic() && IsBootImage()) { std::unique_ptr<File> oat_file(OS::OpenFileReadWrite(oat_unstripped_.c_str())); if (oat_file.get() == nullptr) { PLOG(ERROR) << "Failed to open ELF file: " << oat_unstripped_; @@ -1983,7 +1773,8 @@ class Dex2Oat FINAL { std::unique_ptr<std::unordered_set<std::string>> image_classes_; std::unique_ptr<std::unordered_set<std::string>> compiled_classes_; std::unique_ptr<std::unordered_set<std::string>> compiled_methods_; - bool image_; + bool app_image_; + bool boot_image_; bool is_host_; std::string android_root_; std::vector<const DexFile*> dex_files_; @@ -2002,10 +1793,11 @@ class Dex2Oat FINAL { bool dump_cfg_append_; std::string swap_file_name_; int swap_fd_; + std::string app_image_file_name_; + int app_image_fd_; std::string profile_file_; // Profile file to use TimingLogger* timings_; std::unique_ptr<CumulativeLogger> compiler_phases_timings_; - std::unique_ptr<std::ostream> init_failure_output_; DISALLOW_IMPLICIT_CONSTRUCTORS(Dex2Oat); }; @@ -2131,7 +1923,7 @@ static int dex2oat(int argc, char** argv) { // 3) Compiling with --host // 4) Compiling on the host (not a target build) // Otherwise, print a stripped command line. - if (kIsDebugBuild || dex2oat.IsImage() || dex2oat.IsHost() || !kIsTargetBuild) { + if (kIsDebugBuild || dex2oat.IsBootImage() || dex2oat.IsHost() || !kIsTargetBuild) { LOG(INFO) << CommandLine(); } else { LOG(INFO) << StrippedCommandLine(); diff --git a/dexdump/dexdump.cc b/dexdump/dexdump.cc index 282db5de83..52e6c023fe 100644 --- a/dexdump/dexdump.cc +++ b/dexdump/dexdump.cc @@ -775,7 +775,7 @@ static void dumpInstruction(const DexFile* pDexFile, // case Instruction::k35ms: // [opt] invoke-virtual+super // case Instruction::k35mi: // [opt] inline invoke { - u4 arg[5]; + u4 arg[Instruction::kMaxVarArgRegs]; pDecInsn->GetVarArgs(arg); fputs(" {", gOutFile); for (int i = 0, n = pDecInsn->VRegA(); i < n; i++) { @@ -788,6 +788,21 @@ static void dumpInstruction(const DexFile* pDexFile, fprintf(gOutFile, "}, %s", indexBuf); } break; + case Instruction::k25x: // op vC, {vD, vE, vF, vG} (B: count) + { + u4 arg[Instruction::kMaxVarArgRegs25x]; + pDecInsn->GetAllArgs25x(arg); + fprintf(gOutFile, " v%d, {", arg[0]); + for (int i = 0, n = pDecInsn->VRegB(); i < n; i++) { + if (i == 0) { + fprintf(gOutFile, "v%d", arg[Instruction::kLambdaVirtualRegisterWidth + i]); + } else { + fprintf(gOutFile, ", v%d", arg[Instruction::kLambdaVirtualRegisterWidth + i]); + } + } // for + fputc('}', gOutFile); + } + break; case Instruction::k3rc: // op {vCCCC .. v(CCCC+AA-1)}, thing@BBBB // NOT SUPPORTED: // case Instruction::k3rms: // [opt] invoke-virtual+super/range diff --git a/runtime/Android.mk b/runtime/Android.mk index 09d73119e6..1fdffe3e17 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -47,6 +47,7 @@ LIBART_COMMON_SRC_FILES := \ dex_file_verifier.cc \ dex_instruction.cc \ elf_file.cc \ + fault_handler.cc \ gc/allocation_record.cc \ gc/allocator/dlmalloc.cc \ gc/allocator/rosalloc.cc \ @@ -162,6 +163,7 @@ LIBART_COMMON_SRC_FILES := \ os_linux.cc \ parsed_options.cc \ primitive.cc \ + profiler.cc \ quick_exception_handler.cc \ quick/inline_method_analyser.cc \ reference_table.cc \ @@ -176,8 +178,7 @@ LIBART_COMMON_SRC_FILES := \ thread_pool.cc \ trace.cc \ transaction.cc \ - profiler.cc \ - fault_handler.cc \ + type_lookup_table.cc \ utf.cc \ utils.cc \ verifier/dex_gc_map.cc \ diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index be5a15ec39..9ccabad1cc 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -1437,7 +1437,107 @@ END art_quick_set64_static ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // Generate the allocation entrypoints for each allocator. -GENERATE_ALL_ALLOC_ENTRYPOINTS +GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_tlab, TLAB) +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_rosalloc, RosAlloc). +ENTRY art_quick_alloc_object_rosalloc + // Fast path rosalloc allocation. + // x0: type_idx/return value, x1: ArtMethod*, xSELF(x19): Thread::Current + // x2-x7: free. + ldr x2, [x1, #ART_METHOD_DEX_CACHE_TYPES_OFFSET_64] // Load dex cache resolved types array + // Load the class (x2) + ldr w2, [x2, x0, lsl #COMPRESSED_REFERENCE_SIZE_SHIFT] + cbz x2, .Lart_quick_alloc_object_rosalloc_slow_path // Check null class + // Check class status. + ldr w3, [x2, #MIRROR_CLASS_STATUS_OFFSET] + cmp x3, #MIRROR_CLASS_STATUS_INITIALIZED + bne .Lart_quick_alloc_object_rosalloc_slow_path + // Add a fake dependence from the + // following access flag and size + // loads to the status load. + // This is to prevent those loads + // from being reordered above the + // status load and reading wrong + // values (an alternative is to use + // a load-acquire for the status). + eor x3, x3, x3 + add x2, x2, x3 + // Check access flags has + // kAccClassIsFinalizable + ldr w3, [x2, #MIRROR_CLASS_ACCESS_FLAGS_OFFSET] + tst x3, #ACCESS_FLAGS_CLASS_IS_FINALIZABLE + bne .Lart_quick_alloc_object_rosalloc_slow_path + ldr x3, [xSELF, #THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET] // Check if the thread local + // allocation stack has room. + // ldp won't work due to large offset. + ldr x4, [xSELF, #THREAD_LOCAL_ALLOC_STACK_END_OFFSET] + cmp x3, x4 + bhs .Lart_quick_alloc_object_rosalloc_slow_path + ldr w3, [x2, #MIRROR_CLASS_OBJECT_SIZE_OFFSET] // Load the object size (x3) + cmp x3, #ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE // Check if the size is for a thread + // local allocation + bhs .Lart_quick_alloc_object_rosalloc_slow_path + // Compute the rosalloc bracket index + // from the size. + // Align up the size by the rosalloc + // bracket quantum size and divide + // by the quantum size and subtract + // by 1. This code is a shorter but + // equivalent version. + sub x3, x3, #1 + lsr x3, x3, #ROSALLOC_BRACKET_QUANTUM_SIZE_SHIFT + // Load the rosalloc run (x4) + add x4, xSELF, x3, lsl #POINTER_SIZE_SHIFT + ldr x4, [x4, #THREAD_ROSALLOC_RUNS_OFFSET] + // Load the free list head (x3). This + // will be the return val. + ldr x3, [x4, #(ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)] + cbz x3, .Lart_quick_alloc_object_rosalloc_slow_path + // "Point of no slow path". Won't go to the slow path from here on. OK to clobber x0 and x1. + ldr x1, [x3, #ROSALLOC_SLOT_NEXT_OFFSET] // Load the next pointer of the head + // and update the list head with the + // next pointer. + str x1, [x4, #(ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)] + // Store the class pointer in the + // header. This also overwrites the + // next pointer. The offsets are + // asserted to match. +#if ROSALLOC_SLOT_NEXT_OFFSET != MIRROR_OBJECT_CLASS_OFFSET +#error "Class pointer needs to overwrite next pointer." +#endif + POISON_HEAP_REF w2 + str w2, [x3, #MIRROR_OBJECT_CLASS_OFFSET] + // Push the new object onto the thread + // local allocation stack and + // increment the thread local + // allocation stack top. + ldr x1, [xSELF, #THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET] + str w3, [x1], #COMPRESSED_REFERENCE_SIZE // (Increment x1 as a side effect.) + str x1, [xSELF, #THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET] + // Decrement the size of the free list + ldr w1, [x4, #(ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_SIZE_OFFSET)] + sub x1, x1, #1 + // TODO: consider combining this store + // and the list head store above using + // strd. + str w1, [x4, #(ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_SIZE_OFFSET)] + // Fence. This is "ish" not "ishst" so + // that the code after this allocation + // site will see the right values in + // the fields of the class. + // Alternatively we could use "ishst" + // if we use load-acquire for the + // class status load.) + dmb ish + mov x0, x3 // Set the return value and return. + ret +.Lart_quick_alloc_object_rosalloc_slow_path: + SETUP_REFS_ONLY_CALLEE_SAVE_FRAME // save callee saves in case of GC + mov x2, xSELF // pass Thread::Current + bl artAllocObjectFromCodeRosAlloc // (uint32_t type_idx, Method* method, Thread*) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME + RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER +END art_quick_alloc_object_rosalloc /* * Called by managed code when the thread has been asked to suspend. diff --git a/runtime/arch/mips/context_mips.cc b/runtime/arch/mips/context_mips.cc index 4dedb3339e..375a03acee 100644 --- a/runtime/arch/mips/context_mips.cc +++ b/runtime/arch/mips/context_mips.cc @@ -28,11 +28,11 @@ void MipsContext::Reset() { std::fill_n(gprs_, arraysize(gprs_), nullptr); std::fill_n(fprs_, arraysize(fprs_), nullptr); gprs_[SP] = &sp_; - gprs_[RA] = &ra_; + gprs_[T9] = &t9_; gprs_[A0] = &arg0_; // Initialize registers with easy to spot debug values. sp_ = MipsContext::kBadGprBase + SP; - ra_ = MipsContext::kBadGprBase + RA; + t9_ = MipsContext::kBadGprBase + T9; arg0_ = 0; } diff --git a/runtime/arch/mips/context_mips.h b/runtime/arch/mips/context_mips.h index f1e2905592..7dcff630d1 100644 --- a/runtime/arch/mips/context_mips.h +++ b/runtime/arch/mips/context_mips.h @@ -41,7 +41,7 @@ class MipsContext : public Context { } void SetPC(uintptr_t new_pc) OVERRIDE { - SetGPR(RA, new_pc); + SetGPR(T9, new_pc); } bool IsAccessibleGPR(uint32_t reg) OVERRIDE { @@ -86,9 +86,10 @@ class MipsContext : public Context { // Pointers to registers in the stack, initialized to null except for the special cases below. uintptr_t* gprs_[kNumberOfCoreRegisters]; uint32_t* fprs_[kNumberOfFRegisters]; - // Hold values for sp and ra (return address) if they are not located within a stack frame, as - // well as the first argument. - uintptr_t sp_, ra_, arg0_; + // Hold values for sp and t9 if they are not located within a stack frame. We use t9 for the + // PC (as ra is required to be valid for single-frame deopt and must not be clobbered). We + // also need the first argument for single-frame deopt. + uintptr_t sp_, t9_, arg0_; }; } // namespace mips } // namespace art diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S index ba58c3fccb..0691f2a620 100644 --- a/runtime/arch/mips/quick_entrypoints_mips.S +++ b/runtime/arch/mips/quick_entrypoints_mips.S @@ -374,7 +374,7 @@ ENTRY art_quick_do_long_jump lw $ra, 124($a0) lw $a0, 16($a0) move $v0, $zero # clear result registers r0 and r1 - jalr $zero, $ra # do long jump + jalr $zero, $t9 # do long jump move $v1, $zero END art_quick_do_long_jump diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index f741732046..cf548ada33 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -468,12 +468,6 @@ void ArtMethod::VisitRoots(RootVisitorType& visitor, size_t pointer_size) { } } -inline void ArtMethod::CopyFrom(const ArtMethod* src, size_t image_pointer_size) { - memcpy(reinterpret_cast<void*>(this), reinterpret_cast<const void*>(src), - Size(image_pointer_size)); - declaring_class_ = GcRoot<mirror::Class>(const_cast<ArtMethod*>(src)->GetDeclaringClass()); -} - } // namespace art #endif // ART_RUNTIME_ART_METHOD_INL_H_ diff --git a/runtime/art_method.cc b/runtime/art_method.cc index f5befdfc07..f4a5f233ff 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -163,18 +163,13 @@ uint32_t ArtMethod::FindDexMethodIndexInOtherDexFile(const DexFile& other_dexfil return dex_method_idx; } const char* mid_declaring_class_descriptor = dexfile->StringByTypeIdx(mid.class_idx_); - const DexFile::StringId* other_descriptor = - other_dexfile.FindStringId(mid_declaring_class_descriptor); - if (other_descriptor != nullptr) { - const DexFile::TypeId* other_type_id = - other_dexfile.FindTypeId(other_dexfile.GetIndexForStringId(*other_descriptor)); - if (other_type_id != nullptr) { - const DexFile::MethodId* other_mid = other_dexfile.FindMethodId( - *other_type_id, other_dexfile.GetStringId(name_and_sig_mid.name_idx_), - other_dexfile.GetProtoId(name_and_sig_mid.proto_idx_)); - if (other_mid != nullptr) { - return other_dexfile.GetIndexForMethodId(*other_mid); - } + const DexFile::TypeId* other_type_id = other_dexfile.FindTypeId(mid_declaring_class_descriptor); + if (other_type_id != nullptr) { + const DexFile::MethodId* other_mid = other_dexfile.FindMethodId( + *other_type_id, other_dexfile.GetStringId(name_and_sig_mid.name_idx_), + other_dexfile.GetProtoId(name_and_sig_mid.proto_idx_)); + if (other_mid != nullptr) { + return other_dexfile.GetIndexForMethodId(*other_mid); } } return DexFile::kDexNoIndex; @@ -361,19 +356,6 @@ bool ArtMethod::EqualParameters(Handle<mirror::ObjectArray<mirror::Class>> param return true; } -ProfilingInfo* ArtMethod::CreateProfilingInfo() { - DCHECK(!Runtime::Current()->IsAotCompiler()); - ProfilingInfo* info = ProfilingInfo::Create(this); - MemberOffset offset = ArtMethod::EntryPointFromJniOffset(sizeof(void*)); - uintptr_t pointer = reinterpret_cast<uintptr_t>(this) + offset.Uint32Value(); - if (!reinterpret_cast<Atomic<ProfilingInfo*>*>(pointer)-> - CompareExchangeStrongSequentiallyConsistent(nullptr, info)) { - return GetProfilingInfo(sizeof(void*)); - } else { - return info; - } -} - const uint8_t* ArtMethod::GetQuickenedInfo() { bool found = false; OatFile::OatMethod oat_method = @@ -385,27 +367,119 @@ const uint8_t* ArtMethod::GetQuickenedInfo() { } const OatQuickMethodHeader* ArtMethod::GetOatQuickMethodHeader(uintptr_t pc) { - if (IsRuntimeMethod() || IsProxyMethod()) { + if (IsRuntimeMethod()) { return nullptr; } Runtime* runtime = Runtime::Current(); - const void* code = runtime->GetInstrumentation()->GetQuickCodeFor(this, sizeof(void*)); - DCHECK(code != nullptr); + const void* existing_entry_point = GetEntryPointFromQuickCompiledCode(); + DCHECK(existing_entry_point != nullptr); + ClassLinker* class_linker = runtime->GetClassLinker(); - if (runtime->GetClassLinker()->IsQuickGenericJniStub(code)) { + if (class_linker->IsQuickGenericJniStub(existing_entry_point)) { // The generic JNI does not have any method header. return nullptr; } - code = EntryPointToCodePointer(code); - OatQuickMethodHeader* method_header = reinterpret_cast<OatQuickMethodHeader*>( - reinterpret_cast<uintptr_t>(code) - sizeof(OatQuickMethodHeader)); + if (existing_entry_point == GetQuickProxyInvokeHandler()) { + DCHECK(IsProxyMethod() && !IsConstructor()); + // The proxy entry point does not have any method header. + return nullptr; + } - // TODO(ngeoffray): validate the pc. Note that unit tests can give unrelated pcs (for - // example arch_test). - UNUSED(pc); + // Check whether the current entry point contains this pc. + if (!class_linker->IsQuickResolutionStub(existing_entry_point) && + !class_linker->IsQuickToInterpreterBridge(existing_entry_point)) { + OatQuickMethodHeader* method_header = + OatQuickMethodHeader::FromEntryPoint(existing_entry_point); + + if (method_header->Contains(pc)) { + return method_header; + } + } + + // Check whether the pc is in the JIT code cache. + jit::Jit* jit = Runtime::Current()->GetJit(); + if (jit != nullptr) { + jit::JitCodeCache* code_cache = jit->GetCodeCache(); + OatQuickMethodHeader* method_header = code_cache->LookupMethodHeader(pc, this); + if (method_header != nullptr) { + DCHECK(method_header->Contains(pc)); + return method_header; + } else { + DCHECK(!code_cache->ContainsPc(reinterpret_cast<const void*>(pc))) << std::hex << pc; + } + } + + // The code has to be in an oat file. + bool found; + OatFile::OatMethod oat_method = class_linker->FindOatMethodFor(this, &found); + if (!found) { + if (class_linker->IsQuickResolutionStub(existing_entry_point)) { + // We are running the generic jni stub, but the entry point of the method has not + // been updated yet. + DCHECK_EQ(pc, 0u) << "Should be a downcall"; + DCHECK(IsNative()); + return nullptr; + } + if (existing_entry_point == GetQuickInstrumentationEntryPoint()) { + // We are running the generic jni stub, but the method is being instrumented. + DCHECK_EQ(pc, 0u) << "Should be a downcall"; + DCHECK(IsNative()); + return nullptr; + } + // Only for unit tests. + // TODO(ngeoffray): Update these tests to pass the right pc? + return OatQuickMethodHeader::FromEntryPoint(existing_entry_point); + } + const void* oat_entry_point = oat_method.GetQuickCode(); + if (oat_entry_point == nullptr || class_linker->IsQuickGenericJniStub(oat_entry_point)) { + DCHECK(IsNative()); + return nullptr; + } + + OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromEntryPoint(oat_entry_point); + if (pc == 0) { + // This is a downcall, it can only happen for a native method. + DCHECK(IsNative()); + return method_header; + } + + if (pc == reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc())) { + // If we're instrumenting, just return the compiled OAT code. + // TODO(ngeoffray): Avoid this call path. + return method_header; + } + + DCHECK(method_header->Contains(pc)) + << PrettyMethod(this) + << std::hex << pc << " " << oat_entry_point + << " " << (uintptr_t)(method_header->code_ + method_header->code_size_); return method_header; } + +void ArtMethod::CopyFrom(ArtMethod* src, size_t image_pointer_size) { + memcpy(reinterpret_cast<void*>(this), reinterpret_cast<const void*>(src), + Size(image_pointer_size)); + declaring_class_ = GcRoot<mirror::Class>(const_cast<ArtMethod*>(src)->GetDeclaringClass()); + + // If the entry point of the method we are copying from is from JIT code, we just + // put the entry point of the new method to interpreter. We could set the entry point + // to the JIT code, but this would require taking the JIT code cache lock to notify + // it, which we do not want at this level. + Runtime* runtime = Runtime::Current(); + if (runtime->GetJit() != nullptr) { + if (runtime->GetJit()->GetCodeCache()->ContainsPc(GetEntryPointFromQuickCompiledCode())) { + SetEntryPointFromQuickCompiledCodePtrSize(GetQuickToInterpreterBridge(), image_pointer_size); + } + } + // Clear the profiling info for the same reasons as the JIT code. + if (!src->IsNative()) { + SetProfilingInfoPtrSize(nullptr, image_pointer_size); + } + // Clear hotness to let the JIT properly decide when to compile this method. + hotness_count_ = 0; +} + } // namespace art diff --git a/runtime/art_method.h b/runtime/art_method.h index 9f1495cf39..ce9f2025ce 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -49,8 +49,8 @@ class ArtMethod FINAL { ArtMethod() : access_flags_(0), dex_code_item_offset_(0), dex_method_index_(0), method_index_(0) { } - ArtMethod(const ArtMethod& src, size_t image_pointer_size) { - CopyFrom(&src, image_pointer_size); + ArtMethod(ArtMethod* src, size_t image_pointer_size) { + CopyFrom(src, image_pointer_size); } static ArtMethod* FromReflectedMethod(const ScopedObjectAccessAlreadyRunnable& soa, @@ -305,12 +305,22 @@ class ArtMethod FINAL { PtrSizedFields, entry_point_from_quick_compiled_code_) / sizeof(void*) * pointer_size); } - ProfilingInfo* CreateProfilingInfo() SHARED_REQUIRES(Locks::mutator_lock_); - ProfilingInfo* GetProfilingInfo(size_t pointer_size) { return reinterpret_cast<ProfilingInfo*>(GetEntryPointFromJniPtrSize(pointer_size)); } + ALWAYS_INLINE void SetProfilingInfo(ProfilingInfo* info) { + SetEntryPointFromJniPtrSize(info, sizeof(void*)); + } + + ALWAYS_INLINE void SetProfilingInfoPtrSize(ProfilingInfo* info, size_t pointer_size) { + SetEntryPointFromJniPtrSize(info, pointer_size); + } + + static MemberOffset ProfilingInfoOffset() { + return EntryPointFromJniOffset(sizeof(void*)); + } + void* GetEntryPointFromJni() { return GetEntryPointFromJniPtrSize(sizeof(void*)); } @@ -423,7 +433,7 @@ class ArtMethod FINAL { return pointer_size; } - void CopyFrom(const ArtMethod* src, size_t image_pointer_size) + void CopyFrom(ArtMethod* src, size_t image_pointer_size) SHARED_REQUIRES(Locks::mutator_lock_); ALWAYS_INLINE GcRoot<mirror::Class>* GetDexCacheResolvedTypes(size_t pointer_size) @@ -433,6 +443,10 @@ class ArtMethod FINAL { return ++hotness_count_; } + void ClearCounter() { + hotness_count_ = 0; + } + const uint8_t* GetQuickenedInfo() SHARED_REQUIRES(Locks::mutator_lock_); // Returns the method header for the compiled code containing 'pc'. Note that runtime diff --git a/runtime/base/allocator.h b/runtime/base/allocator.h index ad255b8694..969f5b953f 100644 --- a/runtime/base/allocator.h +++ b/runtime/base/allocator.h @@ -22,6 +22,7 @@ #include <unordered_map> #include "atomic.h" +#include "base/hash_map.h" #include "base/macros.h" #include "base/mutex.h" #include "base/type_static_if.h" @@ -170,6 +171,14 @@ template<class Key, using AllocationTrackingUnorderedMap = std::unordered_map< Key, T, Hash, Pred, TrackingAllocator<std::pair<const Key, T>, kTag>>; +template<class Key, + class T, + class EmptyFn, + AllocatorTag kTag, + class Hash = std::hash<Key>, + class Pred = std::equal_to<Key>> +using AllocationTrackingHashMap = HashMap< + Key, T, EmptyFn, Hash, Pred, TrackingAllocator<std::pair<Key, T>, kTag>>; } // namespace art #endif // ART_RUNTIME_BASE_ALLOCATOR_H_ diff --git a/runtime/base/dchecked_vector.h b/runtime/base/dchecked_vector.h index 6ec573a5fb..2bd12df2c3 100644 --- a/runtime/base/dchecked_vector.h +++ b/runtime/base/dchecked_vector.h @@ -59,8 +59,10 @@ class dchecked_vector : private std::vector<T, Alloc> { : Base() { } explicit dchecked_vector(const allocator_type& alloc) : Base(alloc) { } + // Note that we cannot forward to std::vector(size_type, const allocator_type&) because it is not + // available in C++11, which is the latest GCC can support. http://b/25022512 explicit dchecked_vector(size_type n, const allocator_type& alloc = allocator_type()) - : Base(n, alloc) { } + : Base(alloc) { resize(n); } dchecked_vector(size_type n, const value_type& value, const allocator_type& alloc = allocator_type()) diff --git a/runtime/base/hash_set.h b/runtime/base/hash_set.h index 4819f06bb4..95baa822b1 100644 --- a/runtime/base/hash_set.h +++ b/runtime/base/hash_set.h @@ -420,6 +420,19 @@ class HashSet { Resize(Size() / max_load_factor_); } + // Reserve enough room to insert until Size() == num_elements without requiring to grow the hash + // set. No-op if the hash set is already large enough to do this. + void Reserve(size_t num_elements) { + size_t num_buckets = num_elements / max_load_factor_; + // Deal with rounding errors. Add one for rounding. + while (static_cast<size_t>(num_buckets * max_load_factor_) <= num_elements + 1u) { + ++num_buckets; + } + if (num_buckets > NumBuckets()) { + Resize(num_buckets); + } + } + // To distance that inserted elements were probed. Used for measuring how good hash functions // are. size_t TotalProbeDistance() const { @@ -488,6 +501,15 @@ class HashSet { } } + // The hash set expands when Size() reaches ElementsUntilExpand(). + size_t ElementsUntilExpand() const { + return elements_until_expand_; + } + + size_t NumBuckets() const { + return num_buckets_; + } + private: T& ElementForIndex(size_t index) { DCHECK_LT(index, NumBuckets()); @@ -543,10 +565,6 @@ class HashSet { return emptyfn_.IsEmpty(ElementForIndex(index)); } - size_t NumBuckets() const { - return num_buckets_; - } - // Allocate a number of buckets. void AllocateStorage(size_t num_buckets) { num_buckets_ = num_buckets; diff --git a/runtime/base/hash_set_test.cc b/runtime/base/hash_set_test.cc index 743e98ed84..825406313a 100644 --- a/runtime/base/hash_set_test.cc +++ b/runtime/base/hash_set_test.cc @@ -333,4 +333,25 @@ TEST_F(HashSetTest, TestLookupByAlternateKeyType) { ASSERT_NE(hash_set.end(), hash_set.Find(std::forward_list<int>({1, 2, 3, 4}))); } +TEST_F(HashSetTest, TestReserve) { + HashSet<std::string, IsEmptyFnString> hash_set; + std::vector<size_t> sizes = {1, 10, 25, 55, 128, 1024, 4096}; + for (size_t size : sizes) { + hash_set.Reserve(size); + const size_t buckets_before = hash_set.NumBuckets(); + // Check that we expanded enough. + CHECK_GE(hash_set.ElementsUntilExpand(), size); + // Try inserting elements until we are at our reserve size and ensure the hash set did not + // expand. + while (hash_set.Size() < size) { + hash_set.Insert(std::to_string(hash_set.Size())); + } + CHECK_EQ(hash_set.NumBuckets(), buckets_before); + } + // Check the behaviour for shrinking, it does not necessarily resize down. + constexpr size_t size = 100; + hash_set.Reserve(size); + CHECK_GE(hash_set.ElementsUntilExpand(), size); +} + } // namespace art diff --git a/runtime/base/timing_logger.cc b/runtime/base/timing_logger.cc index f1f6f9b1c1..1942e1dc1b 100644 --- a/runtime/base/timing_logger.cc +++ b/runtime/base/timing_logger.cc @@ -125,7 +125,7 @@ void CumulativeLogger::DumpHistogram(std::ostream &os) const { histogram->CreateHistogram(&cumulative_data); histogram->PrintConfidenceIntervals(os, 0.99, cumulative_data); } - os << "Done Dumping histograms \n"; + os << "Done Dumping histograms\n"; } TimingLogger::TimingLogger(const char* name, bool precise, bool verbose) diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 81622e14ed..da70456369 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -616,10 +616,7 @@ void ClassLinker::InitWithoutImage(std::vector<std::unique_ptr<const DexFile>> b // initialized. { const DexFile& dex_file = java_lang_Object->GetDexFile(); - const DexFile::StringId* void_string_id = dex_file.FindStringId("V"); - CHECK(void_string_id != nullptr); - uint32_t void_string_index = dex_file.GetIndexForStringId(*void_string_id); - const DexFile::TypeId* void_type_id = dex_file.FindTypeId(void_string_index); + const DexFile::TypeId* void_type_id = dex_file.FindTypeId("V"); CHECK(void_type_id != nullptr); uint16_t void_type_idx = dex_file.GetIndexForTypeId(*void_type_id); // Now we resolve void type so the dex cache contains it. We use java.lang.Object class @@ -1174,15 +1171,26 @@ ClassLinker::~ClassLinker() { mirror::LongArray::ResetArrayClass(); mirror::ShortArray::ResetArrayClass(); Thread* const self = Thread::Current(); - JavaVMExt* const vm = Runtime::Current()->GetJavaVM(); for (const ClassLoaderData& data : class_loaders_) { - vm->DeleteWeakGlobalRef(self, data.weak_root); - delete data.allocator; - delete data.class_table; + DeleteClassLoader(self, data); } class_loaders_.clear(); } +void ClassLinker::DeleteClassLoader(Thread* self, const ClassLoaderData& data) { + Runtime* const runtime = Runtime::Current(); + JavaVMExt* const vm = runtime->GetJavaVM(); + vm->DeleteWeakGlobalRef(self, data.weak_root); + if (runtime->GetJit() != nullptr) { + jit::JitCodeCache* code_cache = runtime->GetJit()->GetCodeCache(); + if (code_cache != nullptr) { + code_cache->RemoveMethodsIn(self, *data.allocator); + } + } + delete data.allocator; + delete data.class_table; +} + mirror::PointerArray* ClassLinker::AllocPointerArray(Thread* self, size_t length) { return down_cast<mirror::PointerArray*>(image_pointer_size_ == 8u ? static_cast<mirror::Array*>(mirror::LongArray::Alloc(self, length)) : @@ -1833,13 +1841,6 @@ const void* ClassLinker::GetQuickOatCodeFor(ArtMethod* method) { return code; } } - jit::Jit* const jit = Runtime::Current()->GetJit(); - if (jit != nullptr) { - auto* code = jit->GetCodeCache()->GetCodeFor(method); - if (code != nullptr) { - return code; - } - } if (method->IsNative()) { // No code and native? Use generic trampoline. return GetQuickGenericJniStub(); @@ -1856,13 +1857,6 @@ const void* ClassLinker::GetOatMethodQuickCodeFor(ArtMethod* method) { if (found) { return oat_method.GetQuickCode(); } - jit::Jit* jit = Runtime::Current()->GetJit(); - if (jit != nullptr) { - auto* code = jit->GetCodeCache()->GetCodeFor(method); - if (code != nullptr) { - return code; - } - } return nullptr; } @@ -2743,17 +2737,13 @@ mirror::Class* ClassLinker::LookupClassFromImage(const char* descriptor) { for (int32_t i = 0; i < dex_caches->GetLength(); ++i) { mirror::DexCache* dex_cache = dex_caches->Get(i); const DexFile* dex_file = dex_cache->GetDexFile(); - // Try binary searching the string/type index. - const DexFile::StringId* string_id = dex_file->FindStringId(descriptor); - if (string_id != nullptr) { - const DexFile::TypeId* type_id = - dex_file->FindTypeId(dex_file->GetIndexForStringId(*string_id)); - if (type_id != nullptr) { - uint16_t type_idx = dex_file->GetIndexForTypeId(*type_id); - mirror::Class* klass = dex_cache->GetResolvedType(type_idx); - if (klass != nullptr) { - return klass; - } + // Try binary searching the type index by descriptor. + const DexFile::TypeId* type_id = dex_file->FindTypeId(descriptor); + if (type_id != nullptr) { + uint16_t type_idx = dex_file->GetIndexForTypeId(*type_id); + mirror::Class* klass = dex_cache->GetResolvedType(type_idx); + if (klass != nullptr) { + return klass; } } } @@ -5289,7 +5279,7 @@ bool ClassLinker::LinkInterfaceMethods( miranda_method = reinterpret_cast<ArtMethod*>(allocator.Alloc(method_size)); CHECK(miranda_method != nullptr); // Point the interface table at a phantom slot. - new(miranda_method) ArtMethod(*interface_method, image_pointer_size_); + new(miranda_method) ArtMethod(interface_method, image_pointer_size_); miranda_methods.push_back(miranda_method); } method_array->SetElementPtrSize(j, miranda_method, image_pointer_size_); @@ -5325,7 +5315,7 @@ bool ClassLinker::LinkInterfaceMethods( ScopedArenaUnorderedMap<ArtMethod*, ArtMethod*> move_table(allocator.Adapter()); if (virtuals != old_virtuals) { // Maps from heap allocated miranda method to linear alloc miranda method. - StrideIterator<ArtMethod> out = virtuals->Begin(method_size, method_alignment); + StrideIterator<ArtMethod> out = virtuals->begin(method_size, method_alignment); // Copy over the old methods + miranda methods. for (auto& m : klass->GetVirtualMethods(image_pointer_size_)) { move_table.emplace(&m, &*out); @@ -5335,7 +5325,7 @@ bool ClassLinker::LinkInterfaceMethods( ++out; } } - StrideIterator<ArtMethod> out(virtuals->Begin(method_size, method_alignment) + StrideIterator<ArtMethod> out(virtuals->begin(method_size, method_alignment) + old_method_count); // Copy over miranda methods before copying vtable since CopyOf may cause thread suspension and // we want the roots of the miranda methods to get visited. @@ -5367,7 +5357,7 @@ bool ClassLinker::LinkInterfaceMethods( move_table.emplace(def_method, &new_method); ++out; } - virtuals->SetLength(new_method_count); + virtuals->SetSize(new_method_count); UpdateClassVirtualMethods(klass.Get(), virtuals); // Done copying methods, they are all roots in the class now, so we can end the no thread // suspension assert. @@ -5382,7 +5372,7 @@ bool ClassLinker::LinkInterfaceMethods( self->AssertPendingOOMException(); return false; } - out = virtuals->Begin(method_size, method_alignment) + old_method_count; + out = virtuals->begin(method_size, method_alignment) + old_method_count; size_t vtable_pos = old_vtable_count; for (size_t i = old_method_count; i < new_method_count; ++i) { // Leave the declaring class alone as type indices are relative to it @@ -6387,7 +6377,6 @@ void ClassLinker::InsertDexFileInToClassLoader(mirror::Object* dex_file, void ClassLinker::CleanupClassLoaders() { Thread* const self = Thread::Current(); WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); - JavaVMExt* const vm = Runtime::Current()->GetJavaVM(); for (auto it = class_loaders_.begin(); it != class_loaders_.end(); ) { const ClassLoaderData& data = *it; // Need to use DecodeJObject so that we get null for cleared JNI weak globals. @@ -6395,10 +6384,7 @@ void ClassLinker::CleanupClassLoaders() { if (class_loader != nullptr) { ++it; } else { - // Weak reference was cleared, delete the data associated with this class loader. - delete data.class_table; - delete data.allocator; - vm->DeleteWeakGlobalRef(self, data.weak_root); + DeleteClassLoader(self, data); it = class_loaders_.erase(it); } } diff --git a/runtime/class_linker.h b/runtime/class_linker.h index a2d38ac620..392efd23e2 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -551,6 +551,10 @@ class ClassLinker { LinearAlloc* allocator; }; + static void DeleteClassLoader(Thread* self, const ClassLoaderData& data) + REQUIRES(Locks::classlinker_classes_lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + void VisitClassLoaders(ClassLoaderVisitor* visitor) const SHARED_REQUIRES(Locks::classlinker_classes_lock_, Locks::mutator_lock_); diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index 0926ce3f6a..04b890063d 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -1032,9 +1032,7 @@ TEST_F(ClassLinkerTest, ResolveVerifyAndClinit) { mirror::Class* klass = class_linker_->FindClass(soa.Self(), "LStaticsFromCode;", class_loader); ArtMethod* clinit = klass->FindClassInitializer(sizeof(void*)); ArtMethod* getS0 = klass->FindDirectMethod("getS0", "()Ljava/lang/Object;", sizeof(void*)); - const DexFile::StringId* string_id = dex_file->FindStringId("LStaticsFromCode;"); - ASSERT_TRUE(string_id != nullptr); - const DexFile::TypeId* type_id = dex_file->FindTypeId(dex_file->GetIndexForStringId(*string_id)); + const DexFile::TypeId* type_id = dex_file->FindTypeId("LStaticsFromCode;"); ASSERT_TRUE(type_id != nullptr); uint32_t type_idx = dex_file->GetIndexForTypeId(*type_id); mirror::Class* uninit = ResolveVerifyAndClinit(type_idx, clinit, soa.Self(), true, false); diff --git a/runtime/debugger.cc b/runtime/debugger.cc index b17b76e2ea..7117be9a54 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -69,29 +69,26 @@ static uint16_t CappedAllocRecordCount(size_t alloc_record_count) { return alloc_record_count; } -class Breakpoint { +class Breakpoint : public ValueObject { public: - Breakpoint(ArtMethod* method, uint32_t dex_pc, - DeoptimizationRequest::Kind deoptimization_kind) - SHARED_REQUIRES(Locks::mutator_lock_) - : method_(nullptr), dex_pc_(dex_pc), deoptimization_kind_(deoptimization_kind) { + Breakpoint(ArtMethod* method, uint32_t dex_pc, DeoptimizationRequest::Kind deoptimization_kind) + : method_(method), + dex_pc_(dex_pc), + deoptimization_kind_(deoptimization_kind) { CHECK(deoptimization_kind_ == DeoptimizationRequest::kNothing || deoptimization_kind_ == DeoptimizationRequest::kSelectiveDeoptimization || deoptimization_kind_ == DeoptimizationRequest::kFullDeoptimization); - ScopedObjectAccessUnchecked soa(Thread::Current()); - method_ = soa.EncodeMethod(method); } Breakpoint(const Breakpoint& other) SHARED_REQUIRES(Locks::mutator_lock_) - : method_(nullptr), dex_pc_(other.dex_pc_), - deoptimization_kind_(other.deoptimization_kind_) { - ScopedObjectAccessUnchecked soa(Thread::Current()); - method_ = soa.EncodeMethod(other.Method()); - } + : method_(other.method_), + dex_pc_(other.dex_pc_), + deoptimization_kind_(other.deoptimization_kind_) {} - ArtMethod* Method() const SHARED_REQUIRES(Locks::mutator_lock_) { - ScopedObjectAccessUnchecked soa(Thread::Current()); - return soa.DecodeMethod(method_); + // Method() is called from root visiting, do not use ScopedObjectAccess here or it can cause + // GC to deadlock if another thread tries to call SuspendAll while the GC is in a runnable state. + ArtMethod* Method() const { + return method_; } uint32_t DexPc() const { @@ -104,7 +101,7 @@ class Breakpoint { private: // The location of this breakpoint. - jmethodID method_; + ArtMethod* method_; uint32_t dex_pc_; // Indicates whether breakpoint needs full deoptimization or selective deoptimization. diff --git a/runtime/dex_file.cc b/runtime/dex_file.cc index ae62e2bfe1..3a93aace83 100644 --- a/runtime/dex_file.cc +++ b/runtime/dex_file.cc @@ -37,6 +37,7 @@ #include "dex_file-inl.h" #include "dex_file_verifier.h" #include "globals.h" +#include "handle_scope-inl.h" #include "leb128.h" #include "mirror/field.h" #include "mirror/method.h" @@ -44,8 +45,8 @@ #include "os.h" #include "reflection.h" #include "safe_map.h" -#include "handle_scope-inl.h" #include "thread.h" +#include "type_lookup_table.h" #include "utf-inl.h" #include "utils.h" #include "well_known_classes.h" @@ -414,11 +415,19 @@ DexFile::DexFile(const uint8_t* base, size_t size, method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)), proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)), class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)), - find_class_def_misses_(0), - class_def_index_(nullptr), oat_dex_file_(oat_dex_file) { CHECK(begin_ != nullptr) << GetLocation(); CHECK_GT(size_, 0U) << GetLocation(); + const uint8_t* lookup_data = (oat_dex_file != nullptr) + ? oat_dex_file->GetLookupTableData() + : nullptr; + if (lookup_data != nullptr) { + if (lookup_data + TypeLookupTable::RawDataLength(*this) > oat_dex_file->GetOatFile()->End()) { + LOG(WARNING) << "found truncated lookup table in " << GetLocation(); + } else { + lookup_table_.reset(TypeLookupTable::Open(lookup_data, *this)); + } + } } DexFile::~DexFile() { @@ -426,8 +435,6 @@ DexFile::~DexFile() { // that's only called after DetachCurrentThread, which means there's no JNIEnv. We could // re-attach, but cleaning up these global references is not obviously useful. It's not as if // the global reference table is otherwise empty! - // Remove the index if one were created. - delete class_def_index_.LoadRelaxed(); } bool DexFile::Init(std::string* error_msg) { @@ -477,50 +484,25 @@ uint32_t DexFile::GetVersion() const { const DexFile::ClassDef* DexFile::FindClassDef(const char* descriptor, size_t hash) const { DCHECK_EQ(ComputeModifiedUtf8Hash(descriptor), hash); - // If we have an index lookup the descriptor via that as its constant time to search. - Index* index = class_def_index_.LoadSequentiallyConsistent(); - if (index != nullptr) { - auto it = index->FindWithHash(descriptor, hash); - return (it == index->end()) ? nullptr : it->second; + if (LIKELY(lookup_table_ != nullptr)) { + const uint32_t class_def_idx = lookup_table_->Lookup(descriptor, hash); + return (class_def_idx != DexFile::kDexNoIndex) ? &GetClassDef(class_def_idx) : nullptr; } + // Fast path for rate no class defs case. - uint32_t num_class_defs = NumClassDefs(); + const uint32_t num_class_defs = NumClassDefs(); if (num_class_defs == 0) { return nullptr; } - // Search for class def with 2 binary searches and then a linear search. - const StringId* string_id = FindStringId(descriptor); - if (string_id != nullptr) { - const TypeId* type_id = FindTypeId(GetIndexForStringId(*string_id)); - if (type_id != nullptr) { - uint16_t type_idx = GetIndexForTypeId(*type_id); - for (size_t i = 0; i < num_class_defs; ++i) { - const ClassDef& class_def = GetClassDef(i); - if (class_def.class_idx_ == type_idx) { - return &class_def; - } - } - } - } - // A miss. If we've had kMaxFailedDexClassDefLookups misses then build an index to speed things - // up. This isn't done eagerly at construction as construction is not performed in multi-threaded - // sections of tools like dex2oat. If we're lazy we hopefully increase the chance of balancing - // out which thread builds the index. - const uint32_t kMaxFailedDexClassDefLookups = 100; - uint32_t old_misses = find_class_def_misses_.FetchAndAddSequentiallyConsistent(1); - if (old_misses == kMaxFailedDexClassDefLookups) { - // Are we the ones moving the miss count past the max? Sanity check the index doesn't exist. - CHECK(class_def_index_.LoadSequentiallyConsistent() == nullptr); - // Build the index. - index = new Index(); - for (uint32_t i = 0; i < num_class_defs; ++i) { + const TypeId* type_id = FindTypeId(descriptor); + if (type_id != nullptr) { + uint16_t type_idx = GetIndexForTypeId(*type_id); + for (size_t i = 0; i < num_class_defs; ++i) { const ClassDef& class_def = GetClassDef(i); - const char* class_descriptor = GetClassDescriptor(class_def); - index->Insert(std::make_pair(class_descriptor, &class_def)); + if (class_def.class_idx_ == type_idx) { + return &class_def; + } } - // Sanity check the index still doesn't exist, only 1 thread should build it. - CHECK(class_def_index_.LoadSequentiallyConsistent() == nullptr); - class_def_index_.StoreSequentiallyConsistent(index); } return nullptr; } @@ -625,6 +607,26 @@ const DexFile::StringId* DexFile::FindStringId(const char* string) const { return nullptr; } +const DexFile::TypeId* DexFile::FindTypeId(const char* string) const { + int32_t lo = 0; + int32_t hi = NumTypeIds() - 1; + while (hi >= lo) { + int32_t mid = (hi + lo) / 2; + const TypeId& type_id = GetTypeId(mid); + const DexFile::StringId& str_id = GetStringId(type_id.descriptor_idx_); + const char* str = GetStringData(str_id); + int compare = CompareModifiedUtf8ToModifiedUtf8AsUtf16CodePointValues(string, str); + if (compare > 0) { + lo = mid + 1; + } else if (compare < 0) { + hi = mid - 1; + } else { + return &type_id; + } + } + return nullptr; +} + const DexFile::StringId* DexFile::FindStringId(const uint16_t* string, size_t length) const { int32_t lo = 0; int32_t hi = NumStringIds() - 1; @@ -697,6 +699,10 @@ const DexFile::ProtoId* DexFile::FindProtoId(uint16_t return_type_idx, return nullptr; } +void DexFile::CreateTypeLookupTable() const { + lookup_table_.reset(TypeLookupTable::Create(*this)); +} + // Given a signature place the type ids into the given vector bool DexFile::CreateTypeList(const StringPiece& signature, uint16_t* return_type_idx, std::vector<uint16_t>* param_type_idxs) const { @@ -732,11 +738,7 @@ bool DexFile::CreateTypeList(const StringPiece& signature, uint16_t* return_type } // TODO: avoid creating a std::string just to get a 0-terminated char array std::string descriptor(signature.data() + start_offset, offset - start_offset); - const DexFile::StringId* string_id = FindStringId(descriptor.c_str()); - if (string_id == nullptr) { - return false; - } - const DexFile::TypeId* type_id = FindTypeId(GetIndexForStringId(*string_id)); + const DexFile::TypeId* type_id = FindTypeId(descriptor.c_str()); if (type_id == nullptr) { return false; } diff --git a/runtime/dex_file.h b/runtime/dex_file.h index 47e5c124ff..e7877b2e78 100644 --- a/runtime/dex_file.h +++ b/runtime/dex_file.h @@ -51,6 +51,7 @@ class OatDexFile; class Signature; template<class T> class Handle; class StringPiece; +class TypeLookupTable; class ZipArchive; // TODO: move all of the macro functionality into the DexCache class. @@ -532,6 +533,8 @@ class DexFile { // Looks up a string id for a given modified utf8 string. const StringId* FindStringId(const char* string) const; + const TypeId* FindTypeId(const char* string) const; + // Looks up a string id for a given utf16 string. const StringId* FindStringId(const uint16_t* string, size_t length) const; @@ -1139,6 +1142,12 @@ class DexFile { return oat_dex_file_; } + TypeLookupTable* GetTypeLookupTable() const { + return lookup_table_.get(); + } + + void CreateTypeLookupTable() const; + private: // Opens a .dex file static std::unique_ptr<const DexFile> OpenFile(int fd, const char* location, @@ -1237,44 +1246,11 @@ class DexFile { // Points to the base of the class definition list. const ClassDef* const class_defs_; - // Number of misses finding a class def from a descriptor. - mutable Atomic<uint32_t> find_class_def_misses_; - - struct UTF16EmptyFn { - void MakeEmpty(std::pair<const char*, const ClassDef*>& pair) const { - pair.first = nullptr; - pair.second = nullptr; - } - bool IsEmpty(const std::pair<const char*, const ClassDef*>& pair) const { - if (pair.first == nullptr) { - DCHECK(pair.second == nullptr); - return true; - } - return false; - } - }; - struct UTF16HashCmp { - // Hash function. - size_t operator()(const char* key) const { - return ComputeModifiedUtf8Hash(key); - } - // std::equal function. - bool operator()(const char* a, const char* b) const { - return CompareModifiedUtf8ToModifiedUtf8AsUtf16CodePointValues(a, b) == 0; - } - }; - using Index = HashMap<const char*, - const ClassDef*, - UTF16EmptyFn, - UTF16HashCmp, - UTF16HashCmp, - std::allocator<std::pair<const char*, const ClassDef*>>>; - mutable Atomic<Index*> class_def_index_; - // If this dex file was loaded from an oat file, oat_dex_file_ contains a // pointer to the OatDexFile it was loaded from. Otherwise oat_dex_file_ is // null. const OatDexFile* oat_dex_file_; + mutable std::unique_ptr<TypeLookupTable> lookup_table_; friend class DexFileVerifierTest; }; diff --git a/runtime/dex_file_test.cc b/runtime/dex_file_test.cc index 90b35a3e04..0a167bb8f7 100644 --- a/runtime/dex_file_test.cc +++ b/runtime/dex_file_test.cc @@ -297,6 +297,7 @@ TEST_F(DexFileTest, FindTypeId) { ASSERT_TRUE(type_str_id != nullptr); uint32_t type_str_idx = java_lang_dex_file_->GetIndexForStringId(*type_str_id); const DexFile::TypeId* type_id = java_lang_dex_file_->FindTypeId(type_str_idx); + ASSERT_EQ(type_id, java_lang_dex_file_->FindTypeId(type_str)); ASSERT_TRUE(type_id != nullptr); EXPECT_EQ(java_lang_dex_file_->GetIndexForTypeId(*type_id), i); } diff --git a/runtime/dex_file_verifier.cc b/runtime/dex_file_verifier.cc index a5f9d09900..440d696ea9 100644 --- a/runtime/dex_file_verifier.cc +++ b/runtime/dex_file_verifier.cc @@ -1416,7 +1416,12 @@ bool DexFileVerifier::CheckIntraSectionIterate(size_t offset, uint32_t section_c } if (IsDataSectionType(type)) { - offset_to_type_map_.Put(aligned_offset, type); + if (aligned_offset == 0u) { + ErrorStringPrintf("Item %d offset is 0", i); + return false; + } + DCHECK(offset_to_type_map_.Find(aligned_offset) == offset_to_type_map_.end()); + offset_to_type_map_.Insert(std::pair<uint32_t, uint16_t>(aligned_offset, type)); } aligned_offset = ptr_ - begin_; @@ -1589,7 +1594,8 @@ bool DexFileVerifier::CheckIntraSection() { } bool DexFileVerifier::CheckOffsetToTypeMap(size_t offset, uint16_t type) { - auto it = offset_to_type_map_.find(offset); + DCHECK_NE(offset, 0u); + auto it = offset_to_type_map_.Find(offset); if (UNLIKELY(it == offset_to_type_map_.end())) { ErrorStringPrintf("No data map entry found @ %zx; expected %x", offset, type); return false; diff --git a/runtime/dex_file_verifier.h b/runtime/dex_file_verifier.h index 4f15357ea0..6c63749f04 100644 --- a/runtime/dex_file_verifier.h +++ b/runtime/dex_file_verifier.h @@ -175,7 +175,35 @@ class DexFileVerifier { const char* const location_; const DexFile::Header* const header_; - AllocationTrackingSafeMap<uint32_t, uint16_t, kAllocatorTagDexFileVerifier> offset_to_type_map_; + struct OffsetTypeMapEmptyFn { + // Make a hash map slot empty by making the offset 0. Offset 0 is a valid dex file offset that + // is in the offset of the dex file header. However, we only store data section items in the + // map, and these are after the header. + void MakeEmpty(std::pair<uint32_t, uint16_t>& pair) const { + pair.first = 0u; + } + // Check if a hash map slot is empty. + bool IsEmpty(const std::pair<uint32_t, uint16_t>& pair) const { + return pair.first == 0; + } + }; + struct OffsetTypeMapHashCompareFn { + // Hash function for offset. + size_t operator()(const uint32_t key) const { + return key; + } + // std::equal function for offset. + bool operator()(const uint32_t a, const uint32_t b) const { + return a == b; + } + }; + // Map from offset to dex file type, HashMap for performance reasons. + AllocationTrackingHashMap<uint32_t, + uint16_t, + OffsetTypeMapEmptyFn, + kAllocatorTagDexFileVerifier, + OffsetTypeMapHashCompareFn, + OffsetTypeMapHashCompareFn> offset_to_type_map_; const uint8_t* ptr_; const void* previous_item_; diff --git a/runtime/dex_instruction.h b/runtime/dex_instruction.h index 2871f76114..035230eb8c 100644 --- a/runtime/dex_instruction.h +++ b/runtime/dex_instruction.h @@ -185,6 +185,7 @@ class Instruction { static constexpr uint32_t kMaxVarArgRegs = 5; static constexpr uint32_t kMaxVarArgRegs25x = 6; // lambdas are 2 registers. + static constexpr uint32_t kLambdaVirtualRegisterWidth = 2; // Returns the size (in 2 byte code units) of this instruction. size_t SizeInCodeUnits() const { diff --git a/runtime/exception_test.cc b/runtime/exception_test.cc index 4de8a8ead9..18ccd082ec 100644 --- a/runtime/exception_test.cc +++ b/runtime/exception_test.cc @@ -92,10 +92,25 @@ class ExceptionTest : public CommonRuntimeTest { fake_header_code_and_maps_.insert(fake_header_code_and_maps_.end(), fake_code_.begin(), fake_code_.end()); - // NOTE: Don't align the code (it will not be executed) but check that the Thumb2 - // adjustment will be a NOP, see ArtMethod::EntryPointToCodePointer(). - CHECK_ALIGNED(mapping_table_offset, 2); - const uint8_t* code_ptr = &fake_header_code_and_maps_[gc_map_offset]; + // Align the code. + const size_t alignment = GetInstructionSetAlignment(kRuntimeISA); + fake_header_code_and_maps_.reserve(fake_header_code_and_maps_.size() + alignment); + const void* unaligned_code_ptr = + fake_header_code_and_maps_.data() + (fake_header_code_and_maps_.size() - code_size); + size_t offset = dchecked_integral_cast<size_t>(reinterpret_cast<uintptr_t>(unaligned_code_ptr)); + size_t padding = RoundUp(offset, alignment) - offset; + // Make sure no resizing takes place. + CHECK_GE(fake_header_code_and_maps_.capacity(), fake_header_code_and_maps_.size() + padding); + fake_header_code_and_maps_.insert(fake_header_code_and_maps_.begin(), padding, 0); + const void* code_ptr = reinterpret_cast<const uint8_t*>(unaligned_code_ptr) + padding; + CHECK_EQ(code_ptr, + static_cast<const void*>(fake_header_code_and_maps_.data() + + (fake_header_code_and_maps_.size() - code_size))); + + if (kRuntimeISA == kArm) { + // Check that the Thumb2 adjustment will be a NOP, see EntryPointToCodePointer(). + CHECK_ALIGNED(mapping_table_offset, 2); + } method_f_ = my_klass_->FindVirtualMethod("f", "()I", sizeof(void*)); ASSERT_TRUE(method_f_ != nullptr); diff --git a/runtime/gc/accounting/bitmap.cc b/runtime/gc/accounting/bitmap.cc index fdded028e1..380cb8efed 100644 --- a/runtime/gc/accounting/bitmap.cc +++ b/runtime/gc/accounting/bitmap.cc @@ -18,6 +18,7 @@ #include "base/bit_utils.h" #include "card_table.h" +#include "jit/jit_code_cache.h" #include "mem_map.h" namespace art { @@ -91,6 +92,7 @@ MemoryRangeBitmap<kAlignment>* MemoryRangeBitmap<kAlignment>::CreateFromMemMap( } template class MemoryRangeBitmap<CardTable::kCardSize>; +template class MemoryRangeBitmap<jit::kJitCodeAlignment>; } // namespace accounting } // namespace gc diff --git a/runtime/gc/accounting/space_bitmap-inl.h b/runtime/gc/accounting/space_bitmap-inl.h index 006d2c7d30..3be7181df2 100644 --- a/runtime/gc/accounting/space_bitmap-inl.h +++ b/runtime/gc/accounting/space_bitmap-inl.h @@ -46,7 +46,7 @@ inline bool SpaceBitmap<kAlignment>::AtomicTestAndSet(const mirror::Object* obj) DCHECK(Test(obj)); return true; } - } while (!atomic_entry->CompareExchangeWeakSequentiallyConsistent(old_word, old_word | mask)); + } while (!atomic_entry->CompareExchangeWeakRelaxed(old_word, old_word | mask)); DCHECK(Test(obj)); return false; } diff --git a/runtime/gc/collector/concurrent_copying-inl.h b/runtime/gc/collector/concurrent_copying-inl.h new file mode 100644 index 0000000000..26f5ad3df5 --- /dev/null +++ b/runtime/gc/collector/concurrent_copying-inl.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015 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_RUNTIME_GC_COLLECTOR_CONCURRENT_COPYING_INL_H_ +#define ART_RUNTIME_GC_COLLECTOR_CONCURRENT_COPYING_INL_H_ + +#include "concurrent_copying.h" + +#include "gc/accounting/space_bitmap-inl.h" +#include "gc/heap.h" +#include "gc/space/region_space.h" +#include "lock_word.h" + +namespace art { +namespace gc { +namespace collector { + +inline mirror::Object* ConcurrentCopying::Mark(mirror::Object* from_ref) { + if (from_ref == nullptr) { + return nullptr; + } + DCHECK(heap_->collector_type_ == kCollectorTypeCC); + if (UNLIKELY(kUseBakerReadBarrier && !is_active_)) { + // In the lock word forward address state, the read barrier bits + // in the lock word are part of the stored forwarding address and + // invalid. This is usually OK as the from-space copy of objects + // aren't accessed by mutators due to the to-space + // invariant. However, during the dex2oat image writing relocation + // and the zygote compaction, objects can be in the forward + // address state (to store the forward/relocation addresses) and + // they can still be accessed and the invalid read barrier bits + // are consulted. If they look like gray but aren't really, the + // read barriers slow path can trigger when it shouldn't. To guard + // against this, return here if the CC collector isn't running. + return from_ref; + } + DCHECK(region_space_ != nullptr) << "Read barrier slow path taken when CC isn't running?"; + space::RegionSpace::RegionType rtype = region_space_->GetRegionType(from_ref); + switch (rtype) { + case space::RegionSpace::RegionType::kRegionTypeToSpace: + // It's already marked. + return from_ref; + case space::RegionSpace::RegionType::kRegionTypeFromSpace: { + mirror::Object* to_ref = GetFwdPtr(from_ref); + if (kUseBakerReadBarrier) { + DCHECK_NE(to_ref, ReadBarrier::GrayPtr()) + << "from_ref=" << from_ref << " to_ref=" << to_ref; + } + if (to_ref == nullptr) { + // It isn't marked yet. Mark it by copying it to the to-space. + to_ref = Copy(from_ref); + } + DCHECK(region_space_->IsInToSpace(to_ref) || heap_->non_moving_space_->HasAddress(to_ref)) + << "from_ref=" << from_ref << " to_ref=" << to_ref; + return to_ref; + } + case space::RegionSpace::RegionType::kRegionTypeUnevacFromSpace: { + // This may or may not succeed, which is ok. + if (kUseBakerReadBarrier) { + from_ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), ReadBarrier::GrayPtr()); + } + mirror::Object* to_ref = from_ref; + if (region_space_bitmap_->AtomicTestAndSet(from_ref)) { + // Already marked. + } else { + // Newly marked. + if (kUseBakerReadBarrier) { + DCHECK_EQ(to_ref->GetReadBarrierPointer(), ReadBarrier::GrayPtr()); + } + PushOntoMarkStack(to_ref); + } + return to_ref; + } + case space::RegionSpace::RegionType::kRegionTypeNone: + return MarkNonMoving(from_ref); + default: + UNREACHABLE(); + } +} + +inline mirror::Object* ConcurrentCopying::GetFwdPtr(mirror::Object* from_ref) { + DCHECK(region_space_->IsInFromSpace(from_ref)); + LockWord lw = from_ref->GetLockWord(false); + if (lw.GetState() == LockWord::kForwardingAddress) { + mirror::Object* fwd_ptr = reinterpret_cast<mirror::Object*>(lw.ForwardingAddress()); + DCHECK(fwd_ptr != nullptr); + return fwd_ptr; + } else { + return nullptr; + } +} + +} // namespace collector +} // namespace gc +} // namespace art + +#endif // ART_RUNTIME_GC_COLLECTOR_CONCURRENT_COPYING_INL_H_ diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index d2d12af6b4..4a49712cbc 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -36,13 +36,16 @@ namespace art { namespace gc { namespace collector { +static constexpr size_t kDefaultGcMarkStackSize = 2 * MB; + ConcurrentCopying::ConcurrentCopying(Heap* heap, const std::string& name_prefix) : GarbageCollector(heap, name_prefix + (name_prefix.empty() ? "" : " ") + "concurrent copying + mark sweep"), region_space_(nullptr), gc_barrier_(new Barrier(0)), gc_mark_stack_(accounting::ObjectStack::Create("concurrent copying gc mark stack", - 2 * MB, 2 * MB)), + kDefaultGcMarkStackSize, + kDefaultGcMarkStackSize)), mark_stack_lock_("concurrent copying mark stack lock", kMarkSweepMarkStackLock), thread_running_gc_(nullptr), is_marking_(false), is_active_(false), is_asserting_to_space_invariant_(false), @@ -336,9 +339,7 @@ class EmptyCheckpoint : public Closure { << thread->GetState() << " thread " << thread << " self " << self; // If thread is a running mutator, then act on behalf of the garbage collector. // See the code in ThreadList::RunCheckpoint. - if (thread->GetState() == kRunnable) { - concurrent_copying_->GetBarrier().Pass(self); - } + concurrent_copying_->GetBarrier().Pass(self); } private: @@ -367,30 +368,15 @@ void ConcurrentCopying::MarkingPhase() { } } } - // TODO: Other garbage collectors uses Runtime::VisitConcurrentRoots(), refactor this part - // to also use the same function. - { - TimingLogger::ScopedTiming split2("VisitConstantRoots", GetTimings()); - Runtime::Current()->VisitConstantRoots(this); - } - { - TimingLogger::ScopedTiming split3("VisitInternTableRoots", GetTimings()); - Runtime::Current()->GetInternTable()->VisitRoots(this, kVisitRootFlagAllRoots); - } { - TimingLogger::ScopedTiming split4("VisitClassLinkerRoots", GetTimings()); - Runtime::Current()->GetClassLinker()->VisitRoots(this, kVisitRootFlagAllRoots); + TimingLogger::ScopedTiming split2("VisitConcurrentRoots", GetTimings()); + Runtime::Current()->VisitConcurrentRoots(this, kVisitRootFlagAllRoots); } { // TODO: don't visit the transaction roots if it's not active. TimingLogger::ScopedTiming split5("VisitNonThreadRoots", GetTimings()); Runtime::Current()->VisitNonThreadRoots(this); } - { - TimingLogger::ScopedTiming split6("Dbg::VisitRoots", GetTimings()); - Dbg::VisitRoots(this); - } - Runtime::Current()->GetHeap()->VisitAllocationRecords(this); // Immune spaces. for (auto& space : heap_->GetContinuousSpaces()) { @@ -511,9 +497,7 @@ class DisableMarkingCheckpoint : public Closure { thread->SetIsGcMarking(false); // If thread is a running mutator, then act on behalf of the garbage collector. // See the code in ThreadList::RunCheckpoint. - if (thread->GetState() == kRunnable) { - concurrent_copying_->GetBarrier().Pass(self); - } + concurrent_copying_->GetBarrier().Pass(self); } private: @@ -577,17 +561,31 @@ void ConcurrentCopying::IssueEmptyCheckpoint() { Locks::mutator_lock_->SharedLock(self); } +void ConcurrentCopying::ExpandGcMarkStack() { + DCHECK(gc_mark_stack_->IsFull()); + const size_t new_size = gc_mark_stack_->Capacity() * 2; + std::vector<StackReference<mirror::Object>> temp(gc_mark_stack_->Begin(), + gc_mark_stack_->End()); + gc_mark_stack_->Resize(new_size); + for (auto& ref : temp) { + gc_mark_stack_->PushBack(ref.AsMirrorPtr()); + } + DCHECK(!gc_mark_stack_->IsFull()); +} + void ConcurrentCopying::PushOntoMarkStack(mirror::Object* to_ref) { CHECK_EQ(is_mark_stack_push_disallowed_.LoadRelaxed(), 0) << " " << to_ref << " " << PrettyTypeOf(to_ref); Thread* self = Thread::Current(); // TODO: pass self as an argument from call sites? CHECK(thread_running_gc_ != nullptr); MarkStackMode mark_stack_mode = mark_stack_mode_.LoadRelaxed(); - if (mark_stack_mode == kMarkStackModeThreadLocal) { - if (self == thread_running_gc_) { + if (LIKELY(mark_stack_mode == kMarkStackModeThreadLocal)) { + if (LIKELY(self == thread_running_gc_)) { // If GC-running thread, use the GC mark stack instead of a thread-local mark stack. CHECK(self->GetThreadLocalMarkStack() == nullptr); - CHECK(!gc_mark_stack_->IsFull()); + if (UNLIKELY(gc_mark_stack_->IsFull())) { + ExpandGcMarkStack(); + } gc_mark_stack_->PushBack(to_ref); } else { // Otherwise, use a thread-local mark stack. @@ -621,7 +619,9 @@ void ConcurrentCopying::PushOntoMarkStack(mirror::Object* to_ref) { } else if (mark_stack_mode == kMarkStackModeShared) { // Access the shared GC mark stack with a lock. MutexLock mu(self, mark_stack_lock_); - CHECK(!gc_mark_stack_->IsFull()); + if (UNLIKELY(gc_mark_stack_->IsFull())) { + ExpandGcMarkStack(); + } gc_mark_stack_->PushBack(to_ref); } else { CHECK_EQ(static_cast<uint32_t>(mark_stack_mode), @@ -633,7 +633,9 @@ void ConcurrentCopying::PushOntoMarkStack(mirror::Object* to_ref) { << "Only GC-running thread should access the mark stack " << "in the GC exclusive mark stack mode"; // Access the GC mark stack without a lock. - CHECK(!gc_mark_stack_->IsFull()); + if (UNLIKELY(gc_mark_stack_->IsFull())) { + ExpandGcMarkStack(); + } gc_mark_stack_->PushBack(to_ref); } } @@ -646,18 +648,6 @@ accounting::ObjectStack* ConcurrentCopying::GetLiveStack() { return heap_->live_stack_.get(); } -inline mirror::Object* ConcurrentCopying::GetFwdPtr(mirror::Object* from_ref) { - DCHECK(region_space_->IsInFromSpace(from_ref)); - LockWord lw = from_ref->GetLockWord(false); - if (lw.GetState() == LockWord::kForwardingAddress) { - mirror::Object* fwd_ptr = reinterpret_cast<mirror::Object*>(lw.ForwardingAddress()); - CHECK(fwd_ptr != nullptr); - return fwd_ptr; - } else { - return nullptr; - } -} - // The following visitors are that used to verify that there's no // references to the from-space left after marking. class ConcurrentCopyingVerifyNoFromSpaceRefsVisitor : public SingleRootVisitor { @@ -916,9 +906,7 @@ class RevokeThreadLocalMarkStackCheckpoint : public Closure { } // If thread is a running mutator, then act on behalf of the garbage collector. // See the code in ThreadList::RunCheckpoint. - if (thread->GetState() == kRunnable) { - concurrent_copying_->GetBarrier().Pass(self); - } + concurrent_copying_->GetBarrier().Pass(self); } private: @@ -1065,7 +1053,7 @@ size_t ConcurrentCopying::ProcessThreadLocalMarkStacks(bool disable_weak_ref_acc return count; } -void ConcurrentCopying::ProcessMarkStackRef(mirror::Object* to_ref) { +inline void ConcurrentCopying::ProcessMarkStackRef(mirror::Object* to_ref) { DCHECK(!region_space_->IsInFromSpace(to_ref)); if (kUseBakerReadBarrier) { DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr()) @@ -1080,9 +1068,10 @@ void ConcurrentCopying::ProcessMarkStackRef(mirror::Object* to_ref) { << " " << to_ref << " " << to_ref->GetReadBarrierPointer() << " is_marked=" << IsMarked(to_ref); } - if (to_ref->GetClass<kVerifyNone, kWithoutReadBarrier>()->IsTypeOfReferenceClass() && - to_ref->AsReference()->GetReferent<kWithoutReadBarrier>() != nullptr && - !IsInToSpace(to_ref->AsReference()->GetReferent<kWithoutReadBarrier>())) { +#ifdef USE_BAKER_OR_BROOKS_READ_BARRIER + if (UNLIKELY((to_ref->GetClass<kVerifyNone, kWithoutReadBarrier>()->IsTypeOfReferenceClass() && + to_ref->AsReference()->GetReferent<kWithoutReadBarrier>() != nullptr && + !IsInToSpace(to_ref->AsReference()->GetReferent<kWithoutReadBarrier>())))) { // Leave this Reference gray in the queue so that GetReferent() will trigger a read barrier. We // will change it to black or white later in ReferenceQueue::DequeuePendingReference(). CHECK(to_ref->AsReference()->IsEnqueued()) << "Left unenqueued ref gray " << to_ref; @@ -1091,14 +1080,13 @@ void ConcurrentCopying::ProcessMarkStackRef(mirror::Object* to_ref) { // be concurrently marked after the Scan() call above has enqueued the Reference, in which case // the above IsInToSpace() evaluates to true and we change the color from gray to black or white // here in this else block. -#ifdef USE_BAKER_OR_BROOKS_READ_BARRIER if (kUseBakerReadBarrier) { if (region_space_->IsInToSpace(to_ref)) { // If to-space, change from gray to white. bool success = to_ref->AtomicSetReadBarrierPointer(ReadBarrier::GrayPtr(), ReadBarrier::WhitePtr()); CHECK(success) << "Must succeed as we won the race."; - CHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::WhitePtr()); + DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::WhitePtr()); } else { // If non-moving space/unevac from space, change from gray // to black. We can't change gray to white because it's not @@ -1110,13 +1098,13 @@ void ConcurrentCopying::ProcessMarkStackRef(mirror::Object* to_ref) { bool success = to_ref->AtomicSetReadBarrierPointer(ReadBarrier::GrayPtr(), ReadBarrier::BlackPtr()); CHECK(success) << "Must succeed as we won the race."; - CHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::BlackPtr()); + DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::BlackPtr()); } } + } #else - DCHECK(!kUseBakerReadBarrier); + DCHECK(!kUseBakerReadBarrier); #endif - } if (ReadBarrier::kEnableToSpaceInvariantChecks || kIsDebugBuild) { ConcurrentCopyingAssertToSpaceInvariantObjectVisitor visitor(this); visitor(to_ref); @@ -1607,6 +1595,7 @@ class ConcurrentCopyingRefFieldsVisitor { } void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const + ALWAYS_INLINE SHARED_REQUIRES(Locks::mutator_lock_) { if (!root->IsNull()) { VisitRoot(root); @@ -1614,6 +1603,7 @@ class ConcurrentCopyingRefFieldsVisitor { } void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const + ALWAYS_INLINE SHARED_REQUIRES(Locks::mutator_lock_) { collector_->MarkRoot(root); } @@ -1623,7 +1613,7 @@ class ConcurrentCopyingRefFieldsVisitor { }; // Scan ref fields of an object. -void ConcurrentCopying::Scan(mirror::Object* to_ref) { +inline void ConcurrentCopying::Scan(mirror::Object* to_ref) { DCHECK(!region_space_->IsInFromSpace(to_ref)); ConcurrentCopyingRefFieldsVisitor visitor(this); to_ref->VisitReferences(visitor, visitor); @@ -1633,9 +1623,6 @@ void ConcurrentCopying::Scan(mirror::Object* to_ref) { inline void ConcurrentCopying::Process(mirror::Object* obj, MemberOffset offset) { mirror::Object* ref = obj->GetFieldObject< mirror::Object, kVerifyNone, kWithoutReadBarrier, false>(offset); - if (ref == nullptr || region_space_->IsInToSpace(ref)) { - return; - } mirror::Object* to_ref = Mark(ref); if (to_ref == ref) { return; @@ -1649,19 +1636,16 @@ inline void ConcurrentCopying::Process(mirror::Object* obj, MemberOffset offset) // It was updated by the mutator. break; } - } while (!obj->CasFieldWeakSequentiallyConsistentObjectWithoutWriteBarrier< + } while (!obj->CasFieldWeakRelaxedObjectWithoutWriteBarrier< false, false, kVerifyNone>(offset, expected_ref, new_ref)); } // Process some roots. -void ConcurrentCopying::VisitRoots( +inline void ConcurrentCopying::VisitRoots( mirror::Object*** roots, size_t count, const RootInfo& info ATTRIBUTE_UNUSED) { for (size_t i = 0; i < count; ++i) { mirror::Object** root = roots[i]; mirror::Object* ref = *root; - if (ref == nullptr || region_space_->IsInToSpace(ref)) { - continue; - } mirror::Object* to_ref = Mark(ref); if (to_ref == ref) { continue; @@ -1674,16 +1658,13 @@ void ConcurrentCopying::VisitRoots( // It was updated by the mutator. break; } - } while (!addr->CompareExchangeWeakSequentiallyConsistent(expected_ref, new_ref)); + } while (!addr->CompareExchangeWeakRelaxed(expected_ref, new_ref)); } } -void ConcurrentCopying::MarkRoot(mirror::CompressedReference<mirror::Object>* root) { +inline void ConcurrentCopying::MarkRoot(mirror::CompressedReference<mirror::Object>* root) { DCHECK(!root->IsNull()); mirror::Object* const ref = root->AsMirrorPtr(); - if (region_space_->IsInToSpace(ref)) { - return; - } mirror::Object* to_ref = Mark(ref); if (to_ref != ref) { auto* addr = reinterpret_cast<Atomic<mirror::CompressedReference<mirror::Object>>*>(root); @@ -1695,11 +1676,11 @@ void ConcurrentCopying::MarkRoot(mirror::CompressedReference<mirror::Object>* ro // It was updated by the mutator. break; } - } while (!addr->CompareExchangeWeakSequentiallyConsistent(expected_ref, new_ref)); + } while (!addr->CompareExchangeWeakRelaxed(expected_ref, new_ref)); } } -void ConcurrentCopying::VisitRoots( +inline void ConcurrentCopying::VisitRoots( mirror::CompressedReference<mirror::Object>** roots, size_t count, const RootInfo& info ATTRIBUTE_UNUSED) { for (size_t i = 0; i < count; ++i) { @@ -1998,148 +1979,85 @@ bool ConcurrentCopying::IsOnAllocStack(mirror::Object* ref) { return alloc_stack->Contains(ref); } -mirror::Object* ConcurrentCopying::Mark(mirror::Object* from_ref) { - if (from_ref == nullptr) { - return nullptr; - } - DCHECK(from_ref != nullptr); - DCHECK(heap_->collector_type_ == kCollectorTypeCC); - if (kUseBakerReadBarrier && !is_active_) { - // In the lock word forward address state, the read barrier bits - // in the lock word are part of the stored forwarding address and - // invalid. This is usually OK as the from-space copy of objects - // aren't accessed by mutators due to the to-space - // invariant. However, during the dex2oat image writing relocation - // and the zygote compaction, objects can be in the forward - // address state (to store the forward/relocation addresses) and - // they can still be accessed and the invalid read barrier bits - // are consulted. If they look like gray but aren't really, the - // read barriers slow path can trigger when it shouldn't. To guard - // against this, return here if the CC collector isn't running. - return from_ref; - } - DCHECK(region_space_ != nullptr) << "Read barrier slow path taken when CC isn't running?"; - space::RegionSpace::RegionType rtype = region_space_->GetRegionType(from_ref); - if (rtype == space::RegionSpace::RegionType::kRegionTypeToSpace) { - // It's already marked. - return from_ref; - } - mirror::Object* to_ref; - if (rtype == space::RegionSpace::RegionType::kRegionTypeFromSpace) { - to_ref = GetFwdPtr(from_ref); - if (kUseBakerReadBarrier) { - DCHECK(to_ref != ReadBarrier::GrayPtr()) << "from_ref=" << from_ref << " to_ref=" << to_ref; - } - if (to_ref == nullptr) { - // It isn't marked yet. Mark it by copying it to the to-space. - to_ref = Copy(from_ref); +mirror::Object* ConcurrentCopying::MarkNonMoving(mirror::Object* ref) { + // ref is in a non-moving space (from_ref == to_ref). + DCHECK(!region_space_->HasAddress(ref)) << ref; + if (immune_region_.ContainsObject(ref)) { + accounting::ContinuousSpaceBitmap* cc_bitmap = + cc_heap_bitmap_->GetContinuousSpaceBitmap(ref); + DCHECK(cc_bitmap != nullptr) + << "An immune space object must have a bitmap"; + if (kIsDebugBuild) { + DCHECK(heap_mark_bitmap_->GetContinuousSpaceBitmap(ref)->Test(ref)) + << "Immune space object must be already marked"; } - DCHECK(region_space_->IsInToSpace(to_ref) || heap_->non_moving_space_->HasAddress(to_ref)) - << "from_ref=" << from_ref << " to_ref=" << to_ref; - } else if (rtype == space::RegionSpace::RegionType::kRegionTypeUnevacFromSpace) { // This may or may not succeed, which is ok. if (kUseBakerReadBarrier) { - from_ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), ReadBarrier::GrayPtr()); + ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), ReadBarrier::GrayPtr()); } - if (region_space_bitmap_->AtomicTestAndSet(from_ref)) { + if (cc_bitmap->AtomicTestAndSet(ref)) { // Already marked. - to_ref = from_ref; } else { // Newly marked. - to_ref = from_ref; if (kUseBakerReadBarrier) { - DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr()); + DCHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::GrayPtr()); } - PushOntoMarkStack(to_ref); + PushOntoMarkStack(ref); } } else { - // from_ref is in a non-moving space. - DCHECK(!region_space_->HasAddress(from_ref)) << from_ref; - if (immune_region_.ContainsObject(from_ref)) { - accounting::ContinuousSpaceBitmap* cc_bitmap = - cc_heap_bitmap_->GetContinuousSpaceBitmap(from_ref); - DCHECK(cc_bitmap != nullptr) - << "An immune space object must have a bitmap"; - if (kIsDebugBuild) { - DCHECK(heap_mark_bitmap_->GetContinuousSpaceBitmap(from_ref)->Test(from_ref)) - << "Immune space object must be already marked"; - } - // This may or may not succeed, which is ok. + // Use the mark bitmap. + accounting::ContinuousSpaceBitmap* mark_bitmap = + heap_mark_bitmap_->GetContinuousSpaceBitmap(ref); + accounting::LargeObjectBitmap* los_bitmap = + heap_mark_bitmap_->GetLargeObjectBitmap(ref); + CHECK(los_bitmap != nullptr) << "LOS bitmap covers the entire address range"; + bool is_los = mark_bitmap == nullptr; + if (!is_los && mark_bitmap->Test(ref)) { + // Already marked. if (kUseBakerReadBarrier) { - from_ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), ReadBarrier::GrayPtr()); + DCHECK(ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr() || + ref->GetReadBarrierPointer() == ReadBarrier::BlackPtr()); } - if (cc_bitmap->AtomicTestAndSet(from_ref)) { - // Already marked. - to_ref = from_ref; - } else { - // Newly marked. - to_ref = from_ref; - if (kUseBakerReadBarrier) { - DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr()); - } - PushOntoMarkStack(to_ref); + } else if (is_los && los_bitmap->Test(ref)) { + // Already marked in LOS. + if (kUseBakerReadBarrier) { + DCHECK(ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr() || + ref->GetReadBarrierPointer() == ReadBarrier::BlackPtr()); } } else { - // Use the mark bitmap. - accounting::ContinuousSpaceBitmap* mark_bitmap = - heap_mark_bitmap_->GetContinuousSpaceBitmap(from_ref); - accounting::LargeObjectBitmap* los_bitmap = - heap_mark_bitmap_->GetLargeObjectBitmap(from_ref); - CHECK(los_bitmap != nullptr) << "LOS bitmap covers the entire address range"; - bool is_los = mark_bitmap == nullptr; - if (!is_los && mark_bitmap->Test(from_ref)) { - // Already marked. - to_ref = from_ref; - if (kUseBakerReadBarrier) { - DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr() || - to_ref->GetReadBarrierPointer() == ReadBarrier::BlackPtr()); + // Not marked. + if (IsOnAllocStack(ref)) { + // If it's on the allocation stack, it's considered marked. Keep it white. + // Objects on the allocation stack need not be marked. + if (!is_los) { + DCHECK(!mark_bitmap->Test(ref)); + } else { + DCHECK(!los_bitmap->Test(ref)); } - } else if (is_los && los_bitmap->Test(from_ref)) { - // Already marked in LOS. - to_ref = from_ref; if (kUseBakerReadBarrier) { - DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr() || - to_ref->GetReadBarrierPointer() == ReadBarrier::BlackPtr()); + DCHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::WhitePtr()); } } else { - // Not marked. - if (IsOnAllocStack(from_ref)) { - // If it's on the allocation stack, it's considered marked. Keep it white. - to_ref = from_ref; - // Objects on the allocation stack need not be marked. - if (!is_los) { - DCHECK(!mark_bitmap->Test(to_ref)); - } else { - DCHECK(!los_bitmap->Test(to_ref)); - } - if (kUseBakerReadBarrier) { - DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::WhitePtr()); - } + // Not marked or on the allocation stack. Try to mark it. + // This may or may not succeed, which is ok. + if (kUseBakerReadBarrier) { + ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), ReadBarrier::GrayPtr()); + } + if (!is_los && mark_bitmap->AtomicTestAndSet(ref)) { + // Already marked. + } else if (is_los && los_bitmap->AtomicTestAndSet(ref)) { + // Already marked in LOS. } else { - // Not marked or on the allocation stack. Try to mark it. - // This may or may not succeed, which is ok. + // Newly marked. if (kUseBakerReadBarrier) { - from_ref->AtomicSetReadBarrierPointer(ReadBarrier::WhitePtr(), ReadBarrier::GrayPtr()); - } - if (!is_los && mark_bitmap->AtomicTestAndSet(from_ref)) { - // Already marked. - to_ref = from_ref; - } else if (is_los && los_bitmap->AtomicTestAndSet(from_ref)) { - // Already marked in LOS. - to_ref = from_ref; - } else { - // Newly marked. - to_ref = from_ref; - if (kUseBakerReadBarrier) { - DCHECK(to_ref->GetReadBarrierPointer() == ReadBarrier::GrayPtr()); - } - PushOntoMarkStack(to_ref); + DCHECK_EQ(ref->GetReadBarrierPointer(), ReadBarrier::GrayPtr()); } + PushOntoMarkStack(ref); } } } } - return to_ref; + return ref; } void ConcurrentCopying::FinishPhase() { diff --git a/runtime/gc/collector/concurrent_copying.h b/runtime/gc/collector/concurrent_copying.h index 8efad731b8..27726e23c1 100644 --- a/runtime/gc/collector/concurrent_copying.h +++ b/runtime/gc/collector/concurrent_copying.h @@ -93,7 +93,7 @@ class ConcurrentCopying : public GarbageCollector { DCHECK(ref != nullptr); return IsMarked(ref) == ref; } - mirror::Object* Mark(mirror::Object* from_ref) SHARED_REQUIRES(Locks::mutator_lock_) + ALWAYS_INLINE mirror::Object* Mark(mirror::Object* from_ref) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_); bool IsMarking() const { return is_marking_; @@ -182,6 +182,9 @@ class ConcurrentCopying : public GarbageCollector { void ReenableWeakRefAccess(Thread* self) SHARED_REQUIRES(Locks::mutator_lock_); void DisableMarking() SHARED_REQUIRES(Locks::mutator_lock_); void IssueDisableMarkingCheckpoint() SHARED_REQUIRES(Locks::mutator_lock_); + void ExpandGcMarkStack() SHARED_REQUIRES(Locks::mutator_lock_); + mirror::Object* MarkNonMoving(mirror::Object* from_ref) SHARED_REQUIRES(Locks::mutator_lock_) + REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_); space::RegionSpace* region_space_; // The underlying region space. std::unique_ptr<Barrier> gc_barrier_; diff --git a/runtime/gc/collector/mark_sweep.cc b/runtime/gc/collector/mark_sweep.cc index 77a288ba68..db516a0a87 100644 --- a/runtime/gc/collector/mark_sweep.cc +++ b/runtime/gc/collector/mark_sweep.cc @@ -1146,9 +1146,7 @@ class CheckpointMarkThreadRoots : public Closure, public RootVisitor { } // If thread is a running mutator, then act on behalf of the garbage collector. // See the code in ThreadList::RunCheckpoint. - if (thread->GetState() == kRunnable) { - mark_sweep_->GetBarrier().Pass(self); - } + mark_sweep_->GetBarrier().Pass(self); } private: diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 657fcb5f08..ab931429d0 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -26,6 +26,7 @@ #include "art_field-inl.h" #include "base/allocator.h" +#include "base/arena_allocator.h" #include "base/dumpable.h" #include "base/histogram-inl.h" #include "base/stl_util.h" @@ -1258,11 +1259,11 @@ void Heap::DoPendingCollectorTransition() { } void Heap::Trim(Thread* self) { + Runtime* const runtime = Runtime::Current(); if (!CareAboutPauseTimes()) { ATRACE_BEGIN("Deflating monitors"); // Deflate the monitors, this can cause a pause but shouldn't matter since we don't care // about pauses. - Runtime* runtime = Runtime::Current(); { ScopedSuspendAll ssa(__FUNCTION__); uint64_t start_time = NanoTime(); @@ -1274,6 +1275,10 @@ void Heap::Trim(Thread* self) { } TrimIndirectReferenceTables(self); TrimSpaces(self); + // Trim arenas that may have been used by JIT or verifier. + ATRACE_BEGIN("Trimming arena maps"); + runtime->GetArenaPool()->TrimMaps(); + ATRACE_END(); } class TrimIndirectReferenceTableClosure : public Closure { @@ -1286,9 +1291,7 @@ class TrimIndirectReferenceTableClosure : public Closure { ATRACE_END(); // If thread is a running mutator, then act on behalf of the trim thread. // See the code in ThreadList::RunCheckpoint. - if (thread->GetState() == kRunnable) { - barrier_->Pass(Thread::Current()); - } + barrier_->Pass(Thread::Current()); } private: diff --git a/runtime/image.cc b/runtime/image.cc index 192371fe75..1bc19ff656 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -150,10 +150,10 @@ std::ostream& operator<<(std::ostream& os, const ImageSection& section) { void ImageSection::VisitPackedArtFields(ArtFieldVisitor* visitor, uint8_t* base) const { for (size_t pos = 0; pos < Size(); ) { auto* array = reinterpret_cast<LengthPrefixedArray<ArtField>*>(base + Offset() + pos); - for (size_t i = 0; i < array->Length(); ++i) { + for (size_t i = 0; i < array->size(); ++i) { visitor->Visit(&array->At(i, sizeof(ArtField))); } - pos += array->ComputeSize(array->Length()); + pos += array->ComputeSize(array->size()); } } @@ -164,10 +164,10 @@ void ImageSection::VisitPackedArtMethods(ArtMethodVisitor* visitor, const size_t method_size = ArtMethod::Size(pointer_size); for (size_t pos = 0; pos < Size(); ) { auto* array = reinterpret_cast<LengthPrefixedArray<ArtMethod>*>(base + Offset() + pos); - for (size_t i = 0; i < array->Length(); ++i) { + for (size_t i = 0; i < array->size(); ++i) { visitor->Visit(&array->At(i, method_size, method_alignment)); } - pos += array->ComputeSize(array->Length(), method_size, method_alignment); + pos += array->ComputeSize(array->size(), method_size, method_alignment); } } diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index ed64d7efbe..4db37e600a 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -97,16 +97,6 @@ void Instrumentation::InstallStubsForClass(mirror::Class* klass) { static void UpdateEntrypoints(ArtMethod* method, const void* quick_code) SHARED_REQUIRES(Locks::mutator_lock_) { - Runtime* const runtime = Runtime::Current(); - jit::Jit* jit = runtime->GetJit(); - if (jit != nullptr) { - const void* old_code_ptr = method->GetEntryPointFromQuickCompiledCode(); - jit::JitCodeCache* code_cache = jit->GetCodeCache(); - if (code_cache->ContainsCodePtr(old_code_ptr)) { - // Save the old compiled code since we need it to implement ClassLinker::GetQuickOatCodeFor. - code_cache->SaveCompiledCode(method, old_code_ptr); - } - } method->SetEntryPointFromQuickCompiledCode(quick_code); } diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index 0607493420..5afd28e7b5 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -49,7 +49,7 @@ JitOptions* JitOptions::CreateFromRuntimeArguments(const RuntimeArgumentMap& opt void Jit::DumpInfo(std::ostream& os) { os << "Code cache size=" << PrettySize(code_cache_->CodeCacheSize()) << " data cache size=" << PrettySize(code_cache_->DataCacheSize()) - << " num methods=" << code_cache_->NumMethods() + << " number of compiled code=" << code_cache_->NumberOfCompiledCode() << "\n"; cumulative_timings_.Dump(os); } diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index e73ba82278..1f89f9b1b7 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -43,7 +43,7 @@ class JitOptions; class Jit { public: static constexpr bool kStressMode = kIsDebugBuild; - static constexpr size_t kDefaultCompileThreshold = kStressMode ? 2 : 1000; + static constexpr size_t kDefaultCompileThreshold = kStressMode ? 2 : 500; static constexpr size_t kDefaultWarmupThreshold = kDefaultCompileThreshold / 2; virtual ~Jit(); diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 4187358bc0..ce972ef976 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -19,8 +19,14 @@ #include <sstream> #include "art_method-inl.h" +#include "entrypoints/runtime_asm_entrypoints.h" +#include "gc/accounting/bitmap-inl.h" +#include "jit/profiling_info.h" +#include "linear_alloc.h" #include "mem_map.h" #include "oat_file-inl.h" +#include "scoped_thread_state_change.h" +#include "thread_list.h" namespace art { namespace jit { @@ -52,9 +58,9 @@ JitCodeCache* JitCodeCache::Create(size_t capacity, std::string* error_msg) { return nullptr; } - // Data cache is 1 / 4 of the map. + // Data cache is 1 / 2 of the map. // TODO: Make this variable? - size_t data_size = RoundUp(data_map->Size() / 4, kPageSize); + size_t data_size = RoundUp(data_map->Size() / 2, kPageSize); size_t code_size = data_map->Size() - data_size; uint8_t* divider = data_map->Begin() + data_size; @@ -74,14 +80,10 @@ JitCodeCache* JitCodeCache::Create(size_t capacity, std::string* error_msg) { JitCodeCache::JitCodeCache(MemMap* code_map, MemMap* data_map) : lock_("Jit code cache", kJitCodeCacheLock), + lock_cond_("Jit code cache variable", lock_), + collection_in_progress_(false), code_map_(code_map), - data_map_(data_map), - num_methods_(0) { - - VLOG(jit) << "Created jit code cache: data size=" - << PrettySize(data_map_->Size()) - << ", code size=" - << PrettySize(code_map_->Size()); + data_map_(data_map) { code_mspace_ = create_mspace_with_base(code_map_->Begin(), code_map_->Size(), false /*locked*/); data_mspace_ = create_mspace_with_base(data_map_->Begin(), data_map_->Size(), false /*locked*/); @@ -96,13 +98,22 @@ JitCodeCache::JitCodeCache(MemMap* code_map, MemMap* data_map) CHECKED_MPROTECT(code_map_->Begin(), code_map_->Size(), kProtCode); CHECKED_MPROTECT(data_map_->Begin(), data_map_->Size(), kProtData); -} -bool JitCodeCache::ContainsMethod(ArtMethod* method) const { - return ContainsCodePtr(method->GetEntryPointFromQuickCompiledCode()); + live_bitmap_.reset(CodeCacheBitmap::Create("code-cache-bitmap", + reinterpret_cast<uintptr_t>(code_map_->Begin()), + reinterpret_cast<uintptr_t>(code_map_->End()))); + + if (live_bitmap_.get() == nullptr) { + PLOG(FATAL) << "creating bitmaps for the JIT code cache failed"; + } + + VLOG(jit) << "Created jit code cache: data size=" + << PrettySize(data_map_->Size()) + << ", code size=" + << PrettySize(code_map_->Size()); } -bool JitCodeCache::ContainsCodePtr(const void* ptr) const { +bool JitCodeCache::ContainsPc(const void* ptr) const { return code_map_->Begin() <= ptr && ptr < code_map_->End(); } @@ -121,6 +132,7 @@ class ScopedCodeCacheWrite { }; uint8_t* JitCodeCache::CommitCode(Thread* self, + ArtMethod* method, const uint8_t* mapping_table, const uint8_t* vmap_table, const uint8_t* gc_map, @@ -129,6 +141,106 @@ uint8_t* JitCodeCache::CommitCode(Thread* self, size_t fp_spill_mask, const uint8_t* code, size_t code_size) { + uint8_t* result = CommitCodeInternal(self, + method, + mapping_table, + vmap_table, + gc_map, + frame_size_in_bytes, + core_spill_mask, + fp_spill_mask, + code, + code_size); + if (result == nullptr) { + // Retry. + GarbageCollectCache(self); + result = CommitCodeInternal(self, + method, + mapping_table, + vmap_table, + gc_map, + frame_size_in_bytes, + core_spill_mask, + fp_spill_mask, + code, + code_size); + } + return result; +} + +bool JitCodeCache::WaitForPotentialCollectionToComplete(Thread* self) { + bool in_collection = false; + while (collection_in_progress_) { + in_collection = true; + lock_cond_.Wait(self); + } + return in_collection; +} + +static uintptr_t FromCodeToAllocation(const void* code) { + size_t alignment = GetInstructionSetAlignment(kRuntimeISA); + return reinterpret_cast<uintptr_t>(code) - RoundUp(sizeof(OatQuickMethodHeader), alignment); +} + +void JitCodeCache::FreeCode(const void* code_ptr, ArtMethod* method ATTRIBUTE_UNUSED) { + uintptr_t allocation = FromCodeToAllocation(code_ptr); + const OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr); + const uint8_t* data = method_header->GetNativeGcMap(); + if (data != nullptr) { + mspace_free(data_mspace_, const_cast<uint8_t*>(data)); + } + data = method_header->GetMappingTable(); + if (data != nullptr) { + mspace_free(data_mspace_, const_cast<uint8_t*>(data)); + } + // Use the offset directly to prevent sanity check that the method is + // compiled with optimizing. + // TODO(ngeoffray): Clean up. + if (method_header->vmap_table_offset_ != 0) { + data = method_header->code_ - method_header->vmap_table_offset_; + mspace_free(data_mspace_, const_cast<uint8_t*>(data)); + } + mspace_free(code_mspace_, reinterpret_cast<uint8_t*>(allocation)); +} + +void JitCodeCache::RemoveMethodsIn(Thread* self, const LinearAlloc& alloc) { + MutexLock mu(self, lock_); + // We do not check if a code cache GC is in progress, as this method comes + // with the classlinker_classes_lock_ held, and suspending ourselves could + // lead to a deadlock. + { + ScopedCodeCacheWrite scc(code_map_.get()); + for (auto it = method_code_map_.begin(); it != method_code_map_.end();) { + if (alloc.ContainsUnsafe(it->second)) { + FreeCode(it->first, it->second); + it = method_code_map_.erase(it); + } else { + ++it; + } + } + } + for (auto it = profiling_infos_.begin(); it != profiling_infos_.end();) { + ProfilingInfo* info = *it; + if (alloc.ContainsUnsafe(info->GetMethod())) { + info->GetMethod()->SetProfilingInfo(nullptr); + mspace_free(data_mspace_, reinterpret_cast<uint8_t*>(info)); + it = profiling_infos_.erase(it); + } else { + ++it; + } + } +} + +uint8_t* JitCodeCache::CommitCodeInternal(Thread* self, + ArtMethod* method, + const uint8_t* mapping_table, + const uint8_t* vmap_table, + const uint8_t* gc_map, + size_t frame_size_in_bytes, + size_t core_spill_mask, + size_t fp_spill_mask, + const uint8_t* code, + size_t code_size) { size_t alignment = GetInstructionSetAlignment(kRuntimeISA); // Ensure the header ends up at expected instruction alignment. size_t header_size = RoundUp(sizeof(OatQuickMethodHeader), alignment); @@ -137,7 +249,9 @@ uint8_t* JitCodeCache::CommitCode(Thread* self, OatQuickMethodHeader* method_header = nullptr; uint8_t* code_ptr = nullptr; + ScopedThreadSuspension sts(self, kSuspended); MutexLock mu(self, lock_); + WaitForPotentialCollectionToComplete(self); { ScopedCodeCacheWrite scc(code_map_.get()); uint8_t* result = reinterpret_cast<uint8_t*>( @@ -149,7 +263,7 @@ uint8_t* JitCodeCache::CommitCode(Thread* self, DCHECK_ALIGNED_PARAM(reinterpret_cast<uintptr_t>(code_ptr), alignment); std::copy(code, code + code_size, code_ptr); - method_header = reinterpret_cast<OatQuickMethodHeader*>(code_ptr) - 1; + method_header = OatQuickMethodHeader::FromCodePointer(code_ptr); new (method_header) OatQuickMethodHeader( (mapping_table == nullptr) ? 0 : code_ptr - mapping_table, (vmap_table == nullptr) ? 0 : code_ptr - vmap_table, @@ -162,8 +276,12 @@ uint8_t* JitCodeCache::CommitCode(Thread* self, __builtin___clear_cache(reinterpret_cast<char*>(code_ptr), reinterpret_cast<char*>(code_ptr + code_size)); - - ++num_methods_; // TODO: This is hacky but works since each method has exactly one code region. + method_code_map_.Put(code_ptr, method); + // We have checked there was no collection in progress earlier. If we + // were, setting the entry point of a method would be unsafe, as the collection + // could delete it. + DCHECK(!collection_in_progress_); + method->SetEntryPointFromQuickCompiledCode(method_header->GetEntryPoint()); return reinterpret_cast<uint8_t*>(method_header); } @@ -181,10 +299,32 @@ size_t JitCodeCache::DataCacheSize() { return bytes_allocated; } +size_t JitCodeCache::NumberOfCompiledCode() { + MutexLock mu(Thread::Current(), lock_); + return method_code_map_.size(); +} + uint8_t* JitCodeCache::ReserveData(Thread* self, size_t size) { size = RoundUp(size, sizeof(void*)); - MutexLock mu(self, lock_); - return reinterpret_cast<uint8_t*>(mspace_malloc(data_mspace_, size)); + uint8_t* result = nullptr; + + { + ScopedThreadSuspension sts(self, kSuspended); + MutexLock mu(self, lock_); + WaitForPotentialCollectionToComplete(self); + result = reinterpret_cast<uint8_t*>(mspace_malloc(data_mspace_, size)); + } + + if (result == nullptr) { + // Retry. + GarbageCollectCache(self); + ScopedThreadSuspension sts(self, kSuspended); + MutexLock mu(self, lock_); + WaitForPotentialCollectionToComplete(self); + result = reinterpret_cast<uint8_t*>(mspace_malloc(data_mspace_, size)); + } + + return result; } uint8_t* JitCodeCache::AddDataArray(Thread* self, const uint8_t* begin, const uint8_t* end) { @@ -196,29 +336,201 @@ uint8_t* JitCodeCache::AddDataArray(Thread* self, const uint8_t* begin, const ui return result; } -const void* JitCodeCache::GetCodeFor(ArtMethod* method) { - const void* code = method->GetEntryPointFromQuickCompiledCode(); - if (ContainsCodePtr(code)) { - return code; +class MarkCodeVisitor FINAL : public StackVisitor { + public: + MarkCodeVisitor(Thread* thread_in, JitCodeCache* code_cache_in) + : StackVisitor(thread_in, nullptr, StackVisitor::StackWalkKind::kSkipInlinedFrames), + code_cache_(code_cache_in), + bitmap_(code_cache_->GetLiveBitmap()) {} + + bool VisitFrame() OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { + const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader(); + if (method_header == nullptr) { + return true; + } + const void* code = method_header->GetCode(); + if (code_cache_->ContainsPc(code)) { + // Use the atomic set version, as multiple threads are executing this code. + bitmap_->AtomicTestAndSet(FromCodeToAllocation(code)); + } + return true; } - MutexLock mu(Thread::Current(), lock_); - auto it = method_code_map_.find(method); - if (it != method_code_map_.end()) { - return it->second; + + private: + JitCodeCache* const code_cache_; + CodeCacheBitmap* const bitmap_; +}; + +class MarkCodeClosure FINAL : public Closure { + public: + MarkCodeClosure(JitCodeCache* code_cache, Barrier* barrier) + : code_cache_(code_cache), barrier_(barrier) {} + + void Run(Thread* thread) OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { + DCHECK(thread == Thread::Current() || thread->IsSuspended()); + MarkCodeVisitor visitor(thread, code_cache_); + visitor.WalkStack(); + barrier_->Pass(Thread::Current()); + } + + private: + JitCodeCache* const code_cache_; + Barrier* const barrier_; +}; + +void JitCodeCache::GarbageCollectCache(Thread* self) { + if (!kIsDebugBuild || VLOG_IS_ON(jit)) { + LOG(INFO) << "Clearing code cache, code=" + << PrettySize(CodeCacheSize()) + << ", data=" << PrettySize(DataCacheSize()); + } + + size_t map_size = 0; + ScopedThreadSuspension sts(self, kSuspended); + + // Walk over all compiled methods and set the entry points of these + // methods to interpreter. + { + MutexLock mu(self, lock_); + if (WaitForPotentialCollectionToComplete(self)) { + return; + } + collection_in_progress_ = true; + map_size = method_code_map_.size(); + for (auto& it : method_code_map_) { + it.second->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge()); + } + for (ProfilingInfo* info : profiling_infos_) { + info->GetMethod()->SetProfilingInfo(nullptr); + } + } + + // Run a checkpoint on all threads to mark the JIT compiled code they are running. + { + Barrier barrier(0); + size_t threads_running_checkpoint = 0; + { + // Walking the stack requires the mutator lock. + // We only take the lock when running the checkpoint and not waiting so that + // when we go back to suspended, we can execute checkpoints that were requested + // concurrently, and then move to waiting for our own checkpoint to finish. + ScopedObjectAccess soa(self); + MarkCodeClosure closure(this, &barrier); + threads_running_checkpoint = + Runtime::Current()->GetThreadList()->RunCheckpoint(&closure); + } + if (threads_running_checkpoint != 0) { + barrier.Increment(self, threads_running_checkpoint); + } + } + + { + MutexLock mu(self, lock_); + DCHECK_EQ(map_size, method_code_map_.size()); + // Free unused compiled code, and restore the entry point of used compiled code. + { + ScopedCodeCacheWrite scc(code_map_.get()); + for (auto it = method_code_map_.begin(); it != method_code_map_.end();) { + const void* code_ptr = it->first; + ArtMethod* method = it->second; + uintptr_t allocation = FromCodeToAllocation(code_ptr); + const OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr); + if (GetLiveBitmap()->Test(allocation)) { + method->SetEntryPointFromQuickCompiledCode(method_header->GetEntryPoint()); + ++it; + } else { + method->ClearCounter(); + DCHECK_NE(method->GetEntryPointFromQuickCompiledCode(), method_header->GetEntryPoint()); + FreeCode(code_ptr, method); + it = method_code_map_.erase(it); + } + } + } + GetLiveBitmap()->Bitmap::Clear(); + + // Free all profiling info. + for (ProfilingInfo* info : profiling_infos_) { + DCHECK(info->GetMethod()->GetProfilingInfo(sizeof(void*)) == nullptr); + mspace_free(data_mspace_, reinterpret_cast<uint8_t*>(info)); + } + profiling_infos_.clear(); + + collection_in_progress_ = false; + lock_cond_.Broadcast(self); + } + + if (!kIsDebugBuild || VLOG_IS_ON(jit)) { + LOG(INFO) << "After clearing code cache, code=" + << PrettySize(CodeCacheSize()) + << ", data=" << PrettySize(DataCacheSize()); } - return nullptr; } -void JitCodeCache::SaveCompiledCode(ArtMethod* method, const void* old_code_ptr) { - DCHECK_EQ(method->GetEntryPointFromQuickCompiledCode(), old_code_ptr); - DCHECK(ContainsCodePtr(old_code_ptr)) << PrettyMethod(method) << " old_code_ptr=" - << old_code_ptr; + +OatQuickMethodHeader* JitCodeCache::LookupMethodHeader(uintptr_t pc, ArtMethod* method) { + static_assert(kRuntimeISA != kThumb2, "kThumb2 cannot be a runtime ISA"); + if (kRuntimeISA == kArm) { + // On Thumb-2, the pc is offset by one. + --pc; + } + if (!ContainsPc(reinterpret_cast<const void*>(pc))) { + return nullptr; + } + MutexLock mu(Thread::Current(), lock_); - auto it = method_code_map_.find(method); - if (it != method_code_map_.end()) { - return; + if (method_code_map_.empty()) { + return nullptr; + } + auto it = method_code_map_.lower_bound(reinterpret_cast<const void*>(pc)); + --it; + + const void* code_ptr = it->first; + OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr); + if (!method_header->Contains(pc)) { + return nullptr; + } + DCHECK_EQ(it->second, method) + << PrettyMethod(method) << " " << PrettyMethod(it->second) << " " << std::hex << pc; + return method_header; +} + +ProfilingInfo* JitCodeCache::AddProfilingInfo(Thread* self, + ArtMethod* method, + const std::vector<uint32_t>& entries, + bool retry_allocation) { + ProfilingInfo* info = AddProfilingInfoInternal(self, method, entries); + + if (info == nullptr && retry_allocation) { + GarbageCollectCache(self); + info = AddProfilingInfoInternal(self, method, entries); + } + return info; +} + +ProfilingInfo* JitCodeCache::AddProfilingInfoInternal(Thread* self, + ArtMethod* method, + const std::vector<uint32_t>& entries) { + size_t profile_info_size = RoundUp( + sizeof(ProfilingInfo) + sizeof(ProfilingInfo::InlineCache) * entries.size(), + sizeof(void*)); + ScopedThreadSuspension sts(self, kSuspended); + MutexLock mu(self, lock_); + WaitForPotentialCollectionToComplete(self); + + // Check whether some other thread has concurrently created it. + ProfilingInfo* info = method->GetProfilingInfo(sizeof(void*)); + if (info != nullptr) { + return info; + } + + uint8_t* data = reinterpret_cast<uint8_t*>(mspace_malloc(data_mspace_, profile_info_size)); + if (data == nullptr) { + return nullptr; } - method_code_map_.Put(method, old_code_ptr); + info = new (data) ProfilingInfo(method, entries); + method->SetProfilingInfo(info); + profiling_infos_.push_back(info); + return info; } } // namespace jit diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h index fa90c1806f..e10f9629ae 100644 --- a/runtime/jit/jit_code_cache.h +++ b/runtime/jit/jit_code_cache.h @@ -22,6 +22,7 @@ #include "atomic.h" #include "base/macros.h" #include "base/mutex.h" +#include "gc/accounting/bitmap.h" #include "gc/allocator/dlmalloc.h" #include "gc_root.h" #include "jni.h" @@ -33,32 +34,41 @@ namespace art { class ArtMethod; -class CompiledMethod; -class CompilerCallbacks; +class LinearAlloc; +class ProfilingInfo; namespace jit { class JitInstrumentationCache; +// Alignment that will suit all architectures. +static constexpr int kJitCodeAlignment = 16; +using CodeCacheBitmap = gc::accounting::MemoryRangeBitmap<kJitCodeAlignment>; + class JitCodeCache { public: static constexpr size_t kMaxCapacity = 1 * GB; - static constexpr size_t kDefaultCapacity = 2 * MB; + // Put the default to a very low amount for debug builds to stress the code cache + // collection. + static constexpr size_t kDefaultCapacity = kIsDebugBuild ? 20 * KB : 2 * MB; // Create the code cache with a code + data capacity equal to "capacity", error message is passed // in the out arg error_msg. static JitCodeCache* Create(size_t capacity, std::string* error_msg); - size_t NumMethods() const { - return num_methods_; - } - + // Number of bytes allocated in the code cache. size_t CodeCacheSize() REQUIRES(!lock_); + // Number of bytes allocated in the data cache. size_t DataCacheSize() REQUIRES(!lock_); + // Number of compiled code in the code cache. Note that this is not the number + // of methods that got JIT compiled, as we might have collected some. + size_t NumberOfCompiledCode() REQUIRES(!lock_); + // Allocate and write code and its metadata to the code cache. uint8_t* CommitCode(Thread* self, + ArtMethod* method, const uint8_t* mapping_table, const uint8_t* vmap_table, const uint8_t* gc_map, @@ -67,51 +77,107 @@ class JitCodeCache { size_t fp_spill_mask, const uint8_t* code, size_t code_size) + SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!lock_); - // Return true if the code cache contains the code pointer which si the entrypoint of the method. - bool ContainsMethod(ArtMethod* method) const - SHARED_REQUIRES(Locks::mutator_lock_); - - // Return true if the code cache contains a code ptr. - bool ContainsCodePtr(const void* ptr) const; + // Return true if the code cache contains this pc. + bool ContainsPc(const void* pc) const; // Reserve a region of data of size at least "size". Returns null if there is no more room. - uint8_t* ReserveData(Thread* self, size_t size) REQUIRES(!lock_); + uint8_t* ReserveData(Thread* self, size_t size) + SHARED_REQUIRES(Locks::mutator_lock_) + REQUIRES(!lock_); // Add a data array of size (end - begin) with the associated contents, returns null if there // is no more room. uint8_t* AddDataArray(Thread* self, const uint8_t* begin, const uint8_t* end) + SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!lock_); - // Get code for a method, returns null if it is not in the jit cache. - const void* GetCodeFor(ArtMethod* method) - SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!lock_); + CodeCacheBitmap* GetLiveBitmap() const { + return live_bitmap_.get(); + } - // Save the compiled code for a method so that GetCodeFor(method) will return old_code_ptr if the - // entrypoint isn't within the cache. - void SaveCompiledCode(ArtMethod* method, const void* old_code_ptr) - SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!lock_); + // Perform a collection on the code cache. + void GarbageCollectCache(Thread* self) + REQUIRES(!lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + + // Given the 'pc', try to find the JIT compiled code associated with it. + // Return null if 'pc' is not in the code cache. 'method' is passed for + // sanity check. + OatQuickMethodHeader* LookupMethodHeader(uintptr_t pc, ArtMethod* method) + REQUIRES(!lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + + // Remove all methods in our cache that were allocated by 'alloc'. + void RemoveMethodsIn(Thread* self, const LinearAlloc& alloc) + REQUIRES(!lock_) + REQUIRES(Locks::classlinker_classes_lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + + // Create a 'ProfileInfo' for 'method'. If 'retry_allocation' is true, + // will collect and retry if the first allocation is unsuccessful. + ProfilingInfo* AddProfilingInfo(Thread* self, + ArtMethod* method, + const std::vector<uint32_t>& entries, + bool retry_allocation) + REQUIRES(!lock_) + SHARED_REQUIRES(Locks::mutator_lock_); private: - // Takes ownership of code_mem_map. + // Take ownership of code_mem_map. JitCodeCache(MemMap* code_map, MemMap* data_map); - // Lock which guards. + // Internal version of 'CommitCode' that will not retry if the + // allocation fails. Return null if the allocation fails. + uint8_t* CommitCodeInternal(Thread* self, + ArtMethod* method, + const uint8_t* mapping_table, + const uint8_t* vmap_table, + const uint8_t* gc_map, + size_t frame_size_in_bytes, + size_t core_spill_mask, + size_t fp_spill_mask, + const uint8_t* code, + size_t code_size) + REQUIRES(!lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + + ProfilingInfo* AddProfilingInfoInternal(Thread* self, + ArtMethod* method, + const std::vector<uint32_t>& entries) + REQUIRES(!lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + + // If a collection is in progress, wait for it to finish. Return + // whether the thread actually waited. + bool WaitForPotentialCollectionToComplete(Thread* self) + REQUIRES(lock_) REQUIRES(!Locks::mutator_lock_); + + // Free in the mspace allocations taken by 'method'. + void FreeCode(const void* code_ptr, ArtMethod* method) REQUIRES(lock_); + + // Lock for guarding allocations, collections, and the method_code_map_. Mutex lock_; + // Condition to wait on during collection. + ConditionVariable lock_cond_ GUARDED_BY(lock_); + // Whether there is a code cache collection in progress. + bool collection_in_progress_ GUARDED_BY(lock_); // Mem map which holds code. std::unique_ptr<MemMap> code_map_; // Mem map which holds data (stack maps and profiling info). std::unique_ptr<MemMap> data_map_; // The opaque mspace for allocating code. - void* code_mspace_; + void* code_mspace_ GUARDED_BY(lock_); // The opaque mspace for allocating data. - void* data_mspace_; - // Number of compiled methods. - size_t num_methods_; - // This map holds code for methods if they were deoptimized by the instrumentation stubs. This is - // required since we have to implement ClassLinker::GetQuickOatCodeFor for walking stacks. - SafeMap<ArtMethod*, const void*> method_code_map_ GUARDED_BY(lock_); + void* data_mspace_ GUARDED_BY(lock_); + // Bitmap for collecting code and data. + std::unique_ptr<CodeCacheBitmap> live_bitmap_; + // This map holds compiled code associated to the ArtMethod. + SafeMap<const void*, ArtMethod*> method_code_map_ GUARDED_BY(lock_); + // ProfilingInfo objects we have allocated. + std::vector<ProfilingInfo*> profiling_infos_ GUARDED_BY(lock_); DISALLOW_IMPLICIT_CONSTRUCTORS(JitCodeCache); }; diff --git a/runtime/jit/jit_instrumentation.cc b/runtime/jit/jit_instrumentation.cc index 9b9c5d2760..7931306ff6 100644 --- a/runtime/jit/jit_instrumentation.cc +++ b/runtime/jit/jit_instrumentation.cc @@ -26,7 +26,12 @@ namespace jit { class JitCompileTask FINAL : public Task { public: - explicit JitCompileTask(ArtMethod* method) : method_(method) { + enum TaskKind { + kAllocateProfile, + kCompile + }; + + JitCompileTask(ArtMethod* method, TaskKind kind) : method_(method), kind_(kind) { ScopedObjectAccess soa(Thread::Current()); // Add a global ref to the class to prevent class unloading until compilation is done. klass_ = soa.Vm()->AddGlobalRef(soa.Self(), method_->GetDeclaringClass()); @@ -40,9 +45,16 @@ class JitCompileTask FINAL : public Task { void Run(Thread* self) OVERRIDE { ScopedObjectAccess soa(self); - VLOG(jit) << "JitCompileTask compiling method " << PrettyMethod(method_); - if (!Runtime::Current()->GetJit()->CompileMethod(method_, self)) { - VLOG(jit) << "Failed to compile method " << PrettyMethod(method_); + if (kind_ == kCompile) { + VLOG(jit) << "JitCompileTask compiling method " << PrettyMethod(method_); + if (!Runtime::Current()->GetJit()->CompileMethod(method_, self)) { + VLOG(jit) << "Failed to compile method " << PrettyMethod(method_); + } + } else { + DCHECK(kind_ == kAllocateProfile); + if (ProfilingInfo::Create(self, method_, /* retry_allocation */ true)) { + VLOG(jit) << "Start profiling " << PrettyMethod(method_); + } } } @@ -52,6 +64,7 @@ class JitCompileTask FINAL : public Task { private: ArtMethod* const method_; + const TaskKind kind_; jobject klass_; DISALLOW_IMPLICIT_CONSTRUCTORS(JitCompileTask); @@ -73,11 +86,9 @@ void JitInstrumentationCache::DeleteThreadPool() { } void JitInstrumentationCache::AddSamples(Thread* self, ArtMethod* method, size_t) { - ScopedObjectAccessUnchecked soa(self); // Since we don't have on-stack replacement, some methods can remain in the interpreter longer // than we want resulting in samples even after the method is compiled. - if (method->IsClassInitializer() || method->IsNative() || - Runtime::Current()->GetJit()->GetCodeCache()->ContainsMethod(method)) { + if (method->IsClassInitializer() || method->IsNative()) { return; } if (thread_pool_.get() == nullptr) { @@ -86,14 +97,18 @@ void JitInstrumentationCache::AddSamples(Thread* self, ArtMethod* method, size_t } uint16_t sample_count = method->IncrementCounter(); if (sample_count == warm_method_threshold_) { - ProfilingInfo* info = method->CreateProfilingInfo(); - if (info != nullptr) { + if (ProfilingInfo::Create(self, method, /* retry_allocation */ false)) { VLOG(jit) << "Start profiling " << PrettyMethod(method); + } else { + // We failed allocating. Instead of doing the collection on the Java thread, we push + // an allocation to a compiler thread, that will do the collection. + thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kAllocateProfile)); + thread_pool_->StartWorkers(self); } } + if (sample_count == hot_method_threshold_) { - thread_pool_->AddTask(self, new JitCompileTask( - method->GetInterfaceMethodIfProxy(sizeof(void*)))); + thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompile)); thread_pool_->StartWorkers(self); } } @@ -108,14 +123,18 @@ void JitInstrumentationListener::InvokeVirtualOrInterface(Thread* thread, ArtMethod* caller, uint32_t dex_pc, ArtMethod* callee ATTRIBUTE_UNUSED) { + instrumentation_cache_->AddSamples(thread, caller, 1); + // We make sure we cannot be suspended, as the profiling info can be concurrently deleted. + thread->StartAssertNoThreadSuspension("Instrumenting invoke"); DCHECK(this_object != nullptr); ProfilingInfo* info = caller->GetProfilingInfo(sizeof(void*)); if (info != nullptr) { // Since the instrumentation is marked from the declaring class we need to mark the card so // that mod-union tables and card rescanning know about the update. Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(caller->GetDeclaringClass()); - info->AddInvokeInfo(thread, dex_pc, this_object->GetClass()); + info->AddInvokeInfo(dex_pc, this_object->GetClass()); } + thread->EndAssertNoThreadSuspension(nullptr); } void JitInstrumentationCache::WaitForCompilationToFinish(Thread* self) { diff --git a/runtime/jit/profiling_info.cc b/runtime/jit/profiling_info.cc index 0c039f2bbd..2e52b1b4dc 100644 --- a/runtime/jit/profiling_info.cc +++ b/runtime/jit/profiling_info.cc @@ -25,18 +25,13 @@ namespace art { -ProfilingInfo* ProfilingInfo::Create(ArtMethod* method) { +bool ProfilingInfo::Create(Thread* self, ArtMethod* method, bool retry_allocation) { // Walk over the dex instructions of the method and keep track of // instructions we are interested in profiling. - const uint16_t* code_ptr = nullptr; - const uint16_t* code_end = nullptr; - { - ScopedObjectAccess soa(Thread::Current()); - DCHECK(!method->IsNative()); - const DexFile::CodeItem& code_item = *method->GetCodeItem(); - code_ptr = code_item.insns_; - code_end = code_item.insns_ + code_item.insns_size_in_code_units_; - } + DCHECK(!method->IsNative()); + const DexFile::CodeItem& code_item = *method->GetCodeItem(); + const uint16_t* code_ptr = code_item.insns_; + const uint16_t* code_end = code_item.insns_ + code_item.insns_size_in_code_units_; uint32_t dex_pc = 0; std::vector<uint32_t> entries; @@ -62,23 +57,15 @@ ProfilingInfo* ProfilingInfo::Create(ArtMethod* method) { // If there is no instruction we are interested in, no need to create a `ProfilingInfo` // object, it will never be filled. if (entries.empty()) { - return nullptr; + return true; } // Allocate the `ProfilingInfo` object int the JIT's data space. jit::JitCodeCache* code_cache = Runtime::Current()->GetJit()->GetCodeCache(); - size_t profile_info_size = sizeof(ProfilingInfo) + sizeof(InlineCache) * entries.size(); - uint8_t* data = code_cache->ReserveData(Thread::Current(), profile_info_size); - - if (data == nullptr) { - VLOG(jit) << "Cannot allocate profiling info anymore"; - return nullptr; - } - - return new (data) ProfilingInfo(entries); + return code_cache->AddProfilingInfo(self, method, entries, retry_allocation) != nullptr; } -void ProfilingInfo::AddInvokeInfo(Thread* self, uint32_t dex_pc, mirror::Class* cls) { +void ProfilingInfo::AddInvokeInfo(uint32_t dex_pc, mirror::Class* cls) { InlineCache* cache = nullptr; // TODO: binary search if array is too long. for (size_t i = 0; i < number_of_inline_caches_; ++i) { @@ -89,9 +76,8 @@ void ProfilingInfo::AddInvokeInfo(Thread* self, uint32_t dex_pc, mirror::Class* } DCHECK(cache != nullptr); - ScopedObjectAccess soa(self); for (size_t i = 0; i < InlineCache::kIndividualCacheSize; ++i) { - mirror::Class* existing = cache->classes_[i].Read<kWithoutReadBarrier>(); + mirror::Class* existing = cache->classes_[i].Read(); if (existing == cls) { // Receiver type is already in the cache, nothing else to do. return; diff --git a/runtime/jit/profiling_info.h b/runtime/jit/profiling_info.h index 73ca41a9a1..b13a315d64 100644 --- a/runtime/jit/profiling_info.h +++ b/runtime/jit/profiling_info.h @@ -26,6 +26,10 @@ namespace art { class ArtMethod; +namespace jit { +class JitCodeCache; +} + namespace mirror { class Class; } @@ -36,10 +40,17 @@ class Class; */ class ProfilingInfo { public: - static ProfilingInfo* Create(ArtMethod* method); + // Create a ProfilingInfo for 'method'. Return whether it succeeded, or if it is + // not needed in case the method does not have virtual/interface invocations. + static bool Create(Thread* self, ArtMethod* method, bool retry_allocation) + SHARED_REQUIRES(Locks::mutator_lock_); // Add information from an executed INVOKE instruction to the profile. - void AddInvokeInfo(Thread* self, uint32_t dex_pc, mirror::Class* cls); + void AddInvokeInfo(uint32_t dex_pc, mirror::Class* cls) + // Method should not be interruptible, as it manipulates the ProfilingInfo + // which can be concurrently collected. + REQUIRES(Roles::uninterruptible_) + SHARED_REQUIRES(Locks::mutator_lock_); // NO_THREAD_SAFETY_ANALYSIS since we don't know what the callback requires. template<typename RootVisitorType> @@ -52,6 +63,10 @@ class ProfilingInfo { } } + ArtMethod* GetMethod() const { + return method_; + } + private: // Structure to store the classes seen at runtime for a specific instruction. // Once the classes_ array is full, we consider the INVOKE to be megamorphic. @@ -84,8 +99,9 @@ class ProfilingInfo { GcRoot<mirror::Class> classes_[kIndividualCacheSize]; }; - explicit ProfilingInfo(const std::vector<uint32_t>& entries) - : number_of_inline_caches_(entries.size()) { + ProfilingInfo(ArtMethod* method, const std::vector<uint32_t>& entries) + : number_of_inline_caches_(entries.size()), + method_(method) { memset(&cache_, 0, number_of_inline_caches_ * sizeof(InlineCache)); for (size_t i = 0; i < number_of_inline_caches_; ++i) { cache_[i].dex_pc = entries[i]; @@ -95,9 +111,14 @@ class ProfilingInfo { // Number of instructions we are profiling in the ArtMethod. const uint32_t number_of_inline_caches_; + // Method this profiling info is for. + ArtMethod* const method_; + // Dynamically allocated array of size `number_of_inline_caches_`. InlineCache cache_[0]; + friend class jit::JitCodeCache; + DISALLOW_COPY_AND_ASSIGN(ProfilingInfo); }; diff --git a/runtime/length_prefixed_array.h b/runtime/length_prefixed_array.h index 0ff6d7a9aa..e01b6ccd26 100644 --- a/runtime/length_prefixed_array.h +++ b/runtime/length_prefixed_array.h @@ -30,19 +30,34 @@ template<typename T> class LengthPrefixedArray { public: explicit LengthPrefixedArray(size_t length) - : length_(dchecked_integral_cast<uint32_t>(length)) {} + : size_(dchecked_integral_cast<uint32_t>(length)) {} T& At(size_t index, size_t element_size = sizeof(T), size_t alignment = alignof(T)) { - DCHECK_LT(index, length_); + DCHECK_LT(index, size_); return AtUnchecked(index, element_size, alignment); } - StrideIterator<T> Begin(size_t element_size = sizeof(T), size_t alignment = alignof(T)) { + const T& At(size_t index, size_t element_size = sizeof(T), size_t alignment = alignof(T)) const { + DCHECK_LT(index, size_); + return AtUnchecked(index, element_size, alignment); + } + + StrideIterator<T> begin(size_t element_size = sizeof(T), size_t alignment = alignof(T)) { return StrideIterator<T>(&AtUnchecked(0, element_size, alignment), element_size); } - StrideIterator<T> End(size_t element_size = sizeof(T), size_t alignment = alignof(T)) { - return StrideIterator<T>(&AtUnchecked(length_, element_size, alignment), element_size); + StrideIterator<const T> begin(size_t element_size = sizeof(T), + size_t alignment = alignof(T)) const { + return StrideIterator<const T>(&AtUnchecked(0, element_size, alignment), element_size); + } + + StrideIterator<T> end(size_t element_size = sizeof(T), size_t alignment = alignof(T)) { + return StrideIterator<T>(&AtUnchecked(size_, element_size, alignment), element_size); + } + + StrideIterator<const T> end(size_t element_size = sizeof(T), + size_t alignment = alignof(T)) const { + return StrideIterator<const T>(&AtUnchecked(size_, element_size, alignment), element_size); } static size_t OffsetOfElement(size_t index, @@ -60,13 +75,13 @@ class LengthPrefixedArray { return result; } - uint64_t Length() const { - return length_; + size_t size() const { + return size_; } // Update the length but does not reallocate storage. - void SetLength(size_t length) { - length_ = dchecked_integral_cast<uint32_t>(length); + void SetSize(size_t length) { + size_ = dchecked_integral_cast<uint32_t>(length); } private: @@ -75,7 +90,12 @@ class LengthPrefixedArray { reinterpret_cast<uintptr_t>(this) + OffsetOfElement(index, element_size, alignment)); } - uint32_t length_; + const T& AtUnchecked(size_t index, size_t element_size, size_t alignment) const { + return *reinterpret_cast<T*>( + reinterpret_cast<uintptr_t>(this) + OffsetOfElement(index, element_size, alignment)); + } + + uint32_t size_; uint8_t data[0]; }; @@ -84,7 +104,7 @@ template<typename T> IterationRange<StrideIterator<T>> MakeIterationRangeFromLengthPrefixedArray( LengthPrefixedArray<T>* arr, size_t element_size = sizeof(T), size_t alignment = alignof(T)) { return arr != nullptr ? - MakeIterationRange(arr->Begin(element_size, alignment), arr->End(element_size, alignment)) : + MakeIterationRange(arr->begin(element_size, alignment), arr->end(element_size, alignment)) : MakeEmptyIterationRange(StrideIterator<T>(nullptr, 0)); } diff --git a/runtime/linear_alloc.cc b/runtime/linear_alloc.cc index 43e81d9d94..f91b0ed9ea 100644 --- a/runtime/linear_alloc.cc +++ b/runtime/linear_alloc.cc @@ -48,4 +48,8 @@ bool LinearAlloc::Contains(void* ptr) const { return allocator_.Contains(ptr); } +bool LinearAlloc::ContainsUnsafe(void* ptr) const { + return allocator_.Contains(ptr); +} + } // namespace art diff --git a/runtime/linear_alloc.h b/runtime/linear_alloc.h index 1b21527317..df7f17dd7a 100644 --- a/runtime/linear_alloc.h +++ b/runtime/linear_alloc.h @@ -47,6 +47,10 @@ class LinearAlloc { // Return true if the linear alloc contrains an address. bool Contains(void* ptr) const REQUIRES(!lock_); + // Unsafe version of 'Contains' only to be used when the allocator is going + // to be deleted. + bool ContainsUnsafe(void* ptr) const NO_THREAD_SAFETY_ANALYSIS; + private: mutable Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER; ArenaAllocator allocator_ GUARDED_BY(lock_); diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index a528c3b890..19ee7f4dd4 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -928,22 +928,22 @@ inline bool Class::IsAssignableFrom(Class* src) { inline uint32_t Class::NumDirectMethods() { LengthPrefixedArray<ArtMethod>* arr = GetDirectMethodsPtrUnchecked(); - return arr != nullptr ? arr->Length() : 0u; + return arr != nullptr ? arr->size() : 0u; } inline uint32_t Class::NumVirtualMethods() { LengthPrefixedArray<ArtMethod>* arr = GetVirtualMethodsPtrUnchecked(); - return arr != nullptr ? arr->Length() : 0u; + return arr != nullptr ? arr->size() : 0u; } inline uint32_t Class::NumInstanceFields() { LengthPrefixedArray<ArtField>* arr = GetIFieldsPtrUnchecked(); - return arr != nullptr ? arr->Length() : 0u; + return arr != nullptr ? arr->size() : 0u; } inline uint32_t Class::NumStaticFields() { LengthPrefixedArray<ArtField>* arr = GetSFieldsPtrUnchecked(); - return arr != nullptr ? arr->Length() : 0u; + return arr != nullptr ? arr->size() : 0u; } } // namespace mirror diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc index 53fedab377..9d01a1db60 100644 --- a/runtime/mirror/class.cc +++ b/runtime/mirror/class.cc @@ -574,7 +574,7 @@ static ArtField* FindFieldByNameAndType(LengthPrefixedArray<ArtField>* fields, return nullptr; } size_t low = 0; - size_t high = fields->Length(); + size_t high = fields->size(); ArtField* ret = nullptr; while (low < high) { size_t mid = (low + high) / 2; diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h index 90180c545b..5c12091ecb 100644 --- a/runtime/mirror/object-inl.h +++ b/runtime/mirror/object-inl.h @@ -95,6 +95,12 @@ inline bool Object::CasLockWordWeakRelaxed(LockWord old_val, LockWord new_val) { OFFSET_OF_OBJECT_MEMBER(Object, monitor_), old_val.GetValue(), new_val.GetValue()); } +inline bool Object::CasLockWordWeakRelease(LockWord old_val, LockWord new_val) { + // Force use of non-transactional mode and do not check. + return CasFieldWeakRelease32<false, false>( + OFFSET_OF_OBJECT_MEMBER(Object, monitor_), old_val.GetValue(), new_val.GetValue()); +} + inline uint32_t Object::GetLockOwnerThreadId() { return Monitor::GetLockOwnerThreadId(this); } @@ -175,7 +181,10 @@ inline bool Object::AtomicSetReadBarrierPointer(Object* expected_rb_ptr, Object* static_cast<uint32_t>(reinterpret_cast<uintptr_t>(expected_rb_ptr))); new_lw = lw; new_lw.SetReadBarrierState(static_cast<uint32_t>(reinterpret_cast<uintptr_t>(rb_ptr))); - } while (!CasLockWordWeakSequentiallyConsistent(expected_lw, new_lw)); + // This CAS is a CAS release so that when GC updates all the fields of an object and then + // changes the object from gray to black, the field updates (stores) will be visible (won't be + // reordered after this CAS.) + } while (!CasLockWordWeakRelease(expected_lw, new_lw)); return true; #elif USE_BROOKS_READ_BARRIER DCHECK(kUseBrooksReadBarrier); @@ -671,6 +680,24 @@ inline bool Object::CasFieldWeakRelaxed32(MemberOffset field_offset, } template<bool kTransactionActive, bool kCheckTransaction, VerifyObjectFlags kVerifyFlags> +inline bool Object::CasFieldWeakRelease32(MemberOffset field_offset, + int32_t old_value, int32_t new_value) { + if (kCheckTransaction) { + DCHECK_EQ(kTransactionActive, Runtime::Current()->IsActiveTransaction()); + } + if (kTransactionActive) { + Runtime::Current()->RecordWriteField32(this, field_offset, old_value, true); + } + if (kVerifyFlags & kVerifyThis) { + VerifyObject(this); + } + uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value(); + AtomicInteger* atomic_addr = reinterpret_cast<AtomicInteger*>(raw_addr); + + return atomic_addr->CompareExchangeWeakRelease(old_value, new_value); +} + +template<bool kTransactionActive, bool kCheckTransaction, VerifyObjectFlags kVerifyFlags> inline bool Object::CasFieldStrongSequentiallyConsistent32(MemberOffset field_offset, int32_t old_value, int32_t new_value) { if (kCheckTransaction) { @@ -944,6 +971,62 @@ inline bool Object::CasFieldStrongSequentiallyConsistentObjectWithoutWriteBarrie return success; } +template<bool kTransactionActive, bool kCheckTransaction, VerifyObjectFlags kVerifyFlags> +inline bool Object::CasFieldWeakRelaxedObjectWithoutWriteBarrier( + MemberOffset field_offset, Object* old_value, Object* new_value) { + if (kCheckTransaction) { + DCHECK_EQ(kTransactionActive, Runtime::Current()->IsActiveTransaction()); + } + if (kVerifyFlags & kVerifyThis) { + VerifyObject(this); + } + if (kVerifyFlags & kVerifyWrites) { + VerifyObject(new_value); + } + if (kVerifyFlags & kVerifyReads) { + VerifyObject(old_value); + } + if (kTransactionActive) { + Runtime::Current()->RecordWriteFieldReference(this, field_offset, old_value, true); + } + HeapReference<Object> old_ref(HeapReference<Object>::FromMirrorPtr(old_value)); + HeapReference<Object> new_ref(HeapReference<Object>::FromMirrorPtr(new_value)); + uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value(); + Atomic<uint32_t>* atomic_addr = reinterpret_cast<Atomic<uint32_t>*>(raw_addr); + + bool success = atomic_addr->CompareExchangeWeakRelaxed(old_ref.reference_, + new_ref.reference_); + return success; +} + +template<bool kTransactionActive, bool kCheckTransaction, VerifyObjectFlags kVerifyFlags> +inline bool Object::CasFieldStrongRelaxedObjectWithoutWriteBarrier( + MemberOffset field_offset, Object* old_value, Object* new_value) { + if (kCheckTransaction) { + DCHECK_EQ(kTransactionActive, Runtime::Current()->IsActiveTransaction()); + } + if (kVerifyFlags & kVerifyThis) { + VerifyObject(this); + } + if (kVerifyFlags & kVerifyWrites) { + VerifyObject(new_value); + } + if (kVerifyFlags & kVerifyReads) { + VerifyObject(old_value); + } + if (kTransactionActive) { + Runtime::Current()->RecordWriteFieldReference(this, field_offset, old_value, true); + } + HeapReference<Object> old_ref(HeapReference<Object>::FromMirrorPtr(old_value)); + HeapReference<Object> new_ref(HeapReference<Object>::FromMirrorPtr(new_value)); + uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value(); + Atomic<uint32_t>* atomic_addr = reinterpret_cast<Atomic<uint32_t>*>(raw_addr); + + bool success = atomic_addr->CompareExchangeStrongRelaxed(old_ref.reference_, + new_ref.reference_); + return success; +} + template<bool kIsStatic, typename Visitor> inline void Object::VisitFieldsReferences(uint32_t ref_offsets, const Visitor& visitor) { if (!kIsStatic && (ref_offsets != mirror::Class::kClassWalkSuper)) { diff --git a/runtime/mirror/object.h b/runtime/mirror/object.h index f75b8aeef4..5c6520fcab 100644 --- a/runtime/mirror/object.h +++ b/runtime/mirror/object.h @@ -99,7 +99,7 @@ class MANAGED LOCKABLE Object { #ifndef USE_BAKER_OR_BROOKS_READ_BARRIER NO_RETURN #endif - bool AtomicSetReadBarrierPointer(Object* expected_rb_ptr, Object* rb_ptr) + ALWAYS_INLINE bool AtomicSetReadBarrierPointer(Object* expected_rb_ptr, Object* rb_ptr) SHARED_REQUIRES(Locks::mutator_lock_); void AssertReadBarrierPointer() const SHARED_REQUIRES(Locks::mutator_lock_); @@ -135,6 +135,8 @@ class MANAGED LOCKABLE Object { SHARED_REQUIRES(Locks::mutator_lock_); bool CasLockWordWeakRelaxed(LockWord old_val, LockWord new_val) SHARED_REQUIRES(Locks::mutator_lock_); + bool CasLockWordWeakRelease(LockWord old_val, LockWord new_val) + SHARED_REQUIRES(Locks::mutator_lock_); uint32_t GetLockOwnerThreadId(); mirror::Object* MonitorEnter(Thread* self) @@ -276,7 +278,6 @@ class MANAGED LOCKABLE Object { Object* old_value, Object* new_value) SHARED_REQUIRES(Locks::mutator_lock_); - template<bool kTransactionActive, bool kCheckTransaction = true, VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> bool CasFieldStrongSequentiallyConsistentObject(MemberOffset field_offset, Object* old_value, @@ -288,6 +289,18 @@ class MANAGED LOCKABLE Object { Object* old_value, Object* new_value) SHARED_REQUIRES(Locks::mutator_lock_); + template<bool kTransactionActive, bool kCheckTransaction = true, + VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + bool CasFieldWeakRelaxedObjectWithoutWriteBarrier(MemberOffset field_offset, + Object* old_value, + Object* new_value) + SHARED_REQUIRES(Locks::mutator_lock_); + template<bool kTransactionActive, bool kCheckTransaction = true, + VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + bool CasFieldStrongRelaxedObjectWithoutWriteBarrier(MemberOffset field_offset, + Object* old_value, + Object* new_value) + SHARED_REQUIRES(Locks::mutator_lock_); template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> HeapReference<Object>* GetFieldObjectReferenceAddr(MemberOffset field_offset); @@ -396,6 +409,12 @@ class MANAGED LOCKABLE Object { template<bool kTransactionActive, bool kCheckTransaction = true, VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + bool CasFieldWeakRelease32(MemberOffset field_offset, int32_t old_value, + int32_t new_value) ALWAYS_INLINE + SHARED_REQUIRES(Locks::mutator_lock_); + + template<bool kTransactionActive, bool kCheckTransaction = true, + VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> bool CasFieldStrongSequentiallyConsistent32(MemberOffset field_offset, int32_t old_value, int32_t new_value) ALWAYS_INLINE SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/runtime/mirror/object_array-inl.h b/runtime/mirror/object_array-inl.h index 5b73557941..5337760fb8 100644 --- a/runtime/mirror/object_array-inl.h +++ b/runtime/mirror/object_array-inl.h @@ -270,7 +270,7 @@ inline MemberOffset ObjectArray<T>::OffsetOfElement(int32_t i) { } template<class T> template<typename Visitor> -void ObjectArray<T>::VisitReferences(const Visitor& visitor) { +inline void ObjectArray<T>::VisitReferences(const Visitor& visitor) { const size_t length = static_cast<size_t>(GetLength()); for (size_t i = 0; i < length; ++i) { visitor(this, OffsetOfElement(i), false); diff --git a/runtime/mirror/object_test.cc b/runtime/mirror/object_test.cc index f5a04457e7..c1284a639f 100644 --- a/runtime/mirror/object_test.cc +++ b/runtime/mirror/object_test.cc @@ -307,10 +307,7 @@ TEST_F(ObjectTest, CheckAndAllocArrayFromCode) { ScopedObjectAccess soa(Thread::Current()); Class* java_util_Arrays = class_linker_->FindSystemClass(soa.Self(), "Ljava/util/Arrays;"); ArtMethod* sort = java_util_Arrays->FindDirectMethod("sort", "([I)V", sizeof(void*)); - const DexFile::StringId* string_id = java_lang_dex_file_->FindStringId("[I"); - ASSERT_TRUE(string_id != nullptr); - const DexFile::TypeId* type_id = java_lang_dex_file_->FindTypeId( - java_lang_dex_file_->GetIndexForStringId(*string_id)); + const DexFile::TypeId* type_id = java_lang_dex_file_->FindTypeId("[I"); ASSERT_TRUE(type_id != nullptr); uint32_t type_idx = java_lang_dex_file_->GetIndexForTypeId(*type_id); Object* array = CheckAndAllocArrayFromCodeInstrumented( @@ -367,16 +364,10 @@ TEST_F(ObjectTest, StaticFieldFromCode) { Handle<mirror::ClassLoader> loader(hs.NewHandle(soa.Decode<ClassLoader*>(class_loader))); Class* klass = class_linker_->FindClass(soa.Self(), "LStaticsFromCode;", loader); ArtMethod* clinit = klass->FindClassInitializer(sizeof(void*)); - const DexFile::StringId* klass_string_id = dex_file->FindStringId("LStaticsFromCode;"); - ASSERT_TRUE(klass_string_id != nullptr); - const DexFile::TypeId* klass_type_id = dex_file->FindTypeId( - dex_file->GetIndexForStringId(*klass_string_id)); + const DexFile::TypeId* klass_type_id = dex_file->FindTypeId("LStaticsFromCode;"); ASSERT_TRUE(klass_type_id != nullptr); - const DexFile::StringId* type_string_id = dex_file->FindStringId("Ljava/lang/Object;"); - ASSERT_TRUE(type_string_id != nullptr); - const DexFile::TypeId* type_type_id = dex_file->FindTypeId( - dex_file->GetIndexForStringId(*type_string_id)); + const DexFile::TypeId* type_type_id = dex_file->FindTypeId("Ljava/lang/Object;"); ASSERT_TRUE(type_type_id != nullptr); const DexFile::StringId* name_str_id = dex_file->FindStringId("s0"); diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc index 3a73900efa..5e423920c0 100644 --- a/runtime/native/java_lang_Class.cc +++ b/runtime/native/java_lang_Class.cc @@ -190,7 +190,7 @@ ALWAYS_INLINE static inline ArtField* FindFieldByName( return nullptr; } size_t low = 0; - size_t high = fields->Length(); + size_t high = fields->size(); const uint16_t* const data = name->GetValue(); const size_t length = name->GetLength(); while (low < high) { diff --git a/runtime/oat.h b/runtime/oat.h index 276e7f3ea5..5b780c38f8 100644 --- a/runtime/oat.h +++ b/runtime/oat.h @@ -31,7 +31,7 @@ class InstructionSetFeatures; class PACKED(4) OatHeader { public: static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' }; - static constexpr uint8_t kOatVersion[] = { '0', '7', '2', '\0' }; + static constexpr uint8_t kOatVersion[] = { '0', '7', '3', '\0' }; static constexpr const char* kImageLocationKey = "image-location"; static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline"; diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index a162a4ea72..680f4ac027 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -547,6 +547,25 @@ bool OatFile::Setup(const char* abs_dex_location, std::string* error_msg) { return false; } const DexFile::Header* header = reinterpret_cast<const DexFile::Header*>(dex_file_pointer); + + if (UNLIKELY(oat > End())) { + *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' with truncated " + "lookup table offset", GetLocation().c_str(), i, + dex_file_location.c_str()); + return false; + } + uint32_t lookup_table_offset = *reinterpret_cast<const uint32_t*>(oat); + oat += sizeof(lookup_table_offset); + if (Begin() + lookup_table_offset > End()) { + *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' with truncated " + "lookup table", GetLocation().c_str(), i, + dex_file_location.c_str()); + return false; + } + const uint8_t* lookup_table_data = lookup_table_offset != 0u + ? Begin() + lookup_table_offset + : nullptr; + const uint32_t* methods_offsets_pointer = reinterpret_cast<const uint32_t*>(oat); oat += (sizeof(*methods_offsets_pointer) * header->class_defs_size_); @@ -586,6 +605,7 @@ bool OatFile::Setup(const char* abs_dex_location, std::string* error_msg) { canonical_location, dex_file_checksum, dex_file_pointer, + lookup_table_data, methods_offsets_pointer, current_dex_cache_arrays); oat_dex_files_storage_.push_back(oat_dex_file); @@ -709,6 +729,7 @@ OatFile::OatDexFile::OatDexFile(const OatFile* oat_file, const std::string& canonical_dex_file_location, uint32_t dex_file_location_checksum, const uint8_t* dex_file_pointer, + const uint8_t* lookup_table_data, const uint32_t* oat_class_offsets_pointer, uint8_t* dex_cache_arrays) : oat_file_(oat_file), @@ -716,6 +737,7 @@ OatFile::OatDexFile::OatDexFile(const OatFile* oat_file, canonical_dex_file_location_(canonical_dex_file_location), dex_file_location_checksum_(dex_file_location_checksum), dex_file_pointer_(dex_file_pointer), + lookup_table_data_(lookup_table_data), oat_class_offsets_pointer_(oat_class_offsets_pointer), dex_cache_arrays_(dex_cache_arrays) {} diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 6acdf86208..0a77654903 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -400,6 +400,10 @@ class OatDexFile FINAL { return dex_cache_arrays_; } + const uint8_t* GetLookupTableData() const { + return lookup_table_data_; + } + ~OatDexFile(); private: @@ -408,6 +412,7 @@ class OatDexFile FINAL { const std::string& canonical_dex_file_location, uint32_t dex_file_checksum, const uint8_t* dex_file_pointer, + const uint8_t* lookup_table_data, const uint32_t* oat_class_offsets_pointer, uint8_t* dex_cache_arrays); @@ -416,6 +421,7 @@ class OatDexFile FINAL { const std::string canonical_dex_file_location_; const uint32_t dex_file_location_checksum_; const uint8_t* const dex_file_pointer_; + const uint8_t* lookup_table_data_; const uint32_t* const oat_class_offsets_pointer_; uint8_t* const dex_cache_arrays_; diff --git a/runtime/oat_quick_method_header.h b/runtime/oat_quick_method_header.h index 6eadd87d38..03cad0835e 100644 --- a/runtime/oat_quick_method_header.h +++ b/runtime/oat_quick_method_header.h @@ -21,6 +21,7 @@ #include "base/macros.h" #include "quick/quick_method_frame_info.h" #include "stack_map.h" +#include "utils.h" namespace art { @@ -39,6 +40,18 @@ class PACKED(4) OatQuickMethodHeader { ~OatQuickMethodHeader(); + static OatQuickMethodHeader* FromCodePointer(const void* code_ptr) { + uintptr_t code = reinterpret_cast<uintptr_t>(code_ptr); + uintptr_t header = code - OFFSETOF_MEMBER(OatQuickMethodHeader, code_); + DCHECK(IsAlignedParam(code, GetInstructionSetAlignment(kRuntimeISA)) || + IsAlignedParam(header, GetInstructionSetAlignment(kRuntimeISA))); + return reinterpret_cast<OatQuickMethodHeader*>(header); + } + + static OatQuickMethodHeader* FromEntryPoint(const void* entry_point) { + return FromCodePointer(EntryPointToCodePointer(entry_point)); + } + OatQuickMethodHeader& operator=(const OatQuickMethodHeader&) = default; uintptr_t NativeQuickPcOffset(const uintptr_t pc) const { @@ -74,6 +87,11 @@ class PACKED(4) OatQuickMethodHeader { bool Contains(uintptr_t pc) const { uintptr_t code_start = reinterpret_cast<uintptr_t>(code_); + static_assert(kRuntimeISA != kThumb2, "kThumb2 cannot be a runtime ISA"); + if (kRuntimeISA == kArm) { + // On Thumb-2, the pc is offset by one. + code_start++; + } return code_start <= pc && pc <= (code_start + code_size_); } diff --git a/runtime/proxy_test.cc b/runtime/proxy_test.cc index bc9ba377bc..57472adb64 100644 --- a/runtime/proxy_test.cc +++ b/runtime/proxy_test.cc @@ -216,10 +216,10 @@ TEST_F(ProxyTest, CheckArtMirrorFieldsOfProxyStaticFields) { LengthPrefixedArray<ArtField>* static_fields0 = proxyClass0->GetSFieldsPtr(); ASSERT_TRUE(static_fields0 != nullptr); - ASSERT_EQ(2u, static_fields0->Length()); + ASSERT_EQ(2u, static_fields0->size()); LengthPrefixedArray<ArtField>* static_fields1 = proxyClass1->GetSFieldsPtr(); ASSERT_TRUE(static_fields1 != nullptr); - ASSERT_EQ(2u, static_fields1->Length()); + ASSERT_EQ(2u, static_fields1->size()); EXPECT_EQ(static_fields0->At(0).GetDeclaringClass(), proxyClass0.Get()); EXPECT_EQ(static_fields0->At(1).GetDeclaringClass(), proxyClass0.Get()); diff --git a/runtime/quick/inline_method_analyser.cc b/runtime/quick/inline_method_analyser.cc index 99e262e6a9..65543942c7 100644 --- a/runtime/quick/inline_method_analyser.cc +++ b/runtime/quick/inline_method_analyser.cc @@ -73,7 +73,6 @@ static_assert(InlineMethodAnalyser::IGetVariant(Instruction::IGET_SHORT) == bool InlineMethodAnalyser::AnalyseMethodCode(verifier::MethodVerifier* verifier, InlineMethod* method) { DCHECK(verifier != nullptr); - DCHECK_EQ(Runtime::Current()->IsCompiler(), method != nullptr); if (!Runtime::Current()->UseJit()) { DCHECK_EQ(verifier->CanLoadClasses(), method != nullptr); } diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc index 53b4f3a3b5..1552318c1e 100644 --- a/runtime/quick_exception_handler.cc +++ b/runtime/quick_exception_handler.cc @@ -372,9 +372,14 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { StackMapEncoding encoding = code_info.ExtractEncoding(); StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset, encoding); const size_t number_of_vregs = m->GetCodeItem()->registers_size_; - DexRegisterMap vreg_map = code_info.GetDexRegisterMapOf(stack_map, encoding, number_of_vregs); MemoryRegion stack_mask = stack_map.GetStackMask(encoding); uint32_t register_mask = stack_map.GetRegisterMask(encoding); + DexRegisterMap vreg_map = IsInInlinedFrame() + ? code_info.GetDexRegisterMapAtDepth(GetCurrentInliningDepth() - 1, + code_info.GetInlineInfoOf(stack_map, encoding), + encoding, + number_of_vregs) + : code_info.GetDexRegisterMapOf(stack_map, encoding, number_of_vregs); for (uint16_t vreg = 0; vreg < number_of_vregs; ++vreg) { if (updated_vregs != nullptr && updated_vregs[vreg]) { diff --git a/runtime/read_barrier-inl.h b/runtime/read_barrier-inl.h index 85ac4aab96..7de6c06f2b 100644 --- a/runtime/read_barrier-inl.h +++ b/runtime/read_barrier-inl.h @@ -19,7 +19,7 @@ #include "read_barrier.h" -#include "gc/collector/concurrent_copying.h" +#include "gc/collector/concurrent_copying-inl.h" #include "gc/heap.h" #include "mirror/object_reference.h" #include "mirror/reference.h" @@ -63,7 +63,7 @@ inline MirrorType* ReadBarrier::Barrier( ref = reinterpret_cast<MirrorType*>(Mark(old_ref)); // Update the field atomically. This may fail if mutator updates before us, but it's ok. if (ref != old_ref) { - obj->CasFieldStrongSequentiallyConsistentObjectWithoutWriteBarrier<false, false>( + obj->CasFieldStrongRelaxedObjectWithoutWriteBarrier<false, false>( offset, old_ref, ref); } } @@ -101,7 +101,7 @@ inline MirrorType* ReadBarrier::BarrierForRoot(MirrorType** root, // Update the field atomically. This may fail if mutator updates before us, but it's ok. if (ref != old_ref) { Atomic<mirror::Object*>* atomic_root = reinterpret_cast<Atomic<mirror::Object*>*>(root); - atomic_root->CompareExchangeStrongSequentiallyConsistent(old_ref, ref); + atomic_root->CompareExchangeStrongRelaxed(old_ref, ref); } } AssertToSpaceInvariant(gc_root_source, ref); @@ -140,7 +140,7 @@ inline MirrorType* ReadBarrier::BarrierForRoot(mirror::CompressedReference<Mirro if (new_ref.AsMirrorPtr() != old_ref.AsMirrorPtr()) { auto* atomic_root = reinterpret_cast<Atomic<mirror::CompressedReference<MirrorType>>*>(root); - atomic_root->CompareExchangeStrongSequentiallyConsistent(old_ref, new_ref); + atomic_root->CompareExchangeStrongRelaxed(old_ref, new_ref); } } AssertToSpaceInvariant(gc_root_source, ref); diff --git a/runtime/stack.cc b/runtime/stack.cc index 9359d27822..d7edfade15 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -856,13 +856,11 @@ static void AssertPcIsWithinQuickCode(ArtMethod* method, uintptr_t pc) // If we are the JIT then we may have just compiled the method after the // IsQuickToInterpreterBridge check. jit::Jit* const jit = Runtime::Current()->GetJit(); - if (jit != nullptr && - jit->GetCodeCache()->ContainsCodePtr(reinterpret_cast<const void*>(code))) { + if (jit != nullptr && jit->GetCodeCache()->ContainsPc(code)) { return; } - uint32_t code_size = reinterpret_cast<const OatQuickMethodHeader*>( - EntryPointToCodePointer(code))[-1].code_size_; + uint32_t code_size = OatQuickMethodHeader::FromEntryPoint(code)->code_size_; uintptr_t code_start = reinterpret_cast<uintptr_t>(code); CHECK(code_start <= pc && pc <= (code_start + code_size)) << PrettyMethod(method) @@ -960,26 +958,18 @@ QuickMethodFrameInfo StackVisitor::GetCurrentQuickFrameInfo() const { return runtime->GetRuntimeMethodFrameInfo(method); } - // For Proxy method we add special handling for the direct method case (there is only one - // direct method - constructor). Direct method is cloned from original - // java.lang.reflect.Proxy class together with code and as a result it is executed as usual - // quick compiled method without any stubs. So the frame info should be returned as it is a - // quick method not a stub. However, if instrumentation stubs are installed, the - // instrumentation->GetQuickCodeFor() returns the artQuickProxyInvokeHandler instead of an - // oat code pointer, thus we have to add a special case here. if (method->IsProxyMethod()) { - if (method->IsDirect()) { - CHECK(method->IsConstructor()); - const void* code_pointer = - EntryPointToCodePointer(method->GetEntryPointFromQuickCompiledCode()); - return reinterpret_cast<const OatQuickMethodHeader*>(code_pointer)[-1].frame_info_; - } else { - return runtime->GetCalleeSaveMethodFrameInfo(Runtime::kRefsAndArgs); - } + // There is only one direct method of a proxy class: the constructor. A direct method is + // cloned from the original java.lang.reflect.Proxy and is executed as usual quick + // compiled method without any stubs. Therefore the method must have a OatQuickMethodHeader. + DCHECK(!method->IsDirect() && !method->IsConstructor()) + << "Constructors of proxy classes must have a OatQuickMethodHeader"; + return runtime->GetCalleeSaveMethodFrameInfo(Runtime::kRefsAndArgs); } - ClassLinker* class_linker = runtime->GetClassLinker(); + // The only remaining case is if the method is native and uses the generic JNI stub. DCHECK(method->IsNative()); + ClassLinker* class_linker = runtime->GetClassLinker(); const void* entry_point = runtime->GetInstrumentation()->GetQuickCodeFor(method, sizeof(void*)); DCHECK(class_linker->IsQuickGenericJniStub(entry_point)) << PrettyMethod(method); // Generic JNI frame. diff --git a/runtime/stack.h b/runtime/stack.h index 1276b244e7..aa7b6160fe 100644 --- a/runtime/stack.h +++ b/runtime/stack.h @@ -698,6 +698,10 @@ class StackVisitor { return current_inlining_depth_ != 0; } + size_t GetCurrentInliningDepth() const { + return current_inlining_depth_; + } + uintptr_t GetCurrentQuickFramePc() const { return cur_quick_frame_pc_; } diff --git a/runtime/stride_iterator.h b/runtime/stride_iterator.h index a9da51ba29..ac04c3b403 100644 --- a/runtime/stride_iterator.h +++ b/runtime/stride_iterator.h @@ -19,6 +19,8 @@ #include <iterator> +#include "base/logging.h" + namespace art { template<typename T> diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index bdd5d1099c..b09b87fb58 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -60,8 +60,11 @@ static constexpr useconds_t kThreadSuspendMaxYieldUs = 3000; static constexpr useconds_t kThreadSuspendMaxSleepUs = 5000; ThreadList::ThreadList() - : suspend_all_count_(0), debug_suspend_all_count_(0), unregistering_count_(0), - suspend_all_historam_("suspend all histogram", 16, 64), long_suspend_(false) { + : suspend_all_count_(0), + debug_suspend_all_count_(0), + unregistering_count_(0), + suspend_all_historam_("suspend all histogram", 16, 64), + long_suspend_(false) { CHECK(Monitor::IsValidLockWord(LockWord::FromThinLockId(kMaxThreadId, 1, 0U))); } @@ -195,9 +198,7 @@ class DumpCheckpoint FINAL : public Closure { MutexLock mu(self, *Locks::logging_lock_); *os_ << local_os.str(); } - if (thread->GetState() == kRunnable) { - barrier_.Pass(self); - } + barrier_.Pass(self); } void WaitForThreadsToRunThroughCheckpoint(size_t threads_running_checkpoint) { @@ -274,9 +275,6 @@ size_t ThreadList::RunCheckpoint(Closure* checkpoint_function) { Locks::mutator_lock_->AssertNotExclusiveHeld(self); Locks::thread_list_lock_->AssertNotHeld(self); Locks::thread_suspend_count_lock_->AssertNotHeld(self); - if (kDebugLocking && gAborting == 0) { - CHECK_NE(self->GetState(), kRunnable); - } std::vector<Thread*> suspended_count_modified_threads; size_t count = 0; @@ -285,12 +283,12 @@ size_t ThreadList::RunCheckpoint(Closure* checkpoint_function) { // manually called. MutexLock mu(self, *Locks::thread_list_lock_); MutexLock mu2(self, *Locks::thread_suspend_count_lock_); + count = list_.size(); for (const auto& thread : list_) { if (thread != self) { while (true) { if (thread->RequestCheckpoint(checkpoint_function)) { // This thread will run its checkpoint some time in the near future. - count++; break; } else { // We are probably suspended, try to make sure that we stay suspended. @@ -383,7 +381,8 @@ size_t ThreadList::RunCheckpointOnRunnableThreads(Closure* checkpoint_function) // from-space to to-space refs. Used to synchronize threads at a point // to mark the initiation of marking while maintaining the to-space // invariant. -size_t ThreadList::FlipThreadRoots(Closure* thread_flip_visitor, Closure* flip_callback, +size_t ThreadList::FlipThreadRoots(Closure* thread_flip_visitor, + Closure* flip_callback, gc::collector::GarbageCollector* collector) { TimingLogger::ScopedTiming split("ThreadListFlip", collector->GetTimings()); const uint64_t start_time = NanoTime(); @@ -511,7 +510,9 @@ void ThreadList::SuspendAll(const char* cause, bool long_suspend) { // Debugger thread might be set to kRunnable for a short period of time after the // SuspendAllInternal. This is safe because it will be set back to suspended state before // the SuspendAll returns. -void ThreadList::SuspendAllInternal(Thread* self, Thread* ignore1, Thread* ignore2, +void ThreadList::SuspendAllInternal(Thread* self, + Thread* ignore1, + Thread* ignore2, bool debug_suspend) { Locks::mutator_lock_->AssertNotExclusiveHeld(self); Locks::thread_list_lock_->AssertNotHeld(self); @@ -700,12 +701,14 @@ void ThreadList::Resume(Thread* thread, bool for_debugger) { VLOG(threads) << "Resume(" << reinterpret_cast<void*>(thread) << ") complete"; } -static void ThreadSuspendByPeerWarning(Thread* self, LogSeverity severity, const char* message, +static void ThreadSuspendByPeerWarning(Thread* self, + LogSeverity severity, + const char* message, jobject peer) { JNIEnvExt* env = self->GetJniEnv(); ScopedLocalRef<jstring> - scoped_name_string(env, (jstring)env->GetObjectField( - peer, WellKnownClasses::java_lang_Thread_name)); + scoped_name_string(env, static_cast<jstring>(env->GetObjectField( + peer, WellKnownClasses::java_lang_Thread_name))); ScopedUtfChars scoped_name_chars(env, scoped_name_string.get()); if (scoped_name_chars.c_str() == nullptr) { LOG(severity) << message << ": " << peer; @@ -715,8 +718,10 @@ static void ThreadSuspendByPeerWarning(Thread* self, LogSeverity severity, const } } -Thread* ThreadList::SuspendThreadByPeer(jobject peer, bool request_suspension, - bool debug_suspension, bool* timed_out) { +Thread* ThreadList::SuspendThreadByPeer(jobject peer, + bool request_suspension, + bool debug_suspension, + bool* timed_out) { const uint64_t start_time = NanoTime(); useconds_t sleep_us = kThreadSuspendInitialSleepUs; *timed_out = false; @@ -813,12 +818,14 @@ Thread* ThreadList::SuspendThreadByPeer(jobject peer, bool request_suspension, } } -static void ThreadSuspendByThreadIdWarning(LogSeverity severity, const char* message, +static void ThreadSuspendByThreadIdWarning(LogSeverity severity, + const char* message, uint32_t thread_id) { LOG(severity) << StringPrintf("%s: %d", message, thread_id); } -Thread* ThreadList::SuspendThreadByThreadId(uint32_t thread_id, bool debug_suspension, +Thread* ThreadList::SuspendThreadByThreadId(uint32_t thread_id, + bool debug_suspension, bool* timed_out) { const uint64_t start_time = NanoTime(); useconds_t sleep_us = kThreadSuspendInitialSleepUs; diff --git a/runtime/thread_list.h b/runtime/thread_list.h index c727432977..07ea10dbea 100644 --- a/runtime/thread_list.h +++ b/runtime/thread_list.h @@ -55,8 +55,8 @@ class ThreadList { // Thread suspension support. void ResumeAll() - UNLOCK_FUNCTION(Locks::mutator_lock_) - REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); + REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_) + UNLOCK_FUNCTION(Locks::mutator_lock_); void Resume(Thread* thread, bool for_debugger = false) REQUIRES(!Locks::thread_suspend_count_lock_); @@ -76,7 +76,8 @@ class ThreadList { // is set to true. Thread* SuspendThreadByPeer(jobject peer, bool request_suspension, bool debug_suspension, bool* timed_out) - REQUIRES(!Locks::mutator_lock_, !Locks::thread_list_lock_, + REQUIRES(!Locks::mutator_lock_, + !Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); // Suspend a thread using its thread id, typically used by lock/monitor inflation. Returns the @@ -84,14 +85,16 @@ class ThreadList { // the thread terminating. Note that as thread ids are recycled this may not suspend the expected // thread, that may be terminating. If the suspension times out then *timeout is set to true. Thread* SuspendThreadByThreadId(uint32_t thread_id, bool debug_suspension, bool* timed_out) - REQUIRES(!Locks::mutator_lock_, !Locks::thread_list_lock_, + REQUIRES(!Locks::mutator_lock_, + !Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); // Find an already suspended thread (or self) by its id. Thread* FindThreadByThreadId(uint32_t thin_lock_id); // Run a checkpoint on threads, running threads are not suspended but run the checkpoint inside - // of the suspend check. Returns how many checkpoints we should expect to run. + // of the suspend check. Returns how many checkpoints that are expected to run, including for + // already suspended threads for b/24191051. size_t RunCheckpoint(Closure* checkpoint_function) REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); @@ -100,14 +103,17 @@ class ThreadList { // Flip thread roots from from-space refs to to-space refs. Used by // the concurrent copying collector. - size_t FlipThreadRoots(Closure* thread_flip_visitor, Closure* flip_callback, + size_t FlipThreadRoots(Closure* thread_flip_visitor, + Closure* flip_callback, gc::collector::GarbageCollector* collector) - REQUIRES(!Locks::mutator_lock_, !Locks::thread_list_lock_, + REQUIRES(!Locks::mutator_lock_, + !Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); // Suspends all threads void SuspendAllForDebugger() - REQUIRES(!Locks::mutator_lock_, !Locks::thread_list_lock_, + REQUIRES(!Locks::mutator_lock_, + !Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); void SuspendSelfForDebugger() @@ -126,10 +132,14 @@ class ThreadList { // Add/remove current thread from list. void Register(Thread* self) - REQUIRES(Locks::runtime_shutdown_lock_, !Locks::mutator_lock_, !Locks::thread_list_lock_, + REQUIRES(Locks::runtime_shutdown_lock_) + REQUIRES(!Locks::mutator_lock_, + !Locks::thread_list_lock_, + !Locks::thread_suspend_count_lock_); + void Unregister(Thread* self) + REQUIRES(!Locks::mutator_lock_, + !Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); - void Unregister(Thread* self) REQUIRES(!Locks::mutator_lock_, !Locks::thread_list_lock_, - !Locks::thread_suspend_count_lock_); void VisitRoots(RootVisitor* visitor) const SHARED_REQUIRES(Locks::mutator_lock_); @@ -159,7 +169,9 @@ class ThreadList { void WaitForOtherNonDaemonThreadsToExit() REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); - void SuspendAllInternal(Thread* self, Thread* ignore1, Thread* ignore2 = nullptr, + void SuspendAllInternal(Thread* self, + Thread* ignore1, + Thread* ignore2 = nullptr, bool debug_suspend = false) REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); @@ -200,8 +212,8 @@ class ScopedSuspendAll : public ValueObject { !Locks::mutator_lock_); // No REQUIRES(mutator_lock_) since the unlock function already asserts this. ~ScopedSuspendAll() - UNLOCK_FUNCTION(Locks::mutator_lock_) - REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_); + REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_lock_) + UNLOCK_FUNCTION(Locks::mutator_lock_); }; } // namespace art diff --git a/runtime/type_lookup_table.cc b/runtime/type_lookup_table.cc new file mode 100644 index 0000000000..0d40bb7be4 --- /dev/null +++ b/runtime/type_lookup_table.cc @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2015 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 "type_lookup_table.h" + +#include "dex_file-inl.h" +#include "utf-inl.h" +#include "utils.h" + +#include <memory> +#include <cstring> + +namespace art { + +static uint16_t MakeData(uint16_t class_def_idx, uint32_t hash, uint32_t mask) { + uint16_t hash_mask = static_cast<uint16_t>(~mask); + return (static_cast<uint16_t>(hash) & hash_mask) | class_def_idx; +} + +TypeLookupTable::~TypeLookupTable() { + if (!owns_entries_) { + // We don't actually own the entries, don't let the unique_ptr release them. + entries_.release(); + } +} + +uint32_t TypeLookupTable::RawDataLength() const { + return RawDataLength(dex_file_); +} + +uint32_t TypeLookupTable::RawDataLength(const DexFile& dex_file) { + return RoundUpToPowerOfTwo(dex_file.NumClassDefs()) * sizeof(Entry); +} + +TypeLookupTable* TypeLookupTable::Create(const DexFile& dex_file) { + const uint32_t num_class_defs = dex_file.NumClassDefs(); + return (num_class_defs == 0 || num_class_defs > std::numeric_limits<uint16_t>::max()) + ? nullptr + : new TypeLookupTable(dex_file); +} + +TypeLookupTable* TypeLookupTable::Open(const uint8_t* raw_data, const DexFile& dex_file) { + return new TypeLookupTable(raw_data, dex_file); +} + +TypeLookupTable::TypeLookupTable(const DexFile& dex_file) + : dex_file_(dex_file), + mask_(RoundUpToPowerOfTwo(dex_file.NumClassDefs()) - 1), + entries_(new Entry[mask_ + 1]), + owns_entries_(true) { + std::vector<uint16_t> conflict_class_defs; + // The first stage. Put elements on their initial positions. If an initial position is already + // occupied then delay the insertion of the element to the second stage to reduce probing + // distance. + for (size_t i = 0; i < dex_file.NumClassDefs(); ++i) { + const DexFile::ClassDef& class_def = dex_file.GetClassDef(i); + const DexFile::TypeId& type_id = dex_file.GetTypeId(class_def.class_idx_); + const DexFile::StringId& str_id = dex_file.GetStringId(type_id.descriptor_idx_); + const uint32_t hash = ComputeModifiedUtf8Hash(dex_file.GetStringData(str_id)); + Entry entry; + entry.str_offset = str_id.string_data_off_; + entry.data = MakeData(i, hash, GetSizeMask()); + if (!SetOnInitialPos(entry, hash)) { + conflict_class_defs.push_back(i); + } + } + // The second stage. The initial position of these elements had a collision. Put these elements + // into the nearest free cells and link them together by updating next_pos_delta. + for (uint16_t class_def_idx : conflict_class_defs) { + const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_idx); + const DexFile::TypeId& type_id = dex_file.GetTypeId(class_def.class_idx_); + const DexFile::StringId& str_id = dex_file.GetStringId(type_id.descriptor_idx_); + const uint32_t hash = ComputeModifiedUtf8Hash(dex_file.GetStringData(str_id)); + Entry entry; + entry.str_offset = str_id.string_data_off_; + entry.data = MakeData(class_def_idx, hash, GetSizeMask()); + Insert(entry, hash); + } +} + +TypeLookupTable::TypeLookupTable(const uint8_t* raw_data, const DexFile& dex_file) + : dex_file_(dex_file), + mask_(RoundUpToPowerOfTwo(dex_file.NumClassDefs()) - 1), + entries_(reinterpret_cast<Entry*>(const_cast<uint8_t*>(raw_data))), + owns_entries_(false) {} + +bool TypeLookupTable::SetOnInitialPos(const Entry& entry, uint32_t hash) { + const uint32_t pos = hash & GetSizeMask(); + if (!entries_[pos].IsEmpty()) { + return false; + } + entries_[pos] = entry; + entries_[pos].next_pos_delta = 0; + return true; +} + +void TypeLookupTable::Insert(const Entry& entry, uint32_t hash) { + uint32_t pos = FindLastEntryInBucket(hash & GetSizeMask()); + uint32_t next_pos = (pos + 1) & GetSizeMask(); + while (!entries_[next_pos].IsEmpty()) { + next_pos = (next_pos + 1) & GetSizeMask(); + } + const uint32_t delta = (next_pos >= pos) ? (next_pos - pos) : (next_pos + Size() - pos); + entries_[pos].next_pos_delta = delta; + entries_[next_pos] = entry; + entries_[next_pos].next_pos_delta = 0; +} + +uint32_t TypeLookupTable::FindLastEntryInBucket(uint32_t pos) const { + const Entry* entry = &entries_[pos]; + while (!entry->IsLast()) { + pos = (pos + entry->next_pos_delta) & GetSizeMask(); + entry = &entries_[pos]; + } + return pos; +} + +} // namespace art diff --git a/runtime/type_lookup_table.h b/runtime/type_lookup_table.h new file mode 100644 index 0000000000..3c2295c428 --- /dev/null +++ b/runtime/type_lookup_table.h @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 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_RUNTIME_TYPE_LOOKUP_TABLE_H_ +#define ART_RUNTIME_TYPE_LOOKUP_TABLE_H_ + +#include "dex_file.h" +#include "leb128.h" +#include "utf.h" + +namespace art { + +/** + * TypeLookupTable used to find class_def_idx by class descriptor quickly. + * Implementation of TypeLookupTable is based on hash table. + * This class instantiated at compile time by calling Create() method and written into OAT file. + * At runtime, the raw data is read from memory-mapped file by calling Open() method. The table + * memory remains clean. + */ +class TypeLookupTable { + public: + ~TypeLookupTable(); + + // Return the number of buckets in the lookup table. + uint32_t Size() const { + return mask_ + 1; + } + + // Method search class_def_idx by class descriptor and it's hash. + // If no data found then the method returns DexFile::kDexNoIndex + ALWAYS_INLINE uint32_t Lookup(const char* str, uint32_t hash) const { + uint32_t pos = hash & GetSizeMask(); + // Thanks to special insertion algorithm, the element at position pos can be empty or start of + // bucket. + const Entry* entry = &entries_[pos]; + while (!entry->IsEmpty()) { + if (CmpHashBits(entry->data, hash) && IsStringsEquals(str, entry->str_offset)) { + return GetClassDefIdx(entry->data); + } + if (entry->IsLast()) { + return DexFile::kDexNoIndex; + } + pos = (pos + entry->next_pos_delta) & GetSizeMask(); + entry = &entries_[pos]; + } + return DexFile::kDexNoIndex; + } + + // Method creates lookup table for dex file + static TypeLookupTable* Create(const DexFile& dex_file); + + // Method opens lookup table from binary data. Lookup table does not owns binary data. + static TypeLookupTable* Open(const uint8_t* raw_data, const DexFile& dex_file); + + // Method returns pointer to binary data of lookup table. Used by the oat writer. + const uint8_t* RawData() const { + return reinterpret_cast<const uint8_t*>(entries_.get()); + } + + // Method returns length of binary data. Used by the oat writer. + uint32_t RawDataLength() const; + + // Method returns length of binary data for the specified dex file. + static uint32_t RawDataLength(const DexFile& dex_file); + + private: + /** + * To find element we need to compare strings. + * It is faster to compare first hashes and then strings itself. + * But we have no full hash of element of table. But we can use 2 ideas. + * 1. All minor bits of hash inside one bucket are equals. + * 2. If dex file contains N classes and size of hash table is 2^n (where N <= 2^n) + * then 16-n bits are free. So we can encode part of element's hash into these bits. + * So hash of element can be divided on three parts: + * XXXX XXXX XXXX YYYY YZZZ ZZZZ ZZZZZ + * Z - a part of hash encoded in bucket (these bits of has are same for all elements in bucket) - + * n bits + * Y - a part of hash that we can write into free 16-n bits (because only n bits used to store + * class_def_idx) + * X - a part of has that we can't use without increasing increase + * So the data element of Entry used to store class_def_idx and part of hash of the entry. + */ + struct Entry { + uint32_t str_offset; + uint16_t data; + uint16_t next_pos_delta; + + Entry() : str_offset(0), data(0), next_pos_delta(0) {} + + bool IsEmpty() const { + return str_offset == 0; + } + + bool IsLast() const { + return next_pos_delta == 0; + } + }; + + // Construct from a dex file. + explicit TypeLookupTable(const DexFile& dex_file); + + // Construct from a dex file with existing data. + TypeLookupTable(const uint8_t* raw_data, const DexFile& dex_file); + + bool IsStringsEquals(const char* str, uint32_t str_offset) const { + const uint8_t* ptr = dex_file_.Begin() + str_offset; + // Skip string length. + DecodeUnsignedLeb128(&ptr); + return CompareModifiedUtf8ToModifiedUtf8AsUtf16CodePointValues( + str, reinterpret_cast<const char*>(ptr)) == 0; + } + + // Method extracts hash bits from element's data and compare them with + // the corresponding bits of the specified hash + bool CmpHashBits(uint32_t data, uint32_t hash) const { + uint32_t mask = static_cast<uint16_t>(~GetSizeMask()); + return (hash & mask) == (data & mask); + } + + uint32_t GetClassDefIdx(uint32_t data) const { + return data & mask_; + } + + uint32_t GetSizeMask() const { + return mask_; + } + + // Attempt to set an entry on it's hash' slot. If there is alrady something there, return false. + // Otherwise return true. + bool SetOnInitialPos(const Entry& entry, uint32_t hash); + + // Insert an entry, probes until there is an empty slot. + void Insert(const Entry& entry, uint32_t hash); + + // Find the last entry in a chain. + uint32_t FindLastEntryInBucket(uint32_t cur_pos) const; + + const DexFile& dex_file_; + const uint32_t mask_; + std::unique_ptr<Entry[]> entries_; + // owns_entries_ specifies if the lookup table owns the entries_ array. + const bool owns_entries_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(TypeLookupTable); +}; + +} // namespace art + +#endif // ART_RUNTIME_TYPE_LOOKUP_TABLE_H_ diff --git a/runtime/type_lookup_table_test.cc b/runtime/type_lookup_table_test.cc new file mode 100644 index 0000000000..7f500cc9a1 --- /dev/null +++ b/runtime/type_lookup_table_test.cc @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011 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 <memory> + +#include "common_runtime_test.h" +#include "dex_file-inl.h" +#include "scoped_thread_state_change.h" +#include "type_lookup_table.h" +#include "utf-inl.h" + +namespace art { + +class TypeLookupTableTest : public CommonRuntimeTest { + public: + size_t kDexNoIndex = DexFile::kDexNoIndex; // Make copy to prevent linking errors. +}; + +TEST_F(TypeLookupTableTest, CreateLookupTable) { + ScopedObjectAccess soa(Thread::Current()); + std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup")); + std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file)); + ASSERT_NE(nullptr, table.get()); + ASSERT_NE(nullptr, table->RawData()); + ASSERT_EQ(32U, table->RawDataLength()); +} + +TEST_F(TypeLookupTableTest, FindNonExistingClassWithoutCollisions) { + ScopedObjectAccess soa(Thread::Current()); + std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup")); + std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file)); + ASSERT_NE(nullptr, table.get()); + const char* descriptor = "LBA;"; + size_t hash = ComputeModifiedUtf8Hash(descriptor); + uint32_t class_def_idx = table->Lookup(descriptor, hash); + ASSERT_EQ(kDexNoIndex, class_def_idx); +} + +TEST_F(TypeLookupTableTest, FindNonExistingClassWithCollisions) { + ScopedObjectAccess soa(Thread::Current()); + std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup")); + std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file)); + ASSERT_NE(nullptr, table.get()); + const char* descriptor = "LDA;"; + size_t hash = ComputeModifiedUtf8Hash(descriptor); + uint32_t class_def_idx = table->Lookup(descriptor, hash); + ASSERT_EQ(kDexNoIndex, class_def_idx); +} + +TEST_F(TypeLookupTableTest, FindClassNoCollisions) { + ScopedObjectAccess soa(Thread::Current()); + std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup")); + std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file)); + ASSERT_NE(nullptr, table.get()); + const char* descriptor = "LC;"; + size_t hash = ComputeModifiedUtf8Hash(descriptor); + uint32_t class_def_idx = table->Lookup(descriptor, hash); + ASSERT_EQ(2U, class_def_idx); +} + +TEST_F(TypeLookupTableTest, FindClassWithCollisions) { + ScopedObjectAccess soa(Thread::Current()); + std::unique_ptr<const DexFile> dex_file(OpenTestDexFile("Lookup")); + std::unique_ptr<TypeLookupTable> table(TypeLookupTable::Create(*dex_file)); + ASSERT_NE(nullptr, table.get()); + const char* descriptor = "LAB;"; + size_t hash = ComputeModifiedUtf8Hash(descriptor); + uint32_t class_def_idx = table->Lookup(descriptor, hash); + ASSERT_EQ(1U, class_def_idx); +} + +} // namespace art diff --git a/runtime/utils.cc b/runtime/utils.cc index dee4f9c891..48dce63f00 100644 --- a/runtime/utils.cc +++ b/runtime/utils.cc @@ -1835,4 +1835,43 @@ void DumpMethodCFG(const DexFile* dex_file, uint32_t dex_method_idx, std::ostrea os << "Something went wrong, didn't find the method in the class data."; } +static void ParseStringAfterChar(const std::string& s, + char c, + std::string* parsed_value, + UsageFn Usage) { + std::string::size_type colon = s.find(c); + if (colon == std::string::npos) { + Usage("Missing char %c in option %s\n", c, s.c_str()); + } + // Add one to remove the char we were trimming until. + *parsed_value = s.substr(colon + 1); +} + +void ParseDouble(const std::string& option, + char after_char, + double min, + double max, + double* parsed_value, + UsageFn Usage) { + std::string substring; + ParseStringAfterChar(option, after_char, &substring, Usage); + bool sane_val = true; + double value; + if ((false)) { + // TODO: this doesn't seem to work on the emulator. b/15114595 + std::stringstream iss(substring); + iss >> value; + // Ensure that we have a value, there was no cruft after it and it satisfies a sensible range. + sane_val = iss.eof() && (value >= min) && (value <= max); + } else { + char* end = nullptr; + value = strtod(substring.c_str(), &end); + sane_val = *end == '\0' && value >= min && value <= max; + } + if (!sane_val) { + Usage("Invalid double value %s for option %s\n", substring.c_str(), option.c_str()); + } + *parsed_value = value; +} + } // namespace art diff --git a/runtime/utils.h b/runtime/utils.h index bd52b686fd..3690f86a80 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -26,8 +26,10 @@ #include <vector> #include "arch/instruction_set.h" +#include "base/casts.h" #include "base/logging.h" #include "base/mutex.h" +#include "base/stringpiece.h" #include "globals.h" #include "primitive.h" @@ -35,7 +37,6 @@ class BacktraceMap; namespace art { -class ArtCode; class ArtField; class ArtMethod; class DexFile; @@ -321,6 +322,34 @@ static inline const void* EntryPointToCodePointer(const void* entry_point) { return reinterpret_cast<const void*>(code); } +using UsageFn = void (*)(const char*, ...); + +template <typename T> +static void ParseUintOption(const StringPiece& option, + const std::string& option_name, + T* out, + UsageFn Usage, + bool is_long_option = true) { + std::string option_prefix = option_name + (is_long_option ? "=" : ""); + DCHECK(option.starts_with(option_prefix)); + const char* value_string = option.substr(option_prefix.size()).data(); + int64_t parsed_integer_value = 0; + if (!ParseInt(value_string, &parsed_integer_value)) { + Usage("Failed to parse %s '%s' as an integer", option_name.c_str(), value_string); + } + if (parsed_integer_value < 0) { + Usage("%s passed a negative value %d", option_name.c_str(), parsed_integer_value); + } + *out = dchecked_integral_cast<T>(parsed_integer_value); +} + +void ParseDouble(const std::string& option, + char after_char, + double min, + double max, + double* parsed_value, + UsageFn Usage); + } // namespace art #endif // ART_RUNTIME_UTILS_H_ diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index e1d4160aac..2db79ab229 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -665,20 +665,22 @@ bool MethodVerifier::Verify() { // Interfaces may always have static initializers for their fields. If we are running with // default methods enabled we also allow other public, static, non-final methods to have code. // Otherwise that is the only type of method allowed. - if (runtime->AreExperimentalFlagsEnabled(ExperimentalFlags::kDefaultMethods)) { - if (IsInstanceConstructor()) { - Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have non-static constructor"; - return false; - } else if (method_access_flags_ & kAccFinal) { - Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have final methods"; - return false; - } else if (!(method_access_flags_ & kAccPublic)) { - Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have non-public members"; + if (!(IsConstructor() && IsStatic())) { + if (runtime->AreExperimentalFlagsEnabled(ExperimentalFlags::kDefaultMethods)) { + if (IsInstanceConstructor()) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have non-static constructor"; + return false; + } else if (method_access_flags_ & kAccFinal) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have final methods"; + return false; + } else if (!(method_access_flags_ & kAccPublic)) { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have non-public members"; + return false; + } + } else { + Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interface methods must be abstract"; return false; } - } else if (!IsConstructor() || !IsStatic()) { - Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interface methods must be abstract"; - return false; } } @@ -3662,8 +3664,15 @@ ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess( << PrettyMethod(res_method); return nullptr; } - // Check that interface methods match interface classes. - if (klass->IsInterface() && method_type != METHOD_INTERFACE) { + // Check that interface methods are static or match interface classes. + // We only allow statics if we don't have default methods enabled. + Runtime* runtime = Runtime::Current(); + const bool default_methods_supported = + runtime == nullptr || + runtime->AreExperimentalFlagsEnabled(ExperimentalFlags::kDefaultMethods); + if (klass->IsInterface() && + method_type != METHOD_INTERFACE && + (!default_methods_supported || method_type != METHOD_STATIC)) { Fail(VERIFY_ERROR_CLASS_CHANGE) << "non-interface method " << PrettyMethod(res_method) << " is in an interface class " << PrettyClass(klass); return nullptr; diff --git a/test/004-ReferenceMap/stack_walk_refmap_jni.cc b/test/004-ReferenceMap/stack_walk_refmap_jni.cc index 34fb3f8a01..2dbd7e8126 100644 --- a/test/004-ReferenceMap/stack_walk_refmap_jni.cc +++ b/test/004-ReferenceMap/stack_walk_refmap_jni.cc @@ -49,7 +49,9 @@ struct ReferenceMap2Visitor : public CheckReferenceMapVisitor { if (m_name.compare("f") == 0) { CHECK_REGS_CONTAIN_REFS(0x03U, true, 8); // v8: this CHECK_REGS_CONTAIN_REFS(0x06U, true, 8, 1); // v8: this, v1: x - CHECK_REGS_CONTAIN_REFS(0x08U, true, 8, 3, 1); // v8: this, v3: y, v1: x + if (!GetCurrentOatQuickMethodHeader()->IsOptimized()) { + CHECK_REGS_CONTAIN_REFS(0x08U, true, 8, 3, 1); // v8: this, v3: y, v1: x + } CHECK_REGS_CONTAIN_REFS(0x0cU, true, 8, 3, 1); // v8: this, v3: y, v1: x if (!GetCurrentOatQuickMethodHeader()->IsOptimized()) { CHECK_REGS_CONTAIN_REFS(0x0eU, true, 8, 3, 1); // v8: this, v3: y, v1: x @@ -66,9 +68,10 @@ struct ReferenceMap2Visitor : public CheckReferenceMapVisitor { CHECK_REGS_CONTAIN_REFS(0x13U, false, 3); // v3: y // Note that v0: ex can be eliminated because it's a dead merge of two different exceptions. CHECK_REGS_CONTAIN_REFS(0x18U, true, 8, 2, 1); // v8: this, v2: y, v1: x (dead v0: ex) - CHECK_REGS_CONTAIN_REFS(0x1aU, true, 8, 5, 2, 1); // v8: this, v5: x[1], v2: y, v1: x (dead v0: ex) if (!GetCurrentOatQuickMethodHeader()->IsOptimized()) { // v8: this, v5: x[1], v2: y, v1: x (dead v0: ex) + CHECK_REGS_CONTAIN_REFS(0x1aU, true, 8, 5, 2, 1); + // v8: this, v5: x[1], v2: y, v1: x (dead v0: ex) CHECK_REGS_CONTAIN_REFS(0x1dU, true, 8, 5, 2, 1); // v5 is removed from the root set because there is a "merge" operation. // See 0015: if-nez v2, 001f. diff --git a/test/449-checker-bce/src/Main.java b/test/449-checker-bce/src/Main.java index 22829cddc8..ffeae7d9a2 100644 --- a/test/449-checker-bce/src/Main.java +++ b/test/449-checker-bce/src/Main.java @@ -624,12 +624,13 @@ public class Main { constantIndexing2(new int[3]); } catch (ArrayIndexOutOfBoundsException e) { assertIsManaged(); // This is to ensure that single-frame deoptimization works. - // Will need to be updated if constantIndexing2 is inlined. + // Will need to be updated if constantIndexing2 is inlined. try { // This will cause AIOOBE. constantIndexingForward6(new int[3]); } catch (ArrayIndexOutOfBoundsException e2) { - assertIsManaged(); + // Having deopted, we expect to be running interpreted at this point. + // Does not apply to debuggable, however, since we do not inline. return 99; } } diff --git a/test/538-checker-embed-constants/src/Main.java b/test/538-checker-embed-constants/src/Main.java index d8618e30fb..979c4c86c0 100644 --- a/test/538-checker-embed-constants/src/Main.java +++ b/test/538-checker-embed-constants/src/Main.java @@ -260,6 +260,29 @@ public class Main { return arg ^ 0xf00000000000000fL; } + /** + * Test that the `-1` constant is not synthesized in a register and that we + * instead simply switch between `add` and `sub` instructions with the + * constant embedded. + * We need two uses (or more) of the constant because the compiler always + * defers to immediate value handling to VIXL when it has only one use. + */ + + /// CHECK-START-ARM64: long Main.addM1(long) register (after) + /// CHECK: <<Arg:j\d+>> ParameterValue + /// CHECK: <<ConstM1:j\d+>> LongConstant -1 + /// CHECK-NOT: ParallelMove + /// CHECK: Add [<<Arg>>,<<ConstM1>>] + /// CHECK: Sub [<<Arg>>,<<ConstM1>>] + + /// CHECK-START-ARM64: long Main.addM1(long) disassembly (after) + /// CHECK: sub x{{\d+}}, x{{\d+}}, #0x1 + /// CHECK: add x{{\d+}}, x{{\d+}}, #0x1 + + public static long addM1(long arg) { + return (arg + (-1)) | (arg - (-1)); + } + public static void main(String[] args) { int arg = 0x87654321; assertIntEquals(and255(arg), 0x21); @@ -286,5 +309,7 @@ public class Main { assertLongEquals(xorNot15(longArg), 0xedcba987789abcd1L); assertLongEquals(xor0xfffffff00000000f(longArg), 0xedcba9888765432eL); assertLongEquals(xor0xf00000000000000f(longArg), 0xe23456788765432eL); + + assertLongEquals(14, addM1(7)); } } diff --git a/test/539-checker-arm64-encodable-immediates/info.txt b/test/539-checker-arm64-encodable-immediates/info.txt deleted file mode 100644 index efeef33231..0000000000 --- a/test/539-checker-arm64-encodable-immediates/info.txt +++ /dev/null @@ -1,2 +0,0 @@ -Basic tests that check the compiler recognizes when constant values can be -encoded in the immediate field of instructions. diff --git a/test/539-checker-arm64-encodable-immediates/src/Main.java b/test/539-checker-arm64-encodable-immediates/src/Main.java deleted file mode 100644 index 7e3ff9fde8..0000000000 --- a/test/539-checker-arm64-encodable-immediates/src/Main.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2015 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. - */ - - -public class Main { - - public static void assertLongEquals(long expected, long result) { - if (expected != result) { - throw new Error("Expected: " + expected + ", found: " + result); - } - } - - /** - * Test that the `-1` constant is not synthesized in a register and that we - * instead simply switch between `add` and `sub` instructions with the - * constant embedded. - * We need two uses (or more) of the constant because the compiler always - * delegates the immediate value handling to VIXL when there is only one use. - */ - - /// CHECK-START-ARM64: long Main.addM1(long) register (after) - /// CHECK: <<Arg:j\d+>> ParameterValue - /// CHECK: <<ConstM1:j\d+>> LongConstant -1 - /// CHECK-NOT: ParallelMove - /// CHECK: Add [<<Arg>>,<<ConstM1>>] - /// CHECK: Sub [<<Arg>>,<<ConstM1>>] - - /// CHECK-START-ARM64: long Main.addM1(long) disassembly (after) - /// CHECK: sub x{{\d+}}, x{{\d+}}, #0x1 - /// CHECK: add x{{\d+}}, x{{\d+}}, #0x1 - - public static long addM1(long arg) { - return (arg + (-1)) | (arg - (-1)); - } - - public static void main(String[] args) { - assertLongEquals(14, addM1(7)); - } -} diff --git a/test/539-checker-arm64-encodable-immediates/expected.txt b/test/541-regression-inlined-deopt/expected.txt index e69de29bb2..e69de29bb2 100644 --- a/test/539-checker-arm64-encodable-immediates/expected.txt +++ b/test/541-regression-inlined-deopt/expected.txt diff --git a/test/541-regression-inlined-deopt/info.txt b/test/541-regression-inlined-deopt/info.txt new file mode 100644 index 0000000000..209588fe05 --- /dev/null +++ b/test/541-regression-inlined-deopt/info.txt @@ -0,0 +1,4 @@ +Regression test for deopt from optimized code which would use the top-level +stack map for deopting inlined frames. Test case is written in smali for full +control over vregs because the previous test 449 would pass because the vreg +maps at the various inlining depths were similar. diff --git a/test/541-regression-inlined-deopt/smali/TestCase.smali b/test/541-regression-inlined-deopt/smali/TestCase.smali new file mode 100644 index 0000000000..a109775dfe --- /dev/null +++ b/test/541-regression-inlined-deopt/smali/TestCase.smali @@ -0,0 +1,55 @@ +# Copyright (C) 2015 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. + +.class public LTestCase; +.super Ljava/lang/Object; + +.method private static $inline$depth1([I)V + .registers 3 + + # Expects array in v2. + + const v0, 0x0 + + const v1, 0x3 + aput v0, p0, v1 + + const v1, 0x4 + aput v0, p0, v1 + + return-void +.end method + +.method private static $inline$depth0([I)V + .registers 1 + + # Expects array in v0. + + invoke-static {p0}, LTestCase;->$inline$depth1([I)V + return-void +.end method + +.method public static foo()V + .registers 10 + + # Create a new array short enough to throw AIOOB in $inline$depth1. + # Make sure the reference is not stored in the same vreg as used by + # the inlined methods. + + const v5, 0x3 + new-array v6, v5, [I + + invoke-static {v6}, LTestCase;->$inline$depth0([I)V + return-void +.end method diff --git a/test/541-regression-inlined-deopt/src/Main.java b/test/541-regression-inlined-deopt/src/Main.java new file mode 100644 index 0000000000..fa79590e65 --- /dev/null +++ b/test/541-regression-inlined-deopt/src/Main.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 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. + */ + +import java.lang.reflect.*; + +public class Main { + + // Workaround for b/18051191. + class InnerClass {} + + public static void main(String[] args) throws Throwable { + try { + Class<?> c = Class.forName("TestCase"); + Method m = c.getMethod("foo"); + m.invoke(null, (Object[]) null); + } catch (InvocationTargetException ex) { + // Code should have thrown AIOOB. + if (!(ex.getCause() instanceof ArrayIndexOutOfBoundsException)) { + throw ex; + } + } + } +} diff --git a/test/542-inline-trycatch/expected.txt b/test/542-inline-trycatch/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/542-inline-trycatch/expected.txt diff --git a/test/542-inline-trycatch/info.txt b/test/542-inline-trycatch/info.txt new file mode 100644 index 0000000000..b3e50d3d61 --- /dev/null +++ b/test/542-inline-trycatch/info.txt @@ -0,0 +1 @@ +Tests inlining in the optimizing compiler under try/catch.
\ No newline at end of file diff --git a/test/542-inline-trycatch/src/Main.java b/test/542-inline-trycatch/src/Main.java new file mode 100644 index 0000000000..5a6e06fa0c --- /dev/null +++ b/test/542-inline-trycatch/src/Main.java @@ -0,0 +1,178 @@ +/* + * 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. + */ + +public class Main { + + // The following tests make sure that we inline methods used inside try and catch + // blocks, provided they meet other inlining criteria. To do that, we rely on + // the compiler recognizing and enforcing the $inline$ and $noinline$ markers. + + // We expect a single block to always be inlined. + + private static int $inline$SingleBlock(String str) throws NumberFormatException { + return Integer.parseInt(str); + } + + // We expect a "simple" method with multiple blocks to always be inlined. + + private static int $inline$MultipleBlocks(String str, boolean is_hex) + throws NumberFormatException { + return is_hex ? Integer.parseInt(str, 16) : Integer.parseInt(str); + } + + // We expect methods with try/catch to not be inlined. Inlined try/catch + // blocks are not supported at the moment. + + private static int $noinline$TryCatch(String str) { + try { + return Integer.parseInt(str); + } catch (NumberFormatException ex) { + return -1; + } + } + + public static void testSingleBlockFromTry() { + int val = 0; + + try { + val = $inline$SingleBlock("42"); + } catch (NumberFormatException ex) { + unreachable(); + } + assertEquals(42, val); + + try { + $inline$SingleBlock("xyz"); + unreachable(); + } catch (NumberFormatException ex) {} + } + + public static void testSingleBlockFromCatch() { + int val = 0; + + try { + throwException(); + } catch (Exception ex) { + val = $inline$SingleBlock("42"); + } + assertEquals(42, val); + } + + public static void testMultipleBlocksFromTry() { + int val = 0; + + try { + val = $inline$MultipleBlocks("42", false); + } catch (NumberFormatException ex) { + unreachable(); + } + assertEquals(42, val); + + try { + val = $inline$MultipleBlocks("20", true); + } catch (NumberFormatException ex) { + unreachable(); + } + assertEquals(32, val); + + try { + $inline$MultipleBlocks("xyz", false); + unreachable(); + } catch (NumberFormatException ex) {} + + try { + $inline$MultipleBlocks("xyz", true); + unreachable(); + } catch (NumberFormatException ex) {} + } + + public static void testMultipleBlocksFromCatch() { + int val = 0; + + try { + throwException(); + } catch (Exception ex) { + val = $inline$MultipleBlocks("42", false); + } + assertEquals(42, val); + + try { + throwException(); + } catch (Exception ex) { + val = $inline$MultipleBlocks("20", true); + } + assertEquals(32, val); + } + + public static void testTryCatchFromTry() { + int val = 0; + + try { + val = $noinline$TryCatch("42"); + } catch (NumberFormatException ex) { + unreachable(); + } + assertEquals(42, val); + + try { + val = $noinline$TryCatch("xyz"); + } catch (NumberFormatException ex) { + unreachable(); + } + assertEquals(-1, val); + } + + public static void testTryCatchFromCatch() { + int val = 0; + + try { + throwException(); + } catch (Exception ex) { + val = $noinline$TryCatch("42"); + } + assertEquals(42, val); + + try { + throwException(); + } catch (Exception ex) { + val = $noinline$TryCatch("xyz"); + } + assertEquals(-1, val); + } + + public static void main(String[] args) { + testSingleBlockFromTry(); + testSingleBlockFromCatch(); + testMultipleBlocksFromTry(); + testMultipleBlocksFromCatch(); + testTryCatchFromTry(); + testTryCatchFromCatch(); + } + + private static void assertEquals(int expected, int actual) { + if (expected != actual) { + throw new AssertionError("Wrong result: " + expected + " != " + actual); + } + } + + private static void unreachable() { + throw new Error("Unreachable"); + } + + private static void throwException() throws Exception { + throw new Exception(); + } +} diff --git a/test/542-unresolved-access-check/expected.txt b/test/542-unresolved-access-check/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/542-unresolved-access-check/expected.txt diff --git a/test/542-unresolved-access-check/info.txt b/test/542-unresolved-access-check/info.txt new file mode 100644 index 0000000000..30d45b8ec3 --- /dev/null +++ b/test/542-unresolved-access-check/info.txt @@ -0,0 +1 @@ +Test unresolved/access checks entry points with the JIT. diff --git a/test/542-unresolved-access-check/src/Main.java b/test/542-unresolved-access-check/src/Main.java new file mode 100644 index 0000000000..2bdf47f172 --- /dev/null +++ b/test/542-unresolved-access-check/src/Main.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 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. + */ + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import p1.InP1; +import p1.PlaceHolder; + + +// Custom class loader to prevent loading while verifying. +class MyClassLoader extends ClassLoader { + MyClassLoader() throws Exception { + super(MyClassLoader.class.getClassLoader()); + + // Some magic to get access to the pathList field of BaseDexClassLoader. + ClassLoader loader = getClass().getClassLoader(); + Class<?> baseDexClassLoader = loader.getClass().getSuperclass(); + Field f = baseDexClassLoader.getDeclaredField("pathList"); + f.setAccessible(true); + Object pathList = f.get(loader); + + // Some magic to get access to the dexField field of pathList. + f = pathList.getClass().getDeclaredField("dexElements"); + f.setAccessible(true); + dexElements = (Object[]) f.get(pathList); + dexFileField = dexElements[0].getClass().getDeclaredField("dexFile"); + dexFileField.setAccessible(true); + } + + Object[] dexElements; + Field dexFileField; + + protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { + if (className.equals("p1.OtherInP1") && !p1.PlaceHolder.entered) { + // The request comes from the verifier. Return null to get the access check entry + // point in the compiled code. + return null; + } + // Mimic what DexPathList.findClass is doing. + try { + for (Object element : dexElements) { + Object dex = dexFileField.get(element); + Method method = dex.getClass().getDeclaredMethod( + "loadClassBinaryName", String.class, ClassLoader.class, List.class); + + if (dex != null) { + Class clazz = (Class)method.invoke(dex, className, this, null); + if (clazz != null) { + return clazz; + } + } + } + } catch (Exception e) { /* Ignore */ } + return getParent().loadClass(className); + } +} + +public class Main { + public static void main(String[] args) throws Exception { + MyClassLoader o = new MyClassLoader(); + Class foo = o.loadClass("LoadedByMyClassLoader"); + Method m = foo.getDeclaredMethod("main"); + m.invoke(null); + } +} + +class LoadedByMyClassLoader { + public static void main() throws Exception { + for (int i = 0; i < 10000; ++i) { + // Warm up the JIT. + doTheCall(i); + } + // Sleep a while to let the JIT compile things. + // TODO(ngeoffray): Remove the sleep. b/25414532 + Thread.sleep(2000); + doTheCall(10001); + } + + public static void doTheCall(int i) { + InP1.$inline$AllocateOtherInP1(i); + InP1.$inline$AllocateArrayOtherInP1(i); + InP1.$inline$UseStaticFieldOtherInP1(i); + InP1.$inline$SetStaticFieldOtherInP1(i); + InP1.$inline$UseInstanceFieldOtherInP1(i); + InP1.$inline$SetInstanceFieldOtherInP1(i); + InP1.$inline$LoadOtherInP1(i); + InP1.$inline$StaticCallOtherInP1(i); + InP1.$inline$InstanceCallOtherInP1(i); + } +} diff --git a/test/542-unresolved-access-check/src/p1/InP1.java b/test/542-unresolved-access-check/src/p1/InP1.java new file mode 100644 index 0000000000..3516c7231b --- /dev/null +++ b/test/542-unresolved-access-check/src/p1/InP1.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 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. + */ + +package p1; + +public class InP1 { + public static Object $inline$AllocateOtherInP1(int i) { + // Let this method execute a while to make sure the JIT sees it hot. + if (i <= 10000) { + return null; + } + // Set the flag that we have entered InP1 code to get OtherInP1 loaded. + PlaceHolder.entered = true; + return new OtherInP1(); + } + + public static Object $inline$AllocateArrayOtherInP1(int i) { + if (i <= 10000) { + return null; + } + return new OtherInP1[10]; + } + + public static Object $inline$UseStaticFieldOtherInP1(int i) { + if (i <= 10000) { + return null; + } + return OtherInP1.staticField; + } + + public static void $inline$SetStaticFieldOtherInP1(int i) { + if (i <= 10000) { + return; + } + OtherInP1.staticField = new Object(); + } + + public static Object $inline$UseInstanceFieldOtherInP1(int i) { + if (i <= 10000) { + return null; + } + return $noinline$AllocateOtherInP1().instanceField; + } + + public static void $inline$SetInstanceFieldOtherInP1(int i) { + if (i <= 10000) { + return; + } + $noinline$AllocateOtherInP1().instanceField = new Object(); + } + + public static OtherInP1 $noinline$AllocateOtherInP1() { + try { + return new OtherInP1(); + } catch (Exception e) { + throw new Error(e); + } + } + + public static Object $inline$LoadOtherInP1(int i) { + if (i <= 10000) { + return null; + } + return OtherInP1.class; + } + + public static Object $inline$StaticCallOtherInP1(int i) { + if (i <= 10000) { + return null; + } + return OtherInP1.doTheStaticCall(); + } + + public static Object $inline$InstanceCallOtherInP1(int i) { + if (i <= 10000) { + return null; + } + return $noinline$AllocateOtherInP1().doTheInstanceCall(); + } +} diff --git a/test/542-unresolved-access-check/src/p1/OtherInP1.java b/test/542-unresolved-access-check/src/p1/OtherInP1.java new file mode 100644 index 0000000000..adc1ce1297 --- /dev/null +++ b/test/542-unresolved-access-check/src/p1/OtherInP1.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2015 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. + */ + +package p1; + +class OtherInP1 { + OtherInP1() { + } + static Object staticField = new Object(); + Object instanceField = new Object(); + + static Object doTheStaticCall() { + return null; + } + + Object doTheInstanceCall() { + return null; + } +} diff --git a/test/542-unresolved-access-check/src/p1/PlaceHolder.java b/test/542-unresolved-access-check/src/p1/PlaceHolder.java new file mode 100644 index 0000000000..2bf4bdf15f --- /dev/null +++ b/test/542-unresolved-access-check/src/p1/PlaceHolder.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015 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. + */ + +package p1; + +// Specific class for putting the 'entered' marker. If we were to put the marker +// in InP1 or in OtherInP1, the code in MyClassLoader using that marker would load +// InP1 or OtherInP1 in the system class loader, and not in MyClassLoader. +public class PlaceHolder { + public static boolean entered = false; +} diff --git a/test/955-lambda-smali/run b/test/955-lambda-smali/run index b7546801b9..2fb2f89f6b 100755 --- a/test/955-lambda-smali/run +++ b/test/955-lambda-smali/run @@ -15,4 +15,4 @@ # limitations under the License. # Ensure that the lambda experimental opcodes are turned on for dalvikvm and dex2oat -${RUN} "$@" --runtime-option -Xexperimental:lambdas -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:lambdas +${RUN} "$@" --experimental lambdas diff --git a/test/960-default-smali/build b/test/960-default-smali/build index c7866878e9..3946de3787 100755 --- a/test/960-default-smali/build +++ b/test/960-default-smali/build @@ -18,16 +18,25 @@ set -e # Generate the smali Main.smali file or fail -./util-src/generate_smali.py ./smali +${ANDROID_BUILD_TOP}/art/test/utils/python/generate_smali_main.py ./smali -if [[ $@ == *"--jvm"* ]]; then - # Build the Java files if we are running a --jvm test +USES_JAVA="false" +if [[ $ARGS == *"--jvm"* ]]; then + USES_JAVA="true" +elif [[ "$USE_JACK" == "true" ]]; then + if $JACK -D jack.java.source.version=1.8 >& /dev/null; then + USES_JAVA="true" + else + echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 + fi +fi + +if [[ "$USES_JAVA" == "true" ]]; then + # We are compiling java code, create it. mkdir -p src - mkdir -p classes ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src - ${JAVAC} -implicit:none -d classes $(find src -name '*.java') + # Ignore the smali directory. + EXTRA_ARGS="--no-smali" fi -# Build the smali files and make a dex -${SMALI} -JXmx256m --experimental --api-level 23 --output classes.dex $(find smali -name '*.smali') -zip "$TEST_NAME.jar" classes.dex +./default-build "$@" "$EXTRA_ARGS" --experimental default-methods diff --git a/test/960-default-smali/info.txt b/test/960-default-smali/info.txt index eb596e2c9f..9583abbdc1 100644 --- a/test/960-default-smali/info.txt +++ b/test/960-default-smali/info.txt @@ -2,15 +2,16 @@ Smali-based tests for experimental interface default methods. Obviously needs to run under ART or a Java 8 Language runtime and compiler. -When run a Main.smali file will be generated by the util-src/generate_smali.py -script. If we run with --jvm we will use the tools/extract-embedded-java script to -turn the smali into equivalent Java using the embedded Java code. +When run a Main.smali file will be generated by the +test/utils/python/generate_smali_main.py script. If we run with --jvm we will +use the tools/extract-embedded-java script to turn the smali into equivalent +Java using the embedded Java code. When updating be sure to write the equivalent Java code in comments of the smali files. -Care should be taken when updating the generate_smali.py script. It must always -return equivalent output when run multiple times. +Care should be taken when updating the generate_smali_main.py script. It must +always return equivalent output when run multiple times. To update the test files do the following steps: <Add new classes/interfaces> diff --git a/test/960-default-smali/run b/test/960-default-smali/run index e378b061d9..22f68006e7 100755 --- a/test/960-default-smali/run +++ b/test/960-default-smali/run @@ -14,8 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -if echo $@ | grep -q -- "--jvm"; then - ${RUN} "$@" -else - ${RUN} "$@" --runtime-option -Xexperimental:default-methods -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:default-methods -fi +${RUN} --experimental default-methods "$@" diff --git a/test/961-default-iface-resolution-generated/build b/test/961-default-iface-resolution-generated/build index 707c17e1cf..03cc62459a 100755 --- a/test/961-default-iface-resolution-generated/build +++ b/test/961-default-iface-resolution-generated/build @@ -17,8 +17,6 @@ # make us exit on a failure set -e -mkdir -p ./smali - # We will be making more files than the ulimit is set to allow. Remove it temporarily. OLD_ULIMIT=`ulimit -S` ulimit -S unlimited @@ -28,20 +26,31 @@ restore_ulimit() { } trap 'restore_ulimit' ERR +mkdir -p ./smali + # Generate the smali files and expected.txt or fail ./util-src/generate_smali.py ./smali ./expected.txt -if [[ $@ == *"--jvm"* ]]; then - # Build the Java files if we are running a --jvm test +USES_JAVA="false" +if [[ $ARGS == *"--jvm"* ]]; then + USES_JAVA="true" +elif [[ $USE_JACK == "true" ]]; then + if "$JACK" -D jack.java.source.version=1.8 >& /dev/null; then + USES_JAVA="true" + else + echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 + fi +fi + +if [[ "$USES_JAVA" == "true" ]]; then + # We are compiling java code, create it. mkdir -p src - mkdir -p classes ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src - ${JAVAC} -implicit:none -d classes $(find src -name '*.java') + # Ignore the smali directory. + EXTRA_ARGS="--no-smali" fi -# Build the smali files and make a dex -${SMALI} -JXmx512m --experimental --api-level 23 --output classes.dex $(find smali -name '*.smali') -zip $TEST_NAME.jar classes.dex +./default-build "$@" "$EXTRA_ARGS" --experimental default-methods # Reset the ulimit back to its initial value restore_ulimit diff --git a/test/961-default-iface-resolution-generated/run b/test/961-default-iface-resolution-generated/run index e378b061d9..22f68006e7 100755 --- a/test/961-default-iface-resolution-generated/run +++ b/test/961-default-iface-resolution-generated/run @@ -14,8 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -if echo $@ | grep -q -- "--jvm"; then - ${RUN} "$@" -else - ${RUN} "$@" --runtime-option -Xexperimental:default-methods -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:default-methods -fi +${RUN} --experimental default-methods "$@" diff --git a/test/962-iface-static/build b/test/962-iface-static/build index 5ad82f70d1..24e2feb228 100755 --- a/test/962-iface-static/build +++ b/test/962-iface-static/build @@ -17,14 +17,23 @@ # make us exit on a failure set -e +USES_JAVA="false" if [[ $@ == *"--jvm"* ]]; then - # Build the Java files if we are running a --jvm test + USES_JAVA="true" +elif [[ "$USE_JACK" == "true" ]]; then + if $JACK -D jack.java.source.version=1.8 2>/dev/null; then + USES_JAVA="true" + else + echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 + fi +fi + +if [[ "$USES_JAVA" == "true" ]]; then + # We are compiling java code, create it. mkdir -p src - mkdir -p classes ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src - ${JAVAC} -implicit:none -d classes $(find src -name '*.java') + # Ignore the smali directory. + EXTRA_ARGS="--no-smali" fi -# Build the smali files and make a dex -${SMALI} -JXmx512m --experimental --api-level 23 --output classes.dex $(find smali -name '*.smali') -zip $TEST_NAME.jar classes.dex +./default-build "$@" "$EXTRA_ARGS" --experimental default-methods diff --git a/test/962-iface-static/run b/test/962-iface-static/run index e713708c18..d37737f3da 100755 --- a/test/962-iface-static/run +++ b/test/962-iface-static/run @@ -14,8 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -if echo $@ | grep -q -- "--jvm"; then - ${RUN} "$@" -else - ${RUN} "$@" --runtime-option -Xexperimental:default-methods -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:default-methods -fi +${RUN} --experimental default-methods "$@" diff --git a/test/962-iface-static/smali/Displayer.smali b/test/962-iface-static/smali/Displayer.smali index 06bec16432..ed4c013d3b 100644 --- a/test/962-iface-static/smali/Displayer.smali +++ b/test/962-iface-static/smali/Displayer.smali @@ -27,7 +27,7 @@ .class public LDisplayer; .super Ljava/lang/Object; -.method public static <clinit>()V +.method static constructor <clinit>()V .locals 3 sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; const-string v0, "init" diff --git a/test/962-iface-static/smali/iface.smali b/test/962-iface-static/smali/iface.smali index 441aae669e..5b9c03ec46 100644 --- a/test/962-iface-static/smali/iface.smali +++ b/test/962-iface-static/smali/iface.smali @@ -27,7 +27,7 @@ .field public final static f:LDisplayer; -.method public static <clinit>()V +.method static constructor <clinit>()V .locals 3 new-instance v1, LDisplayer; invoke-direct {v1}, LDisplayer;-><init>()V diff --git a/test/963-default-range-smali/build b/test/963-default-range-smali/build index 5ad82f70d1..24e2feb228 100755 --- a/test/963-default-range-smali/build +++ b/test/963-default-range-smali/build @@ -17,14 +17,23 @@ # make us exit on a failure set -e +USES_JAVA="false" if [[ $@ == *"--jvm"* ]]; then - # Build the Java files if we are running a --jvm test + USES_JAVA="true" +elif [[ "$USE_JACK" == "true" ]]; then + if $JACK -D jack.java.source.version=1.8 2>/dev/null; then + USES_JAVA="true" + else + echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 + fi +fi + +if [[ "$USES_JAVA" == "true" ]]; then + # We are compiling java code, create it. mkdir -p src - mkdir -p classes ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src - ${JAVAC} -implicit:none -d classes $(find src -name '*.java') + # Ignore the smali directory. + EXTRA_ARGS="--no-smali" fi -# Build the smali files and make a dex -${SMALI} -JXmx512m --experimental --api-level 23 --output classes.dex $(find smali -name '*.smali') -zip $TEST_NAME.jar classes.dex +./default-build "$@" "$EXTRA_ARGS" --experimental default-methods diff --git a/test/963-default-range-smali/run b/test/963-default-range-smali/run index e713708c18..d37737f3da 100755 --- a/test/963-default-range-smali/run +++ b/test/963-default-range-smali/run @@ -14,8 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -if echo $@ | grep -q -- "--jvm"; then - ${RUN} "$@" -else - ${RUN} "$@" --runtime-option -Xexperimental:default-methods -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:default-methods -fi +${RUN} --experimental default-methods "$@" diff --git a/test/964-default-iface-init-generated/build b/test/964-default-iface-init-generated/build index deef803813..d916f1b8e9 100755 --- a/test/964-default-iface-init-generated/build +++ b/test/964-default-iface-init-generated/build @@ -29,17 +29,26 @@ trap 'restore_ulimit' ERR # Generate the smali files and expected.txt or fail ./util-src/generate_smali.py ./smali ./expected.txt +USES_JAVA="false" if [[ $@ == *"--jvm"* ]]; then - # Build the Java files if we are running a --jvm test + USES_JAVA="true" +elif [[ "$USE_JACK" == "true" ]]; then + if $JACK -D jack.java.source.version=1.8 2>/dev/null; then + USES_JAVA="true" + else + echo "WARNING: Cannot use jack because it does not support JLS 1.8. Falling back to smali" >&2 + fi +fi + +if [[ "$USES_JAVA" == "true" ]]; then + # We are compiling java code, create it. mkdir -p src - mkdir -p classes ${ANDROID_BUILD_TOP}/art/tools/extract-embedded-java ./smali ./src - ${JAVAC} -implicit:none -d classes $(find src -name '*.java') + # Ignore the smali directory. + EXTRA_ARGS="--no-smali" fi -# Build the smali files and make a dex -${SMALI} -JXmx512m --experimental --api-level 23 --output classes.dex $(find smali -name '*.smali') -zip $TEST_NAME.jar classes.dex +./default-build "$@" "$EXTRA_ARGS" --experimental default-methods # Reset the ulimit back to its initial value restore_ulimit diff --git a/test/964-default-iface-init-generated/run b/test/964-default-iface-init-generated/run index e378b061d9..22f68006e7 100755 --- a/test/964-default-iface-init-generated/run +++ b/test/964-default-iface-init-generated/run @@ -14,8 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -if echo $@ | grep -q -- "--jvm"; then - ${RUN} "$@" -else - ${RUN} "$@" --runtime-option -Xexperimental:default-methods -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:default-methods -fi +${RUN} --experimental default-methods "$@" diff --git a/test/964-default-iface-init-generated/util-src/generate_smali.py b/test/964-default-iface-init-generated/util-src/generate_smali.py index be2d3ba563..3c138abf26 100755 --- a/test/964-default-iface-init-generated/util-src/generate_smali.py +++ b/test/964-default-iface-init-generated/util-src/generate_smali.py @@ -334,7 +334,7 @@ class TestInterface(mixins.DumpMixin, mixins.Named, mixins.NameComparableMixin, # public static final Displayer field = new Displayer("{tree}"); .field public final static field:LDisplayer; -.method public static constructor <clinit>()V +.method static constructor <clinit>()V .locals 3 const-string v2, "{tree}" new-instance v1, LDisplayer; diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index 5bbbbc1f51..6ce3d9472c 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -441,11 +441,8 @@ TEST_ART_BROKEN_DEFAULT_RUN_TESTS := # Known broken tests for the mips32 optimizing compiler backend. TEST_ART_BROKEN_OPTIMIZING_MIPS_RUN_TESTS := \ 441-checker-inliner \ - 449-checker-bce \ 510-checker-try-catch \ 521-checker-array-set-null \ - 529-checker-unresolved \ - 534-checker-bce-deoptimization \ 536-checker-intrinsic-optimization \ ifeq (mips,$(TARGET_ARCH)) diff --git a/test/Lookup/A.java b/test/Lookup/A.java new file mode 100644 index 0000000000..666ba181d9 --- /dev/null +++ b/test/Lookup/A.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2011 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. + */ + +class A {} diff --git a/test/Lookup/AB.java b/test/Lookup/AB.java new file mode 100644 index 0000000000..b231708111 --- /dev/null +++ b/test/Lookup/AB.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2011 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. + */ + +class AB {} diff --git a/test/Lookup/C.java b/test/Lookup/C.java new file mode 100644 index 0000000000..5b90069262 --- /dev/null +++ b/test/Lookup/C.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2011 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. + */ + +class C {} diff --git a/test/etc/default-build b/test/etc/default-build index c92402b529..7242428f1e 100755 --- a/test/etc/default-build +++ b/test/etc/default-build @@ -17,8 +17,45 @@ # Stop if something fails. set -e +# Set default values for directories. +if [ -d smali ]; then + HAS_SMALI=true +else + HAS_SMALI=false +fi + +if [ -d src ]; then + HAS_SRC=true +else + HAS_SRC=false +fi + +if [ -d src2 ]; then + HAS_SRC2=true +else + HAS_SRC2=false +fi + +if [ -d src-multidex ]; then + HAS_SRC_MULTIDEX=true +else + HAS_SRC_MULTIDEX=false +fi + +if [ -d src-ex ]; then + HAS_SRC_EX=true +else + HAS_SRC_EX=false +fi + DX_FLAGS="" SKIP_DX_MERGER="false" +EXPERIMENTAL="" + +# Setup experimental flag mappings in a bash associative array. +declare -A JACK_EXPERIMENTAL_ARGS +JACK_EXPERIMENTAL_ARGS["default-methods"]="-D jack.java.source.version=1.8" +JACK_EXPERIMENTAL_ARGS["lambdas"]="-D jack.java.source.version=1.8" while true; do if [ "x$1" = "x--dx-option" ]; then @@ -28,6 +65,25 @@ while true; do shift elif [ "x$1" = "x--jvm" ]; then shift + elif [ "x$1" = "x--no-src" ]; then + HAS_SRC=false + shift + elif [ "x$1" = "x--no-src2" ]; then + HAS_SRC2=false + shift + elif [ "x$1" = "x--no-src-multidex" ]; then + HAS_SRC_MULTIDEX=false + shift + elif [ "x$1" = "x--no-src-ex" ]; then + HAS_SRC_EX=false + shift + elif [ "x$1" = "x--no-smali" ]; then + HAS_SMALI=false + shift + elif [ "x$1" = "x--experimental" ]; then + shift + EXPERIMENTAL="${EXPERIMENTAL} $1" + shift elif expr "x$1" : "x--" >/dev/null 2>&1; then echo "unknown $0 option: $1" 1>&2 exit 1 @@ -36,17 +92,22 @@ while true; do fi done +# Add args from the experimental mappings. +for experiment in ${EXPERIMENTAL}; do + JACK_ARGS="${JACK_ARGS} ${JACK_EXPERIMENTAL_ARGS[${experiment}]}" +done + if [ -e classes.dex ]; then zip $TEST_NAME.jar classes.dex exit 0 fi -if ! [ -d src ] && ! [ -d src2 ]; then +if ! [ "${HAS_SRC}" = "true" ] && ! [ "${HAS_SRC2}" = "true" ]; then # No src directory? Then forget about trying to run dx. SKIP_DX_MERGER="true" fi -if [ -d src-multidex ]; then +if [ "${HAS_SRC_MULTIDEX}" = "true" ]; then # Jack does not support this configuration unless we specify how to partition the DEX file # with a .jpp file. USE_JACK="false" @@ -54,27 +115,29 @@ fi if [ ${USE_JACK} = "true" ]; then # Jack toolchain - if [ -d src ]; then - ${JACK} --output-jack src.jack src + if [ "${HAS_SRC}" = "true" ]; then + ${JACK} ${JACK_ARGS} --output-jack src.jack src imported_jack_files="--import src.jack" fi - if [ -d src2 ]; then - ${JACK} --output-jack src2.jack src2 + if [ "${HAS_SRC2}" = "true" ]; then + ${JACK} ${JACK_ARGS} --output-jack src2.jack src2 imported_jack_files="--import src2.jack ${imported_jack_files}" fi # Compile jack files into a DEX file. We set jack.import.type.policy=keep-first to consider # class definitions from src2 first. - ${JACK} ${imported_jack_files} -D jack.import.type.policy=keep-first --output-dex . + if [ "${HAS_SRC}" = "true" ] || [ "${HAS_SRC2}" = "true" ]; then + ${JACK} ${JACK_ARGS} ${imported_jack_files} -D jack.import.type.policy=keep-first --output-dex . + fi else # Legacy toolchain with javac+dx - if [ -d src ]; then + if [ "${HAS_SRC}" = "true" ]; then mkdir classes - ${JAVAC} -implicit:none -classpath src-multidex -d classes `find src -name '*.java'` + ${JAVAC} ${JAVAC_ARGS} -implicit:none -classpath src-multidex -d classes `find src -name '*.java'` fi - if [ -d src-multidex ]; then + if [ "${HAS_SRC_MULTIDEX}" = "true" ]; then mkdir classes2 ${JAVAC} -implicit:none -classpath src -d classes2 `find src-multidex -name '*.java'` if [ ${NEED_DEX} = "true" ]; then @@ -83,20 +146,22 @@ else fi fi - if [ -d src2 ]; then + if [ "${HAS_SRC2}" = "true" ]; then mkdir -p classes - ${JAVAC} -d classes `find src2 -name '*.java'` + ${JAVAC} ${JAVAC_ARGS} -d classes `find src2 -name '*.java'` fi - if [ ${NEED_DEX} = "true" -a ${SKIP_DX_MERGER} = "false" ]; then - ${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex \ - --dump-width=1000 ${DX_FLAGS} classes + if [ "${HAS_SRC}" = "true" ] || [ "${HAS_SRC2}" = "true" ]; then + if [ ${NEED_DEX} = "true" -a ${SKIP_DX_MERGER} = "false" ]; then + ${DX} -JXmx256m --debug --dex --dump-to=classes.lst --output=classes.dex \ + --dump-width=1000 ${DX_FLAGS} classes + fi fi fi -if [ -d smali ]; then +if [ "${HAS_SMALI}" = "true" ]; then # Compile Smali classes - ${SMALI} -JXmx256m --experimental --api-level 23 --output smali_classes.dex `find smali -name '*.smali'` + ${SMALI} -JXmx512m ${SMALI_ARGS} --output smali_classes.dex `find smali -name '*.smali'` # Don't bother with dexmerger if we provide our own main function in a smali file. if [ ${SKIP_DX_MERGER} = "false" ]; then @@ -106,18 +171,18 @@ if [ -d smali ]; then fi fi -if [ -d src-ex ]; then +if [ ${HAS_SRC_EX} = "true" ]; then if [ ${USE_JACK} = "true" ]; then # Rename previous "classes.dex" so it is not overwritten. mv classes.dex classes-1.dex #TODO find another way to append src.jack to the jack classpath - ${JACK}:src.jack --output-dex . src-ex + ${JACK}:src.jack ${JACK_ARGS} --output-dex . src-ex zip $TEST_NAME-ex.jar classes.dex # Restore previous "classes.dex" so it can be zipped. mv classes-1.dex classes.dex else mkdir classes-ex - ${JAVAC} -d classes-ex -cp classes `find src-ex -name '*.java'` + ${JAVAC} ${JAVAC_ARGS} -d classes-ex -cp classes `find src-ex -name '*.java'` if [ ${NEED_DEX} = "true" ]; then ${DX} -JXmx256m --debug --dex --dump-to=classes-ex.lst --output=classes-ex.dex \ --dump-width=1000 ${DX_FLAGS} classes-ex @@ -133,7 +198,7 @@ if [ -d src-ex ]; then fi # Create a single jar with two dex files for multidex. -if [ -d src-multidex ]; then +if [ ${HAS_SRC_MULTIDEX} = "true" ]; then zip $TEST_NAME.jar classes.dex classes2.dex elif [ ${NEED_DEX} = "true" ]; then zip $TEST_NAME.jar classes.dex diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index fbefa073c4..18867fd035 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -18,6 +18,7 @@ DALVIKVM="dalvikvm32" DEBUGGER="n" DEV_MODE="n" DEX2OAT="" +EXPERIMENTAL="" FALSE_BIN="/system/bin/false" FLAGS="" GDB="" @@ -196,6 +197,13 @@ while true; do FLAGS="${FLAGS} -Xcompiler-option --compile-pic" COMPILE_FLAGS="${COMPILE_FLAGS} --compile-pic" shift + elif [ "x$1" = "x--experimental" ]; then + if [ "$#" -lt 2 ]; then + echo "missing --experimental option" 1>&2 + exit 1 + fi + EXPERIMENTAL="$EXPERIMENTAL $2" + shift 2 elif expr "x$1" : "x--" >/dev/null 2>&1; then echo "unknown $0 option: $1" 1>&2 exit 1 @@ -204,6 +212,13 @@ while true; do fi done +if [ "$USE_JVM" = "n" ]; then + for feature in ${EXPERIMENTAL}; do + FLAGS="${FLAGS} -Xexperimental:${feature} -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:${feature}" + COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xexperimental:${feature}" + done +fi + if [ "x$1" = "x" ] ; then MAIN="Main" else diff --git a/test/run-all-tests b/test/run-all-tests index 76283b7a8d..6d5c28c7e0 100755 --- a/test/run-all-tests +++ b/test/run-all-tests @@ -44,12 +44,45 @@ while true; do elif [ "x$1" = "x--use-java-home" ]; then run_args="${run_args} --use-java-home" shift + elif [ "x$1" = "x--no-image" ]; then + run_args="${run_args} --no-image" + shift + elif [ "x$1" = "x--quick" ]; then + run_args="${run_args} --quick" + shift + elif [ "x$1" = "x--optimizing" ]; then + run_args="${run_args} --optimizing" + shift + elif [ "x$1" = "x--image" ]; then + run_args="${run_args} --image" + shift + elif [ "x$1" = "x--never-clean" ]; then + run_args="${run_args} --never-clean" + shift elif [ "x$1" = "x--jvm" ]; then run_args="${run_args} --jvm" shift elif [ "x$1" = "x--debug" ]; then run_args="${run_args} --debug" shift + elif [ "x$1" = "x--build-only" ]; then + run_args="${run_args} --build-only" + shift + elif [ "x$1" = "x--build-with-jack" ]; then + run_args="${run_args} --build-with-jack" + shift + elif [ "x$1" = "x--build-with-javac-dx" ]; then + run_args="${run_args} --build-with-javac-dx" + shift + elif [ "x$1" = "x--dex2oat-swap" ]; then + run_args="${run_args} --dex2oat-swap" + shift + elif [ "x$1" = "x--dalvik" ]; then + run_args="${run_args} --dalvik" + shift + elif [ "x$1" = "x--debuggable" ]; then + run_args="${run_args} --debuggable" + shift elif [ "x$1" = "x--zygote" ]; then run_args="${run_args} --zygote" shift @@ -59,15 +92,15 @@ while true; do elif [ "x$1" = "x--jit" ]; then run_args="${run_args} --jit" shift + elif [ "x$1" = "x--verify-soft-fail" ]; then + run_args="${run_args} --verify-soft-fail" + shift elif [ "x$1" = "x--no-verify" ]; then run_args="${run_args} --no-verify" shift elif [ "x$1" = "x--no-optimize" ]; then run_args="${run_args} --no-optimize" shift - elif [ "x$1" = "x--valgrind" ]; then - run_args="${run_args} --valgrind" - shift elif [ "x$1" = "x--dev" ]; then run_args="${run_args} --dev" shift @@ -116,6 +149,15 @@ while true; do elif [ "x$1" = "x--always-clean" ]; then run_args="${run_args} --always-clean" shift + elif [ "x$1" = "x--pic-test" ]; then + run_args="${run_args} --pic-test" + shift + elif [ "x$1" = "x--pic-image" ]; then + run_args="${run_args} --pic-image" + shift + elif [ "x$1" = "x--strace" ]; then + run_args="${run_args} --strace" + shift elif expr "x$1" : "x--" >/dev/null 2>&1; then echo "unknown $0 option: $1" 1>&2 usage="yes" @@ -134,9 +176,13 @@ if [ "$usage" = "yes" ]; then echo " Options are all passed to run-test; refer to that for " \ "further documentation:" echo " --debug --dev --host --interpreter --jit --jvm --no-optimize" - echo " --no-verify -O --update --valgrind --zygote --64 --relocate" - echo " --prebuild --always-clean --gcstress --gcverify --trace" - echo " --no-patchoat --no-dex2oat --use-java-home" + echo " --no-verify --verify-soft-fail -O --update --zygote --64" + echo " --relocate --prebuild --always-clean --gcstress --gcverify" + echo " --trace --no-patchoat --no-dex2oat --use-java-home --pic-image" + echo " --pic-test --strace --debuggable --dalvik --dex2oat-swap" + echo " --build-only --build-with-jack --build-with-javac-dx" + echo " --never-clean --image --no-image --quick --optimizing" + echo " --no-relocate --no-prebuild" echo " Specific Runtime Options:" echo " --seq Run tests one-by-one, avoiding failures caused by busy CPU" ) 1>&2 diff --git a/test/run-test b/test/run-test index 293779f7c3..f2bbaa7747 100755 --- a/test/run-test +++ b/test/run-test @@ -46,6 +46,7 @@ export RUN="${progdir}/etc/run-test-jar" export DEX_LOCATION=/data/run-test/${test_dir} export NEED_DEX="true" export USE_JACK="false" +export SMALI_ARGS="--experimental --api-level 23" # If dx was not set by the environment variable, assume it is in the path. if [ -z "$DX" ]; then @@ -84,7 +85,7 @@ fi # If JACK_CLASSPATH is not set, assume it only contains core-libart. if [ -z "$JACK_CLASSPATH" ]; then - export JACK_CLASSPATH="$ANDROID_BUILD_TOP/out/host/common/obj/JAVA_LIBRARIES/core-libart-hostdex_intermediates/classes.jack" + export JACK_CLASSPATH="${OUT_DIR:-$ANDROID_BUILD_TOP/out}/host/common/obj/JAVA_LIBRARIES/core-libart-hostdex_intermediates/classes.jack" fi # If JILL_JAR is not set, assume it is located in the prebuilts directory. @@ -439,7 +440,7 @@ elif [ "$runtime" = "art" ]; then if [ "$target_mode" = "no" ]; then # ANDROID_HOST_OUT is not set in a build environment. if [ -z "$ANDROID_HOST_OUT" ]; then - export ANDROID_HOST_OUT=$ANDROID_BUILD_TOP/out/host/linux-x86 + export ANDROID_HOST_OUT=${OUT_DIR:-$ANDROID_BUILD_TOP/out/}host/linux-x86 fi guess_host_arch_name run_args="${run_args} --boot ${ANDROID_HOST_OUT}/framework/core${image_suffix}${pic_image_suffix}.art" @@ -527,6 +528,7 @@ if [ "$usage" = "yes" ]; then echo " --debug Wait for a debugger to attach." echo " --debuggable Whether to compile Java code for a debugger." echo " --gdb Run under gdb; incompatible with some tests." + echo " --gdb-arg Pass an option to gdb." echo " --build-only Build test files only (off by default)." echo " --build-with-javac-dx Build test files with javac and dx (on by default)." echo " --build-with-jack Build test files with jack and jill (off by default)." @@ -552,6 +554,8 @@ if [ "$usage" = "yes" ]; then echo " the image and oat files be relocated to a random" echo " address before running. (default)" echo " --no-relocate Force the use of no relocating in the test" + echo " --image Run the test using a precompiled boot image. (default)" + echo " --no-image Run the test without a precompiled boot image." echo " --host Use the host-mode virtual machine." echo " --invoke-with Pass --invoke-with option to runtime." echo " --dalvik Use Dalvik (off by default)." @@ -563,6 +567,7 @@ if [ "$usage" = "yes" ]; then "files." echo " --64 Run the test in 64-bit mode" echo " --trace Run with method tracing" + echo " --strace Run with syscall tracing from strace." echo " --stream Run method tracing in streaming mode (requires --trace)" echo " --gcstress Run with gc stress testing" echo " --gcverify Run with gc verification" @@ -572,6 +577,9 @@ if [ "$usage" = "yes" ]; then echo " --dex2oat-swap Use a dex2oat swap file." echo " --instruction-set-features [string]" echo " Set instruction-set-features for compilation." + echo " --pic-image Use an image compiled with position independent code for the" + echo " boot class path." + echo " --pic-test Compile the test code position independent." ) 1>&2 exit 1 fi diff --git a/test/960-default-smali/util-src/generate_smali.py b/test/utils/python/generate_smali_main.py index b2bf1f0761..d796d313c6 100755 --- a/test/960-default-smali/util-src/generate_smali.py +++ b/test/utils/python/generate_smali_main.py @@ -15,7 +15,7 @@ # limitations under the License. """ -Generate Smali Main file for test 960 +Generate Smali Main file from a classes.xml file. """ import os diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt index 81ea79a6e2..9a8b462740 100644 --- a/tools/libcore_failures.txt +++ b/tools/libcore_failures.txt @@ -159,13 +159,6 @@ bug: 22786792 }, { - description: "Formatting failures", - result: EXEC_FAILED, - names: ["libcore.java.text.NumberFormatTest#test_currencyFromLocale", - "libcore.java.text.NumberFormatTest#test_currencyWithPatternDigits"], - bug: 25136848 -}, -{ description: "Lack of IPv6 on some buildbot slaves", result: EXEC_FAILED, names: ["libcore.io.OsTest#test_byteBufferPositions_sendto_recvfrom_af_inet6", diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh index edec362025..de27a6faaa 100755 --- a/tools/run-jdwp-tests.sh +++ b/tools/run-jdwp-tests.sh @@ -30,8 +30,6 @@ fi art="/data/local/tmp/system/bin/art" art_debugee="sh /data/local/tmp/system/bin/art" -# We use Quick's image on target because optimizing's image is not compiled debuggable. -image="-Ximage:/data/art-test/core.art" args=$@ debuggee_args="-Xcompiler-option --debuggable" device_dir="--device-dir=/data/local/tmp" @@ -41,6 +39,8 @@ vm_command="--vm-command=$art" image_compiler_option="" debug="no" verbose="no" +image="-Ximage:/data/art-test/core-jit.art" +vm_args="" # By default, we run the whole JDWP test suite. test="org.apache.harmony.jpda.tests.share.AllTests" @@ -88,7 +88,11 @@ while true; do fi done -vm_args="--vm-arg $image" +if [[ "$image" != "" ]]; then + vm_args="--vm-arg $image" +fi +vm_args="$vm_args --vm-arg -Xusejit:true" +debuggee_args="$debuggee_args -Xusejit:true" if [[ $debug == "yes" ]]; then art="$art -d" art_debugee="$art_debugee -d" diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh index 80f7a3737f..4b5a5ca76d 100755 --- a/tools/run-libcore-tests.sh +++ b/tools/run-libcore-tests.sh @@ -57,7 +57,6 @@ working_packages=("dalvik.system" "org.apache.harmony.luni" "org.apache.harmony.nio" "org.apache.harmony.regex" - "org.apache.harmony.security" "org.apache.harmony.testframework" "org.apache.harmony.tests.java.io" "org.apache.harmony.tests.java.lang" @@ -68,6 +67,10 @@ working_packages=("dalvik.system" "tests.java.lang.String" "jsr166") +# List of packages we could run, but don't have rights to revert +# changes in case of failures. +# "org.apache.harmony.security" + vogar_args=$@ while true; do if [[ "$1" == "--mode=device" ]]; then @@ -102,4 +105,4 @@ vogar_args="$vogar_args --timeout 480" # Run the tests using vogar. echo "Running tests for the following test packages:" echo ${working_packages[@]} | tr " " "\n" -vogar $vogar_args --expectations art/tools/libcore_failures.txt --classpath $jsr166_test_jar --classpath $test_jar ${working_packages[@]} +vogar $vogar_args --vm-arg -Xusejit:true --expectations art/tools/libcore_failures.txt --classpath $jsr166_test_jar --classpath $test_jar ${working_packages[@]} diff --git a/tools/setup-buildbot-device.sh b/tools/setup-buildbot-device.sh index 7faf86ed5c..d5b89897e5 100755 --- a/tools/setup-buildbot-device.sh +++ b/tools/setup-buildbot-device.sh @@ -30,3 +30,10 @@ adb shell ifconfig echo -e "${green}List properties${nc}" adb shell getprop + +echo -e "${green}Uptime${nc}" +adb shell uptime + +echo -e "${green}Kill stalled dalvikvm processes${nc}" +processes=$(adb shell "ps" | grep dalvikvm | awk '{print $2}') +for i in $processes; do adb shell kill -9 $i; done |