Merge "ART: Fix ignoring duplicate fields when loading class."
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index ff41736..0afec2d 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -74,6 +74,7 @@
ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods
ART_GTEST_oat_file_assistant_test_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested
ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex
+ART_GTEST_oat_test_DEX_DEPS := Main
ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY
ART_GTEST_proxy_test_DEX_DEPS := Interfaces
ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods
diff --git a/compiler/compiler.h b/compiler/compiler.h
index 8788dc1..3a9ce1b 100644
--- a/compiler/compiler.h
+++ b/compiler/compiler.h
@@ -22,6 +22,10 @@
namespace art {
+namespace jit {
+ class JitCodeCache;
+}
+
class ArtMethod;
class Backend;
struct CompilationUnit;
@@ -58,6 +62,13 @@
uint32_t method_idx,
const DexFile& dex_file) const = 0;
+ virtual bool JitCompile(Thread* self ATTRIBUTE_UNUSED,
+ jit::JitCodeCache* code_cache ATTRIBUTE_UNUSED,
+ ArtMethod* method ATTRIBUTE_UNUSED)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ return false;
+ }
+
virtual uintptr_t GetEntryPointOf(ArtMethod* method) const
SHARED_REQUIRES(Locks::mutator_lock_) = 0;
diff --git a/compiler/dex/quick/arm64/call_arm64.cc b/compiler/dex/quick/arm64/call_arm64.cc
index 036da2e..b1acf5e 100644
--- a/compiler/dex/quick/arm64/call_arm64.cc
+++ b/compiler/dex/quick/arm64/call_arm64.cc
@@ -447,7 +447,7 @@
static bool Arm64UseRelativeCall(CompilationUnit* cu, const MethodReference& target_method) {
// Emit relative calls anywhere in the image or within a dex file otherwise.
- return cu->compiler_driver->IsImage() || cu->dex_file == target_method.dex_file;
+ return cu->compiler_driver->IsBootImage() || cu->dex_file == target_method.dex_file;
}
/*
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index d055b37..aa5e411 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -341,7 +341,7 @@
Compiler::Kind compiler_kind,
InstructionSet instruction_set,
const InstructionSetFeatures* instruction_set_features,
- bool image, std::unordered_set<std::string>* image_classes,
+ bool boot_image, std::unordered_set<std::string>* image_classes,
std::unordered_set<std::string>* compiled_classes,
std::unordered_set<std::string>* compiled_methods,
size_t thread_count, bool dump_stats, bool dump_passes,
@@ -361,7 +361,7 @@
compiled_methods_lock_("compiled method lock"),
compiled_methods_(MethodTable::key_compare()),
non_relative_linker_patch_count_(0u),
- image_(image),
+ boot_image_(boot_image),
image_classes_(image_classes),
classes_to_compile_(compiled_classes),
methods_to_compile_(compiled_methods),
@@ -383,7 +383,7 @@
compiler_->Init();
- CHECK_EQ(image_, image_classes_.get() != nullptr);
+ CHECK_EQ(boot_image_, image_classes_.get() != nullptr);
// Read the profile file if one is provided.
if (!profile_file.empty()) {
@@ -559,7 +559,7 @@
}
} else if ((access_flags & kAccAbstract) != 0) {
// Abstract methods don't have code.
- } else if (Runtime::Current()->IsAotCompiler()) {
+ } else {
const VerifiedMethod* verified_method =
driver->GetVerificationResults()->GetVerifiedMethod(method_ref);
bool compile = compilation_enabled &&
@@ -598,13 +598,6 @@
? dex_to_dex_compilation_level
: optimizer::DexToDexCompilationLevel::kRequired);
}
- } else {
- // This is for the JIT compiler, which has already ensured the class is verified.
- // We can go straight to compiling.
- DCHECK(Runtime::Current()->UseJit());
- compiled_method = driver->GetCompiler()->Compile(code_item, access_flags, invoke_type,
- class_def_idx, method_idx, class_loader,
- dex_file, dex_cache);
}
if (kTimeCompileMethod) {
uint64_t duration_ns = NanoTime() - start_ns;
@@ -696,42 +689,6 @@
self->GetJniEnv()->DeleteGlobalRef(jclass_loader);
}
-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();
- StackHandleScope<2> hs(self);
- Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
- method->GetDeclaringClass()->GetClassLoader()));
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache()));
- jobject jclass_loader = class_loader.ToJObject();
- const DexFile* dex_file = method->GetDexFile();
- const uint16_t class_def_idx = method->GetClassDefIndex();
- const DexFile::ClassDef& class_def = dex_file->GetClassDef(class_def_idx);
- optimizer::DexToDexCompilationLevel dex_to_dex_compilation_level =
- GetDexToDexCompilationLevel(self, *this, class_loader, *dex_file, class_def);
- const DexFile::CodeItem* code_item = dex_file->GetCodeItem(method->GetCodeItemOffset());
- // Go to native so that we don't block GC during compilation.
- ScopedThreadSuspension sts(self, kNative);
- CompileMethod(self,
- this,
- code_item,
- access_flags,
- invoke_type,
- class_def_idx,
- method_idx,
- jclass_loader,
- *dex_file,
- dex_to_dex_compilation_level,
- true,
- dex_cache);
- auto* compiled_method = GetCompiledMethod(MethodReference(dex_file, method_idx));
- return compiled_method;
-}
-
void CompilerDriver::Resolve(jobject class_loader, const std::vector<const DexFile*>& dex_files,
ThreadPool* thread_pool, TimingLogger* timings) {
for (size_t i = 0; i != dex_files.size(); ++i) {
@@ -781,7 +738,7 @@
}
bool CompilerDriver::IsImageClass(const char* descriptor) const {
- if (!IsImage()) {
+ if (!IsBootImage()) {
// NOTE: Currently unreachable, all callers check IsImage().
return false;
} else {
@@ -790,7 +747,7 @@
}
bool CompilerDriver::IsClassToCompile(const char* descriptor) const {
- if (kRestrictCompilationFiltersToImage && !IsImage()) {
+ if (kRestrictCompilationFiltersToImage && !IsBootImage()) {
return true;
}
@@ -801,7 +758,7 @@
}
bool CompilerDriver::IsMethodToCompile(const MethodReference& method_ref) const {
- if (kRestrictCompilationFiltersToImage && !IsImage()) {
+ if (kRestrictCompilationFiltersToImage && !IsBootImage()) {
return true;
}
@@ -889,7 +846,7 @@
// Make a list of descriptors for classes to include in the image
void CompilerDriver::LoadImageClasses(TimingLogger* timings) {
CHECK(timings != nullptr);
- if (!IsImage()) {
+ if (!IsBootImage()) {
return;
}
@@ -1118,7 +1075,7 @@
};
void CompilerDriver::UpdateImageClasses(TimingLogger* timings) {
- if (IsImage()) {
+ if (IsBootImage()) {
TimingLogger::ScopedTiming t("UpdateImageClasses", timings);
Runtime* runtime = Runtime::Current();
@@ -1145,7 +1102,7 @@
// Having the klass reference here implies that the klass is already loaded.
return true;
}
- if (!IsImage()) {
+ if (!IsBootImage()) {
// Assume loaded only if klass is in the boot image. App classes cannot be assumed
// loaded because we don't even know what class loader will be used to load them.
bool class_in_image = runtime->GetHeap()->FindSpaceFromObject(klass, false)->IsImageSpace();
@@ -1157,7 +1114,7 @@
}
bool CompilerDriver::CanAssumeTypeIsPresentInDexCache(const DexFile& dex_file, uint32_t type_idx) {
- if (IsImage() &&
+ if (IsBootImage() &&
IsImageClass(dex_file.StringDataByIdx(dex_file.GetTypeId(type_idx).descriptor_idx_))) {
{
ScopedObjectAccess soa(Thread::Current());
@@ -1183,7 +1140,7 @@
// See also Compiler::ResolveDexFile
bool result = false;
- if (IsImage()) {
+ if (IsBootImage()) {
// We resolve all const-string strings when building for the image.
ScopedObjectAccess soa(Thread::Current());
StackHandleScope<1> hs(soa.Self());
@@ -1300,7 +1257,7 @@
if (compiling_boot) {
// boot -> boot class pointers.
// True if the class is in the image at boot compiling time.
- const bool is_image_class = IsImage() && IsImageClass(
+ const bool is_image_class = IsBootImage() && IsImageClass(
dex_file.StringDataByIdx(dex_file.GetTypeId(type_idx).descriptor_idx_));
// True if pc relative load works.
if (is_image_class && support_boot_image_fixup) {
@@ -1548,7 +1505,7 @@
}
if (!use_dex_cache && force_relocations) {
bool is_in_image;
- if (IsImage()) {
+ if (IsBootImage()) {
is_in_image = IsImageClass(method->GetDeclaringClassDescriptor());
} else {
is_in_image = instruction_set_ != kX86 && instruction_set_ != kX86_64 &&
@@ -2019,7 +1976,7 @@
ParallelCompilationManager context(class_linker, class_loader, this, &dex_file, dex_files,
thread_pool);
- if (IsImage()) {
+ if (IsBootImage()) {
// For images we resolve all types, such as array, whereas for applications just those with
// classdefs are resolved by ResolveClassFieldsAndMethods.
TimingLogger::ScopedTiming t("Resolve Types", timings);
@@ -2101,8 +2058,8 @@
// It is *very* problematic if there are verification errors in the boot classpath. For example,
// we rely on things working OK without verification when the decryption dialog is brought up.
// So abort in a debug build if we find this violated.
- DCHECK(!manager_->GetCompiler()->IsImage() || klass->IsVerified()) << "Boot classpath class "
- << PrettyClass(klass.Get()) << " failed to fully verify.";
+ DCHECK(!manager_->GetCompiler()->IsBootImage() || klass->IsVerified())
+ << "Boot classpath class " << PrettyClass(klass.Get()) << " failed to fully verify.";
}
soa.Self()->AssertNoPendingException();
}
@@ -2222,7 +2179,7 @@
if (!klass->IsInitialized()) {
// We need to initialize static fields, we only do this for image classes that aren't
// marked with the $NoPreloadHolder (which implies this should not be initialized early).
- bool can_init_static_fields = manager_->GetCompiler()->IsImage() &&
+ bool can_init_static_fields = manager_->GetCompiler()->IsBootImage() &&
manager_->GetCompiler()->IsImageClass(descriptor) &&
!StringPiece(descriptor).ends_with("$NoPreloadHolder;");
if (can_init_static_fields) {
@@ -2286,7 +2243,7 @@
ParallelCompilationManager context(class_linker, jni_class_loader, this, &dex_file, dex_files,
thread_pool);
size_t thread_count;
- if (IsImage()) {
+ if (IsBootImage()) {
// TODO: remove this when transactional mode supports multithreading.
thread_count = 1U;
} else {
@@ -2304,7 +2261,7 @@
CHECK(dex_file != nullptr);
InitializeClasses(class_loader, *dex_file, dex_files, thread_pool, timings);
}
- if (IsImage()) {
+ if (IsBootImage()) {
// Prune garbage objects created during aborted transactions.
Runtime::Current()->GetHeap()->CollectGarbage(true);
}
diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h
index 4ed4dc6..5683b03 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -92,7 +92,7 @@
Compiler::Kind compiler_kind,
InstructionSet instruction_set,
const InstructionSetFeatures* instruction_set_features,
- bool image, std::unordered_set<std::string>* image_classes,
+ bool boot_image, std::unordered_set<std::string>* image_classes,
std::unordered_set<std::string>* compiled_classes,
std::unordered_set<std::string>* compiled_methods,
size_t thread_count, bool dump_stats, bool dump_passes,
@@ -119,9 +119,6 @@
TimingLogger* timings)
REQUIRES(!Locks::mutator_lock_, !compiled_classes_lock_);
- CompiledMethod* CompileArtMethod(Thread* self, ArtMethod*)
- SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!compiled_methods_lock_) WARN_UNUSED;
-
// Compile a single Method.
void CompileOne(Thread* self, ArtMethod* method, TimingLogger* timings)
SHARED_REQUIRES(Locks::mutator_lock_)
@@ -156,8 +153,8 @@
}
// Are we compiling and creating an image file?
- bool IsImage() const {
- return image_;
+ bool IsBootImage() const {
+ return boot_image_;
}
const std::unordered_set<std::string>* GetImageClasses() const {
@@ -637,7 +634,7 @@
// in the .oat_patches ELF section if requested in the compiler options.
size_t non_relative_linker_patch_count_ GUARDED_BY(compiled_methods_lock_);
- const bool image_;
+ const bool boot_image_;
// If image_ is true, specifies the classes that will be included in
// the image. Note if image_classes_ is null, all classes are
diff --git a/compiler/elf_builder.h b/compiler/elf_builder.h
index bbd962f..e977798 100644
--- a/compiler/elf_builder.h
+++ b/compiler/elf_builder.h
@@ -266,8 +266,8 @@
// Writer of .dynstr .strtab and .shstrtab sections.
class StrtabSection FINAL : public Section {
public:
- StrtabSection(const std::string& name, Elf_Word flags)
- : Section(name, SHT_STRTAB, flags, nullptr, 0, 1, 0) {
+ StrtabSection(const std::string& name, Elf_Word flags, Elf_Word align)
+ : Section(name, SHT_STRTAB, flags, nullptr, 0, align, 0) {
buffer_.reserve(4 * KB);
// The first entry of strtab must be empty string.
buffer_ += '\0';
@@ -459,16 +459,8 @@
private:
Elf_Word GetNumBuckets() const {
const auto& symbols = symtab_->symbols_;
- if (symbols.size() < 8) {
- return 2;
- } else if (symbols.size() < 32) {
- return 4;
- } else if (symbols.size() < 256) {
- return 16;
- } else {
- // Have about 32 ids per bucket.
- return RoundUp(symbols.size()/32, 2);
- }
+ // Have about 32 ids per bucket.
+ return 1 + symbols.size()/32;
}
// from bionic
@@ -495,7 +487,7 @@
Elf_Word text_size, CodeOutput* text_writer,
Elf_Word bss_size)
: isa_(isa),
- dynstr_(".dynstr", SHF_ALLOC),
+ dynstr_(".dynstr", SHF_ALLOC, kPageSize),
dynsym_(".dynsym", SHT_DYNSYM, SHF_ALLOC, &dynstr_),
hash_(".hash", SHF_ALLOC, &dynsym_),
rodata_(".rodata", SHT_PROGBITS, SHF_ALLOC,
@@ -504,9 +496,9 @@
nullptr, 0, kPageSize, 0, text_size, text_writer),
bss_(".bss", bss_size),
dynamic_(".dynamic", &dynstr_),
- strtab_(".strtab", 0),
+ strtab_(".strtab", 0, kPageSize),
symtab_(".symtab", SHT_SYMTAB, 0, &strtab_),
- shstrtab_(".shstrtab", 0) {
+ shstrtab_(".shstrtab", 0, 1) {
}
~ElfBuilder() {}
@@ -606,18 +598,19 @@
// Create a list of all section which we want to write.
// This is the order in which they will be written.
std::vector<Section*> sections;
- sections.push_back(&dynsym_);
- sections.push_back(&dynstr_);
- sections.push_back(&hash_);
sections.push_back(&rodata_);
+ // Need to write text to update checksum of header even if it is empty.
sections.push_back(&text_);
if (bss_.GetSize() != 0u) {
sections.push_back(&bss_);
}
+ sections.push_back(&dynstr_);
+ sections.push_back(&dynsym_);
+ sections.push_back(&hash_);
sections.push_back(&dynamic_);
if (!symtab_.IsEmpty()) {
- sections.push_back(&symtab_);
sections.push_back(&strtab_);
+ sections.push_back(&symtab_);
}
for (Section* section : other_sections_) {
sections.push_back(section);
@@ -643,7 +636,7 @@
// We do not know the number of headers until the final stages of write.
// It is easiest to just reserve a fixed amount of space for them.
- constexpr size_t kMaxProgramHeaders = 8;
+ constexpr size_t kMaxProgramHeaders = 16;
constexpr size_t kProgramHeadersOffset = sizeof(Elf_Ehdr);
// Layout of all sections - determine the final file offsets and addresses.
@@ -690,10 +683,17 @@
const Elf_Shdr* rodata = rodata_.GetHeader();
program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R,
0, rodata->sh_offset + rodata->sh_size, rodata->sh_addralign));
- program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R | PF_X, text_));
+ if (text_.GetHeader()->sh_size != 0u) {
+ program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R | PF_X, text_));
+ }
if (bss_.GetHeader()->sh_size != 0u) {
program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R | PF_W, bss_));
}
+ program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R, dynstr_));
+ int dynstr_dynsym_hash_size = hash_.GetHeader()->sh_offset +
+ hash_.GetHeader()->sh_size - dynstr_.GetHeader()->sh_offset;
+ program_headers.back().p_filesz = dynstr_dynsym_hash_size;
+ program_headers.back().p_memsz = dynstr_dynsym_hash_size;
program_headers.push_back(MakeProgramHeader(PT_LOAD, PF_R | PF_W, dynamic_));
program_headers.push_back(MakeProgramHeader(PT_DYNAMIC, PF_R | PF_W, dynamic_));
const Section* eh_frame = FindSection(".eh_frame");
@@ -912,10 +912,16 @@
void BuildDynsymSection() {
dynsym_.AddSymbol("oatdata", &rodata_, 0, true,
rodata_.GetSize(), STB_GLOBAL, STT_OBJECT);
- dynsym_.AddSymbol("oatexec", &text_, 0, true,
- text_.GetSize(), STB_GLOBAL, STT_OBJECT);
- dynsym_.AddSymbol("oatlastword", &text_, text_.GetSize() - 4,
- true, 4, STB_GLOBAL, STT_OBJECT);
+ if (text_.GetSize() != 0u) {
+ dynsym_.AddSymbol("oatexec", &text_, 0, true,
+ text_.GetSize(), STB_GLOBAL, STT_OBJECT);
+ dynsym_.AddSymbol("oatlastword", &text_, text_.GetSize() - 4,
+ true, 4, STB_GLOBAL, STT_OBJECT);
+ } else if (rodata_.GetSize() != 0) {
+ // rodata_ be size 0 for dwarf_test.
+ dynsym_.AddSymbol("oatlastword", &rodata_, rodata_.GetSize() - 4,
+ true, 4, STB_GLOBAL, STT_OBJECT);
+ }
if (bss_.GetSize() != 0u) {
dynsym_.AddSymbol("oatbss", &bss_, 0, true,
bss_.GetSize(), STB_GLOBAL, STT_OBJECT);
diff --git a/compiler/image_test.cc b/compiler/image_test.cc
index fd6cd82..a38e1f5 100644
--- a/compiler/image_test.cc
+++ b/compiler/image_test.cc
@@ -64,8 +64,10 @@
ScratchFile oat_file(OS::CreateEmptyFile(oat_filename.c_str()));
const uintptr_t requested_image_base = ART_BASE_ADDRESS;
- std::unique_ptr<ImageWriter> writer(new ImageWriter(*compiler_driver_, requested_image_base,
- /*compile_pic*/false));
+ std::unique_ptr<ImageWriter> writer(new ImageWriter(*compiler_driver_,
+ requested_image_base,
+ /*compile_pic*/false,
+ /*compile_app_image*/false));
// TODO: compile_pic should be a test argument.
{
{
@@ -81,8 +83,15 @@
t.NewTiming("WriteElf");
SafeMap<std::string, std::string> key_value_store;
- OatWriter oat_writer(class_linker->GetBootClassPath(), 0, 0, 0, compiler_driver_.get(),
- writer.get(), &timings, &key_value_store);
+ OatWriter oat_writer(class_linker->GetBootClassPath(),
+ 0,
+ 0,
+ 0,
+ compiler_driver_.get(),
+ writer.get(),
+ /*compiling_boot_image*/true,
+ &timings,
+ &key_value_store);
bool success = writer->PrepareImageAddressSpace() &&
compiler_driver_->WriteElf(GetTestAndroidRoot(),
!kIsTargetBuild,
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index af2a4f9..0c85323 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -20,6 +20,7 @@
#include <memory>
#include <numeric>
+#include <unordered_set>
#include <vector>
#include "art_field-inl.h"
@@ -72,6 +73,27 @@
// Separate objects into multiple bins to optimize dirty memory use.
static constexpr bool kBinObjects = true;
+// Return true if an object is already in an image space.
+bool ImageWriter::IsInBootImage(const void* obj) const {
+ if (!compile_app_image_) {
+ DCHECK(boot_image_space_ == nullptr);
+ return false;
+ }
+ const uint8_t* image_begin = boot_image_space_->Begin();
+ // Real image end including ArtMethods and ArtField sections.
+ const uint8_t* image_end = image_begin + boot_image_space_->GetImageHeader().GetImageSize();
+ return image_begin <= obj && obj < image_end;
+}
+
+bool ImageWriter::IsInBootOatFile(const void* ptr) const {
+ if (!compile_app_image_) {
+ DCHECK(boot_image_space_ == nullptr);
+ return false;
+ }
+ const ImageHeader& image_header = boot_image_space_->GetImageHeader();
+ return image_header.GetOatFileBegin() <= ptr && ptr < image_header.GetOatFileEnd();
+}
+
static void CheckNoDexObjectsCallback(Object* obj, void* arg ATTRIBUTE_UNUSED)
SHARED_REQUIRES(Locks::mutator_lock_) {
Class* klass = obj->GetClass();
@@ -85,12 +107,20 @@
bool ImageWriter::PrepareImageAddressSpace() {
target_ptr_size_ = InstructionSetPointerSize(compiler_driver_.GetInstructionSet());
+ gc::Heap* const heap = Runtime::Current()->GetHeap();
+ // Cache boot image space.
+ for (gc::space::ContinuousSpace* space : heap->GetContinuousSpaces()) {
+ if (space->IsImageSpace()) {
+ CHECK(compile_app_image_);
+ CHECK(boot_image_space_ == nullptr) << "Multiple image spaces";
+ boot_image_space_ = space->AsImageSpace();
+ }
+ }
{
ScopedObjectAccess soa(Thread::Current());
PruneNonImageClasses(); // Remove junk
ComputeLazyFieldsForImageClasses(); // Add useful information
}
- gc::Heap* heap = Runtime::Current()->GetHeap();
heap->CollectGarbage(false); // Remove garbage.
// Dex caches must not have their dex fields set in the image. These are memory buffers of mapped
@@ -144,21 +174,21 @@
Runtime::Current()->GetOatFileManager().RegisterOatFile(
std::unique_ptr<const OatFile>(oat_file_));
- interpreter_to_interpreter_bridge_offset_ =
- oat_file_->GetOatHeader().GetInterpreterToInterpreterBridgeOffset();
- interpreter_to_compiled_code_bridge_offset_ =
- oat_file_->GetOatHeader().GetInterpreterToCompiledCodeBridgeOffset();
-
- jni_dlsym_lookup_offset_ = oat_file_->GetOatHeader().GetJniDlsymLookupOffset();
-
- quick_generic_jni_trampoline_offset_ =
- oat_file_->GetOatHeader().GetQuickGenericJniTrampolineOffset();
- quick_imt_conflict_trampoline_offset_ =
- oat_file_->GetOatHeader().GetQuickImtConflictTrampolineOffset();
- quick_resolution_trampoline_offset_ =
- oat_file_->GetOatHeader().GetQuickResolutionTrampolineOffset();
- quick_to_interpreter_bridge_offset_ =
- oat_file_->GetOatHeader().GetQuickToInterpreterBridgeOffset();
+ const OatHeader& oat_header = oat_file_->GetOatHeader();
+ oat_address_offsets_[kOatAddressInterpreterToInterpreterBridge] =
+ oat_header.GetInterpreterToInterpreterBridgeOffset();
+ oat_address_offsets_[kOatAddressInterpreterToCompiledCodeBridge] =
+ oat_header.GetInterpreterToCompiledCodeBridgeOffset();
+ oat_address_offsets_[kOatAddressJNIDlsymLookup] =
+ oat_header.GetJniDlsymLookupOffset();
+ oat_address_offsets_[kOatAddressQuickGenericJNITrampoline] =
+ oat_header.GetQuickGenericJniTrampolineOffset();
+ oat_address_offsets_[kOatAddressQuickIMTConflictTrampoline] =
+ oat_header.GetQuickImtConflictTrampolineOffset();
+ oat_address_offsets_[kOatAddressQuickResolutionTrampoline] =
+ oat_header.GetQuickResolutionTrampolineOffset();
+ oat_address_offsets_[kOatAddressQuickToInterpreterBridge] =
+ oat_header.GetQuickToInterpreterBridgeOffset();
size_t oat_loaded_size = 0;
size_t oat_data_offset = 0;
@@ -307,7 +337,7 @@
for (jobject weak_root : class_linker->GetDexCaches()) {
mirror::DexCache* dex_cache =
down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
- if (dex_cache == nullptr) {
+ if (dex_cache == nullptr || IsInBootImage(dex_cache)) {
continue;
}
const DexFile* dex_file = dex_cache->GetDexFile();
@@ -331,6 +361,7 @@
void ImageWriter::AddDexCacheArrayRelocation(void* array, size_t offset) {
if (array != nullptr) {
+ DCHECK(!IsInBootImage(array));
native_object_relocations_.emplace(
array,
NativeObjectRelocation { offset, kNativeObjectRelocationTypeDexCacheArray });
@@ -344,8 +375,8 @@
auto* method = arr->GetElementPtrSize<ArtMethod*>(i, target_ptr_size_);
if (method != nullptr && !method->IsRuntimeMethod()) {
auto* klass = method->GetDeclaringClass();
- CHECK(klass == nullptr || IsImageClass(klass)) << PrettyClass(klass)
- << " should be an image class";
+ CHECK(klass == nullptr || KeepClass(klass))
+ << PrettyClass(klass) << " should be a kept class";
}
}
}
@@ -539,10 +570,66 @@
class_linker->VisitClassesWithoutClassesLock(&visitor);
}
-bool ImageWriter::IsImageClass(Class* klass) {
+static bool IsBootClassLoaderClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_) {
+ return klass->GetClassLoader() == nullptr;
+}
+
+bool ImageWriter::IsBootClassLoaderNonImageClass(mirror::Class* klass) {
+ return IsBootClassLoaderClass(klass) && !IsInBootImage(klass);
+}
+
+bool ImageWriter::ContainsBootClassLoaderNonImageClass(mirror::Class* klass) {
if (klass == nullptr) {
return false;
}
+ auto found = prune_class_memo_.find(klass);
+ if (found != prune_class_memo_.end()) {
+ // Already computed, return the found value.
+ return found->second;
+ }
+ // Place holder value to prevent infinite recursion.
+ prune_class_memo_.emplace(klass, false);
+ bool result = IsBootClassLoaderNonImageClass(klass);
+ if (!result) {
+ // Check interfaces since these wont be visited through VisitReferences.)
+ mirror::IfTable* if_table = klass->GetIfTable();
+ for (size_t i = 0, num_interfaces = klass->GetIfTableCount(); i < num_interfaces; ++i) {
+ result = result || ContainsBootClassLoaderNonImageClass(if_table->GetInterface(i));
+ }
+ }
+ // Check static fields and their classes.
+ size_t num_static_fields = klass->NumReferenceStaticFields();
+ if (num_static_fields != 0 && klass->IsResolved()) {
+ // Presumably GC can happen when we are cross compiling, it should not cause performance
+ // problems to do pointer size logic.
+ MemberOffset field_offset = klass->GetFirstReferenceStaticFieldOffset(
+ Runtime::Current()->GetClassLinker()->GetImagePointerSize());
+ for (size_t i = 0u; i < num_static_fields; ++i) {
+ mirror::Object* ref = klass->GetFieldObject<mirror::Object>(field_offset);
+ if (ref != nullptr) {
+ if (ref->IsClass()) {
+ result = result || ContainsBootClassLoaderNonImageClass(ref->AsClass());
+ }
+ result = result || ContainsBootClassLoaderNonImageClass(ref->GetClass());
+ }
+ field_offset = MemberOffset(field_offset.Uint32Value() +
+ sizeof(mirror::HeapReference<mirror::Object>));
+ }
+ }
+ result = result || ContainsBootClassLoaderNonImageClass(klass->GetSuperClass());
+ prune_class_memo_[klass] = result;
+ return result;
+}
+
+bool ImageWriter::KeepClass(Class* klass) {
+ if (klass == nullptr) {
+ return false;
+ }
+ if (compile_app_image_) {
+ // For app images, we need to prune boot loader classes that are not in the boot image since
+ // these may have already been loaded when the app image is loaded.
+ return !ContainsBootClassLoaderNonImageClass(klass);
+ }
std::string temp;
return compiler_driver_.IsImageClass(klass->GetDescriptor(&temp));
}
@@ -552,21 +639,17 @@
explicit NonImageClassesVisitor(ImageWriter* image_writer) : image_writer_(image_writer) {}
bool Visit(Class* klass) OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) {
- if (!image_writer_->IsImageClass(klass)) {
- std::string temp;
- non_image_classes_.insert(klass->GetDescriptor(&temp));
+ if (!image_writer_->KeepClass(klass)) {
+ classes_to_prune_.insert(klass);
}
return true;
}
- std::set<std::string> non_image_classes_;
+ std::unordered_set<mirror::Class*> classes_to_prune_;
ImageWriter* const image_writer_;
};
void ImageWriter::PruneNonImageClasses() {
- if (compiler_driver_.GetImageClasses() == nullptr) {
- return;
- }
Runtime* runtime = Runtime::Current();
ClassLinker* class_linker = runtime->GetClassLinker();
Thread* self = Thread::Current();
@@ -576,8 +659,14 @@
class_linker->VisitClasses(&visitor);
// Remove the undesired classes from the class roots.
- for (const std::string& it : visitor.non_image_classes_) {
- bool result = class_linker->RemoveClass(it.c_str(), nullptr);
+ for (mirror::Class* klass : visitor.classes_to_prune_) {
+ std::string temp;
+ const char* name = klass->GetDescriptor(&temp);
+ VLOG(compiler) << "Pruning class " << name;
+ if (!compile_app_image_) {
+ DCHECK(IsBootClassLoaderClass(klass));
+ }
+ bool result = class_linker->RemoveClass(name, klass->GetClassLoader());
DCHECK(result);
}
@@ -594,7 +683,7 @@
}
for (size_t i = 0; i < dex_cache->NumResolvedTypes(); i++) {
Class* klass = dex_cache->GetResolvedType(i);
- if (klass != nullptr && !IsImageClass(klass)) {
+ if (klass != nullptr && !KeepClass(klass)) {
dex_cache->SetResolvedType(i, nullptr);
}
}
@@ -607,7 +696,7 @@
// Miranda methods may be held live by a class which was not an image class but have a
// declaring class which is an image class. Set it to the resolution method to be safe and
// prevent dangling pointers.
- if (method->IsMiranda() || !IsImageClass(declaring_class)) {
+ if (method->IsMiranda() || !KeepClass(declaring_class)) {
mirror::DexCache::SetElementPtrSize(resolved_methods,
i,
resolution_method,
@@ -621,7 +710,7 @@
}
for (size_t i = 0; i < dex_cache->NumResolvedFields(); i++) {
ArtField* field = dex_cache->GetResolvedField(i, target_ptr_size_);
- if (field != nullptr && !IsImageClass(field->GetDeclaringClass())) {
+ if (field != nullptr && !KeepClass(field->GetDeclaringClass())) {
dex_cache->SetResolvedField(i, nullptr, target_ptr_size_);
}
}
@@ -632,6 +721,9 @@
// Drop the array class cache in the ClassLinker, as these are roots holding those classes live.
class_linker->DropFindArrayClassCache();
+
+ // Clear to save RAM.
+ prune_class_memo_.clear();
}
void ImageWriter::CheckNonImageClassesRemoved() {
@@ -643,13 +735,13 @@
void ImageWriter::CheckNonImageClassesRemovedCallback(Object* obj, void* arg) {
ImageWriter* image_writer = reinterpret_cast<ImageWriter*>(arg);
- if (obj->IsClass()) {
+ if (obj->IsClass() && !image_writer->IsInBootImage(obj)) {
Class* klass = obj->AsClass();
- if (!image_writer->IsImageClass(klass)) {
+ if (!image_writer->KeepClass(klass)) {
image_writer->DumpImageClasses();
std::string temp;
- CHECK(image_writer->IsImageClass(klass)) << klass->GetDescriptor(&temp)
- << " " << PrettyDescriptor(klass);
+ CHECK(image_writer->KeepClass(klass)) << klass->GetDescriptor(&temp)
+ << " " << PrettyDescriptor(klass);
}
}
}
@@ -703,25 +795,35 @@
// ObjectArray, we lock the dex lock twice, first to get the number
// of dex caches first and then lock it again to copy the dex
// caches. We check that the number of dex caches does not change.
- size_t dex_cache_count;
+ size_t dex_cache_count = 0;
{
ReaderMutexLock mu(self, *class_linker->DexLock());
- dex_cache_count = class_linker->GetDexCacheCount();
+ // Count number of dex caches not in the boot image.
+ for (jobject weak_root : class_linker->GetDexCaches()) {
+ mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
+ dex_cache_count += IsInBootImage(dex_cache) ? 0u : 1u;
+ }
}
Handle<ObjectArray<Object>> dex_caches(
- hs.NewHandle(ObjectArray<Object>::Alloc(self, object_array_class.Get(),
- dex_cache_count)));
+ hs.NewHandle(ObjectArray<Object>::Alloc(self, object_array_class.Get(), dex_cache_count)));
CHECK(dex_caches.Get() != nullptr) << "Failed to allocate a dex cache array.";
{
ReaderMutexLock mu(self, *class_linker->DexLock());
- CHECK_EQ(dex_cache_count, class_linker->GetDexCacheCount())
- << "The number of dex caches changed.";
+ size_t non_image_dex_caches = 0;
+ // Re-count number of non image dex caches.
+ for (jobject weak_root : class_linker->GetDexCaches()) {
+ mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
+ non_image_dex_caches += IsInBootImage(dex_cache) ? 0u : 1u;
+ }
+ CHECK_EQ(dex_cache_count, non_image_dex_caches)
+ << "The number of non-image dex caches changed.";
size_t i = 0;
for (jobject weak_root : class_linker->GetDexCaches()) {
- mirror::DexCache* dex_cache =
- down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
- dex_caches->Set<false>(i, dex_cache);
- ++i;
+ mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
+ if (!IsInBootImage(dex_cache)) {
+ dex_caches->Set<false>(i, dex_cache);
+ ++i;
+ }
}
}
@@ -761,6 +863,10 @@
// For an unvisited object, visit it then all its children found via fields.
void ImageWriter::WalkFieldsInOrder(mirror::Object* obj) {
+ if (IsInBootImage(obj)) {
+ // Object is in the image, don't need to fix it up.
+ return;
+ }
// Use our own visitor routine (instead of GC visitor) to get better locality between
// an object and its fields
if (!IsImageBinSlotAssigned(obj)) {
@@ -797,6 +903,7 @@
CHECK(it == native_object_relocations_.end()) << "Field array " << cur_fields
<< " already forwarded";
size_t& offset = bin_slot_sizes_[kBinArtField];
+ DCHECK(!IsInBootImage(cur_fields));
native_object_relocations_.emplace(
cur_fields, NativeObjectRelocation {
offset, kNativeObjectRelocationTypeArtFieldArray });
@@ -808,6 +915,7 @@
auto it2 = native_object_relocations_.find(field);
CHECK(it2 == native_object_relocations_.end()) << "Field at index=" << i
<< " already assigned " << PrettyField(field) << " static=" << field->IsStatic();
+ DCHECK(!IsInBootImage(field));
native_object_relocations_.emplace(
field, NativeObjectRelocation {offset, kNativeObjectRelocationTypeArtField });
offset += sizeof(ArtField);
@@ -843,6 +951,7 @@
CHECK(it == native_object_relocations_.end()) << "Method array " << array
<< " already forwarded";
size_t& offset = bin_slot_sizes_[bin_type];
+ DCHECK(!IsInBootImage(array));
native_object_relocations_.emplace(array, NativeObjectRelocation { offset,
any_dirty ? kNativeObjectRelocationTypeArtMethodArrayDirty :
kNativeObjectRelocationTypeArtMethodArrayClean });
@@ -867,6 +976,7 @@
}
void ImageWriter::AssignMethodOffset(ArtMethod* method, NativeObjectRelocationType type) {
+ DCHECK(!IsInBootImage(method));
auto it = native_object_relocations_.find(method);
CHECK(it == native_object_relocations_.end()) << "Method " << method << " already assigned "
<< PrettyMethod(method);
@@ -884,10 +994,13 @@
void ImageWriter::UnbinObjectsIntoOffsetCallback(mirror::Object* obj, void* arg) {
ImageWriter* writer = reinterpret_cast<ImageWriter*>(arg);
DCHECK(writer != nullptr);
- writer->UnbinObjectsIntoOffset(obj);
+ if (!writer->IsInBootImage(obj)) {
+ writer->UnbinObjectsIntoOffset(obj);
+ }
}
void ImageWriter::UnbinObjectsIntoOffset(mirror::Object* obj) {
+ DCHECK(!IsInBootImage(obj));
CHECK(obj != nullptr);
// We know the bin slot, and the total bin sizes for all objects by now,
@@ -925,13 +1038,15 @@
image_methods_[ImageHeader::kRefsAndArgsSaveMethod] =
runtime->GetCalleeSaveMethod(Runtime::kRefsAndArgs);
- // Add room for fake length prefixed array.
+ // Add room for fake length prefixed array for holding the image methods.
const auto image_method_type = kNativeObjectRelocationTypeArtMethodArrayClean;
auto it = native_object_relocations_.find(&image_method_array_);
CHECK(it == native_object_relocations_.end());
size_t& offset = bin_slot_sizes_[BinTypeForNativeRelocationType(image_method_type)];
- native_object_relocations_.emplace(&image_method_array_,
- NativeObjectRelocation { offset, image_method_type });
+ if (!compile_app_image_) {
+ native_object_relocations_.emplace(&image_method_array_,
+ NativeObjectRelocation { offset, image_method_type });
+ }
size_t method_alignment = ArtMethod::Alignment(target_ptr_size_);
const size_t array_size = LengthPrefixedArray<ArtMethod>::ComputeSize(
0, ArtMethod::Size(target_ptr_size_), method_alignment);
@@ -940,7 +1055,10 @@
for (auto* m : image_methods_) {
CHECK(m != nullptr);
CHECK(m->IsRuntimeMethod());
- AssignMethodOffset(m, kNativeObjectRelocationTypeArtMethodClean);
+ DCHECK_EQ(compile_app_image_, IsInBootImage(m)) << "Trampolines should be in boot image";
+ if (!IsInBootImage(m)) {
+ AssignMethodOffset(m, kNativeObjectRelocationTypeArtMethodClean);
+ }
}
// Calculate size of the dex cache arrays slot and prepare offsets.
PrepareDexCacheArraySlots();
@@ -1090,6 +1208,7 @@
NativeObjectRelocation& relocation = pair.second;
auto* dest = image_->Begin() + relocation.offset;
DCHECK_GE(dest, image_->Begin() + image_end_);
+ DCHECK(!IsInBootImage(pair.first));
switch (relocation.type) {
case kNativeObjectRelocationTypeArtField: {
memcpy(dest, pair.first, sizeof(ArtField));
@@ -1126,16 +1245,18 @@
auto* image_header = reinterpret_cast<ImageHeader*>(image_->Begin());
const ImageSection& methods_section = image_header->GetMethodsSection();
for (size_t i = 0; i < ImageHeader::kImageMethodsCount; ++i) {
- auto* m = image_methods_[i];
- CHECK(m != nullptr);
- auto it = native_object_relocations_.find(m);
- CHECK(it != native_object_relocations_.end()) << "No fowarding for " << PrettyMethod(m);
- NativeObjectRelocation& relocation = it->second;
- CHECK(methods_section.Contains(relocation.offset)) << relocation.offset << " not in "
- << methods_section;
- CHECK(relocation.IsArtMethodRelocation()) << relocation.type;
- auto* dest = reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset);
- image_header->SetImageMethod(static_cast<ImageHeader::ImageMethod>(i), dest);
+ ArtMethod* method = image_methods_[i];
+ CHECK(method != nullptr);
+ if (!IsInBootImage(method)) {
+ auto it = native_object_relocations_.find(method);
+ CHECK(it != native_object_relocations_.end()) << "No fowarding for " << PrettyMethod(method);
+ NativeObjectRelocation& relocation = it->second;
+ CHECK(methods_section.Contains(relocation.offset)) << relocation.offset << " not in "
+ << methods_section;
+ CHECK(relocation.IsArtMethodRelocation()) << relocation.type;
+ method = reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset);
+ }
+ image_header->SetImageMethod(static_cast<ImageHeader::ImageMethod>(i), method);
}
// Write the intern table into the image.
const ImageSection& intern_table_section = image_header->GetImageSection(
@@ -1183,8 +1304,8 @@
dst->SetClass(GetImageAddress(arr->GetClass()));
auto* dest_array = down_cast<mirror::PointerArray*>(dst);
for (size_t i = 0, count = num_elements; i < count; ++i) {
- auto* elem = arr->GetElementPtrSize<void*>(i, target_ptr_size_);
- if (elem != nullptr) {
+ void* elem = arr->GetElementPtrSize<void*>(i, target_ptr_size_);
+ if (elem != nullptr && !IsInBootImage(elem)) {
auto it = native_object_relocations_.find(elem);
if (UNLIKELY(it == native_object_relocations_.end())) {
if (it->second.IsArtMethodRelocation()) {
@@ -1209,6 +1330,9 @@
}
void ImageWriter::CopyAndFixupObject(Object* obj) {
+ if (IsInBootImage(obj)) {
+ return;
+ }
size_t offset = GetImageOffset(obj);
auto* dst = reinterpret_cast<Object*>(image_->Begin() + offset);
DCHECK_LT(offset, image_end_);
@@ -1282,18 +1406,19 @@
uintptr_t ImageWriter::NativeOffsetInImage(void* obj) {
DCHECK(obj != nullptr);
+ DCHECK(!IsInBootImage(obj));
auto it = native_object_relocations_.find(obj);
- CHECK(it != native_object_relocations_.end()) << obj;
+ CHECK(it != native_object_relocations_.end()) << obj << " spaces "
+ << Runtime::Current()->GetHeap()->DumpSpaces();
const NativeObjectRelocation& relocation = it->second;
return relocation.offset;
}
template <typename T>
T* ImageWriter::NativeLocationInImage(T* obj) {
- if (obj == nullptr) {
- return nullptr;
- }
- return reinterpret_cast<T*>(image_begin_ + NativeOffsetInImage(obj));
+ return (obj == nullptr || IsInBootImage(obj))
+ ? obj
+ : reinterpret_cast<T*>(image_begin_ + NativeOffsetInImage(obj));
}
void ImageWriter::FixupClass(mirror::Class* orig, mirror::Class* copy) {
@@ -1306,18 +1431,22 @@
// Update dex cache strings.
copy->SetDexCacheStrings(NativeLocationInImage(orig->GetDexCacheStrings()));
// Fix up embedded tables.
- if (orig->ShouldHaveEmbeddedImtAndVTable()) {
- for (int32_t i = 0; i < orig->GetEmbeddedVTableLength(); ++i) {
- auto it = native_object_relocations_.find(orig->GetEmbeddedVTableEntry(i, target_ptr_size_));
- CHECK(it != native_object_relocations_.end()) << PrettyClass(orig);
- copy->SetEmbeddedVTableEntryUnchecked(
- i, reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset), target_ptr_size_);
- }
- for (size_t i = 0; i < mirror::Class::kImtSize; ++i) {
- auto it = native_object_relocations_.find(orig->GetEmbeddedImTableEntry(i, target_ptr_size_));
- CHECK(it != native_object_relocations_.end()) << PrettyClass(orig);
- copy->SetEmbeddedImTableEntry(
- i, reinterpret_cast<ArtMethod*>(image_begin_ + it->second.offset), target_ptr_size_);
+ if (!orig->IsTemp()) {
+ // TODO: Why do we have temp classes in some cases?
+ if (orig->ShouldHaveEmbeddedImtAndVTable()) {
+ for (int32_t i = 0; i < orig->GetEmbeddedVTableLength(); ++i) {
+ ArtMethod* orig_method = orig->GetEmbeddedVTableEntry(i, target_ptr_size_);
+ copy->SetEmbeddedVTableEntryUnchecked(
+ i,
+ NativeLocationInImage(orig_method),
+ target_ptr_size_);
+ }
+ for (size_t i = 0; i < mirror::Class::kImtSize; ++i) {
+ copy->SetEmbeddedImTableEntry(
+ i,
+ NativeLocationInImage(orig->GetEmbeddedImTableEntry(i, target_ptr_size_)),
+ target_ptr_size_);
+ }
}
}
FixupClassVisitor visitor(this, copy);
@@ -1419,7 +1548,7 @@
reinterpret_cast<ArtMethod**>(image_->Begin() + copy_methods_offset);
for (size_t i = 0, num = orig_dex_cache->NumResolvedMethods(); i != num; ++i) {
ArtMethod* orig = mirror::DexCache::GetElementPtrSize(orig_methods, i, target_ptr_size_);
- ArtMethod* copy = NativeLocationInImage(orig);
+ ArtMethod* copy = IsInBootImage(orig) ? orig : NativeLocationInImage(orig);
mirror::DexCache::SetElementPtrSize(copy_methods, i, copy, target_ptr_size_);
}
}
@@ -1432,15 +1561,51 @@
ArtField** copy_fields = reinterpret_cast<ArtField**>(image_->Begin() + copy_fields_offset);
for (size_t i = 0, num = orig_dex_cache->NumResolvedFields(); i != num; ++i) {
ArtField* orig = mirror::DexCache::GetElementPtrSize(orig_fields, i, target_ptr_size_);
- ArtField* copy = NativeLocationInImage(orig);
+ ArtField* copy = IsInBootImage(orig) ? orig : NativeLocationInImage(orig);
mirror::DexCache::SetElementPtrSize(copy_fields, i, copy, target_ptr_size_);
}
}
}
+const uint8_t* ImageWriter::GetOatAddress(OatAddress type) const {
+ DCHECK_LT(type, kOatAddressCount);
+ // If we are compiling an app image, we need to use the stubs of the boot image.
+ if (compile_app_image_) {
+ // Use the current image pointers.
+ gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace();
+ DCHECK(image_space != nullptr);
+ const OatFile* oat_file = image_space->GetOatFile();
+ CHECK(oat_file != nullptr);
+ const OatHeader& header = oat_file->GetOatHeader();
+ switch (type) {
+ // TODO: We could maybe clean this up if we stored them in an array in the oat header.
+ case kOatAddressQuickGenericJNITrampoline:
+ return static_cast<const uint8_t*>(header.GetQuickGenericJniTrampoline());
+ case kOatAddressInterpreterToInterpreterBridge:
+ return static_cast<const uint8_t*>(header.GetInterpreterToInterpreterBridge());
+ case kOatAddressInterpreterToCompiledCodeBridge:
+ return static_cast<const uint8_t*>(header.GetInterpreterToCompiledCodeBridge());
+ case kOatAddressJNIDlsymLookup:
+ return static_cast<const uint8_t*>(header.GetJniDlsymLookup());
+ case kOatAddressQuickIMTConflictTrampoline:
+ return static_cast<const uint8_t*>(header.GetQuickImtConflictTrampoline());
+ case kOatAddressQuickResolutionTrampoline:
+ return static_cast<const uint8_t*>(header.GetQuickResolutionTrampoline());
+ case kOatAddressQuickToInterpreterBridge:
+ return static_cast<const uint8_t*>(header.GetQuickToInterpreterBridge());
+ default:
+ UNREACHABLE();
+ }
+ }
+ return GetOatAddressForOffset(oat_address_offsets_[type]);
+}
+
const uint8_t* ImageWriter::GetQuickCode(ArtMethod* method, bool* quick_is_interpreted) {
- DCHECK(!method->IsResolutionMethod() && !method->IsImtConflictMethod() &&
- !method->IsImtUnimplementedMethod() && !method->IsAbstract()) << PrettyMethod(method);
+ DCHECK(!method->IsResolutionMethod()) << PrettyMethod(method);
+ DCHECK(!method->IsImtConflictMethod()) << PrettyMethod(method);
+ DCHECK(!method->IsImtUnimplementedMethod()) << PrettyMethod(method);
+ DCHECK(!method->IsAbstract()) << PrettyMethod(method);
+ DCHECK(!IsInBootImage(method)) << PrettyMethod(method);
// Use original code if it exists. Otherwise, set the code pointer to the resolution
// trampoline.
@@ -1448,27 +1613,26 @@
// Quick entrypoint:
uint32_t quick_oat_code_offset = PointerToLowMemUInt32(
method->GetEntryPointFromQuickCompiledCodePtrSize(target_ptr_size_));
- const uint8_t* quick_code = GetOatAddress(quick_oat_code_offset);
+ const uint8_t* quick_code = GetOatAddressForOffset(quick_oat_code_offset);
*quick_is_interpreted = false;
if (quick_code != nullptr && (!method->IsStatic() || method->IsConstructor() ||
method->GetDeclaringClass()->IsInitialized())) {
// We have code for a non-static or initialized method, just use the code.
- DCHECK_GE(quick_code, oat_data_begin_);
} else if (quick_code == nullptr && method->IsNative() &&
(!method->IsStatic() || method->GetDeclaringClass()->IsInitialized())) {
// Non-static or initialized native method missing compiled code, use generic JNI version.
- quick_code = GetOatAddress(quick_generic_jni_trampoline_offset_);
- DCHECK_GE(quick_code, oat_data_begin_);
+ quick_code = GetOatAddress(kOatAddressQuickGenericJNITrampoline);
} else if (quick_code == nullptr && !method->IsNative()) {
// We don't have code at all for a non-native method, use the interpreter.
- quick_code = GetOatAddress(quick_to_interpreter_bridge_offset_);
+ quick_code = GetOatAddress(kOatAddressQuickToInterpreterBridge);
*quick_is_interpreted = true;
- DCHECK_GE(quick_code, oat_data_begin_);
} else {
CHECK(!method->GetDeclaringClass()->IsInitialized());
// We have code for a static method, but need to go through the resolution stub for class
// initialization.
- quick_code = GetOatAddress(quick_resolution_trampoline_offset_);
+ quick_code = GetOatAddress(kOatAddressQuickResolutionTrampoline);
+ }
+ if (!IsInBootOatFile(quick_code)) {
DCHECK_GE(quick_code, oat_data_begin_);
}
return quick_code;
@@ -1479,16 +1643,16 @@
// The resolution method has a special trampoline to call.
Runtime* runtime = Runtime::Current();
if (UNLIKELY(method == runtime->GetResolutionMethod())) {
- return GetOatAddress(quick_resolution_trampoline_offset_);
+ return GetOatAddress(kOatAddressQuickResolutionTrampoline);
} else if (UNLIKELY(method == runtime->GetImtConflictMethod() ||
method == runtime->GetImtUnimplementedMethod())) {
- return GetOatAddress(quick_imt_conflict_trampoline_offset_);
+ return GetOatAddress(kOatAddressQuickIMTConflictTrampoline);
} else {
// We assume all methods have code. If they don't currently then we set them to the use the
// resolution trampoline. Abstract methods never have code and so we need to make sure their
// use results in an AbstractMethodError. We use the interpreter to achieve this.
if (UNLIKELY(method->IsAbstract())) {
- return GetOatAddress(quick_to_interpreter_bridge_offset_);
+ return GetOatAddress(kOatAddressQuickToInterpreterBridge);
} else {
bool quick_is_interpreted;
return GetQuickCode(method, &quick_is_interpreted);
@@ -1513,11 +1677,11 @@
Runtime* runtime = Runtime::Current();
if (UNLIKELY(orig == runtime->GetResolutionMethod())) {
copy->SetEntryPointFromQuickCompiledCodePtrSize(
- GetOatAddress(quick_resolution_trampoline_offset_), target_ptr_size_);
+ GetOatAddress(kOatAddressQuickResolutionTrampoline), target_ptr_size_);
} else if (UNLIKELY(orig == runtime->GetImtConflictMethod() ||
orig == runtime->GetImtUnimplementedMethod())) {
copy->SetEntryPointFromQuickCompiledCodePtrSize(
- GetOatAddress(quick_imt_conflict_trampoline_offset_), target_ptr_size_);
+ GetOatAddress(kOatAddressQuickIMTConflictTrampoline), target_ptr_size_);
} else if (UNLIKELY(orig->IsRuntimeMethod())) {
bool found_one = false;
for (size_t i = 0; i < static_cast<size_t>(Runtime::kLastCalleeSaveType); ++i) {
@@ -1535,7 +1699,7 @@
// use results in an AbstractMethodError. We use the interpreter to achieve this.
if (UNLIKELY(orig->IsAbstract())) {
copy->SetEntryPointFromQuickCompiledCodePtrSize(
- GetOatAddress(quick_to_interpreter_bridge_offset_), target_ptr_size_);
+ GetOatAddress(kOatAddressQuickToInterpreterBridge), target_ptr_size_);
} else {
bool quick_is_interpreted;
const uint8_t* quick_code = GetQuickCode(orig, &quick_is_interpreted);
@@ -1546,7 +1710,7 @@
// The native method's pointer is set to a stub to lookup via dlsym.
// Note this is not the code_ pointer, that is handled above.
copy->SetEntryPointFromJniPtrSize(
- GetOatAddress(jni_dlsym_lookup_offset_), target_ptr_size_);
+ GetOatAddress(kOatAddressJNIDlsymLookup), target_ptr_size_);
}
}
}
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index 7a2febc..120de97 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -40,27 +40,42 @@
#include "utils.h"
namespace art {
+namespace gc {
+namespace space {
+class ImageSpace;
+} // namespace space
+} // namespace gc
static constexpr int kInvalidImageFd = -1;
// Write a Space built during compilation for use during execution.
class ImageWriter FINAL {
public:
- ImageWriter(const CompilerDriver& compiler_driver, uintptr_t image_begin,
- bool compile_pic)
- : compiler_driver_(compiler_driver), image_begin_(reinterpret_cast<uint8_t*>(image_begin)),
- image_end_(0), image_objects_offset_begin_(0), image_roots_address_(0), oat_file_(nullptr),
- oat_data_begin_(nullptr), interpreter_to_interpreter_bridge_offset_(0),
- interpreter_to_compiled_code_bridge_offset_(0), jni_dlsym_lookup_offset_(0),
- quick_generic_jni_trampoline_offset_(0),
- quick_imt_conflict_trampoline_offset_(0), quick_resolution_trampoline_offset_(0),
- quick_to_interpreter_bridge_offset_(0), compile_pic_(compile_pic),
+ ImageWriter(const CompilerDriver& compiler_driver,
+ uintptr_t image_begin,
+ bool compile_pic,
+ bool compile_app_image)
+ : compiler_driver_(compiler_driver),
+ image_begin_(reinterpret_cast<uint8_t*>(image_begin)),
+ image_end_(0),
+ image_objects_offset_begin_(0),
+ image_roots_address_(0),
+ oat_file_(nullptr),
+ oat_data_begin_(nullptr),
+ compile_pic_(compile_pic),
+ compile_app_image_(compile_app_image),
+ boot_image_space_(nullptr),
target_ptr_size_(InstructionSetPointerSize(compiler_driver_.GetInstructionSet())),
- bin_slot_sizes_(), bin_slot_offsets_(), bin_slot_count_(),
- intern_table_bytes_(0u), image_method_array_(ImageHeader::kImageMethodsCount),
- dirty_methods_(0u), clean_methods_(0u) {
+ bin_slot_sizes_(),
+ bin_slot_offsets_(),
+ bin_slot_count_(),
+ intern_table_bytes_(0u),
+ image_method_array_(ImageHeader::kImageMethodsCount),
+ dirty_methods_(0u),
+ clean_methods_(0u) {
CHECK_NE(image_begin, 0U);
- std::fill(image_methods_, image_methods_ + arraysize(image_methods_), nullptr);
+ std::fill_n(image_methods_, arraysize(image_methods_), nullptr);
+ std::fill_n(oat_address_offsets_, arraysize(oat_address_offsets_), 0);
}
~ImageWriter() {
@@ -74,8 +89,9 @@
template <typename T>
T* GetImageAddress(T* object) const SHARED_REQUIRES(Locks::mutator_lock_) {
- return object == nullptr ? nullptr :
- reinterpret_cast<T*>(image_begin_ + GetImageOffset(object));
+ return (object == nullptr || IsInBootImage(object))
+ ? object
+ : reinterpret_cast<T*>(image_begin_ + GetImageOffset(object));
}
ArtMethod* GetImageMethodAddress(ArtMethod* method) SHARED_REQUIRES(Locks::mutator_lock_);
@@ -150,6 +166,19 @@
};
friend std::ostream& operator<<(std::ostream& stream, const NativeObjectRelocationType& type);
+ enum OatAddress {
+ kOatAddressInterpreterToInterpreterBridge,
+ kOatAddressInterpreterToCompiledCodeBridge,
+ kOatAddressJNIDlsymLookup,
+ kOatAddressQuickGenericJNITrampoline,
+ kOatAddressQuickIMTConflictTrampoline,
+ kOatAddressQuickResolutionTrampoline,
+ kOatAddressQuickToInterpreterBridge,
+ // Number of elements in the enum.
+ kOatAddressCount,
+ };
+ friend std::ostream& operator<<(std::ostream& stream, const OatAddress& oat_address);
+
static constexpr size_t kBinBits = MinimumBitsToStore<uint32_t>(kBinMirrorCount - 1);
// uint32 = typeof(lockword_)
// Subtract read barrier bits since we want these to remain 0, or else it may result in DCHECK
@@ -215,7 +244,10 @@
return reinterpret_cast<mirror::Object*>(dst);
}
- const uint8_t* GetOatAddress(uint32_t offset) const {
+ // Returns the address in the boot image if we are compiling the app image.
+ const uint8_t* GetOatAddress(OatAddress type) const;
+
+ const uint8_t* GetOatAddressForOffset(uint32_t offset) const {
// With Quick, code is within the OatFile, as there are all in one
// .o ELF object.
DCHECK_LE(offset, oat_file_->Size());
@@ -224,7 +256,7 @@
}
// Returns true if the class was in the original requested image classes list.
- bool IsImageClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_);
+ bool KeepClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_);
// Debug aid that list of requested image classes.
void DumpImageClasses();
@@ -299,6 +331,11 @@
void AssignMethodOffset(ArtMethod* method, NativeObjectRelocationType type)
SHARED_REQUIRES(Locks::mutator_lock_);
+ bool IsBootClassLoaderNonImageClass(mirror::Class* klass) SHARED_REQUIRES(Locks::mutator_lock_);
+
+ bool ContainsBootClassLoaderNonImageClass(mirror::Class* klass)
+ SHARED_REQUIRES(Locks::mutator_lock_);
+
static Bin BinTypeForNativeRelocationType(NativeObjectRelocationType type);
uintptr_t NativeOffsetInImage(void* obj);
@@ -306,6 +343,13 @@
template <typename T>
T* NativeLocationInImage(T* obj);
+ // Return true of obj is inside of the boot image space. This may only return true if we are
+ // compiling an app image.
+ bool IsInBootImage(const void* obj) const;
+
+ // Return true if ptr is within the boot oat file.
+ bool IsInBootOatFile(const void* ptr) const;
+
const CompilerDriver& compiler_driver_;
// Beginning target image address for the output image.
@@ -344,14 +388,14 @@
std::unique_ptr<gc::accounting::ContinuousSpaceBitmap> image_bitmap_;
// Offset from oat_data_begin_ to the stubs.
- uint32_t interpreter_to_interpreter_bridge_offset_;
- uint32_t interpreter_to_compiled_code_bridge_offset_;
- uint32_t jni_dlsym_lookup_offset_;
- uint32_t quick_generic_jni_trampoline_offset_;
- uint32_t quick_imt_conflict_trampoline_offset_;
- uint32_t quick_resolution_trampoline_offset_;
- uint32_t quick_to_interpreter_bridge_offset_;
+ uint32_t oat_address_offsets_[kOatAddressCount];
+
+ // Boolean flags.
const bool compile_pic_;
+ const bool compile_app_image_;
+
+ // Boot image space for fast lookups.
+ gc::space::ImageSpace* boot_image_space_;
// Size of pointers on the target architecture.
size_t target_ptr_size_;
@@ -388,6 +432,10 @@
uint64_t dirty_methods_;
uint64_t clean_methods_;
+ // Prune class memoization table.
+ std::unordered_map<mirror::Class*, bool> prune_class_memo_;
+
+ friend class ContainsBootClassLoaderNonImageClassVisitor;
friend class FixupClassVisitor;
friend class FixupRootVisitor;
friend class FixupVisitor;
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index d520208..5f4f472 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -189,13 +189,14 @@
}
// Do the compilation.
- CompiledMethod* compiled_method = nullptr;
+ JitCodeCache* const code_cache = runtime->GetJit()->GetCodeCache();
+ bool success = false;
{
TimingLogger::ScopedTiming t2("Compiling", &logger);
// 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);
+ success = compiler_driver_->GetCompiler()->JitCompile(self, code_cache, method_to_compile);
}
// Trim maps to reduce memory usage.
@@ -205,105 +206,14 @@
runtime->GetArenaPool()->TrimMaps();
}
- // Check if we failed compiling.
- if (compiled_method == nullptr) {
- return false;
- }
-
total_time_ += NanoTime() - start_time;
- bool result = false;
- const void* code = runtime->GetClassLinker()->GetOatMethodQuickCodeFor(method);
-
- if (code != nullptr) {
- // Already have some compiled code, just use this instead of linking.
- // TODO: Fix recompilation.
- method->SetEntryPointFromQuickCompiledCode(code);
- result = true;
- } else {
- TimingLogger::ScopedTiming t2("LinkCode", &logger);
- if (AddToCodeCache(method, compiled_method)) {
- result = true;
- }
- }
-
- // Remove the compiled method to save memory.
- compiler_driver_->RemoveCompiledMethod(
- MethodReference(h_class->GetDexCache()->GetDexFile(), method->GetDexMethodIndex()));
runtime->GetJit()->AddTimingLogger(logger);
- return result;
+ return success;
}
CompilerCallbacks* JitCompiler::GetCompilerCallbacks() const {
return callbacks_.get();
}
-bool JitCompiler::AddToCodeCache(ArtMethod* method,
- const CompiledMethod* compiled_method) {
- Runtime* runtime = Runtime::Current();
- JitCodeCache* const code_cache = runtime->GetJit()->GetCodeCache();
- auto const quick_code = compiled_method->GetQuickCode();
- if (quick_code.empty()) {
- return false;
- }
- 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();
- uint8_t* mapping_table_ptr = nullptr;
- uint8_t* vmap_table_ptr = nullptr;
- uint8_t* gc_map_ptr = 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());
- if (mapping_table_ptr == nullptr) {
- return false; // Out of data cache.
- }
- }
-
- if (!vmap_table.empty()) {
- vmap_table_ptr = code_cache->AddDataArray(
- self, vmap_table.data(), vmap_table.data() + vmap_table.size());
- if (vmap_table_ptr == nullptr) {
- return false; // Out of data cache.
- }
- }
-
- if (!gc_map.empty()) {
- gc_map_ptr = code_cache->AddDataArray(
- 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());
-
- if (code == nullptr) {
- return false;
- }
-
- const size_t thumb_offset = compiled_method->CodeDelta();
- const uint32_t code_offset = sizeof(OatQuickMethodHeader) + thumb_offset;
- VLOG(jit)
- << "JIT added "
- << PrettyMethod(method) << "@" << method
- << " ccache_size=" << PrettySize(code_cache->CodeCacheSize()) << ": "
- << reinterpret_cast<void*>(code + code_offset)
- << "," << reinterpret_cast<void*>(code + code_offset + code_size);
- return true;
-}
-
} // namespace jit
} // namespace art
diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc
index ea3cb66..16f641a 100644
--- a/compiler/oat_test.cc
+++ b/compiler/oat_test.cc
@@ -16,6 +16,7 @@
#include "arch/instruction_set_features.h"
#include "art_method-inl.h"
+#include "base/unix_file/fd_file.h"
#include "class_linker.h"
#include "common_compiler_test.h"
#include "compiled_method.h"
@@ -37,6 +38,16 @@
namespace art {
+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);
+ UNREACHABLE();
+}
+
class OatTest : public CommonCompilerTest {
protected:
static const bool kCompile = false; // DISABLED_ due to the time to compile libcore
@@ -71,6 +82,67 @@
CHECK_EQ(0, memcmp(quick_oat_code, &quick_code[0], code_size));
}
}
+
+ void SetupCompiler(Compiler::Kind compiler_kind,
+ InstructionSet insn_set,
+ const std::vector<std::string>& compiler_options,
+ /*out*/std::string* error_msg) {
+ ASSERT_TRUE(error_msg != nullptr);
+ insn_features_.reset(InstructionSetFeatures::FromVariant(insn_set, "default", error_msg));
+ ASSERT_TRUE(insn_features_ != nullptr) << error_msg;
+ compiler_options_.reset(new CompilerOptions);
+ for (const std::string& option : compiler_options) {
+ compiler_options_->ParseCompilerOption(option, Usage);
+ }
+ verification_results_.reset(new VerificationResults(compiler_options_.get()));
+ method_inliner_map_.reset(new DexFileToMethodInlinerMap);
+ callbacks_.reset(new QuickCompilerCallbacks(verification_results_.get(),
+ method_inliner_map_.get(),
+ CompilerCallbacks::CallbackMode::kCompileApp));
+ Runtime::Current()->SetCompilerCallbacks(callbacks_.get());
+ timer_.reset(new CumulativeLogger("Compilation times"));
+ compiler_driver_.reset(new CompilerDriver(compiler_options_.get(),
+ verification_results_.get(),
+ method_inliner_map_.get(),
+ compiler_kind,
+ insn_set,
+ insn_features_.get(),
+ false,
+ nullptr,
+ nullptr,
+ nullptr,
+ 2,
+ true,
+ true,
+ "",
+ false,
+ timer_.get(),
+ -1,
+ ""));
+ }
+
+ bool WriteElf(File* file,
+ const std::vector<const DexFile*>& dex_files,
+ SafeMap<std::string, std::string>& key_value_store) {
+ TimingLogger timings("WriteElf", false, false);
+ OatWriter oat_writer(dex_files,
+ 42U,
+ 4096U,
+ 0,
+ compiler_driver_.get(),
+ nullptr,
+ /*compiling_boot_image*/false,
+ &timings,
+ &key_value_store);
+ return compiler_driver_->WriteElf(GetTestAndroidRoot(),
+ !kIsTargetBuild,
+ dex_files,
+ &oat_writer,
+ file);
+ }
+
+ std::unique_ptr<const InstructionSetFeatures> insn_features_;
+ std::unique_ptr<QuickCompilerCallbacks> callbacks_;
};
TEST_F(OatTest, WriteRead) {
@@ -80,21 +152,9 @@
// TODO: make selectable.
Compiler::Kind compiler_kind = Compiler::kQuick;
InstructionSet insn_set = kIsTargetBuild ? kThumb2 : kX86;
-
std::string error_msg;
- std::unique_ptr<const InstructionSetFeatures> insn_features(
- InstructionSetFeatures::FromVariant(insn_set, "default", &error_msg));
- ASSERT_TRUE(insn_features.get() != nullptr) << error_msg;
- compiler_options_.reset(new CompilerOptions);
- verification_results_.reset(new VerificationResults(compiler_options_.get()));
- method_inliner_map_.reset(new DexFileToMethodInlinerMap);
- timer_.reset(new CumulativeLogger("Compilation times"));
- compiler_driver_.reset(new CompilerDriver(compiler_options_.get(),
- verification_results_.get(),
- method_inliner_map_.get(),
- compiler_kind, insn_set,
- insn_features.get(), false, nullptr, nullptr, nullptr,
- 2, true, true, "", false, timer_.get(), -1, ""));
+ SetupCompiler(compiler_kind, insn_set, std::vector<std::string>(), /*out*/ &error_msg);
+
jobject class_loader = nullptr;
if (kCompile) {
TimingLogger timings2("OatTest::WriteRead", false, false);
@@ -105,19 +165,7 @@
ScratchFile tmp;
SafeMap<std::string, std::string> key_value_store;
key_value_store.Put(OatHeader::kImageLocationKey, "lue.art");
- OatWriter oat_writer(class_linker->GetBootClassPath(),
- 42U,
- 4096U,
- 0,
- compiler_driver_.get(),
- nullptr,
- &timings,
- &key_value_store);
- bool success = compiler_driver_->WriteElf(GetTestAndroidRoot(),
- !kIsTargetBuild,
- class_linker->GetBootClassPath(),
- &oat_writer,
- tmp.GetFile());
+ bool success = WriteElf(tmp.GetFile(), class_linker->GetBootClassPath(), key_value_store);
ASSERT_TRUE(success);
if (kCompile) { // OatWriter strips the code, regenerate to compare
@@ -212,4 +260,53 @@
ASSERT_FALSE(oat_header->IsValid());
}
+TEST_F(OatTest, EmptyTextSection) {
+ TimingLogger timings("OatTest::EmptyTextSection", false, false);
+
+ // TODO: make selectable.
+ Compiler::Kind compiler_kind = Compiler::kQuick;
+ InstructionSet insn_set = kRuntimeISA;
+ if (insn_set == kArm) insn_set = kThumb2;
+ std::string error_msg;
+ std::vector<std::string> compiler_options;
+ compiler_options.push_back("--compiler-filter=verify-at-runtime");
+ SetupCompiler(compiler_kind, insn_set, compiler_options, /*out*/ &error_msg);
+
+ jobject class_loader;
+ {
+ ScopedObjectAccess soa(Thread::Current());
+ class_loader = LoadDex("Main");
+ }
+ ASSERT_TRUE(class_loader != nullptr);
+ std::vector<const DexFile*> dex_files = GetDexFiles(class_loader);
+ ASSERT_TRUE(!dex_files.empty());
+
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ for (const DexFile* dex_file : dex_files) {
+ ScopedObjectAccess soa(Thread::Current());
+ class_linker->RegisterDexFile(
+ *dex_file,
+ class_linker->GetOrCreateAllocatorForClassLoader(
+ soa.Decode<mirror::ClassLoader*>(class_loader)));
+ }
+ compiler_driver_->SetDexFilesForOatFile(dex_files);
+ compiler_driver_->CompileAll(class_loader, dex_files, &timings);
+
+ ScratchFile tmp;
+ SafeMap<std::string, std::string> key_value_store;
+ key_value_store.Put(OatHeader::kImageLocationKey, "test.art");
+ bool success = WriteElf(tmp.GetFile(), dex_files, key_value_store);
+ ASSERT_TRUE(success);
+
+ std::unique_ptr<OatFile> oat_file(OatFile::Open(tmp.GetFilename(),
+ tmp.GetFilename(),
+ nullptr,
+ nullptr,
+ false,
+ nullptr,
+ &error_msg));
+ ASSERT_TRUE(oat_file != nullptr);
+ EXPECT_LT(static_cast<size_t>(oat_file->Size()), static_cast<size_t>(tmp.GetFile()->GetLength()));
+}
+
} // namespace art
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index c7b8884..3f2271e 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -65,10 +65,12 @@
int32_t image_patch_delta,
const CompilerDriver* compiler,
ImageWriter* image_writer,
+ bool compiling_boot_image,
TimingLogger* timings,
SafeMap<std::string, std::string>* key_value_store)
: compiler_driver_(compiler),
image_writer_(image_writer),
+ compiling_boot_image_(compiling_boot_image),
dex_files_(&dex_files),
size_(0u),
bss_size_(0u),
@@ -113,7 +115,9 @@
size_oat_lookup_table_(0),
method_offset_map_() {
CHECK(key_value_store != nullptr);
-
+ if (compiling_boot_image) {
+ CHECK(image_writer != nullptr);
+ }
InstructionSet instruction_set = compiler_driver_->GetInstructionSet();
const InstructionSetFeatures* features = compiler_driver_->GetInstructionSetFeatures();
relative_patcher_ = linker::RelativePatcher::Create(instruction_set, features,
@@ -154,7 +158,7 @@
}
size_ = offset;
- if (!HasImage()) {
+ if (!HasBootImage()) {
// Allocate space for app dex cache arrays in the .bss section.
size_t bss_start = RoundUp(size_, kPageSize);
size_t pointer_size = GetInstructionSetPointerSize(instruction_set);
@@ -167,9 +171,10 @@
}
CHECK_EQ(dex_files_->size(), oat_dex_files_.size());
- CHECK_EQ(compiler->IsImage(), image_writer_ != nullptr);
- CHECK_EQ(compiler->IsImage(),
- key_value_store_->find(OatHeader::kImageLocationKey) == key_value_store_->end());
+ if (compiling_boot_image_) {
+ CHECK_EQ(image_writer_ != nullptr,
+ key_value_store_->find(OatHeader::kImageLocationKey) == key_value_store_->end());
+ }
CHECK_ALIGNED(image_patch_delta_, kPageSize);
}
@@ -672,7 +677,7 @@
class_linker_(Runtime::Current()->GetClassLinker()),
dex_cache_(nullptr) {
patched_code_.reserve(16 * KB);
- if (writer_->HasImage()) {
+ if (writer_->HasBootImage()) {
// If we're creating the image, the address space must be ready so that we can apply patches.
CHECK(writer_->image_writer_->IsImageAddressSpaceReady());
}
@@ -855,7 +860,7 @@
}
uint32_t GetDexCacheOffset(const LinkerPatch& patch) SHARED_REQUIRES(Locks::mutator_lock_) {
- if (writer_->HasImage()) {
+ if (writer_->HasBootImage()) {
auto* element = writer_->image_writer_->GetDexCacheArrayElementImageAddress<const uint8_t*>(
patch.TargetDexCacheDexFile(), patch.TargetDexCacheElementOffset());
const uint8_t* oat_data = writer_->image_writer_->GetOatFileBegin() + file_offset_;
@@ -868,7 +873,7 @@
void PatchObjectAddress(std::vector<uint8_t>* code, uint32_t offset, mirror::Object* object)
SHARED_REQUIRES(Locks::mutator_lock_) {
- if (writer_->HasImage()) {
+ if (writer_->HasBootImage()) {
object = writer_->image_writer_->GetImageAddress(object);
} else {
// NOTE: We're using linker patches for app->boot references when the image can
@@ -888,7 +893,7 @@
void PatchMethodAddress(std::vector<uint8_t>* code, uint32_t offset, ArtMethod* method)
SHARED_REQUIRES(Locks::mutator_lock_) {
- if (writer_->HasImage()) {
+ if (writer_->HasBootImage()) {
method = writer_->image_writer_->GetImageMethodAddress(method);
} else if (kIsDebugBuild) {
// NOTE: We're using linker patches for app->boot references when the image can
@@ -911,7 +916,7 @@
void PatchCodeAddress(std::vector<uint8_t>* code, uint32_t offset, uint32_t target_offset)
SHARED_REQUIRES(Locks::mutator_lock_) {
uint32_t address = target_offset;
- if (writer_->HasImage()) {
+ if (writer_->HasBootImage()) {
address = PointerToLowMemUInt32(writer_->image_writer_->GetOatFileBegin() +
writer_->oat_data_offset_ + target_offset);
}
@@ -1123,7 +1128,7 @@
offset = RoundUp(offset, kPageSize);
oat_header_->SetExecutableOffset(offset);
size_executable_offset_alignment_ = offset - old_offset;
- if (compiler_driver_->IsImage()) {
+ if (compiler_driver_->IsBootImage()) {
CHECK_EQ(image_patch_delta_, 0);
InstructionSet instruction_set = compiler_driver_->GetInstructionSet();
@@ -1164,7 +1169,7 @@
} while (false)
VISIT(InitCodeMethodVisitor);
- if (compiler_driver_->IsImage()) {
+ if (compiler_driver_->IsBootImage()) {
VISIT(InitImageMethodVisitor);
}
@@ -1408,7 +1413,7 @@
}
size_t OatWriter::WriteCode(OutputStream* out, const size_t file_offset, size_t relative_offset) {
- if (compiler_driver_->IsImage()) {
+ if (compiler_driver_->IsBootImage()) {
InstructionSet instruction_set = compiler_driver_->GetInstructionSet();
#define DO_TRAMPOLINE(field) \
diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h
index f2fe048..7027434 100644
--- a/compiler/oat_writer.h
+++ b/compiler/oat_writer.h
@@ -93,6 +93,7 @@
int32_t image_patch_delta,
const CompilerDriver* compiler,
ImageWriter* image_writer,
+ bool compiling_boot_image,
TimingLogger* timings,
SafeMap<std::string, std::string>* key_value_store);
@@ -103,6 +104,10 @@
return image_writer_ != nullptr;
}
+ bool HasBootImage() const {
+ return compiling_boot_image_;
+ }
+
const OatHeader& GetOatHeader() const {
return *oat_header_;
}
@@ -279,6 +284,7 @@
const CompilerDriver* const compiler_driver_;
ImageWriter* const image_writer_;
+ const bool compiling_boot_image_;
// note OatFile does not take ownership of the DexFiles
const std::vector<const DexFile*>* dex_files_;
diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc
index bcc3240..cca0baf 100644
--- a/compiler/optimizing/bounds_check_elimination.cc
+++ b/compiler/optimizing/bounds_check_elimination.cc
@@ -1169,8 +1169,10 @@
// Return the range resulting from induction variable analysis of "instruction" when the value
// is used from "context", for example, an index used from a bounds-check inside a loop body.
ValueRange* LookupInductionRange(HInstruction* context, HInstruction* instruction) {
- InductionVarRange::Value v1 = induction_range_.GetMinInduction(context, instruction);
- InductionVarRange::Value v2 = induction_range_.GetMaxInduction(context, instruction);
+ InductionVarRange::Value v1;
+ InductionVarRange::Value v2;
+ bool needs_finite_test = false;
+ induction_range_.GetInductionRange(context, instruction, &v1, &v2, &needs_finite_test);
if (v1.is_known && (v1.a_constant == 0 || v1.a_constant == 1) &&
v2.is_known && (v2.a_constant == 0 || v2.a_constant == 1)) {
DCHECK(v1.a_constant == 1 || v1.instruction == nullptr);
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index a1bb5e0..ce92470 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -42,7 +42,7 @@
#include "compiled_method.h"
#include "dex/verified_method.h"
-#include "driver/dex_compilation_unit.h"
+#include "driver/compiler_driver.h"
#include "gc_map_builder.h"
#include "graph_visualizer.h"
#include "intrinsics.h"
@@ -787,9 +787,10 @@
}
void CodeGenerator::BuildNativeGCMap(
- ArenaVector<uint8_t>* data, const DexCompilationUnit& dex_compilation_unit) const {
+ ArenaVector<uint8_t>* data, const CompilerDriver& compiler_driver) const {
const std::vector<uint8_t>& gc_map_raw =
- dex_compilation_unit.GetVerifiedMethod()->GetDexGcMap();
+ compiler_driver.GetVerifiedMethod(&GetGraph()->GetDexFile(), GetGraph()->GetMethodIdx())
+ ->GetDexGcMap();
verifier::DexPcToReferenceMap dex_gc_map(&(gc_map_raw)[0]);
uint32_t max_native_offset = stack_map_stream_.ComputeMaxNativePcOffset();
@@ -911,19 +912,22 @@
vmap_encoder.PushBackUnsigned(VmapTable::kAdjustedFpMarker);
}
-void CodeGenerator::BuildStackMaps(ArenaVector<uint8_t>* data) {
- uint32_t size = stack_map_stream_.PrepareForFillIn();
- data->resize(size);
- MemoryRegion region(data->data(), size);
+size_t CodeGenerator::ComputeStackMapsSize() {
+ return stack_map_stream_.PrepareForFillIn();
+}
+
+void CodeGenerator::BuildStackMaps(MemoryRegion region) {
stack_map_stream_.FillIn(region);
}
void CodeGenerator::RecordNativeDebugInfo(uint32_t dex_pc,
uintptr_t native_pc_begin,
uintptr_t native_pc_end) {
- if (src_map_ != nullptr && dex_pc != kNoDexPc && native_pc_begin != native_pc_end) {
- src_map_->push_back(SrcMapElem({static_cast<uint32_t>(native_pc_begin),
- static_cast<int32_t>(dex_pc)}));
+ if (compiler_options_.GetGenerateDebugInfo() &&
+ dex_pc != kNoDexPc &&
+ native_pc_begin != native_pc_end) {
+ src_map_.push_back(SrcMapElem({static_cast<uint32_t>(native_pc_begin),
+ static_cast<int32_t>(dex_pc)}));
}
}
diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h
index 47b6f30..a92014d 100644
--- a/compiler/optimizing/code_generator.h
+++ b/compiler/optimizing/code_generator.h
@@ -22,6 +22,7 @@
#include "base/arena_containers.h"
#include "base/arena_object.h"
#include "base/bit_field.h"
+#include "compiled_method.h"
#include "driver/compiler_options.h"
#include "globals.h"
#include "graph_visualizer.h"
@@ -51,13 +52,9 @@
class Assembler;
class CodeGenerator;
-class DexCompilationUnit;
+class CompilerDriver;
class LinkerPatch;
class ParallelMoveResolver;
-class SrcMapElem;
-template <class Alloc>
-class SrcMap;
-using DefaultSrcMap = SrcMap<std::allocator<SrcMapElem>>;
class CodeAllocator {
public:
@@ -284,13 +281,12 @@
slow_paths_.push_back(slow_path);
}
- void SetSrcMap(DefaultSrcMap* src_map) { src_map_ = src_map; }
-
void BuildMappingTable(ArenaVector<uint8_t>* vector) const;
void BuildVMapTable(ArenaVector<uint8_t>* vector) const;
void BuildNativeGCMap(
- ArenaVector<uint8_t>* vector, const DexCompilationUnit& dex_compilation_unit) const;
- void BuildStackMaps(ArenaVector<uint8_t>* vector);
+ ArenaVector<uint8_t>* vector, const CompilerDriver& compiler_driver) const;
+ void BuildStackMaps(MemoryRegion region);
+ size_t ComputeStackMapsSize();
bool IsBaseline() const {
return is_baseline_;
@@ -446,6 +442,10 @@
// Copy the result of a call into the given target.
virtual void MoveFromReturnRegister(Location trg, Primitive::Type type) = 0;
+ const ArenaVector<SrcMapElem>& GetSrcMappingTable() const {
+ return src_map_;
+ }
+
protected:
// Method patch info used for recording locations of required linker patches and
// target methods. The target method can be used for various purposes, whether for
@@ -488,7 +488,7 @@
stats_(stats),
graph_(graph),
compiler_options_(compiler_options),
- src_map_(nullptr),
+ src_map_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
slow_paths_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
current_block_index_(0),
is_leaf_(true),
@@ -602,7 +602,7 @@
const CompilerOptions& compiler_options_;
// Native to dex_pc map used for native debugging/profiling tools.
- DefaultSrcMap* src_map_;
+ ArenaVector<SrcMapElem> src_map_;
ArenaVector<SlowPathCode*> slow_paths_;
// The current block index in `block_order_` of the block
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index 3dc3b7f..6d05293 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -1300,20 +1300,29 @@
DCHECK_EQ(cond_value, 0);
}
} else {
- if (!cond->IsCondition() || cond->AsCondition()->NeedsMaterialization()) {
- // Condition has been materialized, compare the output to 0
+ // Can we optimize the jump if we know that the next block is the true case?
+ HCondition* condition = cond->AsCondition();
+ bool can_jump_to_false = CanReverseCondition(always_true_target, false_target, condition);
+ if (condition == nullptr || condition->NeedsMaterialization()) {
+ // Condition has been materialized, compare the output to 0.
DCHECK(instruction->GetLocations()->InAt(0).IsRegister());
+ if (can_jump_to_false) {
+ __ CompareAndBranchIfZero(instruction->GetLocations()->InAt(0).AsRegister<Register>(),
+ false_target);
+ return;
+ }
__ CompareAndBranchIfNonZero(instruction->GetLocations()->InAt(0).AsRegister<Register>(),
true_target);
} else {
// Condition has not been materialized, use its inputs as the
// comparison and its condition as the branch condition.
- Primitive::Type type =
- cond->IsCondition() ? cond->InputAt(0)->GetType() : Primitive::kPrimInt;
+ Primitive::Type type = (condition != nullptr)
+ ? cond->InputAt(0)->GetType()
+ : Primitive::kPrimInt;
// Is this a long or FP comparison that has been folded into the HCondition?
if (type == Primitive::kPrimLong || Primitive::IsFloatingPointType(type)) {
// Generate the comparison directly.
- GenerateCompareTestAndBranch(instruction->AsIf(), cond->AsCondition(),
+ GenerateCompareTestAndBranch(instruction->AsIf(), condition,
true_target, false_target, always_true_target);
return;
}
@@ -1328,7 +1337,12 @@
DCHECK(right.IsConstant());
GenerateCompareWithImmediate(left, CodeGenerator::GetInt32ValueOf(right.GetConstant()));
}
- __ b(true_target, ARMCondition(cond->AsCondition()->GetCondition()));
+ if (can_jump_to_false) {
+ __ b(false_target, ARMCondition(condition->GetOppositeCondition()));
+ return;
+ }
+
+ __ b(true_target, ARMCondition(condition->GetCondition()));
}
}
if (false_target != nullptr) {
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index 55efd5f..d4fcaf9 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -2659,14 +2659,10 @@
codegen_->RecordPcInfo(invoke, invoke->GetDexPc());
}
-void InstructionCodeGeneratorMIPS64::VisitInvokeVirtual(HInvokeVirtual* invoke) {
- if (TryGenerateIntrinsicCode(invoke, codegen_)) {
- return;
- }
-
+void CodeGeneratorMIPS64::GenerateVirtualCall(HInvokeVirtual* invoke, Location temp_location) {
LocationSummary* locations = invoke->GetLocations();
Location receiver = locations->InAt(0);
- GpuRegister temp = invoke->GetLocations()->GetTemp(0).AsRegister<GpuRegister>();
+ GpuRegister temp = temp_location.AsRegister<GpuRegister>();
size_t method_offset = mirror::Class::EmbeddedVTableEntryOffset(
invoke->GetVTableIndex(), kMips64PointerSize).SizeValue();
uint32_t class_offset = mirror::Object::ClassOffset().Int32Value();
@@ -2675,13 +2671,21 @@
// temp = object->GetClass();
DCHECK(receiver.IsRegister());
__ LoadFromOffset(kLoadUnsignedWord, temp, receiver.AsRegister<GpuRegister>(), class_offset);
- codegen_->MaybeRecordImplicitNullCheck(invoke);
+ MaybeRecordImplicitNullCheck(invoke);
// temp = temp->GetMethodAt(method_offset);
__ LoadFromOffset(kLoadDoubleword, temp, temp, method_offset);
// T9 = temp->GetEntryPoint();
__ LoadFromOffset(kLoadDoubleword, T9, temp, entry_point.Int32Value());
// T9();
__ Jalr(T9);
+}
+
+void InstructionCodeGeneratorMIPS64::VisitInvokeVirtual(HInvokeVirtual* invoke) {
+ if (TryGenerateIntrinsicCode(invoke, codegen_)) {
+ return;
+ }
+
+ codegen_->GenerateVirtualCall(invoke, invoke->GetLocations()->GetTemp(0));
DCHECK(!codegen_->IsLeafMethod());
codegen_->RecordPcInfo(invoke, invoke->GetDexPc());
}
diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h
index 9bbd027..4f91c71 100644
--- a/compiler/optimizing/code_generator_mips64.h
+++ b/compiler/optimizing/code_generator_mips64.h
@@ -333,10 +333,7 @@
MethodReference target_method) OVERRIDE;
void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE;
- void GenerateVirtualCall(HInvokeVirtual* invoke ATTRIBUTE_UNUSED,
- Location temp ATTRIBUTE_UNUSED) OVERRIDE {
- UNIMPLEMENTED(FATAL);
- }
+ void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE;
void MoveFromReturnRegister(Location trg ATTRIBUTE_UNUSED,
Primitive::Type type ATTRIBUTE_UNUSED) OVERRIDE {
diff --git a/compiler/optimizing/code_generator_utils.cc b/compiler/optimizing/code_generator_utils.cc
index 921c1d8..bf354e7 100644
--- a/compiler/optimizing/code_generator_utils.cc
+++ b/compiler/optimizing/code_generator_utils.cc
@@ -15,6 +15,7 @@
*/
#include "code_generator_utils.h"
+#include "nodes.h"
#include "base/logging.h"
@@ -94,4 +95,19 @@
*shift = is_long ? p - 64 : p - 32;
}
+// Is it valid to reverse the condition? Uses the values supplied to
+// GenerateTestAndBranch() in instruction generators.
+bool CanReverseCondition(Label* always_true_target,
+ Label* false_target,
+ HCondition* condition) {
+ // 'always_true_target' is null when the 'true' path is to the next
+ // block to be generated. Check the type of the condition to ensure that
+ // FP conditions are not swapped. This is for future fusing of HCompare and
+ // HCondition.
+ // Note: If the condition is nullptr, then it is always okay to reverse.
+ return always_true_target == nullptr && false_target != nullptr &&
+ (condition == nullptr ||
+ !Primitive::IsFloatingPointType(condition->InputAt(0)->GetType()));
+}
+
} // namespace art
diff --git a/compiler/optimizing/code_generator_utils.h b/compiler/optimizing/code_generator_utils.h
index 59b495c..628eee8 100644
--- a/compiler/optimizing/code_generator_utils.h
+++ b/compiler/optimizing/code_generator_utils.h
@@ -21,10 +21,19 @@
namespace art {
+class Label;
+class HCondition;
+
// Computes the magic number and the shift needed in the div/rem by constant algorithm, as out
// arguments `magic` and `shift`
void CalculateMagicAndShiftForDivRem(int64_t divisor, bool is_long, int64_t* magic, int* shift);
+// Is it valid to reverse the condition? Uses the values supplied to
+// GenerateTestAndBranch() in instruction generators.
+bool CanReverseCondition(Label* always_true_target,
+ Label* false_target,
+ HCondition* condition);
+
} // namespace art
#endif // ART_COMPILER_OPTIMIZING_CODE_GENERATOR_UTILS_H_
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 0df7e3b..0db5837 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -1216,16 +1216,21 @@
DCHECK_EQ(cond_value, 0);
}
} else {
+ HCondition* condition = cond->AsCondition();
bool is_materialized =
- !cond->IsCondition() || cond->AsCondition()->NeedsMaterialization();
+ condition == nullptr || condition->NeedsMaterialization();
// Moves do not affect the eflags register, so if the condition is
// evaluated just before the if, we don't need to evaluate it
// again. We can't use the eflags on long/FP conditions if they are
// materialized due to the complex branching.
- Primitive::Type type = cond->IsCondition() ? cond->InputAt(0)->GetType() : Primitive::kPrimInt;
- bool eflags_set = cond->IsCondition()
- && cond->AsCondition()->IsBeforeWhenDisregardMoves(instruction)
+ Primitive::Type type = (condition != nullptr)
+ ? cond->InputAt(0)->GetType()
+ : Primitive::kPrimInt;
+ bool eflags_set = condition != nullptr
+ && condition->IsBeforeWhenDisregardMoves(instruction)
&& (type != Primitive::kPrimLong && !Primitive::IsFloatingPointType(type));
+ // Can we optimize the jump if we know that the next block is the true case?
+ bool can_jump_to_false = CanReverseCondition(always_true_target, false_target, condition);
if (is_materialized) {
if (!eflags_set) {
// Materialized condition, compare against 0.
@@ -1235,9 +1240,17 @@
} else {
__ cmpl(Address(ESP, lhs.GetStackIndex()), Immediate(0));
}
+ if (can_jump_to_false) {
+ __ j(kEqual, false_target);
+ return;
+ }
__ j(kNotEqual, true_target);
} else {
- __ j(X86Condition(cond->AsCondition()->GetCondition()), true_target);
+ if (can_jump_to_false) {
+ __ j(X86Condition(condition->GetOppositeCondition()), false_target);
+ return;
+ }
+ __ j(X86Condition(condition->GetCondition()), true_target);
}
} else {
// Condition has not been materialized, use its inputs as the
@@ -1247,7 +1260,7 @@
if (type == Primitive::kPrimLong || Primitive::IsFloatingPointType(type)) {
// Generate the comparison directly.
GenerateCompareTestAndBranch(instruction->AsIf(),
- cond->AsCondition(),
+ condition,
true_target,
false_target,
always_true_target);
@@ -1270,7 +1283,13 @@
} else {
__ cmpl(lhs.AsRegister<Register>(), Address(ESP, rhs.GetStackIndex()));
}
- __ j(X86Condition(cond->AsCondition()->GetCondition()), true_target);
+
+ if (can_jump_to_false) {
+ __ j(X86Condition(condition->GetOppositeCondition()), false_target);
+ return;
+ }
+
+ __ j(X86Condition(condition->GetCondition()), true_target);
}
}
if (false_target != nullptr) {
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 5218d70..ee8a299 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -1183,16 +1183,20 @@
DCHECK_EQ(cond_value, 0);
}
} else {
- bool is_materialized =
- !cond->IsCondition() || cond->AsCondition()->NeedsMaterialization();
+ HCondition* condition = cond->AsCondition();
+ bool is_materialized = condition == nullptr || condition->NeedsMaterialization();
// Moves do not affect the eflags register, so if the condition is
// evaluated just before the if, we don't need to evaluate it
// again. We can't use the eflags on FP conditions if they are
// materialized due to the complex branching.
- Primitive::Type type = cond->IsCondition() ? cond->InputAt(0)->GetType() : Primitive::kPrimInt;
- bool eflags_set = cond->IsCondition()
- && cond->AsCondition()->IsBeforeWhenDisregardMoves(instruction)
+ Primitive::Type type = (condition != nullptr)
+ ? cond->InputAt(0)->GetType()
+ : Primitive::kPrimInt;
+ bool eflags_set = condition != nullptr
+ && condition->IsBeforeWhenDisregardMoves(instruction)
&& !Primitive::IsFloatingPointType(type);
+ // Can we optimize the jump if we know that the next block is the true case?
+ bool can_jump_to_false = CanReverseCondition(always_true_target, false_target, condition);
if (is_materialized) {
if (!eflags_set) {
@@ -1204,9 +1208,17 @@
__ cmpl(Address(CpuRegister(RSP), lhs.GetStackIndex()),
Immediate(0));
}
+ if (can_jump_to_false) {
+ __ j(kEqual, false_target);
+ return;
+ }
__ j(kNotEqual, true_target);
} else {
- __ j(X86_64IntegerCondition(cond->AsCondition()->GetCondition()), true_target);
+ if (can_jump_to_false) {
+ __ j(X86_64IntegerCondition(condition->GetOppositeCondition()), false_target);
+ return;
+ }
+ __ j(X86_64IntegerCondition(condition->GetCondition()), true_target);
}
} else {
// Condition has not been materialized, use its inputs as the
@@ -1215,7 +1227,7 @@
// Is this a long or FP comparison that has been folded into the HCondition?
if (type == Primitive::kPrimLong || Primitive::IsFloatingPointType(type)) {
// Generate the comparison directly.
- GenerateCompareTestAndBranch(instruction->AsIf(), cond->AsCondition(),
+ GenerateCompareTestAndBranch(instruction->AsIf(), condition,
true_target, false_target, always_true_target);
return;
}
@@ -1235,7 +1247,13 @@
__ cmpl(lhs.AsRegister<CpuRegister>(),
Address(CpuRegister(RSP), rhs.GetStackIndex()));
}
- __ j(X86_64IntegerCondition(cond->AsCondition()->GetCondition()), true_target);
+
+ if (can_jump_to_false) {
+ __ j(X86_64IntegerCondition(condition->GetOppositeCondition()), false_target);
+ return;
+ }
+
+ __ j(X86_64IntegerCondition(condition->GetCondition()), true_target);
}
}
if (false_target != nullptr) {
@@ -2562,7 +2580,7 @@
case Primitive::kPrimLong: {
locations->SetInAt(0, Location::RequiresRegister());
// We can use a leaq or addq if the constant can fit in an immediate.
- locations->SetInAt(1, Location::RegisterOrInt32LongConstant(add->InputAt(1)));
+ locations->SetInAt(1, Location::RegisterOrInt32Constant(add->InputAt(1)));
locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
break;
}
@@ -2682,7 +2700,7 @@
}
case Primitive::kPrimLong: {
locations->SetInAt(0, Location::RequiresRegister());
- locations->SetInAt(1, Location::RegisterOrInt32LongConstant(sub->InputAt(1)));
+ locations->SetInAt(1, Location::RegisterOrInt32Constant(sub->InputAt(1)));
locations->SetOut(Location::SameAsFirstInput());
break;
}
@@ -3755,14 +3773,25 @@
LocationSummary* locations =
new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall);
Primitive::Type field_type = field_info.GetFieldType();
+ bool is_volatile = field_info.IsVolatile();
bool needs_write_barrier =
CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1));
locations->SetInAt(0, Location::RequiresRegister());
if (Primitive::IsFloatingPointType(instruction->InputAt(1)->GetType())) {
- locations->SetInAt(1, Location::RequiresFpuRegister());
+ if (is_volatile) {
+ // In order to satisfy the semantics of volatile, this must be a single instruction store.
+ locations->SetInAt(1, Location::FpuRegisterOrInt32Constant(instruction->InputAt(1)));
+ } else {
+ locations->SetInAt(1, Location::FpuRegisterOrConstant(instruction->InputAt(1)));
+ }
} else {
- locations->SetInAt(1, Location::RegisterOrInt32LongConstant(instruction->InputAt(1)));
+ if (is_volatile) {
+ // In order to satisfy the semantics of volatile, this must be a single instruction store.
+ locations->SetInAt(1, Location::RegisterOrInt32Constant(instruction->InputAt(1)));
+ } else {
+ locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
+ }
}
if (needs_write_barrier) {
// Temporary registers for the write barrier.
@@ -3790,11 +3819,13 @@
GenerateMemoryBarrier(MemBarrierKind::kAnyStore);
}
+ bool maybe_record_implicit_null_check_done = false;
+
switch (field_type) {
case Primitive::kPrimBoolean:
case Primitive::kPrimByte: {
if (value.IsConstant()) {
- int32_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant());
+ int8_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant());
__ movb(Address(base, offset), Immediate(v));
} else {
__ movb(Address(base, offset), value.AsRegister<CpuRegister>());
@@ -3805,7 +3836,7 @@
case Primitive::kPrimShort:
case Primitive::kPrimChar: {
if (value.IsConstant()) {
- int32_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant());
+ int16_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant());
__ movw(Address(base, offset), Immediate(v));
} else {
__ movw(Address(base, offset), value.AsRegister<CpuRegister>());
@@ -3838,9 +3869,11 @@
case Primitive::kPrimLong: {
if (value.IsConstant()) {
int64_t v = value.GetConstant()->AsLongConstant()->GetValue();
- DCHECK(IsInt<32>(v));
- int32_t v_32 = v;
- __ movq(Address(base, offset), Immediate(v_32));
+ codegen_->MoveInt64ToAddress(Address(base, offset),
+ Address(base, offset + sizeof(int32_t)),
+ v,
+ instruction);
+ maybe_record_implicit_null_check_done = true;
} else {
__ movq(Address(base, offset), value.AsRegister<CpuRegister>());
}
@@ -3848,12 +3881,28 @@
}
case Primitive::kPrimFloat: {
- __ movss(Address(base, offset), value.AsFpuRegister<XmmRegister>());
+ if (value.IsConstant()) {
+ int32_t v =
+ bit_cast<int32_t, float>(value.GetConstant()->AsFloatConstant()->GetValue());
+ __ movl(Address(base, offset), Immediate(v));
+ } else {
+ __ movss(Address(base, offset), value.AsFpuRegister<XmmRegister>());
+ }
break;
}
case Primitive::kPrimDouble: {
- __ movsd(Address(base, offset), value.AsFpuRegister<XmmRegister>());
+ if (value.IsConstant()) {
+ int64_t v =
+ bit_cast<int64_t, double>(value.GetConstant()->AsDoubleConstant()->GetValue());
+ codegen_->MoveInt64ToAddress(Address(base, offset),
+ Address(base, offset + sizeof(int32_t)),
+ v,
+ instruction);
+ maybe_record_implicit_null_check_done = true;
+ } else {
+ __ movsd(Address(base, offset), value.AsFpuRegister<XmmRegister>());
+ }
break;
}
@@ -3862,7 +3911,9 @@
UNREACHABLE();
}
- codegen_->MaybeRecordImplicitNullCheck(instruction);
+ if (!maybe_record_implicit_null_check_done) {
+ codegen_->MaybeRecordImplicitNullCheck(instruction);
+ }
if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) {
CpuRegister temp = locations->GetTemp(0).AsRegister<CpuRegister>();
@@ -4170,13 +4221,9 @@
may_need_runtime_call ? LocationSummary::kCallOnSlowPath : LocationSummary::kNoCall);
locations->SetInAt(0, Location::RequiresRegister());
- locations->SetInAt(
- 1, Location::RegisterOrConstant(instruction->InputAt(1)));
- locations->SetInAt(2, Location::RequiresRegister());
- if (value_type == Primitive::kPrimLong) {
- locations->SetInAt(2, Location::RegisterOrInt32LongConstant(instruction->InputAt(2)));
- } else if (value_type == Primitive::kPrimFloat || value_type == Primitive::kPrimDouble) {
- locations->SetInAt(2, Location::RequiresFpuRegister());
+ locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
+ if (Primitive::IsFloatingPointType(value_type)) {
+ locations->SetInAt(2, Location::FpuRegisterOrConstant(instruction->InputAt(2)));
} else {
locations->SetInAt(2, Location::RegisterOrConstant(instruction->InputAt(2)));
}
@@ -4330,13 +4377,15 @@
: Address(array, index.AsRegister<CpuRegister>(), TIMES_8, offset);
if (value.IsRegister()) {
__ movq(address, value.AsRegister<CpuRegister>());
+ codegen_->MaybeRecordImplicitNullCheck(instruction);
} else {
int64_t v = value.GetConstant()->AsLongConstant()->GetValue();
- DCHECK(IsInt<32>(v));
- int32_t v_32 = v;
- __ movq(address, Immediate(v_32));
+ Address address_high = index.IsConstant()
+ ? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) +
+ offset + sizeof(int32_t))
+ : Address(array, index.AsRegister<CpuRegister>(), TIMES_8, offset + sizeof(int32_t));
+ codegen_->MoveInt64ToAddress(address, address_high, v, instruction);
}
- codegen_->MaybeRecordImplicitNullCheck(instruction);
break;
}
@@ -4345,8 +4394,14 @@
Address address = index.IsConstant()
? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + offset)
: Address(array, index.AsRegister<CpuRegister>(), TIMES_4, offset);
- DCHECK(value.IsFpuRegister());
- __ movss(address, value.AsFpuRegister<XmmRegister>());
+ if (value.IsFpuRegister()) {
+ __ movss(address, value.AsFpuRegister<XmmRegister>());
+ } else {
+ DCHECK(value.IsConstant());
+ int32_t v =
+ bit_cast<int32_t, float>(value.GetConstant()->AsFloatConstant()->GetValue());
+ __ movl(address, Immediate(v));
+ }
codegen_->MaybeRecordImplicitNullCheck(instruction);
break;
}
@@ -4356,9 +4411,18 @@
Address address = index.IsConstant()
? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + offset)
: Address(array, index.AsRegister<CpuRegister>(), TIMES_8, offset);
- DCHECK(value.IsFpuRegister());
- __ movsd(address, value.AsFpuRegister<XmmRegister>());
- codegen_->MaybeRecordImplicitNullCheck(instruction);
+ if (value.IsFpuRegister()) {
+ __ movsd(address, value.AsFpuRegister<XmmRegister>());
+ codegen_->MaybeRecordImplicitNullCheck(instruction);
+ } else {
+ int64_t v =
+ bit_cast<int64_t, double>(value.GetConstant()->AsDoubleConstant()->GetValue());
+ Address address_high = index.IsConstant()
+ ? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) +
+ offset + sizeof(int32_t))
+ : Address(array, index.AsRegister<CpuRegister>(), TIMES_8, offset + sizeof(int32_t));
+ codegen_->MoveInt64ToAddress(address, address_high, v, instruction);
+ }
break;
}
@@ -5564,6 +5628,24 @@
return Address::RIP(table_fixup);
}
+void CodeGeneratorX86_64::MoveInt64ToAddress(const Address& addr_low,
+ const Address& addr_high,
+ int64_t v,
+ HInstruction* instruction) {
+ if (IsInt<32>(v)) {
+ int32_t v_32 = v;
+ __ movq(addr_low, Immediate(v_32));
+ MaybeRecordImplicitNullCheck(instruction);
+ } else {
+ // Didn't fit in a register. Do it in pieces.
+ int32_t low_v = Low32Bits(v);
+ int32_t high_v = High32Bits(v);
+ __ movl(addr_low, Immediate(low_v));
+ MaybeRecordImplicitNullCheck(instruction);
+ __ movl(addr_high, Immediate(high_v));
+ }
+}
+
#undef __
} // namespace x86_64
diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h
index fc485f5..7a52473 100644
--- a/compiler/optimizing/code_generator_x86_64.h
+++ b/compiler/optimizing/code_generator_x86_64.h
@@ -368,6 +368,12 @@
// Store a 64 bit value into a DoubleStackSlot in the most efficient manner.
void Store64BitValueToStack(Location dest, int64_t value);
+ // Assign a 64 bit constant to an address.
+ void MoveInt64ToAddress(const Address& addr_low,
+ const Address& addr_high,
+ int64_t v,
+ HInstruction* instruction);
+
private:
struct PcRelativeDexCacheAccessInfo {
PcRelativeDexCacheAccessInfo(const DexFile& dex_file, uint32_t element_off)
diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc
index 5530d26..b40ef5a 100644
--- a/compiler/optimizing/induction_var_range.cc
+++ b/compiler/optimizing/induction_var_range.cc
@@ -75,10 +75,12 @@
return v;
}
-static HInstruction* Insert(HBasicBlock* preheader, HInstruction* instruction) {
- DCHECK(preheader != nullptr);
+/** Helper method to insert an instruction. */
+static HInstruction* Insert(HBasicBlock* block, HInstruction* instruction) {
+ DCHECK(block != nullptr);
+ DCHECK(block->GetLastInstruction() != nullptr) << block->GetBlockId();
DCHECK(instruction != nullptr);
- preheader->InsertInstructionBefore(instruction, preheader->GetLastInstruction());
+ block->InsertInstructionBefore(instruction, block->GetLastInstruction());
return instruction;
}
@@ -91,48 +93,98 @@
DCHECK(induction_analysis != nullptr);
}
-InductionVarRange::Value InductionVarRange::GetMinInduction(HInstruction* context,
- HInstruction* instruction) {
- return GetInduction(context, instruction, /* is_min */ true);
-}
-
-InductionVarRange::Value InductionVarRange::GetMaxInduction(HInstruction* context,
- HInstruction* instruction) {
- return SimplifyMax(GetInduction(context, instruction, /* is_min */ false));
+void InductionVarRange::GetInductionRange(HInstruction* context,
+ HInstruction* instruction,
+ /*out*/Value* min_val,
+ /*out*/Value* max_val,
+ /*out*/bool* needs_finite_test) {
+ HLoopInformation* loop = context->GetBlock()->GetLoopInformation(); // closest enveloping loop
+ if (loop != nullptr) {
+ // Set up loop information.
+ HBasicBlock* header = loop->GetHeader();
+ bool in_body = context->GetBlock() != header;
+ HInductionVarAnalysis::InductionInfo* info =
+ induction_analysis_->LookupInfo(loop, instruction);
+ HInductionVarAnalysis::InductionInfo* trip =
+ induction_analysis_->LookupInfo(loop, header->GetLastInstruction());
+ // Find range.
+ *min_val = GetVal(info, trip, in_body, /* is_min */ true);
+ *max_val = SimplifyMax(GetVal(info, trip, in_body, /* is_min */ false));
+ *needs_finite_test = NeedsTripCount(info) && IsUnsafeTripCount(trip);
+ } else {
+ // No loop to analyze.
+ *min_val = Value();
+ *max_val = Value();
+ *needs_finite_test = false;
+ }
}
bool InductionVarRange::CanGenerateCode(HInstruction* context,
HInstruction* instruction,
- /*out*/bool* top_test) {
- return GenerateCode(context, instruction, nullptr, nullptr, nullptr, nullptr, top_test);
+ /*out*/bool* needs_finite_test,
+ /*out*/bool* needs_taken_test) {
+ return GenerateCode(context,
+ instruction,
+ nullptr, nullptr, nullptr, nullptr, nullptr, // nothing generated yet
+ needs_finite_test,
+ needs_taken_test);
}
-bool InductionVarRange::GenerateCode(HInstruction* context,
- HInstruction* instruction,
- HGraph* graph,
- HBasicBlock* block,
- /*out*/HInstruction** lower,
- /*out*/HInstruction** upper) {
- return GenerateCode(context, instruction, graph, block, lower, upper, nullptr);
+void InductionVarRange::GenerateRangeCode(HInstruction* context,
+ HInstruction* instruction,
+ HGraph* graph,
+ HBasicBlock* block,
+ /*out*/HInstruction** lower,
+ /*out*/HInstruction** upper) {
+ bool b1, b2; // unused
+ if (!GenerateCode(context, instruction, graph, block, lower, upper, nullptr, &b1, &b2)) {
+ LOG(FATAL) << "Failed precondition: GenerateCode()";
+ }
+}
+
+void InductionVarRange::GenerateTakenTest(HInstruction* context,
+ HGraph* graph,
+ HBasicBlock* block,
+ /*out*/HInstruction** taken_test) {
+ bool b1, b2; // unused
+ if (!GenerateCode(context, context, graph, block, nullptr, nullptr, taken_test, &b1, &b2)) {
+ LOG(FATAL) << "Failed precondition: GenerateCode()";
+ }
}
//
// Private class methods.
//
-InductionVarRange::Value InductionVarRange::GetInduction(HInstruction* context,
- HInstruction* instruction,
- bool is_min) {
- HLoopInformation* loop = context->GetBlock()->GetLoopInformation(); // closest enveloping loop
- if (loop != nullptr) {
- HBasicBlock* header = loop->GetHeader();
- bool in_body = context->GetBlock() != header;
- return GetVal(induction_analysis_->LookupInfo(loop, instruction),
- induction_analysis_->LookupInfo(loop, header->GetLastInstruction()),
- in_body,
- is_min);
+bool InductionVarRange::NeedsTripCount(HInductionVarAnalysis::InductionInfo* info) {
+ if (info != nullptr) {
+ if (info->induction_class == HInductionVarAnalysis::kLinear) {
+ return true;
+ } else if (info->induction_class == HInductionVarAnalysis::kWrapAround) {
+ return NeedsTripCount(info->op_b);
+ }
}
- return Value();
+ return false;
+}
+
+bool InductionVarRange::IsBodyTripCount(HInductionVarAnalysis::InductionInfo* trip) {
+ if (trip != nullptr) {
+ if (trip->induction_class == HInductionVarAnalysis::kInvariant) {
+ return trip->operation == HInductionVarAnalysis::kTripCountInBody ||
+ trip->operation == HInductionVarAnalysis::kTripCountInBodyUnsafe;
+ }
+ }
+ return false;
+}
+
+bool InductionVarRange::IsUnsafeTripCount(HInductionVarAnalysis::InductionInfo* trip) {
+ if (trip != nullptr) {
+ if (trip->induction_class == HInductionVarAnalysis::kInvariant) {
+ return trip->operation == HInductionVarAnalysis::kTripCountInBodyUnsafe ||
+ trip->operation == HInductionVarAnalysis::kTripCountInLoopUnsafe;
+ }
+ }
+ return false;
}
InductionVarRange::Value InductionVarRange::GetFetch(HInstruction* instruction,
@@ -184,11 +236,13 @@
case HInductionVarAnalysis::kFetch:
return GetFetch(info->fetch, trip, in_body, is_min);
case HInductionVarAnalysis::kTripCountInLoop:
+ case HInductionVarAnalysis::kTripCountInLoopUnsafe:
if (!in_body && !is_min) { // one extra!
return GetVal(info->op_a, trip, in_body, is_min);
}
FALLTHROUGH_INTENDED;
case HInductionVarAnalysis::kTripCountInBody:
+ case HInductionVarAnalysis::kTripCountInBodyUnsafe:
if (is_min) {
return Value(0);
} else if (in_body) {
@@ -356,25 +410,42 @@
HBasicBlock* block,
/*out*/HInstruction** lower,
/*out*/HInstruction** upper,
- /*out*/bool* top_test) {
+ /*out*/HInstruction** taken_test,
+ /*out*/bool* needs_finite_test,
+ /*out*/bool* needs_taken_test) {
HLoopInformation* loop = context->GetBlock()->GetLoopInformation(); // closest enveloping loop
if (loop != nullptr) {
+ // Set up loop information.
HBasicBlock* header = loop->GetHeader();
bool in_body = context->GetBlock() != header;
- HInductionVarAnalysis::InductionInfo* info = induction_analysis_->LookupInfo(loop, instruction);
+ HInductionVarAnalysis::InductionInfo* info =
+ induction_analysis_->LookupInfo(loop, instruction);
+ if (info == nullptr) {
+ return false; // nothing to analyze
+ }
HInductionVarAnalysis::InductionInfo* trip =
induction_analysis_->LookupInfo(loop, header->GetLastInstruction());
- if (info != nullptr && trip != nullptr) {
- if (top_test != nullptr) {
- *top_test = trip->operation != HInductionVarAnalysis::kTripCountInLoop;
+ // Determine what tests are needed.
+ *needs_finite_test = NeedsTripCount(info) && IsUnsafeTripCount(trip);
+ *needs_taken_test = NeedsTripCount(info) && IsBodyTripCount(trip);
+ // Code generation for taken test: generate the code when requested or otherwise analyze
+ // if code generation is feasible when taken test is needed.
+ if (taken_test != nullptr) {
+ return GenerateCode(
+ trip->op_b, nullptr, graph, block, taken_test, in_body, /* is_min */ false);
+ } else if (*needs_taken_test) {
+ if (!GenerateCode(
+ trip->op_b, nullptr, nullptr, nullptr, nullptr, in_body, /* is_min */ false)) {
+ return false;
}
- return
+ }
+ // Code generation for lower and upper.
+ return
// Success on lower if invariant (not set), or code can be generated.
((info->induction_class == HInductionVarAnalysis::kInvariant) ||
GenerateCode(info, trip, graph, block, lower, in_body, /* is_min */ true)) &&
// And success on upper.
GenerateCode(info, trip, graph, block, upper, in_body, /* is_min */ false);
- }
}
return false;
}
@@ -387,19 +458,38 @@
bool in_body,
bool is_min) {
if (info != nullptr) {
+ // Handle current operation.
Primitive::Type type = Primitive::kPrimInt;
HInstruction* opa = nullptr;
HInstruction* opb = nullptr;
- int32_t value = 0;
switch (info->induction_class) {
case HInductionVarAnalysis::kInvariant:
// Invariants.
switch (info->operation) {
case HInductionVarAnalysis::kAdd:
+ case HInductionVarAnalysis::kLT:
+ case HInductionVarAnalysis::kLE:
+ case HInductionVarAnalysis::kGT:
+ case HInductionVarAnalysis::kGE:
if (GenerateCode(info->op_a, trip, graph, block, &opa, in_body, is_min) &&
GenerateCode(info->op_b, trip, graph, block, &opb, in_body, is_min)) {
if (graph != nullptr) {
- *result = Insert(block, new (graph->GetArena()) HAdd(type, opa, opb));
+ HInstruction* operation = nullptr;
+ switch (info->operation) {
+ case HInductionVarAnalysis::kAdd:
+ operation = new (graph->GetArena()) HAdd(type, opa, opb); break;
+ case HInductionVarAnalysis::kLT:
+ operation = new (graph->GetArena()) HLessThan(opa, opb); break;
+ case HInductionVarAnalysis::kLE:
+ operation = new (graph->GetArena()) HLessThanOrEqual(opa, opb); break;
+ case HInductionVarAnalysis::kGT:
+ operation = new (graph->GetArena()) HGreaterThan(opa, opb); break;
+ case HInductionVarAnalysis::kGE:
+ operation = new (graph->GetArena()) HGreaterThanOrEqual(opa, opb); break;
+ default:
+ LOG(FATAL) << "unknown operation";
+ }
+ *result = Insert(block, operation);
}
return true;
}
@@ -427,11 +517,13 @@
}
return true;
case HInductionVarAnalysis::kTripCountInLoop:
+ case HInductionVarAnalysis::kTripCountInLoopUnsafe:
if (!in_body && !is_min) { // one extra!
return GenerateCode(info->op_a, trip, graph, block, result, in_body, is_min);
}
FALLTHROUGH_INTENDED;
case HInductionVarAnalysis::kTripCountInBody:
+ case HInductionVarAnalysis::kTripCountInBodyUnsafe:
if (is_min) {
if (graph != nullptr) {
*result = graph->GetIntConstant(0);
@@ -452,23 +544,31 @@
break;
}
break;
- case HInductionVarAnalysis::kLinear:
- // Linear induction a * i + b, for normalized 0 <= i < TC. Restrict to unit stride only
- // to avoid arithmetic wrap-around situations that are hard to guard against.
- if (GetConstant(info->op_a, &value)) {
- if (value == 1 || value == -1) {
- const bool is_min_a = value == 1 ? is_min : !is_min;
- if (GenerateCode(trip, trip, graph, block, &opa, in_body, is_min_a) &&
- GenerateCode(info->op_b, trip, graph, block, &opb, in_body, is_min)) {
- if (graph != nullptr) {
- *result = Insert(block, new (graph->GetArena()) HAdd(type, opa, opb));
+ case HInductionVarAnalysis::kLinear: {
+ // Linear induction a * i + b, for normalized 0 <= i < TC. Restrict to unit stride only
+ // to avoid arithmetic wrap-around situations that are hard to guard against.
+ int32_t stride_value = 0;
+ if (GetConstant(info->op_a, &stride_value)) {
+ if (stride_value == 1 || stride_value == -1) {
+ const bool is_min_a = stride_value == 1 ? is_min : !is_min;
+ if (GenerateCode(trip, trip, graph, block, &opa, in_body, is_min_a) &&
+ GenerateCode(info->op_b, trip, graph, block, &opb, in_body, is_min)) {
+ if (graph != nullptr) {
+ HInstruction* oper;
+ if (stride_value == 1) {
+ oper = new (graph->GetArena()) HAdd(type, opa, opb);
+ } else {
+ oper = new (graph->GetArena()) HSub(type, opb, opa);
+ }
+ *result = Insert(block, oper);
+ }
+ return true;
}
- return true;
}
}
}
break;
- default: // TODO(ajcbik): add more cases
+ default:
break;
}
}
diff --git a/compiler/optimizing/induction_var_range.h b/compiler/optimizing/induction_var_range.h
index 7fa5a26..7984871 100644
--- a/compiler/optimizing/induction_var_range.h
+++ b/compiler/optimizing/induction_var_range.h
@@ -57,29 +57,33 @@
explicit InductionVarRange(HInductionVarAnalysis* induction);
/**
- * Given a context denoted by the first instruction, returns a,
- * possibly conservative, lower bound on the instruction's value.
+ * Given a context denoted by the first instruction, returns a possibly conservative
+ * lower and upper bound on the instruction's value in the output parameters min_val
+ * and max_val, respectively. The need_finite_test flag denotes if an additional finite-test
+ * is needed to protect the range evaluation inside its loop.
*/
- Value GetMinInduction(HInstruction* context, HInstruction* instruction);
+ void GetInductionRange(HInstruction* context,
+ HInstruction* instruction,
+ /*out*/Value* min_val,
+ /*out*/Value* max_val,
+ /*out*/bool* needs_finite_test);
/**
- * Given a context denoted by the first instruction, returns a,
- * possibly conservative, upper bound on the instruction's value.
+ * Returns true if range analysis is able to generate code for the lower and upper
+ * bound expressions on the instruction in the given context. The need_finite_test
+ * and need_taken test flags denote if an additional finite-test and/or taken-test
+ * are needed to protect the range evaluation inside its loop.
*/
- Value GetMaxInduction(HInstruction* context, HInstruction* instruction);
-
- /**
- * Returns true if range analysis is able to generate code for the lower and upper bound
- * expressions on the instruction in the given context. Output parameter top_test denotes
- * whether a top test is needed to protect the trip-count expression evaluation.
- */
- bool CanGenerateCode(HInstruction* context, HInstruction* instruction, /*out*/bool* top_test);
+ bool CanGenerateCode(HInstruction* context,
+ HInstruction* instruction,
+ /*out*/bool* needs_finite_test,
+ /*out*/bool* needs_taken_test);
/**
* Generates the actual code in the HIR for the lower and upper bound expressions on the
* instruction in the given context. Code for the lower and upper bound expression are
- * generated in given block and graph and are returned in lower and upper, respectively.
- * For a loop invariant, lower is not set.
+ * generated in given block and graph and are returned in the output parameters lower and
+ * upper, respectively. For a loop invariant, lower is not set.
*
* For example, given expression x+i with range [0, 5] for i, calling this method
* will generate the following sequence:
@@ -87,20 +91,35 @@
* block:
* lower: add x, 0
* upper: add x, 5
+ *
+ * Precondition: CanGenerateCode() returns true.
*/
- bool GenerateCode(HInstruction* context,
- HInstruction* instruction,
- HGraph* graph,
- HBasicBlock* block,
- /*out*/HInstruction** lower,
- /*out*/HInstruction** upper);
+ void GenerateRangeCode(HInstruction* context,
+ HInstruction* instruction,
+ HGraph* graph,
+ HBasicBlock* block,
+ /*out*/HInstruction** lower,
+ /*out*/HInstruction** upper);
+
+ /**
+ * Generates explicit taken-test for the loop in the given context. Code is generated in
+ * given block and graph. The taken-test is returned in parameter test.
+ *
+ * Precondition: CanGenerateCode() returns true and needs_taken_test is set.
+ */
+ void GenerateTakenTest(HInstruction* context,
+ HGraph* graph,
+ HBasicBlock* block,
+ /*out*/HInstruction** taken_test);
private:
//
// Private helper methods.
//
- Value GetInduction(HInstruction* context, HInstruction* instruction, bool is_min);
+ static bool NeedsTripCount(HInductionVarAnalysis::InductionInfo* info);
+ static bool IsBodyTripCount(HInductionVarAnalysis::InductionInfo* trip);
+ static bool IsUnsafeTripCount(HInductionVarAnalysis::InductionInfo* trip);
static Value GetFetch(HInstruction* instruction,
HInductionVarAnalysis::InductionInfo* trip,
@@ -130,8 +149,8 @@
static Value MergeVal(Value v1, Value v2, bool is_min);
/**
- * Generates code for lower/upper expression in the HIR. Returns true on success.
- * With graph == nullptr, the method can be used to determine if code generation
+ * Generates code for lower/upper/taken-test in the HIR. Returns true on success.
+ * With values nullptr, the method can be used to determine if code generation
* would be successful without generating actual code yet.
*/
bool GenerateCode(HInstruction* context,
@@ -140,7 +159,9 @@
HBasicBlock* block,
/*out*/HInstruction** lower,
/*out*/HInstruction** upper,
- bool* top_test);
+ /*out*/HInstruction** taken_test,
+ /*out*/bool* needs_finite_test,
+ /*out*/bool* needs_taken_test);
static bool GenerateCode(HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* trip,
diff --git a/compiler/optimizing/induction_var_range_test.cc b/compiler/optimizing/induction_var_range_test.cc
index ce8926a..fda5153 100644
--- a/compiler/optimizing/induction_var_range_test.cc
+++ b/compiler/optimizing/induction_var_range_test.cc
@@ -46,6 +46,10 @@
EXPECT_EQ(v1.is_known, v2.is_known);
}
+ //
+ // Construction methods.
+ //
+
/** Constructs bare minimum graph. */
void BuildGraph() {
graph_->SetNumberOfVRegs(1);
@@ -58,7 +62,7 @@
}
/** Constructs loop with given upper bound. */
- void BuildLoop(HInstruction* upper) {
+ void BuildLoop(int32_t lower, HInstruction* upper, int32_t stride) {
// Control flow.
loop_preheader_ = new (&allocator_) HBasicBlock(graph_);
graph_->AddBlock(loop_preheader_);
@@ -75,18 +79,22 @@
HLocal* induc = new (&allocator_) HLocal(0);
entry_block_->AddInstruction(induc);
loop_preheader_->AddInstruction(
- new (&allocator_) HStoreLocal(induc, graph_->GetIntConstant(0))); // i = 0
+ new (&allocator_) HStoreLocal(induc, graph_->GetIntConstant(lower))); // i = l
loop_preheader_->AddInstruction(new (&allocator_) HGoto());
HInstruction* load = new (&allocator_) HLoadLocal(induc, Primitive::kPrimInt);
loop_header->AddInstruction(load);
- condition_ = new (&allocator_) HLessThan(load, upper);
+ if (stride > 0) {
+ condition_ = new (&allocator_) HLessThan(load, upper); // i < u
+ } else {
+ condition_ = new (&allocator_) HGreaterThan(load, upper); // i > u
+ }
loop_header->AddInstruction(condition_);
- loop_header->AddInstruction(new (&allocator_) HIf(condition_)); // i < u
+ loop_header->AddInstruction(new (&allocator_) HIf(condition_));
load = new (&allocator_) HLoadLocal(induc, Primitive::kPrimInt);
loop_body->AddInstruction(load);
- increment_ = new (&allocator_) HAdd(Primitive::kPrimInt, load, graph_->GetIntConstant(1));
+ increment_ = new (&allocator_) HAdd(Primitive::kPrimInt, load, graph_->GetIntConstant(stride));
loop_body->AddInstruction(increment_);
- loop_body->AddInstruction(new (&allocator_) HStoreLocal(induc, increment_)); // i++
+ loop_body->AddInstruction(new (&allocator_) HStoreLocal(induc, increment_)); // i += s
loop_body->AddInstruction(new (&allocator_) HGoto());
exit_block_->AddInstruction(new (&allocator_) HReturnVoid());
}
@@ -124,8 +132,20 @@
}
/** Constructs a trip-count. */
- HInductionVarAnalysis::InductionInfo* CreateTripCount(int32_t tc) {
- return iva_->CreateTripCount(HInductionVarAnalysis::kTripCountInLoop, CreateConst(tc), nullptr);
+ HInductionVarAnalysis::InductionInfo* CreateTripCount(int32_t tc, bool in_loop, bool safe) {
+ if (in_loop && safe) {
+ return iva_->CreateTripCount(
+ HInductionVarAnalysis::kTripCountInLoop, CreateConst(tc), nullptr);
+ } else if (in_loop) {
+ return iva_->CreateTripCount(
+ HInductionVarAnalysis::kTripCountInLoopUnsafe, CreateConst(tc), nullptr);
+ } else if (safe) {
+ return iva_->CreateTripCount(
+ HInductionVarAnalysis::kTripCountInBody, CreateConst(tc), nullptr);
+ } else {
+ return iva_->CreateTripCount(
+ HInductionVarAnalysis::kTripCountInBodyUnsafe, CreateConst(tc), nullptr);
+ }
}
/** Constructs a linear a * i + b induction. */
@@ -139,16 +159,34 @@
HInductionVarAnalysis::kPeriodic, CreateConst(lo), CreateConst(hi));
}
+ /** Constructs a wrap-around induction consisting of a constant, followed info */
+ HInductionVarAnalysis::InductionInfo* CreateWrapAround(
+ int32_t initial,
+ HInductionVarAnalysis::InductionInfo* info) {
+ return iva_->CreateInduction(HInductionVarAnalysis::kWrapAround, CreateConst(initial), info);
+ }
+
/** Constructs a wrap-around induction consisting of a constant, followed by a range. */
HInductionVarAnalysis::InductionInfo* CreateWrapAround(int32_t initial, int32_t lo, int32_t hi) {
- return iva_->CreateInduction(
- HInductionVarAnalysis::kWrapAround, CreateConst(initial), CreateRange(lo, hi));
+ return CreateWrapAround(initial, CreateRange(lo, hi));
}
//
// Relay methods.
//
+ bool NeedsTripCount(HInductionVarAnalysis::InductionInfo* info) {
+ return InductionVarRange::NeedsTripCount(info);
+ }
+
+ bool IsBodyTripCount(HInductionVarAnalysis::InductionInfo* trip) {
+ return InductionVarRange::IsBodyTripCount(trip);
+ }
+
+ bool IsUnsafeTripCount(HInductionVarAnalysis::InductionInfo* trip) {
+ return InductionVarRange::IsUnsafeTripCount(trip);
+ }
+
Value GetMin(HInductionVarAnalysis::InductionInfo* info,
HInductionVarAnalysis::InductionInfo* induc) {
return InductionVarRange::GetVal(info, induc, /* in_body */ true, /* is_min */ true);
@@ -202,6 +240,26 @@
// Tests on static methods.
//
+TEST_F(InductionVarRangeTest, TripCountProperties) {
+ EXPECT_FALSE(NeedsTripCount(nullptr));
+ EXPECT_FALSE(NeedsTripCount(CreateConst(1)));
+ EXPECT_TRUE(NeedsTripCount(CreateLinear(1, 1)));
+ EXPECT_FALSE(NeedsTripCount(CreateWrapAround(1, 2, 3)));
+ EXPECT_TRUE(NeedsTripCount(CreateWrapAround(1, CreateLinear(1, 1))));
+
+ EXPECT_FALSE(IsBodyTripCount(nullptr));
+ EXPECT_FALSE(IsBodyTripCount(CreateTripCount(100, true, true)));
+ EXPECT_FALSE(IsBodyTripCount(CreateTripCount(100, true, false)));
+ EXPECT_TRUE(IsBodyTripCount(CreateTripCount(100, false, true)));
+ EXPECT_TRUE(IsBodyTripCount(CreateTripCount(100, false, false)));
+
+ EXPECT_FALSE(IsUnsafeTripCount(nullptr));
+ EXPECT_FALSE(IsUnsafeTripCount(CreateTripCount(100, true, true)));
+ EXPECT_TRUE(IsUnsafeTripCount(CreateTripCount(100, true, false)));
+ EXPECT_FALSE(IsUnsafeTripCount(CreateTripCount(100, false, true)));
+ EXPECT_TRUE(IsUnsafeTripCount(CreateTripCount(100, false, false)));
+}
+
TEST_F(InductionVarRangeTest, GetMinMaxNull) {
ExpectEqual(Value(), GetMin(nullptr, nullptr));
ExpectEqual(Value(), GetMax(nullptr, nullptr));
@@ -279,10 +337,10 @@
}
TEST_F(InductionVarRangeTest, GetMinMaxLinear) {
- ExpectEqual(Value(20), GetMin(CreateLinear(10, 20), CreateTripCount(100)));
- ExpectEqual(Value(1010), GetMax(CreateLinear(10, 20), CreateTripCount(100)));
- ExpectEqual(Value(-970), GetMin(CreateLinear(-10, 20), CreateTripCount(100)));
- ExpectEqual(Value(20), GetMax(CreateLinear(-10, 20), CreateTripCount(100)));
+ ExpectEqual(Value(20), GetMin(CreateLinear(10, 20), CreateTripCount(100, true, true)));
+ ExpectEqual(Value(1010), GetMax(CreateLinear(10, 20), CreateTripCount(100, true, true)));
+ ExpectEqual(Value(-970), GetMin(CreateLinear(-10, 20), CreateTripCount(100, true, true)));
+ ExpectEqual(Value(20), GetMax(CreateLinear(-10, 20), CreateTripCount(100, true, true)));
}
TEST_F(InductionVarRangeTest, GetMinMaxWrapAround) {
@@ -398,61 +456,98 @@
// Tests on instance methods.
//
-TEST_F(InductionVarRangeTest, FindRangeConstantTripCount) {
- BuildLoop(graph_->GetIntConstant(1000));
+TEST_F(InductionVarRangeTest, ConstantTripCountUp) {
+ BuildLoop(0, graph_->GetIntConstant(1000), 1);
PerformInductionVarAnalysis();
InductionVarRange range(iva_);
+ Value v1, v2;
+ bool needs_finite_test = true;
+
// In context of header: known.
- ExpectEqual(Value(0), range.GetMinInduction(condition_, condition_->InputAt(0)));
- ExpectEqual(Value(1000), range.GetMaxInduction(condition_, condition_->InputAt(0)));
+ range.GetInductionRange(condition_, condition_->InputAt(0), &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(0), v1);
+ ExpectEqual(Value(1000), v2);
// In context of loop-body: known.
- ExpectEqual(Value(0), range.GetMinInduction(increment_, condition_->InputAt(0)));
- ExpectEqual(Value(999), range.GetMaxInduction(increment_, condition_->InputAt(0)));
- ExpectEqual(Value(1), range.GetMinInduction(increment_, increment_));
- ExpectEqual(Value(1000), range.GetMaxInduction(increment_, increment_));
+ range.GetInductionRange(increment_, condition_->InputAt(0), &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(0), v1);
+ ExpectEqual(Value(999), v2);
+ range.GetInductionRange(increment_, increment_, &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(1), v1);
+ ExpectEqual(Value(1000), v2);
}
-TEST_F(InductionVarRangeTest, FindRangeSymbolicTripCount) {
- HInstruction* parameter = new (&allocator_) HParameterValue(
- graph_->GetDexFile(), 0, 0, Primitive::kPrimInt);
- entry_block_->AddInstruction(parameter);
- BuildLoop(parameter);
+TEST_F(InductionVarRangeTest, ConstantTripCountDown) {
+ BuildLoop(1000, graph_->GetIntConstant(0), -1);
PerformInductionVarAnalysis();
InductionVarRange range(iva_);
- // In context of header: full range unknown.
- ExpectEqual(Value(0), range.GetMinInduction(condition_, condition_->InputAt(0)));
- ExpectEqual(Value(), range.GetMaxInduction(condition_, condition_->InputAt(0)));
+ Value v1, v2;
+ bool needs_finite_test = true;
+
+ // In context of header: known.
+ range.GetInductionRange(condition_, condition_->InputAt(0), &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(0), v1);
+ ExpectEqual(Value(1000), v2);
// In context of loop-body: known.
- ExpectEqual(Value(0), range.GetMinInduction(increment_, condition_->InputAt(0)));
- ExpectEqual(Value(parameter, 1, -1), range.GetMaxInduction(increment_, condition_->InputAt(0)));
- ExpectEqual(Value(1), range.GetMinInduction(increment_, increment_));
- ExpectEqual(Value(parameter, 1, 0), range.GetMaxInduction(increment_, increment_));
+ range.GetInductionRange(increment_, condition_->InputAt(0), &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(1), v1);
+ ExpectEqual(Value(1000), v2);
+ range.GetInductionRange(increment_, increment_, &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(0), v1);
+ ExpectEqual(Value(999), v2);
}
-TEST_F(InductionVarRangeTest, CodeGeneration) {
+TEST_F(InductionVarRangeTest, SymbolicTripCountUp) {
HInstruction* parameter = new (&allocator_) HParameterValue(
graph_->GetDexFile(), 0, 0, Primitive::kPrimInt);
entry_block_->AddInstruction(parameter);
- BuildLoop(parameter);
+ BuildLoop(0, parameter, 1);
PerformInductionVarAnalysis();
InductionVarRange range(iva_);
+ Value v1, v2;
+ bool needs_finite_test = true;
+ bool needs_taken_test = true;
+
+ // In context of header: upper unknown.
+ range.GetInductionRange(condition_, condition_->InputAt(0), &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(0), v1);
+ ExpectEqual(Value(), v2);
+
+ // In context of loop-body: known.
+ range.GetInductionRange(increment_, condition_->InputAt(0), &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(0), v1);
+ ExpectEqual(Value(parameter, 1, -1), v2);
+ range.GetInductionRange(increment_, increment_, &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(1), v1);
+ ExpectEqual(Value(parameter, 1, 0), v2);
+
HInstruction* lower = nullptr;
HInstruction* upper = nullptr;
- bool top_test = false;
+ HInstruction* taken = nullptr;
// Can generate code in context of loop-body only.
- EXPECT_FALSE(range.CanGenerateCode(condition_, condition_->InputAt(0), &top_test));
- ASSERT_TRUE(range.CanGenerateCode(increment_, condition_->InputAt(0), &top_test));
- EXPECT_TRUE(top_test);
+ EXPECT_FALSE(range.CanGenerateCode(
+ condition_, condition_->InputAt(0), &needs_finite_test, &needs_taken_test));
+ ASSERT_TRUE(range.CanGenerateCode(
+ increment_, condition_->InputAt(0), &needs_finite_test, &needs_taken_test));
+ EXPECT_FALSE(needs_finite_test);
+ EXPECT_TRUE(needs_taken_test);
// Generates code.
- EXPECT_TRUE(range.GenerateCode(
- increment_, condition_->InputAt(0), graph_, loop_preheader_, &lower, &upper));
+ range.GenerateRangeCode(increment_, condition_->InputAt(0), graph_, loop_preheader_, &lower, &upper);
// Verify lower is 0+0.
ASSERT_TRUE(lower != nullptr);
@@ -462,7 +557,7 @@
ASSERT_TRUE(lower->InputAt(1)->IsIntConstant());
EXPECT_EQ(0, lower->InputAt(1)->AsIntConstant()->GetValue());
- // Verify upper is (V-1)+0
+ // Verify upper is (V-1)+0.
ASSERT_TRUE(upper != nullptr);
ASSERT_TRUE(upper->IsAdd());
ASSERT_TRUE(upper->InputAt(0)->IsSub());
@@ -471,6 +566,91 @@
EXPECT_EQ(1, upper->InputAt(0)->InputAt(1)->AsIntConstant()->GetValue());
ASSERT_TRUE(upper->InputAt(1)->IsIntConstant());
EXPECT_EQ(0, upper->InputAt(1)->AsIntConstant()->GetValue());
+
+ // Verify taken-test is 0<V.
+ range.GenerateTakenTest(increment_, graph_, loop_preheader_, &taken);
+ ASSERT_TRUE(taken != nullptr);
+ ASSERT_TRUE(taken->IsLessThan());
+ ASSERT_TRUE(taken->InputAt(0)->IsIntConstant());
+ EXPECT_EQ(0, taken->InputAt(0)->AsIntConstant()->GetValue());
+ EXPECT_TRUE(taken->InputAt(1)->IsParameterValue());
+}
+
+TEST_F(InductionVarRangeTest, SymbolicTripCountDown) {
+ HInstruction* parameter = new (&allocator_) HParameterValue(
+ graph_->GetDexFile(), 0, 0, Primitive::kPrimInt);
+ entry_block_->AddInstruction(parameter);
+ BuildLoop(1000, parameter, -1);
+ PerformInductionVarAnalysis();
+ InductionVarRange range(iva_);
+
+ Value v1, v2;
+ bool needs_finite_test = true;
+ bool needs_taken_test = true;
+
+ // In context of header: lower unknown.
+ range.GetInductionRange(condition_, condition_->InputAt(0), &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(), v1);
+ ExpectEqual(Value(1000), v2);
+
+ // In context of loop-body: known.
+ range.GetInductionRange(increment_, condition_->InputAt(0), &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(parameter, 1, 1), v1);
+ ExpectEqual(Value(1000), v2);
+ range.GetInductionRange(increment_, increment_, &v1, &v2, &needs_finite_test);
+ EXPECT_FALSE(needs_finite_test);
+ ExpectEqual(Value(parameter, 1, 0), v1);
+ ExpectEqual(Value(999), v2);
+
+ HInstruction* lower = nullptr;
+ HInstruction* upper = nullptr;
+ HInstruction* taken = nullptr;
+
+ // Can generate code in context of loop-body only.
+ EXPECT_FALSE(range.CanGenerateCode(
+ condition_, condition_->InputAt(0), &needs_finite_test, &needs_taken_test));
+ ASSERT_TRUE(range.CanGenerateCode(
+ increment_, condition_->InputAt(0), &needs_finite_test, &needs_taken_test));
+ EXPECT_FALSE(needs_finite_test);
+ EXPECT_TRUE(needs_taken_test);
+
+ // Generates code.
+ range.GenerateRangeCode(increment_, condition_->InputAt(0), graph_, loop_preheader_, &lower, &upper);
+
+ // Verify lower is 1000-(-(V-1000)-1).
+ ASSERT_TRUE(lower != nullptr);
+ ASSERT_TRUE(lower->IsSub());
+ ASSERT_TRUE(lower->InputAt(0)->IsIntConstant());
+ EXPECT_EQ(1000, lower->InputAt(0)->AsIntConstant()->GetValue());
+ lower = lower->InputAt(1);
+ ASSERT_TRUE(lower->IsSub());
+ ASSERT_TRUE(lower->InputAt(1)->IsIntConstant());
+ EXPECT_EQ(1, lower->InputAt(1)->AsIntConstant()->GetValue());
+ lower = lower->InputAt(0);
+ ASSERT_TRUE(lower->IsNeg());
+ lower = lower->InputAt(0);
+ ASSERT_TRUE(lower->IsSub());
+ EXPECT_TRUE(lower->InputAt(0)->IsParameterValue());
+ ASSERT_TRUE(lower->InputAt(1)->IsIntConstant());
+ EXPECT_EQ(1000, lower->InputAt(1)->AsIntConstant()->GetValue());
+
+ // Verify upper is 1000-0.
+ ASSERT_TRUE(upper != nullptr);
+ ASSERT_TRUE(upper->IsSub());
+ ASSERT_TRUE(upper->InputAt(0)->IsIntConstant());
+ EXPECT_EQ(1000, upper->InputAt(0)->AsIntConstant()->GetValue());
+ ASSERT_TRUE(upper->InputAt(1)->IsIntConstant());
+ EXPECT_EQ(0, upper->InputAt(1)->AsIntConstant()->GetValue());
+
+ // Verify taken-test is 1000>V.
+ range.GenerateTakenTest(increment_, graph_, loop_preheader_, &taken);
+ ASSERT_TRUE(taken != nullptr);
+ ASSERT_TRUE(taken->IsGreaterThan());
+ ASSERT_TRUE(taken->InputAt(0)->IsIntConstant());
+ EXPECT_EQ(1000, taken->InputAt(0)->AsIntConstant()->GetValue());
+ EXPECT_TRUE(taken->InputAt(1)->IsParameterValue());
}
} // namespace art
diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc
index 05c7eb0..ff843eb 100644
--- a/compiler/optimizing/intrinsics_mips64.cc
+++ b/compiler/optimizing/intrinsics_mips64.cc
@@ -101,11 +101,10 @@
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();
+ codegen->GenerateVirtualCall(invoke_->AsInvokeVirtual(), Location::RegisterLocation(A0));
}
+ codegen->RecordPcInfo(invoke_, invoke_->GetDexPc(), this);
// Copy the result back to the expected output.
Location out = invoke_->GetLocations()->Out();
diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc
index 14c65c9..a29f3ef 100644
--- a/compiler/optimizing/intrinsics_x86_64.cc
+++ b/compiler/optimizing/intrinsics_x86_64.cc
@@ -1605,7 +1605,7 @@
LocationSummary::kNoCall,
kIntrinsified);
locations->SetInAt(0, Location::RequiresRegister());
- locations->SetInAt(1, Location::RegisterOrInt32LongConstant(invoke->InputAt(1)));
+ locations->SetInAt(1, Location::RegisterOrInt32Constant(invoke->InputAt(1)));
}
static void GenPoke(LocationSummary* locations, Primitive::Type size, X86_64Assembler* assembler) {
diff --git a/compiler/optimizing/locations.cc b/compiler/optimizing/locations.cc
index ebdf7a2..1ab206f 100644
--- a/compiler/optimizing/locations.cc
+++ b/compiler/optimizing/locations.cc
@@ -17,6 +17,7 @@
#include "locations.h"
#include "nodes.h"
+#include "code_generator.h"
namespace art {
@@ -47,18 +48,26 @@
: Location::RequiresRegister();
}
-Location Location::RegisterOrInt32LongConstant(HInstruction* instruction) {
- if (instruction->IsIntConstant() || instruction->IsNullConstant()) {
- return Location::ConstantLocation(instruction->AsConstant());
- } else if (instruction->IsLongConstant()) {
- // Does the long constant fit in a 32 bit int?
- int64_t value = instruction->AsLongConstant()->GetValue();
- return IsInt<32>(value)
- ? Location::ConstantLocation(instruction->AsConstant())
- : Location::RequiresRegister();
- } else {
- return Location::RequiresRegister();
+Location Location::RegisterOrInt32Constant(HInstruction* instruction) {
+ HConstant* constant = instruction->AsConstant();
+ if (constant != nullptr) {
+ int64_t value = CodeGenerator::GetInt64ValueOf(constant);
+ if (IsInt<32>(value)) {
+ return Location::ConstantLocation(constant);
+ }
}
+ return Location::RequiresRegister();
+}
+
+Location Location::FpuRegisterOrInt32Constant(HInstruction* instruction) {
+ HConstant* constant = instruction->AsConstant();
+ if (constant != nullptr) {
+ int64_t value = CodeGenerator::GetInt64ValueOf(constant);
+ if (IsInt<32>(value)) {
+ return Location::ConstantLocation(constant);
+ }
+ }
+ return Location::RequiresFpuRegister();
}
Location Location::ByteRegisterOrConstant(int reg, HInstruction* instruction) {
@@ -67,6 +76,12 @@
: Location::RegisterLocation(reg);
}
+Location Location::FpuRegisterOrConstant(HInstruction* instruction) {
+ return instruction->IsConstant()
+ ? Location::ConstantLocation(instruction->AsConstant())
+ : Location::RequiresFpuRegister();
+}
+
std::ostream& operator<<(std::ostream& os, const Location& location) {
os << location.DebugString();
if (location.IsRegister() || location.IsFpuRegister()) {
diff --git a/compiler/optimizing/locations.h b/compiler/optimizing/locations.h
index d014379..1181007 100644
--- a/compiler/optimizing/locations.h
+++ b/compiler/optimizing/locations.h
@@ -354,8 +354,10 @@
}
static Location RegisterOrConstant(HInstruction* instruction);
- static Location RegisterOrInt32LongConstant(HInstruction* instruction);
+ static Location RegisterOrInt32Constant(HInstruction* instruction);
static Location ByteRegisterOrConstant(int reg, HInstruction* instruction);
+ static Location FpuRegisterOrConstant(HInstruction* instruction);
+ static Location FpuRegisterOrInt32Constant(HInstruction* instruction);
// The location of the first input to the instruction will be
// used to replace this unallocated location.
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 1995797..0f2c1cf 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -4238,7 +4238,7 @@
inputs_(number_of_inputs, arena->Adapter(kArenaAllocPhiInputs)),
reg_number_(reg_number),
type_(type),
- is_live_(true),
+ is_live_(false),
can_be_null_(true) {
}
@@ -4263,18 +4263,7 @@
void RemoveInputAt(size_t index);
Primitive::Type GetType() const OVERRIDE { return type_; }
- void SetType(Primitive::Type new_type) {
- // Make sure that only valid type changes occur. The following are allowed:
- // (1) void -> * (initial type assignment),
- // (2) int -> float/ref (primitive type propagation),
- // (3) long -> double (primitive type propagation).
- DCHECK(type_ == new_type ||
- type_ == Primitive::kPrimVoid ||
- (type_ == Primitive::kPrimInt && new_type == Primitive::kPrimFloat) ||
- (type_ == Primitive::kPrimInt && new_type == Primitive::kPrimNot) ||
- (type_ == Primitive::kPrimLong && new_type == Primitive::kPrimDouble));
- type_ = new_type;
- }
+ void SetType(Primitive::Type type) { type_ = type; }
bool CanBeNull() const OVERRIDE { return can_be_null_; }
void SetCanBeNull(bool can_be_null) { can_be_null_ = can_be_null; }
@@ -4496,8 +4485,7 @@
SideEffects additional_side_effects = SideEffects::None())
: HExpression(type,
SideEffects::ArrayReadOfType(type).Union(additional_side_effects),
- dex_pc),
- fixed_type_(type != Primitive::kPrimInt && type != Primitive::kPrimLong) {
+ dex_pc) {
SetRawInputAt(0, array);
SetRawInputAt(1, index);
}
@@ -4515,13 +4503,7 @@
return false;
}
- void SetType(Primitive::Type type) {
- DCHECK(type_ == type || !IsTypeFixed());
- type_ = type;
- }
-
- bool IsTypeFixed() const { return fixed_type_; }
- void FixType() { fixed_type_ = true; }
+ void SetType(Primitive::Type type) { type_ = type; }
HInstruction* GetArray() const { return InputAt(0); }
HInstruction* GetIndex() const { return InputAt(1); }
@@ -4529,12 +4511,6 @@
DECLARE_INSTRUCTION(ArrayGet);
private:
- // Bytecode aget(-wide) instructions have ambiguous type (int/float, long/double).
- // If the type can be determined from uses, `fixed_type_` should be set to true,
- // to prevent PrimitiveTypePropagation from changing it while typing phis. With
- // other aget-* variants, the type is always unambiguous.
- bool fixed_type_;
-
DISALLOW_COPY_AND_ASSIGN(HArrayGet);
};
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 8cb2cfc..7e3c5e6 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -56,6 +56,7 @@
#include "inliner.h"
#include "instruction_simplifier.h"
#include "intrinsics.h"
+#include "jit/jit_code_cache.h"
#include "licm.h"
#include "jni/quick/jni_compiler.h"
#include "load_store_elimination.h"
@@ -258,15 +259,6 @@
const DexFile& dex_file,
Handle<mirror::DexCache> dex_cache) const OVERRIDE;
- CompiledMethod* TryCompile(const DexFile::CodeItem* code_item,
- uint32_t access_flags,
- InvokeType invoke_type,
- uint16_t class_def_idx,
- uint32_t method_idx,
- jobject class_loader,
- const DexFile& dex_file,
- Handle<mirror::DexCache> dex_cache) const;
-
CompiledMethod* JniCompile(uint32_t access_flags,
uint32_t method_idx,
const DexFile& dex_file) const OVERRIDE {
@@ -291,23 +283,45 @@
}
}
+ bool JitCompile(Thread* self, jit::JitCodeCache* code_cache, ArtMethod* method)
+ OVERRIDE
+ SHARED_REQUIRES(Locks::mutator_lock_);
+
private:
// Whether we should run any optimization or register allocation. If false, will
// just run the code generation after the graph was built.
const bool run_optimizations_;
- // Optimize and compile `graph`.
- CompiledMethod* CompileOptimized(HGraph* graph,
- CodeGenerator* codegen,
- CompilerDriver* driver,
- const DexCompilationUnit& dex_compilation_unit,
- PassObserver* pass_observer) const;
+ // Create a 'CompiledMethod' for an optimized graph.
+ CompiledMethod* EmitOptimized(ArenaAllocator* arena,
+ CodeVectorAllocator* code_allocator,
+ CodeGenerator* codegen,
+ CompilerDriver* driver) const;
- // Just compile without doing optimizations.
- CompiledMethod* CompileBaseline(CodeGenerator* codegen,
- CompilerDriver* driver,
- const DexCompilationUnit& dex_compilation_unit,
- PassObserver* pass_observer) const;
+ // Create a 'CompiledMethod' for a non-optimized graph.
+ CompiledMethod* EmitBaseline(ArenaAllocator* arena,
+ CodeVectorAllocator* code_allocator,
+ CodeGenerator* codegen,
+ CompilerDriver* driver) const;
+
+ // Try compiling a method and return the code generator used for
+ // compiling it.
+ // This method:
+ // 1) Builds the graph. Returns null if it failed to build it.
+ // 2) If `run_optimizations_` is set:
+ // 2.1) Transform the graph to SSA. Returns null if it failed.
+ // 2.2) Run optimizations on the graph, including register allocator.
+ // 3) Generate code with the `code_allocator` provided.
+ CodeGenerator* TryCompile(ArenaAllocator* arena,
+ CodeVectorAllocator* code_allocator,
+ const DexFile::CodeItem* code_item,
+ uint32_t access_flags,
+ InvokeType invoke_type,
+ uint16_t class_def_idx,
+ uint32_t method_idx,
+ jobject class_loader,
+ const DexFile& dex_file,
+ Handle<mirror::DexCache> dex_cache) const;
std::unique_ptr<OptimizingCompilerStats> compilation_stats_;
@@ -446,13 +460,32 @@
}
}
+NO_INLINE // Avoid increasing caller's frame size by large stack-allocated objects.
+static void AllocateRegisters(HGraph* graph,
+ CodeGenerator* codegen,
+ PassObserver* pass_observer) {
+ PrepareForRegisterAllocation(graph).Run();
+ SsaLivenessAnalysis liveness(graph, codegen);
+ {
+ PassScope scope(SsaLivenessAnalysis::kLivenessPassName, pass_observer);
+ liveness.Analyze();
+ }
+ {
+ PassScope scope(RegisterAllocator::kRegisterAllocatorPassName, pass_observer);
+ RegisterAllocator(graph->GetArena(), codegen, liveness).AllocateRegisters();
+ }
+}
+
static void RunOptimizations(HGraph* graph,
CodeGenerator* codegen,
CompilerDriver* driver,
OptimizingCompilerStats* stats,
const DexCompilationUnit& dex_compilation_unit,
- PassObserver* pass_observer,
- StackHandleScopeCollection* handles) {
+ PassObserver* pass_observer) {
+ ScopedObjectAccess soa(Thread::Current());
+ StackHandleScopeCollection handles(soa.Self());
+ ScopedThreadSuspension sts(soa.Self(), kNative);
+
ArenaAllocator* arena = graph->GetArena();
HDeadCodeElimination* dce1 = new (arena) HDeadCodeElimination(
graph, stats, HDeadCodeElimination::kInitialDeadCodeEliminationPassName);
@@ -469,7 +502,7 @@
HInductionVarAnalysis* induction = new (arena) HInductionVarAnalysis(graph);
BoundsCheckElimination* bce = new (arena) BoundsCheckElimination(graph, induction);
ReferenceTypePropagation* type_propagation =
- new (arena) ReferenceTypePropagation(graph, handles);
+ 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");
@@ -492,7 +525,7 @@
RunOptimizations(optimizations1, arraysize(optimizations1), pass_observer);
- MaybeRunInliner(graph, codegen, driver, stats, dex_compilation_unit, pass_observer, handles);
+ 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.
@@ -532,6 +565,7 @@
}
RunArchOptimizations(driver->GetInstructionSet(), graph, stats, pass_observer);
+ AllocateRegisters(graph, codegen, pass_observer);
}
// The stack map we generate must be 4-byte aligned on ARM. Since existing
@@ -545,22 +579,6 @@
return ArrayRef<const uint8_t>(vector);
}
-NO_INLINE // Avoid increasing caller's frame size by large stack-allocated objects.
-static void AllocateRegisters(HGraph* graph,
- CodeGenerator* codegen,
- PassObserver* pass_observer) {
- PrepareForRegisterAllocation(graph).Run();
- SsaLivenessAnalysis liveness(graph, codegen);
- {
- PassScope scope(SsaLivenessAnalysis::kLivenessPassName, pass_observer);
- liveness.Analyze();
- }
- {
- PassScope scope(RegisterAllocator::kRegisterAllocatorPassName, pass_observer);
- RegisterAllocator(graph->GetArena(), codegen, liveness).AllocateRegisters();
- }
-}
-
static ArenaVector<LinkerPatch> EmitAndSortLinkerPatches(CodeGenerator* codegen) {
ArenaVector<LinkerPatch> linker_patches(codegen->GetGraph()->GetArena()->Adapter());
codegen->EmitLinkerPatches(&linker_patches);
@@ -574,74 +592,42 @@
return linker_patches;
}
-CompiledMethod* OptimizingCompiler::CompileOptimized(HGraph* graph,
- CodeGenerator* codegen,
- CompilerDriver* compiler_driver,
- const DexCompilationUnit& dex_compilation_unit,
- PassObserver* pass_observer) const {
- ScopedObjectAccess soa(Thread::Current());
- StackHandleScopeCollection handles(soa.Self());
- soa.Self()->TransitionFromRunnableToSuspended(kNative);
- RunOptimizations(graph,
- codegen,
- compiler_driver,
- compilation_stats_.get(),
- dex_compilation_unit,
- pass_observer,
- &handles);
-
- AllocateRegisters(graph, codegen, pass_observer);
-
- ArenaAllocator* arena = graph->GetArena();
- CodeVectorAllocator allocator(arena);
- DefaultSrcMap src_mapping_table;
- codegen->SetSrcMap(compiler_driver->GetCompilerOptions().GetGenerateDebugInfo()
- ? &src_mapping_table
- : nullptr);
- codegen->CompileOptimized(&allocator);
-
+CompiledMethod* OptimizingCompiler::EmitOptimized(ArenaAllocator* arena,
+ CodeVectorAllocator* code_allocator,
+ CodeGenerator* codegen,
+ CompilerDriver* compiler_driver) const {
ArenaVector<LinkerPatch> linker_patches = EmitAndSortLinkerPatches(codegen);
-
ArenaVector<uint8_t> stack_map(arena->Adapter(kArenaAllocStackMaps));
- codegen->BuildStackMaps(&stack_map);
+ stack_map.resize(codegen->ComputeStackMapsSize());
+ codegen->BuildStackMaps(MemoryRegion(stack_map.data(), stack_map.size()));
MaybeRecordStat(MethodCompilationStat::kCompiledOptimized);
CompiledMethod* compiled_method = CompiledMethod::SwapAllocCompiledMethod(
compiler_driver,
codegen->GetInstructionSet(),
- ArrayRef<const uint8_t>(allocator.GetMemory()),
+ ArrayRef<const uint8_t>(code_allocator->GetMemory()),
// Follow Quick's behavior and set the frame size to zero if it is
// considered "empty" (see the definition of
// art::CodeGenerator::HasEmptyFrame).
codegen->HasEmptyFrame() ? 0 : codegen->GetFrameSize(),
codegen->GetCoreSpillMask(),
codegen->GetFpuSpillMask(),
- ArrayRef<const SrcMapElem>(src_mapping_table),
+ ArrayRef<const SrcMapElem>(codegen->GetSrcMappingTable()),
ArrayRef<const uint8_t>(), // mapping_table.
ArrayRef<const uint8_t>(stack_map),
ArrayRef<const uint8_t>(), // native_gc_map.
ArrayRef<const uint8_t>(*codegen->GetAssembler()->cfi().data()),
ArrayRef<const LinkerPatch>(linker_patches));
- pass_observer->DumpDisassembly();
- soa.Self()->TransitionFromSuspendedToRunnable();
return compiled_method;
}
-CompiledMethod* OptimizingCompiler::CompileBaseline(
+CompiledMethod* OptimizingCompiler::EmitBaseline(
+ ArenaAllocator* arena,
+ CodeVectorAllocator* code_allocator,
CodeGenerator* codegen,
- CompilerDriver* compiler_driver,
- const DexCompilationUnit& dex_compilation_unit,
- PassObserver* pass_observer) const {
- ArenaAllocator* arena = codegen->GetGraph()->GetArena();
- CodeVectorAllocator allocator(arena);
- DefaultSrcMap src_mapping_table;
- codegen->SetSrcMap(compiler_driver->GetCompilerOptions().GetGenerateDebugInfo()
- ? &src_mapping_table
- : nullptr);
- codegen->CompileBaseline(&allocator);
-
+ CompilerDriver* compiler_driver) const {
ArenaVector<LinkerPatch> linker_patches = EmitAndSortLinkerPatches(codegen);
ArenaVector<uint8_t> mapping_table(arena->Adapter(kArenaAllocBaselineMaps));
@@ -649,37 +635,38 @@
ArenaVector<uint8_t> vmap_table(arena->Adapter(kArenaAllocBaselineMaps));
codegen->BuildVMapTable(&vmap_table);
ArenaVector<uint8_t> gc_map(arena->Adapter(kArenaAllocBaselineMaps));
- codegen->BuildNativeGCMap(&gc_map, dex_compilation_unit);
+ codegen->BuildNativeGCMap(&gc_map, *compiler_driver);
MaybeRecordStat(MethodCompilationStat::kCompiledBaseline);
CompiledMethod* compiled_method = CompiledMethod::SwapAllocCompiledMethod(
compiler_driver,
codegen->GetInstructionSet(),
- ArrayRef<const uint8_t>(allocator.GetMemory()),
+ ArrayRef<const uint8_t>(code_allocator->GetMemory()),
// Follow Quick's behavior and set the frame size to zero if it is
// considered "empty" (see the definition of
// art::CodeGenerator::HasEmptyFrame).
codegen->HasEmptyFrame() ? 0 : codegen->GetFrameSize(),
codegen->GetCoreSpillMask(),
codegen->GetFpuSpillMask(),
- ArrayRef<const SrcMapElem>(src_mapping_table),
+ ArrayRef<const SrcMapElem>(codegen->GetSrcMappingTable()),
AlignVectorSize(mapping_table),
AlignVectorSize(vmap_table),
AlignVectorSize(gc_map),
ArrayRef<const uint8_t>(*codegen->GetAssembler()->cfi().data()),
ArrayRef<const LinkerPatch>(linker_patches));
- pass_observer->DumpDisassembly();
return compiled_method;
}
-CompiledMethod* OptimizingCompiler::TryCompile(const DexFile::CodeItem* code_item,
- uint32_t access_flags,
- InvokeType invoke_type,
- uint16_t class_def_idx,
- uint32_t method_idx,
- jobject class_loader,
- const DexFile& dex_file,
- Handle<mirror::DexCache> dex_cache) const {
+CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* arena,
+ CodeVectorAllocator* code_allocator,
+ const DexFile::CodeItem* code_item,
+ uint32_t access_flags,
+ InvokeType invoke_type,
+ uint16_t class_def_idx,
+ uint32_t method_idx,
+ jobject class_loader,
+ const DexFile& dex_file,
+ Handle<mirror::DexCache> dex_cache) const {
std::string method_name = PrettyMethod(method_idx, dex_file);
MaybeRecordStat(MethodCompilationStat::kAttemptCompilation);
CompilerDriver* compiler_driver = GetCompilerDriver();
@@ -721,13 +708,10 @@
&& compiler_driver->RequiresConstructorBarrier(Thread::Current(),
dex_compilation_unit.GetDexFile(),
dex_compilation_unit.GetClassDefIndex());
- ArenaAllocator arena(Runtime::Current()->GetArenaPool());
- HGraph* graph = new (&arena) HGraph(
- &arena, dex_file, method_idx, requires_barrier, compiler_driver->GetInstructionSet(),
+ HGraph* graph = new (arena) HGraph(
+ arena, dex_file, method_idx, requires_barrier, compiler_driver->GetInstructionSet(),
kInvalidInvokeType, compiler_driver->GetCompilerOptions().GetDebuggable());
- bool shouldOptimize = method_name.find("$opt$reg$") != std::string::npos && run_optimizations_;
-
std::unique_ptr<CodeGenerator> codegen(
CodeGenerator::Create(graph,
instruction_set,
@@ -779,16 +763,8 @@
}
}
- bool can_allocate_registers = RegisterAllocator::CanAllocateRegistersFor(*graph, instruction_set);
-
- // `run_optimizations_` is set explicitly (either through a compiler filter
- // or the debuggable flag). If it is set, we can run baseline. Otherwise, we fall back
- // to Quick.
- bool can_use_baseline = !run_optimizations_ && builder.CanUseBaselineForStringInit();
- CompiledMethod* compiled_method = nullptr;
- if (run_optimizations_ && can_allocate_registers) {
- VLOG(compiler) << "Optimizing " << method_name;
-
+ VLOG(compiler) << "Optimizing " << method_name;
+ if (run_optimizations_) {
{
PassScope scope(SsaBuilder::kSsaBuilderPassName, &pass_observer);
if (!graph->TryBuildingSsa()) {
@@ -800,37 +776,26 @@
}
}
- compiled_method = CompileOptimized(graph,
- codegen.get(),
- compiler_driver,
- dex_compilation_unit,
- &pass_observer);
- } else if (shouldOptimize && can_allocate_registers) {
- LOG(FATAL) << "Could not allocate registers in optimizing compiler";
- UNREACHABLE();
- } else if (can_use_baseline) {
- VLOG(compiler) << "Compile baseline " << method_name;
-
- if (!run_optimizations_) {
- MaybeRecordStat(MethodCompilationStat::kNotOptimizedDisabled);
- } else if (!can_allocate_registers) {
- MaybeRecordStat(MethodCompilationStat::kNotOptimizedRegisterAllocator);
- }
-
- compiled_method = CompileBaseline(codegen.get(),
- compiler_driver,
- dex_compilation_unit,
- &pass_observer);
+ RunOptimizations(graph,
+ codegen.get(),
+ compiler_driver,
+ compilation_stats_.get(),
+ dex_compilation_unit,
+ &pass_observer);
+ codegen->CompileOptimized(code_allocator);
+ } else {
+ codegen->CompileBaseline(code_allocator);
}
+ pass_observer.DumpDisassembly();
if (kArenaAllocatorCountAllocations) {
- if (arena.BytesAllocated() > 4 * MB) {
- MemStats mem_stats(arena.GetMemStats());
+ if (arena->BytesAllocated() > 4 * MB) {
+ MemStats mem_stats(arena->GetMemStats());
LOG(INFO) << PrettyMethod(method_idx, dex_file) << " " << Dumpable<MemStats>(mem_stats);
}
}
- return compiled_method;
+ return codegen.release();
}
static bool CanHandleVerificationFailure(const VerifiedMethod* verified_method) {
@@ -852,26 +817,37 @@
Handle<mirror::DexCache> dex_cache) const {
CompilerDriver* compiler_driver = GetCompilerDriver();
CompiledMethod* method = nullptr;
- if (Runtime::Current()->IsAotCompiler()) {
- const VerifiedMethod* verified_method = compiler_driver->GetVerifiedMethod(&dex_file, method_idx);
- DCHECK(!verified_method->HasRuntimeThrow());
- if (compiler_driver->IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file)
- || CanHandleVerificationFailure(verified_method)) {
- method = TryCompile(code_item, access_flags, invoke_type, class_def_idx,
- method_idx, jclass_loader, dex_file, dex_cache);
- } else {
- if (compiler_driver->GetCompilerOptions().VerifyAtRuntime()) {
- MaybeRecordStat(MethodCompilationStat::kNotCompiledVerifyAtRuntime);
+ DCHECK(Runtime::Current()->IsAotCompiler());
+ const VerifiedMethod* verified_method = compiler_driver->GetVerifiedMethod(&dex_file, method_idx);
+ DCHECK(!verified_method->HasRuntimeThrow());
+ if (compiler_driver->IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file)
+ || CanHandleVerificationFailure(verified_method)) {
+ ArenaAllocator arena(Runtime::Current()->GetArenaPool());
+ CodeVectorAllocator code_allocator(&arena);
+ std::unique_ptr<CodeGenerator> codegen(
+ TryCompile(&arena,
+ &code_allocator,
+ code_item,
+ access_flags,
+ invoke_type,
+ class_def_idx,
+ method_idx,
+ jclass_loader,
+ dex_file,
+ dex_cache));
+ if (codegen.get() != nullptr) {
+ if (run_optimizations_) {
+ method = EmitOptimized(&arena, &code_allocator, codegen.get(), compiler_driver);
} else {
- MaybeRecordStat(MethodCompilationStat::kNotCompiledClassNotVerified);
+ method = EmitBaseline(&arena, &code_allocator, codegen.get(), compiler_driver);
}
}
} else {
- // This is for the JIT compiler, which has already ensured the class is verified.
- // We can go straight to compiling.
- DCHECK(Runtime::Current()->UseJit());
- method = TryCompile(code_item, access_flags, invoke_type, class_def_idx,
- method_idx, jclass_loader, dex_file, dex_cache);
+ if (compiler_driver->GetCompilerOptions().VerifyAtRuntime()) {
+ MaybeRecordStat(MethodCompilationStat::kNotCompiledVerifyAtRuntime);
+ } else {
+ MaybeRecordStat(MethodCompilationStat::kNotCompiledClassNotVerified);
+ }
}
if (kIsDebugBuild &&
@@ -896,4 +872,70 @@
return EndsWith(image, "core.art") || EndsWith(image, "core-optimizing.art");
}
+bool OptimizingCompiler::JitCompile(Thread* self,
+ jit::JitCodeCache* code_cache,
+ ArtMethod* method) {
+ StackHandleScope<2> hs(self);
+ Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
+ method->GetDeclaringClass()->GetClassLoader()));
+ Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache()));
+
+ jobject jclass_loader = class_loader.ToJObject();
+ const DexFile* dex_file = method->GetDexFile();
+ const uint16_t class_def_idx = method->GetClassDefIndex();
+ const DexFile::CodeItem* code_item = dex_file->GetCodeItem(method->GetCodeItemOffset());
+ const uint32_t method_idx = method->GetDexMethodIndex();
+ const uint32_t access_flags = method->GetAccessFlags();
+ const InvokeType invoke_type = method->GetInvokeType();
+
+ ArenaAllocator arena(Runtime::Current()->GetArenaPool());
+ CodeVectorAllocator code_allocator(&arena);
+ std::unique_ptr<CodeGenerator> codegen;
+ {
+ // Go to native so that we don't block GC during compilation.
+ ScopedThreadSuspension sts(self, kNative);
+
+ DCHECK(run_optimizations_);
+ codegen.reset(
+ TryCompile(&arena,
+ &code_allocator,
+ code_item,
+ access_flags,
+ invoke_type,
+ class_def_idx,
+ method_idx,
+ jclass_loader,
+ *dex_file,
+ dex_cache));
+ if (codegen.get() == nullptr) {
+ return false;
+ }
+ }
+
+ size_t stack_map_size = codegen->ComputeStackMapsSize();
+ uint8_t* stack_map_data = code_cache->ReserveData(self, stack_map_size);
+ if (stack_map_data == nullptr) {
+ return false;
+ }
+ codegen->BuildStackMaps(MemoryRegion(stack_map_data, stack_map_size));
+ const void* code = code_cache->CommitCode(
+ self,
+ method,
+ nullptr,
+ stack_map_data,
+ nullptr,
+ codegen->HasEmptyFrame() ? 0 : codegen->GetFrameSize(),
+ codegen->GetCoreSpillMask(),
+ codegen->GetFpuSpillMask(),
+ code_allocator.GetMemory().data(),
+ code_allocator.GetSize());
+
+ if (code == nullptr) {
+ code_cache->ClearData(self, stack_map_data);
+ return false;
+ }
+
+ return true;
+}
+
} // namespace art
diff --git a/compiler/optimizing/primitive_type_propagation.cc b/compiler/optimizing/primitive_type_propagation.cc
index c38f466..c98f43e 100644
--- a/compiler/optimizing/primitive_type_propagation.cc
+++ b/compiler/optimizing/primitive_type_propagation.cc
@@ -21,120 +21,60 @@
namespace art {
-bool PrimitiveTypePropagation::TypePhiFromInputs(HPhi* phi) {
- Primitive::Type common_type = phi->GetType();
-
- for (HInputIterator it(phi); !it.Done(); it.Advance()) {
- HInstruction* input = it.Current();
- if (input->IsPhi() && input->AsPhi()->IsDead()) {
- // Phis are constructed live so if an input is a dead phi, it must have
- // been made dead due to type conflict. Mark this phi conflicting too.
- return false;
- }
-
- Primitive::Type input_type = HPhi::ToPhiType(input->GetType());
- if (common_type == Primitive::kPrimVoid) {
- // Setting type for the first time.
- common_type = input_type;
- } else if (common_type == input_type) {
- // No change in type.
- } else if (input_type == Primitive::kPrimVoid) {
- // Input is a phi which has not been typed yet. Do nothing.
- DCHECK(input->IsPhi());
- } else if (Primitive::ComponentSize(common_type) != Primitive::ComponentSize(input_type)) {
- // Types are of different sizes, e.g. int vs. long. Must be a conflict.
- return false;
- } else if (Primitive::IsIntegralType(common_type)) {
- // Previous inputs were integral, this one is not but is of the same size.
- // This does not imply conflict since some bytecode instruction types are
- // ambiguous. TypeInputsOfPhi will either type them or detect a conflict.
- DCHECK(Primitive::IsFloatingPointType(input_type) || input_type == Primitive::kPrimNot);
- common_type = input_type;
- } else if (Primitive::IsIntegralType(input_type)) {
- // Input is integral, common type is not. Same as in the previous case, if
- // there is a conflict, it will be detected during TypeInputsOfPhi.
- DCHECK(Primitive::IsFloatingPointType(common_type) || common_type == Primitive::kPrimNot);
- } else {
- // Combining float and reference types. Clearly a conflict.
- DCHECK((common_type == Primitive::kPrimFloat && input_type == Primitive::kPrimNot) ||
- (common_type == Primitive::kPrimNot && input_type == Primitive::kPrimFloat));
- return false;
- }
+static Primitive::Type MergeTypes(Primitive::Type existing, Primitive::Type new_type) {
+ // We trust the verifier has already done the necessary checking.
+ switch (existing) {
+ case Primitive::kPrimFloat:
+ case Primitive::kPrimDouble:
+ case Primitive::kPrimNot:
+ return existing;
+ default:
+ // Phis are initialized with a void type, so if we are asked
+ // to merge with a void type, we should use the existing one.
+ return new_type == Primitive::kPrimVoid
+ ? existing
+ : HPhi::ToPhiType(new_type);
}
-
- // We have found a candidate type for the phi. Set it and return true. We may
- // still discover conflict whilst typing the individual inputs in TypeInputsOfPhi.
- phi->SetType(common_type);
- return true;
}
-bool PrimitiveTypePropagation::TypeInputsOfPhi(HPhi* phi) {
- Primitive::Type common_type = phi->GetType();
- if (common_type == Primitive::kPrimVoid || Primitive::IsIntegralType(common_type)) {
- // Phi either contains only other untyped phis (common_type == kPrimVoid),
- // or `common_type` is integral and we do not need to retype ambiguous inputs
- // because they are always constructed with the integral type candidate.
- if (kIsDebugBuild) {
- for (size_t i = 0, e = phi->InputCount(); i < e; ++i) {
- HInstruction* input = phi->InputAt(i);
- if (common_type == Primitive::kPrimVoid) {
- DCHECK(input->IsPhi() && input->GetType() == Primitive::kPrimVoid);
- } else {
- DCHECK((input->IsPhi() && input->GetType() == Primitive::kPrimVoid) ||
- HPhi::ToPhiType(input->GetType()) == common_type);
- }
- }
- }
- // Inputs did not need to be replaced, hence no conflict. Report success.
- return true;
- } else {
- DCHECK(common_type == Primitive::kPrimNot || Primitive::IsFloatingPointType(common_type));
+// Re-compute and update the type of the instruction. Returns
+// whether or not the type was changed.
+bool PrimitiveTypePropagation::UpdateType(HPhi* phi) {
+ DCHECK(phi->IsLive());
+ Primitive::Type existing = phi->GetType();
+
+ Primitive::Type new_type = existing;
+ for (size_t i = 0, e = phi->InputCount(); i < e; ++i) {
+ Primitive::Type input_type = phi->InputAt(i)->GetType();
+ new_type = MergeTypes(new_type, input_type);
+ }
+ phi->SetType(new_type);
+
+ if (new_type == Primitive::kPrimDouble
+ || new_type == Primitive::kPrimFloat
+ || new_type == Primitive::kPrimNot) {
+ // If the phi is of floating point type, we need to update its inputs to that
+ // type. For inputs that are phis, we need to recompute their types.
for (size_t i = 0, e = phi->InputCount(); i < e; ++i) {
HInstruction* input = phi->InputAt(i);
- if (input->GetType() != common_type) {
- // Input type does not match phi's type. Try to retype the input or
- // generate a suitably typed equivalent.
- HInstruction* equivalent = (common_type == Primitive::kPrimNot)
+ if (input->GetType() != new_type) {
+ HInstruction* equivalent = (new_type == Primitive::kPrimNot)
? SsaBuilder::GetReferenceTypeEquivalent(input)
- : SsaBuilder::GetFloatOrDoubleEquivalent(phi, input, common_type);
- if (equivalent == nullptr) {
- // Input could not be typed. Report conflict.
- return false;
- }
-
+ : SsaBuilder::GetFloatOrDoubleEquivalent(phi, input, new_type);
phi->ReplaceInput(equivalent, i);
if (equivalent->IsPhi()) {
+ equivalent->AsPhi()->SetLive();
AddToWorklist(equivalent->AsPhi());
} else if (equivalent == input) {
// The input has changed its type. It can be an input of other phis,
// so we need to put phi users in the work list.
- AddDependentInstructionsToWorklist(input);
+ AddDependentInstructionsToWorklist(equivalent);
}
}
}
- // All inputs either matched the type of the phi or we successfully replaced
- // them with a suitable equivalent. Report success.
- return true;
- }
-}
-
-bool PrimitiveTypePropagation::UpdateType(HPhi* phi) {
- DCHECK(phi->IsLive());
- Primitive::Type original_type = phi->GetType();
-
- // Try to type the phi in two stages:
- // (1) find a candidate type for the phi by merging types of all its inputs,
- // (2) try to type the phi's inputs to that candidate type.
- // Either of these stages may detect a type conflict and fail, in which case
- // we immediately abort.
- if (!TypePhiFromInputs(phi) || !TypeInputsOfPhi(phi)) {
- // Conflict detected. Mark the phi dead and return true because it changed.
- phi->SetDead();
- return true;
}
- // Return true if the type of the phi has changed.
- return phi->GetType() != original_type;
+ return existing != new_type;
}
void PrimitiveTypePropagation::Run() {
@@ -169,12 +109,10 @@
void PrimitiveTypePropagation::ProcessWorklist() {
while (!worklist_.empty()) {
- HPhi* phi = worklist_.back();
+ HPhi* instruction = worklist_.back();
worklist_.pop_back();
- // The phi could have been made dead as a result of conflicts while in the
- // worklist. If it is now dead, there is no point in updating its type.
- if (phi->IsLive() && UpdateType(phi)) {
- AddDependentInstructionsToWorklist(phi);
+ if (UpdateType(instruction)) {
+ AddDependentInstructionsToWorklist(instruction);
}
}
}
@@ -185,17 +123,10 @@
}
void PrimitiveTypePropagation::AddDependentInstructionsToWorklist(HInstruction* instruction) {
- // If `instruction` is a dead phi, type conflict was just identified. All its
- // live phi users, and transitively users of those users, therefore need to be
- // marked dead/conflicting too, so we add them to the worklist. Otherwise we
- // add users whose type does not match and needs to be updated.
- bool add_all_live_phis = instruction->IsPhi() && instruction->AsPhi()->IsDead();
for (HUseIterator<HInstruction*> it(instruction->GetUses()); !it.Done(); it.Advance()) {
- HInstruction* user = it.Current()->GetUser();
- if (user->IsPhi() && user->AsPhi()->IsLive()) {
- if (add_all_live_phis || user->GetType() != instruction->GetType()) {
- AddToWorklist(user->AsPhi());
- }
+ HPhi* phi = it.Current()->GetUser()->AsPhi();
+ if (phi != nullptr && phi->IsLive() && phi->GetType() != instruction->GetType()) {
+ AddToWorklist(phi);
}
}
}
diff --git a/compiler/optimizing/primitive_type_propagation.h b/compiler/optimizing/primitive_type_propagation.h
index 0b69483..212fcfc 100644
--- a/compiler/optimizing/primitive_type_propagation.h
+++ b/compiler/optimizing/primitive_type_propagation.h
@@ -38,8 +38,6 @@
void AddToWorklist(HPhi* phi);
void AddDependentInstructionsToWorklist(HInstruction* instruction);
bool UpdateType(HPhi* phi);
- bool TypePhiFromInputs(HPhi* phi);
- bool TypeInputsOfPhi(HPhi* phi);
HGraph* const graph_;
ArenaVector<HPhi*> worklist_;
diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc
index 20a8bdb..4565590 100644
--- a/compiler/optimizing/ssa_builder.cc
+++ b/compiler/optimizing/ssa_builder.cc
@@ -22,18 +22,204 @@
namespace art {
-void SsaBuilder::SetLoopPhiInputs() {
- for (HBasicBlock* block : loop_headers_) {
- for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
- HPhi* phi = it.Current()->AsPhi();
- for (HBasicBlock* predecessor : block->GetPredecessors()) {
- HInstruction* input = ValueOfLocal(predecessor, phi->GetRegNumber());
- phi->AddInput(input);
+/**
+ * A debuggable application may require to reviving phis, to ensure their
+ * associated DEX register is available to a debugger. This class implements
+ * the logic for statement (c) of the SsaBuilder (see ssa_builder.h). It
+ * also makes sure that phis with incompatible input types are not revived
+ * (statement (b) of the SsaBuilder).
+ *
+ * This phase must be run after detecting dead phis through the
+ * DeadPhiElimination phase, and before deleting the dead phis.
+ */
+class DeadPhiHandling : public ValueObject {
+ public:
+ explicit DeadPhiHandling(HGraph* graph)
+ : graph_(graph), worklist_(graph->GetArena()->Adapter(kArenaAllocSsaBuilder)) {
+ worklist_.reserve(kDefaultWorklistSize);
+ }
+
+ void Run();
+
+ private:
+ void VisitBasicBlock(HBasicBlock* block);
+ void ProcessWorklist();
+ void AddToWorklist(HPhi* phi);
+ void AddDependentInstructionsToWorklist(HPhi* phi);
+ bool UpdateType(HPhi* phi);
+
+ HGraph* const graph_;
+ ArenaVector<HPhi*> worklist_;
+
+ static constexpr size_t kDefaultWorklistSize = 8;
+
+ DISALLOW_COPY_AND_ASSIGN(DeadPhiHandling);
+};
+
+static bool HasConflictingEquivalent(HPhi* phi) {
+ if (phi->GetNext() == nullptr) {
+ return false;
+ }
+ HPhi* next = phi->GetNext()->AsPhi();
+ if (next->GetRegNumber() == phi->GetRegNumber()) {
+ if (next->GetType() == Primitive::kPrimVoid) {
+ // We only get a void type for an equivalent phi we processed and found out
+ // it was conflicting.
+ return true;
+ } else {
+ // Go to the next phi, in case it is also an equivalent.
+ return HasConflictingEquivalent(next);
+ }
+ }
+ return false;
+}
+
+bool DeadPhiHandling::UpdateType(HPhi* phi) {
+ if (phi->IsDead()) {
+ // Phi was rendered dead while waiting in the worklist because it was replaced
+ // with an equivalent.
+ return false;
+ }
+
+ Primitive::Type existing = phi->GetType();
+
+ bool conflict = false;
+ Primitive::Type new_type = existing;
+ for (size_t i = 0, e = phi->InputCount(); i < e; ++i) {
+ HInstruction* input = phi->InputAt(i);
+ if (input->IsPhi() && input->AsPhi()->IsDead()) {
+ // We are doing a reverse post order visit of the graph, reviving
+ // phis that have environment uses and updating their types. If an
+ // input is a phi, and it is dead (because its input types are
+ // conflicting), this phi must be marked dead as well.
+ conflict = true;
+ break;
+ }
+ Primitive::Type input_type = HPhi::ToPhiType(input->GetType());
+
+ // The only acceptable transitions are:
+ // - From void to typed: first time we update the type of this phi.
+ // - From int to reference (or reference to int): the phi has to change
+ // to reference type. If the integer input cannot be converted to a
+ // reference input, the phi will remain dead.
+ if (new_type == Primitive::kPrimVoid) {
+ new_type = input_type;
+ } else if (new_type == Primitive::kPrimNot && input_type == Primitive::kPrimInt) {
+ if (input->IsPhi() && HasConflictingEquivalent(input->AsPhi())) {
+ // If we already asked for an equivalent of the input phi, but that equivalent
+ // ended up conflicting, make this phi conflicting too.
+ conflict = true;
+ break;
+ }
+ HInstruction* equivalent = SsaBuilder::GetReferenceTypeEquivalent(input);
+ if (equivalent == nullptr) {
+ conflict = true;
+ break;
+ }
+ phi->ReplaceInput(equivalent, i);
+ if (equivalent->IsPhi()) {
+ DCHECK_EQ(equivalent->GetType(), Primitive::kPrimNot);
+ // We created a new phi, but that phi has the same inputs as the old phi. We
+ // add it to the worklist to ensure its inputs can also be converted to reference.
+ // If not, it will remain dead, and the algorithm will make the current phi dead
+ // as well.
+ equivalent->AsPhi()->SetLive();
+ AddToWorklist(equivalent->AsPhi());
+ }
+ } else if (new_type == Primitive::kPrimInt && input_type == Primitive::kPrimNot) {
+ new_type = Primitive::kPrimNot;
+ // Start over, we may request reference equivalents for the inputs of the phi.
+ i = -1;
+ } else if (new_type != input_type) {
+ conflict = true;
+ break;
+ }
+ }
+
+ if (conflict) {
+ phi->SetType(Primitive::kPrimVoid);
+ phi->SetDead();
+ return true;
+ } else if (existing == new_type) {
+ return false;
+ }
+
+ DCHECK(phi->IsLive());
+ phi->SetType(new_type);
+
+ // There might exist a `new_type` equivalent of `phi` already. In that case,
+ // we replace the equivalent with the, now live, `phi`.
+ HPhi* equivalent = phi->GetNextEquivalentPhiWithSameType();
+ if (equivalent != nullptr) {
+ // There cannot be more than two equivalents with the same type.
+ DCHECK(equivalent->GetNextEquivalentPhiWithSameType() == nullptr);
+ // If doing fix-point iteration, the equivalent might be in `worklist_`.
+ // Setting it dead will make UpdateType skip it.
+ equivalent->SetDead();
+ equivalent->ReplaceWith(phi);
+ }
+
+ return true;
+}
+
+void DeadPhiHandling::VisitBasicBlock(HBasicBlock* block) {
+ for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
+ HPhi* phi = it.Current()->AsPhi();
+ if (phi->IsDead() && phi->HasEnvironmentUses()) {
+ phi->SetLive();
+ if (block->IsLoopHeader()) {
+ // Give a type to the loop phi to guarantee convergence of the algorithm.
+ // Note that the dead phi may already have a type if it is an equivalent
+ // generated for a typed LoadLocal. In that case we do not change the
+ // type because it could lead to an unsupported PrimNot/Float/Double ->
+ // PrimInt/Long transition and create same type equivalents.
+ if (phi->GetType() == Primitive::kPrimVoid) {
+ phi->SetType(phi->InputAt(0)->GetType());
+ }
+ AddToWorklist(phi);
+ } else {
+ // Because we are doing a reverse post order visit, all inputs of
+ // this phi have been visited and therefore had their (initial) type set.
+ UpdateType(phi);
}
}
}
}
+void DeadPhiHandling::ProcessWorklist() {
+ while (!worklist_.empty()) {
+ HPhi* instruction = worklist_.back();
+ worklist_.pop_back();
+ // Note that the same equivalent phi can be added multiple times in the work list, if
+ // used by multiple phis. The first call to `UpdateType` will know whether the phi is
+ // dead or live.
+ if (instruction->IsLive() && UpdateType(instruction)) {
+ AddDependentInstructionsToWorklist(instruction);
+ }
+ }
+}
+
+void DeadPhiHandling::AddToWorklist(HPhi* instruction) {
+ DCHECK(instruction->IsLive());
+ worklist_.push_back(instruction);
+}
+
+void DeadPhiHandling::AddDependentInstructionsToWorklist(HPhi* instruction) {
+ for (HUseIterator<HInstruction*> it(instruction->GetUses()); !it.Done(); it.Advance()) {
+ HPhi* phi = it.Current()->GetUser()->AsPhi();
+ if (phi != nullptr && !phi->IsDead()) {
+ AddToWorklist(phi);
+ }
+ }
+}
+
+void DeadPhiHandling::Run() {
+ for (HReversePostOrderIterator it(*graph_); !it.Done(); it.Advance()) {
+ VisitBasicBlock(it.Current());
+ }
+ ProcessWorklist();
+}
+
void SsaBuilder::FixNullConstantType() {
// The order doesn't matter here.
for (HReversePostOrderIterator itb(*GetGraph()); !itb.Done(); itb.Advance()) {
@@ -73,11 +259,10 @@
HPhi* phi = it.Current()->AsPhi();
HPhi* next = phi->GetNextEquivalentPhiWithSameType();
if (next != nullptr) {
- // Make sure we do not replace a live phi with a dead phi. A live phi
- // has been handled by the type propagation phase, unlike a dead phi.
+ // Make sure we do not replace a live phi with a dead phi. A live phi has been
+ // handled by the type propagation phase, unlike a dead phi.
if (next->IsLive()) {
phi->ReplaceWith(next);
- phi->SetDead();
} else {
next->ReplaceWith(phi);
}
@@ -89,7 +274,72 @@
}
}
-void SsaBuilder::FixEnvironmentPhis() {
+void SsaBuilder::BuildSsa() {
+ // 1) Visit in reverse post order. We need to have all predecessors of a block visited
+ // (with the exception of loops) in order to create the right environment for that
+ // block. For loops, we create phis whose inputs will be set in 2).
+ for (HReversePostOrderIterator it(*GetGraph()); !it.Done(); it.Advance()) {
+ VisitBasicBlock(it.Current());
+ }
+
+ // 2) Set inputs of loop phis.
+ for (HBasicBlock* block : loop_headers_) {
+ for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
+ HPhi* phi = it.Current()->AsPhi();
+ for (HBasicBlock* predecessor : block->GetPredecessors()) {
+ HInstruction* input = ValueOfLocal(predecessor, phi->GetRegNumber());
+ phi->AddInput(input);
+ }
+ }
+ }
+
+ // 3) Mark dead phis. This will mark phis that are only used by environments:
+ // at the DEX level, the type of these phis does not need to be consistent, but
+ // our code generator will complain if the inputs of a phi do not have the same
+ // type. The marking allows the type propagation to know which phis it needs
+ // to handle. We mark but do not eliminate: the elimination will be done in
+ // step 9).
+ SsaDeadPhiElimination dead_phis_for_type_propagation(GetGraph());
+ dead_phis_for_type_propagation.MarkDeadPhis();
+
+ // 4) Propagate types of phis. At this point, phis are typed void in the general
+ // case, or float/double/reference when we created an equivalent phi. So we
+ // need to propagate the types across phis to give them a correct type.
+ PrimitiveTypePropagation type_propagation(GetGraph());
+ type_propagation.Run();
+
+ // 5) When creating equivalent phis we copy the inputs of the original phi which
+ // may be improperly typed. This was fixed during the type propagation in 4) but
+ // as a result we may end up with two equivalent phis with the same type for
+ // the same dex register. This pass cleans them up.
+ EquivalentPhisCleanup();
+
+ // 6) Mark dead phis again. Step 4) may have introduced new phis.
+ // Step 5) might enable the death of new phis.
+ SsaDeadPhiElimination dead_phis(GetGraph());
+ dead_phis.MarkDeadPhis();
+
+ // 7) Now that the graph is correctly typed, we can get rid of redundant phis.
+ // Note that we cannot do this phase before type propagation, otherwise
+ // we could get rid of phi equivalents, whose presence is a requirement for the
+ // type propagation phase. Note that this is to satisfy statement (a) of the
+ // SsaBuilder (see ssa_builder.h).
+ SsaRedundantPhiElimination redundant_phi(GetGraph());
+ redundant_phi.Run();
+
+ // 8) Fix the type for null constants which are part of an equality comparison.
+ // We need to do this after redundant phi elimination, to ensure the only cases
+ // that we can see are reference comparison against 0. The redundant phi
+ // elimination ensures we do not see a phi taking two 0 constants in a HEqual
+ // or HNotEqual.
+ FixNullConstantType();
+
+ // 9) Make sure environments use the right phi "equivalent": a phi marked dead
+ // can have a phi equivalent that is not dead. We must therefore update
+ // all environment uses of the dead phi to use its equivalent. Note that there
+ // can be multiple phis for the same Dex register that are live (for example
+ // when merging constants), in which case it is OK for the environments
+ // to just reference one.
for (HReversePostOrderIterator it(*GetGraph()); !it.Done(); it.Advance()) {
HBasicBlock* block = it.Current();
for (HInstructionIterator it_phis(block->GetPhis()); !it_phis.Done(); it_phis.Advance()) {
@@ -110,68 +360,24 @@
phi->ReplaceWith(next);
}
}
-}
-void SsaBuilder::BuildSsa() {
- // 1) Visit in reverse post order. We need to have all predecessors of a block visited
- // (with the exception of loops) in order to create the right environment for that
- // block. For loops, we create phis whose inputs will be set in 2).
- for (HReversePostOrderIterator it(*GetGraph()); !it.Done(); it.Advance()) {
- VisitBasicBlock(it.Current());
+ // 10) Deal with phis to guarantee liveness of phis in case of a debuggable
+ // application. This is for satisfying statement (c) of the SsaBuilder
+ // (see ssa_builder.h).
+ if (GetGraph()->IsDebuggable()) {
+ DeadPhiHandling dead_phi_handler(GetGraph());
+ dead_phi_handler.Run();
}
- // 2) Set inputs of loop phis.
- SetLoopPhiInputs();
-
- // 3) Propagate types of phis. At this point, phis are typed void in the general
- // case, or float/double/reference if we created an equivalent phi. So we need
- // to propagate the types across phis to give them a correct type. If a type
- // conflict is detected in this stage, the phi is marked dead.
- PrimitiveTypePropagation(GetGraph()).Run();
-
- // 4) When creating equivalent phis we copy the inputs of the original phi which
- // may be improperly typed. This was fixed during the type propagation in 4) but
- // as a result we may end up with two equivalent phis with the same type for
- // the same dex register. This pass cleans them up.
- EquivalentPhisCleanup();
-
- // 5) Mark dead phis. This will mark phis which are not used by instructions or
- // other live phis. If compiling as debuggable code, phis will also be kept live
- // if they have an environment use.
- SsaDeadPhiElimination dead_phis(GetGraph());
- dead_phis.MarkDeadPhis();
-
- // 6) Make sure environments use the right phi equivalent: a phi marked dead
- // can have a phi equivalent that is not dead. In that case we have to replace
- // it with the live equivalent because deoptimization and try/catch rely on
- // environments containing values of all live vregs at that point. Note that
- // there can be multiple phis for the same Dex register that are live
- // (for example when merging constants), in which case it is okay for the
- // environments to just reference one.
- FixEnvironmentPhis();
-
- // 7) Now that the right phis are used for the environments, we can eliminate
- // phis we do not need. Regardless of the debuggable status, this phase is
- /// necessary for statement (b) of the SsaBuilder (see ssa_builder.h), as well
- // as for the code generation, which does not deal with phis of conflicting
+ // 11) Now that the right phis are used for the environments, and we
+ // have potentially revive dead phis in case of a debuggable application,
+ // we can eliminate phis we do not need. Regardless of the debuggable status,
+ // this phase is necessary for statement (b) of the SsaBuilder (see ssa_builder.h),
+ // as well as for the code generation, which does not deal with phis of conflicting
// input types.
dead_phis.EliminateDeadPhis();
- // 8) Now that the graph is correctly typed, we can get rid of redundant phis.
- // Note that we cannot do this phase before type propagation, otherwise
- // we could get rid of phi equivalents, whose presence is a requirement for the
- // type propagation phase. Note that this is to satisfy statement (a) of the
- // SsaBuilder (see ssa_builder.h).
- SsaRedundantPhiElimination(GetGraph()).Run();
-
- // 9) Fix the type for null constants which are part of an equality comparison.
- // We need to do this after redundant phi elimination, to ensure the only cases
- // that we can see are reference comparison against 0. The redundant phi
- // elimination ensures we do not see a phi taking two 0 constants in a HEqual
- // or HNotEqual.
- FixNullConstantType();
-
- // 10) Clear locals.
+ // 12) Clear locals.
for (HInstructionIterator it(GetGraph()->GetEntryBlock()->GetInstructions());
!it.Done();
it.Advance()) {
@@ -355,8 +561,6 @@
* phi with a floating point / reference type.
*/
HPhi* SsaBuilder::GetFloatDoubleOrReferenceEquivalentOfPhi(HPhi* phi, Primitive::Type type) {
- DCHECK(phi->IsLive()) << "Cannot get equivalent of a dead phi since it would create a live one.";
-
// We place the floating point /reference phi next to this phi.
HInstruction* next = phi->GetNext();
if (next != nullptr
@@ -372,18 +576,15 @@
ArenaAllocator* allocator = phi->GetBlock()->GetGraph()->GetArena();
HPhi* new_phi = new (allocator) HPhi(allocator, phi->GetRegNumber(), phi->InputCount(), type);
for (size_t i = 0, e = phi->InputCount(); i < e; ++i) {
- // Copy the inputs. Note that the graph may not be correctly typed
- // by doing this copy, but the type propagation phase will fix it.
+ // Copy the inputs. Note that the graph may not be correctly typed by doing this copy,
+ // but the type propagation phase will fix it.
new_phi->SetRawInputAt(i, phi->InputAt(i));
}
phi->GetBlock()->InsertPhiAfter(new_phi, phi);
- DCHECK(new_phi->IsLive());
return new_phi;
} else {
DCHECK_EQ(next->GetType(), type);
- // An existing equivalent was found. If it is dead, conflict was previously
- // identified and we return nullptr instead.
- return next->AsPhi()->IsLive() ? next->AsPhi() : nullptr;
+ return next->AsPhi();
}
}
@@ -391,15 +592,11 @@
HInstruction* value,
Primitive::Type type) {
if (value->IsArrayGet()) {
- HArrayGet* aget = value->AsArrayGet();
- if (aget->GetType() != type && aget->IsTypeFixed()) {
- // Requested a float/double equivalent of ArrayGet with int/long uses.
- // Must be a phi with type conflict.
- DCHECK(user->IsPhi());
- return nullptr;
- }
- aget->SetType(type);
- return aget;
+ // The verifier has checked that values in arrays cannot be used for both
+ // floating point and non-floating point operations. It is therefore safe to just
+ // change the type of the operation.
+ value->AsArrayGet()->SetType(type);
+ return value;
} else if (value->IsLongConstant()) {
return GetDoubleEquivalent(value->AsLongConstant());
} else if (value->IsIntConstant()) {
@@ -407,7 +604,12 @@
} else if (value->IsPhi()) {
return GetFloatDoubleOrReferenceEquivalentOfPhi(value->AsPhi(), type);
} else {
- return nullptr;
+ // For other instructions, we assume the verifier has checked that the dex format is correctly
+ // typed and the value in a dex register will not be used for both floating point and
+ // non-floating point operations. So the only reason an instruction would want a floating
+ // point equivalent is for an unused phi that will be removed by the dead phi elimination phase.
+ DCHECK(user->IsPhi()) << "is actually " << user->DebugName() << " (" << user->GetId() << ")";
+ return value;
}
}
@@ -431,21 +633,6 @@
value = GetReferenceTypeEquivalent(value);
}
}
-
- // If value is HArrayGet, check if uses of the HLoadLocal disambiguate its
- // type between int/long and float/double.
- if (value->IsArrayGet() && !value->AsArrayGet()->IsTypeFixed()) {
- for (HUseIterator<HInstruction*> use_it(load->GetUses()); !use_it.Done(); use_it.Advance()) {
- HInstruction* user = use_it.Current()->GetUser();
- if (!user->IsStoreLocal() &&
- !user->IsPhi() &&
- (!user->IsArraySet() || user->AsArraySet()->GetIndex() == value)) {
- value->AsArrayGet()->FixType();
- break;
- }
- }
- }
-
load->ReplaceWith(value);
load->GetBlock()->RemoveInstruction(load);
}
diff --git a/compiler/optimizing/ssa_builder.h b/compiler/optimizing/ssa_builder.h
index da0583c..79f1a28 100644
--- a/compiler/optimizing/ssa_builder.h
+++ b/compiler/optimizing/ssa_builder.h
@@ -81,8 +81,6 @@
static constexpr const char* kSsaBuilderPassName = "ssa_builder";
private:
- void SetLoopPhiInputs();
- void FixEnvironmentPhis();
void FixNullConstantType();
void EquivalentPhisCleanup();
diff --git a/compiler/optimizing/ssa_phi_elimination.cc b/compiler/optimizing/ssa_phi_elimination.cc
index c6993f3..72f9ddd 100644
--- a/compiler/optimizing/ssa_phi_elimination.cc
+++ b/compiler/optimizing/ssa_phi_elimination.cc
@@ -29,28 +29,17 @@
HBasicBlock* block = it.Current();
for (HInstructionIterator inst_it(block->GetPhis()); !inst_it.Done(); inst_it.Advance()) {
HPhi* phi = inst_it.Current()->AsPhi();
- if (phi->IsDead()) {
- // Phis are constructed live so this one was proven conflicting.
- continue;
- }
-
- bool is_live = false;
- if (graph_->IsDebuggable() && phi->HasEnvironmentUses()) {
- is_live = true;
- } else {
- for (HUseIterator<HInstruction*> use_it(phi->GetUses()); !use_it.Done(); use_it.Advance()) {
- if (!use_it.Current()->GetUser()->IsPhi()) {
- is_live = true;
- break;
- }
+ // Set dead ahead of running through uses. The phi may have no use.
+ phi->SetDead();
+ for (HUseIterator<HInstruction*> use_it(phi->GetUses()); !use_it.Done(); use_it.Advance()) {
+ HUseListNode<HInstruction*>* current = use_it.Current();
+ HInstruction* user = current->GetUser();
+ if (!user->IsPhi()) {
+ worklist_.push_back(phi);
+ phi->SetLive();
+ break;
}
}
-
- if (is_live) {
- worklist_.push_back(phi);
- } else {
- phi->SetDead();
- }
}
}
@@ -61,10 +50,8 @@
for (HInputIterator it(phi); !it.Done(); it.Advance()) {
HInstruction* input = it.Current();
if (input->IsPhi() && input->AsPhi()->IsDead()) {
- // If we revive a phi it must have been live at the beginning of
- // the pass but had no non-phi uses of its own.
- input->AsPhi()->SetLive();
worklist_.push_back(input->AsPhi());
+ input->AsPhi()->SetLive();
}
}
}
@@ -88,8 +75,8 @@
for (HUseIterator<HInstruction*> use_it(phi->GetUses()); !use_it.Done();
use_it.Advance()) {
HInstruction* user = use_it.Current()->GetUser();
- DCHECK(user->IsPhi());
- DCHECK(user->AsPhi()->IsDead());
+ DCHECK(user->IsLoopHeaderPhi()) << user->GetId();
+ DCHECK(user->AsPhi()->IsDead()) << user->GetId();
}
}
// Remove the phi from use lists of its inputs.
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 2653807..92ed58c 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1355,9 +1355,20 @@
uint32_t image_file_location_oat_checksum = 0;
uintptr_t image_file_location_oat_data_begin = 0;
int32_t image_patch_delta = 0;
+
+ if (app_image_ && image_base_ == 0) {
+ gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace();
+ image_base_ = RoundUp(
+ reinterpret_cast<uintptr_t>(image_space->GetImageHeader().GetOatFileEnd()),
+ kPageSize);
+ VLOG(compiler) << "App image base=" << reinterpret_cast<void*>(image_base_);
+ }
+
if (IsImage()) {
PrepareImageWriter(image_base_);
- } else {
+ }
+
+ if (!IsBootImage()) {
TimingLogger::ScopedTiming t3("Loading image checksum", timings_);
gc::space::ImageSpace* image_space = Runtime::Current()->GetHeap()->GetImageSpace();
image_file_location_oat_checksum = image_space->GetImageHeader().GetOatChecksum();
@@ -1371,11 +1382,13 @@
key_value_store_->Put(OatHeader::kImageLocationKey, image_file_location);
}
- oat_writer.reset(new OatWriter(dex_files_, image_file_location_oat_checksum,
+ oat_writer.reset(new OatWriter(dex_files_,
+ image_file_location_oat_checksum,
image_file_location_oat_data_begin,
image_patch_delta,
driver_.get(),
image_writer_.get(),
+ IsBootImage(),
timings_,
key_value_store_.get()));
}
@@ -1591,7 +1604,11 @@
}
void PrepareImageWriter(uintptr_t image_base) {
- image_writer_.reset(new ImageWriter(*driver_, image_base, compiler_options_->GetCompilePic()));
+ DCHECK(IsImage());
+ image_writer_.reset(new ImageWriter(*driver_,
+ image_base,
+ compiler_options_->GetCompilePic(),
+ IsAppImage()));
}
// Let the ImageWriter write the image file. If we do not compile PIC, also fix up the oat file.
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index df3613a..514d3cc 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -104,12 +104,13 @@
va_end(args);
}
-bool ClassLinker::HasInitWithString(Thread* self, const char* descriptor) {
+static bool HasInitWithString(Thread* self, ClassLinker* class_linker, const char* descriptor)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
ArtMethod* method = self->GetCurrentMethod(nullptr);
StackHandleScope<1> hs(self);
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(method != nullptr ?
method->GetDeclaringClass()->GetClassLoader() : nullptr));
- mirror::Class* exception_class = FindClass(self, descriptor, class_loader);
+ mirror::Class* exception_class = class_linker->FindClass(self, descriptor, class_loader);
if (exception_class == nullptr) {
// No exc class ~ no <init>-with-string.
@@ -119,10 +120,39 @@
}
ArtMethod* exception_init_method = exception_class->FindDeclaredDirectMethod(
- "<init>", "(Ljava/lang/String;)V", image_pointer_size_);
+ "<init>", "(Ljava/lang/String;)V", class_linker->GetImagePointerSize());
return exception_init_method != nullptr;
}
+// Helper for ThrowEarlierClassFailure. Throws the stored error.
+static void HandleEarlierVerifyError(Thread* self, ClassLinker* class_linker, mirror::Class* c)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ mirror::Object* obj = c->GetVerifyError();
+ DCHECK(obj != nullptr);
+ self->AssertNoPendingException();
+ if (obj->IsClass()) {
+ // Previous error has been stored as class. Create a new exception of that type.
+
+ // It's possible the exception doesn't have a <init>(String).
+ std::string temp;
+ const char* descriptor = obj->AsClass()->GetDescriptor(&temp);
+
+ if (HasInitWithString(self, class_linker, descriptor)) {
+ self->ThrowNewException(descriptor, PrettyDescriptor(c).c_str());
+ } else {
+ self->ThrowNewException(descriptor, nullptr);
+ }
+ } else {
+ // Previous error has been stored as an instance. Just rethrow.
+ mirror::Class* throwable_class =
+ self->DecodeJObject(WellKnownClasses::java_lang_Throwable)->AsClass();
+ mirror::Class* error_class = obj->GetClass();
+ CHECK(throwable_class->IsAssignableFrom(error_class));
+ self->SetException(obj->AsThrowable());
+ }
+ self->AssertPendingException();
+}
+
void ClassLinker::ThrowEarlierClassFailure(mirror::Class* c) {
// The class failed to initialize on a previous attempt, so we want to throw
// a NoClassDefFoundError (v2 2.17.5). The exception to this rule is if we
@@ -131,8 +161,11 @@
Runtime* const runtime = Runtime::Current();
if (!runtime->IsAotCompiler()) { // Give info if this occurs at runtime.
std::string extra;
- if (c->GetVerifyErrorClass() != nullptr) {
- extra = PrettyDescriptor(c->GetVerifyErrorClass());
+ if (c->GetVerifyError() != nullptr) {
+ mirror::Class* descr_from = c->GetVerifyError()->IsClass()
+ ? c->GetVerifyError()->AsClass()
+ : c->GetVerifyError()->GetClass();
+ extra = PrettyDescriptor(descr_from);
}
LOG(INFO) << "Rejecting re-init on previously-failed class " << PrettyClass(c) << ": " << extra;
}
@@ -144,17 +177,8 @@
mirror::Throwable* pre_allocated = runtime->GetPreAllocatedNoClassDefFoundError();
self->SetException(pre_allocated);
} else {
- if (c->GetVerifyErrorClass() != nullptr) {
- // TODO: change the verifier to store an _instance_, with a useful detail message?
- // It's possible the exception doesn't have a <init>(String).
- std::string temp;
- const char* descriptor = c->GetVerifyErrorClass()->GetDescriptor(&temp);
-
- if (HasInitWithString(self, descriptor)) {
- self->ThrowNewException(descriptor, PrettyDescriptor(c).c_str());
- } else {
- self->ThrowNewException(descriptor, nullptr);
- }
+ if (c->GetVerifyError() != nullptr) {
+ HandleEarlierVerifyError(self, this, c);
} else {
self->ThrowNewException("Ljava/lang/NoClassDefFoundError;",
PrettyDescriptor(c).c_str());
@@ -847,8 +871,8 @@
hs.NewHandle(dex_caches_object->AsObjectArray<mirror::DexCache>()));
Handle<mirror::ObjectArray<mirror::Class>> class_roots(hs.NewHandle(
- space->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)->
- AsObjectArray<mirror::Class>()));
+ space->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)->
+ AsObjectArray<mirror::Class>()));
class_roots_ = GcRoot<mirror::ObjectArray<mirror::Class>>(class_roots.Get());
// Special case of setting up the String class early so that we can test arbitrary objects
@@ -857,7 +881,7 @@
mirror::Class* java_lang_Object = GetClassRoot(kJavaLangObject);
java_lang_Object->SetObjectSize(sizeof(mirror::Object));
- Runtime::Current()->SetSentinel(Runtime::Current()->GetHeap()->AllocObject<true>(self,
+ Runtime::Current()->SetSentinel(heap->AllocObject<true>(self,
java_lang_Object,
java_lang_Object->GetObjectSize(),
VoidFunctor()));
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 392efd2..73f9d4b 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -854,9 +854,6 @@
SHARED_REQUIRES(Locks::mutator_lock_)
REQUIRES(!dex_lock_);
- bool HasInitWithString(Thread* self, const char* descriptor)
- SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!dex_lock_);
-
bool CanWeInitializeClass(mirror::Class* klass, bool can_init_statics, bool can_init_parents)
SHARED_REQUIRES(Locks::mutator_lock_);
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 04b8900..2c086c5 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -515,7 +515,7 @@
addOffset(OFFSETOF_MEMBER(mirror::Class, sfields_), "sFields");
addOffset(OFFSETOF_MEMBER(mirror::Class, status_), "status");
addOffset(OFFSETOF_MEMBER(mirror::Class, super_class_), "superClass");
- addOffset(OFFSETOF_MEMBER(mirror::Class, verify_error_class_), "verifyErrorClass");
+ addOffset(OFFSETOF_MEMBER(mirror::Class, verify_error_), "verifyError");
addOffset(OFFSETOF_MEMBER(mirror::Class, virtual_methods_), "virtualMethods");
addOffset(OFFSETOF_MEMBER(mirror::Class, vtable_), "vtable");
};
diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc
index e57569e..87e29ae 100644
--- a/runtime/entrypoints/entrypoint_utils.cc
+++ b/runtime/entrypoints/entrypoint_utils.cc
@@ -364,36 +364,34 @@
(reinterpret_cast<uint8_t*>(sp) + callee_return_pc_offset));
ArtMethod* outer_method = *caller_sp;
ArtMethod* caller = outer_method;
-
- if (outer_method != nullptr) {
- const OatQuickMethodHeader* current_code = outer_method->GetOatQuickMethodHeader(caller_pc);
- if (current_code->IsOptimized()) {
- if (LIKELY(caller_pc != reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()))) {
- uintptr_t native_pc_offset = current_code->NativeQuickPcOffset(caller_pc);
- CodeInfo code_info = current_code->GetOptimizedCodeInfo();
- StackMapEncoding encoding = code_info.ExtractEncoding();
- StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset, encoding);
- DCHECK(stack_map.IsValid());
- if (stack_map.HasInlineInfo(encoding)) {
- InlineInfo inline_info = code_info.GetInlineInfoOf(stack_map, encoding);
- caller = GetResolvedMethod(outer_method, inline_info, inline_info.GetDepth() - 1);
- }
- } else {
- // We're instrumenting, just use the StackVisitor which knows how to
- // handle instrumented frames.
- NthCallerVisitor visitor(Thread::Current(), 1, true);
- visitor.WalkStack();
- caller = visitor.caller;
+ if (LIKELY(caller_pc != reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()))) {
+ if (outer_method != nullptr) {
+ const OatQuickMethodHeader* current_code = outer_method->GetOatQuickMethodHeader(caller_pc);
+ if (current_code->IsOptimized()) {
+ uintptr_t native_pc_offset = current_code->NativeQuickPcOffset(caller_pc);
+ CodeInfo code_info = current_code->GetOptimizedCodeInfo();
+ StackMapEncoding encoding = code_info.ExtractEncoding();
+ StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset, encoding);
+ DCHECK(stack_map.IsValid());
+ if (stack_map.HasInlineInfo(encoding)) {
+ InlineInfo inline_info = code_info.GetInlineInfoOf(stack_map, encoding);
+ caller = GetResolvedMethod(outer_method, inline_info, inline_info.GetDepth() - 1);
+ }
}
}
- }
-
- if (kIsDebugBuild && do_caller_check) {
- // Note that do_caller_check is optional, as this method can be called by
- // stubs, and tests without a proper call stack.
+ if (kIsDebugBuild && do_caller_check) {
+ // Note that do_caller_check is optional, as this method can be called by
+ // stubs, and tests without a proper call stack.
+ NthCallerVisitor visitor(Thread::Current(), 1, true);
+ visitor.WalkStack();
+ CHECK_EQ(caller, visitor.caller);
+ }
+ } else {
+ // We're instrumenting, just use the StackVisitor which knows how to
+ // handle instrumented frames.
NthCallerVisitor visitor(Thread::Current(), 1, true);
visitor.WalkStack();
- CHECK_EQ(caller, visitor.caller);
+ caller = visitor.caller;
}
return caller;
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index 5afd28e..f691151 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -26,7 +26,6 @@
#include "jit_instrumentation.h"
#include "runtime.h"
#include "runtime_options.h"
-#include "thread_list.h"
#include "utils.h"
namespace art {
@@ -145,7 +144,7 @@
void Jit::DeleteThreadPool() {
if (instrumentation_cache_.get() != nullptr) {
- instrumentation_cache_->DeleteThreadPool();
+ instrumentation_cache_->DeleteThreadPool(Thread::Current());
}
}
@@ -164,16 +163,8 @@
void Jit::CreateInstrumentationCache(size_t compile_threshold, size_t warmup_threshold) {
CHECK_GT(compile_threshold, 0U);
- ScopedSuspendAll ssa(__FUNCTION__);
- // Add Jit interpreter instrumentation, tells the interpreter when to notify the jit to compile
- // something.
instrumentation_cache_.reset(
new jit::JitInstrumentationCache(compile_threshold, warmup_threshold));
- Runtime::Current()->GetInstrumentation()->AddListener(
- new jit::JitInstrumentationListener(instrumentation_cache_.get()),
- instrumentation::Instrumentation::kMethodEntered |
- instrumentation::Instrumentation::kBackwardBranch |
- instrumentation::Instrumentation::kInvokeVirtualOrInterface);
}
} // namespace jit
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 4c7cb1e..fbcba1b 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -248,40 +248,49 @@
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*>(
- mspace_memalign(code_mspace_, alignment, total_size));
- if (result == nullptr) {
- return nullptr;
+ ScopedThreadSuspension sts(self, kSuspended);
+ MutexLock mu(self, lock_);
+ WaitForPotentialCollectionToComplete(self);
+ {
+ ScopedCodeCacheWrite scc(code_map_.get());
+ uint8_t* result = reinterpret_cast<uint8_t*>(
+ mspace_memalign(code_mspace_, alignment, total_size));
+ if (result == nullptr) {
+ return nullptr;
+ }
+ code_ptr = result + header_size;
+ DCHECK_ALIGNED_PARAM(reinterpret_cast<uintptr_t>(code_ptr), alignment);
+
+ std::copy(code, code + code_size, code_ptr);
+ 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,
+ (gc_map == nullptr) ? 0 : code_ptr - gc_map,
+ frame_size_in_bytes,
+ core_spill_mask,
+ fp_spill_mask,
+ code_size);
}
- code_ptr = result + header_size;
- DCHECK_ALIGNED_PARAM(reinterpret_cast<uintptr_t>(code_ptr), alignment);
- std::copy(code, code + code_size, code_ptr);
- 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,
- (gc_map == nullptr) ? 0 : code_ptr - gc_map,
- frame_size_in_bytes,
- core_spill_mask,
- fp_spill_mask,
- code_size);
+ __builtin___clear_cache(reinterpret_cast<char*>(code_ptr),
+ reinterpret_cast<char*>(code_ptr + code_size));
+ 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());
}
+ VLOG(jit)
+ << "JIT added "
+ << PrettyMethod(method) << "@" << method
+ << " ccache_size=" << PrettySize(CodeCacheSize()) << ": "
+ << " dcache_size=" << PrettySize(DataCacheSize()) << ": "
+ << reinterpret_cast<const void*>(method_header->GetEntryPoint()) << ","
+ << reinterpret_cast<const void*>(method_header->GetEntryPoint() + method_header->code_size_);
- __builtin___clear_cache(reinterpret_cast<char*>(code_ptr),
- reinterpret_cast<char*>(code_ptr + code_size));
- 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);
}
@@ -304,6 +313,11 @@
return method_code_map_.size();
}
+void JitCodeCache::ClearData(Thread* self, void* data) {
+ MutexLock mu(self, lock_);
+ mspace_free(data_mspace_, data);
+}
+
uint8_t* JitCodeCache::ReserveData(Thread* self, size_t size) {
size = RoundUp(size, sizeof(void*));
uint8_t* result = nullptr;
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index e10f962..afff657 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -88,6 +88,11 @@
SHARED_REQUIRES(Locks::mutator_lock_)
REQUIRES(!lock_);
+ // Clear data from the data portion of the code cache.
+ void ClearData(Thread* self, void* data)
+ 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)
diff --git a/runtime/jit/jit_instrumentation.cc b/runtime/jit/jit_instrumentation.cc
index 7931306..6531325 100644
--- a/runtime/jit/jit_instrumentation.cc
+++ b/runtime/jit/jit_instrumentation.cc
@@ -20,6 +20,7 @@
#include "jit.h"
#include "jit_code_cache.h"
#include "scoped_thread_state_change.h"
+#include "thread_list.h"
namespace art {
namespace jit {
@@ -73,16 +74,48 @@
JitInstrumentationCache::JitInstrumentationCache(size_t hot_method_threshold,
size_t warm_method_threshold)
: hot_method_threshold_(hot_method_threshold),
- warm_method_threshold_(warm_method_threshold) {
+ warm_method_threshold_(warm_method_threshold),
+ listener_(this) {
}
void JitInstrumentationCache::CreateThreadPool() {
+ // Create the thread pool before setting the instrumentation, so that
+ // when the threads stopped being suspended, they can use it directly.
+ // There is a DCHECK in the 'AddSamples' method to ensure the tread pool
+ // is not null when we instrument.
thread_pool_.reset(new ThreadPool("Jit thread pool", 1));
+ thread_pool_->StartWorkers(Thread::Current());
+ {
+ // Add Jit interpreter instrumentation, tells the interpreter when
+ // to notify the jit to compile something.
+ ScopedSuspendAll ssa(__FUNCTION__);
+ Runtime::Current()->GetInstrumentation()->AddListener(
+ &listener_, JitInstrumentationListener::kJitEvents);
+ }
}
-void JitInstrumentationCache::DeleteThreadPool() {
- DCHECK(Runtime::Current()->IsShuttingDown(Thread::Current()));
- thread_pool_.reset();
+void JitInstrumentationCache::DeleteThreadPool(Thread* self) {
+ DCHECK(Runtime::Current()->IsShuttingDown(self));
+ if (thread_pool_ != nullptr) {
+ // First remove the listener, to avoid having mutators enter
+ // 'AddSamples'.
+ ThreadPool* cache = nullptr;
+ {
+ ScopedSuspendAll ssa(__FUNCTION__);
+ Runtime::Current()->GetInstrumentation()->RemoveListener(
+ &listener_, JitInstrumentationListener::kJitEvents);
+ // Clear thread_pool_ field while the threads are suspended.
+ // A mutator in the 'AddSamples' method will check against it.
+ cache = thread_pool_.release();
+ }
+ cache->StopWorkers(self);
+ cache->RemoveAllTasks(self);
+ // We could just suspend all threads, but we know those threads
+ // will finish in a short period, so it's not worth adding a suspend logic
+ // here. Besides, this is only done for shutdown.
+ cache->Wait(self, false, false);
+ delete cache;
+ }
}
void JitInstrumentationCache::AddSamples(Thread* self, ArtMethod* method, size_t) {
@@ -91,25 +124,32 @@
if (method->IsClassInitializer() || method->IsNative()) {
return;
}
- if (thread_pool_.get() == nullptr) {
- DCHECK(Runtime::Current()->IsShuttingDown(self));
- return;
- }
+ DCHECK(thread_pool_ != nullptr);
+
uint16_t sample_count = method->IncrementCounter();
if (sample_count == warm_method_threshold_) {
- if (ProfilingInfo::Create(self, method, /* retry_allocation */ false)) {
+ bool success = ProfilingInfo::Create(self, method, /* retry_allocation */ false);
+ if (success) {
VLOG(jit) << "Start profiling " << PrettyMethod(method);
- } else {
+ }
+
+ if (thread_pool_ == nullptr) {
+ // Calling ProfilingInfo::Create might put us in a suspended state, which could
+ // lead to the thread pool being deleted when we are shutting down.
+ DCHECK(Runtime::Current()->IsShuttingDown(self));
+ return;
+ }
+
+ if (!success) {
// 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_) {
+ DCHECK(thread_pool_ != nullptr);
thread_pool_->AddTask(self, new JitCompileTask(method, JitCompileTask::kCompile));
- thread_pool_->StartWorkers(self);
}
}
@@ -118,6 +158,20 @@
CHECK(instrumentation_cache_ != nullptr);
}
+void JitInstrumentationListener::MethodEntered(Thread* thread,
+ mirror::Object* /*this_object*/,
+ ArtMethod* method,
+ uint32_t /*dex_pc*/) {
+ instrumentation_cache_->AddSamples(thread, method, 1);
+}
+
+void JitInstrumentationListener::BackwardBranch(Thread* thread,
+ ArtMethod* method,
+ int32_t dex_pc_offset) {
+ CHECK_LE(dex_pc_offset, 0);
+ instrumentation_cache_->AddSamples(thread, method, 1);
+}
+
void JitInstrumentationListener::InvokeVirtualOrInterface(Thread* thread,
mirror::Object* this_object,
ArtMethod* caller,
@@ -138,7 +192,9 @@
}
void JitInstrumentationCache::WaitForCompilationToFinish(Thread* self) {
- thread_pool_->Wait(self, false, false);
+ if (thread_pool_ != nullptr) {
+ thread_pool_->Wait(self, false, false);
+ }
}
} // namespace jit
diff --git a/runtime/jit/jit_instrumentation.h b/runtime/jit/jit_instrumentation.h
index 9eb464b..1f96d59 100644
--- a/runtime/jit/jit_instrumentation.h
+++ b/runtime/jit/jit_instrumentation.h
@@ -31,7 +31,6 @@
namespace art {
namespace mirror {
- class Class;
class Object;
class Throwable;
} // namespace mirror
@@ -42,24 +41,7 @@
namespace jit {
-// Keeps track of which methods are hot.
-class JitInstrumentationCache {
- public:
- JitInstrumentationCache(size_t hot_method_threshold, size_t warm_method_threshold);
- void AddSamples(Thread* self, ArtMethod* method, size_t samples)
- SHARED_REQUIRES(Locks::mutator_lock_);
- void CreateThreadPool();
- void DeleteThreadPool();
- // Wait until there is no more pending compilation tasks.
- void WaitForCompilationToFinish(Thread* self);
-
- private:
- size_t hot_method_threshold_;
- size_t warm_method_threshold_;
- std::unique_ptr<ThreadPool> thread_pool_;
-
- DISALLOW_IMPLICIT_CONSTRUCTORS(JitInstrumentationCache);
-};
+class JitInstrumentationCache;
class JitInstrumentationListener : public instrumentation::InstrumentationListener {
public:
@@ -67,9 +49,8 @@
void MethodEntered(Thread* thread, mirror::Object* /*this_object*/,
ArtMethod* method, uint32_t /*dex_pc*/)
- OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) {
- instrumentation_cache_->AddSamples(thread, method, 1);
- }
+ OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_);
+
void MethodExited(Thread* /*thread*/, mirror::Object* /*this_object*/,
ArtMethod* /*method*/, uint32_t /*dex_pc*/,
const JValue& /*return_value*/)
@@ -90,10 +71,7 @@
ArtMethod* /*method*/, uint32_t /*new_dex_pc*/) OVERRIDE { }
void BackwardBranch(Thread* thread, ArtMethod* method, int32_t dex_pc_offset)
- OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) {
- CHECK_LE(dex_pc_offset, 0);
- instrumentation_cache_->AddSamples(thread, method, 1);
- }
+ OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_);
void InvokeVirtualOrInterface(Thread* thread,
mirror::Object* this_object,
@@ -102,12 +80,37 @@
ArtMethod* callee)
OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_);
+ static constexpr uint32_t kJitEvents =
+ instrumentation::Instrumentation::kMethodEntered |
+ instrumentation::Instrumentation::kBackwardBranch |
+ instrumentation::Instrumentation::kInvokeVirtualOrInterface;
+
private:
JitInstrumentationCache* const instrumentation_cache_;
DISALLOW_IMPLICIT_CONSTRUCTORS(JitInstrumentationListener);
};
+// Keeps track of which methods are hot.
+class JitInstrumentationCache {
+ public:
+ JitInstrumentationCache(size_t hot_method_threshold, size_t warm_method_threshold);
+ void AddSamples(Thread* self, ArtMethod* method, size_t samples)
+ SHARED_REQUIRES(Locks::mutator_lock_);
+ void CreateThreadPool();
+ void DeleteThreadPool(Thread* self);
+ // Wait until there is no more pending compilation tasks.
+ void WaitForCompilationToFinish(Thread* self);
+
+ private:
+ size_t hot_method_threshold_;
+ size_t warm_method_threshold_;
+ JitInstrumentationListener listener_;
+ std::unique_ptr<ThreadPool> thread_pool_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(JitInstrumentationCache);
+};
+
} // namespace jit
} // namespace art
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index 19ee7f4..174de0e 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -520,15 +520,6 @@
}
}
-inline void Class::SetVerifyErrorClass(Class* klass) {
- CHECK(klass != nullptr) << PrettyClass(this);
- if (Runtime::Current()->IsActiveTransaction()) {
- SetFieldObject<true>(OFFSET_OF_OBJECT_MEMBER(Class, verify_error_class_), klass);
- } else {
- SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(Class, verify_error_class_), klass);
- }
-}
-
template<VerifyObjectFlags kVerifyFlags>
inline uint32_t Class::GetAccessFlags() {
// Check class is loaded/retired or this is java.lang.String that has a
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 9d01a1d..77275f0 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -57,6 +57,15 @@
java_lang_Class_.VisitRootIfNonNull(visitor, RootInfo(kRootStickyClass));
}
+inline void Class::SetVerifyError(mirror::Object* error) {
+ CHECK(error != nullptr) << PrettyClass(this);
+ if (Runtime::Current()->IsActiveTransaction()) {
+ SetFieldObject<true>(OFFSET_OF_OBJECT_MEMBER(Class, verify_error_), error);
+ } else {
+ SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(Class, verify_error_), error);
+ }
+}
+
void Class::SetStatus(Handle<Class> h_this, Status new_status, Thread* self) {
Status old_status = h_this->GetStatus();
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
@@ -109,7 +118,13 @@
// case.
Class* exception_class = old_exception->GetClass();
if (!eiie_class->IsAssignableFrom(exception_class)) {
- h_this->SetVerifyErrorClass(exception_class);
+ // Store the exception class when this is the AoT compiler. Don't store full exceptions,
+ // as they need to be trimmed (native components are not storable in an image).
+ if (Runtime::Current()->IsAotCompiler()) {
+ h_this->SetVerifyError(exception_class);
+ } else {
+ h_this->SetVerifyError(old_exception.Get());
+ }
}
}
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 8219d69..c4339b9 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -1015,9 +1015,9 @@
void SetClinitThreadId(pid_t new_clinit_thread_id) SHARED_REQUIRES(Locks::mutator_lock_);
- Class* GetVerifyErrorClass() SHARED_REQUIRES(Locks::mutator_lock_) {
+ Object* GetVerifyError() SHARED_REQUIRES(Locks::mutator_lock_) {
// DCHECK(IsErroneous());
- return GetFieldObject<Class>(OFFSET_OF_OBJECT_MEMBER(Class, verify_error_class_));
+ return GetFieldObject<Class>(OFFSET_OF_OBJECT_MEMBER(Class, verify_error_));
}
uint16_t GetDexClassDefIndex() SHARED_REQUIRES(Locks::mutator_lock_) {
@@ -1158,7 +1158,7 @@
SHARED_REQUIRES(Locks::mutator_lock_);
private:
- void SetVerifyErrorClass(Class* klass) SHARED_REQUIRES(Locks::mutator_lock_);
+ void SetVerifyError(Object* klass) SHARED_REQUIRES(Locks::mutator_lock_);
template <bool throw_on_failure, bool use_referrers_cache>
bool ResolvedFieldAccessTest(Class* access_to, ArtField* field,
@@ -1230,8 +1230,9 @@
// check for interfaces and return null.
HeapReference<Class> super_class_;
- // If class verify fails, we must return same error on subsequent tries.
- HeapReference<Class> verify_error_class_;
+ // If class verify fails, we must return same error on subsequent tries. We may store either
+ // the class of the error, or an actual instance of Throwable here.
+ HeapReference<Object> verify_error_;
// Virtual method table (vtable), for use by "invoke-virtual". The vtable from the superclass is
// copied in, and virtual methods from our class either replace those from the super or are
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 81e7e6d..da21fee 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -27,7 +27,7 @@
#include "base/time_utils.h"
#include "class_linker.h"
#include "dex_file-inl.h"
-#include "dex_instruction.h"
+#include "dex_instruction-inl.h"
#include "lock_word-inl.h"
#include "mirror/class-inl.h"
#include "mirror/object-inl.h"
@@ -1034,15 +1034,15 @@
for (uint32_t monitor_dex_pc : monitor_enter_dex_pcs) {
// The verifier works in terms of the dex pcs of the monitor-enter instructions.
// We want the registers used by those instructions (so we can read the values out of them).
- uint16_t monitor_enter_instruction = code_item->insns_[monitor_dex_pc];
+ const Instruction* monitor_enter_instruction =
+ Instruction::At(&code_item->insns_[monitor_dex_pc]);
// Quick sanity check.
- if ((monitor_enter_instruction & 0xff) != Instruction::MONITOR_ENTER) {
- LOG(FATAL) << "expected monitor-enter @" << monitor_dex_pc << "; was "
- << reinterpret_cast<void*>(monitor_enter_instruction);
- }
+ CHECK_EQ(monitor_enter_instruction->Opcode(), Instruction::MONITOR_ENTER)
+ << "expected monitor-enter @" << monitor_dex_pc << "; was "
+ << reinterpret_cast<const void*>(monitor_enter_instruction);
- uint16_t monitor_register = ((monitor_enter_instruction >> 8) & 0xff);
+ uint16_t monitor_register = monitor_enter_instruction->VRegA();
uint32_t value;
bool success = stack_visitor->GetVReg(m, monitor_register, kReferenceVReg, &value);
CHECK(success) << "Failed to read v" << monitor_register << " of kind "
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 7b1fdb2..a8ba19b 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -151,6 +151,11 @@
return compiler_callbacks_;
}
+ void SetCompilerCallbacks(CompilerCallbacks* callbacks) {
+ CHECK(callbacks != nullptr);
+ compiler_callbacks_ = callbacks;
+ }
+
bool IsZygote() const {
return is_zygote_;
}
diff --git a/runtime/thread_pool.cc b/runtime/thread_pool.cc
index 0527d3a..5a4dfb8 100644
--- a/runtime/thread_pool.cc
+++ b/runtime/thread_pool.cc
@@ -82,6 +82,11 @@
}
}
+void ThreadPool::RemoveAllTasks(Thread* self) {
+ MutexLock mu(self, task_queue_lock_);
+ tasks_.clear();
+}
+
ThreadPool::ThreadPool(const char* name, size_t num_threads)
: name_(name),
task_queue_lock_("task queue lock"),
diff --git a/runtime/thread_pool.h b/runtime/thread_pool.h
index a2338d6..6cd4ad3 100644
--- a/runtime/thread_pool.h
+++ b/runtime/thread_pool.h
@@ -91,6 +91,9 @@
// after running it, it is the caller's responsibility.
void AddTask(Thread* self, Task* task) REQUIRES(!task_queue_lock_);
+ // Remove all tasks in the queue.
+ void RemoveAllTasks(Thread* self) REQUIRES(!task_queue_lock_);
+
ThreadPool(const char* name, size_t num_threads);
virtual ~ThreadPool();
diff --git a/test/008-exceptions/expected.txt b/test/008-exceptions/expected.txt
index 92c79dc..ea59a49 100644
--- a/test/008-exceptions/expected.txt
+++ b/test/008-exceptions/expected.txt
@@ -1,12 +1,15 @@
Got an NPE: second throw
java.lang.NullPointerException: second throw
- at Main.catchAndRethrow(Main.java:58)
- at Main.exceptions_007(Main.java:41)
- at Main.main(Main.java:49)
+ at Main.catchAndRethrow(Main.java:77)
+ at Main.exceptions_007(Main.java:59)
+ at Main.main(Main.java:67)
Caused by: java.lang.NullPointerException: first throw
- at Main.throwNullPointerException(Main.java:65)
- at Main.catchAndRethrow(Main.java:55)
+ at Main.throwNullPointerException(Main.java:84)
+ at Main.catchAndRethrow(Main.java:74)
... 2 more
Static Init
-BadError: This is bad by convention
-BadError: This is bad by convention
+BadError: This is bad by convention: BadInit
+BadError: This is bad by convention: BadInit
+Static BadInitNoStringInit
+BadErrorNoStringInit: This is bad by convention
+BadErrorNoStringInit: This is bad by convention
diff --git a/test/008-exceptions/src/Main.java b/test/008-exceptions/src/Main.java
index 7f6d0c5..c050204 100644
--- a/test/008-exceptions/src/Main.java
+++ b/test/008-exceptions/src/Main.java
@@ -14,20 +14,38 @@
* limitations under the License.
*/
-// An exception that doesn't have a <init>(String) method.
+// An error class.
class BadError extends Error {
- public BadError() {
- super("This is bad by convention");
+ public BadError(String s) {
+ super("This is bad by convention: " + s);
}
}
-// A class that throws BadException during static initialization.
+// A class that throws BadError during static initialization.
class BadInit {
static int dummy;
static {
System.out.println("Static Init");
if (true) {
- throw new BadError();
+ throw new BadError("BadInit");
+ }
+ }
+}
+
+// An error that doesn't have a <init>(String) method.
+class BadErrorNoStringInit extends Error {
+ public BadErrorNoStringInit() {
+ super("This is bad by convention");
+ }
+}
+
+// A class that throws BadErrorNoStringInit during static initialization.
+class BadInitNoStringInit {
+ static int dummy;
+ static {
+ System.out.println("Static BadInitNoStringInit");
+ if (true) {
+ throw new BadErrorNoStringInit();
}
}
}
@@ -48,6 +66,7 @@
public static void main (String args[]) {
exceptions_007();
exceptionsRethrowClassInitFailure();
+ exceptionsRethrowClassInitFailureNoStringInit();
}
private static void catchAndRethrow() {
@@ -86,4 +105,26 @@
error.printStackTrace();
}
}
+
+ private static void exceptionsRethrowClassInitFailureNoStringInit() {
+ try {
+ try {
+ BadInitNoStringInit.dummy = 1;
+ throw new IllegalStateException("Should not reach here.");
+ } catch (BadErrorNoStringInit e) {
+ System.out.println(e);
+ }
+
+ // Check if it works a second time.
+
+ try {
+ BadInitNoStringInit.dummy = 1;
+ throw new IllegalStateException("Should not reach here.");
+ } catch (BadErrorNoStringInit e) {
+ System.out.println(e);
+ }
+ } catch (Exception error) {
+ error.printStackTrace();
+ }
+ }
}
diff --git a/test/538-checker-typeprop-debuggable/expected.txt b/test/537-checker-jump-over-jump/expected.txt
similarity index 100%
rename from test/538-checker-typeprop-debuggable/expected.txt
rename to test/537-checker-jump-over-jump/expected.txt
diff --git a/test/537-checker-jump-over-jump/info.txt b/test/537-checker-jump-over-jump/info.txt
new file mode 100644
index 0000000..aeb30bb
--- /dev/null
+++ b/test/537-checker-jump-over-jump/info.txt
@@ -0,0 +1 @@
+Test for X86-64 elimination of jump over jump.
diff --git a/test/537-checker-jump-over-jump/src/Main.java b/test/537-checker-jump-over-jump/src/Main.java
new file mode 100644
index 0000000..fb666ea
--- /dev/null
+++ b/test/537-checker-jump-over-jump/src/Main.java
@@ -0,0 +1,44 @@
+/*
+ * 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 int FIBCOUNT = 64;
+ public static int[] fibs;
+
+ /// CHECK-START-X86_64: int Main.test() disassembly (after)
+ /// CHECK: If
+ /// CHECK-NEXT: cmp
+ /// CHECK-NEXT: jnl/ge
+ /// CHECK-NOT: jmp
+ /// CHECK: ArrayGet
+ // Checks that there is no conditional jump over a jmp. The ArrayGet is in
+ // the next block.
+ public static int test() {
+ for (int i = 1; ; i++) {
+ if (i >= FIBCOUNT) {
+ return fibs[0];
+ }
+ fibs[i] = (i + fibs[(i - 1)]);
+ }
+ }
+
+ public static void main(String[] args) {
+ fibs = new int[FIBCOUNT];
+ fibs[0] = 1;
+ test();
+ }
+}
diff --git a/test/538-checker-typeprop-debuggable/info.txt b/test/538-checker-typeprop-debuggable/info.txt
deleted file mode 100644
index 9d69056..0000000
--- a/test/538-checker-typeprop-debuggable/info.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Test that phis with environment uses which can be properly typed are kept
-in --debuggable mode.
\ No newline at end of file
diff --git a/test/538-checker-typeprop-debuggable/smali/ArrayGet.smali b/test/538-checker-typeprop-debuggable/smali/ArrayGet.smali
deleted file mode 100644
index 19583af..0000000
--- a/test/538-checker-typeprop-debuggable/smali/ArrayGet.smali
+++ /dev/null
@@ -1,167 +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.
-
-.class public LArrayGet;
-.super Ljava/lang/Object;
-
-
-# Test phi with fixed-type ArrayGet as an input and a matching second input.
-# The phi should be typed accordingly.
-
-## CHECK-START: void ArrayGet.matchingFixedType(float[], float) ssa_builder (after)
-## CHECK-NOT: Phi
-
-## CHECK-START-DEBUGGABLE: void ArrayGet.matchingFixedType(float[], float) ssa_builder (after)
-## CHECK-DAG: <<Arg1:f\d+>> ParameterValue
-## CHECK-DAG: <<Aget:f\d+>> ArrayGet
-## CHECK-DAG: {{f\d+}} Phi [<<Aget>>,<<Arg1>>] reg:0
-.method public static matchingFixedType([FF)V
- .registers 8
-
- const v0, 0x0
- const v1, 0x1
-
- aget v0, p0, v0 # read value
- add-float v2, v0, v1 # float use fixes type
-
- float-to-int v2, p1
- if-eqz v2, :after
- move v0, p1
- :after
- # v0 = Phi [ArrayGet, Arg1] => float
-
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-
-# Test phi with fixed-type ArrayGet as an input and a conflicting second input.
-# The phi should be eliminated due to the conflict.
-
-## CHECK-START: void ArrayGet.conflictingFixedType(float[], int) ssa_builder (after)
-## CHECK-NOT: Phi
-
-## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFixedType(float[], int) ssa_builder (after)
-## CHECK-NOT: Phi
-.method public static conflictingFixedType([FI)V
- .registers 8
-
- const v0, 0x0
- const v1, 0x1
-
- aget v0, p0, v0 # read value
- add-float v2, v0, v1 # float use fixes type
-
- if-eqz p1, :after
- move v0, p1
- :after
- # v0 = Phi [ArrayGet, Arg1] => conflict
-
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-
-# Same test as the one above, only this time tests that type of ArrayGet is not
-# changed.
-
-## CHECK-START: void ArrayGet.conflictingFixedType2(int[], float) ssa_builder (after)
-## CHECK-NOT: Phi
-
-## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFixedType2(int[], float) ssa_builder (after)
-## CHECK-NOT: Phi
-
-## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFixedType2(int[], float) ssa_builder (after)
-## CHECK: {{i\d+}} ArrayGet
-.method public static conflictingFixedType2([IF)V
- .registers 8
-
- const v0, 0x0
- const v1, 0x1
-
- aget v0, p0, v0 # read value
- add-int v2, v0, v1 # int use fixes type
-
- float-to-int v2, p1
- if-eqz v2, :after
- move v0, p1
- :after
- # v0 = Phi [ArrayGet, Arg1] => conflict
-
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-
-# Test phi with free-type ArrayGet as an input and a matching second input.
-# The phi should be typed accordingly.
-
-## CHECK-START: void ArrayGet.matchingFreeType(float[], float) ssa_builder (after)
-## CHECK-NOT: Phi
-
-## CHECK-START-DEBUGGABLE: void ArrayGet.matchingFreeType(float[], float) ssa_builder (after)
-## CHECK-DAG: <<Arg1:f\d+>> ParameterValue
-## CHECK-DAG: <<Aget:f\d+>> ArrayGet
-## CHECK-DAG: ArraySet [{{l\d+}},{{i\d+}},<<Aget>>]
-## CHECK-DAG: {{f\d+}} Phi [<<Aget>>,<<Arg1>>] reg:0
-.method public static matchingFreeType([FF)V
- .registers 8
-
- const v0, 0x0
- const v1, 0x1
-
- aget v0, p0, v0 # read value, should be float but has no typed use
- aput v0, p0, v1 # aput does not disambiguate the type
-
- float-to-int v2, p1
- if-eqz v2, :after
- move v0, p1
- :after
- # v0 = Phi [ArrayGet, Arg1] => float
-
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-
-# Test phi with free-type ArrayGet as an input and a conflicting second input.
-# The phi will be kept and typed according to the second input despite the
-# conflict.
-
-## CHECK-START: void ArrayGet.conflictingFreeType(int[], float) ssa_builder (after)
-## CHECK-NOT: Phi
-
-## CHECK-START-DEBUGGABLE: void ArrayGet.conflictingFreeType(int[], float) ssa_builder (after)
-## CHECK-DAG: <<Arg1:f\d+>> ParameterValue
-## CHECK-DAG: <<Aget:f\d+>> ArrayGet
-## CHECK-DAG: ArraySet [{{l\d+}},{{i\d+}},<<Aget>>]
-## CHECK-DAG: {{f\d+}} Phi [<<Aget>>,<<Arg1>>] reg:0
-.method public static conflictingFreeType([IF)V
- .registers 8
-
- const v0, 0x0
- const v1, 0x1
-
- aget v0, p0, v0 # read value, should be int but has no typed use
- aput v0, p0, v1 # aput does not disambiguate the type
-
- float-to-int v2, p1
- if-eqz v2, :after
- move v0, p1
- :after
- # v0 = Phi [ArrayGet, Arg1] => float
-
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
diff --git a/test/538-checker-typeprop-debuggable/smali/SsaBuilder.smali b/test/538-checker-typeprop-debuggable/smali/SsaBuilder.smali
deleted file mode 100644
index 395feaa..0000000
--- a/test/538-checker-typeprop-debuggable/smali/SsaBuilder.smali
+++ /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.
-
-.class public LSsaBuilder;
-.super Ljava/lang/Object;
-
-# Check that a dead phi with a live equivalent is replaced in an environment. The
-# following test case throws an exception and uses v0 afterwards. However, v0
-# contains a phi that is interpreted as int for the environment, and as float for
-# instruction use. SsaBuilder must substitute the int variant before removing it,
-# otherwise running the code with an array short enough to throw will crash at
-# runtime because v0 is undefined.
-
-## CHECK-START: int SsaBuilder.environmentPhi(boolean, int[]) ssa_builder (after)
-## CHECK-DAG: <<Cst0:f\d+>> FloatConstant 0
-## CHECK-DAG: <<Cst2:f\d+>> FloatConstant 2
-## CHECK-DAG: <<Phi:f\d+>> Phi [<<Cst0>>,<<Cst2>>]
-## CHECK-DAG: BoundsCheck env:[[<<Phi>>,{{i\d+}},{{z\d+}},{{l\d+}}]]
-
-.method public static environmentPhi(Z[I)I
- .registers 4
-
- const v0, 0x0
- if-eqz p0, :else
- const v0, 0x40000000
- :else
- # v0 = phi that can be both int and float
-
- :try_start
- const v1, 0x3
- aput v1, p1, v1
- const v0, 0x1 # generate catch phi for v0
- const v1, 0x4
- aput v1, p1, v1
- :try_end
- .catchall {:try_start .. :try_end} :use_as_float
-
- :use_as_float
- float-to-int v0, v0
- return v0
-.end method
\ No newline at end of file
diff --git a/test/538-checker-typeprop-debuggable/smali/TypePropagation.smali b/test/538-checker-typeprop-debuggable/smali/TypePropagation.smali
deleted file mode 100644
index 58682a1..0000000
--- a/test/538-checker-typeprop-debuggable/smali/TypePropagation.smali
+++ /dev/null
@@ -1,136 +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.
-
-.class public LTypePropagation;
-.super Ljava/lang/Object;
-
-## CHECK-START-DEBUGGABLE: void TypePropagation.mergeDeadPhi(boolean, boolean, int, float, float) ssa_builder (after)
-## CHECK-NOT: Phi
-.method public static mergeDeadPhi(ZZIFF)V
- .registers 8
-
- if-eqz p0, :after1
- move p2, p3
- :after1
- # p2 = merge(int,float) = conflict
-
- if-eqz p1, :after2
- move p2, p4
- :after2
- # p2 = merge(conflict,float) = conflict
-
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-## CHECK-START-DEBUGGABLE: void TypePropagation.mergeSameType(boolean, int, int) ssa_builder (after)
-## CHECK: {{i\d+}} Phi
-## CHECK-NOT: Phi
-.method public static mergeSameType(ZII)V
- .registers 8
- if-eqz p0, :after
- move p1, p2
- :after
- # p1 = merge(int,int) = int
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-## CHECK-START-DEBUGGABLE: void TypePropagation.mergeVoidInput(boolean, boolean, int, int) ssa_builder (after)
-## CHECK: {{i\d+}} Phi
-## CHECK: {{i\d+}} Phi
-## CHECK-NOT: Phi
-.method public static mergeVoidInput(ZZII)V
- .registers 8
- :loop
- # p2 = void (loop phi) => p2 = merge(int,int) = int
- if-eqz p0, :after
- move p2, p3
- :after
- # p2 = merge(void,int) = int
- if-eqz p1, :loop
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-## CHECK-START-DEBUGGABLE: void TypePropagation.mergeDifferentSize(boolean, int, long) ssa_builder (after)
-## CHECK-NOT: Phi
-.method public static mergeDifferentSize(ZIJ)V
- .registers 8
- if-eqz p0, :after
- move-wide p1, p2
- :after
- # p1 = merge(int,long) = conflict
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-## CHECK-START-DEBUGGABLE: void TypePropagation.mergeRefFloat(boolean, float, java.lang.Object) ssa_builder (after)
-## CHECK-NOT: Phi
-.method public static mergeRefFloat(ZFLjava/lang/Object;)V
- .registers 8
- if-eqz p0, :after
- move-object p1, p2
- :after
- # p1 = merge(float,reference) = conflict
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-## CHECK-START-DEBUGGABLE: void TypePropagation.mergeIntFloat_Success(boolean, float) ssa_builder (after)
-## CHECK: {{f\d+}} Phi
-## CHECK-NOT: Phi
-.method public static mergeIntFloat_Success(ZF)V
- .registers 8
- if-eqz p0, :after
- const/4 p1, 0x0
- :after
- # p1 = merge(float,0x0) = float
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-## CHECK-START-DEBUGGABLE: void TypePropagation.mergeIntFloat_Fail(boolean, int, float) ssa_builder (after)
-## CHECK-NOT: Phi
-.method public static mergeIntFloat_Fail(ZIF)V
- .registers 8
- if-eqz p0, :after
- move p1, p2
- :after
- # p1 = merge(int,float) = conflict
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
-
-## CHECK-START-DEBUGGABLE: void TypePropagation.updateAllUsersOnConflict(boolean, boolean, int, float, int) ssa_builder (after)
-## CHECK-NOT: Phi
-.method public static updateAllUsersOnConflict(ZZIFI)V
- .registers 8
-
- :loop1
- # loop phis for all args
- # p2 = merge(int,float) = float? => conflict
- move p2, p3
- if-eqz p0, :loop1
-
- :loop2
- # loop phis for all args
- # requests float equivalent of p4 phi in loop1 => conflict
- # propagates conflict to loop2's phis
- move p2, p4
- if-eqz p1, :loop2
-
- invoke-static {}, Ljava/lang/System;->nanoTime()J # create an env use
- return-void
-.end method
diff --git a/test/538-checker-typeprop-debuggable/src/Main.java b/test/538-checker-typeprop-debuggable/src/Main.java
deleted file mode 100644
index fe2343e..0000000
--- a/test/538-checker-typeprop-debuggable/src/Main.java
+++ /dev/null
@@ -1,43 +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.
- */
-
-import java.lang.reflect.Method;
-
-public class Main {
-
- // Workaround for b/18051191.
- class InnerClass {}
-
- private static void assertEquals(int expected, int actual) {
- if (expected != actual) {
- throw new Error("Wrong result, expected=" + expected + ", actual=" + actual);
- }
- }
-
- public static void main(String[] args) throws Exception {
- Class<?> c = Class.forName("SsaBuilder");
- Method m = c.getMethod("environmentPhi", new Class[] { boolean.class, int[].class });
-
- int[] array = new int[3];
- int result;
-
- result = (Integer) m.invoke(null, new Object[] { true, array } );
- assertEquals(2, result);
-
- result = (Integer) m.invoke(null, new Object[] { false, array } );
- assertEquals(0, result);
- }
-}
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 18867fd..3efa6ff 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -367,6 +367,7 @@
--boot-image=${BOOT_IMAGE} \
--dex-file=$DEX_LOCATION/$TEST_NAME.jar \
--oat-file=$DEX_LOCATION/dalvik-cache/$ISA/$(echo $DEX_LOCATION/$TEST_NAME.jar/classes.dex | cut -d/ -f 2- | sed "s:/:@:g") \
+ --app-image-file=$DEX_LOCATION/dalvik-cache/$ISA/$(echo $DEX_LOCATION/$TEST_NAME.jar/classes.art | cut -d/ -f 2- | sed "s:/:@:g") \
--instruction-set=$ISA"
if [ "x$INSTRUCTION_SET_FEATURES" != "x" ] ; then
dex2oat_cmdline="${dex2oat_cmdline} --instruction-set-features=${INSTRUCTION_SET_FEATURES}"
diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt
index d6f55aa..aa548cc 100644
--- a/tools/ahat/README.txt
+++ b/tools/ahat/README.txt
@@ -13,8 +13,6 @@
- Recommend how to start looking at a heap dump.
- Say how to enable allocation sites.
- Where to submit feedback, questions, and bug reports.
- * Submit perflib fix for getting stack traces, then uncomment that code in
- AhatSnapshot to use that.
* Dim 'image' and 'zygote' heap sizes slightly? Why do we even show these?
* Filter out RootObjs in mSnapshot.getGCRoots, not RootsHandler.
* Let user re-sort sites objects info by clicking column headers.
@@ -25,23 +23,15 @@
* Show root types.
* Heaped Table
- Make sortable by clicking on headers.
- - Use consistent order for heap columns.
- Sometimes I see "app" first, sometimes last (from one heap dump to
- another) How about, always sort by name?
* For HeapTable with single heap shown, the heap name isn't centered?
* Consistently document functions.
* Should help be part of an AhatHandler, that automatically gets the menu and
stylesheet link rather than duplicating that?
* Show version number with --version.
* Show somewhere where to send bugs.
- * /objects query takes a long time to load without parameters.
* Include a link to /objects in the overview and menu?
* Turn on LOCAL_JAVACFLAGS := -Xlint:unchecked -Werror
* Use hex for object ids in URLs?
- * In general, all tables and descriptions should show a limited amount to
- start, and only show more when requested by the user.
- * Don't have handlers inherit from HttpHandler
- - because they should be independent from http.
* [low priority] by site allocations won't line up if the stack has been
truncated. Is there any way to manually line them up in that case?
@@ -60,8 +50,6 @@
objects normally sorted by 'app' heap by default.
* Visit /objects without parameters and verify it doesn't throw an exception.
* Visit /objects with an invalid site, verify it doesn't throw an exception.
- * That we can view an array with 3 million elements in a reasonably short
- amount of time (not more than 1 second?)
* That we can view the list of all objects in a reasonably short amount of
time.
* That we don't show the 'extra' column in the DominatedList if we are
@@ -72,8 +60,6 @@
Reported Issues:
* Request to be able to sort tables by size.
- * Hangs on showing large arrays, where hat does not hang.
- - Solution is probably to not show all the array elements by default.
Perflib Requests:
* Class objects should have java.lang.Class as their class object, not null.
diff --git a/tools/ahat/src/AhatHandler.java b/tools/ahat/src/AhatHandler.java
index 2da02f8..d4b4d1b 100644
--- a/tools/ahat/src/AhatHandler.java
+++ b/tools/ahat/src/AhatHandler.java
@@ -16,51 +16,17 @@
package com.android.ahat;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
-import java.io.PrintStream;
/**
* AhatHandler.
*
- * Common base class of all the ahat HttpHandlers.
+ * Interface for an ahat page handler.
*/
-abstract class AhatHandler implements HttpHandler {
+interface AhatHandler {
- protected AhatSnapshot mSnapshot;
-
- public AhatHandler(AhatSnapshot snapshot) {
- mSnapshot = snapshot;
- }
-
- public abstract void handle(Doc doc, Query query) throws IOException;
-
- @Override
- public void handle(HttpExchange exchange) throws IOException {
- exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
- exchange.sendResponseHeaders(200, 0);
- PrintStream ps = new PrintStream(exchange.getResponseBody());
- try {
- HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
- DocString menu = new DocString();
- menu.appendLink(DocString.uri("/"), DocString.text("overview"));
- menu.append(" - ");
- menu.appendLink(DocString.uri("roots"), DocString.text("roots"));
- menu.append(" - ");
- menu.appendLink(DocString.uri("sites"), DocString.text("allocations"));
- menu.append(" - ");
- menu.appendLink(DocString.uri("help"), DocString.text("help"));
- doc.menu(menu);
- handle(doc, new Query(exchange.getRequestURI()));
- doc.close();
- } catch (RuntimeException e) {
- // Print runtime exceptions to standard error for debugging purposes,
- // because otherwise they are swallowed and not reported.
- System.err.println("Exception when handling " + exchange.getRequestURI() + ": ");
- e.printStackTrace();
- throw e;
- }
- ps.close();
- }
+ /**
+ * Handle the given query, rendering the page to the given document.
+ */
+ void handle(Doc doc, Query query) throws IOException;
}
diff --git a/tools/ahat/src/AhatHttpHandler.java b/tools/ahat/src/AhatHttpHandler.java
new file mode 100644
index 0000000..0553713
--- /dev/null
+++ b/tools/ahat/src/AhatHttpHandler.java
@@ -0,0 +1,64 @@
+/*
+ * 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 com.android.ahat;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * AhatHttpHandler.
+ *
+ * HttpHandler for AhatHandlers.
+ */
+class AhatHttpHandler implements HttpHandler {
+
+ private AhatHandler mAhatHandler;
+
+ public AhatHttpHandler(AhatHandler handler) {
+ mAhatHandler = handler;
+ }
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
+ exchange.sendResponseHeaders(200, 0);
+ PrintStream ps = new PrintStream(exchange.getResponseBody());
+ try {
+ HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
+ DocString menu = new DocString();
+ menu.appendLink(DocString.uri("/"), DocString.text("overview"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("roots"), DocString.text("roots"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("sites"), DocString.text("allocations"));
+ menu.append(" - ");
+ menu.appendLink(DocString.uri("help"), DocString.text("help"));
+ doc.menu(menu);
+ mAhatHandler.handle(doc, new Query(exchange.getRequestURI()));
+ doc.close();
+ } catch (RuntimeException e) {
+ // Print runtime exceptions to standard error for debugging purposes,
+ // because otherwise they are swallowed and not reported.
+ System.err.println("Exception when handling " + exchange.getRequestURI() + ": ");
+ e.printStackTrace();
+ throw e;
+ }
+ ps.close();
+ }
+}
diff --git a/tools/ahat/src/Doc.java b/tools/ahat/src/Doc.java
index 7fa70de..5a70c4c 100644
--- a/tools/ahat/src/Doc.java
+++ b/tools/ahat/src/Doc.java
@@ -25,27 +25,27 @@
/**
* Output the title of the page.
*/
- public void title(String format, Object... args);
+ void title(String format, Object... args);
/**
* Print a line of text for a page menu.
*/
- public void menu(DocString string);
+ void menu(DocString string);
/**
* Start a new section with the given title.
*/
- public void section(String title);
+ void section(String title);
/**
* Print a line of text in a normal font.
*/
- public void println(DocString string);
+ void println(DocString string);
/**
* Print a line of text in a large font that is easy to see and click on.
*/
- public void big(DocString string);
+ void big(DocString string);
/**
* Start a table with the given columns.
@@ -55,7 +55,7 @@
* This should be followed by calls to the 'row' method to fill in the table
* contents and the 'end' method to end the table.
*/
- public void table(Column... columns);
+ void table(Column... columns);
/**
* Start a table with the following heading structure:
@@ -68,14 +68,14 @@
* This should be followed by calls to the 'row' method to fill in the table
* contents and the 'end' method to end the table.
*/
- public void table(DocString description, List<Column> subcols, List<Column> cols);
+ void table(DocString description, List<Column> subcols, List<Column> cols);
/**
* Add a row to the currently active table.
* The number of values must match the number of columns provided for the
* currently active table.
*/
- public void row(DocString... values);
+ void row(DocString... values);
/**
* Start a new description list.
@@ -83,15 +83,15 @@
* This should be followed by calls to description() and finally a call to
* end().
*/
- public void descriptions();
+ void descriptions();
/**
* Add a description to the currently active description list.
*/
- public void description(DocString key, DocString value);
+ void description(DocString key, DocString value);
/**
* End the currently active table or description list.
*/
- public void end();
+ void end();
}
diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java
index 123d8be..34a5665 100644
--- a/tools/ahat/src/DominatedList.java
+++ b/tools/ahat/src/DominatedList.java
@@ -21,71 +21,35 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* Class for rendering a list of instances dominated by a single instance in a
* pretty way.
*/
class DominatedList {
- private static final int kIncrAmount = 100;
- private static final int kDefaultShown = 100;
-
/**
* Render a table to the given HtmlWriter showing a pretty list of
* instances.
*
- * Rather than show all of the instances (which may be very many), we use
- * the query parameter "dominated" to specify a limited number of
- * instances to show. The 'uri' parameter should be the current page URI, so
- * that we can add links to "show more" and "show less" objects that go to
- * the same page with only the number of objects adjusted.
+ * @param snapshot the snapshot where the instances reside
+ * @param doc the document to render the dominated list to
+ * @param query the current page query
+ * @param id a unique identifier to use for the dominated list in the current page
+ * @param instances the collection of instances to generate a list for
*/
- public static void render(final AhatSnapshot snapshot, Doc doc,
- Collection<Instance> instances, Query query) {
+ public static void render(final AhatSnapshot snapshot,
+ Doc doc, Query query, String id, Collection<Instance> instances) {
List<Instance> insts = new ArrayList<Instance>(instances);
Collections.sort(insts, Sort.defaultInstanceCompare(snapshot));
-
- int numInstancesToShow = getNumInstancesToShow(query, insts.size());
- List<Instance> shown = new ArrayList<Instance>(insts.subList(0, numInstancesToShow));
- List<Instance> hidden = insts.subList(numInstancesToShow, insts.size());
-
- // Add 'null' as a marker for "all the rest of the objects".
- if (!hidden.isEmpty()) {
- shown.add(null);
- }
- HeapTable.render(doc, new TableConfig(snapshot, hidden), snapshot, shown);
-
- if (insts.size() > kDefaultShown) {
- printMenu(doc, query, numInstancesToShow, insts.size());
- }
+ HeapTable.render(doc, query, id, new TableConfig(snapshot), snapshot, insts);
}
private static class TableConfig implements HeapTable.TableConfig<Instance> {
AhatSnapshot mSnapshot;
- // Map from heap name to the total size of the instances not shown in the
- // table.
- Map<Heap, Long> mHiddenSizes;
-
- public TableConfig(AhatSnapshot snapshot, List<Instance> hidden) {
+ public TableConfig(AhatSnapshot snapshot) {
mSnapshot = snapshot;
- mHiddenSizes = new HashMap<Heap, Long>();
- for (Heap heap : snapshot.getHeaps()) {
- mHiddenSizes.put(heap, 0L);
- }
-
- if (!hidden.isEmpty()) {
- for (Instance inst : hidden) {
- for (Heap heap : snapshot.getHeaps()) {
- int index = snapshot.getHeapIndex(heap);
- long size = inst.getRetainedSize(index);
- mHiddenSizes.put(heap, mHiddenSizes.get(heap) + size);
- }
- }
- }
}
@Override
@@ -95,9 +59,6 @@
@Override
public long getSize(Instance element, Heap heap) {
- if (element == null) {
- return mHiddenSizes.get(heap);
- }
int index = mSnapshot.getHeapIndex(heap);
return element.getRetainedSize(index);
}
@@ -110,56 +71,10 @@
}
public DocString render(Instance element) {
- if (element == null) {
- return DocString.text("...");
- } else {
- return Value.render(element);
- }
+ return Value.render(element);
}
};
return Collections.singletonList(value);
}
}
-
- // Figure out how many objects to show based on the query parameter.
- // The resulting value is guaranteed to be at least zero, and no greater
- // than the number of total objects.
- private static int getNumInstancesToShow(Query query, int totalNumInstances) {
- String value = query.get("dominated", null);
- try {
- int count = Math.min(totalNumInstances, Integer.parseInt(value));
- return Math.max(0, count);
- } catch (NumberFormatException e) {
- // We can't parse the value as a number. Ignore it.
- }
- return Math.min(kDefaultShown, totalNumInstances);
- }
-
- // Print a menu line after the table to control how many objects are shown.
- // It has the form:
- // (showing X of Y objects - show none - show less - show more - show all)
- private static void printMenu(Doc doc, Query query, int shown, int all) {
- DocString menu = new DocString();
- menu.appendFormat("(%d of %d objects shown - ", shown, all);
- if (shown > 0) {
- int less = Math.max(0, shown - kIncrAmount);
- menu.appendLink(query.with("dominated", 0), DocString.text("show none"));
- menu.append(" - ");
- menu.appendLink(query.with("dominated", less), DocString.text("show less"));
- menu.append(" - ");
- } else {
- menu.append("show none - show less - ");
- }
- if (shown < all) {
- int more = Math.min(shown + kIncrAmount, all);
- menu.appendLink(query.with("dominated", more), DocString.text("show more"));
- menu.append(" - ");
- menu.appendLink(query.with("dominated", all), DocString.text("show all"));
- menu.append(")");
- } else {
- menu.append("show more - show all)");
- }
- doc.println(menu);
- }
}
-
diff --git a/tools/ahat/src/HeapTable.java b/tools/ahat/src/HeapTable.java
index 37d5816..ed11d17 100644
--- a/tools/ahat/src/HeapTable.java
+++ b/tools/ahat/src/HeapTable.java
@@ -18,7 +18,9 @@
import com.android.tools.perflib.heap.Heap;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Class for rendering a table that includes sizes of some kind for each heap.
@@ -27,22 +29,27 @@
/**
* Configuration for a value column of a heap table.
*/
- public static interface ValueConfig<T> {
- public String getDescription();
- public DocString render(T element);
+ public interface ValueConfig<T> {
+ String getDescription();
+ DocString render(T element);
}
/**
* Configuration for the HeapTable.
*/
- public static interface TableConfig<T> {
- public String getHeapsDescription();
- public long getSize(T element, Heap heap);
- public List<ValueConfig<T>> getValueConfigs();
+ public interface TableConfig<T> {
+ String getHeapsDescription();
+ long getSize(T element, Heap heap);
+ List<ValueConfig<T>> getValueConfigs();
}
- public static <T> void render(Doc doc, TableConfig<T> config,
- AhatSnapshot snapshot, List<T> elements) {
+ /**
+ * Render the table to the given document.
+ * @param query - The page query.
+ * @param id - A unique identifier for the table on the page.
+ */
+ public static <T> void render(Doc doc, Query query, String id,
+ TableConfig<T> config, AhatSnapshot snapshot, List<T> elements) {
// Only show the heaps that have non-zero entries.
List<Heap> heaps = new ArrayList<Heap>();
for (Heap heap : snapshot.getHeaps()) {
@@ -68,9 +75,10 @@
}
doc.table(DocString.text(config.getHeapsDescription()), subcols, cols);
- // Print the entries.
+ // Print the entries up to the selected limit.
+ SubsetSelector<T> selector = new SubsetSelector(query, id, elements);
ArrayList<DocString> vals = new ArrayList<DocString>();
- for (T elem : elements) {
+ for (T elem : selector.selected()) {
vals.clear();
long total = 0;
for (Heap heap : heaps) {
@@ -87,7 +95,39 @@
}
doc.row(vals.toArray(new DocString[0]));
}
+
+ // Print a summary of the remaining entries if there are any.
+ List<T> remaining = selector.remaining();
+ if (!remaining.isEmpty()) {
+ Map<Heap, Long> summary = new HashMap<Heap, Long>();
+ for (Heap heap : heaps) {
+ summary.put(heap, 0L);
+ }
+
+ for (T elem : remaining) {
+ for (Heap heap : heaps) {
+ summary.put(heap, summary.get(heap) + config.getSize(elem, heap));
+ }
+ }
+
+ vals.clear();
+ long total = 0;
+ for (Heap heap : heaps) {
+ long size = summary.get(heap);
+ total += size;
+ vals.add(DocString.format("%,14d", size));
+ }
+ if (showTotal) {
+ vals.add(DocString.format("%,14d", total));
+ }
+
+ for (ValueConfig<T> value : values) {
+ vals.add(DocString.text("..."));
+ }
+ doc.row(vals.toArray(new DocString[0]));
+ }
doc.end();
+ selector.render(doc);
}
// Returns true if the given heap has a non-zero size entry.
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
index 1563aa0..96fc53b 100644
--- a/tools/ahat/src/Main.java
+++ b/tools/ahat/src/Main.java
@@ -73,11 +73,11 @@
InetAddress loopback = InetAddress.getLoopbackAddress();
InetSocketAddress addr = new InetSocketAddress(loopback, port);
HttpServer server = HttpServer.create(addr, 0);
- server.createContext("/", new OverviewHandler(ahat, hprof));
- server.createContext("/roots", new RootsHandler(ahat));
- server.createContext("/object", new ObjectHandler(ahat));
- server.createContext("/objects", new ObjectsHandler(ahat));
- server.createContext("/site", new SiteHandler(ahat));
+ server.createContext("/", new AhatHttpHandler(new OverviewHandler(ahat, hprof)));
+ server.createContext("/roots", new AhatHttpHandler(new RootsHandler(ahat)));
+ server.createContext("/object", new AhatHttpHandler(new ObjectHandler(ahat)));
+ server.createContext("/objects", new AhatHttpHandler(new ObjectsHandler(ahat)));
+ server.createContext("/site", new AhatHttpHandler(new SiteHandler(ahat)));
server.createContext("/bitmap", new BitmapHandler(ahat));
server.createContext("/help", new StaticHandler("help.html", "text/html"));
server.createContext("/style.css", new StaticHandler("style.css", "text/css"));
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index 5e321e2..9e4ce56 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -25,13 +25,26 @@
import com.android.tools.perflib.heap.RootObj;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-class ObjectHandler extends AhatHandler {
+class ObjectHandler implements AhatHandler {
+
+ private static final String ARRAY_ELEMENTS_ID = "elements";
+ private static final String DOMINATOR_PATH_ID = "dompath";
+ private static final String ALLOCATION_SITE_ID = "frames";
+ private static final String DOMINATED_OBJECTS_ID = "dominated";
+ private static final String INSTANCE_FIELDS_ID = "ifields";
+ private static final String STATIC_FIELDS_ID = "sfields";
+ private static final String HARD_REFS_ID = "refs";
+ private static final String SOFT_REFS_ID = "srefs";
+
+ private AhatSnapshot mSnapshot;
+
public ObjectHandler(AhatSnapshot snapshot) {
- super(snapshot);
+ mSnapshot = snapshot;
}
@Override
@@ -46,8 +59,8 @@
doc.title("Object %08x", inst.getUniqueId());
doc.big(Value.render(inst));
- printAllocationSite(doc, inst);
- printDominatorPath(doc, inst);
+ printAllocationSite(doc, query, inst);
+ printDominatorPath(doc, query, inst);
doc.section("Object Info");
ClassObj cls = inst.getClassObj();
@@ -62,39 +75,46 @@
printBitmap(doc, inst);
if (inst instanceof ClassInstance) {
- printClassInstanceFields(doc, (ClassInstance)inst);
+ printClassInstanceFields(doc, query, (ClassInstance)inst);
} else if (inst instanceof ArrayInstance) {
- printArrayElements(doc, (ArrayInstance)inst);
+ printArrayElements(doc, query, (ArrayInstance)inst);
} else if (inst instanceof ClassObj) {
- printClassInfo(doc, (ClassObj)inst);
+ printClassInfo(doc, query, (ClassObj)inst);
}
- printReferences(doc, inst);
+ printReferences(doc, query, inst);
printDominatedObjects(doc, query, inst);
}
- private static void printClassInstanceFields(Doc doc, ClassInstance inst) {
+ private static void printClassInstanceFields(Doc doc, Query query, ClassInstance inst) {
doc.section("Fields");
doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
- for (ClassInstance.FieldValue field : inst.getValues()) {
+ SubsetSelector<ClassInstance.FieldValue> selector
+ = new SubsetSelector(query, INSTANCE_FIELDS_ID, inst.getValues());
+ for (ClassInstance.FieldValue field : selector.selected()) {
doc.row(
DocString.text(field.getField().getType().toString()),
DocString.text(field.getField().getName()),
Value.render(field.getValue()));
}
doc.end();
+ selector.render(doc);
}
- private static void printArrayElements(Doc doc, ArrayInstance array) {
+ private static void printArrayElements(Doc doc, Query query, ArrayInstance array) {
doc.section("Array Elements");
doc.table(new Column("Index", Column.Align.RIGHT), new Column("Value"));
- Object[] elements = array.getValues();
- for (int i = 0; i < elements.length; i++) {
- doc.row(DocString.format("%d", i), Value.render(elements[i]));
+ List<Object> elements = Arrays.asList(array.getValues());
+ SubsetSelector<Object> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
+ int i = 0;
+ for (Object elem : selector.selected()) {
+ doc.row(DocString.format("%d", i), Value.render(elem));
+ i++;
}
doc.end();
+ selector.render(doc);
}
- private static void printClassInfo(Doc doc, ClassObj clsobj) {
+ private static void printClassInfo(Doc doc, Query query, ClassObj clsobj) {
doc.section("Class Info");
doc.descriptions();
doc.description(DocString.text("Super Class"), Value.render(clsobj.getSuperClassObj()));
@@ -103,41 +123,52 @@
doc.section("Static Fields");
doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
- for (Map.Entry<Field, Object> field : clsobj.getStaticFieldValues().entrySet()) {
+ List<Map.Entry<Field, Object>> fields
+ = new ArrayList<Map.Entry<Field, Object>>(clsobj.getStaticFieldValues().entrySet());
+ SubsetSelector<Map.Entry<Field, Object>> selector
+ = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
+ for (Map.Entry<Field, Object> field : selector.selected()) {
doc.row(
DocString.text(field.getKey().getType().toString()),
DocString.text(field.getKey().getName()),
Value.render(field.getValue()));
}
doc.end();
+ selector.render(doc);
}
- private static void printReferences(Doc doc, Instance inst) {
+ private static void printReferences(Doc doc, Query query, Instance inst) {
doc.section("Objects with References to this Object");
if (inst.getHardReferences().isEmpty()) {
doc.println(DocString.text("(none)"));
} else {
doc.table(new Column("Object"));
- for (Instance ref : inst.getHardReferences()) {
+ List<Instance> references = inst.getHardReferences();
+ SubsetSelector<Instance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
+ for (Instance ref : selector.selected()) {
doc.row(Value.render(ref));
}
doc.end();
+ selector.render(doc);
}
if (inst.getSoftReferences() != null) {
doc.section("Objects with Soft References to this Object");
doc.table(new Column("Object"));
- for (Instance ref : inst.getSoftReferences()) {
- doc.row(Value.render(inst));
+ List<Instance> references = inst.getSoftReferences();
+ SubsetSelector<Instance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
+ for (Instance ref : selector.selected()) {
+ doc.row(Value.render(ref));
}
doc.end();
+ selector.render(doc);
}
}
- private void printAllocationSite(Doc doc, Instance inst) {
+ private void printAllocationSite(Doc doc, Query query, Instance inst) {
doc.section("Allocation Site");
Site site = mSnapshot.getSiteForInstance(inst);
- SitePrinter.printSite(doc, mSnapshot, site);
+ SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
}
// Draw the bitmap corresponding to this instance if there is one.
@@ -150,7 +181,7 @@
}
}
- private void printDominatorPath(Doc doc, Instance inst) {
+ private void printDominatorPath(Doc doc, Query query, Instance inst) {
doc.section("Dominator Path from Root");
List<Instance> path = new ArrayList<Instance>();
for (Instance parent = inst;
@@ -193,14 +224,14 @@
return Collections.singletonList(value);
}
};
- HeapTable.render(doc, table, mSnapshot, path);
+ HeapTable.render(doc, query, DOMINATOR_PATH_ID, table, mSnapshot, path);
}
public void printDominatedObjects(Doc doc, Query query, Instance inst) {
doc.section("Immediately Dominated Objects");
List<Instance> instances = mSnapshot.getDominated(inst);
if (instances != null) {
- DominatedList.render(mSnapshot, doc, instances, query);
+ DominatedList.render(mSnapshot, doc, query, DOMINATED_OBJECTS_ID, instances);
} else {
doc.println(DocString.text("(none)"));
}
diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java
index 4e9c42e..8ad3f48 100644
--- a/tools/ahat/src/ObjectsHandler.java
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -22,9 +22,13 @@
import java.util.Collections;
import java.util.List;
-class ObjectsHandler extends AhatHandler {
+class ObjectsHandler implements AhatHandler {
+ private static final String OBJECTS_ID = "objects";
+
+ private AhatSnapshot mSnapshot;
+
public ObjectsHandler(AhatSnapshot snapshot) {
- super(snapshot);
+ mSnapshot = snapshot;
}
@Override
@@ -51,13 +55,15 @@
new Column("Size", Column.Align.RIGHT),
new Column("Heap"),
new Column("Object"));
- for (Instance inst : insts) {
+ SubsetSelector<Instance> selector = new SubsetSelector(query, OBJECTS_ID, insts);
+ for (Instance inst : selector.selected()) {
doc.row(
DocString.format("%,d", inst.getSize()),
DocString.text(inst.getHeap().getName()),
Value.render(inst));
}
doc.end();
+ selector.render(doc);
}
}
diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java
index f49c009..e86679f 100644
--- a/tools/ahat/src/OverviewHandler.java
+++ b/tools/ahat/src/OverviewHandler.java
@@ -22,11 +22,15 @@
import java.util.Collections;
import java.util.List;
-class OverviewHandler extends AhatHandler {
+class OverviewHandler implements AhatHandler {
+
+ private static final String OVERVIEW_ID = "overview";
+
+ private AhatSnapshot mSnapshot;
private File mHprof;
public OverviewHandler(AhatSnapshot snapshot, File hprof) {
- super(snapshot);
+ mSnapshot = snapshot;
mHprof = hprof;
}
@@ -43,7 +47,7 @@
doc.end();
doc.section("Heap Sizes");
- printHeapSizes(doc);
+ printHeapSizes(doc, query);
DocString menu = new DocString();
menu.appendLink(DocString.uri("roots"), DocString.text("Roots"));
@@ -54,7 +58,7 @@
doc.big(menu);
}
- private void printHeapSizes(Doc doc) {
+ private void printHeapSizes(Doc doc, Query query) {
List<Object> dummy = Collections.singletonList(null);
HeapTable.TableConfig<Object> table = new HeapTable.TableConfig<Object>() {
@@ -70,7 +74,7 @@
return Collections.emptyList();
}
};
- HeapTable.render(doc, table, mSnapshot, dummy);
+ HeapTable.render(doc, query, OVERVIEW_ID, table, mSnapshot, dummy);
}
}
diff --git a/tools/ahat/src/RootsHandler.java b/tools/ahat/src/RootsHandler.java
index 185b9bf..2a92c90 100644
--- a/tools/ahat/src/RootsHandler.java
+++ b/tools/ahat/src/RootsHandler.java
@@ -24,9 +24,14 @@
import java.util.List;
import java.util.Set;
-class RootsHandler extends AhatHandler {
+class RootsHandler implements AhatHandler {
+
+ private static final String ROOTS_ID = "roots";
+
+ private AhatSnapshot mSnapshot;
+
public RootsHandler(AhatSnapshot snapshot) {
- super(snapshot);
+ mSnapshot = snapshot;
}
@Override
@@ -45,7 +50,7 @@
for (Instance inst : rootset) {
roots.add(inst);
}
- DominatedList.render(mSnapshot, doc, roots, query);
+ DominatedList.render(mSnapshot, doc, query, ROOTS_ID, roots);
}
}
diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java
index 0a9381e..0425a5a 100644
--- a/tools/ahat/src/SiteHandler.java
+++ b/tools/ahat/src/SiteHandler.java
@@ -22,9 +22,15 @@
import java.util.Comparator;
import java.util.List;
-class SiteHandler extends AhatHandler {
+class SiteHandler implements AhatHandler {
+ private static final String ALLOCATION_SITE_ID = "frames";
+ private static final String SITES_CALLED_ID = "called";
+ private static final String OBJECTS_ALLOCATED_ID = "objects";
+
+ private AhatSnapshot mSnapshot;
+
public SiteHandler(AhatSnapshot snapshot) {
- super(snapshot);
+ mSnapshot = snapshot;
}
@Override
@@ -35,7 +41,7 @@
doc.title("Site %s", site.getName());
doc.section("Allocation Site");
- SitePrinter.printSite(doc, mSnapshot, site);
+ SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
doc.section("Sites Called from Here");
List<Site> children = site.getChildren();
@@ -69,7 +75,7 @@
return Collections.singletonList(value);
}
};
- HeapTable.render(doc, table, mSnapshot, children);
+ HeapTable.render(doc, query, SITES_CALLED_ID, table, mSnapshot, children);
}
doc.section("Objects Allocated");
@@ -84,7 +90,9 @@
new Sort.ObjectsInfoBySize(),
new Sort.ObjectsInfoByClassName());
Collections.sort(infos, compare);
- for (Site.ObjectsInfo info : infos) {
+ SubsetSelector<Site.ObjectsInfo> selector
+ = new SubsetSelector(query, OBJECTS_ALLOCATED_ID, infos);
+ for (Site.ObjectsInfo info : selector.selected()) {
String className = AhatSnapshot.getClassName(info.classObj);
doc.row(
DocString.format("%,14d", info.numBytes),
@@ -96,6 +104,7 @@
Value.render(info.classObj));
}
doc.end();
+ selector.render(doc);
}
}
diff --git a/tools/ahat/src/SitePrinter.java b/tools/ahat/src/SitePrinter.java
index be87032..2c06b47 100644
--- a/tools/ahat/src/SitePrinter.java
+++ b/tools/ahat/src/SitePrinter.java
@@ -22,7 +22,7 @@
import java.util.List;
class SitePrinter {
- public static void printSite(Doc doc, AhatSnapshot snapshot, Site site) {
+ public static void printSite(AhatSnapshot snapshot, Doc doc, Query query, String id, Site site) {
List<Site> path = new ArrayList<Site>();
for (Site parent = site; parent != null; parent = parent.getParent()) {
path.add(parent);
@@ -60,6 +60,6 @@
return Collections.singletonList(value);
}
};
- HeapTable.render(doc, table, snapshot, path);
+ HeapTable.render(doc, query, id, table, snapshot, path);
}
}
diff --git a/tools/ahat/src/SubsetSelector.java b/tools/ahat/src/SubsetSelector.java
new file mode 100644
index 0000000..79399c1
--- /dev/null
+++ b/tools/ahat/src/SubsetSelector.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.android.ahat;
+
+import java.util.List;
+
+/**
+ * The SubsetSelector is that can be added to a page that lets the
+ * user select a limited number of elements to show.
+ * This is used to limit the number of elements shown on a page by default,
+ * requiring the user to explicitly request more, so users not interested in
+ * more don't have to wait for everything to render.
+ */
+class SubsetSelector<T> {
+ private static final int kIncrAmount = 1000;
+ private static final int kDefaultShown = 1000;
+
+ private Query mQuery;
+ private String mId;
+ private int mLimit;
+ private List<T> mElements;
+
+ /**
+ * @param id - the name of the query parameter key that should hold
+ * the limit selectors selected value.
+ * @param query - The query for the current page. This is required so the
+ * LimitSelector can add a link to the same page with modified limit
+ * selection.
+ * @param elements - the elements to select from. The collection of elements
+ * should not be modified during the lifetime of the SubsetSelector object.
+ */
+ public SubsetSelector(Query query, String id, List<T> elements) {
+ mQuery = query;
+ mId = id;
+ mLimit = getSelectedLimit(query, id, elements.size());
+ mElements = elements;
+ }
+
+ // Return the list of elements included in the selected subset.
+ public List<T> selected() {
+ return mElements.subList(0, mLimit);
+ }
+
+ // Return the list of remaining elements not included in the selected subset.
+ public List<T> remaining() {
+ return mElements.subList(mLimit, mElements.size());
+ }
+
+ /**
+ * Returns the currently selected limit.
+ * @param query the current page query
+ * @param size the total number of elements to select from
+ * @return the number of selected elements
+ */
+ private static int getSelectedLimit(Query query, String id, int size) {
+ String value = query.get(id, null);
+ try {
+ int ivalue = Math.min(size, Integer.parseInt(value));
+ return Math.max(0, ivalue);
+ } catch (NumberFormatException e) {
+ // We can't parse the value as a number. Ignore it.
+ }
+ return Math.min(kDefaultShown, size);
+ }
+
+ // Render the limit selector to the given doc.
+ // It has the form:
+ // (showing X of Y - show none - show less - show more - show all)
+ public void render(Doc doc) {
+ int all = mElements.size();
+ if (all > kDefaultShown) {
+ DocString menu = new DocString();
+ menu.appendFormat("(%d of %d elements shown - ", mLimit, all);
+ if (mLimit > 0) {
+ int less = Math.max(0, mLimit - kIncrAmount);
+ menu.appendLink(mQuery.with(mId, 0), DocString.text("show none"));
+ menu.append(" - ");
+ menu.appendLink(mQuery.with(mId, less), DocString.text("show less"));
+ menu.append(" - ");
+ } else {
+ menu.append("show none - show less - ");
+ }
+ if (mLimit < all) {
+ int more = Math.min(mLimit + kIncrAmount, all);
+ menu.appendLink(mQuery.with(mId, more), DocString.text("show more"));
+ menu.append(" - ");
+ menu.appendLink(mQuery.with(mId, all), DocString.text("show all"));
+ menu.append(")");
+ } else {
+ menu.append("show more - show all)");
+ }
+ doc.println(menu);
+ }
+ }
+}
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
index 7b8774a..90cd7af 100644
--- a/tools/ahat/test-dump/Main.java
+++ b/tools/ahat/test-dump/Main.java
@@ -39,6 +39,15 @@
public ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
public PhantomReference aPhantomReference = new PhantomReference(anObject, referenceQueue);
public WeakReference aWeakReference = new WeakReference(anObject, referenceQueue);
+ public byte[] bigArray;
+
+ DumpedStuff() {
+ int N = 1000000;
+ bigArray = new byte[N];
+ for (int i = 0; i < N; i++) {
+ bigArray[i] = (byte)((i*i) & 0xFF);
+ }
+ }
}
public static void main(String[] args) throws IOException {
diff --git a/tools/ahat/test/PerformanceTest.java b/tools/ahat/test/PerformanceTest.java
new file mode 100644
index 0000000..6e46800
--- /dev/null
+++ b/tools/ahat/test/PerformanceTest.java
@@ -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.
+ */
+
+package com.android.ahat;
+
+import com.android.tools.perflib.heap.Instance;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class PerformanceTest {
+ private static class NullOutputStream extends OutputStream {
+ public void write(int b) throws IOException {
+ }
+ }
+
+ @Test
+ public void bigArray() throws IOException {
+ // It should not take more than 1 second to load the default object view
+ // for any object, including big arrays.
+ TestDump dump = TestDump.getTestDump();
+
+ Instance bigArray = (Instance)dump.getDumpedThing("bigArray");
+ assertNotNull(bigArray);
+
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
+ AhatHandler handler = new ObjectHandler(snapshot);
+
+ PrintStream ps = new PrintStream(new NullOutputStream());
+ HtmlDoc doc = new HtmlDoc(ps, DocString.text("bigArray test"), DocString.uri("style.css"));
+ String uri = "http://localhost:7100/object?id=" + bigArray.getId();
+ Query query = new Query(DocString.uri(uri));
+
+ long start = System.currentTimeMillis();
+ handler.handle(doc, query);
+ long time = System.currentTimeMillis() - start;
+ assertTrue("bigArray took too long: " + time + "ms", time < 1000);
+ }
+}
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index bab7121..e8894e2 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -23,8 +23,9 @@
if (args.length == 0) {
args = new String[]{
"com.android.ahat.InstanceUtilsTest",
+ "com.android.ahat.PerformanceTest",
"com.android.ahat.QueryTest",
- "com.android.ahat.SortTest"
+ "com.android.ahat.SortTest",
};
}
JUnitCore.main(args);
diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh
index 631e0a0..047c24f 100755
--- a/tools/buildbot-build.sh
+++ b/tools/buildbot-build.sh
@@ -21,7 +21,7 @@
out_dir=${OUT_DIR-out}
java_libraries_dir=${out_dir}/target/common/obj/JAVA_LIBRARIES
-common_targets="vogar vogar.jar ${java_libraries_dir}/core-tests_intermediates/javalib.jar apache-harmony-jdwp-tests-hostdex ${java_libraries_dir}/jsr166-tests_intermediates/javalib.jar"
+common_targets="vogar ${java_libraries_dir}/core-tests_intermediates/javalib.jar apache-harmony-jdwp-tests-hostdex ${java_libraries_dir}/jsr166-tests_intermediates/javalib.jar"
mode="target"
j_arg="-j$(nproc)"
showcommands=