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
diff --git a/compiler/debug/elf_debug_writer.cc b/compiler/debug/elf_debug_writer.cc
index da3230f..cf7254b 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 @@
// 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 @@
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 32b2f67..ed43a0b 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 @@
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 d84a132..f01554a 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -127,17 +127,14 @@
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 @@
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 737771f..c69a376 100644
--- a/compiler/jit/jit_compiler.h
+++ b/compiler/jit/jit_compiler.h
@@ -53,6 +53,11 @@
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 b1a3abe..5d06969 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -1481,17 +1481,13 @@
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 2a5485c..2446a44 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 @@
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 @@
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 @@
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 @@
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 @@
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 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;
+ DCHECK(std::is_sorted(removed.begin(), removed.end()));
+ jit::Jit* jit = Runtime::Current()->GetJit();
+ if (jit == nullptr) {
+ return;
+ }
- 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 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);
+ }
+ }
+ auto cmp = [](JITCodeEntry* lhs, JITCodeEntry* rhs) { return lhs->addr_ < rhs->addr_; };
+ std::sort(entries.begin(), entries.end(), cmp); // Sort by address.
- // 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);
- }
- 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.
+ // 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 f84f08d..be5e5ac 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 @@
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 @@
// 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 c264f7c..3a103f5 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 @@
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 @@
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 b5c3279..dc2bb7c 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -490,15 +490,17 @@
}
}
-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 @@
->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 284a36a..6aa5f31 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -388,7 +388,8 @@
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_);