Merge "ART: Change behavior for rethrowing init failures (2)"
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_mips.cc b/compiler/optimizing/code_generator_mips.cc
index 8106499..959adb4 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -40,12 +40,8 @@
static constexpr Register kMethodRegisterArgument = A0;
// We need extra temporary/scratch registers (in addition to AT) in some cases.
-static constexpr Register TMP = T8;
static constexpr FRegister FTMP = F8;
-// ART Thread Register.
-static constexpr Register TR = S1;
-
Location MipsReturnLocation(Primitive::Type return_type) {
switch (return_type) {
case Primitive::kPrimBoolean:
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..8308d9e 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) {
@@ -4043,16 +4062,16 @@
// Ensure the value is in a byte register.
locations->SetInAt(1, Location::RegisterLocation(EAX));
} else if (Primitive::IsFloatingPointType(field_type)) {
- locations->SetInAt(1, Location::RequiresFpuRegister());
- } else {
+ if (is_volatile && field_type == Primitive::kPrimDouble) {
+ // In order to satisfy the semantics of volatile, this must be a single instruction store.
+ locations->SetInAt(1, Location::RequiresFpuRegister());
+ } else {
+ locations->SetInAt(1, Location::FpuRegisterOrConstant(instruction->InputAt(1)));
+ }
+ } else if (is_volatile && field_type == Primitive::kPrimLong) {
+ // In order to satisfy the semantics of volatile, this must be a single instruction store.
locations->SetInAt(1, Location::RequiresRegister());
- }
- if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) {
- // Temporary registers for the write barrier.
- locations->AddTemp(Location::RequiresRegister()); // Possibly used for reference poisoning too.
- // Ensure the card is in a byte register.
- locations->AddTemp(Location::RegisterLocation(ECX));
- } else if (is_volatile && (field_type == Primitive::kPrimLong)) {
+
// 64bits value can be atomically written to an address with movsd and an XMM register.
// We need two XMM registers because there's no easier way to (bit) copy a register pair
// into a single XMM register (we copy each pair part into the XMMs and then interleave them).
@@ -4060,6 +4079,15 @@
// isolated cases when we need this it isn't worth adding the extra complexity.
locations->AddTemp(Location::RequiresFpuRegister());
locations->AddTemp(Location::RequiresFpuRegister());
+ } else {
+ locations->SetInAt(1, Location::RegisterOrConstant(instruction->InputAt(1)));
+
+ if (CodeGenerator::StoreNeedsWriteBarrier(field_type, instruction->InputAt(1))) {
+ // Temporary registers for the write barrier.
+ locations->AddTemp(Location::RequiresRegister()); // May be used for reference poisoning too.
+ // Ensure the card is in a byte register.
+ locations->AddTemp(Location::RegisterLocation(ECX));
+ }
}
}
@@ -4081,6 +4109,8 @@
GenerateMemoryBarrier(MemBarrierKind::kAnyStore);
}
+ bool maybe_record_implicit_null_check_done = false;
+
switch (field_type) {
case Primitive::kPrimBoolean:
case Primitive::kPrimByte: {
@@ -4090,7 +4120,12 @@
case Primitive::kPrimShort:
case Primitive::kPrimChar: {
- __ movw(Address(base, offset), value.AsRegister<Register>());
+ if (value.IsConstant()) {
+ int16_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant());
+ __ movw(Address(base, offset), Immediate(v));
+ } else {
+ __ movw(Address(base, offset), value.AsRegister<Register>());
+ }
break;
}
@@ -4105,6 +4140,9 @@
__ movl(temp, value.AsRegister<Register>());
__ PoisonHeapReference(temp);
__ movl(Address(base, offset), temp);
+ } else if (value.IsConstant()) {
+ int32_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant());
+ __ movl(Address(base, offset), Immediate(v));
} else {
__ movl(Address(base, offset), value.AsRegister<Register>());
}
@@ -4120,21 +4158,40 @@
__ punpckldq(temp1, temp2);
__ movsd(Address(base, offset), temp1);
codegen_->MaybeRecordImplicitNullCheck(instruction);
+ } else if (value.IsConstant()) {
+ int64_t v = CodeGenerator::GetInt64ValueOf(value.GetConstant());
+ __ movl(Address(base, offset), Immediate(Low32Bits(v)));
+ codegen_->MaybeRecordImplicitNullCheck(instruction);
+ __ movl(Address(base, kX86WordSize + offset), Immediate(High32Bits(v)));
} else {
__ movl(Address(base, offset), value.AsRegisterPairLow<Register>());
codegen_->MaybeRecordImplicitNullCheck(instruction);
__ movl(Address(base, kX86WordSize + offset), value.AsRegisterPairHigh<Register>());
}
+ maybe_record_implicit_null_check_done = true;
break;
}
case Primitive::kPrimFloat: {
- __ movss(Address(base, offset), value.AsFpuRegister<XmmRegister>());
+ if (value.IsConstant()) {
+ int32_t v = CodeGenerator::GetInt32ValueOf(value.GetConstant());
+ __ 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 = CodeGenerator::GetInt64ValueOf(value.GetConstant());
+ __ movl(Address(base, offset), Immediate(Low32Bits(v)));
+ codegen_->MaybeRecordImplicitNullCheck(instruction);
+ __ movl(Address(base, kX86WordSize + offset), Immediate(High32Bits(v)));
+ maybe_record_implicit_null_check_done = true;
+ } else {
+ __ movsd(Address(base, offset), value.AsFpuRegister<XmmRegister>());
+ }
break;
}
@@ -4143,8 +4200,7 @@
UNREACHABLE();
}
- // Longs are handled in the switch.
- if (field_type != Primitive::kPrimLong) {
+ if (!maybe_record_implicit_null_check_done) {
codegen_->MaybeRecordImplicitNullCheck(instruction);
}
@@ -4481,7 +4537,7 @@
// Ensure the value is in a byte register.
locations->SetInAt(2, Location::ByteRegisterOrConstant(EAX, instruction->InputAt(2)));
} else if (Primitive::IsFloatingPointType(value_type)) {
- locations->SetInAt(2, Location::RequiresFpuRegister());
+ locations->SetInAt(2, Location::FpuRegisterOrConstant(instruction->InputAt(2)));
} else {
locations->SetInAt(2, Location::RegisterOrConstant(instruction->InputAt(2)));
}
@@ -4667,8 +4723,14 @@
Address address = index.IsConstant()
? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_4) + offset)
: Address(array, index.AsRegister<Register>(), 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;
}
@@ -4677,8 +4739,19 @@
Address address = index.IsConstant()
? Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) + offset)
: Address(array, index.AsRegister<Register>(), TIMES_8, offset);
- DCHECK(value.IsFpuRegister());
- __ movsd(address, value.AsFpuRegister<XmmRegister>());
+ if (value.IsFpuRegister()) {
+ __ movsd(address, value.AsFpuRegister<XmmRegister>());
+ } else {
+ DCHECK(value.IsConstant());
+ Address address_hi = index.IsConstant() ?
+ Address(array, (index.GetConstant()->AsIntConstant()->GetValue() << TIMES_8) +
+ offset + kX86WordSize) :
+ Address(array, index.AsRegister<Register>(), TIMES_8, offset + kX86WordSize);
+ int64_t v = bit_cast<int64_t, double>(value.GetConstant()->AsDoubleConstant()->GetValue());
+ __ movl(address, Immediate(Low32Bits(v)));
+ codegen_->MaybeRecordImplicitNullCheck(instruction);
+ __ movl(address_hi, Immediate(High32Bits(v)));
+ }
break;
}
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.cc b/compiler/optimizing/intrinsics.cc
index dbe7524..b01324e 100644
--- a/compiler/optimizing/intrinsics.cc
+++ b/compiler/optimizing/intrinsics.cc
@@ -89,10 +89,7 @@
}
}
-static Intrinsics GetIntrinsic(InlineMethod method, InstructionSet instruction_set) {
- if (instruction_set == kMips) {
- return Intrinsics::kNone;
- }
+static Intrinsics GetIntrinsic(InlineMethod method) {
switch (method.opcode) {
// Floating-point conversions.
case kIntrinsicDoubleCvt:
@@ -431,7 +428,7 @@
DexFileMethodInliner* inliner = driver_->GetMethodInlinerMap()->GetMethodInliner(&dex_file);
DCHECK(inliner != nullptr);
if (inliner->IsIntrinsic(invoke->GetDexMethodIndex(), &method)) {
- Intrinsics intrinsic = GetIntrinsic(method, graph_->GetInstructionSet());
+ Intrinsics intrinsic = GetIntrinsic(method);
if (intrinsic != Intrinsics::kNone) {
if (!CheckInvokeType(intrinsic, invoke, dex_file)) {
diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc
index 5efcf4e..a94e3a8 100644
--- a/compiler/optimizing/intrinsics_mips.cc
+++ b/compiler/optimizing/intrinsics_mips.cc
@@ -138,6 +138,108 @@
#define __ assembler->
+// boolean java.lang.String.equals(Object anObject)
+void IntrinsicLocationsBuilderMIPS::VisitStringEquals(HInvoke* invoke) {
+ LocationSummary* locations = new (arena_) LocationSummary(invoke,
+ LocationSummary::kNoCall,
+ kIntrinsified);
+ locations->SetInAt(0, Location::RequiresRegister());
+ locations->SetInAt(1, Location::RequiresRegister());
+ locations->SetOut(Location::RequiresRegister());
+
+ // Temporary registers to store lengths of strings and for calculations.
+ locations->AddTemp(Location::RequiresRegister());
+ locations->AddTemp(Location::RequiresRegister());
+ locations->AddTemp(Location::RequiresRegister());
+}
+
+void IntrinsicCodeGeneratorMIPS::VisitStringEquals(HInvoke* invoke) {
+ MipsAssembler* assembler = GetAssembler();
+ LocationSummary* locations = invoke->GetLocations();
+
+ Register str = locations->InAt(0).AsRegister<Register>();
+ Register arg = locations->InAt(1).AsRegister<Register>();
+ Register out = locations->Out().AsRegister<Register>();
+
+ Register temp1 = locations->GetTemp(0).AsRegister<Register>();
+ Register temp2 = locations->GetTemp(1).AsRegister<Register>();
+ Register temp3 = locations->GetTemp(2).AsRegister<Register>();
+
+ MipsLabel loop;
+ MipsLabel end;
+ MipsLabel return_true;
+ MipsLabel return_false;
+
+ // Get offsets of count, value, and class fields within a string object.
+ const uint32_t count_offset = mirror::String::CountOffset().Uint32Value();
+ const uint32_t value_offset = mirror::String::ValueOffset().Uint32Value();
+ const uint32_t class_offset = mirror::Object::ClassOffset().Uint32Value();
+
+ // Note that the null check must have been done earlier.
+ DCHECK(!invoke->CanDoImplicitNullCheckOn(invoke->InputAt(0)));
+
+ // If the register containing the pointer to "this", and the register
+ // containing the pointer to "anObject" are the same register then
+ // "this", and "anObject" are the same object and we can
+ // short-circuit the logic to a true result.
+ if (str == arg) {
+ __ LoadConst32(out, 1);
+ return;
+ }
+
+ // Check if input is null, return false if it is.
+ __ Beqz(arg, &return_false);
+
+ // Reference equality check, return true if same reference.
+ __ Beq(str, arg, &return_true);
+
+ // Instanceof check for the argument by comparing class fields.
+ // All string objects must have the same type since String cannot be subclassed.
+ // Receiver must be a string object, so its class field is equal to all strings' class fields.
+ // If the argument is a string object, its class field must be equal to receiver's class field.
+ __ Lw(temp1, str, class_offset);
+ __ Lw(temp2, arg, class_offset);
+ __ Bne(temp1, temp2, &return_false);
+
+ // Load lengths of this and argument strings.
+ __ Lw(temp1, str, count_offset);
+ __ Lw(temp2, arg, count_offset);
+ // Check if lengths are equal, return false if they're not.
+ __ Bne(temp1, temp2, &return_false);
+ // Return true if both strings are empty.
+ __ Beqz(temp1, &return_true);
+
+ // Don't overwrite input registers
+ __ Move(TMP, str);
+ __ Move(temp3, arg);
+
+ // Assertions that must hold in order to compare strings 2 characters at a time.
+ DCHECK_ALIGNED(value_offset, 4);
+ static_assert(IsAligned<4>(kObjectAlignment), "String of odd length is not zero padded");
+
+ // Loop to compare strings 2 characters at a time starting at the beginning of the string.
+ // Ok to do this because strings are zero-padded.
+ __ Bind(&loop);
+ __ Lw(out, TMP, value_offset);
+ __ Lw(temp2, temp3, value_offset);
+ __ Bne(out, temp2, &return_false);
+ __ Addiu(TMP, TMP, 4);
+ __ Addiu(temp3, temp3, 4);
+ __ Addiu(temp1, temp1, -2);
+ __ Bgtz(temp1, &loop);
+
+ // Return true and exit the function.
+ // If loop does not result in returning false, we return true.
+ __ Bind(&return_true);
+ __ LoadConst32(out, 1);
+ __ B(&end);
+
+ // Return false and exit the function.
+ __ Bind(&return_false);
+ __ LoadConst32(out, 0);
+ __ Bind(&end);
+}
+
// Unimplemented intrinsics.
#define UNIMPLEMENTED_INTRINSIC(Name) \
@@ -204,7 +306,6 @@
UNIMPLEMENTED_INTRINSIC(UnsafeCASObject)
UNIMPLEMENTED_INTRINSIC(StringCharAt)
UNIMPLEMENTED_INTRINSIC(StringCompareTo)
-UNIMPLEMENTED_INTRINSIC(StringEquals)
UNIMPLEMENTED_INTRINSIC(StringIndexOf)
UNIMPLEMENTED_INTRINSIC(StringIndexOfAfter)
UNIMPLEMENTED_INTRINSIC(StringNewStringFromBytes)
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/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/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/arch/mips/registers_mips.h b/runtime/arch/mips/registers_mips.h
index 0f784ed..1096af0 100644
--- a/runtime/arch/mips/registers_mips.h
+++ b/runtime/arch/mips/registers_mips.h
@@ -59,6 +59,8 @@
SP = 29, // Stack pointer.
FP = 30, // Saved value/frame pointer.
RA = 31, // Return address.
+ TR = S1, // ART Thread Register
+ TMP = T8, // scratch register (in addition to AT)
kNumberOfCoreRegisters = 32,
kNoRegister = -1 // Signals an illegal register.
};
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index cdb7712..5dac95d 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -876,8 +876,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
@@ -886,7 +886,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()));
@@ -2155,8 +2155,6 @@
last_field_idx = field_idx;
}
}
- klass->SetSFieldsPtr(sfields);
- DCHECK_EQ(klass->NumStaticFields(), num_sfields);
// Load instance fields.
LengthPrefixedArray<ArtField>* ifields = AllocArtFieldArray(self,
allocator,
@@ -2178,8 +2176,17 @@
LOG(WARNING) << "Duplicate fields in class " << PrettyDescriptor(klass.Get())
<< " (unique static fields: " << num_sfields << "/" << it.NumStaticFields()
<< ", unique instance fields: " << num_ifields << "/" << it.NumInstanceFields() << ")";
- // NOTE: Not shrinking the over-allocated sfields/ifields.
+ // NOTE: Not shrinking the over-allocated sfields/ifields, just setting size.
+ if (sfields != nullptr) {
+ sfields->SetSize(num_sfields);
+ }
+ if (ifields != nullptr) {
+ ifields->SetSize(num_ifields);
+ }
}
+ // Set the field arrays.
+ klass->SetSFieldsPtr(sfields);
+ DCHECK_EQ(klass->NumStaticFields(), num_sfields);
klass->SetIFieldsPtr(ifields);
DCHECK_EQ(klass->NumInstanceFields(), num_ifields);
// Load methods.
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index e523fbb..6e4b7a3 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -4069,13 +4069,15 @@
if (is_constructor) {
// If we invoked a constructor (which actually returns void), return the receiver,
// unless we threw, in which case we return null.
- result_tag = JDWP::JT_OBJECT;
+ DCHECK_EQ(JDWP::JT_VOID, result_tag);
if (exceptionObjectId == 0) {
// TODO we could keep the receiver ObjectId in the DebugInvokeReq to avoid looking into the
// object registry.
result_value = GetObjectRegistry()->Add(pReq->receiver.Read());
+ result_tag = TagFromObject(soa, pReq->receiver.Read());
} else {
result_value = 0;
+ result_tag = JDWP::JT_OBJECT;
}
}
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/jni_internal.cc b/runtime/jni_internal.cc
index 234a733..415109f 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -1670,7 +1670,7 @@
CHECK_NON_NULL_ARGUMENT_RETURN_VOID(java_string);
ScopedObjectAccess soa(env);
mirror::String* s = soa.Decode<mirror::String*>(java_string);
- if (start < 0 || length < 0 || start + length > s->GetLength()) {
+ if (start < 0 || length < 0 || length > s->GetLength() - start) {
ThrowSIOOBE(soa, start, length, s->GetLength());
} else {
CHECK_NON_NULL_MEMCPY_ARGUMENT(length, buf);
@@ -1684,7 +1684,7 @@
CHECK_NON_NULL_ARGUMENT_RETURN_VOID(java_string);
ScopedObjectAccess soa(env);
mirror::String* s = soa.Decode<mirror::String*>(java_string);
- if (start < 0 || length < 0 || start + length > s->GetLength()) {
+ if (start < 0 || length < 0 || length > s->GetLength() - start) {
ThrowSIOOBE(soa, start, length, s->GetLength());
} else {
CHECK_NON_NULL_MEMCPY_ARGUMENT(length, buf);
@@ -2473,7 +2473,7 @@
"GetPrimitiveArrayRegion",
"get region of");
if (array != nullptr) {
- if (start < 0 || length < 0 || start + length > array->GetLength()) {
+ if (start < 0 || length < 0 || length > array->GetLength() - start) {
ThrowAIOOBE(soa, array, start, length, "src");
} else {
CHECK_NON_NULL_MEMCPY_ARGUMENT(length, buf);
@@ -2493,7 +2493,7 @@
"SetPrimitiveArrayRegion",
"set region of");
if (array != nullptr) {
- if (start < 0 || length < 0 || start + length > array->GetLength()) {
+ if (start < 0 || length < 0 || length > array->GetLength() - start) {
ThrowAIOOBE(soa, array, start, length, "dst");
} else {
CHECK_NON_NULL_MEMCPY_ARGUMENT(length, buf);
diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc
index 41b368e..649df5f 100644
--- a/runtime/jni_internal_test.cc
+++ b/runtime/jni_internal_test.cc
@@ -1077,6 +1077,12 @@
env_->set_region_fn(a, size - 1, size, nullptr); \
ExpectException(aioobe_); \
\
+ /* Regression test against integer overflow in range check. */ \
+ env_->get_region_fn(a, 0x7fffffff, 0x7fffffff, nullptr); \
+ ExpectException(aioobe_); \
+ env_->set_region_fn(a, 0x7fffffff, 0x7fffffff, nullptr); \
+ ExpectException(aioobe_); \
+ \
/* It's okay for the buffer to be null as long as the length is 0. */ \
env_->get_region_fn(a, 2, 0, nullptr); \
/* Even if the offset is invalid... */ \
@@ -1507,6 +1513,9 @@
ExpectException(sioobe_);
env_->GetStringRegion(s, 10, 1, nullptr);
ExpectException(sioobe_);
+ // Regression test against integer overflow in range check.
+ env_->GetStringRegion(s, 0x7fffffff, 0x7fffffff, nullptr);
+ ExpectException(sioobe_);
jchar chars[4] = { 'x', 'x', 'x', 'x' };
env_->GetStringRegion(s, 1, 2, &chars[1]);
@@ -1529,6 +1538,9 @@
ExpectException(sioobe_);
env_->GetStringUTFRegion(s, 10, 1, nullptr);
ExpectException(sioobe_);
+ // Regression test against integer overflow in range check.
+ env_->GetStringUTFRegion(s, 0x7fffffff, 0x7fffffff, nullptr);
+ ExpectException(sioobe_);
char bytes[4] = { 'x', 'x', 'x', 'x' };
env_->GetStringUTFRegion(s, 1, 2, &bytes[1]);
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/537-checker-jump-over-jump/expected.txt b/test/537-checker-jump-over-jump/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/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/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=