diff options
author | 2019-07-31 18:40:09 +0100 | |
---|---|---|
committer | 2019-08-16 09:10:55 +0000 | |
commit | 8fc2f95291206806599d4f2a50da529da85155b6 (patch) | |
tree | c3c9de9a9a925d6ab790f6db466d1f9c17ed1010 | |
parent | 9ce340f829f836560278ecd078fbefcf19c9d629 (diff) |
JIT mini-debug-info: Remove global maps.
Keep the extra bookkeeping information in JITCodeEntry.
Also do the compression eagerly during GC rather then lazily.
Test: test.py -b --host --jit
Bug: 119800099
Change-Id: Ie6cc682033a32c01d4c2cac242d8a4201116f940
-rw-r--r-- | compiler/debug/elf_debug_writer.cc | 12 | ||||
-rw-r--r-- | compiler/debug/elf_debug_writer.h | 7 | ||||
-rw-r--r-- | compiler/jit/jit_compiler.cc | 24 | ||||
-rw-r--r-- | compiler/jit/jit_compiler.h | 5 | ||||
-rw-r--r-- | compiler/optimizing/optimizing_compiler.cc | 18 | ||||
-rw-r--r-- | runtime/jit/debugger_interface.cc | 280 | ||||
-rw-r--r-- | runtime/jit/debugger_interface.h | 27 | ||||
-rw-r--r-- | runtime/jit/jit.h | 10 | ||||
-rw-r--r-- | runtime/jit/jit_code_cache.cc | 21 | ||||
-rw-r--r-- | runtime/jit/jit_code_cache.h | 3 |
10 files changed, 211 insertions, 196 deletions
diff --git a/compiler/debug/elf_debug_writer.cc b/compiler/debug/elf_debug_writer.cc index da3230f8ce..cf7254b105 100644 --- a/compiler/debug/elf_debug_writer.cc +++ b/compiler/debug/elf_debug_writer.cc @@ -34,6 +34,7 @@ #include "elf/elf_debug_reader.h" #include "elf/elf_utils.h" #include "elf/xz_utils.h" +#include "jit/debugger_interface.h" #include "oat.h" #include "stream/vector_output_stream.h" @@ -227,15 +228,14 @@ std::vector<uint8_t> MakeElfFileForJIT( // Combine several mini-debug-info ELF files into one, while filtering some symbols. std::vector<uint8_t> PackElfFileForJIT( - InstructionSet isa, - const InstructionSetFeatures* features ATTRIBUTE_UNUSED, - std::vector<ArrayRef<const uint8_t>>& added_elf_files, - std::vector<const void*>& removed_symbols, + ArrayRef<JITCodeEntry*> jit_entries, + ArrayRef<const void*> removed_symbols, bool compress, /*out*/ size_t* num_symbols) { using ElfTypes = ElfRuntimeTypes; using Elf_Addr = typename ElfTypes::Addr; using Elf_Sym = typename ElfTypes::Sym; + const InstructionSet isa = kRuntimeISA; CHECK_EQ(sizeof(Elf_Addr), static_cast<size_t>(GetInstructionSetPointerSize(isa))); auto is_removed_symbol = [&removed_symbols](Elf_Addr addr) { const void* code_ptr = reinterpret_cast<const void*>(addr); @@ -260,8 +260,8 @@ std::vector<uint8_t> PackElfFileForJIT( using Reader = ElfDebugReader<ElfTypes>; std::deque<Reader> readers; - for (ArrayRef<const uint8_t> added_elf_file : added_elf_files) { - readers.emplace_back(added_elf_file); + for (JITCodeEntry* it : jit_entries) { + readers.emplace_back(GetJITCodeEntrySymFile(it)); } // Write symbols names. All other data is buffered. diff --git a/compiler/debug/elf_debug_writer.h b/compiler/debug/elf_debug_writer.h index 32b2f67f10..ed43a0b702 100644 --- a/compiler/debug/elf_debug_writer.h +++ b/compiler/debug/elf_debug_writer.h @@ -29,6 +29,7 @@ namespace art { class OatHeader; +struct JITCodeEntry; namespace mirror { class Class; } // namespace mirror @@ -56,10 +57,8 @@ std::vector<uint8_t> MakeElfFileForJIT( const MethodDebugInfo& method_info); std::vector<uint8_t> PackElfFileForJIT( - InstructionSet isa, - const InstructionSetFeatures* features, - std::vector<ArrayRef<const uint8_t>>& added_elf_files, - std::vector<const void*>& removed_symbols, + ArrayRef<JITCodeEntry*> jit_entries, + ArrayRef<const void*> removed_symbols, bool compress, /*out*/ size_t* num_symbols); diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index d84a132900..f01554a197 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -127,17 +127,14 @@ void JitCompiler::TypesLoaded(mirror::Class** types, size_t count) REQUIRES_SHARED(Locks::mutator_lock_) { const CompilerOptions& compiler_options = GetCompilerOptions(); if (compiler_options.GetGenerateDebugInfo()) { + InstructionSet isa = compiler_options.GetInstructionSet(); + const InstructionSetFeatures* features = compiler_options.GetInstructionSetFeatures(); const ArrayRef<mirror::Class*> types_array(types, count); - std::vector<uint8_t> elf_file = debug::WriteDebugElfFileForClasses( - kRuntimeISA, compiler_options.GetInstructionSetFeatures(), types_array); - // We never free debug info for types, so we don't need to provide a handle - // (which would have been otherwise used as identifier to remove it later). - AddNativeDebugInfoForJit(Thread::Current(), - /*code_ptr=*/ nullptr, - elf_file, - /*pack*/ nullptr, - compiler_options.GetInstructionSet(), - compiler_options.GetInstructionSetFeatures()); + std::vector<uint8_t> elf_file = + debug::WriteDebugElfFileForClasses(isa, features, types_array); + + // NB: Don't allow packing since it would remove non-backtrace data. + AddNativeDebugInfoForJit(/*code_ptr=*/ nullptr, elf_file, /*allow_packing=*/ false); } } @@ -145,6 +142,13 @@ bool JitCompiler::GenerateDebugInfo() { return GetCompilerOptions().GetGenerateDebugInfo(); } +std::vector<uint8_t> JitCompiler::PackElfFileForJIT(ArrayRef<JITCodeEntry*> elf_files, + ArrayRef<const void*> removed_symbols, + bool compress, + /*out*/ size_t* num_symbols) { + return debug::PackElfFileForJIT(elf_files, removed_symbols, compress, num_symbols); +} + JitCompiler::JitCompiler() { compiler_options_.reset(new CompilerOptions()); ParseCompilerOptions(); diff --git a/compiler/jit/jit_compiler.h b/compiler/jit/jit_compiler.h index 737771fbf6..c69a376abb 100644 --- a/compiler/jit/jit_compiler.h +++ b/compiler/jit/jit_compiler.h @@ -53,6 +53,11 @@ class JitCompiler : public JitCompilerInterface { void TypesLoaded(mirror::Class**, size_t count) REQUIRES_SHARED(Locks::mutator_lock_) override; + std::vector<uint8_t> PackElfFileForJIT(ArrayRef<JITCodeEntry*> elf_files, + ArrayRef<const void*> removed_symbols, + bool compress, + /*out*/ size_t* num_symbols) override; + private: std::unique_ptr<CompilerOptions> compiler_options_; std::unique_ptr<Compiler> compiler_; diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc index b1a3abee2f..5d06969b4e 100644 --- a/compiler/optimizing/optimizing_compiler.cc +++ b/compiler/optimizing/optimizing_compiler.cc @@ -1481,17 +1481,13 @@ void OptimizingCompiler::GenerateJitDebugInfo(ArtMethod* method ATTRIBUTE_UNUSED const bool mini_debug_info = !compiler_options.GetGenerateDebugInfo(); // Create entry for the single method that we just compiled. - std::vector<uint8_t> elf_file = debug::MakeElfFileForJIT( - compiler_options.GetInstructionSet(), - compiler_options.GetInstructionSetFeatures(), - mini_debug_info, - info); - AddNativeDebugInfoForJit(Thread::Current(), - reinterpret_cast<const void*>(info.code_address), - elf_file, - mini_debug_info ? debug::PackElfFileForJIT : nullptr, - compiler_options.GetInstructionSet(), - compiler_options.GetInstructionSetFeatures()); + InstructionSet isa = compiler_options.GetInstructionSet(); + const InstructionSetFeatures* features = compiler_options.GetInstructionSetFeatures(); + std::vector<uint8_t> elf = debug::MakeElfFileForJIT(isa, features, mini_debug_info, info); + + // NB: Don't allow packing of full info since it would remove non-backtrace data. + const void* code_ptr = reinterpret_cast<const void*>(info.code_address); + AddNativeDebugInfoForJit(code_ptr, elf, /*allow_packing=*/ mini_debug_info); } Runtime::Current()->GetJit()->AddTimingLogger(logger); } diff --git a/runtime/jit/debugger_interface.cc b/runtime/jit/debugger_interface.cc index 2a5485c422..2446a441a1 100644 --- a/runtime/jit/debugger_interface.cc +++ b/runtime/jit/debugger_interface.cc @@ -19,19 +19,19 @@ #include <android-base/logging.h> #include "base/array_ref.h" +#include "base/bit_utils.h" #include "base/logging.h" #include "base/mutex.h" #include "base/time_utils.h" #include "base/utils.h" #include "dex/dex_file.h" +#include "jit/jit.h" +#include "runtime.h" #include "thread-current-inl.h" #include "thread.h" #include <atomic> #include <cstddef> -#include <deque> -#include <map> -#include <set> // // Debug interface for native tools (gdb, lldb, libunwind, simpleperf). @@ -92,19 +92,32 @@ extern "C" { JIT_UNREGISTER_FN }; - struct JITCodeEntry { + // Public/stable binary interface. + struct JITCodeEntryPublic { // Atomic to ensure the reader can always iterate over the linked list // (e.g. the process could crash in the middle of writing this field). std::atomic<JITCodeEntry*> next_; - // Non-atomic. The reader should not use it. It is only used for deletion. - JITCodeEntry* prev_; - const uint8_t* symfile_addr_; - uint64_t symfile_size_; // Beware of the offset (12 on x86; but 16 on ARM32). + JITCodeEntry* prev_; // For linked list deletion. Unused in readers. + const uint8_t* symfile_addr_; // Address of the in-memory ELF file. + uint64_t symfile_size_; // Beware of the offset (12 on x86; but 16 on ARM32). // Android-specific fields: uint64_t register_timestamp_; // CLOCK_MONOTONIC time of entry registration. }; + // Implementation-specific fields (which can be used only in this file). + struct JITCodeEntry : public JITCodeEntryPublic { + // Unpacked entries: Code address of the symbol in the ELF file. + // Packed entries: The start address of the covered memory range. + const void* addr_ = nullptr; + // Allow merging of ELF files to save space. + // Packing drops advanced DWARF data, so it is not always desirable. + bool allow_packing_ = false; + // Whether this entry has been LZMA compressed. + // Compression is expensive, so we don't always do it. + bool is_compressed_ = false; + }; + struct JITDescriptor { uint32_t version_ = 1; // NB: GDB supports only version 1. uint32_t action_flag_ = JIT_NOACTION; // One of the JITAction enum values. @@ -115,7 +128,7 @@ extern "C" { uint8_t magic_[8] = {'A', 'n', 'd', 'r', 'o', 'i', 'd', '1'}; uint32_t flags_ = 0; // Reserved for future use. Must be 0. uint32_t sizeof_descriptor = sizeof(JITDescriptor); - uint32_t sizeof_entry = sizeof(JITCodeEntry); + uint32_t sizeof_entry = sizeof(JITCodeEntryPublic); std::atomic_uint32_t action_seqlock_{0}; // Incremented before and after any modification. uint64_t action_timestamp_ = 1; // CLOCK_MONOTONIC time of last action. }; @@ -145,6 +158,10 @@ extern "C" { JITDescriptor __dex_debug_descriptor GUARDED_BY(g_dex_debug_lock) {}; } +ArrayRef<const uint8_t> GetJITCodeEntrySymFile(JITCodeEntry* entry) { + return ArrayRef<const uint8_t>(entry->symfile_addr_, entry->symfile_size_); +} + // Mark the descriptor as "locked", so native tools know the data is being modified. static void ActionSeqlock(JITDescriptor& descriptor) { DCHECK_EQ(descriptor.action_seqlock_.load() & 1, 0u) << "Already locked"; @@ -165,7 +182,10 @@ static JITCodeEntry* CreateJITCodeEntryInternal( JITDescriptor& descriptor, void (*register_code_ptr)(), ArrayRef<const uint8_t> symfile, - bool copy_symfile) { + bool copy_symfile, + const void* addr = nullptr, + bool allow_packing = false, + bool is_compressed = false) { // Make a copy of the buffer to shrink it and to pass ownership to JITCodeEntry. if (copy_symfile) { uint8_t* copy = new uint8_t[symfile.size()]; @@ -179,13 +199,16 @@ static JITCodeEntry* CreateJITCodeEntryInternal( uint64_t timestamp = std::max(descriptor.action_timestamp_ + 1, NanoTime()); JITCodeEntry* head = descriptor.head_.load(std::memory_order_relaxed); - JITCodeEntry* entry = new JITCodeEntry; + JITCodeEntry* entry = new JITCodeEntry(); CHECK(entry != nullptr); entry->symfile_addr_ = symfile.data(); entry->symfile_size_ = symfile.size(); entry->prev_ = nullptr; entry->next_.store(head, std::memory_order_relaxed); entry->register_timestamp_ = timestamp; + entry->addr_ = addr; + entry->allow_packing_ = allow_packing; + entry->is_compressed_ = is_compressed; // We are going to modify the linked list, so take the seqlock. ActionSeqlock(descriptor); @@ -244,196 +267,171 @@ static void DeleteJITCodeEntryInternal( } } -static std::map<const DexFile*, JITCodeEntry*> g_dex_debug_entries GUARDED_BY(g_dex_debug_lock); - void AddNativeDebugInfoForDex(Thread* self, const DexFile* dexfile) { MutexLock mu(self, g_dex_debug_lock); DCHECK(dexfile != nullptr); - // This is just defensive check. The class linker should not register the dex file twice. - if (g_dex_debug_entries.count(dexfile) == 0) { - const ArrayRef<const uint8_t> symfile(dexfile->Begin(), dexfile->Size()); - JITCodeEntry* entry = CreateJITCodeEntryInternal(__dex_debug_descriptor, - __dex_debug_register_code_ptr, - symfile, - /*copy_symfile=*/ false); - g_dex_debug_entries.emplace(dexfile, entry); - } + const ArrayRef<const uint8_t> symfile(dexfile->Begin(), dexfile->Size()); + CreateJITCodeEntryInternal(__dex_debug_descriptor, + __dex_debug_register_code_ptr, + symfile, + /*copy_symfile=*/ false); } void RemoveNativeDebugInfoForDex(Thread* self, const DexFile* dexfile) { MutexLock mu(self, g_dex_debug_lock); - auto it = g_dex_debug_entries.find(dexfile); + DCHECK(dexfile != nullptr); // We register dex files in the class linker and free them in DexFile_closeDexFile, but // there might be cases where we load the dex file without using it in the class linker. - if (it != g_dex_debug_entries.end()) { - DeleteJITCodeEntryInternal(__dex_debug_descriptor, - __dex_debug_register_code_ptr, - /*entry=*/ it->second, - /*free_symfile=*/ false); - g_dex_debug_entries.erase(it); + // On the other hand, single dex file might also be used with different class-loaders. + for (JITCodeEntry* entry = __dex_debug_descriptor.head_; entry != nullptr; ) { + JITCodeEntry* next = entry->next_; // Save next pointer before we free the memory. + if (entry->symfile_addr_ == dexfile->Begin()) { + DeleteJITCodeEntryInternal(__dex_debug_descriptor, + __dex_debug_register_code_ptr, + entry, + /*free_symfile=*/ false); + } + entry = next; } } -// Mapping from handle to entry. Used to manage life-time of the entries. -using JITCodeEntries = std::multimap<const void*, JITCodeEntry*>; -static JITCodeEntries g_uncompressed_jit_debug_entries GUARDED_BY(g_jit_debug_lock); -static JITCodeEntries g_compressed_jit_debug_entries GUARDED_BY(g_jit_debug_lock); +// Size of JIT code range covered by each packed JITCodeEntry. +static constexpr uint32_t kJitRepackGroupSize = 64 * KB; -// Number of entries added since last packing. Used to pack entries in bulk. -static size_t g_jit_num_unpacked_entries GUARDED_BY(g_jit_debug_lock) = 0; -static constexpr uint32_t kJitMaxUnpackedEntries = 64; +// Automatically call the repack method every 'n' new entries. +static constexpr uint32_t kJitRepackFrequency = 64; +static uint32_t g_jit_num_unpacked_entries = 0; -// We postpone removal so that it is done in bulk. -static std::set<const void*> g_jit_removed_entries GUARDED_BY(g_jit_debug_lock); - -// Split the JIT code cache into groups of fixed size and create singe JITCodeEntry for each group. +// Split the JIT code cache into groups of fixed size and create single JITCodeEntry for each group. // The start address of method's code determines which group it belongs to. The end is irrelevant. -// As a consequnce, newly added mini debug infos will be merged and old ones (GCed) will be pruned. -static void RepackEntries(PackElfFileForJITFunction pack, - InstructionSet isa, - const InstructionSetFeatures* features, - bool compress, - /*inout*/ JITCodeEntries* entries) +// New mini debug infos will be merged if possible, and entries for GCed functions will be removed. +static void RepackEntries(bool compress, ArrayRef<const void*> removed) REQUIRES(g_jit_debug_lock) { - // Size of memory range covered by each JITCodeEntry. - // The number of methods per entry is variable (depending on how many fit in that range). - constexpr uint32_t kGroupSize = 64 * KB; - - CHECK(pack != nullptr); - JITCodeEntries packed_entries; - std::vector<ArrayRef<const uint8_t>> added; - std::vector<const void*> removed; - while (!entries->empty()) { - const void* group_ptr = AlignDown(entries->begin()->first, kGroupSize); - const void* group_end = reinterpret_cast<const uint8_t*>(group_ptr) + kGroupSize; - - // Collect all entries that have been added or removed within our memory range. - added.clear(); - auto add_it = entries->begin(); - for (; add_it != entries->end() && add_it->first < group_end; ++add_it) { - JITCodeEntry* entry = add_it->second; - added.emplace_back(entry->symfile_addr_, entry->symfile_size_); - } - removed.clear(); - auto remove_it = g_jit_removed_entries.lower_bound(group_ptr); - for (; remove_it != g_jit_removed_entries.end() && *remove_it < group_end; ++remove_it) { - removed.push_back(*remove_it); + DCHECK(std::is_sorted(removed.begin(), removed.end())); + jit::Jit* jit = Runtime::Current()->GetJit(); + if (jit == nullptr) { + return; + } + + // Collect entries that we want to pack. + std::vector<JITCodeEntry*> entries; + entries.reserve(2 * kJitRepackFrequency); + for (JITCodeEntry* it = __jit_debug_descriptor.head_; it != nullptr; it = it->next_) { + if (it->allow_packing_) { + if (!compress && it->is_compressed_ && removed.empty()) { + continue; // If we are not compressing, also avoid decompressing. + } + entries.push_back(it); } - CHECK_GT(added.size(), 0u); - if (added.size() == 1 && removed.size() == 0) { - packed_entries.insert(entries->extract(entries->begin())); - continue; // Nothing changed in this memory range. + } + auto cmp = [](JITCodeEntry* lhs, JITCodeEntry* rhs) { return lhs->addr_ < rhs->addr_; }; + std::sort(entries.begin(), entries.end(), cmp); // Sort by address. + + // Process the entries in groups (each spanning memory range of size kJitRepackGroupSize). + for (auto group_it = entries.begin(); group_it != entries.end();) { + const void* group_ptr = AlignDown((*group_it)->addr_, kJitRepackGroupSize); + const void* group_end = reinterpret_cast<const uint8_t*>(group_ptr) + kJitRepackGroupSize; + + // Find all entries in this group (each entry is an in-memory ELF file). + auto begin = group_it; + auto end = std::find_if(begin, entries.end(), [=](auto* e) { return e->addr_ >= group_end; }); + CHECK(end > begin); + ArrayRef<JITCodeEntry*> elfs(&*begin, end - begin); + + // Find all symbols that have been removed in this memory range. + auto removed_begin = std::lower_bound(removed.begin(), removed.end(), group_ptr); + auto removed_end = std::lower_bound(removed.begin(), removed.end(), group_end); + CHECK(removed_end >= removed_begin); + ArrayRef<const void*> removed_subset(&*removed_begin, removed_end - removed_begin); + + // Bail out early if there is nothing to do for this group. + if (elfs.size() == 1 && removed_subset.empty() && (*begin)->is_compressed_ == compress) { + group_it = end; // Go to next group. + continue; } // Create new single JITCodeEntry that covers this memory range. uint64_t start_time = MicroTime(); - size_t symbols; - std::vector<uint8_t> packed = pack(isa, features, added, removed, compress, &symbols); + size_t live_symbols; + std::vector<uint8_t> packed = jit->GetJitCompiler()->PackElfFileForJIT( + elfs, removed_subset, compress, &live_symbols); VLOG(jit) << "JIT mini-debug-info repacked" << " for " << group_ptr << " in " << MicroTime() - start_time << "us" - << " files=" << added.size() - << " removed=" << removed.size() - << " symbols=" << symbols - << " size=" << PrettySize(packed.size()) - << " compress=" << compress; + << " elfs=" << elfs.size() + << " dead=" << removed_subset.size() + << " live=" << live_symbols + << " size=" << packed.size() << (compress ? "(lzma)" : ""); // Replace the old entries with the new one (with their lifetime temporally overlapping). - packed_entries.emplace(group_ptr, CreateJITCodeEntryInternal( + CreateJITCodeEntryInternal( __jit_debug_descriptor, __jit_debug_register_code_ptr, ArrayRef<const uint8_t>(packed), - /*copy_symfile=*/ true)); - for (auto it = entries->begin(); it != add_it; it = entries->erase(it)) { + /*copy_symfile=*/ true, + /*addr_=*/ group_ptr, + /*allow_packing_=*/ true, + /*is_compressed_=*/ compress); + for (auto it : elfs) { DeleteJITCodeEntryInternal(__jit_debug_descriptor, __jit_debug_register_code_ptr, - /*entry=*/ it->second, + /*entry=*/ it, /*free_symfile=*/ true); } + group_it = end; // Go to next group. } - entries->swap(packed_entries); + g_jit_num_unpacked_entries = 0; } -void AddNativeDebugInfoForJit(Thread* self, - const void* code_ptr, +void AddNativeDebugInfoForJit(const void* code_ptr, const std::vector<uint8_t>& symfile, - PackElfFileForJITFunction pack, - InstructionSet isa, - const InstructionSetFeatures* features) { - MutexLock mu(self, g_jit_debug_lock); + bool allow_packing) { + MutexLock mu(Thread::Current(), g_jit_debug_lock); DCHECK_NE(symfile.size(), 0u); - // Pack and compress all entries. This will run on first compilation after a GC. - // Must be done before addition in case the added code_ptr is in the removed set. - if (!g_jit_removed_entries.empty()) { - g_compressed_jit_debug_entries.merge(g_uncompressed_jit_debug_entries); - if (pack != nullptr) { - RepackEntries(pack, isa, features, /*compress=*/ true, &g_compressed_jit_debug_entries); - } else { - // If repacking function is not provided, just remove the individual entries. - for (const void* removed_code_ptr : g_jit_removed_entries) { - auto it = g_compressed_jit_debug_entries.find(removed_code_ptr); - if (it != g_compressed_jit_debug_entries.end()) { - DeleteJITCodeEntryInternal(__jit_debug_descriptor, - __jit_debug_register_code_ptr, - /*entry=*/ it->second, - /*free_symfile=*/ true); - g_compressed_jit_debug_entries.erase(it); - } - } - } - g_jit_removed_entries.clear(); - g_jit_num_unpacked_entries = 0; - } - - JITCodeEntry* entry = CreateJITCodeEntryInternal( + CreateJITCodeEntryInternal( __jit_debug_descriptor, __jit_debug_register_code_ptr, ArrayRef<const uint8_t>(symfile), - /*copy_symfile=*/ true); + /*copy_symfile=*/ true, + /*addr=*/ code_ptr, + /*allow_packing=*/ allow_packing, + /*is_compressed=*/ false); VLOG(jit) << "JIT mini-debug-info added" << " for " << code_ptr << " size=" << PrettySize(symfile.size()); - // We don't provide code_ptr for type debug info, which means we cannot free it later. - // (this only happens when --generate-debug-info flag is enabled for the purpose - // of being debugged with gdb; it does not happen for debuggable apps by default). - if (code_ptr == nullptr) { - return; - } - - g_uncompressed_jit_debug_entries.emplace(code_ptr, entry); - // Count how many entries we have added since the last mini-debug-info packing. - // We avoid g_uncompressed_jit_debug_entries.size() because it can shrink during packing. - ++g_jit_num_unpacked_entries; - + // Automatically repack entries on regular basis to save space. // Pack (but don't compress) recent entries - this is cheap and reduces memory use by ~4x. // We delay compression until after GC since it is more expensive (and saves further ~4x). - if (g_jit_num_unpacked_entries >= kJitMaxUnpackedEntries && pack != nullptr) { - RepackEntries(pack, isa, features, /*compress=*/ false, &g_uncompressed_jit_debug_entries); - g_jit_num_unpacked_entries = 0; + if (++g_jit_num_unpacked_entries >= kJitRepackFrequency) { + RepackEntries(/*compress=*/ false, /*removed=*/ ArrayRef<const void*>()); } } -void RemoveNativeDebugInfoForJit(Thread* self, const void* code_ptr) { - MutexLock mu(self, g_jit_debug_lock); - // We generate JIT native debug info only if the right runtime flags are enabled, - // but we try to remove it unconditionally whenever code is freed from JIT cache. - if (!g_uncompressed_jit_debug_entries.empty() || !g_compressed_jit_debug_entries.empty()) { - g_jit_removed_entries.insert(code_ptr); +void RemoveNativeDebugInfoForJit(ArrayRef<const void*> removed) { + MutexLock mu(Thread::Current(), g_jit_debug_lock); + RepackEntries(/*compress=*/ true, removed); + + // Remove entries which are not allowed to be packed (containing single method each). + for (JITCodeEntry* it = __jit_debug_descriptor.head_; it != nullptr; it = it->next_) { + if (!it->allow_packing_ && std::binary_search(removed.begin(), removed.end(), it->addr_)) { + DeleteJITCodeEntryInternal(__jit_debug_descriptor, + __jit_debug_register_code_ptr, + /*entry=*/ it, + /*free_symfile=*/ true); + } } } size_t GetJitMiniDebugInfoMemUsage() { MutexLock mu(Thread::Current(), g_jit_debug_lock); size_t size = 0; - for (const auto& entries : {g_uncompressed_jit_debug_entries, g_compressed_jit_debug_entries}) { - for (const auto& entry : entries) { - size += sizeof(JITCodeEntry) + entry.second->symfile_size_ + /*map entry*/ 4 * sizeof(void*); - } + for (JITCodeEntry* it = __jit_debug_descriptor.head_; it != nullptr; it = it->next_) { + size += sizeof(JITCodeEntry) + it->symfile_size_; } return size; } diff --git a/runtime/jit/debugger_interface.h b/runtime/jit/debugger_interface.h index f84f08d019..be5e5acfbc 100644 --- a/runtime/jit/debugger_interface.h +++ b/runtime/jit/debugger_interface.h @@ -17,6 +17,7 @@ #ifndef ART_RUNTIME_JIT_DEBUGGER_INTERFACE_H_ #define ART_RUNTIME_JIT_DEBUGGER_INTERFACE_H_ +#include <functional> #include <inttypes.h> #include <vector> @@ -29,16 +30,9 @@ namespace art { class DexFile; class Mutex; class Thread; +struct JITCodeEntry; -// This method is declared in the compiler library. -// We need to pass it by pointer to be able to call it from runtime. -typedef std::vector<uint8_t> PackElfFileForJITFunction( - InstructionSet isa, - const InstructionSetFeatures* features, - std::vector<ArrayRef<const uint8_t>>& added_elf_files, - std::vector<const void*>& removed_symbols, - bool compress, - /*out*/ size_t* num_symbols); +ArrayRef<const uint8_t> GetJITCodeEntrySymFile(JITCodeEntry*); // Notify native tools (e.g. libunwind) that DEX file has been opened. void AddNativeDebugInfoForDex(Thread* self, const DexFile* dexfile); @@ -46,19 +40,16 @@ void AddNativeDebugInfoForDex(Thread* self, const DexFile* dexfile); // Notify native tools (e.g. libunwind) that DEX file has been closed. void RemoveNativeDebugInfoForDex(Thread* self, const DexFile* dexfile); -// Notify native tools (e.g. libunwind) that JIT has compiled a new method. +// Notify native tools (e.g. libunwind) that JIT has compiled a single new method. // The method will make copy of the passed ELF file (to shrink it to the minimum size). -// If packing function is provided, ELF files can be merged to save space -// (however, the merging drops advanced gdb debug-info as it is too complex). -void AddNativeDebugInfoForJit(Thread* self, - const void* code_ptr, +// If packing is allowed, the ELF file might be merged with others to save space +// (however, this drops all ELF sections other than symbols names and unwinding info). +void AddNativeDebugInfoForJit(const void* code_ptr, const std::vector<uint8_t>& symfile, - PackElfFileForJITFunction pack, - InstructionSet isa, - const InstructionSetFeatures* features); + bool allow_packing); // Notify native tools (e.g. libunwind) that JIT code has been garbage collected. -void RemoveNativeDebugInfoForJit(Thread* self, const void* code_ptr); +void RemoveNativeDebugInfoForJit(ArrayRef<const void*> removed_code_ptrs); // Returns approximate memory used by debug info for JIT code. size_t GetJitMiniDebugInfoMemUsage(); diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index c264f7c6bf..3a103f5fd2 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -23,6 +23,7 @@ #include "base/runtime_debug.h" #include "base/timing_logger.h" #include "handle.h" +#include "jit/debugger_interface.h" #include "jit/profile_saver_options.h" #include "obj_ptr.h" #include "thread_pool.h" @@ -175,6 +176,11 @@ class JitCompilerInterface { REQUIRES_SHARED(Locks::mutator_lock_) = 0; virtual bool GenerateDebugInfo() = 0; virtual void ParseCompilerOptions() = 0; + + virtual std::vector<uint8_t> PackElfFileForJIT(ArrayRef<JITCodeEntry*> elf_files, + ArrayRef<const void*> removed_symbols, + bool compress, + /*out*/ size_t* num_symbols) = 0; }; class Jit { @@ -202,6 +208,10 @@ class Jit { return code_cache_; } + JitCompilerInterface* GetJitCompiler() const { + return jit_compiler_; + } + void CreateThreadPool(); void DeleteThreadPool(); void WaitForWorkersToBeCreated(); diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index b5c32793a3..dc2bb7c2b4 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -490,15 +490,17 @@ void JitCodeCache::SweepRootTables(IsMarkedVisitor* visitor) { } } -void JitCodeCache::FreeCodeAndData(const void* code_ptr) { +void JitCodeCache::FreeCodeAndData(const void* code_ptr, bool free_debug_info) { if (IsInZygoteExecSpace(code_ptr)) { // No need to free, this is shared memory. return; } uintptr_t allocation = FromCodeToAllocation(code_ptr); - // Notify native debugger that we are about to remove the code. - // It does nothing if we are not using native debugger. - RemoveNativeDebugInfoForJit(Thread::Current(), code_ptr); + if (free_debug_info) { + // Remove compressed mini-debug info for the method. + // TODO: This is expensive, so we should always do it in the caller in bulk. + RemoveNativeDebugInfoForJit(ArrayRef<const void*>(&code_ptr, 1)); + } if (OatQuickMethodHeader::FromCodePointer(code_ptr)->IsOptimized()) { private_region_.FreeData(GetRootTable(code_ptr)); } // else this is a JNI stub without any data. @@ -519,9 +521,18 @@ void JitCodeCache::FreeAllMethodHeaders( ->RemoveDependentsWithMethodHeaders(method_headers); } + // Remove compressed mini-debug info for the methods. + std::vector<const void*> removed_symbols; + removed_symbols.reserve(method_headers.size()); + for (const OatQuickMethodHeader* method_header : method_headers) { + removed_symbols.push_back(method_header->GetCode()); + } + std::sort(removed_symbols.begin(), removed_symbols.end()); + RemoveNativeDebugInfoForJit(ArrayRef<const void*>(removed_symbols)); + ScopedCodeCacheWrite scc(private_region_); for (const OatQuickMethodHeader* method_header : method_headers) { - FreeCodeAndData(method_header->GetCode()); + FreeCodeAndData(method_header->GetCode(), /*free_debug_info=*/ false); } } diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h index 284a36a197..6aa5f317cf 100644 --- a/runtime/jit/jit_code_cache.h +++ b/runtime/jit/jit_code_cache.h @@ -388,7 +388,8 @@ class JitCodeCache { REQUIRES(Locks::mutator_lock_); // Free code and data allocations for `code_ptr`. - void FreeCodeAndData(const void* code_ptr) REQUIRES(Locks::jit_lock_); + void FreeCodeAndData(const void* code_ptr, bool free_debug_info = true) + REQUIRES(Locks::jit_lock_); // Number of bytes allocated in the code cache. size_t CodeCacheSize() REQUIRES(!Locks::jit_lock_); |