Merge "Add support for registering classpath classes status."
diff --git a/Android.bp b/Android.bp
index 0ce7916..8678fd0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -41,4 +41,5 @@
"test",
"tools/cpp-define-generator",
"tools/dmtracedump",
+ "tools/titrace",
]
diff --git a/compiler/Android.bp b/compiler/Android.bp
index d0b5192..c798d97 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -54,6 +54,7 @@
"optimizing/code_generator_utils.cc",
"optimizing/code_sinking.cc",
"optimizing/constant_folding.cc",
+ "optimizing/constructor_fence_redundancy_elimination.cc",
"optimizing/dead_code_elimination.cc",
"optimizing/escape.cc",
"optimizing/graph_checker.cc",
diff --git a/compiler/compiled_method.h b/compiler/compiled_method.h
index 97127f5..5ef6cbf 100644
--- a/compiler/compiled_method.h
+++ b/compiler/compiled_method.h
@@ -124,8 +124,10 @@
kCall,
kCallRelative, // NOTE: Actual patching is instruction_set-dependent.
kTypeRelative, // NOTE: Actual patching is instruction_set-dependent.
+ kTypeClassTable, // NOTE: Actual patching is instruction_set-dependent.
kTypeBssEntry, // NOTE: Actual patching is instruction_set-dependent.
kStringRelative, // NOTE: Actual patching is instruction_set-dependent.
+ kStringInternTable, // NOTE: Actual patching is instruction_set-dependent.
kStringBssEntry, // NOTE: Actual patching is instruction_set-dependent.
kBakerReadBarrierBranch, // NOTE: Actual patching is instruction_set-dependent.
};
@@ -176,6 +178,16 @@
return patch;
}
+ static LinkerPatch TypeClassTablePatch(size_t literal_offset,
+ const DexFile* target_dex_file,
+ uint32_t pc_insn_offset,
+ uint32_t target_type_idx) {
+ LinkerPatch patch(literal_offset, Type::kTypeClassTable, target_dex_file);
+ patch.type_idx_ = target_type_idx;
+ patch.pc_insn_offset_ = pc_insn_offset;
+ return patch;
+ }
+
static LinkerPatch TypeBssEntryPatch(size_t literal_offset,
const DexFile* target_dex_file,
uint32_t pc_insn_offset,
@@ -196,6 +208,16 @@
return patch;
}
+ static LinkerPatch StringInternTablePatch(size_t literal_offset,
+ const DexFile* target_dex_file,
+ uint32_t pc_insn_offset,
+ uint32_t target_string_idx) {
+ LinkerPatch patch(literal_offset, Type::kStringInternTable, target_dex_file);
+ patch.string_idx_ = target_string_idx;
+ patch.pc_insn_offset_ = pc_insn_offset;
+ return patch;
+ }
+
static LinkerPatch StringBssEntryPatch(size_t literal_offset,
const DexFile* target_dex_file,
uint32_t pc_insn_offset,
@@ -232,8 +254,10 @@
case Type::kMethodBssEntry:
case Type::kCallRelative:
case Type::kTypeRelative:
+ case Type::kTypeClassTable:
case Type::kTypeBssEntry:
case Type::kStringRelative:
+ case Type::kStringInternTable:
case Type::kStringBssEntry:
case Type::kBakerReadBarrierBranch:
return true;
@@ -252,24 +276,28 @@
const DexFile* TargetTypeDexFile() const {
DCHECK(patch_type_ == Type::kTypeRelative ||
+ patch_type_ == Type::kTypeClassTable ||
patch_type_ == Type::kTypeBssEntry);
return target_dex_file_;
}
dex::TypeIndex TargetTypeIndex() const {
DCHECK(patch_type_ == Type::kTypeRelative ||
+ patch_type_ == Type::kTypeClassTable ||
patch_type_ == Type::kTypeBssEntry);
return dex::TypeIndex(type_idx_);
}
const DexFile* TargetStringDexFile() const {
DCHECK(patch_type_ == Type::kStringRelative ||
+ patch_type_ == Type::kStringInternTable ||
patch_type_ == Type::kStringBssEntry);
return target_dex_file_;
}
dex::StringIndex TargetStringIndex() const {
DCHECK(patch_type_ == Type::kStringRelative ||
+ patch_type_ == Type::kStringInternTable ||
patch_type_ == Type::kStringBssEntry);
return dex::StringIndex(string_idx_);
}
@@ -278,8 +306,10 @@
DCHECK(patch_type_ == Type::kMethodRelative ||
patch_type_ == Type::kMethodBssEntry ||
patch_type_ == Type::kTypeRelative ||
+ patch_type_ == Type::kTypeClassTable ||
patch_type_ == Type::kTypeBssEntry ||
patch_type_ == Type::kStringRelative ||
+ patch_type_ == Type::kStringInternTable ||
patch_type_ == Type::kStringBssEntry);
return pc_insn_offset_;
}
diff --git a/compiler/image_test.cc b/compiler/image_test.cc
index 252fdd6..7b623dd 100644
--- a/compiler/image_test.cc
+++ b/compiler/image_test.cc
@@ -46,7 +46,7 @@
// Make sure that the new stuff in the clinit in ImageLayoutB is in the last image and not in the
// first two images.
ASSERT_EQ(image_sizes.size(), image_sizes.size());
- // Sizes of the images should be the same. These sizes are for the whole image unrounded.
+ // Sizes of the object sections should be the same for all but the last image.
for (size_t i = 0; i < image_sizes.size() - 1; ++i) {
EXPECT_EQ(image_sizes[i], image_sizes_extra[i]);
}
diff --git a/compiler/image_test.h b/compiler/image_test.h
index 15f79cb..f1adedd 100644
--- a/compiler/image_test.h
+++ b/compiler/image_test.h
@@ -133,7 +133,7 @@
ImageHeader image_header;
CHECK_EQ(file->ReadFully(&image_header, sizeof(image_header)), true);
CHECK(image_header.IsValid());
- ret.push_back(image_header.GetImageSize());
+ ret.push_back(image_header.GetObjectsSection().Size());
}
return ret;
}
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index 12f477a..4ffe238 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -73,6 +73,7 @@
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
#include "utils/dex_cache_arrays_layout-inl.h"
+#include "well_known_classes.h"
using ::art::mirror::Class;
using ::art::mirror::DexCache;
@@ -689,7 +690,7 @@
for (ImageInfo& image_info : image_infos_) {
ImageSection unused_sections[ImageHeader::kSectionCount];
const size_t length = RoundUp(
- image_info.CreateImageSections(unused_sections), kPageSize);
+ image_info.CreateImageSections(unused_sections, compile_app_image_), kPageSize);
std::string error_msg;
image_info.image_.reset(MemMap::MapAnonymous("image writer image",
@@ -1834,7 +1835,8 @@
image_info.image_begin_ = global_image_begin_ + image_offset;
image_info.image_offset_ = image_offset;
ImageSection unused_sections[ImageHeader::kSectionCount];
- image_info.image_size_ = RoundUp(image_info.CreateImageSections(unused_sections), kPageSize);
+ image_info.image_size_ =
+ RoundUp(image_info.CreateImageSections(unused_sections, compile_app_image_), kPageSize);
// There should be no gaps until the next image.
image_offset += image_info.image_size_;
}
@@ -1865,7 +1867,8 @@
}
}
-size_t ImageWriter::ImageInfo::CreateImageSections(ImageSection* out_sections) const {
+size_t ImageWriter::ImageInfo::CreateImageSections(ImageSection* out_sections,
+ bool app_image) const {
DCHECK(out_sections != nullptr);
// Do not round up any sections here that are represented by the bins since it will break
@@ -1904,8 +1907,13 @@
ImageSection* dex_cache_arrays_section = &out_sections[ImageHeader::kSectionDexCacheArrays];
*dex_cache_arrays_section = ImageSection(bin_slot_offsets_[kBinDexCacheArray],
bin_slot_sizes_[kBinDexCacheArray]);
- // Round up to the alignment the string table expects. See HashSet::WriteToMemory.
- size_t cur_pos = RoundUp(dex_cache_arrays_section->End(), sizeof(uint64_t));
+ // For boot image, round up to the page boundary to separate the interned strings and
+ // class table from the modifiable data. We shall mprotect() these pages read-only when
+ // we load the boot image. This is more than sufficient for the string table alignment,
+ // namely sizeof(uint64_t). See HashSet::WriteToMemory.
+ static_assert(IsAligned<sizeof(uint64_t)>(kPageSize), "String table alignment check.");
+ size_t cur_pos =
+ RoundUp(dex_cache_arrays_section->End(), app_image ? sizeof(uint64_t) : kPageSize);
// Calculate the size of the interned strings.
ImageSection* interned_strings_section = &out_sections[ImageHeader::kSectionInternedStrings];
*interned_strings_section = ImageSection(cur_pos, intern_table_bytes_);
@@ -1928,7 +1936,7 @@
// Create the image sections.
ImageSection sections[ImageHeader::kSectionCount];
- const size_t image_end = image_info.CreateImageSections(sections);
+ const size_t image_end = image_info.CreateImageSections(sections, compile_app_image_);
// Finally bitmap section.
const size_t bitmap_bytes = image_info.image_bitmap_->Size();
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index 866e204..2fc394e 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -258,7 +258,7 @@
// Create the image sections into the out sections variable, returns the size of the image
// excluding the bitmap.
- size_t CreateImageSections(ImageSection* out_sections) const;
+ size_t CreateImageSections(ImageSection* out_sections, bool app_image) const;
std::unique_ptr<MemMap> image_; // Memory mapped for generating the image.
diff --git a/compiler/linker/arm64/relative_patcher_arm64.cc b/compiler/linker/arm64/relative_patcher_arm64.cc
index db829f3..4960f4d 100644
--- a/compiler/linker/arm64/relative_patcher_arm64.cc
+++ b/compiler/linker/arm64/relative_patcher_arm64.cc
@@ -61,8 +61,10 @@
case LinkerPatch::Type::kMethodRelative:
case LinkerPatch::Type::kMethodBssEntry:
case LinkerPatch::Type::kTypeRelative:
+ case LinkerPatch::Type::kTypeClassTable:
case LinkerPatch::Type::kTypeBssEntry:
case LinkerPatch::Type::kStringRelative:
+ case LinkerPatch::Type::kStringInternTable:
case LinkerPatch::Type::kStringBssEntry:
return patch.LiteralOffset() == patch.PcInsnOffset();
}
@@ -265,7 +267,9 @@
} else {
// LDR/STR 32-bit or 64-bit with imm12 == 0 (unset).
DCHECK(patch.GetType() == LinkerPatch::Type::kMethodBssEntry ||
+ patch.GetType() == LinkerPatch::Type::kTypeClassTable ||
patch.GetType() == LinkerPatch::Type::kTypeBssEntry ||
+ patch.GetType() == LinkerPatch::Type::kStringInternTable ||
patch.GetType() == LinkerPatch::Type::kStringBssEntry) << patch.GetType();
DCHECK_EQ(insn & 0xbfbffc00, 0xb9000000) << std::hex << insn;
}
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 6fbb2bd..cc8c6df 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -28,6 +28,7 @@
#include "base/stl_util.h"
#include "base/unix_file/fd_file.h"
#include "class_linker.h"
+#include "class_table-inl.h"
#include "compiled_method.h"
#include "debug/method_debug_info.h"
#include "dex/verification_results.h"
@@ -335,6 +336,7 @@
bss_method_entries_(),
bss_type_entries_(),
bss_string_entries_(),
+ map_boot_image_tables_to_bss_(false),
oat_data_offset_(0u),
oat_header_(nullptr),
size_vdex_header_(0),
@@ -771,6 +773,9 @@
} else if (patch.GetType() == LinkerPatch::Type::kStringBssEntry) {
StringReference ref(patch.TargetStringDexFile(), patch.TargetStringIndex());
writer_->bss_string_entries_.Overwrite(ref, /* placeholder */ 0u);
+ } else if (patch.GetType() == LinkerPatch::Type::kStringInternTable ||
+ patch.GetType() == LinkerPatch::Type::kTypeClassTable) {
+ writer_->map_boot_image_tables_to_bss_ = true;
}
}
} else {
@@ -1398,6 +1403,14 @@
target_offset);
break;
}
+ case LinkerPatch::Type::kStringInternTable: {
+ uint32_t target_offset = GetInternTableEntryOffset(patch);
+ writer_->relative_patcher_->PatchPcRelativeReference(&patched_code_,
+ patch,
+ offset_ + literal_offset,
+ target_offset);
+ break;
+ }
case LinkerPatch::Type::kStringBssEntry: {
StringReference ref(patch.TargetStringDexFile(), patch.TargetStringIndex());
uint32_t target_offset =
@@ -1416,6 +1429,14 @@
target_offset);
break;
}
+ case LinkerPatch::Type::kTypeClassTable: {
+ uint32_t target_offset = GetClassTableEntryOffset(patch);
+ writer_->relative_patcher_->PatchPcRelativeReference(&patched_code_,
+ patch,
+ offset_ + literal_offset,
+ target_offset);
+ break;
+ }
case LinkerPatch::Type::kTypeBssEntry: {
TypeReference ref(patch.TargetTypeDexFile(), patch.TargetTypeIndex());
uint32_t target_offset = writer_->bss_start_ + writer_->bss_type_entries_.Get(ref);
@@ -1535,7 +1556,6 @@
}
mirror::String* GetTargetString(const LinkerPatch& patch) REQUIRES_SHARED(Locks::mutator_lock_) {
- ScopedObjectAccessUnchecked soa(Thread::Current());
ClassLinker* linker = Runtime::Current()->GetClassLinker();
mirror::String* string = linker->LookupString(*patch.TargetStringDexFile(),
patch.TargetStringIndex(),
@@ -1603,6 +1623,42 @@
data[2] = (address >> 16) & 0xffu;
data[3] = (address >> 24) & 0xffu;
}
+
+ // Calculate the offset of the InternTable slot (GcRoot<String>) when mmapped to the .bss.
+ uint32_t GetInternTableEntryOffset(const LinkerPatch& patch)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!writer_->HasBootImage());
+ const uint8_t* string_root = writer_->LookupBootImageInternTableSlot(
+ *patch.TargetStringDexFile(), patch.TargetStringIndex());
+ DCHECK(string_root != nullptr);
+ return GetBootImageTableEntryOffset(string_root);
+ }
+
+ // Calculate the offset of the ClassTable::TableSlot when mmapped to the .bss.
+ uint32_t GetClassTableEntryOffset(const LinkerPatch& patch)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!writer_->HasBootImage());
+ const uint8_t* table_slot =
+ writer_->LookupBootImageClassTableSlot(*patch.TargetTypeDexFile(), patch.TargetTypeIndex());
+ DCHECK(table_slot != nullptr);
+ return GetBootImageTableEntryOffset(table_slot);
+ }
+
+ uint32_t GetBootImageTableEntryOffset(const uint8_t* raw_root) {
+ uint32_t base_offset = writer_->bss_start_;
+ for (gc::space::ImageSpace* space : Runtime::Current()->GetHeap()->GetBootImageSpaces()) {
+ const uint8_t* const_tables_begin =
+ space->Begin() + space->GetImageHeader().GetBootImageConstantTablesOffset();
+ size_t offset = static_cast<size_t>(raw_root - const_tables_begin);
+ if (offset < space->GetImageHeader().GetBootImageConstantTablesSize()) {
+ DCHECK_LE(base_offset + offset, writer_->bss_start_ + writer_->bss_methods_offset_);
+ return base_offset + offset;
+ }
+ base_offset += space->GetImageHeader().GetBootImageConstantTablesSize();
+ }
+ LOG(FATAL) << "Didn't find boot image string in boot image intern tables!";
+ UNREACHABLE();
+ }
};
class OatWriter::WriteMapMethodVisitor : public OatDexMethodVisitor {
@@ -1942,19 +1998,22 @@
DCHECK_EQ(bss_size_, 0u);
if (HasBootImage()) {
+ DCHECK(!map_boot_image_tables_to_bss_);
DCHECK(bss_string_entries_.empty());
- if (bss_method_entries_.empty() && bss_type_entries_.empty()) {
- // Nothing to put to the .bss section.
- return;
- }
+ }
+ if (!map_boot_image_tables_to_bss_ &&
+ bss_method_entries_.empty() &&
+ bss_type_entries_.empty() &&
+ bss_string_entries_.empty()) {
+ // Nothing to put to the .bss section.
+ return;
}
- // Allocate space for app dex cache arrays in the .bss section.
+ // Allocate space for boot image tables in the .bss section.
PointerSize pointer_size = GetInstructionSetPointerSize(instruction_set);
- if (!HasBootImage()) {
- for (const DexFile* dex_file : *dex_files_) {
- DexCacheArraysLayout layout(pointer_size, dex_file);
- bss_size_ += layout.Size();
+ if (map_boot_image_tables_to_bss_) {
+ for (gc::space::ImageSpace* space : Runtime::Current()->GetHeap()->GetBootImageSpaces()) {
+ bss_size_ += space->GetImageHeader().GetBootImageConstantTablesSize();
}
}
@@ -3505,4 +3564,40 @@
return true;
}
+const uint8_t* OatWriter::LookupBootImageInternTableSlot(const DexFile& dex_file,
+ dex::StringIndex string_idx)
+ NO_THREAD_SAFETY_ANALYSIS { // Single-threaded OatWriter can avoid locking.
+ uint32_t utf16_length;
+ const char* utf8_data = dex_file.StringDataAndUtf16LengthByIdx(string_idx, &utf16_length);
+ DCHECK_EQ(utf16_length, CountModifiedUtf8Chars(utf8_data));
+ InternTable::Utf8String string(utf16_length,
+ utf8_data,
+ ComputeUtf16HashFromModifiedUtf8(utf8_data, utf16_length));
+ const InternTable* intern_table = Runtime::Current()->GetClassLinker()->intern_table_;
+ for (const InternTable::Table::UnorderedSet& table : intern_table->strong_interns_.tables_) {
+ auto it = table.Find(string);
+ if (it != table.end()) {
+ return reinterpret_cast<const uint8_t*>(std::addressof(*it));
+ }
+ }
+ LOG(FATAL) << "Did not find boot image string " << utf8_data;
+ UNREACHABLE();
+}
+
+const uint8_t* OatWriter::LookupBootImageClassTableSlot(const DexFile& dex_file,
+ dex::TypeIndex type_idx)
+ NO_THREAD_SAFETY_ANALYSIS { // Single-threaded OatWriter can avoid locking.
+ const char* descriptor = dex_file.StringByTypeIdx(type_idx);
+ ClassTable::DescriptorHashPair pair(descriptor, ComputeModifiedUtf8Hash(descriptor));
+ ClassTable* table = Runtime::Current()->GetClassLinker()->boot_class_table_.get();
+ for (const ClassTable::ClassSet& class_set : table->classes_) {
+ auto it = class_set.Find(pair);
+ if (it != class_set.end()) {
+ return reinterpret_cast<const uint8_t*>(std::addressof(*it));
+ }
+ }
+ LOG(FATAL) << "Did not find boot image class " << descriptor;
+ UNREACHABLE();
+}
+
} // namespace art
diff --git a/compiler/oat_writer.h b/compiler/oat_writer.h
index 8db00f7..7f2045f 100644
--- a/compiler/oat_writer.h
+++ b/compiler/oat_writer.h
@@ -333,6 +333,12 @@
bool MayHaveCompiledMethods() const;
+ // Find the address of the GcRoot<String> in the InternTable for a boot image string.
+ const uint8_t* LookupBootImageInternTableSlot(const DexFile& dex_file,
+ dex::StringIndex string_idx);
+ // Find the address of the ClassTable::TableSlot for a boot image class.
+ const uint8_t* LookupBootImageClassTableSlot(const DexFile& dex_file, dex::TypeIndex type_idx);
+
enum class WriteState {
kAddingDexFileSources,
kPrepareLayout,
@@ -407,6 +413,10 @@
// is the target offset for patching, starting at `bss_start_ + bss_roots_offset_`.
SafeMap<StringReference, size_t, StringReferenceValueComparator> bss_string_entries_;
+ // Whether boot image tables should be mapped to the .bss. This is needed for compiled
+ // code that reads from these tables with PC-relative instructions.
+ bool map_boot_image_tables_to_bss_;
+
// Offset of the oat data from the start of the mmapped region of the elf file.
size_t oat_data_offset_;
diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc
index 2f96cfa..a170734 100644
--- a/compiler/optimizing/bounds_check_elimination.cc
+++ b/compiler/optimizing/bounds_check_elimination.cc
@@ -596,6 +596,7 @@
// Helper method to assign a new range to an instruction in given basic block.
void AssignRange(HBasicBlock* basic_block, HInstruction* instruction, ValueRange* range) {
+ DCHECK(!range->IsMonotonicValueRange() || instruction->IsLoopHeaderPhi());
GetValueRangeMap(basic_block)->Overwrite(instruction->GetId(), range);
}
@@ -1143,9 +1144,9 @@
ValueBound(nullptr, 1 - right_const),
ValueBound(nullptr, right_const - 1));
- ValueRange* left_range = LookupValueRange(left, left->GetBlock());
+ ValueRange* left_range = LookupValueRange(left, instruction->GetBlock());
if (left_range != nullptr) {
- right_range = left_range->Narrow(right_range);
+ right_range = right_range->Narrow(left_range);
}
AssignRange(instruction->GetBlock(), instruction, right_range);
return;
@@ -1172,9 +1173,9 @@
GetGraph()->GetArena(),
lower,
upper);
- ValueRange* left_range = LookupValueRange(left, left->GetBlock());
+ ValueRange* left_range = LookupValueRange(left, instruction->GetBlock());
if (left_range != nullptr) {
- right_range = left_range->Narrow(right_range);
+ right_range = right_range->Narrow(left_range);
}
AssignRange(instruction->GetBlock(), instruction, right_range);
return;
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 3be774a..19e5d06 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -21,6 +21,7 @@
#include "art_method.h"
#include "base/bit_utils.h"
#include "base/bit_utils_iterator.h"
+#include "class_table.h"
#include "code_generator_utils.h"
#include "compiled_method.h"
#include "entrypoints/quick/quick_entrypoints.h"
@@ -435,11 +436,11 @@
// The string entry page address was preserved in temp_ thanks to kSaveEverything.
} else {
// For non-Baker read barrier, we need to re-calculate the address of the string entry page.
- adrp_label_ = arm64_codegen->NewPcRelativeStringPatch(dex_file, string_index);
+ adrp_label_ = arm64_codegen->NewStringBssEntryPatch(dex_file, string_index);
arm64_codegen->EmitAdrpPlaceholder(adrp_label_, temp_);
}
vixl::aarch64::Label* strp_label =
- arm64_codegen->NewPcRelativeStringPatch(dex_file, string_index, adrp_label_);
+ arm64_codegen->NewStringBssEntryPatch(dex_file, string_index, adrp_label_);
{
SingleEmissionCheckScope guard(arm64_codegen->GetVIXLAssembler());
__ Bind(strp_label);
@@ -1463,6 +1464,7 @@
pc_relative_type_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
type_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
pc_relative_string_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
+ string_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
baker_read_barrier_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
jit_string_patches_(StringReferenceValueComparator(),
graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
@@ -4675,6 +4677,13 @@
NewPcRelativePatch(dex_file, string_index.index_, adrp_label, &pc_relative_string_patches_);
}
+vixl::aarch64::Label* CodeGeneratorARM64::NewStringBssEntryPatch(
+ const DexFile& dex_file,
+ dex::StringIndex string_index,
+ vixl::aarch64::Label* adrp_label) {
+ return NewPcRelativePatch(dex_file, string_index.index_, adrp_label, &string_bss_entry_patches_);
+}
+
vixl::aarch64::Label* CodeGeneratorARM64::NewBakerReadBarrierPatch(uint32_t custom_data) {
baker_read_barrier_patches_.emplace_back(custom_data);
return &baker_read_barrier_patches_.back().label;
@@ -4764,6 +4773,7 @@
pc_relative_type_patches_.size() +
type_bss_entry_patches_.size() +
pc_relative_string_patches_.size() +
+ string_bss_entry_patches_.size() +
baker_read_barrier_patches_.size();
linker_patches->reserve(size);
if (GetCompilerOptions().IsBootImage()) {
@@ -4775,14 +4785,17 @@
linker_patches);
} else {
DCHECK(pc_relative_method_patches_.empty());
- DCHECK(pc_relative_type_patches_.empty());
- EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(pc_relative_string_patches_,
+ EmitPcRelativeLinkerPatches<LinkerPatch::TypeClassTablePatch>(pc_relative_type_patches_,
linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringInternTablePatch>(pc_relative_string_patches_,
+ linker_patches);
}
EmitPcRelativeLinkerPatches<LinkerPatch::MethodBssEntryPatch>(method_bss_entry_patches_,
linker_patches);
EmitPcRelativeLinkerPatches<LinkerPatch::TypeBssEntryPatch>(type_bss_entry_patches_,
linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(string_bss_entry_patches_,
+ linker_patches);
for (const BakerReadBarrierPatchInfo& info : baker_read_barrier_patches_) {
linker_patches->push_back(LinkerPatch::BakerReadBarrierBranchPatch(info.label.GetLocation(),
info.custom_data));
@@ -4850,6 +4863,7 @@
case HLoadClass::LoadKind::kReferrersClass:
break;
case HLoadClass::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadClass::LoadKind::kBootImageClassTable:
case HLoadClass::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -4961,6 +4975,25 @@
__ Ldr(out.W(), codegen_->DeduplicateBootImageAddressLiteral(address));
break;
}
+ case HLoadClass::LoadKind::kBootImageClassTable: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ // Add ADRP with its PC-relative type patch.
+ const DexFile& dex_file = cls->GetDexFile();
+ dex::TypeIndex type_index = cls->GetTypeIndex();
+ vixl::aarch64::Label* adrp_label = codegen_->NewPcRelativeTypePatch(dex_file, type_index);
+ codegen_->EmitAdrpPlaceholder(adrp_label, out.X());
+ // Add LDR with its PC-relative type patch.
+ vixl::aarch64::Label* ldr_label =
+ codegen_->NewPcRelativeTypePatch(dex_file, type_index, adrp_label);
+ codegen_->EmitLdrOffsetPlaceholder(ldr_label, out.W(), out.X());
+ // Extract the reference from the slot data, i.e. clear the hash bits.
+ int32_t masked_hash = ClassTable::TableSlot::MaskHash(
+ ComputeModifiedUtf8Hash(dex_file.StringByTypeIdx(type_index)));
+ if (masked_hash != 0) {
+ __ Sub(out.W(), out.W(), Operand(masked_hash));
+ }
+ break;
+ }
case HLoadClass::LoadKind::kBssEntry: {
// Add ADRP with its PC-relative Class .bss entry patch.
const DexFile& dex_file = cls->GetDexFile();
@@ -5043,6 +5076,7 @@
HLoadString::LoadKind desired_string_load_kind) {
switch (desired_string_load_kind) {
case HLoadString::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadString::LoadKind::kBootImageInternTable:
case HLoadString::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -5090,24 +5124,37 @@
switch (load->GetLoadKind()) {
case HLoadString::LoadKind::kBootImageLinkTimePcRelative: {
+ DCHECK(codegen_->GetCompilerOptions().IsBootImage());
// Add ADRP with its PC-relative String patch.
const DexFile& dex_file = load->GetDexFile();
const dex::StringIndex string_index = load->GetStringIndex();
- DCHECK(codegen_->GetCompilerOptions().IsBootImage());
vixl::aarch64::Label* adrp_label = codegen_->NewPcRelativeStringPatch(dex_file, string_index);
codegen_->EmitAdrpPlaceholder(adrp_label, out.X());
// Add ADD with its PC-relative String patch.
vixl::aarch64::Label* add_label =
codegen_->NewPcRelativeStringPatch(dex_file, string_index, adrp_label);
codegen_->EmitAddPlaceholder(add_label, out.X(), out.X());
- return; // No dex cache slow path.
+ return;
}
case HLoadString::LoadKind::kBootImageAddress: {
uint32_t address = dchecked_integral_cast<uint32_t>(
reinterpret_cast<uintptr_t>(load->GetString().Get()));
DCHECK_NE(address, 0u);
__ Ldr(out.W(), codegen_->DeduplicateBootImageAddressLiteral(address));
- return; // No dex cache slow path.
+ return;
+ }
+ case HLoadString::LoadKind::kBootImageInternTable: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ // Add ADRP with its PC-relative String patch.
+ const DexFile& dex_file = load->GetDexFile();
+ const dex::StringIndex string_index = load->GetStringIndex();
+ vixl::aarch64::Label* adrp_label = codegen_->NewPcRelativeStringPatch(dex_file, string_index);
+ codegen_->EmitAdrpPlaceholder(adrp_label, out.X());
+ // Add LDR with its PC-relative String patch.
+ vixl::aarch64::Label* ldr_label =
+ codegen_->NewPcRelativeStringPatch(dex_file, string_index, adrp_label);
+ codegen_->EmitLdrOffsetPlaceholder(ldr_label, out.W(), out.X());
+ return;
}
case HLoadString::LoadKind::kBssEntry: {
// Add ADRP with its PC-relative String .bss entry patch.
@@ -5115,11 +5162,11 @@
const dex::StringIndex string_index = load->GetStringIndex();
DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
Register temp = XRegisterFrom(load->GetLocations()->GetTemp(0));
- vixl::aarch64::Label* adrp_label = codegen_->NewPcRelativeStringPatch(dex_file, string_index);
+ vixl::aarch64::Label* adrp_label = codegen_->NewStringBssEntryPatch(dex_file, string_index);
codegen_->EmitAdrpPlaceholder(adrp_label, temp);
- // Add LDR with its PC-relative String patch.
+ // Add LDR with its .bss entry String patch.
vixl::aarch64::Label* ldr_label =
- codegen_->NewPcRelativeStringPatch(dex_file, string_index, adrp_label);
+ codegen_->NewStringBssEntryPatch(dex_file, string_index, adrp_label);
// /* GcRoot<mirror::String> */ out = *(base_address + offset) /* PC-relative */
GenerateGcRootFieldLoad(load,
out_loc,
diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h
index c339209..69c5119 100644
--- a/compiler/optimizing/code_generator_arm64.h
+++ b/compiler/optimizing/code_generator_arm64.h
@@ -599,6 +599,14 @@
dex::StringIndex string_index,
vixl::aarch64::Label* adrp_label = nullptr);
+ // Add a new .bss entry string patch for an instruction and return the label
+ // to be bound before the instruction. The instruction will be either the
+ // ADRP (pass `adrp_label = null`) or the ADD (pass `adrp_label` pointing
+ // to the associated ADRP patch label).
+ vixl::aarch64::Label* NewStringBssEntryPatch(const DexFile& dex_file,
+ dex::StringIndex string_index,
+ vixl::aarch64::Label* adrp_label = nullptr);
+
// Add a new baker read barrier patch and return the label to be bound
// before the CBNZ instruction.
vixl::aarch64::Label* NewBakerReadBarrierPatch(uint32_t custom_data);
@@ -825,8 +833,10 @@
ArenaDeque<PcRelativePatchInfo> pc_relative_type_patches_;
// PC-relative type patch info for kBssEntry.
ArenaDeque<PcRelativePatchInfo> type_bss_entry_patches_;
- // PC-relative String patch info; type depends on configuration (app .bss or boot image PIC).
+ // PC-relative String patch info; type depends on configuration (intern table or boot image PIC).
ArenaDeque<PcRelativePatchInfo> pc_relative_string_patches_;
+ // PC-relative String patch info for kBssEntry.
+ ArenaDeque<PcRelativePatchInfo> string_bss_entry_patches_;
// Baker read barrier patch info.
ArenaDeque<BakerReadBarrierPatchInfo> baker_read_barrier_patches_;
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index d78756e..8b9495d 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -21,6 +21,7 @@
#include "art_method.h"
#include "base/bit_utils.h"
#include "base/bit_utils_iterator.h"
+#include "class_table.h"
#include "code_generator_utils.h"
#include "common_arm.h"
#include "compiled_method.h"
@@ -598,7 +599,7 @@
down_cast<CodeGeneratorARMVIXL*>(codegen)->GetVIXLAssembler());
vixl32::Register temp = temps.Acquire();
CodeGeneratorARMVIXL::PcRelativePatchInfo* labels =
- arm_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index);
+ arm_codegen->NewStringBssEntryPatch(load->GetDexFile(), string_index);
arm_codegen->EmitMovwMovtPlaceholder(labels, temp);
__ Str(r0, MemOperand(temp));
}
@@ -2380,6 +2381,7 @@
pc_relative_type_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
type_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
pc_relative_string_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
+ string_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
baker_read_barrier_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
jit_string_patches_(StringReferenceValueComparator(),
graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
@@ -7121,6 +7123,7 @@
case HLoadClass::LoadKind::kReferrersClass:
break;
case HLoadClass::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadClass::LoadKind::kBootImageClassTable:
case HLoadClass::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -7233,6 +7236,20 @@
__ Ldr(out, codegen_->DeduplicateBootImageAddressLiteral(address));
break;
}
+ case HLoadClass::LoadKind::kBootImageClassTable: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ CodeGeneratorARMVIXL::PcRelativePatchInfo* labels =
+ codegen_->NewPcRelativeTypePatch(cls->GetDexFile(), cls->GetTypeIndex());
+ codegen_->EmitMovwMovtPlaceholder(labels, out);
+ __ Ldr(out, MemOperand(out, /* offset */ 0));
+ // Extract the reference from the slot data, i.e. clear the hash bits.
+ int32_t masked_hash = ClassTable::TableSlot::MaskHash(
+ ComputeModifiedUtf8Hash(cls->GetDexFile().StringByTypeIdx(cls->GetTypeIndex())));
+ if (masked_hash != 0) {
+ __ Sub(out, out, Operand(masked_hash));
+ }
+ break;
+ }
case HLoadClass::LoadKind::kBssEntry: {
vixl32::Register temp = (!kUseReadBarrier || kUseBakerReadBarrier)
? RegisterFrom(locations->GetTemp(0))
@@ -7315,6 +7332,7 @@
HLoadString::LoadKind desired_string_load_kind) {
switch (desired_string_load_kind) {
case HLoadString::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadString::LoadKind::kBootImageInternTable:
case HLoadString::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -7372,14 +7390,22 @@
CodeGeneratorARMVIXL::PcRelativePatchInfo* labels =
codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex());
codegen_->EmitMovwMovtPlaceholder(labels, out);
- return; // No dex cache slow path.
+ return;
}
case HLoadString::LoadKind::kBootImageAddress: {
uint32_t address = dchecked_integral_cast<uint32_t>(
reinterpret_cast<uintptr_t>(load->GetString().Get()));
DCHECK_NE(address, 0u);
__ Ldr(out, codegen_->DeduplicateBootImageAddressLiteral(address));
- return; // No dex cache slow path.
+ return;
+ }
+ case HLoadString::LoadKind::kBootImageInternTable: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ CodeGeneratorARMVIXL::PcRelativePatchInfo* labels =
+ codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex());
+ codegen_->EmitMovwMovtPlaceholder(labels, out);
+ __ Ldr(out, MemOperand(out, /* offset */ 0));
+ return;
}
case HLoadString::LoadKind::kBssEntry: {
DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
@@ -7387,7 +7413,7 @@
? RegisterFrom(locations->GetTemp(0))
: out;
CodeGeneratorARMVIXL::PcRelativePatchInfo* labels =
- codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex());
+ codegen_->NewStringBssEntryPatch(load->GetDexFile(), load->GetStringIndex());
codegen_->EmitMovwMovtPlaceholder(labels, temp);
GenerateGcRootFieldLoad(load, out_loc, temp, /* offset */ 0, kCompilerReadBarrierOption);
LoadStringSlowPathARMVIXL* slow_path =
@@ -9119,6 +9145,11 @@
return NewPcRelativePatch(dex_file, string_index.index_, &pc_relative_string_patches_);
}
+CodeGeneratorARMVIXL::PcRelativePatchInfo* CodeGeneratorARMVIXL::NewStringBssEntryPatch(
+ const DexFile& dex_file, dex::StringIndex string_index) {
+ return NewPcRelativePatch(dex_file, string_index.index_, &string_bss_entry_patches_);
+}
+
CodeGeneratorARMVIXL::PcRelativePatchInfo* CodeGeneratorARMVIXL::NewPcRelativePatch(
const DexFile& dex_file, uint32_t offset_or_index, ArenaDeque<PcRelativePatchInfo>* patches) {
patches->emplace_back(dex_file, offset_or_index);
@@ -9187,6 +9218,7 @@
/* MOVW+MOVT for each entry */ 2u * pc_relative_type_patches_.size() +
/* MOVW+MOVT for each entry */ 2u * type_bss_entry_patches_.size() +
/* MOVW+MOVT for each entry */ 2u * pc_relative_string_patches_.size() +
+ /* MOVW+MOVT for each entry */ 2u * string_bss_entry_patches_.size() +
baker_read_barrier_patches_.size();
linker_patches->reserve(size);
if (GetCompilerOptions().IsBootImage()) {
@@ -9198,14 +9230,17 @@
linker_patches);
} else {
DCHECK(pc_relative_method_patches_.empty());
- DCHECK(pc_relative_type_patches_.empty());
- EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(pc_relative_string_patches_,
+ EmitPcRelativeLinkerPatches<LinkerPatch::TypeClassTablePatch>(pc_relative_type_patches_,
linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringInternTablePatch>(pc_relative_string_patches_,
+ linker_patches);
}
EmitPcRelativeLinkerPatches<LinkerPatch::MethodBssEntryPatch>(method_bss_entry_patches_,
linker_patches);
EmitPcRelativeLinkerPatches<LinkerPatch::TypeBssEntryPatch>(type_bss_entry_patches_,
linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(string_bss_entry_patches_,
+ linker_patches);
for (const BakerReadBarrierPatchInfo& info : baker_read_barrier_patches_) {
linker_patches->push_back(LinkerPatch::BakerReadBarrierBranchPatch(info.label.GetLocation(),
info.custom_data));
diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h
index 5feb33b..e78bc15 100644
--- a/compiler/optimizing/code_generator_arm_vixl.h
+++ b/compiler/optimizing/code_generator_arm_vixl.h
@@ -579,6 +579,8 @@
PcRelativePatchInfo* NewTypeBssEntryPatch(const DexFile& dex_file, dex::TypeIndex type_index);
PcRelativePatchInfo* NewPcRelativeStringPatch(const DexFile& dex_file,
dex::StringIndex string_index);
+ PcRelativePatchInfo* NewStringBssEntryPatch(const DexFile& dex_file,
+ dex::StringIndex string_index);
// Add a new baker read barrier patch and return the label to be bound
// before the BNE instruction.
@@ -803,8 +805,10 @@
ArenaDeque<PcRelativePatchInfo> pc_relative_type_patches_;
// PC-relative type patch info for kBssEntry.
ArenaDeque<PcRelativePatchInfo> type_bss_entry_patches_;
- // PC-relative String patch info; type depends on configuration (app .bss or boot image PIC).
+ // PC-relative String patch info; type depends on configuration (intern table or boot image PIC).
ArenaDeque<PcRelativePatchInfo> pc_relative_string_patches_;
+ // PC-relative String patch info for kBssEntry.
+ ArenaDeque<PcRelativePatchInfo> string_bss_entry_patches_;
// Baker read barrier patch info.
ArenaDeque<BakerReadBarrierPatchInfo> baker_read_barrier_patches_;
diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc
index 51f5b96..f0ef007 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -20,6 +20,7 @@
#include "arch/mips/entrypoints_direct_mips.h"
#include "arch/mips/instruction_set_features_mips.h"
#include "art_method.h"
+#include "class_table.h"
#include "code_generator_utils.h"
#include "compiled_method.h"
#include "entrypoints/quick/quick_entrypoints.h"
@@ -360,7 +361,7 @@
// The string entry address was preserved in `entry_address` thanks to kSaveEverything.
DCHECK(bss_info_high_);
CodeGeneratorMIPS::PcRelativePatchInfo* info_low =
- mips_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index, bss_info_high_);
+ mips_codegen->NewStringBssEntryPatch(load->GetDexFile(), string_index, bss_info_high_);
__ Sw(calling_convention.GetRegisterAt(0),
entry_address,
/* placeholder */ 0x5678,
@@ -380,9 +381,9 @@
const bool isR6 = mips_codegen->GetInstructionSetFeatures().IsR6();
Register base = isR6 ? ZERO : locations->InAt(0).AsRegister<Register>();
CodeGeneratorMIPS::PcRelativePatchInfo* info_high =
- mips_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index);
+ mips_codegen->NewStringBssEntryPatch(load->GetDexFile(), string_index);
CodeGeneratorMIPS::PcRelativePatchInfo* info_low =
- mips_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index, info_high);
+ mips_codegen->NewStringBssEntryPatch(load->GetDexFile(), string_index, info_high);
mips_codegen->EmitPcRelativeAddressPlaceholderHigh(info_high, TMP, base);
__ Sw(out, TMP, /* placeholder */ 0x5678, &info_low->label);
}
@@ -1101,6 +1102,7 @@
pc_relative_type_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
type_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
pc_relative_string_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
+ string_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
jit_string_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
jit_class_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
clobbered_ra_(false) {
@@ -1651,7 +1653,8 @@
method_bss_entry_patches_.size() +
pc_relative_type_patches_.size() +
type_bss_entry_patches_.size() +
- pc_relative_string_patches_.size();
+ pc_relative_string_patches_.size() +
+ string_bss_entry_patches_.size();
linker_patches->reserve(size);
if (GetCompilerOptions().IsBootImage()) {
EmitPcRelativeLinkerPatches<LinkerPatch::RelativeMethodPatch>(pc_relative_method_patches_,
@@ -1662,14 +1665,17 @@
linker_patches);
} else {
DCHECK(pc_relative_method_patches_.empty());
- DCHECK(pc_relative_type_patches_.empty());
- EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(pc_relative_string_patches_,
+ EmitPcRelativeLinkerPatches<LinkerPatch::TypeClassTablePatch>(pc_relative_type_patches_,
linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringInternTablePatch>(pc_relative_string_patches_,
+ linker_patches);
}
EmitPcRelativeLinkerPatches<LinkerPatch::MethodBssEntryPatch>(method_bss_entry_patches_,
linker_patches);
EmitPcRelativeLinkerPatches<LinkerPatch::TypeBssEntryPatch>(type_bss_entry_patches_,
linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(string_bss_entry_patches_,
+ linker_patches);
DCHECK_EQ(size, linker_patches->size());
}
@@ -1712,6 +1718,13 @@
return NewPcRelativePatch(dex_file, string_index.index_, info_high, &pc_relative_string_patches_);
}
+CodeGeneratorMIPS::PcRelativePatchInfo* CodeGeneratorMIPS::NewStringBssEntryPatch(
+ const DexFile& dex_file,
+ dex::StringIndex string_index,
+ const PcRelativePatchInfo* info_high) {
+ return NewPcRelativePatch(dex_file, string_index.index_, info_high, &string_bss_entry_patches_);
+}
+
CodeGeneratorMIPS::PcRelativePatchInfo* CodeGeneratorMIPS::NewPcRelativePatch(
const DexFile& dex_file,
uint32_t offset_or_index,
@@ -7365,6 +7378,7 @@
bool fallback_load = has_irreducible_loops && !is_r6;
switch (desired_string_load_kind) {
case HLoadString::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadString::LoadKind::kBootImageInternTable:
case HLoadString::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -7401,6 +7415,7 @@
fallback_load = false;
break;
case HLoadClass::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadClass::LoadKind::kBootImageClassTable:
case HLoadClass::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -7631,6 +7646,7 @@
// We need an extra register for PC-relative literals on R2.
case HLoadClass::LoadKind::kBootImageLinkTimePcRelative:
case HLoadClass::LoadKind::kBootImageAddress:
+ case HLoadClass::LoadKind::kBootImageClassTable:
case HLoadClass::LoadKind::kBssEntry:
if (isR6) {
break;
@@ -7729,6 +7745,24 @@
codegen_->DeduplicateBootImageAddressLiteral(address));
break;
}
+ case HLoadClass::LoadKind::kBootImageClassTable: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ CodeGeneratorMIPS::PcRelativePatchInfo* info_high =
+ codegen_->NewPcRelativeTypePatch(cls->GetDexFile(), cls->GetTypeIndex());
+ CodeGeneratorMIPS::PcRelativePatchInfo* info_low =
+ codegen_->NewPcRelativeTypePatch(cls->GetDexFile(), cls->GetTypeIndex(), info_high);
+ codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high,
+ out,
+ base_or_current_method_reg);
+ __ Lw(out, out, /* placeholder */ 0x5678, &info_low->label);
+ // Extract the reference from the slot data, i.e. clear the hash bits.
+ int32_t masked_hash = ClassTable::TableSlot::MaskHash(
+ ComputeModifiedUtf8Hash(cls->GetDexFile().StringByTypeIdx(cls->GetTypeIndex())));
+ if (masked_hash != 0) {
+ __ Addiu(out, out, -masked_hash);
+ }
+ break;
+ }
case HLoadClass::LoadKind::kBssEntry: {
bss_info_high = codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex());
CodeGeneratorMIPS::PcRelativePatchInfo* info_low =
@@ -7817,6 +7851,7 @@
// We need an extra register for PC-relative literals on R2.
case HLoadString::LoadKind::kBootImageAddress:
case HLoadString::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadString::LoadKind::kBootImageInternTable:
case HLoadString::LoadKind::kBssEntry:
if (isR6) {
break;
@@ -7863,6 +7898,7 @@
// We need an extra register for PC-relative literals on R2.
case HLoadString::LoadKind::kBootImageAddress:
case HLoadString::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadString::LoadKind::kBootImageInternTable:
case HLoadString::LoadKind::kBssEntry:
base_or_current_method_reg = isR6 ? ZERO : locations->InAt(0).AsRegister<Register>();
break;
@@ -7882,7 +7918,7 @@
out,
base_or_current_method_reg);
__ Addiu(out, out, /* placeholder */ 0x5678, &info_low->label);
- return; // No dex cache slow path.
+ return;
}
case HLoadString::LoadKind::kBootImageAddress: {
uint32_t address = dchecked_integral_cast<uint32_t>(
@@ -7891,14 +7927,26 @@
__ LoadLiteral(out,
base_or_current_method_reg,
codegen_->DeduplicateBootImageAddressLiteral(address));
- return; // No dex cache slow path.
+ return;
}
- case HLoadString::LoadKind::kBssEntry: {
+ case HLoadString::LoadKind::kBootImageInternTable: {
DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
CodeGeneratorMIPS::PcRelativePatchInfo* info_high =
codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex());
CodeGeneratorMIPS::PcRelativePatchInfo* info_low =
codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex(), info_high);
+ codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high,
+ out,
+ base_or_current_method_reg);
+ __ Lw(out, out, /* placeholder */ 0x5678, &info_low->label);
+ return;
+ }
+ case HLoadString::LoadKind::kBssEntry: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ CodeGeneratorMIPS::PcRelativePatchInfo* info_high =
+ codegen_->NewStringBssEntryPatch(load->GetDexFile(), load->GetStringIndex());
+ CodeGeneratorMIPS::PcRelativePatchInfo* info_low =
+ codegen_->NewStringBssEntryPatch(load->GetDexFile(), load->GetStringIndex(), info_high);
constexpr bool non_baker_read_barrier = kUseReadBarrier && !kUseBakerReadBarrier;
Register temp = non_baker_read_barrier ? out : locations->GetTemp(0).AsRegister<Register>();
codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high,
diff --git a/compiler/optimizing/code_generator_mips.h b/compiler/optimizing/code_generator_mips.h
index c0e1ec0..f15f8c6 100644
--- a/compiler/optimizing/code_generator_mips.h
+++ b/compiler/optimizing/code_generator_mips.h
@@ -633,6 +633,9 @@
PcRelativePatchInfo* NewPcRelativeStringPatch(const DexFile& dex_file,
dex::StringIndex string_index,
const PcRelativePatchInfo* info_high = nullptr);
+ PcRelativePatchInfo* NewStringBssEntryPatch(const DexFile& dex_file,
+ dex::StringIndex string_index,
+ const PcRelativePatchInfo* info_high = nullptr);
Literal* DeduplicateBootImageAddressLiteral(uint32_t address);
void EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info_high,
@@ -699,8 +702,10 @@
ArenaDeque<PcRelativePatchInfo> pc_relative_type_patches_;
// PC-relative type patch info for kBssEntry.
ArenaDeque<PcRelativePatchInfo> type_bss_entry_patches_;
- // PC-relative String patch info; type depends on configuration (app .bss or boot image PIC).
+ // PC-relative String patch info; type depends on configuration (intern table or boot image PIC).
ArenaDeque<PcRelativePatchInfo> pc_relative_string_patches_;
+ // PC-relative String patch info for kBssEntry.
+ ArenaDeque<PcRelativePatchInfo> string_bss_entry_patches_;
// Patches for string root accesses in JIT compiled code.
ArenaDeque<JitPatchInfo> jit_string_patches_;
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index 25fb1d0..201b1b0 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -18,6 +18,7 @@
#include "arch/mips64/asm_support_mips64.h"
#include "art_method.h"
+#include "class_table.h"
#include "code_generator_utils.h"
#include "compiled_method.h"
#include "entrypoints/quick/quick_entrypoints.h"
@@ -318,9 +319,9 @@
// The string entry address was preserved in `entry_address` thanks to kSaveEverything.
DCHECK(bss_info_high_);
CodeGeneratorMIPS64::PcRelativePatchInfo* info_low =
- mips64_codegen->NewPcRelativeStringPatch(load->GetDexFile(),
- string_index,
- bss_info_high_);
+ mips64_codegen->NewStringBssEntryPatch(load->GetDexFile(),
+ string_index,
+ bss_info_high_);
__ Bind(&info_low->label);
__ StoreToOffset(kStoreWord,
calling_convention.GetRegisterAt(0),
@@ -339,9 +340,9 @@
// For non-Baker read barriers we need to re-calculate the address of
// the string entry.
CodeGeneratorMIPS64::PcRelativePatchInfo* info_high =
- mips64_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index);
+ mips64_codegen->NewStringBssEntryPatch(load->GetDexFile(), string_index);
CodeGeneratorMIPS64::PcRelativePatchInfo* info_low =
- mips64_codegen->NewPcRelativeStringPatch(load->GetDexFile(), string_index, info_high);
+ mips64_codegen->NewStringBssEntryPatch(load->GetDexFile(), string_index, info_high);
mips64_codegen->EmitPcRelativeAddressPlaceholderHigh(info_high, TMP, info_low);
__ StoreToOffset(kStoreWord, out, TMP, /* placeholder */ 0x5678);
}
@@ -1049,6 +1050,7 @@
pc_relative_type_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
type_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
pc_relative_string_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
+ string_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
jit_string_patches_(StringReferenceValueComparator(),
graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
jit_class_patches_(TypeReferenceValueComparator(),
@@ -1560,7 +1562,8 @@
method_bss_entry_patches_.size() +
pc_relative_type_patches_.size() +
type_bss_entry_patches_.size() +
- pc_relative_string_patches_.size();
+ pc_relative_string_patches_.size() +
+ string_bss_entry_patches_.size();
linker_patches->reserve(size);
if (GetCompilerOptions().IsBootImage()) {
EmitPcRelativeLinkerPatches<LinkerPatch::RelativeMethodPatch>(pc_relative_method_patches_,
@@ -1571,14 +1574,17 @@
linker_patches);
} else {
DCHECK(pc_relative_method_patches_.empty());
- DCHECK(pc_relative_type_patches_.empty());
- EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(pc_relative_string_patches_,
+ EmitPcRelativeLinkerPatches<LinkerPatch::TypeClassTablePatch>(pc_relative_type_patches_,
linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringInternTablePatch>(pc_relative_string_patches_,
+ linker_patches);
}
EmitPcRelativeLinkerPatches<LinkerPatch::MethodBssEntryPatch>(method_bss_entry_patches_,
linker_patches);
EmitPcRelativeLinkerPatches<LinkerPatch::TypeBssEntryPatch>(type_bss_entry_patches_,
linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(string_bss_entry_patches_,
+ linker_patches);
DCHECK_EQ(size, linker_patches->size());
}
@@ -1621,6 +1627,13 @@
return NewPcRelativePatch(dex_file, string_index.index_, info_high, &pc_relative_string_patches_);
}
+CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewStringBssEntryPatch(
+ const DexFile& dex_file,
+ dex::StringIndex string_index,
+ const PcRelativePatchInfo* info_high) {
+ return NewPcRelativePatch(dex_file, string_index.index_, info_high, &string_bss_entry_patches_);
+}
+
CodeGeneratorMIPS64::PcRelativePatchInfo* CodeGeneratorMIPS64::NewPcRelativePatch(
const DexFile& dex_file,
uint32_t offset_or_index,
@@ -5729,6 +5742,7 @@
bool fallback_load = false;
switch (desired_string_load_kind) {
case HLoadString::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadString::LoadKind::kBootImageInternTable:
case HLoadString::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -5755,6 +5769,7 @@
case HLoadClass::LoadKind::kReferrersClass:
break;
case HLoadClass::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadClass::LoadKind::kBootImageClassTable:
case HLoadClass::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -6004,6 +6019,22 @@
codegen_->DeduplicateBootImageAddressLiteral(address));
break;
}
+ case HLoadClass::LoadKind::kBootImageClassTable: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ CodeGeneratorMIPS64::PcRelativePatchInfo* info_high =
+ codegen_->NewPcRelativeTypePatch(cls->GetDexFile(), cls->GetTypeIndex());
+ CodeGeneratorMIPS64::PcRelativePatchInfo* info_low =
+ codegen_->NewPcRelativeTypePatch(cls->GetDexFile(), cls->GetTypeIndex(), info_high);
+ codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, AT, info_low);
+ __ Lwu(out, AT, /* placeholder */ 0x5678);
+ // Extract the reference from the slot data, i.e. clear the hash bits.
+ int32_t masked_hash = ClassTable::TableSlot::MaskHash(
+ ComputeModifiedUtf8Hash(cls->GetDexFile().StringByTypeIdx(cls->GetTypeIndex())));
+ if (masked_hash != 0) {
+ __ Daddiu(out, out, -masked_hash);
+ }
+ break;
+ }
case HLoadClass::LoadKind::kBssEntry: {
bss_info_high = codegen_->NewTypeBssEntryPatch(cls->GetDexFile(), cls->GetTypeIndex());
CodeGeneratorMIPS64::PcRelativePatchInfo* info_low =
@@ -6117,7 +6148,7 @@
codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex(), info_high);
codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, AT, info_low);
__ Daddiu(out, AT, /* placeholder */ 0x5678);
- return; // No dex cache slow path.
+ return;
}
case HLoadString::LoadKind::kBootImageAddress: {
uint32_t address = dchecked_integral_cast<uint32_t>(
@@ -6126,14 +6157,24 @@
__ LoadLiteral(out,
kLoadUnsignedWord,
codegen_->DeduplicateBootImageAddressLiteral(address));
- return; // No dex cache slow path.
+ return;
}
- case HLoadString::LoadKind::kBssEntry: {
+ case HLoadString::LoadKind::kBootImageInternTable: {
DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
CodeGeneratorMIPS64::PcRelativePatchInfo* info_high =
codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex());
CodeGeneratorMIPS64::PcRelativePatchInfo* info_low =
codegen_->NewPcRelativeStringPatch(load->GetDexFile(), load->GetStringIndex(), info_high);
+ codegen_->EmitPcRelativeAddressPlaceholderHigh(info_high, AT, info_low);
+ __ Lwu(out, AT, /* placeholder */ 0x5678);
+ return;
+ }
+ case HLoadString::LoadKind::kBssEntry: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ CodeGeneratorMIPS64::PcRelativePatchInfo* info_high =
+ codegen_->NewStringBssEntryPatch(load->GetDexFile(), load->GetStringIndex());
+ CodeGeneratorMIPS64::PcRelativePatchInfo* info_low =
+ codegen_->NewStringBssEntryPatch(load->GetDexFile(), load->GetStringIndex(), info_high);
constexpr bool non_baker_read_barrier = kUseReadBarrier && !kUseBakerReadBarrier;
GpuRegister temp = non_baker_read_barrier
? out
diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h
index 72d4743..3035621 100644
--- a/compiler/optimizing/code_generator_mips64.h
+++ b/compiler/optimizing/code_generator_mips64.h
@@ -605,6 +605,9 @@
PcRelativePatchInfo* NewPcRelativeStringPatch(const DexFile& dex_file,
dex::StringIndex string_index,
const PcRelativePatchInfo* info_high = nullptr);
+ PcRelativePatchInfo* NewStringBssEntryPatch(const DexFile& dex_file,
+ dex::StringIndex string_index,
+ const PcRelativePatchInfo* info_high = nullptr);
Literal* DeduplicateBootImageAddressLiteral(uint64_t address);
void EmitPcRelativeAddressPlaceholderHigh(PcRelativePatchInfo* info_high,
@@ -666,8 +669,10 @@
ArenaDeque<PcRelativePatchInfo> pc_relative_type_patches_;
// PC-relative type patch info for kBssEntry.
ArenaDeque<PcRelativePatchInfo> type_bss_entry_patches_;
- // PC-relative String patch info; type depends on configuration (app .bss or boot image PIC).
+ // PC-relative String patch info; type depends on configuration (intern table or boot image PIC).
ArenaDeque<PcRelativePatchInfo> pc_relative_string_patches_;
+ // PC-relative type patch info for kBssEntry.
+ ArenaDeque<PcRelativePatchInfo> string_bss_entry_patches_;
// Patches for string root accesses in JIT compiled code.
StringToLiteralMap jit_string_patches_;
diff --git a/compiler/optimizing/code_generator_vector_x86_64.cc b/compiler/optimizing/code_generator_vector_x86_64.cc
index 7051ba0..edd0209 100644
--- a/compiler/optimizing/code_generator_vector_x86_64.cc
+++ b/compiler/optimizing/code_generator_vector_x86_64.cc
@@ -67,7 +67,7 @@
case Primitive::kPrimBoolean:
case Primitive::kPrimByte:
DCHECK_EQ(16u, instruction->GetVectorLength());
- __ movd(dst, locations->InAt(0).AsRegister<CpuRegister>());
+ __ movd(dst, locations->InAt(0).AsRegister<CpuRegister>(), /*64-bit*/ false);
__ punpcklbw(dst, dst);
__ punpcklwd(dst, dst);
__ pshufd(dst, dst, Immediate(0));
@@ -75,28 +75,28 @@
case Primitive::kPrimChar:
case Primitive::kPrimShort:
DCHECK_EQ(8u, instruction->GetVectorLength());
- __ movd(dst, locations->InAt(0).AsRegister<CpuRegister>());
+ __ movd(dst, locations->InAt(0).AsRegister<CpuRegister>(), /*64-bit*/ false);
__ punpcklwd(dst, dst);
__ pshufd(dst, dst, Immediate(0));
break;
case Primitive::kPrimInt:
DCHECK_EQ(4u, instruction->GetVectorLength());
- __ movd(dst, locations->InAt(0).AsRegister<CpuRegister>());
+ __ movd(dst, locations->InAt(0).AsRegister<CpuRegister>(), /*64-bit*/ false);
__ pshufd(dst, dst, Immediate(0));
break;
case Primitive::kPrimLong:
DCHECK_EQ(2u, instruction->GetVectorLength());
- __ movd(dst, locations->InAt(0).AsRegister<CpuRegister>()); // is 64-bit
+ __ movd(dst, locations->InAt(0).AsRegister<CpuRegister>(), /*64-bit*/ true);
__ punpcklqdq(dst, dst);
break;
case Primitive::kPrimFloat:
- DCHECK(locations->InAt(0).Equals(locations->Out()));
DCHECK_EQ(4u, instruction->GetVectorLength());
+ DCHECK(locations->InAt(0).Equals(locations->Out()));
__ shufps(dst, dst, Immediate(0));
break;
case Primitive::kPrimDouble:
- DCHECK(locations->InAt(0).Equals(locations->Out()));
DCHECK_EQ(2u, instruction->GetVectorLength());
+ DCHECK(locations->InAt(0).Equals(locations->Out()));
__ shufpd(dst, dst, Immediate(0));
break;
default:
@@ -140,11 +140,11 @@
UNREACHABLE();
case Primitive::kPrimInt:
DCHECK_EQ(4u, instruction->GetVectorLength());
- __ movd(locations->Out().AsRegister<CpuRegister>(), src);
+ __ movd(locations->Out().AsRegister<CpuRegister>(), src, /*64-bit*/ false);
break;
case Primitive::kPrimLong:
DCHECK_EQ(2u, instruction->GetVectorLength());
- __ movd(locations->Out().AsRegister<CpuRegister>(), src); // is 64-bit
+ __ movd(locations->Out().AsRegister<CpuRegister>(), src, /*64-bit*/ true);
break;
case Primitive::kPrimFloat:
case Primitive::kPrimDouble:
@@ -1005,12 +1005,12 @@
}
}
-void LocationsBuilderX86_64::VisitVecMultiplyAccumulate(HVecMultiplyAccumulate* instr) {
- LOG(FATAL) << "No SIMD for " << instr->GetId();
+void LocationsBuilderX86_64::VisitVecMultiplyAccumulate(HVecMultiplyAccumulate* instruction) {
+ LOG(FATAL) << "No SIMD for " << instruction->GetId();
}
-void InstructionCodeGeneratorX86_64::VisitVecMultiplyAccumulate(HVecMultiplyAccumulate* instr) {
- LOG(FATAL) << "No SIMD for " << instr->GetId();
+void InstructionCodeGeneratorX86_64::VisitVecMultiplyAccumulate(HVecMultiplyAccumulate* instruction) {
+ LOG(FATAL) << "No SIMD for " << instruction->GetId();
}
// Helper to set up locations for vector memory operations.
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 99b7793..e45ad0a 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -17,6 +17,7 @@
#include "code_generator_x86.h"
#include "art_method.h"
+#include "class_table.h"
#include "code_generator_utils.h"
#include "compiled_method.h"
#include "entrypoints/quick/quick_entrypoints.h"
@@ -1035,6 +1036,7 @@
boot_image_type_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
type_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
string_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
+ string_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
jit_string_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
jit_class_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
constant_area_start_(-1),
@@ -4652,7 +4654,6 @@
}
void CodeGeneratorX86::RecordBootStringPatch(HLoadString* load_string) {
- DCHECK(GetCompilerOptions().IsBootImage());
HX86ComputeBaseMethodAddress* address = load_string->InputAt(0)->AsX86ComputeBaseMethodAddress();
string_patches_.emplace_back(address,
load_string->GetDexFile(),
@@ -4664,9 +4665,9 @@
DCHECK(!GetCompilerOptions().IsBootImage());
HX86ComputeBaseMethodAddress* address =
load_string->InputAt(0)->AsX86ComputeBaseMethodAddress();
- string_patches_.emplace_back(
+ string_bss_entry_patches_.emplace_back(
address, load_string->GetDexFile(), load_string->GetStringIndex().index_);
- return &string_patches_.back().label;
+ return &string_bss_entry_patches_.back().label;
}
// The label points to the end of the "movl" or another instruction but the literal offset
@@ -4691,7 +4692,8 @@
method_bss_entry_patches_.size() +
boot_image_type_patches_.size() +
type_bss_entry_patches_.size() +
- string_patches_.size();
+ string_patches_.size() +
+ string_bss_entry_patches_.size();
linker_patches->reserve(size);
if (GetCompilerOptions().IsBootImage()) {
EmitPcRelativeLinkerPatches<LinkerPatch::RelativeMethodPatch>(boot_image_method_patches_,
@@ -4701,13 +4703,17 @@
EmitPcRelativeLinkerPatches<LinkerPatch::RelativeStringPatch>(string_patches_, linker_patches);
} else {
DCHECK(boot_image_method_patches_.empty());
- DCHECK(boot_image_type_patches_.empty());
- EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(string_patches_, linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::TypeClassTablePatch>(boot_image_type_patches_,
+ linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringInternTablePatch>(string_patches_,
+ linker_patches);
}
EmitPcRelativeLinkerPatches<LinkerPatch::MethodBssEntryPatch>(method_bss_entry_patches_,
linker_patches);
EmitPcRelativeLinkerPatches<LinkerPatch::TypeBssEntryPatch>(type_bss_entry_patches_,
linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(string_bss_entry_patches_,
+ linker_patches);
DCHECK_EQ(size, linker_patches->size());
}
@@ -6034,6 +6040,7 @@
case HLoadClass::LoadKind::kReferrersClass:
break;
case HLoadClass::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadClass::LoadKind::kBootImageClassTable:
case HLoadClass::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -6071,6 +6078,7 @@
if (load_kind == HLoadClass::LoadKind::kReferrersClass ||
load_kind == HLoadClass::LoadKind::kBootImageLinkTimePcRelative ||
+ load_kind == HLoadClass::LoadKind::kBootImageClassTable ||
load_kind == HLoadClass::LoadKind::kBssEntry) {
locations->SetInAt(0, Location::RequiresRegister());
}
@@ -6147,6 +6155,19 @@
__ movl(out, Immediate(address));
break;
}
+ case HLoadClass::LoadKind::kBootImageClassTable: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ Register method_address = locations->InAt(0).AsRegister<Register>();
+ __ movl(out, Address(method_address, CodeGeneratorX86::kDummy32BitOffset));
+ codegen_->RecordBootTypePatch(cls);
+ // Extract the reference from the slot data, i.e. clear the hash bits.
+ int32_t masked_hash = ClassTable::TableSlot::MaskHash(
+ ComputeModifiedUtf8Hash(cls->GetDexFile().StringByTypeIdx(cls->GetTypeIndex())));
+ if (masked_hash != 0) {
+ __ subl(out, Immediate(masked_hash));
+ }
+ break;
+ }
case HLoadClass::LoadKind::kBssEntry: {
Register method_address = locations->InAt(0).AsRegister<Register>();
Address address(method_address, CodeGeneratorX86::kDummy32BitOffset);
@@ -6219,6 +6240,7 @@
HLoadString::LoadKind desired_string_load_kind) {
switch (desired_string_load_kind) {
case HLoadString::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadString::LoadKind::kBootImageInternTable:
case HLoadString::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -6237,6 +6259,7 @@
LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(load, call_kind);
HLoadString::LoadKind load_kind = load->GetLoadKind();
if (load_kind == HLoadString::LoadKind::kBootImageLinkTimePcRelative ||
+ load_kind == HLoadString::LoadKind::kBootImageInternTable ||
load_kind == HLoadString::LoadKind::kBssEntry) {
locations->SetInAt(0, Location::RequiresRegister());
}
@@ -6282,14 +6305,21 @@
Register method_address = locations->InAt(0).AsRegister<Register>();
__ leal(out, Address(method_address, CodeGeneratorX86::kDummy32BitOffset));
codegen_->RecordBootStringPatch(load);
- return; // No dex cache slow path.
+ return;
}
case HLoadString::LoadKind::kBootImageAddress: {
uint32_t address = dchecked_integral_cast<uint32_t>(
reinterpret_cast<uintptr_t>(load->GetString().Get()));
DCHECK_NE(address, 0u);
__ movl(out, Immediate(address));
- return; // No dex cache slow path.
+ return;
+ }
+ case HLoadString::LoadKind::kBootImageInternTable: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ Register method_address = locations->InAt(0).AsRegister<Register>();
+ __ movl(out, Address(method_address, CodeGeneratorX86::kDummy32BitOffset));
+ codegen_->RecordBootStringPatch(load);
+ return;
}
case HLoadString::LoadKind::kBssEntry: {
Register method_address = locations->InAt(0).AsRegister<Register>();
diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h
index f48753b..b32d57a 100644
--- a/compiler/optimizing/code_generator_x86.h
+++ b/compiler/optimizing/code_generator_x86.h
@@ -640,8 +640,10 @@
ArenaDeque<X86PcRelativePatchInfo> boot_image_type_patches_;
// Type patch locations for kBssEntry.
ArenaDeque<X86PcRelativePatchInfo> type_bss_entry_patches_;
- // String patch locations; type depends on configuration (app .bss or boot image).
+ // String patch locations; type depends on configuration (intern table or boot image PIC).
ArenaDeque<X86PcRelativePatchInfo> string_patches_;
+ // String patch locations for kBssEntry.
+ ArenaDeque<X86PcRelativePatchInfo> string_bss_entry_patches_;
// Patches for string root accesses in JIT compiled code.
ArenaDeque<PatchInfo<Label>> jit_string_patches_;
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 8283887..8c4374d 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -17,6 +17,7 @@
#include "code_generator_x86_64.h"
#include "art_method.h"
+#include "class_table.h"
#include "code_generator_utils.h"
#include "compiled_method.h"
#include "entrypoints/quick/quick_entrypoints.h"
@@ -1089,15 +1090,15 @@
}
void CodeGeneratorX86_64::RecordBootStringPatch(HLoadString* load_string) {
- DCHECK(GetCompilerOptions().IsBootImage());
string_patches_.emplace_back(load_string->GetDexFile(), load_string->GetStringIndex().index_);
__ Bind(&string_patches_.back().label);
}
Label* CodeGeneratorX86_64::NewStringBssEntryPatch(HLoadString* load_string) {
DCHECK(!GetCompilerOptions().IsBootImage());
- string_patches_.emplace_back(load_string->GetDexFile(), load_string->GetStringIndex().index_);
- return &string_patches_.back().label;
+ string_bss_entry_patches_.emplace_back(
+ load_string->GetDexFile(), load_string->GetStringIndex().index_);
+ return &string_bss_entry_patches_.back().label;
}
// The label points to the end of the "movl" or another instruction but the literal offset
@@ -1122,7 +1123,8 @@
method_bss_entry_patches_.size() +
boot_image_type_patches_.size() +
type_bss_entry_patches_.size() +
- string_patches_.size();
+ string_patches_.size() +
+ string_bss_entry_patches_.size();
linker_patches->reserve(size);
if (GetCompilerOptions().IsBootImage()) {
EmitPcRelativeLinkerPatches<LinkerPatch::RelativeMethodPatch>(boot_image_method_patches_,
@@ -1132,13 +1134,17 @@
EmitPcRelativeLinkerPatches<LinkerPatch::RelativeStringPatch>(string_patches_, linker_patches);
} else {
DCHECK(boot_image_method_patches_.empty());
- DCHECK(boot_image_type_patches_.empty());
- EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(string_patches_, linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::TypeClassTablePatch>(boot_image_type_patches_,
+ linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringInternTablePatch>(string_patches_,
+ linker_patches);
}
EmitPcRelativeLinkerPatches<LinkerPatch::MethodBssEntryPatch>(method_bss_entry_patches_,
linker_patches);
EmitPcRelativeLinkerPatches<LinkerPatch::TypeBssEntryPatch>(type_bss_entry_patches_,
linker_patches);
+ EmitPcRelativeLinkerPatches<LinkerPatch::StringBssEntryPatch>(string_bss_entry_patches_,
+ linker_patches);
DCHECK_EQ(size, linker_patches->size());
}
@@ -1230,6 +1236,7 @@
boot_image_type_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
type_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
string_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
+ string_bss_entry_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
jit_string_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
jit_class_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
fixups_to_jump_tables_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)) {
@@ -5451,6 +5458,7 @@
case HLoadClass::LoadKind::kReferrersClass:
break;
case HLoadClass::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadClass::LoadKind::kBootImageClassTable:
case HLoadClass::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -5559,6 +5567,18 @@
__ movl(out, Immediate(static_cast<int32_t>(address))); // Zero-extended.
break;
}
+ case HLoadClass::LoadKind::kBootImageClassTable: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ __ movl(out, Address::Absolute(CodeGeneratorX86_64::kDummy32BitOffset, /* no_rip */ false));
+ codegen_->RecordBootTypePatch(cls);
+ // Extract the reference from the slot data, i.e. clear the hash bits.
+ int32_t masked_hash = ClassTable::TableSlot::MaskHash(
+ ComputeModifiedUtf8Hash(cls->GetDexFile().StringByTypeIdx(cls->GetTypeIndex())));
+ if (masked_hash != 0) {
+ __ subl(out, Immediate(masked_hash));
+ }
+ break;
+ }
case HLoadClass::LoadKind::kBssEntry: {
Address address = Address::Absolute(CodeGeneratorX86_64::kDummy32BitOffset,
/* no_rip */ false);
@@ -5621,6 +5641,7 @@
HLoadString::LoadKind desired_string_load_kind) {
switch (desired_string_load_kind) {
case HLoadString::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadString::LoadKind::kBootImageInternTable:
case HLoadString::LoadKind::kBssEntry:
DCHECK(!Runtime::Current()->UseJitCompilation());
break;
@@ -5678,14 +5699,20 @@
DCHECK(codegen_->GetCompilerOptions().IsBootImage());
__ leal(out, Address::Absolute(CodeGeneratorX86_64::kDummy32BitOffset, /* no_rip */ false));
codegen_->RecordBootStringPatch(load);
- return; // No dex cache slow path.
+ return;
}
case HLoadString::LoadKind::kBootImageAddress: {
uint32_t address = dchecked_integral_cast<uint32_t>(
reinterpret_cast<uintptr_t>(load->GetString().Get()));
DCHECK_NE(address, 0u);
__ movl(out, Immediate(static_cast<int32_t>(address))); // Zero-extended.
- return; // No dex cache slow path.
+ return;
+ }
+ case HLoadString::LoadKind::kBootImageInternTable: {
+ DCHECK(!codegen_->GetCompilerOptions().IsBootImage());
+ __ movl(out, Address::Absolute(CodeGeneratorX86_64::kDummy32BitOffset, /* no_rip */ false));
+ codegen_->RecordBootStringPatch(load);
+ return;
}
case HLoadString::LoadKind::kBssEntry: {
Address address = Address::Absolute(CodeGeneratorX86_64::kDummy32BitOffset,
diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h
index 33c6429..f5fa86b 100644
--- a/compiler/optimizing/code_generator_x86_64.h
+++ b/compiler/optimizing/code_generator_x86_64.h
@@ -611,8 +611,10 @@
ArenaDeque<PatchInfo<Label>> boot_image_type_patches_;
// Type patch locations for kBssEntry.
ArenaDeque<PatchInfo<Label>> type_bss_entry_patches_;
- // String patch locations; type depends on configuration (app .bss or boot image).
+ // String patch locations; type depends on configuration (intern table or boot image PIC).
ArenaDeque<PatchInfo<Label>> string_patches_;
+ // String patch locations for kBssEntry.
+ ArenaDeque<PatchInfo<Label>> string_bss_entry_patches_;
// Patches for string literals in JIT compiled code.
ArenaDeque<PatchInfo<Label>> jit_string_patches_;
diff --git a/compiler/optimizing/code_sinking.cc b/compiler/optimizing/code_sinking.cc
index 6c3a9fd..b558eb1 100644
--- a/compiler/optimizing/code_sinking.cc
+++ b/compiler/optimizing/code_sinking.cc
@@ -64,6 +64,11 @@
// A fence with "0" inputs is dead and should've been removed in a prior pass.
DCHECK_NE(0u, ctor_fence->InputCount());
+ // TODO: this should be simplified to 'return true' since it's
+ // potentially pessimizing any code sinking for inlined constructors with final fields.
+ // TODO: double check that if the final field assignments are not moved,
+ // then the fence is not moved either.
+
return ctor_fence->GetAssociatedAllocation() != nullptr;
}
diff --git a/compiler/optimizing/constructor_fence_redundancy_elimination.cc b/compiler/optimizing/constructor_fence_redundancy_elimination.cc
new file mode 100644
index 0000000..ff7ce60
--- /dev/null
+++ b/compiler/optimizing/constructor_fence_redundancy_elimination.cc
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "constructor_fence_redundancy_elimination.h"
+
+#include "base/arena_allocator.h"
+
+namespace art {
+
+static constexpr bool kCfreLogFenceInputCount = false;
+
+// TODO: refactor this code by reusing escape analysis.
+class CFREVisitor : public HGraphVisitor {
+ public:
+ CFREVisitor(HGraph* graph, OptimizingCompilerStats* stats)
+ : HGraphVisitor(graph),
+ scoped_allocator_(graph->GetArena()->GetArenaPool()),
+ candidate_fences_(scoped_allocator_.Adapter(kArenaAllocCFRE)),
+ candidate_fence_targets_(scoped_allocator_.Adapter(kArenaAllocCFRE)),
+ stats_(stats) {}
+
+ void VisitBasicBlock(HBasicBlock* block) OVERRIDE {
+ // Visit all instructions in block.
+ HGraphVisitor::VisitBasicBlock(block);
+
+ // If there were any unmerged fences left, merge them together,
+ // the objects are considered 'published' at the end of the block.
+ MergeCandidateFences();
+ }
+
+ void VisitConstructorFence(HConstructorFence* constructor_fence) OVERRIDE {
+ candidate_fences_.push_back(constructor_fence);
+
+ for (size_t input_idx = 0; input_idx < constructor_fence->InputCount(); ++input_idx) {
+ candidate_fence_targets_.Insert(constructor_fence->InputAt(input_idx));
+ }
+ }
+
+ void VisitBoundType(HBoundType* bound_type) OVERRIDE {
+ VisitAlias(bound_type);
+ }
+
+ void VisitNullCheck(HNullCheck* null_check) OVERRIDE {
+ VisitAlias(null_check);
+ }
+
+ void VisitSelect(HSelect* select) OVERRIDE {
+ VisitAlias(select);
+ }
+
+ void VisitInstanceFieldSet(HInstanceFieldSet* instruction) OVERRIDE {
+ HInstruction* value = instruction->InputAt(1);
+ VisitSetLocation(instruction, value);
+ }
+
+ void VisitStaticFieldSet(HStaticFieldSet* instruction) OVERRIDE {
+ HInstruction* value = instruction->InputAt(1);
+ VisitSetLocation(instruction, value);
+ }
+
+ void VisitArraySet(HArraySet* instruction) OVERRIDE {
+ HInstruction* value = instruction->InputAt(2);
+ VisitSetLocation(instruction, value);
+ }
+
+ void VisitDeoptimize(HDeoptimize* instruction ATTRIBUTE_UNUSED) {
+ // Pessimize: Merge all fences.
+ MergeCandidateFences();
+ }
+
+ void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) OVERRIDE {
+ HandleInvoke(invoke);
+ }
+
+ void VisitInvokeVirtual(HInvokeVirtual* invoke) OVERRIDE {
+ HandleInvoke(invoke);
+ }
+
+ void VisitInvokeInterface(HInvokeInterface* invoke) OVERRIDE {
+ HandleInvoke(invoke);
+ }
+
+ void VisitInvokeUnresolved(HInvokeUnresolved* invoke) OVERRIDE {
+ HandleInvoke(invoke);
+ }
+
+ void VisitInvokePolymorphic(HInvokePolymorphic* invoke) OVERRIDE {
+ HandleInvoke(invoke);
+ }
+
+ void VisitClinitCheck(HClinitCheck* clinit) OVERRIDE {
+ HandleInvoke(clinit);
+ }
+
+ void VisitUnresolvedInstanceFieldGet(HUnresolvedInstanceFieldGet* instruction) OVERRIDE {
+ // Conservatively treat it as an invocation.
+ HandleInvoke(instruction);
+ }
+
+ void VisitUnresolvedInstanceFieldSet(HUnresolvedInstanceFieldSet* instruction) OVERRIDE {
+ // Conservatively treat it as an invocation.
+ HandleInvoke(instruction);
+ }
+
+ void VisitUnresolvedStaticFieldGet(HUnresolvedStaticFieldGet* instruction) OVERRIDE {
+ // Conservatively treat it as an invocation.
+ HandleInvoke(instruction);
+ }
+
+ void VisitUnresolvedStaticFieldSet(HUnresolvedStaticFieldSet* instruction) OVERRIDE {
+ // Conservatively treat it as an invocation.
+ HandleInvoke(instruction);
+ }
+
+ private:
+ void HandleInvoke(HInstruction* invoke) {
+ // An object is considered "published" if it escapes into an invoke as any of the parameters.
+ if (HasInterestingPublishTargetAsInput(invoke)) {
+ MergeCandidateFences();
+ }
+ }
+
+ // Called by any instruction visitor that may create an alias.
+ // These instructions may create an alias:
+ // - BoundType
+ // - NullCheck
+ // - Select
+ //
+ // These also create an alias, but are not handled by this function:
+ // - Phi: propagates values across blocks, but we always merge at the end of a block.
+ // - Invoke: this is handled by HandleInvoke.
+ void VisitAlias(HInstruction* aliasing_inst) {
+ // An object is considered "published" if it becomes aliased by other instructions.
+ if (HasInterestingPublishTargetAsInput(aliasing_inst)) {
+ // Note that constructing a "NullCheck" for new-instance, new-array,
+ // or a 'this' (receiver) reference is impossible.
+ //
+ // If by some reason we actually encounter such a NullCheck(FenceTarget),
+ // we LOG(WARNING).
+ if (UNLIKELY(aliasing_inst->IsNullCheck())) {
+ LOG(kIsDebugBuild ? FATAL : WARNING)
+ << "Unexpected instruction: NullCheck; should not be legal in graph";
+ // We then do a best-effort to handle this case.
+ }
+ MergeCandidateFences();
+ }
+ }
+
+ void VisitSetLocation(HInstruction* inst ATTRIBUTE_UNUSED, HInstruction* store_input) {
+ // An object is considered "published" if it's stored onto the heap.
+ // Sidenote: A later "LSE" pass can still remove the fence if it proves the
+ // object doesn't actually escape.
+ if (IsInterestingPublishTarget(store_input)) {
+ // Merge all constructor fences that we've seen since
+ // the last interesting store (or since the beginning).
+ MergeCandidateFences();
+ }
+ }
+
+ bool HasInterestingPublishTargetAsInput(HInstruction* inst) {
+ for (size_t input_count = 0; input_count < inst->InputCount(); ++input_count) {
+ if (IsInterestingPublishTarget(inst->InputAt(input_count))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Merges all the existing fences we've seen so far into the last-most fence.
+ //
+ // This resets the list of candidate fences and their targets back to {}.
+ void MergeCandidateFences() {
+ if (candidate_fences_.empty()) {
+ // Nothing to do, need 1+ fences to merge.
+ return;
+ }
+
+ // The merge target is always the "last" candidate fence.
+ HConstructorFence* merge_target = candidate_fences_[candidate_fences_.size() - 1];
+
+ for (HConstructorFence* fence : candidate_fences_) {
+ MaybeMerge(merge_target, fence);
+ }
+
+ if (kCfreLogFenceInputCount) {
+ LOG(INFO) << "CFRE-MergeCandidateFences: Post-merge fence input count "
+ << merge_target->InputCount();
+ }
+
+ // Each merge acts as a cut-off point. The optimization is reset completely.
+ // In theory, we could push the fence as far as its publish, but in practice
+ // there is no benefit to this extra complexity unless we also reordered
+ // the stores to come later.
+ candidate_fences_.clear();
+ candidate_fence_targets_.Clear();
+ }
+
+ // A publishing 'store' is only interesting if the value being stored
+ // is one of the fence `targets` in `candidate_fences`.
+ bool IsInterestingPublishTarget(HInstruction* store_input) const {
+ return candidate_fence_targets_.Find(store_input) != candidate_fence_targets_.end();
+ }
+
+ void MaybeMerge(HConstructorFence* target, HConstructorFence* src) {
+ if (target == src) {
+ return; // Don't merge a fence into itself.
+ // This is mostly for stats-purposes, we don't want to count merge(x,x)
+ // as removing a fence because it's a no-op.
+ }
+
+ target->Merge(src);
+
+ MaybeRecordStat(stats_, MethodCompilationStat::kConstructorFenceRemovedCFRE);
+ }
+
+ // Phase-local heap memory allocator for CFRE optimizer. Storage obtained
+ // through this allocator is immediately released when the CFRE optimizer is done.
+ ArenaAllocator scoped_allocator_;
+
+ // Set of constructor fences that we've seen in the current block.
+ // Each constructor fences acts as a guard for one or more `targets`.
+ // There exist no stores to any `targets` between any of these fences.
+ //
+ // Fences are in succession order (e.g. fence[i] succeeds fence[i-1]
+ // within the same basic block).
+ ArenaVector<HConstructorFence*> candidate_fences_;
+
+ // Stores a set of the fence targets, to allow faster lookup of whether
+ // a detected publish is a target of one of the candidate fences.
+ ArenaHashSet<HInstruction*> candidate_fence_targets_;
+
+ // Used to record stats about the optimization.
+ OptimizingCompilerStats* const stats_;
+
+ DISALLOW_COPY_AND_ASSIGN(CFREVisitor);
+};
+
+void ConstructorFenceRedundancyElimination::Run() {
+ CFREVisitor cfre_visitor(graph_, stats_);
+
+ // Arbitrarily visit in reverse-post order.
+ // The exact block visit order does not matter, as the algorithm
+ // only operates on a single block at a time.
+ cfre_visitor.VisitReversePostOrder();
+}
+
+} // namespace art
diff --git a/compiler/optimizing/constructor_fence_redundancy_elimination.h b/compiler/optimizing/constructor_fence_redundancy_elimination.h
new file mode 100644
index 0000000..d89210c
--- /dev/null
+++ b/compiler/optimizing/constructor_fence_redundancy_elimination.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_COMPILER_OPTIMIZING_CONSTRUCTOR_FENCE_REDUNDANCY_ELIMINATION_H_
+#define ART_COMPILER_OPTIMIZING_CONSTRUCTOR_FENCE_REDUNDANCY_ELIMINATION_H_
+
+#include "optimization.h"
+
+namespace art {
+
+/*
+ * Constructor Fence Redundancy Elimination (CFRE).
+ *
+ * A local optimization pass that merges redundant constructor fences
+ * together within the same basic block.
+ *
+ * Abbreviations:
+ * - CF: Constructor Fence
+ * - CFS: Constructor Fence Set
+ * - CFTargets: The unique set of the inputs of all the instructions in CFS.
+ *
+ * Given any CFS = { CF(x), CF(y), CF(z), ... }, define CFTargets = { x, y, z, ... }.
+ * - Publish(R) must not exist for any R in CFTargets if this Publish(R) is between any CF in CFS.
+ * - This type of Publish(R) is called an "interesting publish".
+ *
+ * A Publish(R) is considered any instruction at which the reference to "R"
+ * may escape (e.g. invoke, store, return, etc) to another thread.
+ *
+ * Starting at the beginning of the block:
+ * - Find the largest contiguous CFS.
+ * - If we see an interesting publish, merge all instructions in CFS into a single CF(CFTargets).
+ * - Repeat until the block is fully visited.
+ * - At the end of the block, merge all instructions in CFS into a single CF(CFTargets).
+ */
+class ConstructorFenceRedundancyElimination : public HOptimization {
+ public:
+ ConstructorFenceRedundancyElimination(HGraph* graph,
+ OptimizingCompilerStats* stats)
+ : HOptimization(graph, kPassName, stats) {}
+
+ void Run() OVERRIDE;
+
+ static constexpr const char* kPassName = "constructor_fence_redundancy_elimination";
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConstructorFenceRedundancyElimination);
+};
+
+} // namespace art
+
+#endif // ART_COMPILER_OPTIMIZING_CONSTRUCTOR_FENCE_REDUNDANCY_ELIMINATION_H_
diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc
index 089340e..191d3d1 100644
--- a/compiler/optimizing/induction_var_range.cc
+++ b/compiler/optimizing/induction_var_range.cc
@@ -670,6 +670,15 @@
return AddValue(GetFetch(instruction->InputAt(0), trip, in_body, is_min),
Value(static_cast<int32_t>(value)));
}
+ } else if (instruction->IsSub()) {
+ // Incorporate suitable constants in the chased value.
+ if (IsInt64AndGet(instruction->InputAt(0), &value) && CanLongValueFitIntoInt(value)) {
+ return SubValue(Value(static_cast<int32_t>(value)),
+ GetFetch(instruction->InputAt(1), trip, in_body, !is_min));
+ } else if (IsInt64AndGet(instruction->InputAt(1), &value) && CanLongValueFitIntoInt(value)) {
+ return SubValue(GetFetch(instruction->InputAt(0), trip, in_body, is_min),
+ Value(static_cast<int32_t>(value)));
+ }
} else if (instruction->IsArrayLength()) {
// Exploit length properties when chasing constants or chase into a new array declaration.
if (chase_hint_ == nullptr) {
diff --git a/compiler/optimizing/induction_var_range_test.cc b/compiler/optimizing/induction_var_range_test.cc
index 2b82b33..9437014 100644
--- a/compiler/optimizing/induction_var_range_test.cc
+++ b/compiler/optimizing/induction_var_range_test.cc
@@ -723,6 +723,29 @@
ExpectEqual(Value(x_, 1, 0), GetMax(CreateFetch(array_length), nullptr));
}
+TEST_F(InductionVarRangeTest, AddOrSubAndConstant) {
+ HInstruction* add = new (&allocator_)
+ HAdd(Primitive::kPrimInt, x_, graph_->GetIntConstant(-1));
+ HInstruction* alt = new (&allocator_)
+ HAdd(Primitive::kPrimInt, graph_->GetIntConstant(-1), x_);
+ HInstruction* sub = new (&allocator_)
+ HSub(Primitive::kPrimInt, x_, graph_->GetIntConstant(1));
+ HInstruction* rev = new (&allocator_)
+ HSub(Primitive::kPrimInt, graph_->GetIntConstant(1), x_);
+ entry_block_->AddInstruction(add);
+ entry_block_->AddInstruction(alt);
+ entry_block_->AddInstruction(sub);
+ entry_block_->AddInstruction(rev);
+ ExpectEqual(Value(x_, 1, -1), GetMin(CreateFetch(add), nullptr));
+ ExpectEqual(Value(x_, 1, -1), GetMax(CreateFetch(add), nullptr));
+ ExpectEqual(Value(x_, 1, -1), GetMin(CreateFetch(alt), nullptr));
+ ExpectEqual(Value(x_, 1, -1), GetMax(CreateFetch(alt), nullptr));
+ ExpectEqual(Value(x_, 1, -1), GetMin(CreateFetch(sub), nullptr));
+ ExpectEqual(Value(x_, 1, -1), GetMax(CreateFetch(sub), nullptr));
+ ExpectEqual(Value(x_, -1, 1), GetMin(CreateFetch(rev), nullptr));
+ ExpectEqual(Value(x_, -1, 1), GetMax(CreateFetch(rev), nullptr));
+}
+
//
// Tests on public methods.
//
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc
index ca3b191..6532ec1 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -25,6 +25,7 @@
#include "quicken_info.h"
#include "scoped_thread_state_change-inl.h"
#include "sharpening.h"
+#include "well_known_classes.h"
namespace art {
diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc
index 98b8592..8a9acf1 100644
--- a/compiler/optimizing/load_store_elimination.cc
+++ b/compiler/optimizing/load_store_elimination.cc
@@ -670,6 +670,11 @@
return;
}
+ // TODO: analyze VecLoad/VecStore better.
+ if (graph_->HasSIMD()) {
+ return;
+ }
+
LSEVisitor lse_visitor(graph_, heap_location_collector, side_effects_, stats_);
for (HBasicBlock* block : graph_->GetReversePostOrder()) {
lse_visitor.VisitBasicBlock(block);
diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc
index a249cac..baa0453 100644
--- a/compiler/optimizing/loop_optimization.cc
+++ b/compiler/optimizing/loop_optimization.cc
@@ -34,6 +34,9 @@
// All current SIMD targets want 16-byte alignment.
static constexpr size_t kAlignedBase = 16;
+// No loop unrolling factor (just one copy of the loop-body).
+static constexpr uint32_t kNoUnrollingFactor = 1;
+
// Remove the instruction from the graph. A bit more elaborate than the usual
// instruction removal, since there may be a cycle in the use structure.
static void RemoveFromCycle(HInstruction* instruction) {
@@ -791,7 +794,7 @@
vector_index_,
ptc,
graph_->GetIntConstant(1),
- /*unroll*/ 1);
+ kNoUnrollingFactor);
}
// Generate vector loop, possibly further unrolled:
@@ -818,7 +821,7 @@
vector_index_,
stc,
graph_->GetIntConstant(1),
- /*unroll*/ 1);
+ kNoUnrollingFactor);
}
// Link reductions to their final uses.
@@ -1761,23 +1764,37 @@
vector_peeling_candidate_ = candidate;
}
+static constexpr uint32_t ARM64_SIMD_MAXIMUM_UNROLL_FACTOR = 8;
+static constexpr uint32_t ARM64_SIMD_HEURISTIC_MAX_BODY_SIZE = 50;
+
uint32_t HLoopOptimization::GetUnrollingFactor(HBasicBlock* block, int64_t trip_count) {
- // Current heuristic: unroll by 2 on ARM64/X86 for large known trip
- // counts and small loop bodies.
- // TODO: refine with operation count, remaining iterations, etc.
- // Artem had some really cool ideas for this already.
switch (compiler_driver_->GetInstructionSet()) {
- case kArm64:
- case kX86:
- case kX86_64: {
- size_t num_instructions = block->GetInstructions().CountSize();
- if (num_instructions <= 10 && trip_count >= 4 * vector_length_) {
- return 2;
+ case kArm64: {
+ // Don't unroll with insufficient iterations.
+ // TODO: Unroll loops with unknown trip count.
+ DCHECK_NE(vector_length_, 0u);
+ if (trip_count < 2 * vector_length_) {
+ return kNoUnrollingFactor;
}
- return 1;
+ // Don't unroll for large loop body size.
+ uint32_t instruction_count = block->GetInstructions().CountSize();
+ if (instruction_count >= ARM64_SIMD_HEURISTIC_MAX_BODY_SIZE) {
+ return kNoUnrollingFactor;
+ }
+ // Find a beneficial unroll factor with the following restrictions:
+ // - At least one iteration of the transformed loop should be executed.
+ // - The loop body shouldn't be "too big" (heuristic).
+ uint32_t uf1 = ARM64_SIMD_HEURISTIC_MAX_BODY_SIZE / instruction_count;
+ uint32_t uf2 = trip_count / vector_length_;
+ uint32_t unroll_factor =
+ TruncToPowerOfTwo(std::min({uf1, uf2, ARM64_SIMD_MAXIMUM_UNROLL_FACTOR}));
+ DCHECK_GE(unroll_factor, 1u);
+ return unroll_factor;
}
+ case kX86:
+ case kX86_64:
default:
- return 1;
+ return kNoUnrollingFactor;
}
}
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index 1510eaf..9cff6b0 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -1289,18 +1289,59 @@
return remove_count;
}
-HInstruction* HConstructorFence::GetAssociatedAllocation() {
+void HConstructorFence::Merge(HConstructorFence* other) {
+ // Do not delete yourself from the graph.
+ DCHECK(this != other);
+ // Don't try to merge with an instruction not associated with a block.
+ DCHECK(other->GetBlock() != nullptr);
+ // A constructor fence's return type is "kPrimVoid"
+ // and therefore it cannot have any environment uses.
+ DCHECK(!other->HasEnvironmentUses());
+
+ auto has_input = [](HInstruction* haystack, HInstruction* needle) {
+ // Check if `haystack` has `needle` as any of its inputs.
+ for (size_t input_count = 0; input_count < haystack->InputCount(); ++input_count) {
+ if (haystack->InputAt(input_count) == needle) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ // Add any inputs from `other` into `this` if it wasn't already an input.
+ for (size_t input_count = 0; input_count < other->InputCount(); ++input_count) {
+ HInstruction* other_input = other->InputAt(input_count);
+ if (!has_input(this, other_input)) {
+ AddInput(other_input);
+ }
+ }
+
+ other->GetBlock()->RemoveInstruction(other);
+}
+
+HInstruction* HConstructorFence::GetAssociatedAllocation(bool ignore_inputs) {
HInstruction* new_instance_inst = GetPrevious();
// Check if the immediately preceding instruction is a new-instance/new-array.
// Otherwise this fence is for protecting final fields.
if (new_instance_inst != nullptr &&
(new_instance_inst->IsNewInstance() || new_instance_inst->IsNewArray())) {
- // TODO: Need to update this code to handle multiple inputs.
- DCHECK_EQ(InputCount(), 1u);
- return new_instance_inst;
- } else {
- return nullptr;
+ if (ignore_inputs) {
+ // If inputs are ignored, simply check if the predecessor is
+ // *any* HNewInstance/HNewArray.
+ //
+ // Inputs are normally only ignored for prepare_for_register_allocation,
+ // at which point *any* prior HNewInstance/Array can be considered
+ // associated.
+ return new_instance_inst;
+ } else {
+ // Normal case: There must be exactly 1 input and the previous instruction
+ // must be that input.
+ if (InputCount() == 1u && InputAt(0) == new_instance_inst) {
+ return new_instance_inst;
+ }
+ }
}
+ return nullptr;
}
#define DEFINE_ACCEPT(name, super) \
@@ -2736,6 +2777,7 @@
}
switch (GetLoadKind()) {
case LoadKind::kBootImageAddress:
+ case LoadKind::kBootImageClassTable:
case LoadKind::kJitTableAddress: {
ScopedObjectAccess soa(Thread::Current());
return GetClass().Get() == other_load_class->GetClass().Get();
@@ -2769,6 +2811,8 @@
return os << "BootImageLinkTimePcRelative";
case HLoadClass::LoadKind::kBootImageAddress:
return os << "BootImageAddress";
+ case HLoadClass::LoadKind::kBootImageClassTable:
+ return os << "BootImageClassTable";
case HLoadClass::LoadKind::kBssEntry:
return os << "BssEntry";
case HLoadClass::LoadKind::kJitTableAddress:
@@ -2791,6 +2835,7 @@
}
switch (GetLoadKind()) {
case LoadKind::kBootImageAddress:
+ case LoadKind::kBootImageInternTable:
case LoadKind::kJitTableAddress: {
ScopedObjectAccess soa(Thread::Current());
return GetString().Get() == other_load_string->GetString().Get();
@@ -2821,6 +2866,8 @@
return os << "BootImageLinkTimePcRelative";
case HLoadString::LoadKind::kBootImageAddress:
return os << "BootImageAddress";
+ case HLoadString::LoadKind::kBootImageInternTable:
+ return os << "BootImageInternTable";
case HLoadString::LoadKind::kBssEntry:
return os << "BssEntry";
case HLoadString::LoadKind::kJitTableAddress:
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 869fdd4..a6d0da1 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -5676,6 +5676,10 @@
// Used for boot image classes referenced by apps in AOT- and JIT-compiled code.
kBootImageAddress,
+ // Use a PC-relative load from a boot image ClassTable mmapped into the .bss
+ // of the oat file.
+ kBootImageClassTable,
+
// Load from an entry in the .bss section using a PC-relative load.
// Used for classes outside boot image when .bss is accessible with a PC-relative load.
kBssEntry,
@@ -5821,6 +5825,7 @@
static bool HasTypeReference(LoadKind load_kind) {
return load_kind == LoadKind::kReferrersClass ||
load_kind == LoadKind::kBootImageLinkTimePcRelative ||
+ load_kind == LoadKind::kBootImageClassTable ||
load_kind == LoadKind::kBssEntry ||
load_kind == LoadKind::kRuntimeCall;
}
@@ -5854,6 +5859,7 @@
// including literal pool loads, which are PC-relative too.
DCHECK(GetLoadKind() == LoadKind::kBootImageLinkTimePcRelative ||
GetLoadKind() == LoadKind::kBootImageAddress ||
+ GetLoadKind() == LoadKind::kBootImageClassTable ||
GetLoadKind() == LoadKind::kBssEntry) << GetLoadKind();
DCHECK(special_input_.GetInstruction() == nullptr);
special_input_ = HUserRecord<HInstruction*>(special_input);
@@ -5872,6 +5878,10 @@
// Used for boot image strings referenced by apps in AOT- and JIT-compiled code.
kBootImageAddress,
+ // Use a PC-relative load from a boot image InternTable mmapped into the .bss
+ // of the oat file.
+ kBootImageInternTable,
+
// Load from an entry in the .bss section using a PC-relative load.
// Used for strings outside boot image when .bss is accessible with a PC-relative load.
kBssEntry,
@@ -5931,6 +5941,7 @@
LoadKind load_kind = GetLoadKind();
if (load_kind == LoadKind::kBootImageLinkTimePcRelative ||
load_kind == LoadKind::kBootImageAddress ||
+ load_kind == LoadKind::kBootImageInternTable ||
load_kind == LoadKind::kJitTableAddress) {
return false;
}
@@ -5991,8 +6002,9 @@
// The special input is used for PC-relative loads on some architectures,
// including literal pool loads, which are PC-relative too.
DCHECK(GetLoadKind() == LoadKind::kBootImageLinkTimePcRelative ||
- GetLoadKind() == LoadKind::kBssEntry ||
- GetLoadKind() == LoadKind::kBootImageAddress) << GetLoadKind();
+ GetLoadKind() == LoadKind::kBootImageAddress ||
+ GetLoadKind() == LoadKind::kBootImageInternTable ||
+ GetLoadKind() == LoadKind::kBssEntry) << GetLoadKind();
// HLoadString::GetInputRecords() returns an empty array at this point,
// so use the GetInputRecords() from the base class to set the input record.
DCHECK(special_input_.GetInstruction() == nullptr);
@@ -6637,13 +6649,24 @@
// Returns how many HConstructorFence instructions were removed from graph.
static size_t RemoveConstructorFences(HInstruction* instruction);
+ // Combine all inputs of `this` and `other` instruction and remove
+ // `other` from the graph.
+ //
+ // Inputs are unique after the merge.
+ //
+ // Requirement: `this` must not be the same as `other.
+ void Merge(HConstructorFence* other);
+
// Check if this constructor fence is protecting
// an HNewInstance or HNewArray that is also the immediate
// predecessor of `this`.
//
+ // If `ignore_inputs` is true, then the immediate predecessor doesn't need
+ // to be one of the inputs of `this`.
+ //
// Returns the associated HNewArray or HNewInstance,
// or null otherwise.
- HInstruction* GetAssociatedAllocation();
+ HInstruction* GetAssociatedAllocation(bool ignore_inputs = false);
DECLARE_INSTRUCTION(ConstructorFence);
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 71d91ae..399cd98 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -53,6 +53,7 @@
#include "compiled_method.h"
#include "compiler.h"
#include "constant_folding.h"
+#include "constructor_fence_redundancy_elimination.h"
#include "dead_code_elimination.h"
#include "debug/elf_debug_writer.h"
#include "debug/method_debug_info.h"
@@ -514,6 +515,8 @@
return new (arena) CHAGuardOptimization(graph);
} else if (opt_name == CodeSinking::kCodeSinkingPassName) {
return new (arena) CodeSinking(graph, stats);
+ } else if (opt_name == ConstructorFenceRedundancyElimination::kPassName) {
+ return new (arena) ConstructorFenceRedundancyElimination(graph, stats);
#ifdef ART_ENABLE_CODEGEN_arm
} else if (opt_name == arm::InstructionSimplifierArm::kInstructionSimplifierArmPassName) {
return new (arena) arm::InstructionSimplifierArm(graph, stats);
@@ -784,6 +787,8 @@
IntrinsicsRecognizer* intrinsics = new (arena) IntrinsicsRecognizer(graph, stats);
CHAGuardOptimization* cha_guard = new (arena) CHAGuardOptimization(graph);
CodeSinking* code_sinking = new (arena) CodeSinking(graph, stats);
+ ConstructorFenceRedundancyElimination* cfre =
+ new (arena) ConstructorFenceRedundancyElimination(graph, stats);
HOptimization* optimizations1[] = {
intrinsics,
@@ -821,6 +826,8 @@
// can satisfy. For example, the code generator does not expect to see a
// HTypeConversion from a type to the same type.
simplify4,
+ cfre, // Eliminate constructor fences after code sinking to avoid
+ // complicated sinking logic to split a fence with many inputs.
};
RunOptimizations(optimizations2, arraysize(optimizations2), pass_observer);
diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h
index ff49056..07f9635 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -93,6 +93,7 @@
kConstructorFenceGeneratedFinal,
kConstructorFenceRemovedLSE,
kConstructorFenceRemovedPFRA,
+ kConstructorFenceRemovedCFRE,
kLastStat
};
@@ -215,6 +216,7 @@
case kConstructorFenceGeneratedFinal: name = "ConstructorFenceGeneratedFinal"; break;
case kConstructorFenceRemovedLSE: name = "ConstructorFenceRemovedLSE"; break;
case kConstructorFenceRemovedPFRA: name = "ConstructorFenceRemovedPFRA"; break;
+ case kConstructorFenceRemovedCFRE: name = "ConstructorFenceRemovedCFRE"; break;
case kLastStat:
LOG(FATAL) << "invalid stat "
diff --git a/compiler/optimizing/pc_relative_fixups_mips.cc b/compiler/optimizing/pc_relative_fixups_mips.cc
index 21b6452..e569b78 100644
--- a/compiler/optimizing/pc_relative_fixups_mips.cc
+++ b/compiler/optimizing/pc_relative_fixups_mips.cc
@@ -75,6 +75,7 @@
switch (load_kind) {
case HLoadClass::LoadKind::kBootImageLinkTimePcRelative:
case HLoadClass::LoadKind::kBootImageAddress:
+ case HLoadClass::LoadKind::kBootImageClassTable:
case HLoadClass::LoadKind::kBssEntry:
// Add a base register for PC-relative literals on R2.
InitializePCRelativeBasePointer();
@@ -88,8 +89,9 @@
void VisitLoadString(HLoadString* load_string) OVERRIDE {
HLoadString::LoadKind load_kind = load_string->GetLoadKind();
switch (load_kind) {
- case HLoadString::LoadKind::kBootImageAddress:
case HLoadString::LoadKind::kBootImageLinkTimePcRelative:
+ case HLoadString::LoadKind::kBootImageAddress:
+ case HLoadString::LoadKind::kBootImageInternTable:
case HLoadString::LoadKind::kBssEntry:
// Add a base register for PC-relative literals on R2.
InitializePCRelativeBasePointer();
diff --git a/compiler/optimizing/pc_relative_fixups_x86.cc b/compiler/optimizing/pc_relative_fixups_x86.cc
index 2743df9..9877e10 100644
--- a/compiler/optimizing/pc_relative_fixups_x86.cc
+++ b/compiler/optimizing/pc_relative_fixups_x86.cc
@@ -83,6 +83,7 @@
void VisitLoadClass(HLoadClass* load_class) OVERRIDE {
HLoadClass::LoadKind load_kind = load_class->GetLoadKind();
if (load_kind == HLoadClass::LoadKind::kBootImageLinkTimePcRelative ||
+ load_kind == HLoadClass::LoadKind::kBootImageClassTable ||
load_kind == HLoadClass::LoadKind::kBssEntry) {
HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(load_class);
load_class->AddSpecialInput(method_address);
@@ -92,6 +93,7 @@
void VisitLoadString(HLoadString* load_string) OVERRIDE {
HLoadString::LoadKind load_kind = load_string->GetLoadKind();
if (load_kind == HLoadString::LoadKind::kBootImageLinkTimePcRelative ||
+ load_kind == HLoadString::LoadKind::kBootImageInternTable ||
load_kind == HLoadString::LoadKind::kBssEntry) {
HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(load_string);
load_string->AddSpecialInput(method_address);
diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc
index 9536d14..e46c9a7 100644
--- a/compiler/optimizing/sharpening.cc
+++ b/compiler/optimizing/sharpening.cc
@@ -205,11 +205,15 @@
// TODO(ngeoffray): Generate HDeoptimize instead.
desired_load_kind = HLoadClass::LoadKind::kRuntimeCall;
}
- } else if (is_in_boot_image && !codegen->GetCompilerOptions().GetCompilePic()) {
- // AOT app compilation. Check if the class is in the boot image.
- desired_load_kind = HLoadClass::LoadKind::kBootImageAddress;
+ } else if (is_in_boot_image) {
+ // AOT app compilation, boot image class.
+ if (codegen->GetCompilerOptions().GetCompilePic()) {
+ desired_load_kind = HLoadClass::LoadKind::kBootImageClassTable;
+ } else {
+ desired_load_kind = HLoadClass::LoadKind::kBootImageAddress;
+ }
} else {
- // Not JIT and either the klass is not in boot image or we are compiling in PIC mode.
+ // Not JIT and the klass is not in boot image.
desired_load_kind = HLoadClass::LoadKind::kBssEntry;
}
}
@@ -278,10 +282,12 @@
} else {
// AOT app compilation. Try to lookup the string without allocating if not found.
string = class_linker->LookupString(dex_file, string_index, dex_cache.Get());
- if (string != nullptr &&
- runtime->GetHeap()->ObjectIsInBootImageSpace(string) &&
- !codegen_->GetCompilerOptions().GetCompilePic()) {
- desired_load_kind = HLoadString::LoadKind::kBootImageAddress;
+ if (string != nullptr && runtime->GetHeap()->ObjectIsInBootImageSpace(string)) {
+ if (codegen_->GetCompilerOptions().GetCompilePic()) {
+ desired_load_kind = HLoadString::LoadKind::kBootImageInternTable;
+ } else {
+ desired_load_kind = HLoadString::LoadKind::kBootImageAddress;
+ }
} else {
desired_load_kind = HLoadString::LoadKind::kBssEntry;
}
diff --git a/compiler/utils/assembler_test.h b/compiler/utils/assembler_test.h
index ef53d72..12954a4 100644
--- a/compiler/utils/assembler_test.h
+++ b/compiler/utils/assembler_test.h
@@ -46,7 +46,12 @@
// For use in the template as the default type to get a nonvector registers version.
struct NoVectorRegs {};
-template<typename Ass, typename Reg, typename FPReg, typename Imm, typename VecReg = NoVectorRegs>
+template<typename Ass,
+ typename Addr,
+ typename Reg,
+ typename FPReg,
+ typename Imm,
+ typename VecReg = NoVectorRegs>
class AssemblerTest : public testing::Test {
public:
Ass* GetAssembler() {
@@ -64,6 +69,10 @@
DriverWrapper(assembly_string, test_name);
}
+ //
+ // Register repeats.
+ //
+
std::string RepeatR(void (Ass::*f)(Reg), const std::string& fmt) {
return RepeatTemplatedRegister<Reg>(f,
GetRegisters(),
@@ -159,7 +168,9 @@
for (auto reg2 : reg2_registers) {
for (int64_t imm : imms) {
ImmType new_imm = CreateImmediate(imm);
- (assembler_.get()->*f)(*reg1, *reg2, new_imm * multiplier + bias);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(*reg1, *reg2, new_imm * multiplier + bias);
+ }
std::string base = fmt;
std::string reg1_string = (this->*GetName1)(*reg1);
@@ -213,7 +224,9 @@
for (auto reg3 : reg3_registers) {
for (int64_t imm : imms) {
ImmType new_imm = CreateImmediate(imm);
- (assembler_.get()->*f)(*reg1, *reg2, *reg3, new_imm + bias);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(*reg1, *reg2, *reg3, new_imm + bias);
+ }
std::string base = fmt;
std::string reg1_string = (this->*GetName1)(*reg1);
@@ -272,7 +285,9 @@
for (auto reg2 : reg2_registers) {
for (int64_t imm : imms) {
ImmType new_imm = CreateImmediate(imm);
- (assembler_.get()->*f)(new_imm, *reg1, *reg2);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(new_imm, *reg1, *reg2);
+ }
std::string base = fmt;
std::string reg1_string = (this->*GetName1)(*reg1);
@@ -320,7 +335,9 @@
for (auto reg : registers) {
for (int64_t imm : imms) {
ImmType new_imm = CreateImmediate(imm);
- (assembler_.get()->*f)(*reg, new_imm + bias);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(*reg, new_imm + bias);
+ }
std::string base = fmt;
std::string reg_string = (this->*GetName)(*reg);
@@ -522,7 +539,9 @@
for (int64_t imm : imms) {
Imm new_imm = CreateImmediate(imm);
- (assembler_.get()->*f)(new_imm);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(new_imm);
+ }
std::string base = fmt;
size_t imm_index = base.find(IMM_TOKEN);
@@ -626,13 +645,23 @@
// The following functions are public so that TestFn can use them...
+ // Returns a vector of address used by any of the repeat methods
+ // involving an "A" (e.g. RepeatA).
+ virtual std::vector<Addr> GetAddresses() = 0;
+
+ // Returns a vector of registers used by any of the repeat methods
+ // involving an "R" (e.g. RepeatR).
virtual std::vector<Reg*> GetRegisters() = 0;
+ // Returns a vector of fp-registers used by any of the repeat methods
+ // involving an "F" (e.g. RepeatFF).
virtual std::vector<FPReg*> GetFPRegisters() {
UNIMPLEMENTED(FATAL) << "Architecture does not support floating-point registers";
UNREACHABLE();
}
+ // Returns a vector of dedicated simd-registers used by any of the repeat
+ // methods involving an "V" (e.g. RepeatVV).
virtual std::vector<VecReg*> GetVectorRegisters() {
UNIMPLEMENTED(FATAL) << "Architecture does not support vector registers";
UNREACHABLE();
@@ -828,6 +857,268 @@
// Create an immediate from the specific value.
virtual Imm CreateImmediate(int64_t imm_value) = 0;
+ //
+ // Addresses repeats.
+ //
+
+ // Repeats over addresses provided by fixture.
+ std::string RepeatA(void (Ass::*f)(const Addr&), const std::string& fmt) {
+ return RepeatA(f, GetAddresses(), fmt);
+ }
+
+ // Variant that takes explicit vector of addresss
+ // (to test restricted addressing modes set).
+ std::string RepeatA(void (Ass::*f)(const Addr&),
+ const std::vector<Addr>& a,
+ const std::string& fmt) {
+ return RepeatTemplatedMem<Addr>(f, a, &AssemblerTest::GetAddrName, fmt);
+ }
+
+ // Repeats over addresses and immediates provided by fixture.
+ std::string RepeatAI(void (Ass::*f)(const Addr&, const Imm&),
+ size_t imm_bytes,
+ const std::string& fmt) {
+ return RepeatAI(f, imm_bytes, GetAddresses(), fmt);
+ }
+
+ // Variant that takes explicit vector of addresss
+ // (to test restricted addressing modes set).
+ std::string RepeatAI(void (Ass::*f)(const Addr&, const Imm&),
+ size_t imm_bytes,
+ const std::vector<Addr>& a,
+ const std::string& fmt) {
+ return RepeatTemplatedMemImm<Addr>(f, imm_bytes, a, &AssemblerTest::GetAddrName, fmt);
+ }
+
+ // Repeats over registers and addresses provided by fixture.
+ std::string RepeatRA(void (Ass::*f)(Reg, const Addr&), const std::string& fmt) {
+ return RepeatRA(f, GetAddresses(), fmt);
+ }
+
+ // Variant that takes explicit vector of addresss
+ // (to test restricted addressing modes set).
+ std::string RepeatRA(void (Ass::*f)(Reg, const Addr&),
+ const std::vector<Addr>& a,
+ const std::string& fmt) {
+ return RepeatTemplatedRegMem<Reg, Addr>(
+ f,
+ GetRegisters(),
+ a,
+ &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
+ &AssemblerTest::GetAddrName,
+ fmt);
+ }
+
+ // Repeats over fp-registers and addresses provided by fixture.
+ std::string RepeatFA(void (Ass::*f)(FPReg, const Addr&), const std::string& fmt) {
+ return RepeatFA(f, GetAddresses(), fmt);
+ }
+
+ // Variant that takes explicit vector of addresss
+ // (to test restricted addressing modes set).
+ std::string RepeatFA(void (Ass::*f)(FPReg, const Addr&),
+ const std::vector<Addr>& a,
+ const std::string& fmt) {
+ return RepeatTemplatedRegMem<FPReg, Addr>(
+ f,
+ GetFPRegisters(),
+ a,
+ &AssemblerTest::GetFPRegName,
+ &AssemblerTest::GetAddrName,
+ fmt);
+ }
+
+ // Repeats over addresses and registers provided by fixture.
+ std::string RepeatAR(void (Ass::*f)(const Addr&, Reg), const std::string& fmt) {
+ return RepeatAR(f, GetAddresses(), fmt);
+ }
+
+ // Variant that takes explicit vector of addresss
+ // (to test restricted addressing modes set).
+ std::string RepeatAR(void (Ass::*f)(const Addr&, Reg),
+ const std::vector<Addr>& a,
+ const std::string& fmt) {
+ return RepeatTemplatedMemReg<Addr, Reg>(
+ f,
+ a,
+ GetRegisters(),
+ &AssemblerTest::GetAddrName,
+ &AssemblerTest::GetRegName<RegisterView::kUsePrimaryName>,
+ fmt);
+ }
+
+ // Repeats over addresses and fp-registers provided by fixture.
+ std::string RepeatAF(void (Ass::*f)(const Addr&, FPReg), const std::string& fmt) {
+ return RepeatAF(f, GetAddresses(), fmt);
+ }
+
+ // Variant that takes explicit vector of addresss
+ // (to test restricted addressing modes set).
+ std::string RepeatAF(void (Ass::*f)(const Addr&, FPReg),
+ const std::vector<Addr>& a,
+ const std::string& fmt) {
+ return RepeatTemplatedMemReg<Addr, FPReg>(
+ f,
+ a,
+ GetFPRegisters(),
+ &AssemblerTest::GetAddrName,
+ &AssemblerTest::GetFPRegName,
+ fmt);
+ }
+
+ template <typename AddrType>
+ std::string RepeatTemplatedMem(void (Ass::*f)(const AddrType&),
+ const std::vector<AddrType> addresses,
+ std::string (AssemblerTest::*GetAName)(const AddrType&),
+ const std::string& fmt) {
+ WarnOnCombinations(addresses.size());
+ std::string str;
+ for (auto addr : addresses) {
+ if (f != nullptr) {
+ (assembler_.get()->*f)(addr);
+ }
+ std::string base = fmt;
+
+ std::string addr_string = (this->*GetAName)(addr);
+ size_t addr_index;
+ if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) {
+ base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string);
+ }
+
+ if (str.size() > 0) {
+ str += "\n";
+ }
+ str += base;
+ }
+ // Add a newline at the end.
+ str += "\n";
+ return str;
+ }
+
+ template <typename AddrType>
+ std::string RepeatTemplatedMemImm(void (Ass::*f)(const AddrType&, const Imm&),
+ size_t imm_bytes,
+ const std::vector<AddrType> addresses,
+ std::string (AssemblerTest::*GetAName)(const AddrType&),
+ const std::string& fmt) {
+ std::vector<int64_t> imms = CreateImmediateValues(imm_bytes);
+ WarnOnCombinations(addresses.size() * imms.size());
+ std::string str;
+ for (auto addr : addresses) {
+ for (int64_t imm : imms) {
+ Imm new_imm = CreateImmediate(imm);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(addr, new_imm);
+ }
+ std::string base = fmt;
+
+ std::string addr_string = (this->*GetAName)(addr);
+ size_t addr_index;
+ if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) {
+ base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string);
+ }
+
+ size_t imm_index = base.find(IMM_TOKEN);
+ if (imm_index != std::string::npos) {
+ std::ostringstream sreg;
+ sreg << imm;
+ std::string imm_string = sreg.str();
+ base.replace(imm_index, ConstexprStrLen(IMM_TOKEN), imm_string);
+ }
+
+ if (str.size() > 0) {
+ str += "\n";
+ }
+ str += base;
+ }
+ }
+ // Add a newline at the end.
+ str += "\n";
+ return str;
+ }
+
+ template <typename RegType, typename AddrType>
+ std::string RepeatTemplatedRegMem(void (Ass::*f)(RegType, const AddrType&),
+ const std::vector<RegType*> registers,
+ const std::vector<AddrType> addresses,
+ std::string (AssemblerTest::*GetRName)(const RegType&),
+ std::string (AssemblerTest::*GetAName)(const AddrType&),
+ const std::string& fmt) {
+ WarnOnCombinations(addresses.size() * registers.size());
+ std::string str;
+ for (auto reg : registers) {
+ for (auto addr : addresses) {
+ if (f != nullptr) {
+ (assembler_.get()->*f)(*reg, addr);
+ }
+ std::string base = fmt;
+
+ std::string reg_string = (this->*GetRName)(*reg);
+ size_t reg_index;
+ if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
+ base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
+ }
+
+ std::string addr_string = (this->*GetAName)(addr);
+ size_t addr_index;
+ if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) {
+ base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string);
+ }
+
+ if (str.size() > 0) {
+ str += "\n";
+ }
+ str += base;
+ }
+ }
+ // Add a newline at the end.
+ str += "\n";
+ return str;
+ }
+
+ template <typename AddrType, typename RegType>
+ std::string RepeatTemplatedMemReg(void (Ass::*f)(const AddrType&, RegType),
+ const std::vector<AddrType> addresses,
+ const std::vector<RegType*> registers,
+ std::string (AssemblerTest::*GetAName)(const AddrType&),
+ std::string (AssemblerTest::*GetRName)(const RegType&),
+ const std::string& fmt) {
+ WarnOnCombinations(addresses.size() * registers.size());
+ std::string str;
+ for (auto addr : addresses) {
+ for (auto reg : registers) {
+ if (f != nullptr) {
+ (assembler_.get()->*f)(addr, *reg);
+ }
+ std::string base = fmt;
+
+ std::string addr_string = (this->*GetAName)(addr);
+ size_t addr_index;
+ if ((addr_index = base.find(ADDRESS_TOKEN)) != std::string::npos) {
+ base.replace(addr_index, ConstexprStrLen(ADDRESS_TOKEN), addr_string);
+ }
+
+ std::string reg_string = (this->*GetRName)(*reg);
+ size_t reg_index;
+ if ((reg_index = base.find(REG_TOKEN)) != std::string::npos) {
+ base.replace(reg_index, ConstexprStrLen(REG_TOKEN), reg_string);
+ }
+
+ if (str.size() > 0) {
+ str += "\n";
+ }
+ str += base;
+ }
+ }
+ // Add a newline at the end.
+ str += "\n";
+ return str;
+ }
+
+ //
+ // Register repeats.
+ //
+
template <typename RegType>
std::string RepeatTemplatedRegister(void (Ass::*f)(RegType),
const std::vector<RegType*> registers,
@@ -835,7 +1126,9 @@
const std::string& fmt) {
std::string str;
for (auto reg : registers) {
- (assembler_.get()->*f)(*reg);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(*reg);
+ }
std::string base = fmt;
std::string reg_string = (this->*GetName)(*reg);
@@ -866,7 +1159,9 @@
std::string str;
for (auto reg1 : reg1_registers) {
for (auto reg2 : reg2_registers) {
- (assembler_.get()->*f)(*reg1, *reg2);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(*reg1, *reg2);
+ }
std::string base = fmt;
std::string reg1_string = (this->*GetName1)(*reg1);
@@ -905,7 +1200,9 @@
for (auto reg1 : reg1_registers) {
for (auto reg2 : reg2_registers) {
if (reg1 == reg2) continue;
- (assembler_.get()->*f)(*reg1, *reg2);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(*reg1, *reg2);
+ }
std::string base = fmt;
std::string reg1_string = (this->*GetName1)(*reg1);
@@ -944,7 +1241,9 @@
for (auto reg1 : reg1_registers) {
for (auto reg2 : reg2_registers) {
for (auto reg3 : reg3_registers) {
- (assembler_.get()->*f)(*reg1, *reg2, *reg3);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(*reg1, *reg2, *reg3);
+ }
std::string base = fmt;
std::string reg1_string = (this->*GetName1)(*reg1);
@@ -993,7 +1292,9 @@
for (auto reg2 : reg2_registers) {
for (int64_t imm : imms) {
Imm new_imm = CreateImmediate(imm);
- (assembler_.get()->*f)(*reg1, *reg2, new_imm);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(*reg1, *reg2, new_imm);
+ }
std::string base = fmt;
std::string reg1_string = (this->*GetName1)(*reg1);
@@ -1028,6 +1329,12 @@
return str;
}
+ std::string GetAddrName(const Addr& addr) {
+ std::ostringstream saddr;
+ saddr << addr;
+ return saddr.str();
+ }
+
template <RegisterView kRegView>
std::string GetRegName(const Reg& reg) {
std::ostringstream sreg;
@@ -1074,6 +1381,7 @@
}
}
+ static constexpr const char* ADDRESS_TOKEN = "{mem}";
static constexpr const char* REG_TOKEN = "{reg}";
static constexpr const char* REG1_TOKEN = "{reg1}";
static constexpr const char* REG2_TOKEN = "{reg2}";
@@ -1094,7 +1402,9 @@
for (auto reg : registers) {
for (int64_t imm : imms) {
Imm new_imm = CreateImmediate(imm);
- (assembler_.get()->*f)(*reg, new_imm);
+ if (f != nullptr) {
+ (assembler_.get()->*f)(*reg, new_imm);
+ }
std::string base = fmt;
std::string reg_string = GetRegName<kRegView>(*reg);
diff --git a/compiler/utils/mips/assembler_mips32r5_test.cc b/compiler/utils/mips/assembler_mips32r5_test.cc
index 24b09b5..a3662db 100644
--- a/compiler/utils/mips/assembler_mips32r5_test.cc
+++ b/compiler/utils/mips/assembler_mips32r5_test.cc
@@ -32,12 +32,14 @@
};
class AssemblerMIPS32r5Test : public AssemblerTest<mips::MipsAssembler,
+ mips::MipsLabel,
mips::Register,
mips::FRegister,
uint32_t,
mips::VectorRegister> {
public:
typedef AssemblerTest<mips::MipsAssembler,
+ mips::MipsLabel,
mips::Register,
mips::FRegister,
uint32_t,
@@ -217,6 +219,11 @@
STLDeleteElements(&vec_registers_);
}
+ std::vector<mips::MipsLabel> GetAddresses() {
+ UNIMPLEMENTED(FATAL) << "Feature not implemented yet";
+ UNREACHABLE();
+ }
+
std::vector<mips::Register*> GetRegisters() OVERRIDE {
return registers_;
}
diff --git a/compiler/utils/mips/assembler_mips32r6_test.cc b/compiler/utils/mips/assembler_mips32r6_test.cc
index a5cd5a7..b6cb30a 100644
--- a/compiler/utils/mips/assembler_mips32r6_test.cc
+++ b/compiler/utils/mips/assembler_mips32r6_test.cc
@@ -32,12 +32,14 @@
};
class AssemblerMIPS32r6Test : public AssemblerTest<mips::MipsAssembler,
+ mips::MipsLabel,
mips::Register,
mips::FRegister,
uint32_t,
mips::VectorRegister> {
public:
typedef AssemblerTest<mips::MipsAssembler,
+ mips::MipsLabel,
mips::Register,
mips::FRegister,
uint32_t,
@@ -230,6 +232,11 @@
STLDeleteElements(&vec_registers_);
}
+ std::vector<mips::MipsLabel> GetAddresses() {
+ UNIMPLEMENTED(FATAL) << "Feature not implemented yet";
+ UNREACHABLE();
+ }
+
std::vector<mips::Register*> GetRegisters() OVERRIDE {
return registers_;
}
diff --git a/compiler/utils/mips/assembler_mips_test.cc b/compiler/utils/mips/assembler_mips_test.cc
index 680c347..eed83a5 100644
--- a/compiler/utils/mips/assembler_mips_test.cc
+++ b/compiler/utils/mips/assembler_mips_test.cc
@@ -32,11 +32,16 @@
};
class AssemblerMIPSTest : public AssemblerTest<mips::MipsAssembler,
+ mips::MipsLabel,
mips::Register,
mips::FRegister,
uint32_t> {
public:
- typedef AssemblerTest<mips::MipsAssembler, mips::Register, mips::FRegister, uint32_t> Base;
+ typedef AssemblerTest<mips::MipsAssembler,
+ mips::MipsLabel,
+ mips::Register,
+ mips::FRegister,
+ uint32_t> Base;
protected:
// Get the typically used name for this architecture, e.g., aarch64, x86-64, ...
@@ -161,6 +166,11 @@
STLDeleteElements(&fp_registers_);
}
+ std::vector<mips::MipsLabel> GetAddresses() {
+ UNIMPLEMENTED(FATAL) << "Feature not implemented yet";
+ UNREACHABLE();
+ }
+
std::vector<mips::Register*> GetRegisters() OVERRIDE {
return registers_;
}
diff --git a/compiler/utils/mips64/assembler_mips64.h b/compiler/utils/mips64/assembler_mips64.h
index 4c8fb68..dd6dcd1 100644
--- a/compiler/utils/mips64/assembler_mips64.h
+++ b/compiler/utils/mips64/assembler_mips64.h
@@ -86,7 +86,7 @@
int32_t y = High32Bits(value);
if (x == y) {
- return (IsUint<16>(x) || IsInt<16>(x) || ((x & 0xFFFF) == 0 && IsInt<16>(value >> 16))) ? 2 : 3;
+ return (IsUint<16>(x) || IsInt<16>(x) || ((x & 0xFFFF) == 0)) ? 2 : 3;
}
return INT_MAX;
diff --git a/compiler/utils/mips64/assembler_mips64_test.cc b/compiler/utils/mips64/assembler_mips64_test.cc
index fc0bd36..16a36f9 100644
--- a/compiler/utils/mips64/assembler_mips64_test.cc
+++ b/compiler/utils/mips64/assembler_mips64_test.cc
@@ -35,12 +35,14 @@
};
class AssemblerMIPS64Test : public AssemblerTest<mips64::Mips64Assembler,
+ mips64::Mips64Label,
mips64::GpuRegister,
mips64::FpuRegister,
uint32_t,
mips64::VectorRegister> {
public:
typedef AssemblerTest<mips64::Mips64Assembler,
+ mips64::Mips64Label,
mips64::GpuRegister,
mips64::FpuRegister,
uint32_t,
@@ -228,6 +230,11 @@
STLDeleteElements(&vec_registers_);
}
+ std::vector<mips64::Mips64Label> GetAddresses() {
+ UNIMPLEMENTED(FATAL) << "Feature not implemented yet";
+ UNREACHABLE();
+ }
+
std::vector<mips64::GpuRegister*> GetRegisters() OVERRIDE {
return registers_;
}
diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc
index b89af10..3162a32 100644
--- a/compiler/utils/x86/assembler_x86.cc
+++ b/compiler/utils/x86/assembler_x86.cc
@@ -32,6 +32,33 @@
return os << "ST" << static_cast<int>(reg);
}
+std::ostream& operator<<(std::ostream& os, const Address& addr) {
+ switch (addr.mod()) {
+ case 0:
+ if (addr.rm() == ESP && addr.index() != ESP) {
+ return os << "(%" << addr.base() << ",%"
+ << addr.index() << "," << (1 << addr.scale()) << ")";
+ }
+ return os << "(%" << addr.rm() << ")";
+ case 1:
+ if (addr.rm() == ESP && addr.index() != ESP) {
+ return os << static_cast<int>(addr.disp8())
+ << "(%" << addr.base() << ",%"
+ << addr.index() << "," << (1 << addr.scale()) << ")";
+ }
+ return os << static_cast<int>(addr.disp8()) << "(%" << addr.rm() << ")";
+ case 2:
+ if (addr.rm() == ESP && addr.index() != ESP) {
+ return os << static_cast<int>(addr.disp32())
+ << "(%" << addr.base() << ",%"
+ << addr.index() << "," << (1 << addr.scale()) << ")";
+ }
+ return os << static_cast<int>(addr.disp32()) << "(%" << addr.rm() << ")";
+ default:
+ return os << "<address?>";
+ }
+}
+
void X86Assembler::call(Register reg) {
AssemblerBuffer::EnsureCapacity ensured(&buffer_);
EmitUint8(0xFF);
diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h
index 511eeb9..2964dba 100644
--- a/compiler/utils/x86/assembler_x86.h
+++ b/compiler/utils/x86/assembler_x86.h
@@ -235,6 +235,7 @@
}
};
+std::ostream& operator<<(std::ostream& os, const Address& addr);
// This is equivalent to the Label class, used in a slightly different context. We
// inherit the functionality of the Label class, but prevent unintended
diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc
index d2122db..c28ed3b 100644
--- a/compiler/utils/x86/assembler_x86_test.cc
+++ b/compiler/utils/x86/assembler_x86_test.cc
@@ -33,11 +33,21 @@
ASSERT_EQ(static_cast<size_t>(5), buffer.Size());
}
-class AssemblerX86Test : public AssemblerTest<x86::X86Assembler, x86::Register,
- x86::XmmRegister, x86::Immediate> {
+//
+// Test fixture.
+//
+
+class AssemblerX86Test : public AssemblerTest<x86::X86Assembler,
+ x86::Address,
+ x86::Register,
+ x86::XmmRegister,
+ x86::Immediate> {
public:
- typedef AssemblerTest<x86::X86Assembler, x86::Register,
- x86::XmmRegister, x86::Immediate> Base;
+ typedef AssemblerTest<x86::X86Assembler,
+ x86::Address,
+ x86::Register,
+ x86::XmmRegister,
+ x86::Immediate> Base;
protected:
std::string GetArchitectureString() OVERRIDE {
@@ -53,6 +63,31 @@
}
void SetUpHelpers() OVERRIDE {
+ if (addresses_singleton_.size() == 0) {
+ // One addressing mode to test the repeat drivers.
+ addresses_singleton_.push_back(x86::Address(x86::EAX, x86::EBX, x86::TIMES_1, 2));
+ }
+
+ if (addresses_.size() == 0) {
+ // Several addressing modes.
+ addresses_.push_back(x86::Address(x86::EDI, x86::EAX, x86::TIMES_1, 15));
+ addresses_.push_back(x86::Address(x86::EDI, x86::EBX, x86::TIMES_2, 16));
+ addresses_.push_back(x86::Address(x86::EDI, x86::ECX, x86::TIMES_4, 17));
+ addresses_.push_back(x86::Address(x86::EDI, x86::EDX, x86::TIMES_8, 18));
+ addresses_.push_back(x86::Address(x86::EAX, -1));
+ addresses_.push_back(x86::Address(x86::EBX, 0));
+ addresses_.push_back(x86::Address(x86::ESI, 1));
+ addresses_.push_back(x86::Address(x86::EDI, 987654321));
+ // Several addressing modes with the special ESP.
+ addresses_.push_back(x86::Address(x86::ESP, x86::EAX, x86::TIMES_1, 15));
+ addresses_.push_back(x86::Address(x86::ESP, x86::EBX, x86::TIMES_2, 16));
+ addresses_.push_back(x86::Address(x86::ESP, x86::ECX, x86::TIMES_4, 17));
+ addresses_.push_back(x86::Address(x86::ESP, x86::EDX, x86::TIMES_8, 18));
+ addresses_.push_back(x86::Address(x86::ESP, -1));
+ addresses_.push_back(x86::Address(x86::ESP, 0));
+ addresses_.push_back(x86::Address(x86::ESP, 1));
+ addresses_.push_back(x86::Address(x86::ESP, 987654321));
+ }
if (registers_.size() == 0) {
registers_.insert(end(registers_),
{ // NOLINT(whitespace/braces)
@@ -88,6 +123,10 @@
STLDeleteElements(&fp_registers_);
}
+ std::vector<x86::Address> GetAddresses() OVERRIDE {
+ return addresses_;
+ }
+
std::vector<x86::Register*> GetRegisters() OVERRIDE {
return registers_;
}
@@ -100,26 +139,116 @@
return x86::Immediate(imm_value);
}
+ std::vector<x86::Address> addresses_singleton_;
+
private:
+ std::vector<x86::Address> addresses_;
std::vector<x86::Register*> registers_;
std::vector<x86::XmmRegister*> fp_registers_;
};
+//
+// Test repeat drivers used in the tests.
+//
+
+TEST_F(AssemblerX86Test, RepeatRR) {
+ EXPECT_EQ("%eax %eax\n%eax %ebx\n%eax %ecx\n%eax %edx\n%eax %ebp\n%eax %esp\n%eax %esi\n"
+ "%eax %edi\n%ebx %eax\n%ebx %ebx\n%ebx %ecx\n%ebx %edx\n%ebx %ebp\n%ebx %esp\n"
+ "%ebx %esi\n%ebx %edi\n%ecx %eax\n%ecx %ebx\n%ecx %ecx\n%ecx %edx\n%ecx %ebp\n"
+ "%ecx %esp\n%ecx %esi\n%ecx %edi\n%edx %eax\n%edx %ebx\n%edx %ecx\n%edx %edx\n"
+ "%edx %ebp\n%edx %esp\n%edx %esi\n%edx %edi\n%ebp %eax\n%ebp %ebx\n%ebp %ecx\n"
+ "%ebp %edx\n%ebp %ebp\n%ebp %esp\n%ebp %esi\n%ebp %edi\n%esp %eax\n%esp %ebx\n"
+ "%esp %ecx\n%esp %edx\n%esp %ebp\n%esp %esp\n%esp %esi\n%esp %edi\n%esi %eax\n"
+ "%esi %ebx\n%esi %ecx\n%esi %edx\n%esi %ebp\n%esi %esp\n%esi %esi\n%esi %edi\n"
+ "%edi %eax\n%edi %ebx\n%edi %ecx\n%edi %edx\n%edi %ebp\n%edi %esp\n%edi %esi\n"
+ "%edi %edi\n",
+ RepeatRR(/*f*/ nullptr, "%{reg1} %{reg2}"));
+}
+
+TEST_F(AssemblerX86Test, RepeatRI) {
+ EXPECT_EQ("%eax $0\n%eax $-1\n%eax $18\n%ebx $0\n%ebx $-1\n%ebx $18\n%ecx $0\n%ecx $-1\n"
+ "%ecx $18\n%edx $0\n%edx $-1\n%edx $18\n%ebp $0\n%ebp $-1\n%ebp $18\n%esp $0\n"
+ "%esp $-1\n%esp $18\n%esi $0\n%esi $-1\n%esi $18\n%edi $0\n%edi $-1\n%edi $18\n",
+ RepeatRI(/*f*/ nullptr, /*imm_bytes*/ 1U, "%{reg} ${imm}"));
+}
+
+TEST_F(AssemblerX86Test, RepeatFF) {
+ EXPECT_EQ("%XMM0 %XMM0\n%XMM0 %XMM1\n%XMM0 %XMM2\n%XMM0 %XMM3\n%XMM0 %XMM4\n%XMM0 %XMM5\n"
+ "%XMM0 %XMM6\n%XMM0 %XMM7\n%XMM1 %XMM0\n%XMM1 %XMM1\n%XMM1 %XMM2\n%XMM1 %XMM3\n"
+ "%XMM1 %XMM4\n%XMM1 %XMM5\n%XMM1 %XMM6\n%XMM1 %XMM7\n%XMM2 %XMM0\n%XMM2 %XMM1\n"
+ "%XMM2 %XMM2\n%XMM2 %XMM3\n%XMM2 %XMM4\n%XMM2 %XMM5\n%XMM2 %XMM6\n%XMM2 %XMM7\n"
+ "%XMM3 %XMM0\n%XMM3 %XMM1\n%XMM3 %XMM2\n%XMM3 %XMM3\n%XMM3 %XMM4\n%XMM3 %XMM5\n"
+ "%XMM3 %XMM6\n%XMM3 %XMM7\n%XMM4 %XMM0\n%XMM4 %XMM1\n%XMM4 %XMM2\n%XMM4 %XMM3\n"
+ "%XMM4 %XMM4\n%XMM4 %XMM5\n%XMM4 %XMM6\n%XMM4 %XMM7\n%XMM5 %XMM0\n%XMM5 %XMM1\n"
+ "%XMM5 %XMM2\n%XMM5 %XMM3\n%XMM5 %XMM4\n%XMM5 %XMM5\n%XMM5 %XMM6\n%XMM5 %XMM7\n"
+ "%XMM6 %XMM0\n%XMM6 %XMM1\n%XMM6 %XMM2\n%XMM6 %XMM3\n%XMM6 %XMM4\n%XMM6 %XMM5\n"
+ "%XMM6 %XMM6\n%XMM6 %XMM7\n%XMM7 %XMM0\n%XMM7 %XMM1\n%XMM7 %XMM2\n%XMM7 %XMM3\n"
+ "%XMM7 %XMM4\n%XMM7 %XMM5\n%XMM7 %XMM6\n%XMM7 %XMM7\n",
+ RepeatFF(/*f*/ nullptr, "%{reg1} %{reg2}"));
+}
+
+TEST_F(AssemblerX86Test, RepeatFFI) {
+ EXPECT_NE(RepeatFFI(/*f*/ nullptr, /*imm_bytes*/ 1U, "%{reg1} %{reg2} ${imm}")
+ .find("%XMM0 %XMM0 $0\n%XMM0 %XMM0 $-1\n%XMM0 %XMM0 $18\n"
+ "%XMM0 %XMM1 $0\n%XMM0 %XMM1 $-1\n%XMM0 %XMM1 $18\n"),
+ std::string::npos);
+}
+
+TEST_F(AssemblerX86Test, RepeatA) {
+ EXPECT_EQ("2(%eax,%ebx,1)\n", RepeatA(/*f*/ nullptr, addresses_singleton_, "{mem}"));
+}
+
+TEST_F(AssemblerX86Test, RepeatAI) {
+ EXPECT_EQ("2(%eax,%ebx,1) $0\n2(%eax,%ebx,1) $-1\n2(%eax,%ebx,1) $18\n",
+ RepeatAI(/*f*/ nullptr, /*imm_bytes*/ 1U, addresses_singleton_, "{mem} ${imm}"));
+}
+
+TEST_F(AssemblerX86Test, RepeatRA) {
+ EXPECT_EQ("%eax 2(%eax,%ebx,1)\n%ebx 2(%eax,%ebx,1)\n%ecx 2(%eax,%ebx,1)\n"
+ "%edx 2(%eax,%ebx,1)\n%ebp 2(%eax,%ebx,1)\n%esp 2(%eax,%ebx,1)\n"
+ "%esi 2(%eax,%ebx,1)\n%edi 2(%eax,%ebx,1)\n",
+ RepeatRA(/*f*/ nullptr, addresses_singleton_, "%{reg} {mem}"));
+}
+
+TEST_F(AssemblerX86Test, RepeatAR) {
+ EXPECT_EQ("2(%eax,%ebx,1) %eax\n2(%eax,%ebx,1) %ebx\n2(%eax,%ebx,1) %ecx\n"
+ "2(%eax,%ebx,1) %edx\n2(%eax,%ebx,1) %ebp\n2(%eax,%ebx,1) %esp\n"
+ "2(%eax,%ebx,1) %esi\n2(%eax,%ebx,1) %edi\n",
+ RepeatAR(/*f*/ nullptr, addresses_singleton_, "{mem} %{reg}"));
+}
+
+TEST_F(AssemblerX86Test, RepeatFA) {
+ EXPECT_EQ("%XMM0 2(%eax,%ebx,1)\n%XMM1 2(%eax,%ebx,1)\n%XMM2 2(%eax,%ebx,1)\n"
+ "%XMM3 2(%eax,%ebx,1)\n%XMM4 2(%eax,%ebx,1)\n%XMM5 2(%eax,%ebx,1)\n"
+ "%XMM6 2(%eax,%ebx,1)\n%XMM7 2(%eax,%ebx,1)\n",
+ RepeatFA(/*f*/ nullptr, addresses_singleton_, "%{reg} {mem}"));
+}
+
+TEST_F(AssemblerX86Test, RepeatAF) {
+ EXPECT_EQ("2(%eax,%ebx,1) %XMM0\n2(%eax,%ebx,1) %XMM1\n2(%eax,%ebx,1) %XMM2\n"
+ "2(%eax,%ebx,1) %XMM3\n2(%eax,%ebx,1) %XMM4\n2(%eax,%ebx,1) %XMM5\n"
+ "2(%eax,%ebx,1) %XMM6\n2(%eax,%ebx,1) %XMM7\n",
+ RepeatAF(/*f*/ nullptr, addresses_singleton_, "{mem} %{reg}"));
+}
+
+//
+// Actual x86 instruction assembler tests.
+//
TEST_F(AssemblerX86Test, Movl) {
- GetAssembler()->movl(x86::EAX, x86::EBX);
- const char* expected = "mov %ebx, %eax\n";
- DriverStr(expected, "movl");
+ DriverStr(RepeatRR(&x86::X86Assembler::movl, "movl %{reg2}, %{reg1}"), "movl");
+}
+
+TEST_F(AssemblerX86Test, MovlLoad) {
+ DriverStr(RepeatRA(&x86::X86Assembler::movl, "movl {mem}, %{reg}"), "movl-load");
+}
+
+TEST_F(AssemblerX86Test, MovlStore) {
+ DriverStr(RepeatAR(&x86::X86Assembler::movl, "movl %{reg}, {mem}"), "movl-store");
}
TEST_F(AssemblerX86Test, Movntl) {
- GetAssembler()->movntl(x86::Address(x86::EDI, x86::EBX, x86::TIMES_4, 12), x86::EAX);
- GetAssembler()->movntl(x86::Address(x86::EDI, 0), x86::EAX);
- const char* expected =
- "movntil %EAX, 0xc(%EDI,%EBX,4)\n"
- "movntil %EAX, (%EDI)\n";
-
- DriverStr(expected, "movntl");
+ DriverStr(RepeatAR(&x86::X86Assembler::movntl, "movntil %{reg}, {mem}"), "movntl");
}
TEST_F(AssemblerX86Test, LoadLongConstant) {
@@ -133,66 +262,29 @@
}
TEST_F(AssemblerX86Test, LockCmpxchgl) {
- GetAssembler()->LockCmpxchgl(x86::Address(
- x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12),
- x86::Register(x86::ESI));
- GetAssembler()->LockCmpxchgl(x86::Address(
- x86::Register(x86::EDI), x86::Register(x86::ESI), x86::TIMES_4, 12),
- x86::Register(x86::ESI));
- GetAssembler()->LockCmpxchgl(x86::Address(
- x86::Register(x86::EDI), x86::Register(x86::ESI), x86::TIMES_4, 12),
- x86::Register(x86::EDI));
- GetAssembler()->LockCmpxchgl(x86::Address(
- x86::Register(x86::EBP), 0), x86::Register(x86::ESI));
- GetAssembler()->LockCmpxchgl(x86::Address(
- x86::Register(x86::EBP), x86::Register(x86::ESI), x86::TIMES_1, 0),
- x86::Register(x86::ESI));
- const char* expected =
- "lock cmpxchgl %ESI, 0xc(%EDI,%EBX,4)\n"
- "lock cmpxchgl %ESI, 0xc(%EDI,%ESI,4)\n"
- "lock cmpxchgl %EDI, 0xc(%EDI,%ESI,4)\n"
- "lock cmpxchgl %ESI, (%EBP)\n"
- "lock cmpxchgl %ESI, (%EBP,%ESI,1)\n";
-
- DriverStr(expected, "lock_cmpxchgl");
+ DriverStr(RepeatAR(&x86::X86Assembler::LockCmpxchgl,
+ "lock cmpxchgl %{reg}, {mem}"), "lock_cmpxchgl");
}
TEST_F(AssemblerX86Test, LockCmpxchg8b) {
- GetAssembler()->LockCmpxchg8b(x86::Address(
- x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12));
- GetAssembler()->LockCmpxchg8b(x86::Address(
- x86::Register(x86::EDI), x86::Register(x86::ESI), x86::TIMES_4, 12));
- GetAssembler()->LockCmpxchg8b(x86::Address(
- x86::Register(x86::EDI), x86::Register(x86::ESI), x86::TIMES_4, 12));
- GetAssembler()->LockCmpxchg8b(x86::Address(x86::Register(x86::EBP), 0));
- GetAssembler()->LockCmpxchg8b(x86::Address(
- x86::Register(x86::EBP), x86::Register(x86::ESI), x86::TIMES_1, 0));
- const char* expected =
- "lock cmpxchg8b 0xc(%EDI,%EBX,4)\n"
- "lock cmpxchg8b 0xc(%EDI,%ESI,4)\n"
- "lock cmpxchg8b 0xc(%EDI,%ESI,4)\n"
- "lock cmpxchg8b (%EBP)\n"
- "lock cmpxchg8b (%EBP,%ESI,1)\n";
-
- DriverStr(expected, "lock_cmpxchg8b");
+ DriverStr(RepeatA(&x86::X86Assembler::LockCmpxchg8b,
+ "lock cmpxchg8b {mem}"), "lock_cmpxchg8b");
}
-TEST_F(AssemblerX86Test, FPUIntegerLoad) {
- GetAssembler()->filds(x86::Address(x86::Register(x86::ESP), 4));
- GetAssembler()->fildl(x86::Address(x86::Register(x86::ESP), 12));
- const char* expected =
- "fildl 0x4(%ESP)\n"
- "fildll 0xc(%ESP)\n";
- DriverStr(expected, "FPUIntegerLoad");
+TEST_F(AssemblerX86Test, FPUIntegerLoadS) {
+ DriverStr(RepeatA(&x86::X86Assembler::filds, "fildl {mem}"), "fildd");
}
-TEST_F(AssemblerX86Test, FPUIntegerStore) {
- GetAssembler()->fistps(x86::Address(x86::Register(x86::ESP), 16));
- GetAssembler()->fistpl(x86::Address(x86::Register(x86::ESP), 24));
- const char* expected =
- "fistpl 0x10(%ESP)\n"
- "fistpll 0x18(%ESP)\n";
- DriverStr(expected, "FPUIntegerStore");
+TEST_F(AssemblerX86Test, FPUIntegerLoadL) {
+ DriverStr(RepeatA(&x86::X86Assembler::fildl, "fildll {mem}"), "fildl");
+}
+
+TEST_F(AssemblerX86Test, FPUIntegerStoreS) {
+ DriverStr(RepeatA(&x86::X86Assembler::fistps, "fistpl {mem}"), "fistps");
+}
+
+TEST_F(AssemblerX86Test, FPUIntegerStoreL) {
+ DriverStr(RepeatA(&x86::X86Assembler::fistpl, "fistpll {mem}"), "fistpl");
}
TEST_F(AssemblerX86Test, Repnescasb) {
@@ -242,12 +334,7 @@
}
TEST_F(AssemblerX86Test, BsflAddress) {
- GetAssembler()->bsfl(x86::Register(x86::EDI), x86::Address(
- x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12));
- const char* expected =
- "bsfl 0xc(%EDI,%EBX,4), %EDI\n";
-
- DriverStr(expected, "bsfl_address");
+ DriverStr(RepeatRA(&x86::X86Assembler::bsfl, "bsfl {mem}, %{reg}"), "bsfl_address");
}
TEST_F(AssemblerX86Test, Bsrl) {
@@ -255,12 +342,7 @@
}
TEST_F(AssemblerX86Test, BsrlAddress) {
- GetAssembler()->bsrl(x86::Register(x86::EDI), x86::Address(
- x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12));
- const char* expected =
- "bsrl 0xc(%EDI,%EBX,4), %EDI\n";
-
- DriverStr(expected, "bsrl_address");
+ DriverStr(RepeatRA(&x86::X86Assembler::bsrl, "bsrl {mem}, %{reg}"), "bsrl_address");
}
TEST_F(AssemblerX86Test, Popcntl) {
@@ -268,26 +350,18 @@
}
TEST_F(AssemblerX86Test, PopcntlAddress) {
- GetAssembler()->popcntl(x86::Register(x86::EDI), x86::Address(
- x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12));
- const char* expected =
- "popcntl 0xc(%EDI,%EBX,4), %EDI\n";
-
- DriverStr(expected, "popcntl_address");
+ DriverStr(RepeatRA(&x86::X86Assembler::popcntl, "popcntl {mem}, %{reg}"), "popcntl_address");
}
// Rorl only allows CL as the shift count.
std::string rorl_fn(AssemblerX86Test::Base* assembler_test, x86::X86Assembler* assembler) {
std::ostringstream str;
-
std::vector<x86::Register*> registers = assembler_test->GetRegisters();
-
x86::Register shifter(x86::ECX);
for (auto reg : registers) {
assembler->rorl(*reg, shifter);
str << "rorl %cl, %" << assembler_test->GetRegisterName(*reg) << "\n";
}
-
return str.str();
}
@@ -302,15 +376,12 @@
// Roll only allows CL as the shift count.
std::string roll_fn(AssemblerX86Test::Base* assembler_test, x86::X86Assembler* assembler) {
std::ostringstream str;
-
std::vector<x86::Register*> registers = assembler_test->GetRegisters();
-
x86::Register shifter(x86::ECX);
for (auto reg : registers) {
assembler->roll(*reg, shifter);
str << "roll %cl, %" << assembler_test->GetRegisterName(*reg) << "\n";
}
-
return str.str();
}
@@ -331,41 +402,29 @@
}
TEST_F(AssemblerX86Test, ComissAddr) {
- GetAssembler()->comiss(x86::XmmRegister(x86::XMM0), x86::Address(x86::EAX, 0));
- const char* expected = "comiss 0(%EAX), %xmm0\n";
- DriverStr(expected, "comiss");
+ DriverStr(RepeatFA(&x86::X86Assembler::comiss, "comiss {mem}, %{reg}"), "comiss");
}
TEST_F(AssemblerX86Test, UComissAddr) {
- GetAssembler()->ucomiss(x86::XmmRegister(x86::XMM0), x86::Address(x86::EAX, 0));
- const char* expected = "ucomiss 0(%EAX), %xmm0\n";
- DriverStr(expected, "ucomiss");
+ DriverStr(RepeatFA(&x86::X86Assembler::ucomiss, "ucomiss {mem}, %{reg}"), "ucomiss");
}
TEST_F(AssemblerX86Test, ComisdAddr) {
- GetAssembler()->comisd(x86::XmmRegister(x86::XMM0), x86::Address(x86::EAX, 0));
- const char* expected = "comisd 0(%EAX), %xmm0\n";
- DriverStr(expected, "comisd");
+ DriverStr(RepeatFA(&x86::X86Assembler::comisd, "comisd {mem}, %{reg}"), "comisd");
}
TEST_F(AssemblerX86Test, UComisdAddr) {
- GetAssembler()->ucomisd(x86::XmmRegister(x86::XMM0), x86::Address(x86::EAX, 0));
- const char* expected = "ucomisd 0(%EAX), %xmm0\n";
- DriverStr(expected, "ucomisd");
+ DriverStr(RepeatFA(&x86::X86Assembler::ucomisd, "ucomisd {mem}, %{reg}"), "ucomisd");
}
TEST_F(AssemblerX86Test, RoundSS) {
- GetAssembler()->roundss(
- x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1), x86::Immediate(1));
- const char* expected = "roundss $1, %xmm1, %xmm0\n";
- DriverStr(expected, "roundss");
+ DriverStr(RepeatFFI(&x86::X86Assembler::roundss, 1U,
+ "roundss ${imm}, %{reg2}, %{reg1}"), "roundss");
}
TEST_F(AssemblerX86Test, RoundSD) {
- GetAssembler()->roundsd(
- x86::XmmRegister(x86::XMM0), x86::XmmRegister(x86::XMM1), x86::Immediate(1));
- const char* expected = "roundsd $1, %xmm1, %xmm0\n";
- DriverStr(expected, "roundsd");
+ DriverStr(RepeatFFI(&x86::X86Assembler::roundsd, 1U,
+ "roundsd ${imm}, %{reg2}, %{reg1}"), "roundsd");
}
TEST_F(AssemblerX86Test, CmovlAddress) {
@@ -379,110 +438,75 @@
"cmovzl 0xc(%EDI,%EBX,4), %eax\n"
"cmovnzl 0xc(%ESI,%EBX,4), %edi\n"
"cmovzl 0xc(%EDI,%EAX,4), %edi\n";
-
DriverStr(expected, "cmovl_address");
}
TEST_F(AssemblerX86Test, TestbAddressImmediate) {
- GetAssembler()->testb(
- x86::Address(x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12),
- x86::Immediate(1));
- GetAssembler()->testb(
- x86::Address(x86::Register(x86::ESP), FrameOffset(7)),
- x86::Immediate(-128));
- GetAssembler()->testb(
- x86::Address(x86::Register(x86::EBX), MemberOffset(130)),
- x86::Immediate(127));
- const char* expected =
- "testb $1, 0xc(%EDI,%EBX,4)\n"
- "testb $-128, 0x7(%ESP)\n"
- "testb $127, 0x82(%EBX)\n";
-
- DriverStr(expected, "TestbAddressImmediate");
+ DriverStr(RepeatAI(&x86::X86Assembler::testb, /*imm_bytes*/ 1U, "testb ${imm}, {mem}"), "testb");
}
TEST_F(AssemblerX86Test, TestlAddressImmediate) {
- GetAssembler()->testl(
- x86::Address(x86::Register(x86::EDI), x86::Register(x86::EBX), x86::TIMES_4, 12),
- x86::Immediate(1));
- GetAssembler()->testl(
- x86::Address(x86::Register(x86::ESP), FrameOffset(7)),
- x86::Immediate(-100000));
- GetAssembler()->testl(
- x86::Address(x86::Register(x86::EBX), MemberOffset(130)),
- x86::Immediate(77777777));
- const char* expected =
- "testl $1, 0xc(%EDI,%EBX,4)\n"
- "testl $-100000, 0x7(%ESP)\n"
- "testl $77777777, 0x82(%EBX)\n";
-
- DriverStr(expected, "TestlAddressImmediate");
+ DriverStr(RepeatAI(&x86::X86Assembler::testl, /*imm_bytes*/ 4U, "testl ${imm}, {mem}"), "testl");
}
TEST_F(AssemblerX86Test, Movaps) {
DriverStr(RepeatFF(&x86::X86Assembler::movaps, "movaps %{reg2}, %{reg1}"), "movaps");
}
-TEST_F(AssemblerX86Test, MovapsAddr) {
- GetAssembler()->movaps(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4));
- GetAssembler()->movaps(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1));
- const char* expected =
- "movaps 0x4(%ESP), %xmm0\n"
- "movaps %xmm1, 0x2(%ESP)\n";
- DriverStr(expected, "movaps_address");
+TEST_F(AssemblerX86Test, MovapsLoad) {
+ DriverStr(RepeatFA(&x86::X86Assembler::movaps, "movaps {mem}, %{reg}"), "movaps_load");
}
-TEST_F(AssemblerX86Test, MovupsAddr) {
- GetAssembler()->movups(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4));
- GetAssembler()->movups(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1));
- const char* expected =
- "movups 0x4(%ESP), %xmm0\n"
- "movups %xmm1, 0x2(%ESP)\n";
- DriverStr(expected, "movups_address");
+TEST_F(AssemblerX86Test, MovapsStore) {
+ DriverStr(RepeatAF(&x86::X86Assembler::movaps, "movaps %{reg}, {mem}"), "movaps_store");
+}
+
+TEST_F(AssemblerX86Test, MovupsLoad) {
+ DriverStr(RepeatFA(&x86::X86Assembler::movups, "movups {mem}, %{reg}"), "movups_load");
+}
+
+TEST_F(AssemblerX86Test, MovupsStore) {
+ DriverStr(RepeatAF(&x86::X86Assembler::movups, "movups %{reg}, {mem}"), "movups_store");
}
TEST_F(AssemblerX86Test, Movapd) {
DriverStr(RepeatFF(&x86::X86Assembler::movapd, "movapd %{reg2}, %{reg1}"), "movapd");
}
-TEST_F(AssemblerX86Test, MovapdAddr) {
- GetAssembler()->movapd(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4));
- GetAssembler()->movapd(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1));
- const char* expected =
- "movapd 0x4(%ESP), %xmm0\n"
- "movapd %xmm1, 0x2(%ESP)\n";
- DriverStr(expected, "movapd_address");
+TEST_F(AssemblerX86Test, MovapdLoad) {
+ DriverStr(RepeatFA(&x86::X86Assembler::movapd, "movapd {mem}, %{reg}"), "movapd_load");
}
-TEST_F(AssemblerX86Test, MovupdAddr) {
- GetAssembler()->movupd(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4));
- GetAssembler()->movupd(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1));
- const char* expected =
- "movupd 0x4(%ESP), %xmm0\n"
- "movupd %xmm1, 0x2(%ESP)\n";
- DriverStr(expected, "movupd_address");
+TEST_F(AssemblerX86Test, MovapdStore) {
+ DriverStr(RepeatAF(&x86::X86Assembler::movapd, "movapd %{reg}, {mem}"), "movapd_store");
+}
+
+TEST_F(AssemblerX86Test, MovupdLoad) {
+ DriverStr(RepeatFA(&x86::X86Assembler::movupd, "movupd {mem}, %{reg}"), "movupd_load");
+}
+
+TEST_F(AssemblerX86Test, MovupdStore) {
+ DriverStr(RepeatAF(&x86::X86Assembler::movupd, "movupd %{reg}, {mem}"), "movupd_store");
}
TEST_F(AssemblerX86Test, Movdqa) {
DriverStr(RepeatFF(&x86::X86Assembler::movdqa, "movdqa %{reg2}, %{reg1}"), "movdqa");
}
-TEST_F(AssemblerX86Test, MovdqaAddr) {
- GetAssembler()->movdqa(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4));
- GetAssembler()->movdqa(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1));
- const char* expected =
- "movdqa 0x4(%ESP), %xmm0\n"
- "movdqa %xmm1, 0x2(%ESP)\n";
- DriverStr(expected, "movdqa_address");
+TEST_F(AssemblerX86Test, MovdqaLoad) {
+ DriverStr(RepeatFA(&x86::X86Assembler::movdqa, "movdqa {mem}, %{reg}"), "movdqa_load");
}
-TEST_F(AssemblerX86Test, MovdquAddr) {
- GetAssembler()->movdqu(x86::XmmRegister(x86::XMM0), x86::Address(x86::Register(x86::ESP), 4));
- GetAssembler()->movdqu(x86::Address(x86::Register(x86::ESP), 2), x86::XmmRegister(x86::XMM1));
- const char* expected =
- "movdqu 0x4(%ESP), %xmm0\n"
- "movdqu %xmm1, 0x2(%ESP)\n";
- DriverStr(expected, "movdqu_address");
+TEST_F(AssemblerX86Test, MovdqaStore) {
+ DriverStr(RepeatAF(&x86::X86Assembler::movdqa, "movdqa %{reg}, {mem}"), "movdqa_store");
+}
+
+TEST_F(AssemblerX86Test, MovdquLoad) {
+ DriverStr(RepeatFA(&x86::X86Assembler::movdqu, "movdqu {mem}, %{reg}"), "movdqu_load");
+}
+
+TEST_F(AssemblerX86Test, MovdquStore) {
+ DriverStr(RepeatAF(&x86::X86Assembler::movdqu, "movdqu %{reg}, {mem}"), "movdqu_store");
}
TEST_F(AssemblerX86Test, AddPS) {
@@ -838,10 +862,6 @@
DriverStr("psrldq $0x10, %xmm0\n", "psrldqi");
}
-/////////////////
-// Near labels //
-/////////////////
-
TEST_F(AssemblerX86Test, Jecxz) {
x86::NearLabel target;
GetAssembler()->jecxz(&target);
@@ -851,7 +871,6 @@
"jecxz 1f\n"
"addl 4(%ESP),%EDI\n"
"1:\n";
-
DriverStr(expected, "jecxz");
}
@@ -873,14 +892,11 @@
"addl 4(%ESP),%EDI\n"
"2: jne 1b\n"
"jmp 1b\n";
-
DriverStr(expected, "near_label");
}
TEST_F(AssemblerX86Test, Cmpb) {
- GetAssembler()->cmpb(x86::Address(x86::EDI, 128), x86::Immediate(0));
- const char* expected = "cmpb $0, 128(%EDI)\n";
- DriverStr(expected, "cmpb");
+ DriverStr(RepeatAI(&x86::X86Assembler::cmpb, /*imm_bytes*/ 1U, "cmpb ${imm}, {mem}"), "cmpb");
}
} // namespace art
diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc
index 85afee0..3e6110d 100644
--- a/compiler/utils/x86_64/assembler_x86_64_test.cc
+++ b/compiler/utils/x86_64/assembler_x86_64_test.cc
@@ -126,11 +126,21 @@
}
};
-class AssemblerX86_64Test : public AssemblerTest<x86_64::X86_64Assembler, x86_64::CpuRegister,
- x86_64::XmmRegister, x86_64::Immediate> {
+//
+// Test fixture.
+//
+
+class AssemblerX86_64Test : public AssemblerTest<x86_64::X86_64Assembler,
+ x86_64::Address,
+ x86_64::CpuRegister,
+ x86_64::XmmRegister,
+ x86_64::Immediate> {
public:
- typedef AssemblerTest<x86_64::X86_64Assembler, x86_64::CpuRegister,
- x86_64::XmmRegister, x86_64::Immediate> Base;
+ typedef AssemblerTest<x86_64::X86_64Assembler,
+ x86_64::Address,
+ x86_64::CpuRegister,
+ x86_64::XmmRegister,
+ x86_64::Immediate> Base;
protected:
// Get the typically used name for this architecture, e.g., aarch64, x86-64, ...
@@ -237,6 +247,11 @@
STLDeleteElements(&fp_registers_);
}
+ std::vector<x86_64::Address> GetAddresses() {
+ UNIMPLEMENTED(FATAL) << "Feature not implemented yet";
+ UNREACHABLE();
+ }
+
std::vector<x86_64::CpuRegister*> GetRegisters() OVERRIDE {
return registers_;
}
@@ -273,12 +288,130 @@
std::vector<x86_64::XmmRegister*> fp_registers_;
};
+//
+// Test repeat drivers used in the tests.
+//
+
+TEST_F(AssemblerX86_64Test, RepeatI4) {
+ EXPECT_EQ("%0\n%-1\n%18\n%4660\n%-4660\n%305419896\n%-305419896\n",
+ RepeatI(/*f*/ nullptr, /*imm_bytes*/ 4U, "%{imm}"));
+}
+
+TEST_F(AssemblerX86_64Test, RepeatI8) {
+ EXPECT_EQ("%0\n%-1\n%18\n%4660\n%-4660\n%305419896\n%-305419896\n"
+ "%20015998343868\n%-20015998343868\n%1311768467463790320\n"
+ "%-1311768467463790320\n",
+ RepeatI(/*f*/ nullptr, /*imm_bytes*/ 8U, "%{imm}"));
+}
+
+TEST_F(AssemblerX86_64Test, Repeatr) {
+ EXPECT_EQ("%eax\n%ebx\n%ecx\n%edx\n%ebp\n%esp\n%esi\n%edi\n"
+ "%r8d\n%r9d\n%r10d\n%r11d\n%r12d\n%r13d\n%r14d\n%r15d\n",
+ Repeatr(/*f*/ nullptr, "%{reg}"));
+}
+
+TEST_F(AssemblerX86_64Test, Repeatri) {
+ EXPECT_NE(Repeatri(/*f*/ nullptr, /*imm_bytes*/ 1U, "%{reg} %{imm}").
+ find("%eax %0\n%eax %-1\n%eax %18\n%ebx %0\n%ebx %-1\n%ebx %18\n"
+ "%ecx %0\n%ecx %-1\n%ecx %18\n%edx %0\n%edx %-1\n%edx %18\n"),
+ std::string::npos);
+}
+
+TEST_F(AssemblerX86_64Test, Repeatrr) {
+ EXPECT_NE(Repeatrr(/*f*/ nullptr, "%{reg1} %{reg2}")
+ .find("%eax %eax\n%eax %ebx\n%eax %ecx\n%eax %edx\n"
+ "%eax %ebp\n%eax %esp\n%eax %esi\n%eax %edi\n"),
+ std::string::npos);
+}
+
+TEST_F(AssemblerX86_64Test, Repeatrb) {
+ EXPECT_NE(Repeatrb(/*f*/ nullptr, "%{reg1} %{reg2}").
+ find("%eax %al\n%eax %bl\n%eax %cl\n%eax %dl\n%eax %bpl\n"
+ "%eax %spl\n%eax %sil\n%eax %dil\n%eax %r8b\n%eax %r9b\n"),
+ std::string::npos);
+}
+
+TEST_F(AssemblerX86_64Test, RepeatrF) {
+ EXPECT_NE(RepeatrF(/*f*/ nullptr, "%{reg1} %{reg2}")
+ .find("%eax %xmm0\n%eax %xmm1\n%eax %xmm2\n%eax %xmm3\n"
+ "%eax %xmm4\n%eax %xmm5\n%eax %xmm6\n%eax %xmm7\n"
+ "%eax %xmm8\n%eax %xmm9\n%eax %xmm10\n%eax %xmm11\n"
+ "%eax %xmm12\n%eax %xmm13\n%eax %xmm14\n%eax %xmm15\n"
+ "%ebx %xmm0\n%ebx %xmm1\n%ebx %xmm2\n%ebx %xmm3\n%ebx %xmm4\n"),
+ std::string::npos);
+}
+
+TEST_F(AssemblerX86_64Test, RepeatR) {
+ EXPECT_EQ("%rax\n%rbx\n%rcx\n%rdx\n%rbp\n%rsp\n%rsi\n%rdi\n"
+ "%r8\n%r9\n%r10\n%r11\n%r12\n%r13\n%r14\n%r15\n",
+ RepeatR(/*f*/ nullptr, "%{reg}"));
+}
+
+TEST_F(AssemblerX86_64Test, RepeatRI) {
+ EXPECT_EQ("%rax %0\n%rax %-1\n%rax %18\n%rbx %0\n%rbx %-1\n%rbx %18\n"
+ "%rcx %0\n%rcx %-1\n%rcx %18\n%rdx %0\n%rdx %-1\n%rdx %18\n"
+ "%rbp %0\n%rbp %-1\n%rbp %18\n%rsp %0\n%rsp %-1\n%rsp %18\n"
+ "%rsi %0\n%rsi %-1\n%rsi %18\n%rdi %0\n%rdi %-1\n%rdi %18\n"
+ "%r8 %0\n%r8 %-1\n%r8 %18\n%r9 %0\n%r9 %-1\n%r9 %18\n"
+ "%r10 %0\n%r10 %-1\n%r10 %18\n%r11 %0\n%r11 %-1\n%r11 %18\n"
+ "%r12 %0\n%r12 %-1\n%r12 %18\n%r13 %0\n%r13 %-1\n%r13 %18\n"
+ "%r14 %0\n%r14 %-1\n%r14 %18\n%r15 %0\n%r15 %-1\n%r15 %18\n",
+ RepeatRI(/*f*/ nullptr, /*imm_bytes*/ 1U, "%{reg} %{imm}"));
+}
+
+TEST_F(AssemblerX86_64Test, RepeatRr) {
+ EXPECT_NE(RepeatRr(/*f*/ nullptr, "%{reg1} %{reg2}")
+ .find("%rax %eax\n%rax %ebx\n%rax %ecx\n%rax %edx\n%rax %ebp\n"
+ "%rax %esp\n%rax %esi\n%rax %edi\n%rax %r8d\n%rax %r9d\n"
+ "%rax %r10d\n%rax %r11d\n%rax %r12d\n%rax %r13d\n%rax %r14d\n"
+ "%rax %r15d\n%rbx %eax\n%rbx %ebx\n%rbx %ecx\n%rbx %edx\n"),
+ std::string::npos);
+}
+
+TEST_F(AssemblerX86_64Test, RepeatRR) {
+ EXPECT_NE(RepeatRR(/*f*/ nullptr, "%{reg1} %{reg2}")
+ .find("%rax %rax\n%rax %rbx\n%rax %rcx\n%rax %rdx\n%rax %rbp\n"
+ "%rax %rsp\n%rax %rsi\n%rax %rdi\n%rax %r8\n%rax %r9\n"
+ "%rax %r10\n%rax %r11\n%rax %r12\n%rax %r13\n%rax %r14\n"
+ "%rax %r15\n%rbx %rax\n%rbx %rbx\n%rbx %rcx\n%rbx %rdx\n"),
+ std::string::npos);
+}
+
+TEST_F(AssemblerX86_64Test, RepeatRF) {
+ EXPECT_NE(RepeatRF(/*f*/ nullptr, "%{reg1} %{reg2}")
+ .find("%rax %xmm0\n%rax %xmm1\n%rax %xmm2\n%rax %xmm3\n%rax %xmm4\n"
+ "%rax %xmm5\n%rax %xmm6\n%rax %xmm7\n%rax %xmm8\n%rax %xmm9\n"
+ "%rax %xmm10\n%rax %xmm11\n%rax %xmm12\n%rax %xmm13\n%rax %xmm14\n"
+ "%rax %xmm15\n%rbx %xmm0\n%rbx %xmm1\n%rbx %xmm2\n%rbx %xmm3\n"),
+ std::string::npos);
+}
+
+TEST_F(AssemblerX86_64Test, RepeatFF) {
+ EXPECT_NE(RepeatFF(/*f*/ nullptr, "%{reg1} %{reg2}")
+ .find("%xmm0 %xmm0\n%xmm0 %xmm1\n%xmm0 %xmm2\n%xmm0 %xmm3\n%xmm0 %xmm4\n"
+ "%xmm0 %xmm5\n%xmm0 %xmm6\n%xmm0 %xmm7\n%xmm0 %xmm8\n%xmm0 %xmm9\n"
+ "%xmm0 %xmm10\n%xmm0 %xmm11\n%xmm0 %xmm12\n%xmm0 %xmm13\n%xmm0 %xmm14\n"
+ "%xmm0 %xmm15\n%xmm1 %xmm0\n%xmm1 %xmm1\n%xmm1 %xmm2\n%xmm1 %xmm3\n"),
+ std::string::npos);
+}
+
+TEST_F(AssemblerX86_64Test, RepeatFFI) {
+ EXPECT_NE(RepeatFFI(/*f*/ nullptr, /*imm_bytes*/ 1U, "%{reg1} %{reg2} %{imm}")
+ .find("%xmm0 %xmm0 %0\n%xmm0 %xmm0 %-1\n%xmm0 %xmm0 %18\n"
+ "%xmm0 %xmm1 %0\n%xmm0 %xmm1 %-1\n%xmm0 %xmm1 %18\n"
+ "%xmm0 %xmm2 %0\n%xmm0 %xmm2 %-1\n%xmm0 %xmm2 %18\n"
+ "%xmm0 %xmm3 %0\n%xmm0 %xmm3 %-1\n%xmm0 %xmm3 %18\n"),
+ std::string::npos);
+}
+
+//
+// Actual x86-64 instruction assembler tests.
+//
TEST_F(AssemblerX86_64Test, Toolchain) {
EXPECT_TRUE(CheckTools());
}
-
TEST_F(AssemblerX86_64Test, PushqRegs) {
DriverStr(RepeatR(&x86_64::X86_64Assembler::pushq, "pushq %{reg}"), "pushq");
}
@@ -978,10 +1111,6 @@
DriverStr(RepeatRr(&x86_64::X86_64Assembler::movsxd, "movsxd %{reg2}, %{reg1}"), "movsxd");
}
-///////////////////
-// FP Operations //
-///////////////////
-
TEST_F(AssemblerX86_64Test, Movaps) {
DriverStr(RepeatFF(&x86_64::X86_64Assembler::movaps, "movaps %{reg2}, %{reg1}"), "movaps");
}
@@ -1176,17 +1305,14 @@
DriverStr(RepeatFr(&x86_64::X86_64Assembler::cvtsi2sd, "cvtsi2sd %{reg2}, %{reg1}"), "cvtsi2sd");
}
-
TEST_F(AssemblerX86_64Test, Cvtss2si) {
DriverStr(RepeatrF(&x86_64::X86_64Assembler::cvtss2si, "cvtss2si %{reg2}, %{reg1}"), "cvtss2si");
}
-
TEST_F(AssemblerX86_64Test, Cvtss2sd) {
DriverStr(RepeatFF(&x86_64::X86_64Assembler::cvtss2sd, "cvtss2sd %{reg2}, %{reg1}"), "cvtss2sd");
}
-
TEST_F(AssemblerX86_64Test, Cvtsd2si) {
DriverStr(RepeatrF(&x86_64::X86_64Assembler::cvtsd2si, "cvtsd2si %{reg2}, %{reg1}"), "cvtsd2si");
}
@@ -1586,8 +1712,6 @@
DriverStr(expected, "ucomisd_address");
}
-// X87
-
std::string x87_fn(AssemblerX86_64Test::Base* assembler_test ATTRIBUTE_UNUSED,
x86_64::X86_64Assembler* assembler) {
std::ostringstream str;
@@ -1629,10 +1753,6 @@
DriverStr(expected, "FPUIntegerStore");
}
-////////////////
-// CALL / JMP //
-////////////////
-
TEST_F(AssemblerX86_64Test, Call) {
DriverStr(RepeatR(&x86_64::X86_64Assembler::call, "call *%{reg}"), "call");
}
@@ -1668,10 +1788,6 @@
DriverFn(&ret_and_leave_fn, "retleave");
}
-//////////
-// MISC //
-//////////
-
TEST_F(AssemblerX86_64Test, Bswapl) {
DriverStr(Repeatr(&x86_64::X86_64Assembler::bswapl, "bswap %{reg}"), "bswapl");
}
@@ -1824,11 +1940,6 @@
DriverStr(expected, "cmovq_address");
}
-
-/////////////////
-// Near labels //
-/////////////////
-
TEST_F(AssemblerX86_64Test, Jrcxz) {
x86_64::NearLabel target;
GetAssembler()->jrcxz(&target);
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index e10b171..5bf3513 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -706,7 +706,7 @@
}
}
- uint64_t GetImageSize(const std::string& image_file_name) {
+ uint64_t GetImageObjectSectionSize(const std::string& image_file_name) {
EXPECT_FALSE(image_file_name.empty());
std::unique_ptr<File> file(OS::OpenFileForReading(image_file_name.c_str()));
CHECK(file != nullptr);
@@ -715,7 +715,7 @@
CHECK(success);
CHECK(image_header.IsValid());
ReaderMutexLock mu(Thread::Current(), *Locks::mutator_lock_);
- return image_header.GetImageSize();
+ return image_header.GetObjectsSection().Size();
}
void RunTest(bool app_image) {
@@ -734,7 +734,7 @@
CheckValidity();
ASSERT_TRUE(success_);
// Don't check the result since CheckResult relies on the class being in the profile.
- image_file_empty_profile = GetImageSize(app_image_file);
+ image_file_empty_profile = GetImageObjectSectionSize(app_image_file);
EXPECT_GT(image_file_empty_profile, 0u);
}
@@ -750,8 +750,8 @@
if (app_image) {
// Test that the profile made a difference by adding more classes.
- const uint64_t image_file_small_profile = GetImageSize(app_image_file);
- CHECK_LT(image_file_empty_profile, image_file_small_profile);
+ const uint64_t image_file_small_profile = GetImageObjectSectionSize(app_image_file);
+ ASSERT_LT(image_file_empty_profile, image_file_small_profile);
}
}
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index fc72bbd..51a67ca 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -229,6 +229,8 @@
if (oat_file_assistant.IsInBootClassPath()) {
return kNoDexOptNeeded;
}
+
+ // TODO(calin): Pass the class loader context as an argument to dexoptanalyzer. b/62269291.
int dexoptNeeded = oat_file_assistant.GetDexOptNeeded(
compiler_filter_, assume_profile_changed_, downgrade_);
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index 00e2a89..d3b8ce1 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -31,11 +31,15 @@
#include "art_field-inl.h"
#include "art_method-inl.h"
#include "base/unix_file/fd_file.h"
+#include "class_linker.h"
#include "gc/heap.h"
#include "gc/space/image_space.h"
#include "image.h"
#include "mirror/class-inl.h"
#include "mirror/object-inl.h"
+#include "oat.h"
+#include "oat_file.h"
+#include "oat_file_manager.h"
#include "os.h"
#include "scoped_thread_state_change-inl.h"
@@ -326,6 +330,37 @@
};
// Region analysis for mirror::Objects
+class ImgObjectVisitor : public ObjectVisitor {
+ public:
+ using ComputeDirtyFunc = std::function<void(mirror::Object* object,
+ const uint8_t* begin_image_ptr,
+ const std::set<size_t>& dirty_pages)>;
+ ImgObjectVisitor(ComputeDirtyFunc dirty_func,
+ const uint8_t* begin_image_ptr,
+ const std::set<size_t>& dirty_pages) :
+ dirty_func_(dirty_func),
+ begin_image_ptr_(begin_image_ptr),
+ dirty_pages_(dirty_pages) { }
+
+ virtual ~ImgObjectVisitor() OVERRIDE { }
+
+ virtual void Visit(mirror::Object* object) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Sanity check that we are reading a real mirror::Object
+ CHECK(object->GetClass() != nullptr) << "Image object at address "
+ << object
+ << " has null class";
+ if (kUseBakerReadBarrier) {
+ object->AssertReadBarrierState();
+ }
+ dirty_func_(object, begin_image_ptr_, dirty_pages_);
+ }
+
+ private:
+ ComputeDirtyFunc dirty_func_;
+ const uint8_t* begin_image_ptr_;
+ const std::set<size_t>& dirty_pages_;
+};
+
template<>
class RegionSpecializedBase<mirror::Object> : public RegionCommon<mirror::Object> {
public:
@@ -339,24 +374,14 @@
os_(*os),
dump_dirty_objects_(dump_dirty_objects) { }
- void CheckEntrySanity(const uint8_t* current) const
- REQUIRES_SHARED(Locks::mutator_lock_) {
- CHECK_ALIGNED(current, kObjectAlignment);
- mirror::Object* entry = reinterpret_cast<mirror::Object*>(const_cast<uint8_t*>(current));
- // Sanity check that we are reading a real mirror::Object
- CHECK(entry->GetClass() != nullptr) << "Image object at address "
- << entry
- << " has null class";
- if (kUseBakerReadBarrier) {
- entry->AssertReadBarrierState();
- }
- }
+ // Define a common public type name for use by RegionData.
+ using VisitorClass = ImgObjectVisitor;
- mirror::Object* GetNextEntry(mirror::Object* entry)
+ void VisitEntries(VisitorClass* visitor,
+ uint8_t* base,
+ PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_) {
- uint8_t* next =
- reinterpret_cast<uint8_t*>(entry) + RoundUp(EntrySize(entry), kObjectAlignment);
- return reinterpret_cast<mirror::Object*>(next);
+ RegionCommon<mirror::Object>::image_header_.VisitObjects(visitor, base, pointer_size);
}
void VisitEntry(mirror::Object* entry)
@@ -616,40 +641,93 @@
};
// Region analysis for ArtMethods.
-// TODO: most of these need work.
+class ImgArtMethodVisitor : public ArtMethodVisitor {
+ public:
+ using ComputeDirtyFunc = std::function<void(ArtMethod*,
+ const uint8_t*,
+ const std::set<size_t>&)>;
+ ImgArtMethodVisitor(ComputeDirtyFunc dirty_func,
+ const uint8_t* begin_image_ptr,
+ const std::set<size_t>& dirty_pages) :
+ dirty_func_(dirty_func),
+ begin_image_ptr_(begin_image_ptr),
+ dirty_pages_(dirty_pages) { }
+ virtual ~ImgArtMethodVisitor() OVERRIDE { }
+ virtual void Visit(ArtMethod* method) OVERRIDE {
+ dirty_func_(method, begin_image_ptr_, dirty_pages_);
+ }
+
+ private:
+ ComputeDirtyFunc dirty_func_;
+ const uint8_t* begin_image_ptr_;
+ const std::set<size_t>& dirty_pages_;
+};
+
+// Struct and functor for computing offsets of members of ArtMethods.
+// template <typename RegionType>
+struct MemberInfo {
+ template <typename T>
+ void operator() (const ArtMethod* method, const T* member_address, const std::string& name) {
+ // Check that member_address is a pointer inside *method.
+ DCHECK(reinterpret_cast<uintptr_t>(method) <= reinterpret_cast<uintptr_t>(member_address));
+ DCHECK(reinterpret_cast<uintptr_t>(member_address) + sizeof(T) <=
+ reinterpret_cast<uintptr_t>(method) + sizeof(ArtMethod));
+ size_t offset =
+ reinterpret_cast<uintptr_t>(member_address) - reinterpret_cast<uintptr_t>(method);
+ offset_to_name_size_.insert({offset, NameAndSize(sizeof(T), name)});
+ }
+
+ struct NameAndSize {
+ size_t size_;
+ std::string name_;
+ NameAndSize(size_t size, const std::string& name) : size_(size), name_(name) { }
+ NameAndSize() : size_(0), name_("INVALID") { }
+ };
+
+ std::map<size_t, NameAndSize> offset_to_name_size_;
+};
+
template<>
-class RegionSpecializedBase<ArtMethod> : RegionCommon<ArtMethod> {
+class RegionSpecializedBase<ArtMethod> : public RegionCommon<ArtMethod> {
public:
RegionSpecializedBase(std::ostream* os,
std::vector<uint8_t>* remote_contents,
std::vector<uint8_t>* zygote_contents,
const backtrace_map_t& boot_map,
- const ImageHeader& image_header) :
- RegionCommon<ArtMethod>(os, remote_contents, zygote_contents, boot_map, image_header),
- os_(*os) { }
-
- void CheckEntrySanity(const uint8_t* current ATTRIBUTE_UNUSED) const
- REQUIRES_SHARED(Locks::mutator_lock_) {
+ const ImageHeader& image_header,
+ bool dump_dirty_objects ATTRIBUTE_UNUSED)
+ : RegionCommon<ArtMethod>(os, remote_contents, zygote_contents, boot_map, image_header),
+ os_(*os) {
+ // Prepare the table for offset to member lookups.
+ ArtMethod* art_method = reinterpret_cast<ArtMethod*>(&(*remote_contents)[0]);
+ art_method->VisitMembers(member_info_);
+ // Prepare the table for address to symbolic entry point names.
+ BuildEntryPointNames();
+ class_linker_ = Runtime::Current()->GetClassLinker();
}
- ArtMethod* GetNextEntry(ArtMethod* entry)
+ // Define a common public type name for use by RegionData.
+ using VisitorClass = ImgArtMethodVisitor;
+
+ void VisitEntries(VisitorClass* visitor,
+ uint8_t* base,
+ PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_) {
- uint8_t* next = reinterpret_cast<uint8_t*>(entry) + RoundUp(EntrySize(entry), kObjectAlignment);
- return reinterpret_cast<ArtMethod*>(next);
+ RegionCommon<ArtMethod>::image_header_.VisitPackedArtMethods(visitor, base, pointer_size);
}
void VisitEntry(ArtMethod* method ATTRIBUTE_UNUSED)
REQUIRES_SHARED(Locks::mutator_lock_) {
}
+ void AddCleanEntry(ArtMethod* method ATTRIBUTE_UNUSED) {
+ }
+
void AddFalseDirtyEntry(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_) {
RegionCommon<ArtMethod>::AddFalseDirtyEntry(method);
}
- void AddCleanEntry(ArtMethod* method ATTRIBUTE_UNUSED) {
- }
-
void AddDirtyEntry(ArtMethod* method, ArtMethod* method_remote)
REQUIRES_SHARED(Locks::mutator_lock_) {
size_t entry_size = EntrySize(method);
@@ -667,14 +745,50 @@
dirty_entries_.push_back(method);
}
- void DiffEntryContents(ArtMethod* method ATTRIBUTE_UNUSED,
- uint8_t* remote_bytes ATTRIBUTE_UNUSED,
- const uint8_t* base_ptr ATTRIBUTE_UNUSED)
+ void DiffEntryContents(ArtMethod* method,
+ uint8_t* remote_bytes,
+ const uint8_t* base_ptr,
+ bool log_dirty_objects ATTRIBUTE_UNUSED)
REQUIRES_SHARED(Locks::mutator_lock_) {
+ const char* tabs = " ";
+ os_ << tabs << "ArtMethod " << ArtMethod::PrettyMethod(method) << "\n";
+
+ std::unordered_set<size_t> dirty_members;
+ // Examine the members comprising the ArtMethod, computing which members are dirty.
+ for (const std::pair<size_t, MemberInfo::NameAndSize>& p : member_info_.offset_to_name_size_) {
+ const size_t offset = p.first;
+ if (memcmp(base_ptr + offset, remote_bytes + offset, p.second.size_) != 0) {
+ dirty_members.insert(p.first);
+ }
+ }
+ // Dump different fields.
+ if (!dirty_members.empty()) {
+ os_ << tabs << "Dirty members " << dirty_members.size() << "\n";
+ for (size_t offset : dirty_members) {
+ const MemberInfo::NameAndSize& member_info = member_info_.offset_to_name_size_[offset];
+ os_ << tabs << member_info.name_
+ << " original=" << StringFromBytes(base_ptr + offset, member_info.size_)
+ << " remote=" << StringFromBytes(remote_bytes + offset, member_info.size_)
+ << "\n";
+ }
+ }
+ os_ << "\n";
+ }
+
+ void DumpDirtyObjects() REQUIRES_SHARED(Locks::mutator_lock_) {
}
void DumpDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
DumpSamplesAndOffsetCount();
+ os_ << " offset to field map:\n";
+ for (const std::pair<size_t, MemberInfo::NameAndSize>& p : member_info_.offset_to_name_size_) {
+ const size_t offset = p.first;
+ const size_t size = p.second.size_;
+ os_ << StringPrintf(" %zu-%zu: ", offset, offset + size - 1)
+ << p.second.name_
+ << std::endl;
+ }
+
os_ << " field contents:\n";
for (ArtMethod* method : dirty_entries_) {
// remote method
@@ -694,6 +808,7 @@
}
void DumpFalseDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+ os_ << "\n" << " False-dirty ArtMethods\n";
os_ << " field contents:\n";
for (ArtMethod* method : false_dirty_entries_) {
// local class
@@ -707,6 +822,84 @@
private:
std::ostream& os_;
+ MemberInfo member_info_;
+ std::map<const void*, std::string> entry_point_names_;
+ ClassLinker* class_linker_;
+
+ // Compute a map of addresses to names in the boot OAT file(s).
+ void BuildEntryPointNames() {
+ OatFileManager& oat_file_manager = Runtime::Current()->GetOatFileManager();
+ std::vector<const OatFile*> boot_oat_files = oat_file_manager.GetBootOatFiles();
+ for (const OatFile* oat_file : boot_oat_files) {
+ const OatHeader& oat_header = oat_file->GetOatHeader();
+ const void* i2ib = oat_header.GetInterpreterToInterpreterBridge();
+ if (i2ib != nullptr) {
+ entry_point_names_[i2ib] = "InterpreterToInterpreterBridge (from boot oat file)";
+ }
+ const void* i2ccb = oat_header.GetInterpreterToCompiledCodeBridge();
+ if (i2ccb != nullptr) {
+ entry_point_names_[i2ccb] = "InterpreterToCompiledCodeBridge (from boot oat file)";
+ }
+ const void* jdl = oat_header.GetJniDlsymLookup();
+ if (jdl != nullptr) {
+ entry_point_names_[jdl] = "JniDlsymLookup (from boot oat file)";
+ }
+ const void* qgjt = oat_header.GetQuickGenericJniTrampoline();
+ if (qgjt != nullptr) {
+ entry_point_names_[qgjt] = "QuickGenericJniTrampoline (from boot oat file)";
+ }
+ const void* qrt = oat_header.GetQuickResolutionTrampoline();
+ if (qrt != nullptr) {
+ entry_point_names_[qrt] = "QuickResolutionTrampoline (from boot oat file)";
+ }
+ const void* qict = oat_header.GetQuickImtConflictTrampoline();
+ if (qict != nullptr) {
+ entry_point_names_[qict] = "QuickImtConflictTrampoline (from boot oat file)";
+ }
+ const void* q2ib = oat_header.GetQuickToInterpreterBridge();
+ if (q2ib != nullptr) {
+ entry_point_names_[q2ib] = "QuickToInterpreterBridge (from boot oat file)";
+ }
+ }
+ }
+
+ std::string StringFromBytes(const uint8_t* bytes, size_t size) {
+ switch (size) {
+ case 1:
+ return StringPrintf("%" PRIx8, *bytes);
+ case 2:
+ return StringPrintf("%" PRIx16, *reinterpret_cast<const uint16_t*>(bytes));
+ case 4:
+ case 8: {
+ // Compute an address if the bytes might contain one.
+ uint64_t intval;
+ if (size == 4) {
+ intval = *reinterpret_cast<const uint32_t*>(bytes);
+ } else {
+ intval = *reinterpret_cast<const uint64_t*>(bytes);
+ }
+ const void* addr = reinterpret_cast<const void*>(intval);
+ // Match the address against those that have Is* methods in the ClassLinker.
+ if (class_linker_->IsQuickToInterpreterBridge(addr)) {
+ return "QuickToInterpreterBridge";
+ } else if (class_linker_->IsQuickGenericJniStub(addr)) {
+ return "QuickGenericJniStub";
+ } else if (class_linker_->IsQuickResolutionStub(addr)) {
+ return "QuickResolutionStub";
+ } else if (class_linker_->IsJniDlsymLookupStub(addr)) {
+ return "JniDlsymLookupStub";
+ }
+ // Match the address against those that we saved from the boot OAT files.
+ if (entry_point_names_.find(addr) != entry_point_names_.end()) {
+ return entry_point_names_[addr];
+ }
+ return StringPrintf("%" PRIx64, intval);
+ }
+ default:
+ LOG(WARNING) << "Don't know how to convert " << size << " bytes to integer";
+ return "<UNKNOWN>";
+ }
+ }
void DumpOneArtMethod(ArtMethod* art_method,
mirror::Class* declaring_class,
@@ -721,7 +914,10 @@
art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size))
<< ", ";
os_ << " isNative? " << (art_method->IsNative() ? "yes" : "no") << ", ";
- os_ << " class_status (local): " << declaring_class->GetStatus();
+ // Null for runtime metionds.
+ if (declaring_class != nullptr) {
+ os_ << " class_status (local): " << declaring_class->GetStatus();
+ }
if (remote_declaring_class != nullptr) {
os_ << ", class_status (remote): " << remote_declaring_class->GetStatus();
}
@@ -755,16 +951,20 @@
// collecting and reporting data regarding dirty, difference, etc.
void ProcessRegion(const MappingData& mapping_data,
RemoteProcesses remotes,
- const uint8_t* begin_image_ptr,
- const uint8_t* end_image_ptr)
+ const uint8_t* begin_image_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
- const uint8_t* current = begin_image_ptr + RoundUp(sizeof(ImageHeader), kObjectAlignment);
- T* entry = reinterpret_cast<T*>(const_cast<uint8_t*>(current));
- while (reinterpret_cast<uintptr_t>(entry) < reinterpret_cast<uintptr_t>(end_image_ptr)) {
- ComputeEntryDirty(entry, begin_image_ptr, mapping_data.dirty_page_set);
-
- entry = RegionSpecializedBase<T>::GetNextEntry(entry);
- }
+ typename RegionSpecializedBase<T>::VisitorClass visitor(
+ [this](T* entry,
+ const uint8_t* begin_image_ptr,
+ const std::set<size_t>& dirty_page_set) REQUIRES_SHARED(Locks::mutator_lock_) {
+ this->ComputeEntryDirty(entry, begin_image_ptr, dirty_page_set);
+ },
+ begin_image_ptr,
+ mapping_data.dirty_page_set);
+ PointerSize pointer_size = InstructionSetPointerSize(Runtime::Current()->GetInstructionSet());
+ RegionSpecializedBase<T>::VisitEntries(&visitor,
+ const_cast<uint8_t*>(begin_image_ptr),
+ pointer_size);
// Looking at only dirty pages, figure out how many of those bytes belong to dirty entries.
// TODO: fix this now that there are multiple regions in a mapping.
@@ -1208,8 +1408,6 @@
<< "\n\n";
const uint8_t* image_begin_unaligned = image_header_.GetImageBegin();
- const uint8_t* image_mirror_end_unaligned = image_begin_unaligned +
- image_header_.GetObjectsSection().Size();
const uint8_t* image_end_unaligned = image_begin_unaligned + image_header_.GetImageSize();
// Adjust range to nearest page
@@ -1235,14 +1433,6 @@
if (!ComputeDirtyBytes(image_begin, &mapping_data)) {
return false;
}
-
- RegionData<mirror::Object> object_region_data(os_,
- &remote_contents_,
- &zygote_contents_,
- boot_map_,
- image_header_,
- dump_dirty_objects_);
-
RemoteProcesses remotes;
if (zygote_pid_only_) {
remotes = RemoteProcesses::kZygoteOnly;
@@ -1252,11 +1442,27 @@
remotes = RemoteProcesses::kImageOnly;
}
+ // Check all the mirror::Object entries in the image.
+ RegionData<mirror::Object> object_region_data(os_,
+ &remote_contents_,
+ &zygote_contents_,
+ boot_map_,
+ image_header_,
+ dump_dirty_objects_);
object_region_data.ProcessRegion(mapping_data,
remotes,
- image_begin_unaligned,
- image_mirror_end_unaligned);
+ image_begin_unaligned);
+ // Check all the ArtMethod entries in the image.
+ RegionData<ArtMethod> artmethod_region_data(os_,
+ &remote_contents_,
+ &zygote_contents_,
+ boot_map_,
+ image_header_,
+ dump_dirty_objects_);
+ artmethod_region_data.ProcessRegion(mapping_data,
+ remotes,
+ image_begin_unaligned);
return true;
}
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 6e4c68b..4161067 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -2006,7 +2006,7 @@
// Intern table is 8-byte aligned.
uint32_t end_caches = dex_cache_arrays_section.Offset() + dex_cache_arrays_section.Size();
- CHECK_EQ(RoundUp(end_caches, 8U), intern_section.Offset());
+ CHECK_ALIGNED(intern_section.Offset(), sizeof(uint64_t));
stats_.alignment_bytes += intern_section.Offset() - end_caches;
// Add space between intern table and class table.
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index 6c0d492..4339b2b 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -207,11 +207,11 @@
}
static jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env,
- jthread thread ATTRIBUTE_UNUSED,
- jobject* monitor_ptr ATTRIBUTE_UNUSED) {
+ jthread thread,
+ jobject* monitor_ptr) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_get_current_contended_monitor);
- return ERR(NOT_IMPLEMENTED);
+ return MonitorUtil::GetCurrentContendedMonitor(env, thread, monitor_ptr);
}
static jvmtiError RunAgentThread(jvmtiEnv* env,
@@ -810,11 +810,11 @@
}
static jvmtiError GetObjectMonitorUsage(jvmtiEnv* env,
- jobject object ATTRIBUTE_UNUSED,
- jvmtiMonitorUsage* info_ptr ATTRIBUTE_UNUSED) {
+ jobject object,
+ jvmtiMonitorUsage* info_ptr) {
ENSURE_VALID_ENV(env);
ENSURE_HAS_CAP(env, can_get_monitor_info);
- return ERR(NOT_IMPLEMENTED);
+ return ObjectUtil::GetObjectMonitorUsage(env, object, info_ptr);
}
static jvmtiError GetFieldName(jvmtiEnv* env,
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 93eee28..10ddfc1 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -225,8 +225,8 @@
.can_get_bytecodes = 1,
.can_get_synthetic_attribute = 1,
.can_get_owned_monitor_info = 1,
- .can_get_current_contended_monitor = 0,
- .can_get_monitor_info = 0,
+ .can_get_current_contended_monitor = 1,
+ .can_get_monitor_info = 1,
.can_pop_frame = 0,
.can_redefine_classes = 1,
.can_signal_thread = 0,
@@ -247,7 +247,7 @@
.can_generate_method_exit_events = 1,
.can_generate_all_class_hook_events = 0,
.can_generate_compiled_method_load_events = 0,
- .can_generate_monitor_events = 0,
+ .can_generate_monitor_events = 1,
.can_generate_vm_object_alloc_events = 1,
.can_generate_native_method_bind_events = 1,
.can_generate_garbage_collection_events = 1,
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index c41e15e..b0f8d8a 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -31,6 +31,8 @@
#include "events-inl.h"
+#include <array>
+
#include "art_field-inl.h"
#include "art_jvmti.h"
#include "art_method-inl.h"
@@ -45,6 +47,7 @@
#include "jni_internal.h"
#include "mirror/class.h"
#include "mirror/object-inl.h"
+#include "monitor.h"
#include "nativehelper/ScopedLocalRef.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
@@ -247,6 +250,127 @@
}
}
+template<typename Type>
+static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj);
+}
+
+template<ArtJvmtiEvent kEvent, typename ...Args>
+static void RunEventCallback(EventHandler* handler,
+ art::Thread* self,
+ art::JNIEnvExt* jnienv,
+ Args... args)
+ REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer()));
+ art::StackHandleScope<1> hs(self);
+ art::Handle<art::mirror::Throwable> old_exception(hs.NewHandle(self->GetException()));
+ self->ClearException();
+ // Just give the event a good sized JNI frame. 100 should be fine.
+ jnienv->PushFrame(100);
+ {
+ // Need to do trampoline! :(
+ art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+ handler->DispatchEvent<kEvent>(self,
+ static_cast<JNIEnv*>(jnienv),
+ thread_jni.get(),
+ args...);
+ }
+ jnienv->PopFrame();
+ if (!self->IsExceptionPending() && !old_exception.IsNull()) {
+ self->SetException(old_exception.Get());
+ }
+}
+
+class JvmtiMonitorListener : public art::MonitorCallback {
+ public:
+ explicit JvmtiMonitorListener(EventHandler* handler) : handler_(handler) {}
+
+ void MonitorContendedLocking(art::Monitor* m)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorContendedEnter)) {
+ art::Thread* self = art::Thread::Current();
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject()));
+ RunEventCallback<ArtJvmtiEvent::kMonitorContendedEnter>(
+ handler_,
+ self,
+ jnienv,
+ mon.get());
+ }
+ }
+
+ void MonitorContendedLocked(art::Monitor* m)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorContendedEntered)) {
+ art::Thread* self = art::Thread::Current();
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject()));
+ RunEventCallback<ArtJvmtiEvent::kMonitorContendedEntered>(
+ handler_,
+ self,
+ jnienv,
+ mon.get());
+ }
+ }
+
+ void ObjectWaitStart(art::Handle<art::mirror::Object> obj, int64_t timeout)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorWait)) {
+ art::Thread* self = art::Thread::Current();
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, obj.Get()));
+ RunEventCallback<ArtJvmtiEvent::kMonitorWait>(
+ handler_,
+ self,
+ jnienv,
+ mon.get(),
+ static_cast<jlong>(timeout));
+ }
+ }
+
+
+ // Our interpretation of the spec is that the JVMTI_EVENT_MONITOR_WAITED will be sent immediately
+ // after a thread has woken up from a sleep caused by a call to Object#wait. If the thread will
+ // never go to sleep (due to not having the lock, having bad arguments, or having an exception
+ // propogated from JVMTI_EVENT_MONITOR_WAIT) we will not send this event.
+ //
+ // This does not fully match the RI semantics. Specifically, we will not send the
+ // JVMTI_EVENT_MONITOR_WAITED event in one situation where the RI would, there was an exception in
+ // the JVMTI_EVENT_MONITOR_WAIT event but otherwise the call was fine. In that case the RI would
+ // send this event and return without going to sleep.
+ //
+ // See b/65558434 for more discussion.
+ void MonitorWaitFinished(art::Monitor* m, bool timeout)
+ OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMonitorWaited)) {
+ art::Thread* self = art::Thread::Current();
+ art::JNIEnvExt* jnienv = self->GetJniEnv();
+ ScopedLocalRef<jobject> mon(jnienv, AddLocalRef<jobject>(jnienv, m->GetObject()));
+ RunEventCallback<ArtJvmtiEvent::kMonitorWaited>(
+ handler_,
+ self,
+ jnienv,
+ mon.get(),
+ static_cast<jboolean>(timeout));
+ }
+ }
+
+ private:
+ EventHandler* handler_;
+};
+
+static void SetupMonitorListener(art::MonitorCallback* listener, bool enable) {
+ // We must not hold the mutator lock here, but if we're in FastJNI, for example, we might. For
+ // now, do a workaround: (possibly) acquire and release.
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ if (enable) {
+ art::Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(listener);
+ } else {
+ art::Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(listener);
+ }
+}
+
// Report GC pauses (see spec) as GARBAGE_COLLECTION_START and GARBAGE_COLLECTION_END.
class JvmtiGcPauseListener : public art::gc::GcPauseListener {
public:
@@ -301,33 +425,10 @@
}
}
-template<typename Type>
-static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj)
- REQUIRES_SHARED(art::Locks::mutator_lock_) {
- return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj);
-}
-
class JvmtiMethodTraceListener FINAL : public art::instrumentation::InstrumentationListener {
public:
explicit JvmtiMethodTraceListener(EventHandler* handler) : event_handler_(handler) {}
- template<ArtJvmtiEvent kEvent, typename ...Args>
- void RunEventCallback(art::Thread* self, art::JNIEnvExt* jnienv, Args... args)
- REQUIRES_SHARED(art::Locks::mutator_lock_) {
- ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer()));
- // Just give the event a good sized JNI frame. 100 should be fine.
- jnienv->PushFrame(100);
- {
- // Need to do trampoline! :(
- art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
- event_handler_->DispatchEvent<kEvent>(self,
- static_cast<JNIEnv*>(jnienv),
- thread_jni.get(),
- args...);
- }
- jnienv->PopFrame();
- }
-
// Call-back for when a method is entered.
void MethodEntered(art::Thread* self,
art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
@@ -337,7 +438,8 @@
if (!method->IsRuntimeMethod() &&
event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodEntry)) {
art::JNIEnvExt* jnienv = self->GetJniEnv();
- RunEventCallback<ArtJvmtiEvent::kMethodEntry>(self,
+ RunEventCallback<ArtJvmtiEvent::kMethodEntry>(event_handler_,
+ self,
jnienv,
art::jni::EncodeArtMethod(method));
}
@@ -360,6 +462,7 @@
ScopedLocalRef<jobject> return_jobj(jnienv, AddLocalRef<jobject>(jnienv, return_value.Get()));
val.l = return_jobj.get();
RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -386,6 +489,7 @@
// the union.
val.j = return_value.GetJ();
RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -412,6 +516,7 @@
CHECK(!old_exception.IsNull());
self->ClearException();
RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -442,11 +547,11 @@
jlocation location = static_cast<jlocation>(new_dex_pc);
// Step event is reported first according to the spec.
if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kSingleStep)) {
- RunEventCallback<ArtJvmtiEvent::kSingleStep>(self, jnienv, jmethod, location);
+ RunEventCallback<ArtJvmtiEvent::kSingleStep>(event_handler_, self, jnienv, jmethod, location);
}
// Next we do the Breakpoint events. The Dispatch code will filter the individual
if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kBreakpoint)) {
- RunEventCallback<ArtJvmtiEvent::kBreakpoint>(self, jnienv, jmethod, location);
+ RunEventCallback<ArtJvmtiEvent::kBreakpoint>(event_handler_, self, jnienv, jmethod, location);
}
}
@@ -464,7 +569,8 @@
ScopedLocalRef<jobject> fklass(jnienv,
AddLocalRef<jobject>(jnienv,
field->GetDeclaringClass().Ptr()));
- RunEventCallback<ArtJvmtiEvent::kFieldAccess>(self,
+ RunEventCallback<ArtJvmtiEvent::kFieldAccess>(event_handler_,
+ self,
jnienv,
art::jni::EncodeArtMethod(method),
static_cast<jlocation>(dex_pc),
@@ -492,6 +598,7 @@
jvalue val;
val.l = fval.get();
RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -525,6 +632,7 @@
// the union.
val.j = field_value.GetJ();
RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -545,6 +653,7 @@
art::JNIEnvExt* jnienv = self->GetJniEnv();
jboolean is_exception_pending = self->IsExceptionPending();
RunEventCallback<ArtJvmtiEvent::kFramePop>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(frame.GetMethod()),
@@ -639,6 +748,7 @@
ScopedLocalRef<jobject> exception(jnienv,
AddLocalRef<jobject>(jnienv, exception_object.Get()));
RunEventCallback<ArtJvmtiEvent::kException>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -664,6 +774,7 @@
ScopedLocalRef<jobject> exception(jnienv,
AddLocalRef<jobject>(jnienv, exception_object.Get()));
RunEventCallback<ArtJvmtiEvent::kExceptionCatch>(
+ event_handler_,
self,
jnienv,
art::jni::EncodeArtMethod(method),
@@ -744,19 +855,51 @@
}
}
+// Makes sure that all compiled methods are AsyncDeoptimizable so we can deoptimize (and force to
+// the switch interpreter) when we try to get or set a local variable.
void EventHandler::HandleLocalAccessCapabilityAdded() {
- art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative);
- art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();
- art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(),
- art::gc::kGcCauseInstrumentation,
- art::gc::kCollectorTypeInstrumentation);
- art::ScopedSuspendAll ssa("Deoptimize everything for local variable access", true);
- // TODO This should be disabled when there are no environments using it.
- if (!instr->CanDeoptimize()) {
- instr->EnableDeoptimization();
+ class UpdateEntryPointsClassVisitor : public art::ClassVisitor {
+ public:
+ explicit UpdateEntryPointsClassVisitor(art::Runtime* runtime)
+ : runtime_(runtime) {}
+
+ bool operator()(art::ObjPtr<art::mirror::Class> klass)
+ OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+ for (auto& m : klass->GetMethods(art::kRuntimePointerSize)) {
+ const void* code = m.GetEntryPointFromQuickCompiledCode();
+ if (m.IsNative() || m.IsProxyMethod()) {
+ continue;
+ } else if (!runtime_->GetClassLinker()->IsQuickToInterpreterBridge(code) &&
+ !runtime_->IsAsyncDeoptimizeable(reinterpret_cast<uintptr_t>(code))) {
+ runtime_->GetInstrumentation()->UpdateMethodsCodeToInterpreterEntryPoint(&m);
+ }
+ }
+ return true;
+ }
+
+ private:
+ art::Runtime* runtime_;
+ };
+ art::ScopedObjectAccess soa(art::Thread::Current());
+ UpdateEntryPointsClassVisitor visitor(art::Runtime::Current());
+ art::Runtime::Current()->GetClassLinker()->VisitClasses(&visitor);
+}
+
+bool EventHandler::OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event) {
+ std::array<ArtJvmtiEvent, 4> events {
+ {
+ ArtJvmtiEvent::kMonitorContendedEnter,
+ ArtJvmtiEvent::kMonitorContendedEntered,
+ ArtJvmtiEvent::kMonitorWait,
+ ArtJvmtiEvent::kMonitorWaited
+ }
+ };
+ for (ArtJvmtiEvent e : events) {
+ if (e != event && IsEventEnabledAnywhere(e)) {
+ return true;
+ }
}
- // TODO We should be able to support can_access_local_variables without this.
- instr->DeoptimizeEverything("jvmti-local-variable-access");
+ return false;
}
// Handle special work for the given event type, if necessary.
@@ -799,7 +942,14 @@
case ArtJvmtiEvent::kExceptionCatch:
SetupTraceListener(method_trace_listener_.get(), event, enable);
return;
-
+ case ArtJvmtiEvent::kMonitorContendedEnter:
+ case ArtJvmtiEvent::kMonitorContendedEntered:
+ case ArtJvmtiEvent::kMonitorWait:
+ case ArtJvmtiEvent::kMonitorWaited:
+ if (!OtherMonitorEventsEnabledAnywhere(event)) {
+ SetupMonitorListener(monitor_listener_.get(), enable);
+ }
+ return;
default:
break;
}
@@ -928,6 +1078,7 @@
alloc_listener_.reset(new JvmtiAllocationListener(this));
gc_pause_listener_.reset(new JvmtiGcPauseListener(this));
method_trace_listener_.reset(new JvmtiMethodTraceListener(this));
+ monitor_listener_.reset(new JvmtiMonitorListener(this));
}
EventHandler::~EventHandler() {
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index b49e80c..0d36c46 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -30,6 +30,7 @@
class JvmtiAllocationListener;
class JvmtiGcPauseListener;
class JvmtiMethodTraceListener;
+class JvmtiMonitorListener;
// an enum for ArtEvents. This differs from the JVMTI events only in that we distinguish between
// retransformation capable and incapable loading
@@ -212,6 +213,8 @@
void HandleEventType(ArtJvmtiEvent event, bool enable);
void HandleLocalAccessCapabilityAdded();
+ bool OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event);
+
// List of all JvmTiEnv objects that have been created, in their creation order.
// NB Some elements might be null representing envs that have been deleted. They should be skipped
// anytime this list is used.
@@ -223,6 +226,7 @@
std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
+ std::unique_ptr<JvmtiMonitorListener> monitor_listener_;
// True if frame pop has ever been enabled. Since we store pointers to stack frames we need to
// continue to listen to this event even if it has been disabled.
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc
index f99b167..05943e7 100644
--- a/openjdkjvmti/ti_method.cc
+++ b/openjdkjvmti/ti_method.cc
@@ -39,6 +39,7 @@
#include "base/mutex-inl.h"
#include "dex_file_annotations.h"
#include "events-inl.h"
+#include "jit/jit.h"
#include "jni_internal.h"
#include "mirror/class-inl.h"
#include "mirror/class_loader.h"
@@ -552,7 +553,7 @@
return;
}
art::ArtMethod* method = visitor.GetMethod();
- if (method->IsNative() || !visitor.IsShadowFrame()) {
+ if (method->IsNative()) {
// TODO We really should support get/set for non-shadow frames.
result_ = ERR(OPAQUE_FRAME);
return;
@@ -560,6 +561,7 @@
result_ = ERR(INVALID_SLOT);
return;
}
+ bool needs_instrument = !visitor.IsShadowFrame();
uint32_t pc = visitor.GetDexPc(/*abort_on_failure*/ false);
if (pc == art::DexFile::kDexNoIndex) {
// Cannot figure out current PC.
@@ -580,6 +582,9 @@
return;
}
result_ = Execute(method, visitor);
+ if (needs_instrument) {
+ art::Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(self);
+ }
}
jvmtiError GetResult() const {
@@ -751,6 +756,8 @@
return ERR(ILLEGAL_ARGUMENT);
}
art::Thread* self = art::Thread::Current();
+ // Suspend JIT since it can get confused if we deoptimize methods getting jitted.
+ art::jit::ScopedJitSuspend suspend_jit;
art::ScopedObjectAccess soa(self);
art::MutexLock mu(self, *art::Locks::thread_list_lock_);
art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
@@ -878,6 +885,8 @@
return ERR(ILLEGAL_ARGUMENT);
}
art::Thread* self = art::Thread::Current();
+ // Suspend JIT since it can get confused if we deoptimize methods getting jitted.
+ art::jit::ScopedJitSuspend suspend_jit;
art::ScopedObjectAccess soa(self);
art::MutexLock mu(self, *art::Locks::thread_list_lock_);
art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
diff --git a/openjdkjvmti/ti_monitor.cc b/openjdkjvmti/ti_monitor.cc
index adaa48c..f92d81e 100644
--- a/openjdkjvmti/ti_monitor.cc
+++ b/openjdkjvmti/ti_monitor.cc
@@ -37,10 +37,13 @@
#include <mutex>
#include "art_jvmti.h"
+#include "monitor.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-current-inl.h"
#include "ti_thread.h"
+#include "thread.h"
+#include "thread_pool.h"
namespace openjdkjvmti {
@@ -323,4 +326,77 @@
return ERR(NONE);
}
+jvmtiError MonitorUtil::GetCurrentContendedMonitor(jvmtiEnv* env ATTRIBUTE_UNUSED,
+ jthread thread,
+ jobject* monitor) {
+ if (monitor == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ art::Thread* self = art::Thread::Current();
+ art::ScopedObjectAccess soa(self);
+ art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+ art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+ if (target == nullptr && thread == nullptr) {
+ return ERR(INVALID_THREAD);
+ }
+ if (target == nullptr) {
+ return ERR(THREAD_NOT_ALIVE);
+ }
+ struct GetContendedMonitorClosure : public art::Closure {
+ public:
+ explicit GetContendedMonitorClosure(art::Thread* current, jobject* out)
+ : result_thread_(current), out_(out) {}
+
+ void Run(art::Thread* target_thread) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ switch (target_thread->GetState()) {
+ // These three we are actually currently waiting on a monitor and have sent the appropriate
+ // events (if anyone is listening).
+ case art::kBlocked:
+ case art::kTimedWaiting:
+ case art::kWaiting: {
+ art::mirror::Object* mon = art::Monitor::GetContendedMonitor(target_thread);
+ *out_ = (mon == nullptr) ? nullptr
+ : result_thread_->GetJniEnv()->AddLocalReference<jobject>(mon);
+ return;
+ }
+ case art::kTerminated:
+ case art::kRunnable:
+ case art::kSleeping:
+ case art::kWaitingForLockInflation:
+ case art::kWaitingForTaskProcessor:
+ case art::kWaitingForGcToComplete:
+ case art::kWaitingForCheckPointsToRun:
+ case art::kWaitingPerformingGc:
+ case art::kWaitingForDebuggerSend:
+ case art::kWaitingForDebuggerToAttach:
+ case art::kWaitingInMainDebuggerLoop:
+ case art::kWaitingForDebuggerSuspension:
+ case art::kWaitingForJniOnLoad:
+ case art::kWaitingForSignalCatcherOutput:
+ case art::kWaitingInMainSignalCatcherLoop:
+ case art::kWaitingForDeoptimization:
+ case art::kWaitingForMethodTracingStart:
+ case art::kWaitingForVisitObjects:
+ case art::kWaitingForGetObjectsAllocated:
+ case art::kWaitingWeakGcRootRead:
+ case art::kWaitingForGcThreadFlip:
+ case art::kStarting:
+ case art::kNative:
+ case art::kSuspended: {
+ // We aren't currently (explicitly) waiting for a monitor anything so just return null.
+ *out_ = nullptr;
+ return;
+ }
+ }
+ }
+
+ private:
+ art::Thread* result_thread_;
+ jobject* out_;
+ };
+ GetContendedMonitorClosure closure(self, monitor);
+ target->RequestSynchronousCheckpoint(&closure);
+ return OK;
+}
+
} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_monitor.h b/openjdkjvmti/ti_monitor.h
index add089c..e0a865b 100644
--- a/openjdkjvmti/ti_monitor.h
+++ b/openjdkjvmti/ti_monitor.h
@@ -52,6 +52,8 @@
static jvmtiError RawMonitorNotify(jvmtiEnv* env, jrawMonitorID monitor);
static jvmtiError RawMonitorNotifyAll(jvmtiEnv* env, jrawMonitorID monitor);
+
+ static jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env, jthread thr, jobject* monitor);
};
} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_object.cc b/openjdkjvmti/ti_object.cc
index 2506aca..89ce352 100644
--- a/openjdkjvmti/ti_object.cc
+++ b/openjdkjvmti/ti_object.cc
@@ -35,6 +35,8 @@
#include "mirror/object-inl.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_thread.h"
namespace openjdkjvmti {
@@ -73,4 +75,59 @@
return ERR(NONE);
}
+jvmtiError ObjectUtil::GetObjectMonitorUsage(
+ jvmtiEnv* baseenv, jobject obj, jvmtiMonitorUsage* usage) {
+ ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(baseenv);
+ if (obj == nullptr) {
+ return ERR(INVALID_OBJECT);
+ }
+ if (usage == nullptr) {
+ return ERR(NULL_POINTER);
+ }
+ art::Thread* self = art::Thread::Current();
+ ThreadUtil::SuspendCheck(self);
+ art::JNIEnvExt* jni = self->GetJniEnv();
+ std::vector<jthread> wait;
+ std::vector<jthread> notify_wait;
+ {
+ art::ScopedObjectAccess soa(self); // Now we know we have the shared lock.
+ art::ScopedThreadSuspension sts(self, art::kNative);
+ art::ScopedSuspendAll ssa("GetObjectMonitorUsage", /*long_suspend*/false);
+ art::ObjPtr<art::mirror::Object> target(self->DecodeJObject(obj));
+ // This gets the list of threads trying to lock or wait on the monitor.
+ art::MonitorInfo info(target.Ptr());
+ usage->owner = info.owner_ != nullptr ?
+ jni->AddLocalReference<jthread>(info.owner_->GetPeerFromOtherThread()) : nullptr;
+ usage->entry_count = info.entry_count_;
+ for (art::Thread* thd : info.waiters_) {
+ // RI seems to consider waiting for notify to be included in those waiting to acquire the
+ // monitor. We will match this behavior.
+ notify_wait.push_back(jni->AddLocalReference<jthread>(thd->GetPeerFromOtherThread()));
+ wait.push_back(jni->AddLocalReference<jthread>(thd->GetPeerFromOtherThread()));
+ }
+ {
+ // Scan all threads to see which are waiting on this particular monitor.
+ art::MutexLock tll(self, *art::Locks::thread_list_lock_);
+ for (art::Thread* thd : art::Runtime::Current()->GetThreadList()->GetList()) {
+ if (thd != info.owner_ && target.Ptr() == thd->GetMonitorEnterObject()) {
+ wait.push_back(jni->AddLocalReference<jthread>(thd->GetPeerFromOtherThread()));
+ }
+ }
+ }
+ }
+ usage->waiter_count = wait.size();
+ usage->notify_waiter_count = notify_wait.size();
+ jvmtiError ret = CopyDataIntoJvmtiBuffer(env,
+ reinterpret_cast<const unsigned char*>(wait.data()),
+ wait.size() * sizeof(jthread),
+ reinterpret_cast<unsigned char**>(&usage->waiters));
+ if (ret != OK) {
+ return ret;
+ }
+ return CopyDataIntoJvmtiBuffer(env,
+ reinterpret_cast<const unsigned char*>(notify_wait.data()),
+ notify_wait.size() * sizeof(jthread),
+ reinterpret_cast<unsigned char**>(&usage->notify_waiters));
+}
+
} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_object.h b/openjdkjvmti/ti_object.h
index fa3bd0f..977ec39 100644
--- a/openjdkjvmti/ti_object.h
+++ b/openjdkjvmti/ti_object.h
@@ -42,6 +42,8 @@
static jvmtiError GetObjectSize(jvmtiEnv* env, jobject object, jlong* size_ptr);
static jvmtiError GetObjectHashCode(jvmtiEnv* env, jobject object, jint* hash_code_ptr);
+
+ static jvmtiError GetObjectMonitorUsage(jvmtiEnv* env, jobject object, jvmtiMonitorUsage* usage);
};
} // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index 27d01ea..d437e52 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -392,6 +392,8 @@
return JVMTI_JAVA_LANG_THREAD_STATE_NEW;
case art::ThreadState::kWaiting:
+ case art::ThreadState::kWaitingForTaskProcessor:
+ case art::ThreadState::kWaitingForLockInflation:
case art::ThreadState::kWaitingForGcToComplete:
case art::ThreadState::kWaitingPerformingGc:
case art::ThreadState::kWaitingForCheckPointsToRun:
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index f54e81f..edce5b4 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -469,6 +469,9 @@
void PatchOat::PatchInternedStrings(const ImageHeader* image_header) {
const auto& section = image_header->GetInternedStringsSection();
+ if (section.Size() == 0) {
+ return;
+ }
InternTable temp_table;
// Note that we require that ReadFromMemory does not make an internal copy of the elements.
// This also relies on visit roots not doing any verification which could fail after we update
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 6144869..db9707f 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -634,3 +634,9 @@
"libvixld-arm64",
],
}
+
+cc_library_headers {
+ name: "libart_runtime_headers",
+ host_supported: true,
+ export_include_dirs: ["."],
+}
diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h
index 057f58c..2532db9 100644
--- a/runtime/art_field-inl.h
+++ b/runtime/art_field-inl.h
@@ -30,7 +30,6 @@
#include "primitive.h"
#include "scoped_thread_state_change-inl.h"
#include "thread-current-inl.h"
-#include "well_known_classes.h"
namespace art {
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 2d67761..dab3f23 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -671,6 +671,24 @@
template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
ALWAYS_INLINE void UpdateEntrypoints(const Visitor& visitor, PointerSize pointer_size);
+ // Visit the individual members of an ArtMethod. Used by imgdiag.
+ // As imgdiag does not support mixing instruction sets or pointer sizes (e.g., using imgdiag32
+ // to inspect 64-bit images, etc.), we can go beneath the accessors directly to the class members.
+ template <typename VisitorFunc>
+ void VisitMembers(VisitorFunc& visitor) {
+ DCHECK(IsImagePointerSize(kRuntimePointerSize));
+ visitor(this, &declaring_class_, "declaring_class_");
+ visitor(this, &access_flags_, "access_flags_");
+ visitor(this, &dex_code_item_offset_, "dex_code_item_offset_");
+ visitor(this, &dex_method_index_, "dex_method_index_");
+ visitor(this, &method_index_, "method_index_");
+ visitor(this, &hotness_count_, "hotness_count_");
+ visitor(this, &ptr_sized_fields_.data_, "ptr_sized_fields_.data_");
+ visitor(this,
+ &ptr_sized_fields_.entry_point_from_quick_compiled_code_,
+ "ptr_sized_fields_.entry_point_from_quick_compiled_code_");
+ }
+
protected:
// Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
// The class we are a part of.
diff --git a/runtime/base/arena_allocator.cc b/runtime/base/arena_allocator.cc
index 148ef86..8738adf 100644
--- a/runtime/base/arena_allocator.cc
+++ b/runtime/base/arena_allocator.cc
@@ -73,6 +73,7 @@
"BCE ",
"DCE ",
"LSE ",
+ "CFRE ",
"LICM ",
"LoopOpt ",
"SsaLiveness ",
diff --git a/runtime/base/arena_allocator.h b/runtime/base/arena_allocator.h
index 0b1a3ba..212edfb 100644
--- a/runtime/base/arena_allocator.h
+++ b/runtime/base/arena_allocator.h
@@ -80,6 +80,7 @@
kArenaAllocBoundsCheckElimination,
kArenaAllocDCE,
kArenaAllocLSE,
+ kArenaAllocCFRE,
kArenaAllocLICM,
kArenaAllocLoopOptimization,
kArenaAllocSsaLiveness,
diff --git a/runtime/base/bit_utils.h b/runtime/base/bit_utils.h
index 0844678..87dac02 100644
--- a/runtime/base/bit_utils.h
+++ b/runtime/base/bit_utils.h
@@ -127,6 +127,14 @@
return (x < 2u) ? x : static_cast<T>(1u) << (std::numeric_limits<T>::digits - CLZ(x - 1u));
}
+// Return highest possible N - a power of two - such that val >= N.
+template <typename T>
+constexpr T TruncToPowerOfTwo(T val) {
+ static_assert(std::is_integral<T>::value, "T must be integral");
+ static_assert(std::is_unsigned<T>::value, "T must be unsigned");
+ return (val != 0) ? static_cast<T>(1u) << (BitSizeOf<T>() - CLZ(val) - 1u) : 0;
+}
+
template<typename T>
constexpr bool IsPowerOfTwo(T x) {
static_assert(std::is_integral<T>::value, "T must be integral");
diff --git a/runtime/base/bit_utils_test.cc b/runtime/base/bit_utils_test.cc
index 9f22fb4..c96c6dc 100644
--- a/runtime/base/bit_utils_test.cc
+++ b/runtime/base/bit_utils_test.cc
@@ -122,6 +122,32 @@
static_assert(33u == MinimumBitsToStore<uint64_t>(UINT64_C(0x1FFFFFFFF)), "TestMinBits2Store64#10");
static_assert(64u == MinimumBitsToStore<uint64_t>(~UINT64_C(0)), "TestMinBits2Store64#11");
+static_assert(0 == TruncToPowerOfTwo<uint32_t>(0u), "TestTruncToPowerOfTwo32#1");
+static_assert(1 == TruncToPowerOfTwo<uint32_t>(1u), "TestTruncToPowerOfTwo32#2");
+static_assert(2 == TruncToPowerOfTwo<uint32_t>(2u), "TestTruncToPowerOfTwo32#3");
+static_assert(2 == TruncToPowerOfTwo<uint32_t>(3u), "TestTruncToPowerOfTwo32#4");
+static_assert(4 == TruncToPowerOfTwo<uint32_t>(7u), "TestTruncToPowerOfTwo32#5");
+static_assert(0x20000u == TruncToPowerOfTwo<uint32_t>(0x3aaaau),
+ "TestTruncToPowerOfTwo32#6");
+static_assert(0x40000000u == TruncToPowerOfTwo<uint32_t>(0x40000001u),
+ "TestTruncToPowerOfTwo32#7");
+static_assert(0x80000000u == TruncToPowerOfTwo<uint32_t>(0x80000000u),
+ "TestTruncToPowerOfTwo32#8");
+
+static_assert(0 == TruncToPowerOfTwo<uint64_t>(UINT64_C(0)), "TestTruncToPowerOfTwo64#1");
+static_assert(1 == TruncToPowerOfTwo<uint64_t>(UINT64_C(1)), "TestTruncToPowerOfTwo64#2");
+static_assert(2 == TruncToPowerOfTwo<uint64_t>(UINT64_C(2)), "TestTruncToPowerOfTwo64#3");
+static_assert(2 == TruncToPowerOfTwo<uint64_t>(UINT64_C(3)), "TestTruncToPowerOfTwo64#4");
+static_assert(4 == TruncToPowerOfTwo<uint64_t>(UINT64_C(7)), "TestTruncToPowerOfTwo64#5");
+static_assert(UINT64_C(0x20000) == TruncToPowerOfTwo<uint64_t>(UINT64_C(0x3aaaa)),
+ "TestTruncToPowerOfTwo64#6");
+static_assert(
+ UINT64_C(0x4000000000000000) == TruncToPowerOfTwo<uint64_t>(UINT64_C(0x4000000000000001)),
+ "TestTruncToPowerOfTwo64#7");
+static_assert(
+ UINT64_C(0x8000000000000000) == TruncToPowerOfTwo<uint64_t>(UINT64_C(0x8000000000000000)),
+ "TestTruncToPowerOfTwo64#8");
+
static_assert(0 == RoundUpToPowerOfTwo<uint32_t>(0u), "TestRoundUpPowerOfTwo32#1");
static_assert(1 == RoundUpToPowerOfTwo<uint32_t>(1u), "TestRoundUpPowerOfTwo32#2");
static_assert(2 == RoundUpToPowerOfTwo<uint32_t>(2u), "TestRoundUpPowerOfTwo32#3");
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index c48fcca..02183ef 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -1189,219 +1189,41 @@
}
};
-// Copies data from one array to another array at the same position
-// if pred returns false. If there is a page of continuous data in
-// the src array for which pred consistently returns true then
-// corresponding page in the dst array will not be touched.
-// This should reduce number of allocated physical pages.
-template <class T, class NullPred>
-static void CopyNonNull(const T* src, size_t count, T* dst, const NullPred& pred) {
- for (size_t i = 0; i < count; ++i) {
- if (!pred(src[i])) {
- dst[i] = src[i];
- }
- }
-}
-
-template <typename T>
-static void CopyDexCachePairs(const std::atomic<mirror::DexCachePair<T>>* src,
- size_t count,
- std::atomic<mirror::DexCachePair<T>>* dst) {
- DCHECK_NE(count, 0u);
- DCHECK(!src[0].load(std::memory_order_relaxed).object.IsNull() ||
- src[0].load(std::memory_order_relaxed).index != 0u);
- for (size_t i = 0; i < count; ++i) {
- DCHECK_EQ(dst[i].load(std::memory_order_relaxed).index, 0u);
- DCHECK(dst[i].load(std::memory_order_relaxed).object.IsNull());
- mirror::DexCachePair<T> source = src[i].load(std::memory_order_relaxed);
- if (source.index != 0u || !source.object.IsNull()) {
- dst[i].store(source, std::memory_order_relaxed);
- }
- }
-}
-
-template <typename T>
-static void CopyNativeDexCachePairs(std::atomic<mirror::NativeDexCachePair<T>>* src,
- size_t count,
- std::atomic<mirror::NativeDexCachePair<T>>* dst,
- PointerSize pointer_size) {
- DCHECK_NE(count, 0u);
- DCHECK(mirror::DexCache::GetNativePairPtrSize(src, 0, pointer_size).object != nullptr ||
- mirror::DexCache::GetNativePairPtrSize(src, 0, pointer_size).index != 0u);
- for (size_t i = 0; i < count; ++i) {
- DCHECK_EQ(mirror::DexCache::GetNativePairPtrSize(dst, i, pointer_size).index, 0u);
- DCHECK(mirror::DexCache::GetNativePairPtrSize(dst, i, pointer_size).object == nullptr);
- mirror::NativeDexCachePair<T> source =
- mirror::DexCache::GetNativePairPtrSize(src, i, pointer_size);
- if (source.index != 0u || source.object != nullptr) {
- mirror::DexCache::SetNativePairPtrSize(dst, i, source, pointer_size);
- }
- }
-}
-
// new_class_set is the set of classes that were read from the class table section in the image.
// If there was no class table section, it is null.
// Note: using a class here to avoid having to make ClassLinker internals public.
class AppImageClassLoadersAndDexCachesHelper {
public:
- static bool Update(
+ static void Update(
ClassLinker* class_linker,
gc::space::ImageSpace* space,
Handle<mirror::ClassLoader> class_loader,
Handle<mirror::ObjectArray<mirror::DexCache>> dex_caches,
- ClassTable::ClassSet* new_class_set,
- bool* out_forward_dex_cache_array,
- std::string* out_error_msg)
+ ClassTable::ClassSet* new_class_set)
REQUIRES(!Locks::dex_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
};
-bool AppImageClassLoadersAndDexCachesHelper::Update(
+void AppImageClassLoadersAndDexCachesHelper::Update(
ClassLinker* class_linker,
gc::space::ImageSpace* space,
Handle<mirror::ClassLoader> class_loader,
Handle<mirror::ObjectArray<mirror::DexCache>> dex_caches,
- ClassTable::ClassSet* new_class_set,
- bool* out_forward_dex_cache_array,
- std::string* out_error_msg)
+ ClassTable::ClassSet* new_class_set)
REQUIRES(!Locks::dex_lock_)
REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(out_forward_dex_cache_array != nullptr);
- DCHECK(out_error_msg != nullptr);
- PointerSize image_pointer_size = class_linker->GetImagePointerSize();
Thread* const self = Thread::Current();
gc::Heap* const heap = Runtime::Current()->GetHeap();
const ImageHeader& header = space->GetImageHeader();
{
- // Add image classes into the class table for the class loader, and fixup the dex caches and
- // class loader fields.
+ // Register dex caches with the class loader.
WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
- // Dex cache array fixup is all or nothing, we must reject app images that have mixed since we
- // rely on clobering the dex cache arrays in the image to forward to bss.
- size_t num_dex_caches_with_bss_arrays = 0;
const size_t num_dex_caches = dex_caches->GetLength();
for (size_t i = 0; i < num_dex_caches; i++) {
- ObjPtr<mirror::DexCache> const dex_cache = dex_caches->Get(i);
- const DexFile* const dex_file = dex_cache->GetDexFile();
- const OatFile::OatDexFile* oat_dex_file = dex_file->GetOatDexFile();
- if (oat_dex_file != nullptr && oat_dex_file->GetDexCacheArrays() != nullptr) {
- ++num_dex_caches_with_bss_arrays;
- }
- }
- *out_forward_dex_cache_array = num_dex_caches_with_bss_arrays != 0;
- if (*out_forward_dex_cache_array) {
- if (num_dex_caches_with_bss_arrays != num_dex_caches) {
- // Reject application image since we cannot forward only some of the dex cache arrays.
- // TODO: We could get around this by having a dedicated forwarding slot. It should be an
- // uncommon case.
- *out_error_msg = StringPrintf("Dex caches in bss does not match total: %zu vs %zu",
- num_dex_caches_with_bss_arrays,
- num_dex_caches);
- return false;
- }
- }
-
- // Only add the classes to the class loader after the points where we can return false.
- for (size_t i = 0; i < num_dex_caches; i++) {
ObjPtr<mirror::DexCache> dex_cache = dex_caches->Get(i);
const DexFile* const dex_file = dex_cache->GetDexFile();
- const OatFile::OatDexFile* oat_dex_file = dex_file->GetOatDexFile();
- if (oat_dex_file != nullptr && oat_dex_file->GetDexCacheArrays() != nullptr) {
- // If the oat file expects the dex cache arrays to be in the BSS, then allocate there and
- // copy over the arrays.
- DCHECK(dex_file != nullptr);
- size_t num_strings = mirror::DexCache::kDexCacheStringCacheSize;
- if (dex_file->NumStringIds() < num_strings) {
- num_strings = dex_file->NumStringIds();
- }
- size_t num_types = mirror::DexCache::kDexCacheTypeCacheSize;
- if (dex_file->NumTypeIds() < num_types) {
- num_types = dex_file->NumTypeIds();
- }
- size_t num_methods = mirror::DexCache::kDexCacheMethodCacheSize;
- if (dex_file->NumMethodIds() < num_methods) {
- num_methods = dex_file->NumMethodIds();
- }
- size_t num_fields = mirror::DexCache::kDexCacheFieldCacheSize;
- if (dex_file->NumFieldIds() < num_fields) {
- num_fields = dex_file->NumFieldIds();
- }
- size_t num_method_types = mirror::DexCache::kDexCacheMethodTypeCacheSize;
- if (dex_file->NumProtoIds() < num_method_types) {
- num_method_types = dex_file->NumProtoIds();
- }
- const size_t num_call_sites = dex_file->NumCallSiteIds();
- CHECK_EQ(num_strings, dex_cache->NumStrings());
- CHECK_EQ(num_types, dex_cache->NumResolvedTypes());
- CHECK_EQ(num_methods, dex_cache->NumResolvedMethods());
- CHECK_EQ(num_fields, dex_cache->NumResolvedFields());
- CHECK_EQ(num_method_types, dex_cache->NumResolvedMethodTypes());
- CHECK_EQ(num_call_sites, dex_cache->NumResolvedCallSites());
- DexCacheArraysLayout layout(image_pointer_size, dex_file);
- uint8_t* const raw_arrays = oat_dex_file->GetDexCacheArrays();
- if (num_strings != 0u) {
- mirror::StringDexCacheType* const image_resolved_strings = dex_cache->GetStrings();
- mirror::StringDexCacheType* const strings =
- reinterpret_cast<mirror::StringDexCacheType*>(raw_arrays + layout.StringsOffset());
- CopyDexCachePairs(image_resolved_strings, num_strings, strings);
- dex_cache->SetStrings(strings);
- }
- if (num_types != 0u) {
- mirror::TypeDexCacheType* const image_resolved_types = dex_cache->GetResolvedTypes();
- mirror::TypeDexCacheType* const types =
- reinterpret_cast<mirror::TypeDexCacheType*>(raw_arrays + layout.TypesOffset());
- CopyDexCachePairs(image_resolved_types, num_types, types);
- dex_cache->SetResolvedTypes(types);
- }
- if (num_methods != 0u) {
- mirror::MethodDexCacheType* const image_resolved_methods =
- dex_cache->GetResolvedMethods();
- mirror::MethodDexCacheType* const methods =
- reinterpret_cast<mirror::MethodDexCacheType*>(raw_arrays + layout.MethodsOffset());
- CopyNativeDexCachePairs(image_resolved_methods, num_methods, methods, image_pointer_size);
- dex_cache->SetResolvedMethods(methods);
- }
- if (num_fields != 0u) {
- mirror::FieldDexCacheType* const image_resolved_fields = dex_cache->GetResolvedFields();
- mirror::FieldDexCacheType* const fields =
- reinterpret_cast<mirror::FieldDexCacheType*>(raw_arrays + layout.FieldsOffset());
- CopyNativeDexCachePairs(image_resolved_fields, num_fields, fields, image_pointer_size);
- dex_cache->SetResolvedFields(fields);
- }
- if (num_method_types != 0u) {
- // NOTE: We currently (Sep 2016) do not resolve any method types at
- // compile time, but plan to in the future. This code exists for the
- // sake of completeness.
- mirror::MethodTypeDexCacheType* const image_resolved_method_types =
- dex_cache->GetResolvedMethodTypes();
- mirror::MethodTypeDexCacheType* const method_types =
- reinterpret_cast<mirror::MethodTypeDexCacheType*>(
- raw_arrays + layout.MethodTypesOffset());
- CopyDexCachePairs(image_resolved_method_types, num_method_types, method_types);
- dex_cache->SetResolvedMethodTypes(method_types);
- }
- if (num_call_sites != 0u) {
- GcRoot<mirror::CallSite>* const image_resolved_call_sites =
- dex_cache->GetResolvedCallSites();
- GcRoot<mirror::CallSite>* const call_sites =
- reinterpret_cast<GcRoot<mirror::CallSite>*>(raw_arrays + layout.CallSitesOffset());
- for (size_t j = 0; kIsDebugBuild && j < num_call_sites; ++j) {
- DCHECK(call_sites[j].IsNull());
- }
- CopyNonNull(image_resolved_call_sites,
- num_call_sites,
- call_sites,
- [](const GcRoot<mirror::CallSite>& elem) {
- return elem.IsNull();
- });
- dex_cache->SetResolvedCallSites(call_sites);
- }
- }
{
WriterMutexLock mu2(self, *Locks::dex_lock_);
- // Make sure to do this after we update the arrays since we store the resolved types array
- // in DexCacheData in RegisterDexFileLocked. We need the array pointer to be the one in the
- // BSS.
CHECK(!class_linker->FindDexCacheDataLocked(*dex_file).IsValid());
class_linker->RegisterDexFileLocked(*dex_file, dex_cache, class_loader.Get());
}
@@ -1468,7 +1290,6 @@
VerifyDeclaringClassVisitor visitor;
header.VisitPackedArtMethods(&visitor, space->Begin(), kRuntimePointerSize);
}
- return true;
}
// Update the class loader. Should only be used on classes in the image space.
@@ -1939,6 +1760,17 @@
header.VisitPackedArtMethods(&visitor, space->Begin(), image_pointer_size_);
}
+ if (!app_image) {
+ // Make the string intern table and class table immutable for boot image.
+ // PIC app oat files may mmap a read-only copy into their own .bss section,
+ // so enforce that the data in the boot image tables remains unchanged.
+ //
+ // We cannot do that for app image even after the fixup as some interned
+ // String references may actually end up pointing to moveable Strings.
+ uint8_t* const_section_begin = space->Begin() + header.GetBootImageConstantTablesOffset();
+ mprotect(const_section_begin, header.GetBootImageConstantTablesSize(), PROT_READ);
+ }
+
ClassTable* class_table = nullptr;
{
WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
@@ -1947,7 +1779,7 @@
// If we have a class table section, read it and use it for verification in
// UpdateAppImageClassLoadersAndDexCaches.
ClassTable::ClassSet temp_set;
- const ImageSection& class_table_section = header.GetImageSection(ImageHeader::kSectionClassTable);
+ const ImageSection& class_table_section = header.GetClassTableSection();
const bool added_class_table = class_table_section.Size() > 0u;
if (added_class_table) {
const uint64_t start_time2 = NanoTime();
@@ -1958,37 +1790,17 @@
VLOG(image) << "Adding class table classes took " << PrettyDuration(NanoTime() - start_time2);
}
if (app_image) {
- bool forward_dex_cache_arrays = false;
- if (!AppImageClassLoadersAndDexCachesHelper::Update(this,
- space,
- class_loader,
- dex_caches,
- &temp_set,
- /*out*/&forward_dex_cache_arrays,
- /*out*/error_msg)) {
- return false;
- }
+ AppImageClassLoadersAndDexCachesHelper::Update(this,
+ space,
+ class_loader,
+ dex_caches,
+ &temp_set);
// Update class loader and resolved strings. If added_class_table is false, the resolved
// strings were forwarded UpdateAppImageClassLoadersAndDexCaches.
UpdateClassLoaderVisitor visitor(space, class_loader.Get());
for (const ClassTable::TableSlot& root : temp_set) {
visitor(root.Read());
}
- // forward_dex_cache_arrays is true iff we copied all of the dex cache arrays into the .bss.
- // In this case, madvise away the dex cache arrays section of the image to reduce RAM usage and
- // mark as PROT_NONE to catch any invalid accesses.
- if (forward_dex_cache_arrays) {
- const ImageSection& dex_cache_section = header.GetDexCacheArraysSection();
- uint8_t* section_begin = AlignUp(space->Begin() + dex_cache_section.Offset(), kPageSize);
- uint8_t* section_end = AlignDown(space->Begin() + dex_cache_section.End(), kPageSize);
- if (section_begin < section_end) {
- madvise(section_begin, section_end - section_begin, MADV_DONTNEED);
- mprotect(section_begin, section_end - section_begin, PROT_NONE);
- VLOG(image) << "Released and protected dex cache array image section from "
- << reinterpret_cast<const void*>(section_begin) << "-"
- << reinterpret_cast<const void*>(section_end);
- }
- }
}
if (!oat_file->GetBssGcRoots().empty()) {
// Insert oat file to class table for visiting .bss GC roots.
@@ -4621,10 +4433,6 @@
DCHECK(proxy_constructor != nullptr)
<< "Could not find <init> method in java.lang.reflect.Proxy";
- // Ensure constructor is in dex cache so that we can use the dex cache to look up the overridden
- // constructor method.
- GetClassRoot(kJavaLangReflectProxy)->GetDexCache()->SetResolvedMethod(
- proxy_constructor->GetDexMethodIndex(), proxy_constructor, image_pointer_size_);
// Clone the existing constructor of Proxy (our constructor would just invoke it so steal its
// code_ too)
DCHECK(out != nullptr);
@@ -4651,15 +4459,6 @@
void ClassLinker::CreateProxyMethod(Handle<mirror::Class> klass, ArtMethod* prototype,
ArtMethod* out) {
- // Ensure prototype is in dex cache so that we can use the dex cache to look up the overridden
- // prototype method
- auto* dex_cache = prototype->GetDeclaringClass()->GetDexCache();
- // Avoid dirtying the dex cache unless we need to.
- if (dex_cache->GetResolvedMethod(prototype->GetDexMethodIndex(), image_pointer_size_) !=
- prototype) {
- dex_cache->SetResolvedMethod(
- prototype->GetDexMethodIndex(), prototype, image_pointer_size_);
- }
// We steal everything from the prototype (such as DexCache, invoke stub, etc.) then specialize
// as necessary
DCHECK(out != nullptr);
@@ -8562,6 +8361,10 @@
(quick_generic_jni_trampoline_ == entry_point);
}
+bool ClassLinker::IsJniDlsymLookupStub(const void* entry_point) const {
+ return entry_point == GetJniDlsymLookupStub();
+}
+
const void* ClassLinker::GetRuntimeQuickGenericJniStub() const {
return GetQuickGenericJniStub();
}
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 783ec74..2f92da3 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -500,6 +500,9 @@
// Is the given entry point quick code to run the generic JNI stub?
bool IsQuickGenericJniStub(const void* entry_point) const;
+ // Is the given entry point the JNI dlsym lookup stub?
+ bool IsJniDlsymLookupStub(const void* entry_point) const;
+
const void* GetQuickToInterpreterBridgeTrampoline() const {
return quick_to_interpreter_bridge_trampoline_;
}
@@ -1278,6 +1281,7 @@
friend class ImageWriter; // for GetClassRoots
friend class JniCompilerTest; // for GetRuntimeQuickGenericJniStub
friend class JniInternalTest; // for GetRuntimeQuickGenericJniStub
+ friend class OatWriter; // for boot image string/class table slot address lookup.
friend class VMClassLoader; // for LookupClass and FindClassInBaseDexClassLoader.
ART_FRIEND_TEST(ClassLinkerTest, RegisterDexFileName); // for DexLock, and RegisterDexFileLocked
ART_FRIEND_TEST(mirror::DexCacheMethodHandlesTest, Open); // for AllocDexCache
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 07afedf..3bd4596 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -187,7 +187,10 @@
// Opens requested class path files and appends them to opened_dex_files. If the dex files have
// been stripped, this opens them from their oat files (which get added to opened_oat_files).
bool ClassLoaderContext::OpenDexFiles(InstructionSet isa, const std::string& classpath_dir) {
- CHECK(!dex_files_open_attempted_) << "OpenDexFiles should not be called twice";
+ if (dex_files_open_attempted_) {
+ // Do not attempt to re-open the files if we already tried.
+ return dex_files_open_result_;
+ }
dex_files_open_attempted_ = true;
// Assume we can open all dex files. If not, we will set this to false as we go.
@@ -203,6 +206,7 @@
// TODO(calin): Refine the dex opening interface to be able to tell if an archive contains
// no dex files. So that we can distinguish the real failures...
for (ClassLoaderInfo& info : class_loader_chain_) {
+ size_t opened_dex_files_index = info.opened_dex_files.size();
for (const std::string& cp_elem : info.classpath) {
// If path is relative, append it to the provided base directory.
std::string raw_location = cp_elem;
@@ -249,6 +253,23 @@
}
}
}
+
+ // We finished opening the dex files from the classpath.
+ // Now update the classpath and the checksum with the locations of the dex files.
+ //
+ // We do this because initially the classpath contains the paths of the dex files; and
+ // some of them might be multi-dexes. So in order to have a consistent view we replace all the
+ // file paths with the actual dex locations being loaded.
+ // This will allow the context to VerifyClassLoaderContextMatch which expects or multidex
+ // location in the class paths.
+ // Note that this will also remove the paths that could not be opened.
+ info.classpath.clear();
+ info.checksums.clear();
+ for (size_t k = opened_dex_files_index; k < info.opened_dex_files.size(); k++) {
+ std::unique_ptr<const DexFile>& dex = info.opened_dex_files[k];
+ info.classpath.push_back(dex->GetLocation());
+ info.checksums.push_back(dex->GetLocationChecksum());
+ }
}
return dex_files_open_result_;
@@ -632,14 +653,25 @@
}
}
+static bool IsAbsoluteLocation(const std::string& location) {
+ return !location.empty() && location[0] == '/';
+}
+
bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) const {
+ DCHECK(dex_files_open_attempted_);
+ DCHECK(dex_files_open_result_);
+
ClassLoaderContext expected_context;
if (!expected_context.Parse(context_spec, /*parse_checksums*/ true)) {
LOG(WARNING) << "Invalid class loader context: " << context_spec;
return false;
}
- if (expected_context.special_shared_library_) {
+ // Special shared library contexts always match. They essentially instruct the runtime
+ // to ignore the class path check because the oat file is known to be loaded in different
+ // contexts. OatFileManager will further verify if the oat file can be loaded based on the
+ // collision check.
+ if (special_shared_library_ || expected_context.special_shared_library_) {
return true;
}
@@ -673,18 +705,52 @@
DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size());
for (size_t k = 0; k < info.classpath.size(); k++) {
- if (info.classpath[k] != expected_info.classpath[k]) {
+ // Compute the dex location that must be compared.
+ // We shouldn't do a naive comparison `info.classpath[k] == expected_info.classpath[k]`
+ // because even if they refer to the same file, one could be encoded as a relative location
+ // and the other as an absolute one.
+ bool is_dex_name_absolute = IsAbsoluteLocation(info.classpath[k]);
+ bool is_expected_dex_name_absolute = IsAbsoluteLocation(expected_info.classpath[k]);
+ std::string dex_name;
+ std::string expected_dex_name;
+
+ if (is_dex_name_absolute == is_expected_dex_name_absolute) {
+ // If both locations are absolute or relative then compare them as they are.
+ // This is usually the case for: shared libraries and secondary dex files.
+ dex_name = info.classpath[k];
+ expected_dex_name = expected_info.classpath[k];
+ } else if (is_dex_name_absolute) {
+ // The runtime name is absolute but the compiled name (the expected one) is relative.
+ // This is the case for split apks which depend on base or on other splits.
+ dex_name = info.classpath[k];
+ expected_dex_name = OatFile::ResolveRelativeEncodedDexLocation(
+ info.classpath[k].c_str(), expected_info.classpath[k]);
+ } else {
+ // The runtime name is relative but the compiled name is absolute.
+ // There is no expected use case that would end up here as dex files are always loaded
+ // with their absolute location. However, be tolerant and do the best effort (in case
+ // there are unexpected new use case...).
+ DCHECK(is_expected_dex_name_absolute);
+ dex_name = OatFile::ResolveRelativeEncodedDexLocation(
+ expected_info.classpath[k].c_str(), info.classpath[k]);
+ expected_dex_name = expected_info.classpath[k];
+ }
+
+ // Compare the locations.
+ if (dex_name != expected_dex_name) {
LOG(WARNING) << "ClassLoaderContext classpath element mismatch for position " << i
<< ". expected=" << expected_info.classpath[k]
<< ", found=" << info.classpath[k]
<< " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
return false;
}
+
+ // Compare the checksums.
if (info.checksums[k] != expected_info.checksums[k]) {
LOG(WARNING) << "ClassLoaderContext classpath element checksum mismatch for position " << i
- << ". expected=" << expected_info.checksums[k]
- << ", found=" << info.checksums[k]
- << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
+ << ". expected=" << expected_info.checksums[k]
+ << ", found=" << info.checksums[k]
+ << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
return false;
}
}
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index 9afa880..692a6cd 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -41,7 +41,12 @@
// to ClassLoaderInfo::opened_oat_files. The 'classpath_dir' argument specifies the directory to
// use for the relative class paths.
// Returns true if all dex files where successfully opened.
- // It may be called only once per ClassLoaderContext. The second call will abort.
+ // It may be called only once per ClassLoaderContext. Subsequent calls will return the same
+ // result without doing anything.
+ //
+ // This will replace the class path locations with the locations of the opened dex files.
+ // (Note that one dex file can contain multidexes. Each multidex will be added to the classpath
+ // separately.)
//
// Note that a "false" return could mean that either an apk/jar contained no dex files or
// that we hit a I/O or checksum mismatch error.
@@ -98,6 +103,7 @@
// - the number and type of the class loaders from the chain matches
// - the class loader from the same position have the same classpath
// (the order and checksum of the dex files matches)
+ // This should be called after OpenDexFiles().
bool VerifyClassLoaderContextMatch(const std::string& context_spec) const;
// Creates the class loader context from the given string.
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index ddbb73b..ae3dcec 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -87,60 +87,29 @@
void VerifyOpenDexFiles(
ClassLoaderContext* context,
size_t index,
- std::vector<std::vector<std::unique_ptr<const DexFile>>*>& all_dex_files,
- LocationCheck mode = LocationCheck::kEquals,
- BaseLocationCheck base_mode = BaseLocationCheck::kEquals) {
+ std::vector<std::unique_ptr<const DexFile>>* all_dex_files) {
ASSERT_TRUE(context != nullptr);
ASSERT_TRUE(context->dex_files_open_attempted_);
ASSERT_TRUE(context->dex_files_open_result_);
ClassLoaderContext::ClassLoaderInfo& info = context->class_loader_chain_[index];
- ASSERT_EQ(all_dex_files.size(), info.classpath.size());
+ ASSERT_EQ(all_dex_files->size(), info.classpath.size());
+ ASSERT_EQ(all_dex_files->size(), info.opened_dex_files.size());
size_t cur_open_dex_index = 0;
- for (size_t k = 0; k < all_dex_files.size(); k++) {
- std::vector<std::unique_ptr<const DexFile>>& dex_files_for_cp_elem = *(all_dex_files[k]);
- for (size_t i = 0; i < dex_files_for_cp_elem.size(); i++) {
- ASSERT_LT(cur_open_dex_index, info.opened_dex_files.size());
-
- std::unique_ptr<const DexFile>& opened_dex_file =
+ for (size_t k = 0; k < all_dex_files->size(); k++) {
+ std::unique_ptr<const DexFile>& opened_dex_file =
info.opened_dex_files[cur_open_dex_index++];
- std::unique_ptr<const DexFile>& expected_dex_file = dex_files_for_cp_elem[i];
+ std::unique_ptr<const DexFile>& expected_dex_file = (*all_dex_files)[k];
- std::string expected_location = expected_dex_file->GetBaseLocation();
- UniqueCPtr<const char[]> expected_real_location(
- realpath(expected_location.c_str(), nullptr));
- ASSERT_TRUE(expected_real_location != nullptr) << expected_location;
- expected_location.assign(expected_real_location.get());
- expected_location += DexFile::GetMultiDexSuffix(expected_dex_file->GetLocation());
+ std::string expected_location = expected_dex_file->GetBaseLocation();
+ UniqueCPtr<const char[]> expected_real_location(
+ realpath(expected_location.c_str(), nullptr));
+ ASSERT_TRUE(expected_real_location != nullptr) << expected_location;
+ expected_location.assign(expected_real_location.get());
+ expected_location += DexFile::GetMultiDexSuffix(expected_dex_file->GetLocation());
- switch (mode) {
- case LocationCheck::kEquals:
- ASSERT_EQ(expected_dex_file->GetLocation(), opened_dex_file->GetLocation());
- break;
- case LocationCheck::kEndsWith:
- ASSERT_TRUE(android::base::EndsWith(expected_dex_file->GetLocation(),
- opened_dex_file->GetLocation().c_str()))
- << opened_dex_file->GetLocation() << " vs " << expected_dex_file->GetLocation();
- break;
- }
- ASSERT_EQ(expected_dex_file->GetLocationChecksum(), opened_dex_file->GetLocationChecksum());
-
- std::string class_path_location = info.classpath[k];
- UniqueCPtr<const char[]> class_path_location_real(
- realpath(class_path_location.c_str(), nullptr));
- ASSERT_TRUE(class_path_location_real != nullptr);
- class_path_location.assign(class_path_location_real.get());
- switch (base_mode) {
- case BaseLocationCheck::kEquals:
- ASSERT_EQ(class_path_location, opened_dex_file->GetBaseLocation());
- break;
-
- case BaseLocationCheck::kEndsWith:
- ASSERT_TRUE(android::base::EndsWith(opened_dex_file->GetBaseLocation(),
- class_path_location.c_str()))
- << info.classpath[k] << " vs " << opened_dex_file->GetBaseLocation();
- break;
- }
- }
+ ASSERT_EQ(expected_location, opened_dex_file->GetLocation());
+ ASSERT_EQ(expected_dex_file->GetLocationChecksum(), opened_dex_file->GetLocationChecksum());
+ ASSERT_EQ(info.classpath[k], opened_dex_file->GetLocation());
}
}
@@ -182,6 +151,11 @@
}
}
+ void PretendContextOpenedDexFiles(ClassLoaderContext* context) {
+ context->dex_files_open_attempted_ = true;
+ context->dex_files_open_result_ = true;
+ }
+
private:
void VerifyClassLoaderInfo(ClassLoaderContext* context,
size_t index,
@@ -201,11 +175,9 @@
ClassLoaderContext::ClassLoaderType type,
const std::string& test_name) {
std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles(test_name.c_str());
- std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files;
- all_dex_files.push_back(&dex_files);
VerifyClassLoaderInfo(context, index, type, GetTestDexFileName(test_name.c_str()));
- VerifyOpenDexFiles(context, index, all_dex_files);
+ VerifyOpenDexFiles(context, index, &dex_files);
}
};
@@ -276,11 +248,8 @@
TEST_F(ClassLoaderContextTest, OpenValidDexFiles) {
std::string multidex_name = GetTestDexFileName("MultiDex");
- std::vector<std::unique_ptr<const DexFile>> multidex_files = OpenTestDexFiles("MultiDex");
std::string myclass_dex_name = GetTestDexFileName("MyClass");
- std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass");
std::string dex_name = GetTestDexFileName("Main");
- std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Main");
std::unique_ptr<ClassLoaderContext> context =
ClassLoaderContext::Create(
@@ -290,14 +259,16 @@
ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, /*classpath_dir*/ ""));
VerifyContextSize(context.get(), 2);
- std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files0;
- all_dex_files0.push_back(&multidex_files);
- all_dex_files0.push_back(&myclass_dex_files);
- std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files1;
- all_dex_files1.push_back(&dex_files);
- VerifyOpenDexFiles(context.get(), 0, all_dex_files0);
- VerifyOpenDexFiles(context.get(), 1, all_dex_files1);
+ std::vector<std::unique_ptr<const DexFile>> all_dex_files0 = OpenTestDexFiles("MultiDex");
+ std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass");
+ for (size_t i = 0; i < myclass_dex_files.size(); i++) {
+ all_dex_files0.emplace_back(myclass_dex_files[i].release());
+ }
+ VerifyOpenDexFiles(context.get(), 0, &all_dex_files0);
+
+ std::vector<std::unique_ptr<const DexFile>> all_dex_files1 = OpenTestDexFiles("Main");
+ VerifyOpenDexFiles(context.get(), 1, &all_dex_files1);
}
class ScratchSymLink {
@@ -330,11 +301,10 @@
ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, /*classpath_dir*/ ""));
VerifyContextSize(context.get(), 1);
- std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files0;
- std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass");
- all_dex_files0.push_back(&myclass_dex_files);
- VerifyOpenDexFiles(context.get(), 0, all_dex_files0);
+ std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass");
+
+ VerifyOpenDexFiles(context.get(), 0, &myclass_dex_files);
}
static std::string CreateRelativeString(const std::string& in, const char* cwd) {
@@ -353,11 +323,8 @@
PLOG(FATAL) << "Could not get working directory";
}
std::string multidex_name = CreateRelativeString(GetTestDexFileName("MultiDex"), cwd_buf);
- std::vector<std::unique_ptr<const DexFile>> multidex_files = OpenTestDexFiles("MultiDex");
std::string myclass_dex_name = CreateRelativeString(GetTestDexFileName("MyClass"), cwd_buf);
- std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass");
std::string dex_name = CreateRelativeString(GetTestDexFileName("Main"), cwd_buf);
- std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Main");
std::unique_ptr<ClassLoaderContext> context =
@@ -367,15 +334,15 @@
ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, /*classpath_dir*/ ""));
- VerifyContextSize(context.get(), 2);
- std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files0;
- all_dex_files0.push_back(&multidex_files);
- all_dex_files0.push_back(&myclass_dex_files);
- std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files1;
- all_dex_files1.push_back(&dex_files);
+ std::vector<std::unique_ptr<const DexFile>> all_dex_files0 = OpenTestDexFiles("MultiDex");
+ std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass");
+ for (size_t i = 0; i < myclass_dex_files.size(); i++) {
+ all_dex_files0.emplace_back(myclass_dex_files[i].release());
+ }
+ VerifyOpenDexFiles(context.get(), 0, &all_dex_files0);
- VerifyOpenDexFiles(context.get(), 0, all_dex_files0, LocationCheck::kEndsWith);
- VerifyOpenDexFiles(context.get(), 1, all_dex_files1, LocationCheck::kEndsWith);
+ std::vector<std::unique_ptr<const DexFile>> all_dex_files1 = OpenTestDexFiles("Main");
+ VerifyOpenDexFiles(context.get(), 1, &all_dex_files1);
}
TEST_F(ClassLoaderContextTest, OpenValidDexFilesClasspathDir) {
@@ -384,12 +351,8 @@
PLOG(FATAL) << "Could not get working directory";
}
std::string multidex_name = CreateRelativeString(GetTestDexFileName("MultiDex"), cwd_buf);
- std::vector<std::unique_ptr<const DexFile>> multidex_files = OpenTestDexFiles("MultiDex");
std::string myclass_dex_name = CreateRelativeString(GetTestDexFileName("MyClass"), cwd_buf);
- std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass");
std::string dex_name = CreateRelativeString(GetTestDexFileName("Main"), cwd_buf);
- std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("Main");
-
std::unique_ptr<ClassLoaderContext> context =
ClassLoaderContext::Create(
@@ -399,16 +362,15 @@
ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, cwd_buf));
VerifyContextSize(context.get(), 2);
- std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files0;
- all_dex_files0.push_back(&multidex_files);
- all_dex_files0.push_back(&myclass_dex_files);
- std::vector<std::vector<std::unique_ptr<const DexFile>>*> all_dex_files1;
- all_dex_files1.push_back(&dex_files);
+ std::vector<std::unique_ptr<const DexFile>> all_dex_files0 = OpenTestDexFiles("MultiDex");
+ std::vector<std::unique_ptr<const DexFile>> myclass_dex_files = OpenTestDexFiles("MyClass");
+ for (size_t i = 0; i < myclass_dex_files.size(); i++) {
+ all_dex_files0.emplace_back(myclass_dex_files[i].release());
+ }
+ VerifyOpenDexFiles(context.get(), 0, &all_dex_files0);
- VerifyOpenDexFiles(
- context.get(), 0, all_dex_files0, LocationCheck::kEquals, BaseLocationCheck::kEndsWith);
- VerifyOpenDexFiles(
- context.get(), 1, all_dex_files1, LocationCheck::kEquals, BaseLocationCheck::kEndsWith);
+ std::vector<std::unique_ptr<const DexFile>> all_dex_files1 = OpenTestDexFiles("Main");
+ VerifyOpenDexFiles(context.get(), 1, &all_dex_files1);
}
TEST_F(ClassLoaderContextTest, OpenInvalidDexFilesMix) {
@@ -660,6 +622,9 @@
TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatch) {
std::string context_spec = "PCL[a.dex*123:b.dex*456];DLC[c.dex*890]";
std::unique_ptr<ClassLoaderContext> context = ParseContextWithChecksums(context_spec);
+ // Pretend that we successfully open the dex files to pass the DCHECKS.
+ // (as it's much easier to test all the corner cases without relying on actual dex files).
+ PretendContextOpenedDexFiles(context.get());
VerifyContextSize(context.get(), 2);
VerifyClassLoaderPCL(context.get(), 0, "a.dex:b.dex");
@@ -697,7 +662,17 @@
std::unique_ptr<ClassLoaderContext> context = CreateContextForClassLoader(class_loader_d);
- ASSERT_TRUE(context->VerifyClassLoaderContextMatch(context->EncodeContextForOatFile("")));
+ std::string context_with_no_base_dir = context->EncodeContextForOatFile("");
+ ASSERT_TRUE(context->VerifyClassLoaderContextMatch(context_with_no_base_dir));
+
+ std::string dex_location = GetTestDexFileName("ForClassLoaderA");
+ size_t pos = dex_location.rfind('/');
+ ASSERT_NE(std::string::npos, pos);
+ std::string parent = dex_location.substr(0, pos);
+
+ std::string context_with_base_dir = context->EncodeContextForOatFile(parent);
+ ASSERT_NE(context_with_base_dir, context_with_no_base_dir);
+ ASSERT_TRUE(context->VerifyClassLoaderContextMatch(context_with_base_dir));
}
TEST_F(ClassLoaderContextTest, VerifyClassLoaderContextMatchAfterEncodingMultidex) {
diff --git a/runtime/class_table.h b/runtime/class_table.h
index a259725..0ffe93e 100644
--- a/runtime/class_table.h
+++ b/runtime/class_table.h
@@ -287,6 +287,7 @@
std::vector<const OatFile*> oat_files_ GUARDED_BY(lock_);
friend class ImageWriter; // for InsertWithoutLocks.
+ friend class OatWriter; // for boot class TableSlot address lookup.
};
} // namespace art
diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc
index a46f531..bf2e706 100644
--- a/runtime/common_throws.cc
+++ b/runtime/common_throws.cc
@@ -35,6 +35,7 @@
#include "obj_ptr-inl.h"
#include "thread.h"
#include "verifier/method_verifier.h"
+#include "well_known_classes.h"
namespace art {
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index af56810..ce75169 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -268,6 +268,11 @@
LOG(ERROR) << "Unexpected exception handled event in debugger";
}
+ // TODO Might be worth it to implement this.
+ void WatchedFramePop(Thread* thread ATTRIBUTE_UNUSED,
+ const ShadowFrame& frame ATTRIBUTE_UNUSED) OVERRIDE {
+ LOG(ERROR) << "Unexpected WatchedFramePop event in debugger";
+ }
private:
static bool IsReturn(ArtMethod* method, uint32_t dex_pc)
@@ -2212,6 +2217,8 @@
case kTerminated:
return JDWP::TS_ZOMBIE;
case kTimedWaiting:
+ case kWaitingForTaskProcessor:
+ case kWaitingForLockInflation:
case kWaitingForCheckPointsToRun:
case kWaitingForDebuggerSend:
case kWaitingForDebuggerSuspension:
diff --git a/runtime/dex_file_annotations.cc b/runtime/dex_file_annotations.cc
index 4225ab9..fe33bde 100644
--- a/runtime/dex_file_annotations.cc
+++ b/runtime/dex_file_annotations.cc
@@ -30,6 +30,7 @@
#include "mirror/method.h"
#include "reflection.h"
#include "thread.h"
+#include "well_known_classes.h"
namespace art {
diff --git a/runtime/entrypoints/quick/quick_lock_entrypoints.cc b/runtime/entrypoints/quick/quick_lock_entrypoints.cc
index b4f945a..4bcce21 100644
--- a/runtime/entrypoints/quick/quick_lock_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_lock_entrypoints.cc
@@ -29,15 +29,22 @@
ThrowNullPointerException("Null reference used for synchronization (monitor-enter)");
return -1; // Failure.
} else {
- if (kIsDebugBuild) {
- obj = obj->MonitorEnter(self); // May block
- CHECK(self->HoldsLock(obj));
- CHECK(!self->IsExceptionPending());
+ obj = obj->MonitorEnter(self); // May block
+ DCHECK(self->HoldsLock(obj));
+ // Exceptions can be thrown by monitor event listeners. This is expected to be rare however.
+ if (UNLIKELY(self->IsExceptionPending())) {
+ // TODO Remove this DCHECK if we expand the use of monitor callbacks.
+ DCHECK(Runtime::Current()->HasLoadedPlugins())
+ << "Exceptions are only expected to be thrown by plugin code which doesn't seem to be "
+ << "loaded.";
+ // We need to get rid of the lock
+ bool unlocked = obj->MonitorExit(self);
+ DCHECK(unlocked);
+ return -1; // Failure.
} else {
- obj->MonitorEnter(self); // May block
+ DCHECK(self->HoldsLock(obj));
+ return 0; // Success.
}
- return 0; // Success.
- // Only possible exception is NPE and is handled before entry
}
}
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index 7b83f20..ad65304 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -1264,48 +1264,6 @@
CHECK(called != nullptr) << orig_called->PrettyMethod() << " "
<< mirror::Object::PrettyTypeOf(receiver) << " "
<< invoke_type << " " << orig_called->GetVtableIndex();
-
- // We came here because of sharpening. Ensure the dex cache is up-to-date on the method index
- // of the sharpened method avoiding dirtying the dex cache if possible.
- // Note, called_method.dex_method_index references the dex method before the
- // FindVirtualMethodFor... This is ok for FindDexMethodIndexInOtherDexFile that only cares
- // about the name and signature.
- uint32_t update_dex_cache_method_index = called->GetDexMethodIndex();
- if (called->GetDexFile() != caller->GetDexFile()) {
- // Calling from one dex file to another, need to compute the method index appropriate to
- // the caller's dex file. Since we get here only if the original called was a runtime
- // method, we've got the correct dex_file and a dex_method_idx from above.
- DCHECK(!called_method_known_on_entry);
- DCHECK_EQ(caller->GetDexFile(), called_method.dex_file);
- const DexFile* caller_dex_file = called_method.dex_file;
- uint32_t caller_method_name_and_sig_index = called_method.dex_method_index;
- update_dex_cache_method_index =
- called->FindDexMethodIndexInOtherDexFile(*caller_dex_file,
- caller_method_name_and_sig_index);
- }
- if (update_dex_cache_method_index != DexFile::kDexNoIndex) {
- // Note: We do not need the read barrier for the dex cache as the SetResolvedMethod()
- // operates on native (non-moveable) data and constants (num_resolved_methods_).
- ObjPtr<mirror::DexCache> caller_dex_cache = caller->GetDexCache<kWithoutReadBarrier>();
- if (caller_dex_cache->GetResolvedMethod(
- update_dex_cache_method_index, kRuntimePointerSize) != called) {
- caller_dex_cache->SetResolvedMethod(update_dex_cache_method_index,
- called,
- kRuntimePointerSize);
- }
- }
- } else if (invoke_type == kStatic) {
- const auto called_dex_method_idx = called->GetDexMethodIndex();
- // For static invokes, we may dispatch to the static method in the superclass but resolve
- // using the subclass. To prevent getting slow paths on each invoke, we force set the
- // resolved method for the super class dex method index if we are in the same dex file.
- // b/19175856
- if (called->GetDexFile() == called_method.dex_file &&
- called_method.dex_method_index != called_dex_method_idx) {
- called->GetDexCache()->SetResolvedMethod(called_dex_method_idx,
- called,
- kRuntimePointerSize);
- }
}
// Ensure that the called method's class is initialized.
diff --git a/runtime/gc/collector/mark_sweep.cc b/runtime/gc/collector/mark_sweep.cc
index 34de83a..2dc5acc 100644
--- a/runtime/gc/collector/mark_sweep.cc
+++ b/runtime/gc/collector/mark_sweep.cc
@@ -1205,7 +1205,7 @@
}
}
}
- // Unlikely to sweep a significant amount of non_movable objects, so we do these after the after
+ // Unlikely to sweep a significant amount of non_movable objects, so we do these after
// the other alloc spaces as an optimization.
if (non_moving_space != nullptr) {
sweep_spaces.push_back(non_moving_space);
diff --git a/runtime/gc/collector/sticky_mark_sweep.cc b/runtime/gc/collector/sticky_mark_sweep.cc
index 98fdfac..b66095f 100644
--- a/runtime/gc/collector/sticky_mark_sweep.cc
+++ b/runtime/gc/collector/sticky_mark_sweep.cc
@@ -54,7 +54,7 @@
void StickyMarkSweep::MarkReachableObjects() {
// All reachable objects must be referenced by a root or a dirty card, so we can clear the mark
- // stack here since all objects in the mark stack will Get scanned by the card scanning anyways.
+ // stack here since all objects in the mark stack will get scanned by the card scanning anyways.
// TODO: Not put these objects in the mark stack in the first place.
mark_stack_->Reset();
RecursiveMarkDirtyObjects(false, accounting::CardTable::kCardDirty - 1);
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index f1685b2..6377c89 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -239,7 +239,7 @@
verify_pre_sweeping_rosalloc_(verify_pre_sweeping_rosalloc),
verify_post_gc_rosalloc_(verify_post_gc_rosalloc),
gc_stress_mode_(gc_stress_mode),
- /* For GC a lot mode, we limit the allocations stacks to be kGcAlotInterval allocations. This
+ /* For GC a lot mode, we limit the allocation stacks to be kGcAlotInterval allocations. This
* causes a lot of GC since we do a GC for alloc whenever the stack is full. When heap
* verification is enabled, we limit the size of allocation stacks to speed up their
* searching.
@@ -2932,7 +2932,7 @@
// TODO: Add handle VerifyObject.
StackHandleScope<1> hs(self);
HandleWrapperObjPtr<mirror::Object> wrapper(hs.NewHandleWrapper(obj));
- // Push our object into the reserve region of the allocaiton stack. This is only required due
+ // Push our object into the reserve region of the allocation stack. This is only required due
// to heap verification requiring that roots are live (either in the live bitmap or in the
// allocation stack).
CHECK(allocation_stack_->AtomicPushBackIgnoreGrowthLimit(obj->Ptr()));
diff --git a/runtime/gc/task_processor.cc b/runtime/gc/task_processor.cc
index 0704a68..e928644 100644
--- a/runtime/gc/task_processor.cc
+++ b/runtime/gc/task_processor.cc
@@ -34,14 +34,14 @@
}
void TaskProcessor::AddTask(Thread* self, HeapTask* task) {
- ScopedThreadStateChange tsc(self, kBlocked);
+ ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor);
MutexLock mu(self, *lock_);
tasks_.insert(task);
cond_->Signal(self);
}
HeapTask* TaskProcessor::GetTask(Thread* self) {
- ScopedThreadStateChange tsc(self, kBlocked);
+ ScopedThreadStateChange tsc(self, kWaitingForTaskProcessor);
MutexLock mu(self, *lock_);
while (true) {
if (tasks_.empty()) {
diff --git a/runtime/image.cc b/runtime/image.cc
index 8debc71..0236f47 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -26,7 +26,7 @@
namespace art {
const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '4', '7', '\0' }; // Smaller ArtMethod.
+const uint8_t ImageHeader::kImageVersion[] = { '0', '4', '8', '\0' }; // Map boot image tables.
ImageHeader::ImageHeader(uint32_t image_begin,
uint32_t image_size,
@@ -144,6 +144,19 @@
return os << "size=" << section.Size() << " range=" << section.Offset() << "-" << section.End();
}
+void ImageHeader::VisitObjects(ObjectVisitor* visitor,
+ uint8_t* base,
+ PointerSize pointer_size) const {
+ DCHECK_EQ(pointer_size, GetPointerSize());
+ const ImageSection& objects = GetObjectsSection();
+ static const size_t kStartPos = RoundUp(sizeof(ImageHeader), kObjectAlignment);
+ for (size_t pos = kStartPos; pos < objects.Size(); ) {
+ mirror::Object* object = reinterpret_cast<mirror::Object*>(base + objects.Offset() + pos);
+ visitor->Visit(object);
+ pos += RoundUp(object->SizeOf(), kObjectAlignment);
+ }
+}
+
void ImageHeader::VisitPackedArtFields(ArtFieldVisitor* visitor, uint8_t* base) const {
const ImageSection& fields = GetFieldsSection();
for (size_t pos = 0; pos < fields.Size(); ) {
diff --git a/runtime/image.h b/runtime/image.h
index 42abffc..da04333 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -19,6 +19,7 @@
#include <string.h>
+#include "base/bit_utils.h"
#include "base/enums.h"
#include "globals.h"
#include "mirror/object.h"
@@ -28,6 +29,13 @@
class ArtField;
class ArtMethod;
+class ObjectVisitor {
+ public:
+ virtual ~ObjectVisitor() {}
+
+ virtual void Visit(mirror::Object* object) = 0;
+};
+
class ArtMethodVisitor {
public:
virtual ~ArtMethodVisitor() {}
@@ -311,6 +319,29 @@
return boot_image_size_ != 0u;
}
+ uint32_t GetBootImageConstantTablesOffset() const {
+ // Interned strings table and class table for boot image are mmapped read only.
+ DCHECK(!IsAppImage());
+ const ImageSection& interned_strings = GetInternedStringsSection();
+ DCHECK_ALIGNED(interned_strings.Offset(), kPageSize);
+ return interned_strings.Offset();
+ }
+
+ uint32_t GetBootImageConstantTablesSize() const {
+ uint32_t start_offset = GetBootImageConstantTablesOffset();
+ const ImageSection& class_table = GetClassTableSection();
+ DCHECK_LE(start_offset, class_table.Offset());
+ size_t tables_size = class_table.Offset() + class_table.Size() - start_offset;
+ return RoundUp(tables_size, kPageSize);
+ }
+
+ // Visit mirror::Objects in the section starting at base.
+ // TODO: Delete base parameter if it is always equal to GetImageBegin.
+ void VisitObjects(ObjectVisitor* visitor,
+ uint8_t* base,
+ PointerSize pointer_size) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Visit ArtMethods in the section starting at base. Includes runtime methods.
// TODO: Delete base parameter if it is always equal to GetImageBegin.
void VisitPackedArtMethods(ArtMethodVisitor* visitor,
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 4d8c687..d7f6b83 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -779,6 +779,10 @@
UpdateMethodsCodeImpl(method, quick_code);
}
+void Instrumentation::UpdateMethodsCodeToInterpreterEntryPoint(ArtMethod* method) {
+ UpdateMethodsCodeImpl(method, GetQuickToInterpreterBridge());
+}
+
void Instrumentation::UpdateMethodsCodeForJavaDebuggable(ArtMethod* method,
const void* quick_code) {
// When the runtime is set to Java debuggable, we may update the entry points of
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 5763a41..5bac931 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -155,9 +155,7 @@
// shadow-frames by deoptimizing stacks.
virtual void WatchedFramePop(Thread* thread ATTRIBUTE_UNUSED,
const ShadowFrame& frame ATTRIBUTE_UNUSED)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return;
- }
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
};
// Instrumentation is a catch-all for when extra information is required from the runtime. The
@@ -282,6 +280,10 @@
void UpdateMethodsCode(ArtMethod* method, const void* quick_code)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_);
+ // Update the code of a method to the interpreter respecting any installed stubs from debugger.
+ void UpdateMethodsCodeToInterpreterEntryPoint(ArtMethod* method)
+ REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_);
+
// Update the code of a method respecting any installed stubs from debugger.
void UpdateMethodsCodeForJavaDebuggable(ArtMethod* method, const void* quick_code)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_);
diff --git a/runtime/intern_table.h b/runtime/intern_table.h
index 8714840..3e3abbd 100644
--- a/runtime/intern_table.h
+++ b/runtime/intern_table.h
@@ -223,6 +223,7 @@
// modifying the zygote intern table. The back of table is modified when strings are interned.
std::vector<UnorderedSet> tables_;
+ friend class OatWriter; // for boot image string table slot address lookup.
ART_FRIEND_TEST(InternTableTest, CrossHash);
};
@@ -282,6 +283,7 @@
// Weak root state, used for concurrent system weak processing and more.
gc::WeakRootState weak_root_state_ GUARDED_BY(Locks::intern_table_lock_);
+ friend class OatWriter; // for boot image string table slot address lookup.
friend class Transaction;
ART_FRIEND_TEST(InternTableTest, CrossHash);
DISALLOW_COPY_AND_ASSIGN(InternTable);
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 3ccab85..50bd7e7 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -65,9 +65,16 @@
static inline void DoMonitorEnter(Thread* self, ShadowFrame* frame, ObjPtr<mirror::Object> ref)
NO_THREAD_SAFETY_ANALYSIS
REQUIRES(!Roles::uninterruptible_) {
+ DCHECK(!ref.IsNull());
StackHandleScope<1> hs(self);
Handle<mirror::Object> h_ref(hs.NewHandle(ref));
h_ref->MonitorEnter(self);
+ DCHECK(self->HoldsLock(h_ref.Get()));
+ if (UNLIKELY(self->IsExceptionPending())) {
+ bool unlocked = h_ref->MonitorExit(self);
+ DCHECK(unlocked);
+ return;
+ }
if (kMonitorCounting && frame->GetMethod()->MustCountLocks()) {
frame->GetLockCountData().AddMonitor(self, h_ref.Get());
}
diff --git a/runtime/interpreter/mterp/arm/entry.S b/runtime/interpreter/mterp/arm/entry.S
index e53c054..ce14b54 100644
--- a/runtime/interpreter/mterp/arm/entry.S
+++ b/runtime/interpreter/mterp/arm/entry.S
@@ -67,6 +67,7 @@
/* Set up for backwards branches & osr profiling */
ldr r0, [rFP, #OFF_FP_METHOD]
add r1, rFP, #OFF_FP_SHADOWFRAME
+ mov r2, rSELF
bl MterpSetUpHotnessCountdown
mov rPROFILE, r0 @ Starting hotness countdown to rPROFILE
diff --git a/runtime/interpreter/mterp/arm64/entry.S b/runtime/interpreter/mterp/arm64/entry.S
index 441c1a1..73c5a88 100644
--- a/runtime/interpreter/mterp/arm64/entry.S
+++ b/runtime/interpreter/mterp/arm64/entry.S
@@ -60,6 +60,7 @@
/* Set up for backwards branches & osr profiling */
ldr x0, [xFP, #OFF_FP_METHOD]
add x1, xFP, #OFF_FP_SHADOWFRAME
+ mov x2, xSELF
bl MterpSetUpHotnessCountdown
mov wPROFILE, w0 // Starting hotness countdown to xPROFILE
diff --git a/runtime/interpreter/mterp/mips/entry.S b/runtime/interpreter/mterp/mips/entry.S
index c806a67..f617a4d 100644
--- a/runtime/interpreter/mterp/mips/entry.S
+++ b/runtime/interpreter/mterp/mips/entry.S
@@ -63,7 +63,8 @@
/* Set up for backwards branches & osr profiling */
lw a0, OFF_FP_METHOD(rFP)
addu a1, rFP, OFF_FP_SHADOWFRAME
- JAL(MterpSetUpHotnessCountdown) # (method, shadow_frame)
+ move a2, rSELF
+ JAL(MterpSetUpHotnessCountdown) # (method, shadow_frame, self)
move rPROFILE, v0 # Starting hotness countdown to rPROFILE
/* start executing the instruction at rPC */
diff --git a/runtime/interpreter/mterp/mips64/entry.S b/runtime/interpreter/mterp/mips64/entry.S
index cc48d45..5536966 100644
--- a/runtime/interpreter/mterp/mips64/entry.S
+++ b/runtime/interpreter/mterp/mips64/entry.S
@@ -82,6 +82,7 @@
/* Set up for backwards branches & osr profiling */
ld a0, OFF_FP_METHOD(rFP)
daddu a1, rFP, OFF_FP_SHADOWFRAME
+ move a2, rSELF
jal MterpSetUpHotnessCountdown
move rPROFILE, v0 # Starting hotness countdown to rPROFILE
diff --git a/runtime/interpreter/mterp/mterp.cc b/runtime/interpreter/mterp/mterp.cc
index b8a7a2a..6c24753 100644
--- a/runtime/interpreter/mterp/mterp.cc
+++ b/runtime/interpreter/mterp/mterp.cc
@@ -888,7 +888,9 @@
* to the full instrumentation via MterpAddHotnessBatch. Called once on entry to the method,
* and regenerated following batch updates.
*/
-extern "C" ssize_t MterpSetUpHotnessCountdown(ArtMethod* method, ShadowFrame* shadow_frame)
+extern "C" ssize_t MterpSetUpHotnessCountdown(ArtMethod* method,
+ ShadowFrame* shadow_frame,
+ Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
uint16_t hotness_count = method->GetCounter();
int32_t countdown_value = jit::kJitHotnessDisabled;
@@ -906,7 +908,7 @@
} else {
countdown_value = jit::kJitCheckForOSR;
}
- if (jit::Jit::ShouldUsePriorityThreadWeight()) {
+ if (jit::Jit::ShouldUsePriorityThreadWeight(self)) {
int32_t priority_thread_weight = jit->PriorityThreadWeight();
countdown_value = std::min(countdown_value, countdown_value / priority_thread_weight);
}
@@ -935,7 +937,7 @@
int16_t count = shadow_frame->GetCachedHotnessCountdown() - shadow_frame->GetHotnessCountdown();
jit->AddSamples(self, method, count, /*with_backedges*/ true);
}
- return MterpSetUpHotnessCountdown(method, shadow_frame);
+ return MterpSetUpHotnessCountdown(method, shadow_frame, self);
}
extern "C" size_t MterpMaybeDoOnStackReplacement(Thread* self,
diff --git a/runtime/interpreter/mterp/out/mterp_arm.S b/runtime/interpreter/mterp/out/mterp_arm.S
index e2b693f..d6a27b8 100644
--- a/runtime/interpreter/mterp/out/mterp_arm.S
+++ b/runtime/interpreter/mterp/out/mterp_arm.S
@@ -386,6 +386,7 @@
/* Set up for backwards branches & osr profiling */
ldr r0, [rFP, #OFF_FP_METHOD]
add r1, rFP, #OFF_FP_SHADOWFRAME
+ mov r2, rSELF
bl MterpSetUpHotnessCountdown
mov rPROFILE, r0 @ Starting hotness countdown to rPROFILE
diff --git a/runtime/interpreter/mterp/out/mterp_arm64.S b/runtime/interpreter/mterp/out/mterp_arm64.S
index ef5a4da..3d05996 100644
--- a/runtime/interpreter/mterp/out/mterp_arm64.S
+++ b/runtime/interpreter/mterp/out/mterp_arm64.S
@@ -401,6 +401,7 @@
/* Set up for backwards branches & osr profiling */
ldr x0, [xFP, #OFF_FP_METHOD]
add x1, xFP, #OFF_FP_SHADOWFRAME
+ mov x2, xSELF
bl MterpSetUpHotnessCountdown
mov wPROFILE, w0 // Starting hotness countdown to xPROFILE
diff --git a/runtime/interpreter/mterp/out/mterp_mips.S b/runtime/interpreter/mterp/out/mterp_mips.S
index 6362897..144c8e5 100644
--- a/runtime/interpreter/mterp/out/mterp_mips.S
+++ b/runtime/interpreter/mterp/out/mterp_mips.S
@@ -797,7 +797,8 @@
/* Set up for backwards branches & osr profiling */
lw a0, OFF_FP_METHOD(rFP)
addu a1, rFP, OFF_FP_SHADOWFRAME
- JAL(MterpSetUpHotnessCountdown) # (method, shadow_frame)
+ move a2, rSELF
+ JAL(MterpSetUpHotnessCountdown) # (method, shadow_frame, self)
move rPROFILE, v0 # Starting hotness countdown to rPROFILE
/* start executing the instruction at rPC */
diff --git a/runtime/interpreter/mterp/out/mterp_mips64.S b/runtime/interpreter/mterp/out/mterp_mips64.S
index bc0d90c..28f1887 100644
--- a/runtime/interpreter/mterp/out/mterp_mips64.S
+++ b/runtime/interpreter/mterp/out/mterp_mips64.S
@@ -383,6 +383,7 @@
/* Set up for backwards branches & osr profiling */
ld a0, OFF_FP_METHOD(rFP)
daddu a1, rFP, OFF_FP_SHADOWFRAME
+ move a2, rSELF
jal MterpSetUpHotnessCountdown
move rPROFILE, v0 # Starting hotness countdown to rPROFILE
@@ -3764,7 +3765,6 @@
GOTO_OPCODE v0 # jump to next instruction
-
/* ------------------------------ */
.balign 128
.L_op_float_to_double: /* 0x89 */
diff --git a/runtime/interpreter/mterp/out/mterp_x86.S b/runtime/interpreter/mterp/out/mterp_x86.S
index 21d9671..169501d 100644
--- a/runtime/interpreter/mterp/out/mterp_x86.S
+++ b/runtime/interpreter/mterp/out/mterp_x86.S
@@ -387,6 +387,8 @@
movl %eax, OUT_ARG0(%esp)
leal OFF_FP_SHADOWFRAME(rFP), %ecx
movl %ecx, OUT_ARG1(%esp)
+ movl rSELF, %eax
+ movl %eax, OUT_ARG2(%esp)
call SYMBOL(MterpSetUpHotnessCountdown)
/* Starting ibase */
diff --git a/runtime/interpreter/mterp/out/mterp_x86_64.S b/runtime/interpreter/mterp/out/mterp_x86_64.S
index b5a5ae5..b643072 100644
--- a/runtime/interpreter/mterp/out/mterp_x86_64.S
+++ b/runtime/interpreter/mterp/out/mterp_x86_64.S
@@ -198,9 +198,12 @@
* restore it in such cases also.
*
*/
+.macro REFRESH_IBASE_REG self_reg
+ movq THREAD_CURRENT_IBASE_OFFSET(\self_reg), rIBASE
+.endm
.macro REFRESH_IBASE
movq rSELF, rIBASE
- movq THREAD_CURRENT_IBASE_OFFSET(rIBASE), rIBASE
+ REFRESH_IBASE_REG rIBASE
.endm
/*
@@ -364,9 +367,10 @@
/* Starting ibase */
movq IN_ARG0, rSELF
- REFRESH_IBASE
+ REFRESH_IBASE_REG IN_ARG0
/* Set up for backwards branches & osr profiling */
+ movq IN_ARG0, OUT_ARG2 /* Set up OUT_ARG2 before clobbering IN_ARG0 */
movq OFF_FP_METHOD(rFP), OUT_ARG0
leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1
call SYMBOL(MterpSetUpHotnessCountdown)
@@ -11908,7 +11912,7 @@
.L_resume_backward_branch:
movq rSELF, %rax
testl $(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(%rax)
- REFRESH_IBASE
+ REFRESH_IBASE_REG %rax
leaq (rPC, rINSTq, 2), rPC
FETCH_INST
jnz .L_suspend_request_pending
diff --git a/runtime/interpreter/mterp/x86/entry.S b/runtime/interpreter/mterp/x86/entry.S
index 384dd9a..34adf53 100644
--- a/runtime/interpreter/mterp/x86/entry.S
+++ b/runtime/interpreter/mterp/x86/entry.S
@@ -69,6 +69,8 @@
movl %eax, OUT_ARG0(%esp)
leal OFF_FP_SHADOWFRAME(rFP), %ecx
movl %ecx, OUT_ARG1(%esp)
+ movl rSELF, %eax
+ movl %eax, OUT_ARG2(%esp)
call SYMBOL(MterpSetUpHotnessCountdown)
/* Starting ibase */
diff --git a/runtime/interpreter/mterp/x86_64/entry.S b/runtime/interpreter/mterp/x86_64/entry.S
index d992956..0f969eb 100644
--- a/runtime/interpreter/mterp/x86_64/entry.S
+++ b/runtime/interpreter/mterp/x86_64/entry.S
@@ -63,9 +63,10 @@
/* Starting ibase */
movq IN_ARG0, rSELF
- REFRESH_IBASE
+ REFRESH_IBASE_REG IN_ARG0
/* Set up for backwards branches & osr profiling */
+ movq IN_ARG0, OUT_ARG2 /* Set up OUT_ARG2 before clobbering IN_ARG0 */
movq OFF_FP_METHOD(rFP), OUT_ARG0
leaq OFF_FP_SHADOWFRAME(rFP), OUT_ARG1
call SYMBOL(MterpSetUpHotnessCountdown)
diff --git a/runtime/interpreter/mterp/x86_64/footer.S b/runtime/interpreter/mterp/x86_64/footer.S
index ed5e5ea..ac6cd19 100644
--- a/runtime/interpreter/mterp/x86_64/footer.S
+++ b/runtime/interpreter/mterp/x86_64/footer.S
@@ -152,7 +152,7 @@
.L_resume_backward_branch:
movq rSELF, %rax
testl $$(THREAD_SUSPEND_OR_CHECKPOINT_REQUEST), THREAD_FLAGS_OFFSET(%rax)
- REFRESH_IBASE
+ REFRESH_IBASE_REG %rax
leaq (rPC, rINSTq, 2), rPC
FETCH_INST
jnz .L_suspend_request_pending
diff --git a/runtime/interpreter/mterp/x86_64/header.S b/runtime/interpreter/mterp/x86_64/header.S
index 7699fc4..f229e84 100644
--- a/runtime/interpreter/mterp/x86_64/header.S
+++ b/runtime/interpreter/mterp/x86_64/header.S
@@ -191,9 +191,12 @@
* restore it in such cases also.
*
*/
+.macro REFRESH_IBASE_REG self_reg
+ movq THREAD_CURRENT_IBASE_OFFSET(\self_reg), rIBASE
+.endm
.macro REFRESH_IBASE
movq rSELF, rIBASE
- movq THREAD_CURRENT_IBASE_OFFSET(rIBASE), rIBASE
+ REFRESH_IBASE_REG rIBASE
.endm
/*
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index 7abf52e..8c27bfe 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -144,9 +144,8 @@
return jit_options;
}
-bool Jit::ShouldUsePriorityThreadWeight() {
- return Runtime::Current()->InJankPerceptibleProcessState()
- && Thread::Current()->IsJitSensitiveThread();
+bool Jit::ShouldUsePriorityThreadWeight(Thread* self) {
+ return self->IsJitSensitiveThread() && Runtime::Current()->InJankPerceptibleProcessState();
}
void Jit::DumpInfo(std::ostream& os) {
@@ -653,7 +652,7 @@
DCHECK_LE(priority_thread_weight_, hot_method_threshold_);
int32_t starting_count = method->GetCounter();
- if (Jit::ShouldUsePriorityThreadWeight()) {
+ if (Jit::ShouldUsePriorityThreadWeight(self)) {
count *= priority_thread_weight_;
}
int32_t new_count = starting_count + count; // int32 here to avoid wrap-around;
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index 51e49ec..791c338 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -152,7 +152,7 @@
bool CanInvokeCompiledCode(ArtMethod* method);
// Return whether the runtime should use a priority thread weight when sampling.
- static bool ShouldUsePriorityThreadWeight();
+ static bool ShouldUsePriorityThreadWeight(Thread* self);
// If an OSR compiled version is available for `method`,
// and `dex_pc + dex_pc_offset` is an entry point of that compiled
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index f96792d..d74cec3 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -2398,10 +2398,12 @@
ScopedObjectAccess soa(env);
ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object);
o = o->MonitorEnter(soa.Self());
+ if (soa.Self()->HoldsLock(o)) {
+ soa.Env()->monitors.Add(o);
+ }
if (soa.Self()->IsExceptionPending()) {
return JNI_ERR;
}
- soa.Env()->monitors.Add(o);
return JNI_OK;
}
@@ -2409,11 +2411,14 @@
CHECK_NON_NULL_ARGUMENT_RETURN(java_object, JNI_ERR);
ScopedObjectAccess soa(env);
ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object);
+ bool remove_mon = soa.Self()->HoldsLock(o);
o->MonitorExit(soa.Self());
+ if (remove_mon) {
+ soa.Env()->monitors.Remove(o);
+ }
if (soa.Self()->IsExceptionPending()) {
return JNI_ERR;
}
- soa.Env()->monitors.Remove(o);
return JNI_OK;
}
diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc
index f6f20ba..2f63dff 100644
--- a/runtime/mirror/dex_cache.cc
+++ b/runtime/mirror/dex_cache.cc
@@ -46,13 +46,10 @@
DexCacheArraysLayout layout(image_pointer_size, dex_file);
uint8_t* raw_arrays = nullptr;
- const OatDexFile* const oat_dex = dex_file->GetOatDexFile();
- if (oat_dex != nullptr && oat_dex->GetDexCacheArrays() != nullptr) {
- raw_arrays = oat_dex->GetDexCacheArrays();
- } else if (dex_file->NumStringIds() != 0u ||
- dex_file->NumTypeIds() != 0u ||
- dex_file->NumMethodIds() != 0u ||
- dex_file->NumFieldIds() != 0u) {
+ if (dex_file->NumStringIds() != 0u ||
+ dex_file->NumTypeIds() != 0u ||
+ dex_file->NumMethodIds() != 0u ||
+ dex_file->NumFieldIds() != 0u) {
static_assert(ArenaAllocator::kAlignment == 8, "Expecting arena alignment of 8.");
DCHECK(layout.Alignment() == 8u || layout.Alignment() == 16u);
// Zero-initialized.
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 5c63dca..051e015 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -346,11 +346,31 @@
return TryLockLocked(self);
}
+// Asserts that a mutex isn't held when the class comes into and out of scope.
+class ScopedAssertNotHeld {
+ public:
+ ScopedAssertNotHeld(Thread* self, Mutex& mu) : self_(self), mu_(mu) {
+ mu_.AssertNotHeld(self_);
+ }
+
+ ~ScopedAssertNotHeld() {
+ mu_.AssertNotHeld(self_);
+ }
+
+ private:
+ Thread* const self_;
+ Mutex& mu_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedAssertNotHeld);
+};
+
+template <LockReason reason>
void Monitor::Lock(Thread* self) {
- MutexLock mu(self, monitor_lock_);
+ ScopedAssertNotHeld sanh(self, monitor_lock_);
+ bool called_monitors_callback = false;
+ monitor_lock_.Lock(self);
while (true) {
if (TryLockLocked(self)) {
- return;
+ break;
}
// Contended.
const bool log_contention = (lock_profiling_threshold_ != 0);
@@ -389,6 +409,12 @@
}
monitor_lock_.Unlock(self); // Let go of locks in order.
+ // Call the contended locking cb once and only once. Also only call it if we are locking for
+ // the first time, not during a Wait wakeup.
+ if (reason == LockReason::kForLock && !called_monitors_callback) {
+ called_monitors_callback = true;
+ Runtime::Current()->GetRuntimeCallbacks()->MonitorContendedLocking(this);
+ }
self->SetMonitorEnterObject(GetObject());
{
ScopedThreadSuspension tsc(self, kBlocked); // Change to blocked and give up mutator_lock_.
@@ -492,10 +518,10 @@
<< PrettyDuration(MsToNs(wait_ms));
}
LogContentionEvent(self,
- wait_ms,
- sample_percent,
- owners_method,
- owners_dex_pc);
+ wait_ms,
+ sample_percent,
+ owners_method,
+ owners_dex_pc);
}
}
}
@@ -508,8 +534,18 @@
monitor_lock_.Lock(self); // Reacquire locks in order.
--num_waiters_;
}
+ monitor_lock_.Unlock(self);
+ // We need to pair this with a single contended locking call. NB we match the RI behavior and call
+ // this even if MonitorEnter failed.
+ if (called_monitors_callback) {
+ CHECK(reason == LockReason::kForLock);
+ Runtime::Current()->GetRuntimeCallbacks()->MonitorContendedLocked(this);
+ }
}
+template void Monitor::Lock<LockReason::kForLock>(Thread* self);
+template void Monitor::Lock<LockReason::kForWait>(Thread* self);
+
static void ThrowIllegalMonitorStateExceptionF(const char* fmt, ...)
__attribute__((format(printf, 1, 2)));
@@ -690,6 +726,7 @@
AtraceMonitorLock(self, GetObject(), true /* is_wait */);
bool was_interrupted = false;
+ bool timed_out = false;
{
// Update thread state. If the GC wakes up, it'll ignore us, knowing
// that we won't touch any references in this state, and we'll check
@@ -718,7 +755,7 @@
self->GetWaitConditionVariable()->Wait(self);
} else {
DCHECK(why == kTimedWaiting || why == kSleeping) << why;
- self->GetWaitConditionVariable()->TimedWait(self, ms, ns);
+ timed_out = self->GetWaitConditionVariable()->TimedWait(self, ms, ns);
}
was_interrupted = self->IsInterrupted();
}
@@ -751,8 +788,11 @@
AtraceMonitorUnlock(); // End Wait().
+ // We just slept, tell the runtime callbacks about this.
+ Runtime::Current()->GetRuntimeCallbacks()->MonitorWaitFinished(this, timed_out);
+
// Re-acquire the monitor and lock.
- Lock(self);
+ Lock<LockReason::kForWait>(self);
monitor_lock_.Lock(self);
self->GetWaitMutex()->AssertNotHeld(self);
@@ -897,7 +937,7 @@
bool timed_out;
Thread* owner;
{
- ScopedThreadSuspension sts(self, kBlocked);
+ ScopedThreadSuspension sts(self, kWaitingForLockInflation);
owner = thread_list->SuspendThreadByThreadId(owner_thread_id,
SuspendReason::kInternal,
&timed_out);
@@ -989,10 +1029,10 @@
contention_count++;
Runtime* runtime = Runtime::Current();
if (contention_count <= runtime->GetMaxSpinsBeforeThinLockInflation()) {
- // TODO: Consider switching the thread state to kBlocked when we are yielding.
- // Use sched_yield instead of NanoSleep since NanoSleep can wait much longer than the
- // parameter you pass in. This can cause thread suspension to take excessively long
- // and make long pauses. See b/16307460.
+ // TODO: Consider switching the thread state to kWaitingForLockInflation when we are
+ // yielding. Use sched_yield instead of NanoSleep since NanoSleep can wait much longer
+ // than the parameter you pass in. This can cause thread suspension to take excessively
+ // long and make long pauses. See b/16307460.
// TODO: We should literally spin first, without sched_yield. Sched_yield either does
// nothing (at significant expense), or guarantees that we wait at least microseconds.
// If the owner is running, I would expect the median lock hold time to be hundreds
@@ -1098,7 +1138,16 @@
bool interruptShouldThrow, ThreadState why) {
DCHECK(self != nullptr);
DCHECK(obj != nullptr);
- LockWord lock_word = obj->GetLockWord(true);
+ StackHandleScope<1> hs(self);
+ Handle<mirror::Object> h_obj(hs.NewHandle(obj));
+
+ Runtime::Current()->GetRuntimeCallbacks()->ObjectWaitStart(h_obj, ms);
+ if (UNLIKELY(self->IsExceptionPending())) {
+ // See b/65558434 for information on handling of exceptions here.
+ return;
+ }
+
+ LockWord lock_word = h_obj->GetLockWord(true);
while (lock_word.GetState() != LockWord::kFatLocked) {
switch (lock_word.GetState()) {
case LockWord::kHashCode:
@@ -1115,8 +1164,8 @@
} else {
// We own the lock, inflate to enqueue ourself on the Monitor. May fail spuriously so
// re-load.
- Inflate(self, self, obj, 0);
- lock_word = obj->GetLockWord(true);
+ Inflate(self, self, h_obj.Get(), 0);
+ lock_word = h_obj->GetLockWord(true);
}
break;
}
@@ -1203,8 +1252,9 @@
if (monitor != nullptr) {
pretty_object = monitor->GetObject();
}
- } else if (state == kBlocked) {
- wait_message = " - waiting to lock ";
+ } else if (state == kBlocked || state == kWaitingForLockInflation) {
+ wait_message = (state == kBlocked) ? " - waiting to lock "
+ : " - waiting for lock inflation of ";
pretty_object = thread->GetMonitorEnterObject();
if (pretty_object != nullptr) {
if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
@@ -1498,13 +1548,21 @@
break;
case LockWord::kThinLocked:
owner_ = Runtime::Current()->GetThreadList()->FindThreadByThreadId(lock_word.ThinLockOwner());
+ DCHECK(owner_ != nullptr) << "Thin-locked without owner!";
entry_count_ = 1 + lock_word.ThinLockCount();
// Thin locks have no waiters.
break;
case LockWord::kFatLocked: {
Monitor* mon = lock_word.FatLockMonitor();
owner_ = mon->owner_;
- entry_count_ = 1 + mon->lock_count_;
+ // Here it is okay for the owner to be null since we don't reset the LockWord back to
+ // kUnlocked until we get a GC. In cases where this hasn't happened yet we will have a fat
+ // lock without an owner.
+ if (owner_ != nullptr) {
+ entry_count_ = 1 + mon->lock_count_;
+ } else {
+ DCHECK_EQ(mon->lock_count_, 0) << "Monitor is fat-locked without any owner!";
+ }
for (Thread* waiter = mon->wait_set_; waiter != nullptr; waiter = waiter->GetWaitNext()) {
waiters_.push_back(waiter);
}
diff --git a/runtime/monitor.h b/runtime/monitor.h
index 09faeba..d7aef34 100644
--- a/runtime/monitor.h
+++ b/runtime/monitor.h
@@ -31,6 +31,7 @@
#include "gc_root.h"
#include "lock_word.h"
#include "read_barrier_option.h"
+#include "runtime_callbacks.h"
#include "thread_state.h"
namespace art {
@@ -47,6 +48,11 @@
class Object;
} // namespace mirror
+enum class LockReason {
+ kForWait,
+ kForLock,
+};
+
class Monitor {
public:
// The default number of spins that are done before thread suspension is used to forcibly inflate
@@ -205,9 +211,11 @@
REQUIRES(monitor_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+ template<LockReason reason = LockReason::kForLock>
void Lock(Thread* self)
REQUIRES(!monitor_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+
bool Unlock(Thread* thread)
REQUIRES(!monitor_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 07dfb65..d40e6d9 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -493,6 +493,8 @@
if (oat_file_assistant.IsInBootClassPath()) {
return OatFileAssistant::kNoDexOptNeeded;
}
+
+ // TODO(calin): Extend DexFile.getDexOptNeeded to accept the class loader context. b/62269291.
return oat_file_assistant.GetDexOptNeeded(filter, profile_changed, downgrade);
}
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index 2e4db7a..d767e98 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -154,7 +154,7 @@
}
}
-static void EnableDebugFeatures(uint32_t debug_flags) {
+static void EnableDebugFeatures(uint32_t runtime_flags) {
// Must match values in com.android.internal.os.Zygote.
enum {
DEBUG_ENABLE_JDWP = 1,
@@ -169,7 +169,7 @@
};
Runtime* const runtime = Runtime::Current();
- if ((debug_flags & DEBUG_ENABLE_CHECKJNI) != 0) {
+ if ((runtime_flags & DEBUG_ENABLE_CHECKJNI) != 0) {
JavaVMExt* vm = runtime->GetJavaVM();
if (!vm->IsCheckJniEnabled()) {
LOG(INFO) << "Late-enabling -Xcheck:jni";
@@ -179,66 +179,66 @@
} else {
LOG(INFO) << "Not late-enabling -Xcheck:jni (already on)";
}
- debug_flags &= ~DEBUG_ENABLE_CHECKJNI;
+ runtime_flags &= ~DEBUG_ENABLE_CHECKJNI;
}
- if ((debug_flags & DEBUG_ENABLE_JNI_LOGGING) != 0) {
+ if ((runtime_flags & DEBUG_ENABLE_JNI_LOGGING) != 0) {
gLogVerbosity.third_party_jni = true;
- debug_flags &= ~DEBUG_ENABLE_JNI_LOGGING;
+ runtime_flags &= ~DEBUG_ENABLE_JNI_LOGGING;
}
- Dbg::SetJdwpAllowed((debug_flags & DEBUG_ENABLE_JDWP) != 0);
- if ((debug_flags & DEBUG_ENABLE_JDWP) != 0) {
+ Dbg::SetJdwpAllowed((runtime_flags & DEBUG_ENABLE_JDWP) != 0);
+ if ((runtime_flags & DEBUG_ENABLE_JDWP) != 0) {
EnableDebugger();
}
- debug_flags &= ~DEBUG_ENABLE_JDWP;
+ runtime_flags &= ~DEBUG_ENABLE_JDWP;
- const bool safe_mode = (debug_flags & DEBUG_ENABLE_SAFEMODE) != 0;
+ const bool safe_mode = (runtime_flags & DEBUG_ENABLE_SAFEMODE) != 0;
if (safe_mode) {
// Only quicken oat files.
runtime->AddCompilerOption("--compiler-filter=quicken");
runtime->SetSafeMode(true);
- debug_flags &= ~DEBUG_ENABLE_SAFEMODE;
+ runtime_flags &= ~DEBUG_ENABLE_SAFEMODE;
}
- const bool generate_debug_info = (debug_flags & DEBUG_GENERATE_DEBUG_INFO) != 0;
+ const bool generate_debug_info = (runtime_flags & DEBUG_GENERATE_DEBUG_INFO) != 0;
if (generate_debug_info) {
runtime->AddCompilerOption("--generate-debug-info");
- debug_flags &= ~DEBUG_GENERATE_DEBUG_INFO;
+ runtime_flags &= ~DEBUG_GENERATE_DEBUG_INFO;
}
// This is for backwards compatibility with Dalvik.
- debug_flags &= ~DEBUG_ENABLE_ASSERT;
+ runtime_flags &= ~DEBUG_ENABLE_ASSERT;
- if ((debug_flags & DEBUG_ALWAYS_JIT) != 0) {
+ if ((runtime_flags & DEBUG_ALWAYS_JIT) != 0) {
jit::JitOptions* jit_options = runtime->GetJITOptions();
CHECK(jit_options != nullptr);
jit_options->SetJitAtFirstUse();
- debug_flags &= ~DEBUG_ALWAYS_JIT;
+ runtime_flags &= ~DEBUG_ALWAYS_JIT;
}
bool needs_non_debuggable_classes = false;
- if ((debug_flags & DEBUG_JAVA_DEBUGGABLE) != 0) {
+ if ((runtime_flags & DEBUG_JAVA_DEBUGGABLE) != 0) {
runtime->AddCompilerOption("--debuggable");
runtime->SetJavaDebuggable(true);
// Deoptimize the boot image as it may be non-debuggable.
runtime->DeoptimizeBootImage();
- debug_flags &= ~DEBUG_JAVA_DEBUGGABLE;
+ runtime_flags &= ~DEBUG_JAVA_DEBUGGABLE;
needs_non_debuggable_classes = true;
}
if (needs_non_debuggable_classes || kAlwaysCollectNonDebuggableClasses) {
CollectNonDebuggableClasses();
}
- if ((debug_flags & DEBUG_NATIVE_DEBUGGABLE) != 0) {
+ if ((runtime_flags & DEBUG_NATIVE_DEBUGGABLE) != 0) {
runtime->AddCompilerOption("--debuggable");
runtime->AddCompilerOption("--generate-debug-info");
runtime->SetNativeDebuggable(true);
- debug_flags &= ~DEBUG_NATIVE_DEBUGGABLE;
+ runtime_flags &= ~DEBUG_NATIVE_DEBUGGABLE;
}
- if (debug_flags != 0) {
- LOG(ERROR) << StringPrintf("Unknown bits set in debug_flags: %#x", debug_flags);
+ if (runtime_flags != 0) {
+ LOG(ERROR) << StringPrintf("Unknown bits set in runtime_flags: %#x", runtime_flags);
}
}
@@ -260,13 +260,13 @@
static void ZygoteHooks_nativePostForkChild(JNIEnv* env,
jclass,
jlong token,
- jint debug_flags,
+ jint runtime_flags,
jboolean is_system_server,
jstring instruction_set) {
Thread* thread = reinterpret_cast<Thread*>(token);
// Our system thread ID, etc, has changed so reset Thread state.
thread->InitAfterFork();
- EnableDebugFeatures(debug_flags);
+ EnableDebugFeatures(runtime_flags);
// Update tracing.
if (Trace::GetMethodTracingMode() != TracingMode::kTracingInactive) {
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index 4fbbb72..94007ff 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -86,6 +86,8 @@
case kWaiting: return kJavaWaiting;
case kStarting: return kJavaNew;
case kNative: return kJavaRunnable;
+ case kWaitingForTaskProcessor: return kJavaWaiting;
+ case kWaitingForLockInflation: return kJavaWaiting;
case kWaitingForGcToComplete: return kJavaWaiting;
case kWaitingPerformingGc: return kJavaWaiting;
case kWaitingForCheckPointsToRun: return kJavaWaiting;
diff --git a/runtime/oat.h b/runtime/oat.h
index 1d79ed6..a3e8eef 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
class PACKED(4) OatHeader {
public:
static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
- // Last oat version changed reason: Add dex section layout info to header.
- static constexpr uint8_t kOatVersion[] = { '1', '3', '2', '\0' };
+ // Last oat version changed reason: Map boot image InternTable and ClassTable into app .bss.
+ static constexpr uint8_t kOatVersion[] = { '1', '3', '4', '\0' };
static constexpr const char* kImageLocationKey = "image-location";
static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index d9cfa53..075875b 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -44,6 +44,7 @@
#include "elf_file.h"
#include "elf_utils.h"
#include "gc_root.h"
+#include "gc/space/image_space.h"
#include "mem_map.h"
#include "mirror/class.h"
#include "mirror/object-inl.h"
@@ -55,7 +56,6 @@
#include "type_lookup_table.h"
#include "utf-inl.h"
#include "utils.h"
-#include "utils/dex_cache_arrays_layout-inl.h"
#include "vdex_file.h"
namespace art {
@@ -279,33 +279,34 @@
return true;
}
-static bool FindDexFileMapItem(const uint8_t* dex_begin,
- const uint8_t* dex_end,
- DexFile::MapItemType map_item_type,
- const DexFile::MapItem** result_item) {
- *result_item = nullptr;
+static inline bool MapConstantTables(const gc::space::ImageSpace* space,
+ uint8_t* address) {
+ // If MREMAP_DUP is ever merged to Linux kernel, use it to avoid the unnecessary open()/close().
+ // Note: The current approach relies on the filename still referencing the same inode.
- const DexFile::Header* header =
- BoundsCheckedCast<const DexFile::Header*>(dex_begin, dex_begin, dex_end);
- if (nullptr == header) return false;
-
- if (!DexFile::IsMagicValid(header->magic_)) return true; // Not a dex file, not an error.
-
- const DexFile::MapList* map_list =
- BoundsCheckedCast<const DexFile::MapList*>(dex_begin + header->map_off_, dex_begin, dex_end);
- if (nullptr == map_list) return false;
-
- const DexFile::MapItem* map_item = map_list->list_;
- size_t count = map_list->size_;
- while (count--) {
- if (map_item->type_ == static_cast<uint16_t>(map_item_type)) {
- *result_item = map_item;
- break;
- }
- map_item = BoundsCheckedCast<const DexFile::MapItem*>(map_item + 1, dex_begin, dex_end);
- if (nullptr == map_item) return false;
+ File file(space->GetImageFilename(), O_RDONLY, /* checkUsage */ false);
+ if (!file.IsOpened()) {
+ LOG(ERROR) << "Failed to open boot image file " << space->GetImageFilename();
+ return false;
}
+ uint32_t offset = space->GetImageHeader().GetBootImageConstantTablesOffset();
+ uint32_t size = space->GetImageHeader().GetBootImageConstantTablesSize();
+ std::string error_msg;
+ std::unique_ptr<MemMap> mem_map(MemMap::MapFileAtAddress(address,
+ size,
+ PROT_READ,
+ MAP_PRIVATE,
+ file.Fd(),
+ offset,
+ /* low_4gb */ false,
+ /* reuse */ true,
+ file.GetPath().c_str(),
+ &error_msg));
+ if (mem_map == nullptr) {
+ LOG(ERROR) << "Failed to mmap boot image tables from file " << space->GetImageFilename();
+ return false;
+ }
return true;
}
@@ -361,7 +362,7 @@
(bss_roots_ != nullptr && (bss_roots_ < bss_begin_ || bss_roots_ > bss_end_)) ||
(bss_methods_ != nullptr && bss_roots_ != nullptr && bss_methods_ > bss_roots_)) {
*error_msg = StringPrintf("In oat file '%s' found bss symbol(s) outside .bss or unordered: "
- "begin = %p, methods_ = %p, roots = %p, end = %p",
+ "begin = %p, methods = %p, roots = %p, end = %p",
GetLocation().c_str(),
bss_begin_,
bss_methods_,
@@ -370,11 +371,12 @@
return false;
}
- uint8_t* after_arrays = (bss_methods_ != nullptr) ? bss_methods_ : bss_roots_; // May be null.
- uint8_t* dex_cache_arrays = (bss_begin_ == after_arrays) ? nullptr : bss_begin_;
- uint8_t* dex_cache_arrays_end =
- (bss_begin_ == after_arrays) ? nullptr : (after_arrays != nullptr) ? after_arrays : bss_end_;
- DCHECK_EQ(dex_cache_arrays != nullptr, dex_cache_arrays_end != nullptr);
+ uint8_t* after_tables =
+ (bss_methods_ != nullptr) ? bss_methods_ : bss_roots_; // May be null.
+ uint8_t* boot_image_tables = (bss_begin_ == after_tables) ? nullptr : bss_begin_;
+ uint8_t* boot_image_tables_end =
+ (bss_begin_ == after_tables) ? nullptr : (after_tables != nullptr) ? after_tables : bss_end_;
+ DCHECK_EQ(boot_image_tables != nullptr, boot_image_tables_end != nullptr);
uint32_t dex_file_count = GetOatHeader().GetDexFileCount();
oat_dex_files_storage_.reserve(dex_file_count);
for (size_t i = 0; i < dex_file_count; i++) {
@@ -609,37 +611,6 @@
reinterpret_cast<const DexFile::Header*>(dex_file_pointer)->method_ids_size_);
}
- uint8_t* current_dex_cache_arrays = nullptr;
- if (dex_cache_arrays != nullptr) {
- // All DexCache types except for CallSite have their instance counts in the
- // DexFile header. For CallSites, we need to read the info from the MapList.
- const DexFile::MapItem* call_sites_item = nullptr;
- if (!FindDexFileMapItem(DexBegin(),
- DexEnd(),
- DexFile::MapItemType::kDexTypeCallSiteIdItem,
- &call_sites_item)) {
- *error_msg = StringPrintf("In oat file '%s' could not read data from truncated DexFile map",
- GetLocation().c_str());
- return false;
- }
- size_t num_call_sites = call_sites_item == nullptr ? 0 : call_sites_item->size_;
- DexCacheArraysLayout layout(pointer_size, *header, num_call_sites);
- if (layout.Size() != 0u) {
- if (static_cast<size_t>(dex_cache_arrays_end - dex_cache_arrays) < layout.Size()) {
- *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with "
- "truncated dex cache arrays, %zu < %zu.",
- GetLocation().c_str(),
- i,
- dex_file_location.c_str(),
- static_cast<size_t>(dex_cache_arrays_end - dex_cache_arrays),
- layout.Size());
- return false;
- }
- current_dex_cache_arrays = dex_cache_arrays;
- dex_cache_arrays += layout.Size();
- }
- }
-
std::string canonical_location = DexFile::GetDexCanonicalLocation(dex_file_location.c_str());
// Create the OatDexFile and add it to the owning container.
@@ -651,7 +622,6 @@
lookup_table_data,
method_bss_mapping,
class_offsets_pointer,
- current_dex_cache_arrays,
dex_layout_sections);
oat_dex_files_storage_.push_back(oat_dex_file);
@@ -664,13 +634,30 @@
}
}
- if (dex_cache_arrays != dex_cache_arrays_end) {
- // We expect the bss section to be either empty (dex_cache_arrays and bss_end_
- // both null) or contain just the dex cache arrays and optionally some GC roots.
- *error_msg = StringPrintf("In oat file '%s' found unexpected bss size bigger by %zu bytes.",
- GetLocation().c_str(),
- static_cast<size_t>(bss_end_ - dex_cache_arrays));
- return false;
+ if (boot_image_tables != nullptr) {
+ // Map boot image tables into the .bss. The reserved size must match size of the tables.
+ size_t reserved_size = static_cast<size_t>(boot_image_tables_end - boot_image_tables);
+ size_t tables_size = 0u;
+ for (gc::space::ImageSpace* space : Runtime::Current()->GetHeap()->GetBootImageSpaces()) {
+ tables_size += space->GetImageHeader().GetBootImageConstantTablesSize();
+ DCHECK_ALIGNED(tables_size, kPageSize);
+ }
+ if (tables_size != reserved_size) {
+ *error_msg = StringPrintf("In oat file '%s' found unexpected boot image table sizes, "
+ " %zu bytes, should be %zu.",
+ GetLocation().c_str(),
+ reserved_size,
+ tables_size);
+ return false;
+ }
+ for (gc::space::ImageSpace* space : Runtime::Current()->GetHeap()->GetBootImageSpaces()) {
+ uint32_t current_tables_size = space->GetImageHeader().GetBootImageConstantTablesSize();
+ if (current_tables_size != 0u && !MapConstantTables(space, boot_image_tables)) {
+ return false;
+ }
+ boot_image_tables += current_tables_size;
+ }
+ DCHECK(boot_image_tables == boot_image_tables_end);
}
return true;
}
@@ -1379,7 +1366,6 @@
const uint8_t* lookup_table_data,
const MethodBssMapping* method_bss_mapping_data,
const uint32_t* oat_class_offsets_pointer,
- uint8_t* dex_cache_arrays,
const DexLayoutSections* dex_layout_sections)
: oat_file_(oat_file),
dex_file_location_(dex_file_location),
@@ -1389,7 +1375,6 @@
lookup_table_data_(lookup_table_data),
method_bss_mapping_(method_bss_mapping_data),
oat_class_offsets_pointer_(oat_class_offsets_pointer),
- dex_cache_arrays_(dex_cache_arrays),
dex_layout_sections_(dex_layout_sections) {
// Initialize TypeLookupTable.
if (lookup_table_data_ != nullptr) {
@@ -1619,6 +1604,10 @@
return GetOatHeader().GetCompilerFilter();
}
+std::string OatFile::GetClassLoaderContext() const {
+ return GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey);
+};
+
OatFile::OatClass OatFile::FindOatClass(const DexFile& dex_file,
uint16_t class_def_idx,
bool* found) {
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 9a7fe51..04cb3a0 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -115,6 +115,8 @@
CompilerFilter::Filter GetCompilerFilter() const;
+ std::string GetClassLoaderContext() const;
+
const std::string& GetLocation() const {
return location_;
}
@@ -422,10 +424,6 @@
// Returns the offset to the OatClass information. Most callers should use GetOatClass.
uint32_t GetOatClassOffset(uint16_t class_def_index) const;
- uint8_t* GetDexCacheArrays() const {
- return dex_cache_arrays_;
- }
-
const uint8_t* GetLookupTableData() const {
return lookup_table_data_;
}
@@ -470,7 +468,6 @@
const uint8_t* lookup_table_data,
const MethodBssMapping* method_bss_mapping,
const uint32_t* oat_class_offsets_pointer,
- uint8_t* dex_cache_arrays,
const DexLayoutSections* dex_layout_sections);
static void AssertAotCompiler();
@@ -483,7 +480,6 @@
const uint8_t* const lookup_table_data_ = nullptr;
const MethodBssMapping* const method_bss_mapping_ = nullptr;
const uint32_t* const oat_class_offsets_pointer_ = 0u;
- uint8_t* const dex_cache_arrays_ = nullptr;
mutable std::unique_ptr<TypeLookupTable> lookup_table_;
const DexLayoutSections* const dex_layout_sections_ = nullptr;
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 3794f51..83a8e09 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -190,9 +190,13 @@
int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target,
bool profile_changed,
- bool downgrade) {
+ bool downgrade,
+ ClassLoaderContext* class_loader_context) {
OatFileInfo& info = GetBestInfo();
- DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target, profile_changed, downgrade);
+ DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target,
+ profile_changed,
+ downgrade,
+ class_loader_context);
if (info.IsOatLocation() || dexopt_needed == kDex2OatFromScratch) {
return dexopt_needed;
}
@@ -227,7 +231,7 @@
OatFileAssistant::ResultOfAttemptToUpdate
OatFileAssistant::MakeUpToDate(bool profile_changed,
- const std::string& class_loader_context,
+ ClassLoaderContext* class_loader_context,
std::string* error_msg) {
CompilerFilter::Filter target;
if (!GetRuntimeCompilerFilterOption(&target, error_msg)) {
@@ -245,7 +249,8 @@
// - however, MakeUpToDate will not always succeed (e.g. for primary apks, or for dex files
// loaded in other processes). So it boils down to how far do we want to complicate
// the logic in order to enable the use of oat files. Maybe its time to try simplify it.
- switch (info.GetDexOptNeeded(target, profile_changed, /*downgrade*/ false)) {
+ switch (info.GetDexOptNeeded(
+ target, profile_changed, /*downgrade*/ false, class_loader_context)) {
case kNoDexOptNeeded:
return kUpdateSucceeded;
@@ -643,7 +648,7 @@
OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::GenerateOatFileNoChecks(
OatFileAssistant::OatFileInfo& info,
CompilerFilter::Filter filter,
- const std::string& class_loader_context,
+ const ClassLoaderContext* class_loader_context,
std::string* error_msg) {
CHECK(error_msg != nullptr);
@@ -720,7 +725,10 @@
args.push_back("--oat-fd=" + std::to_string(oat_file->Fd()));
args.push_back("--oat-location=" + oat_file_name);
args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter));
- args.push_back("--class-loader-context=" + class_loader_context);
+ const std::string dex2oat_context = class_loader_context == nullptr
+ ? OatFile::kSpecialSharedLibrary
+ : class_loader_context->EncodeContextForDex2oat(/*base_dir*/ "");
+ args.push_back("--class-loader-context=" + dex2oat_context);
if (!Dex2Oat(args, error_msg)) {
// Manually delete the oat and vdex files. This ensures there is no garbage
@@ -1016,31 +1024,40 @@
}
OatFileAssistant::DexOptNeeded OatFileAssistant::OatFileInfo::GetDexOptNeeded(
- CompilerFilter::Filter target, bool profile_changed, bool downgrade) {
+ CompilerFilter::Filter target,
+ bool profile_changed,
+ bool downgrade,
+ ClassLoaderContext* context) {
+
bool compilation_desired = CompilerFilter::IsAotCompilationEnabled(target);
bool filter_okay = CompilerFilterIsOkay(target, profile_changed, downgrade);
+ bool class_loader_context_okay = ClassLoaderContextIsOkay(context);
- if (filter_okay && Status() == kOatUpToDate) {
- // The oat file is in good shape as is.
- return kNoDexOptNeeded;
- }
+ // Only check the filter and relocation if the class loader context is ok.
+ // If it is not, we will return kDex2OatFromScratch as the compilation needs to be redone.
+ if (class_loader_context_okay) {
+ if (filter_okay && Status() == kOatUpToDate) {
+ // The oat file is in good shape as is.
+ return kNoDexOptNeeded;
+ }
- if (filter_okay && !compilation_desired && Status() == kOatRelocationOutOfDate) {
- // If no compilation is desired, then it doesn't matter if the oat
- // file needs relocation. It's in good shape as is.
- return kNoDexOptNeeded;
- }
+ if (filter_okay && !compilation_desired && Status() == kOatRelocationOutOfDate) {
+ // If no compilation is desired, then it doesn't matter if the oat
+ // file needs relocation. It's in good shape as is.
+ return kNoDexOptNeeded;
+ }
- if (filter_okay && Status() == kOatRelocationOutOfDate) {
- return kDex2OatForRelocation;
- }
+ if (filter_okay && Status() == kOatRelocationOutOfDate) {
+ return kDex2OatForRelocation;
+ }
- if (IsUseable()) {
- return kDex2OatForFilter;
- }
+ if (IsUseable()) {
+ return kDex2OatForFilter;
+ }
- if (Status() == kOatBootImageOutOfDate) {
- return kDex2OatForBootImage;
+ if (Status() == kOatBootImageOutOfDate) {
+ return kDex2OatForBootImage;
+ }
}
if (oat_file_assistant_->HasOriginalDexFiles()) {
@@ -1090,6 +1107,36 @@
CompilerFilter::IsAsGoodAs(current, target);
}
+bool OatFileAssistant::OatFileInfo::ClassLoaderContextIsOkay(ClassLoaderContext* context) {
+ if (context == nullptr) {
+ VLOG(oat) << "ClassLoaderContext check ignored: null context";
+ return true;
+ }
+
+ const OatFile* file = GetFile();
+ if (file == nullptr) {
+ return false;
+ }
+
+ size_t dir_index = file->GetLocation().rfind('/');
+ std::string classpath_dir = (dir_index != std::string::npos)
+ ? file->GetLocation().substr(0, dir_index)
+ : "";
+
+ if (!context->OpenDexFiles(oat_file_assistant_->isa_, classpath_dir)) {
+ VLOG(oat) << "ClassLoaderContext check failed: dex files from the context could not be opened";
+ return false;
+ }
+
+ bool result = context->VerifyClassLoaderContextMatch(file->GetClassLoaderContext());
+ if (!result) {
+ VLOG(oat) << "ClassLoaderContext check failed. Context was "
+ << file->GetClassLoaderContext()
+ << ". The expected context is " << context->EncodeContextForOatFile(classpath_dir);
+ }
+ return result;
+}
+
bool OatFileAssistant::OatFileInfo::IsExecutable() {
const OatFile* file = GetFile();
return (file != nullptr && file->IsExecutable());
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 5eec943..6dc3c19 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -26,6 +26,7 @@
#include "base/scoped_flock.h"
#include "base/unix_file/fd_file.h"
#include "compiler_filter.h"
+#include "class_loader_context.h"
#include "oat_file.h"
#include "os.h"
@@ -164,7 +165,8 @@
// the oat file in the odex location.
int GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
bool profile_changed = false,
- bool downgrade = false);
+ bool downgrade = false,
+ ClassLoaderContext* context = nullptr);
// Returns true if there is up-to-date code for this dex location,
// irrespective of the compiler filter of the up-to-date code.
@@ -194,7 +196,7 @@
// to a string describing why there was a failure or the update was not
// attempted. error_msg must not be null.
ResultOfAttemptToUpdate MakeUpToDate(bool profile_changed,
- const std::string& class_loader_context,
+ ClassLoaderContext* class_loader_context,
std::string* error_msg);
// Returns an oat file that can be used for loading dex files.
@@ -330,7 +332,8 @@
// compiler filter.
DexOptNeeded GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
bool profile_changed,
- bool downgrade);
+ bool downgrade,
+ ClassLoaderContext* context);
// Returns the loaded file.
// Loads the file if needed. Returns null if the file failed to load.
@@ -367,6 +370,8 @@
// compiler filter.
bool CompilerFilterIsOkay(CompilerFilter::Filter target, bool profile_changed, bool downgrade);
+ bool ClassLoaderContextIsOkay(ClassLoaderContext* context);
+
// Release the loaded oat file.
// Returns null if the oat file hasn't been loaded.
//
@@ -404,7 +409,7 @@
// attempted. error_msg must not be null.
ResultOfAttemptToUpdate GenerateOatFileNoChecks(OatFileInfo& info,
CompilerFilter::Filter target,
- const std::string& class_loader_context,
+ const ClassLoaderContext* class_loader_context,
std::string* error_msg);
// Return info for the best oat file.
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index e048177..3ecd1b5 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -40,6 +40,7 @@
namespace art {
static const std::string kSpecialSharedLibrary = "&";
+static ClassLoaderContext* kSpecialSharedLibraryContext = nullptr;
class OatFileAssistantTest : public DexoptTest {};
@@ -121,7 +122,7 @@
// Trying to make the oat file up to date should not fail or crash.
std::string error_msg;
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg));
// Trying to get the best oat file should fail, but not crash.
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
@@ -774,7 +775,7 @@
std::string error_msg;
Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) <<
error_msg;
EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
@@ -955,7 +956,7 @@
// We should get kUpdateSucceeded from MakeUpToDate since there's nothing
// that can be done in this situation.
ASSERT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg));
// Verify it didn't create an oat in the default location (dalvik-cache).
OatFileAssistant ofm(dex_location.c_str(), kRuntimeISA, false);
@@ -1034,7 +1035,7 @@
std::string error_msg;
Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg));
EXPECT_TRUE(error_msg.empty());
}
@@ -1181,7 +1182,7 @@
std::string error_msg;
Runtime::Current()->AddCompilerOption("--compiler-filter=quicken");
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) <<
error_msg;
EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken));
@@ -1190,7 +1191,7 @@
Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg))
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg))
<< error_msg;
EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken));
@@ -1199,7 +1200,7 @@
Runtime::Current()->AddCompilerOption("--compiler-filter=bogus");
EXPECT_EQ(OatFileAssistant::kUpdateNotAttempted,
- oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg));
}
TEST(OatFileAssistantUtilsTest, DexLocationToOdexFilename) {
@@ -1259,7 +1260,7 @@
OatFileAssistant::kDefaultCompilerFilterForDexLoading;
std::string error_msg;
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
- oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+ oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg)) <<
error_msg;
EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
oat_file_assistant.GetDexOptNeeded(default_filter));
@@ -1277,7 +1278,7 @@
const CompilerFilter::Filter default_filter =
OatFileAssistant::kDefaultCompilerFilterForDexLoading;
std::string error_msg;
- int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg);
+ int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibraryContext, &error_msg);
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg;
EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
oat_file_assistant.GetDexOptNeeded(default_filter));
@@ -1299,19 +1300,52 @@
OatFileAssistant::kDefaultCompilerFilterForDexLoading;
std::string error_msg;
std::string context_str = "PCL[" + context_location + "]";
- int status = oat_file_assistant.MakeUpToDate(false, context_str, &error_msg);
+ std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str);
+ ASSERT_TRUE(context != nullptr);
+ ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, ""));
+
+ int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg);
EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg;
EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- oat_file_assistant.GetDexOptNeeded(default_filter));
+ oat_file_assistant.GetDexOptNeeded(default_filter, false, false, context.get()));
+
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
EXPECT_NE(nullptr, oat_file.get());
- std::unique_ptr<ClassLoaderContext> context =
- ClassLoaderContext::Create(context_str);
- context->OpenDexFiles(kRuntimeISA, "");
EXPECT_EQ(context->EncodeContextForOatFile(""),
oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
}
+TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) {
+ std::string dex_location = GetScratchDir() + "/TestDex.jar";
+ std::string context_location = GetScratchDir() + "/ContextDex.jar";
+ Copy(GetDexSrc1(), dex_location);
+ Copy(GetDexSrc2(), context_location);
+
+ OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
+
+ const CompilerFilter::Filter default_filter =
+ OatFileAssistant::kDefaultCompilerFilterForDexLoading;
+ std::string error_msg;
+ std::string context_str = "PCL[" + context_location + "]";
+ std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str);
+ ASSERT_TRUE(context != nullptr);
+ ASSERT_TRUE(context->OpenDexFiles(kRuntimeISA, ""));
+
+ int status = oat_file_assistant.MakeUpToDate(false, context.get(), &error_msg);
+ EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg;
+ EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(default_filter, false, false, context.get()));
+
+ // Update the context by overriding the jar file.
+ Copy(GetMultiDexSrc2(), context_location);
+ std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str);
+ ASSERT_TRUE(updated_context != nullptr);
+ // DexOptNeeded should advise compilation from scratch.
+ EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+ oat_file_assistant.GetDexOptNeeded(
+ default_filter, false, false, updated_context.get()));
+}
+
// TODO: More Tests:
// * Test class linker falls back to unquickened dex for DexNoOat
// * Test class linker falls back to unquickened dex for MultiDexNoOat
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index 499f356..516c833 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -361,8 +361,7 @@
// If the pat file loading context matches the context used during compilation then we accept
// the oat file without addition checks
- if (context->VerifyClassLoaderContextMatch(
- oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey))) {
+ if (context->VerifyClassLoaderContextMatch(oat_file->GetClassLoaderContext())) {
return false;
}
@@ -426,12 +425,9 @@
// Update the oat file on disk if we can, based on the --compiler-filter
// option derived from the current runtime options.
// This may fail, but that's okay. Best effort is all that matters here.
-
- const std::string& dex2oat_context = context == nullptr
- ? OatFile::kSpecialSharedLibrary
- : context->EncodeContextForDex2oat(/*base_dir*/ "");
- switch (oat_file_assistant.MakeUpToDate(
- /*profile_changed*/false, dex2oat_context, /*out*/ &error_msg)) {
+ switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/false,
+ context.get(),
+ /*out*/ &error_msg)) {
case OatFileAssistant::kUpdateFailed:
LOG(WARNING) << error_msg;
break;
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index a8ccf89..5888762 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -134,6 +134,7 @@
#include "native_bridge_art_interface.h"
#include "native_stack_dump.h"
#include "nativehelper/JniConstants.h"
+#include "nativehelper/JniConstants-priv.h"
#include "nativehelper/ScopedLocalRef.h"
#include "oat_file.h"
#include "oat_file_manager.h"
@@ -400,6 +401,15 @@
// TODO: acquire a static mutex on Runtime to avoid racing.
CHECK(instance_ == nullptr || instance_ == this);
instance_ = nullptr;
+
+ // Well-known classes must be deleted or it is impossible to successfully start another Runtime
+ // instance. We rely on a small initialization order issue in Runtime::Start() that requires
+ // elements of WellKnownClasses to be null, see b/65500943.
+ WellKnownClasses::Clear();
+
+ // Ensure that libnativehelper caching is invalidated, in case a new runtime is to be brought
+ // up later.
+ android::ClearJniConstantsCache();
}
struct AbortState {
@@ -417,6 +427,12 @@
return;
}
Thread* self = Thread::Current();
+
+ // Dump all threads first and then the aborting thread. While this is counter the logical flow,
+ // it improves the chance of relevant data surviving in the Android logs.
+
+ DumpAllThreads(os, self);
+
if (self == nullptr) {
os << "(Aborting thread was not attached to runtime!)\n";
DumpKernelStack(os, GetTid(), " kernel: ", false);
@@ -432,7 +448,6 @@
}
}
}
- DumpAllThreads(os, self);
}
// No thread-safety analysis as we do explicitly test for holding the mutator lock.
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 4e84468..2eb4411 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -658,6 +658,10 @@
RuntimeCallbacks* GetRuntimeCallbacks();
+ bool HasLoadedPlugins() const {
+ return !plugins_.empty();
+ }
+
void InitThreadGroups(Thread* self);
void SetDumpGCPerformanceOnShutdown(bool value) {
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index 16d6c13..88d3f28 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -21,6 +21,7 @@
#include "art_method.h"
#include "base/macros.h"
#include "class_linker.h"
+#include "monitor.h"
#include "thread.h"
namespace art {
@@ -38,6 +39,38 @@
}
}
+void RuntimeCallbacks::MonitorContendedLocking(Monitor* m) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->MonitorContendedLocking(m);
+ }
+}
+
+void RuntimeCallbacks::MonitorContendedLocked(Monitor* m) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->MonitorContendedLocked(m);
+ }
+}
+
+void RuntimeCallbacks::ObjectWaitStart(Handle<mirror::Object> m, int64_t timeout) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->ObjectWaitStart(m, timeout);
+ }
+}
+
+void RuntimeCallbacks::MonitorWaitFinished(Monitor* m, bool timeout) {
+ for (MonitorCallback* cb : monitor_callbacks_) {
+ cb->MonitorWaitFinished(m, timeout);
+ }
+}
+
+void RuntimeCallbacks::AddMonitorCallback(MonitorCallback* cb) {
+ monitor_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveMonitorCallback(MonitorCallback* cb) {
+ Remove(cb, &monitor_callbacks_);
+}
+
void RuntimeCallbacks::RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) {
Remove(cb, &thread_callbacks_);
}
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index e8f1824..fa686d3 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -29,12 +29,14 @@
namespace mirror {
class Class;
class ClassLoader;
+class Object;
} // namespace mirror
class ArtMethod;
class ClassLoadCallback;
class Thread;
class MethodCallback;
+class Monitor;
class ThreadLifecycleCallback;
// Note: RuntimeCallbacks uses the mutator lock to synchronize the callback lists. A thread must
@@ -73,6 +75,25 @@
virtual void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
};
+class MonitorCallback {
+ public:
+ // Called just before the thread goes to sleep to wait for the monitor to become unlocked.
+ virtual void MonitorContendedLocking(Monitor* mon) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ // Called just after the monitor has been successfully acquired when it was already locked.
+ virtual void MonitorContendedLocked(Monitor* mon) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ // Called on entry to the Object#wait method regardless of whether or not the call is valid.
+ virtual void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis_timeout)
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+ // Called just after the monitor has woken up from going to sleep for a wait(). At this point the
+ // thread does not possess a lock on the monitor. This will only be called for threads wait calls
+ // where the thread did (or at least could have) gone to sleep.
+ virtual void MonitorWaitFinished(Monitor* m, bool timed_out)
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+ virtual ~MonitorCallback() {}
+};
+
class RuntimeCallbacks {
public:
void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);
@@ -120,6 +141,16 @@
/*out*/void** new_implementation)
REQUIRES_SHARED(Locks::mutator_lock_);
+ void MonitorContendedLocking(Monitor* m) REQUIRES_SHARED(Locks::mutator_lock_);
+ void MonitorContendedLocked(Monitor* m) REQUIRES_SHARED(Locks::mutator_lock_);
+ void ObjectWaitStart(Handle<mirror::Object> m, int64_t timeout)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void MonitorWaitFinished(Monitor* m, bool timed_out)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ void AddMonitorCallback(MonitorCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+ void RemoveMonitorCallback(MonitorCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+
private:
std::vector<ThreadLifecycleCallback*> thread_callbacks_
GUARDED_BY(Locks::mutator_lock_);
@@ -131,6 +162,8 @@
GUARDED_BY(Locks::mutator_lock_);
std::vector<MethodCallback*> method_callbacks_
GUARDED_BY(Locks::mutator_lock_);
+ std::vector<MonitorCallback*> monitor_callbacks_
+ GUARDED_BY(Locks::mutator_lock_);
};
} // namespace art
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
index ac2ed9e..ef17258 100644
--- a/runtime/runtime_callbacks_test.cc
+++ b/runtime/runtime_callbacks_test.cc
@@ -22,6 +22,7 @@
#include <initializer_list>
#include <memory>
+#include <mutex>
#include <string>
#include "jni.h"
@@ -29,12 +30,14 @@
#include "art_method-inl.h"
#include "base/mutex.h"
#include "class_linker.h"
+#include "class_reference.h"
#include "common_runtime_test.h"
#include "handle.h"
#include "handle_scope-inl.h"
#include "mem_map.h"
#include "mirror/class-inl.h"
#include "mirror/class_loader.h"
+#include "monitor.h"
#include "nativehelper/ScopedLocalRef.h"
#include "obj_ptr.h"
#include "runtime.h"
@@ -433,4 +436,87 @@
ASSERT_EQ(1u, cb_.death_seen);
}
+class MonitorWaitCallbacksTest : public RuntimeCallbacksTest {
+ protected:
+ void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+ Runtime::Current()->GetRuntimeCallbacks()->AddMonitorCallback(&cb_);
+ }
+ void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+ Runtime::Current()->GetRuntimeCallbacks()->RemoveMonitorCallback(&cb_);
+ }
+
+ struct Callback : public MonitorCallback {
+ bool IsInterestingObject(mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (!obj->IsClass()) {
+ return false;
+ }
+ std::lock_guard<std::mutex> lock(ref_guard_);
+ mirror::Class* k = obj->AsClass();
+ ClassReference test = { &k->GetDexFile(), k->GetDexClassDefIndex() };
+ return ref_ == test;
+ }
+
+ void SetInterestingObject(mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ std::lock_guard<std::mutex> lock(ref_guard_);
+ mirror::Class* k = obj->AsClass();
+ ref_ = { &k->GetDexFile(), k->GetDexClassDefIndex() };
+ }
+
+ void MonitorContendedLocking(Monitor* mon ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) { }
+
+ void MonitorContendedLocked(Monitor* mon ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) { }
+
+ void ObjectWaitStart(Handle<mirror::Object> obj, int64_t millis ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (IsInterestingObject(obj.Get())) {
+ saw_wait_start_ = true;
+ }
+ }
+
+ void MonitorWaitFinished(Monitor* m, bool timed_out ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (IsInterestingObject(m->GetObject())) {
+ saw_wait_finished_ = true;
+ }
+ }
+
+ std::mutex ref_guard_;
+ ClassReference ref_ = {nullptr, 0};
+ bool saw_wait_start_ = false;
+ bool saw_wait_finished_ = false;
+ };
+
+ Callback cb_;
+};
+
+// TODO It would be good to have more tests for this but due to the multi-threaded nature of the
+// callbacks this is difficult. For now the run-tests 1931 & 1932 should be sufficient.
+TEST_F(MonitorWaitCallbacksTest, WaitUnlocked) {
+ ASSERT_FALSE(cb_.saw_wait_finished_);
+ ASSERT_FALSE(cb_.saw_wait_start_);
+ {
+ Thread* self = Thread::Current();
+ self->TransitionFromSuspendedToRunnable();
+ bool started = runtime_->Start();
+ ASSERT_TRUE(started);
+ {
+ ScopedObjectAccess soa(self);
+ cb_.SetInterestingObject(
+ soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections).Ptr());
+ Monitor::Wait(
+ self,
+ // Just a random class
+ soa.Decode<mirror::Class>(WellKnownClasses::java_util_Collections).Ptr(),
+ /*ms*/0,
+ /*ns*/0,
+ /*interruptShouldThrow*/false,
+ /*why*/kWaiting);
+ }
+ }
+ ASSERT_TRUE(cb_.saw_wait_start_);
+ ASSERT_FALSE(cb_.saw_wait_finished_);
+}
+
} // namespace art
diff --git a/runtime/thread_state.h b/runtime/thread_state.h
index 8f2f70f..8edfeec 100644
--- a/runtime/thread_state.h
+++ b/runtime/thread_state.h
@@ -29,6 +29,8 @@
kSleeping, // TIMED_WAITING TS_SLEEPING in Thread.sleep()
kBlocked, // BLOCKED TS_MONITOR blocked on a monitor
kWaiting, // WAITING TS_WAIT in Object.wait()
+ kWaitingForLockInflation, // WAITING TS_WAIT blocked inflating a thin-lock
+ kWaitingForTaskProcessor, // WAITING TS_WAIT blocked waiting for taskProcessor
kWaitingForGcToComplete, // WAITING TS_WAIT blocked waiting for GC
kWaitingForCheckPointsToRun, // WAITING TS_WAIT GC waiting for checkpoints to run
kWaitingPerformingGc, // WAITING TS_WAIT performing GC
diff --git a/runtime/trace.cc b/runtime/trace.cc
index b30de79..4c3fa20 100644
--- a/runtime/trace.cc
+++ b/runtime/trace.cc
@@ -832,6 +832,11 @@
<< " " << dex_pc;
}
+void Trace::WatchedFramePop(Thread* self ATTRIBUTE_UNUSED,
+ const ShadowFrame& frame ATTRIBUTE_UNUSED) {
+ LOG(ERROR) << "Unexpected WatchedFramePop event in tracing";
+}
+
void Trace::ReadClocks(Thread* thread, uint32_t* thread_clock_diff, uint32_t* wall_clock_diff) {
if (UseThreadCpuClock()) {
uint64_t clock_base = thread->GetTraceClockBase();
diff --git a/runtime/trace.h b/runtime/trace.h
index 49d5b22..a888dcb 100644
--- a/runtime/trace.h
+++ b/runtime/trace.h
@@ -38,6 +38,7 @@
class ArtField;
class ArtMethod;
class DexFile;
+class ShadowFrame;
class Thread;
using DexIndexBitSet = std::bitset<65536>;
@@ -194,6 +195,8 @@
uint32_t dex_pc,
ArtMethod* callee)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!*unique_methods_lock_) OVERRIDE;
+ void WatchedFramePop(Thread* thread, const ShadowFrame& frame)
+ REQUIRES_SHARED(Locks::mutator_lock_) OVERRIDE;
// Reuse an old stack trace if it exists, otherwise allocate a new one.
static std::vector<ArtMethod*>* AllocStackTrace();
// Clear and store an old stack trace for later use.
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 1c14cf2..74d11a3 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -414,6 +414,124 @@
"Ljava/lang/reflect/InvocationHandler;");
}
+void WellKnownClasses::Clear() {
+ dalvik_annotation_optimization_CriticalNative = nullptr;
+ dalvik_annotation_optimization_FastNative = nullptr;
+ dalvik_system_BaseDexClassLoader = nullptr;
+ dalvik_system_DelegateLastClassLoader = nullptr;
+ dalvik_system_DexClassLoader = nullptr;
+ dalvik_system_DexFile = nullptr;
+ dalvik_system_DexPathList = nullptr;
+ dalvik_system_DexPathList__Element = nullptr;
+ dalvik_system_EmulatedStackFrame = nullptr;
+ dalvik_system_PathClassLoader = nullptr;
+ dalvik_system_VMRuntime = nullptr;
+ java_lang_annotation_Annotation__array = nullptr;
+ java_lang_BootClassLoader = nullptr;
+ java_lang_ClassLoader = nullptr;
+ java_lang_ClassNotFoundException = nullptr;
+ java_lang_Daemons = nullptr;
+ java_lang_Error = nullptr;
+ java_lang_IllegalAccessError = nullptr;
+ java_lang_invoke_MethodHandle = nullptr;
+ java_lang_NoClassDefFoundError = nullptr;
+ java_lang_Object = nullptr;
+ java_lang_OutOfMemoryError = nullptr;
+ java_lang_reflect_Constructor = nullptr;
+ java_lang_reflect_Executable = nullptr;
+ java_lang_reflect_Field = nullptr;
+ java_lang_reflect_Method = nullptr;
+ java_lang_reflect_Parameter = nullptr;
+ java_lang_reflect_Parameter__array = nullptr;
+ java_lang_reflect_Proxy = nullptr;
+ java_lang_RuntimeException = nullptr;
+ java_lang_StackOverflowError = nullptr;
+ java_lang_String = nullptr;
+ java_lang_StringFactory = nullptr;
+ java_lang_System = nullptr;
+ java_lang_Thread = nullptr;
+ java_lang_ThreadGroup = nullptr;
+ java_lang_Throwable = nullptr;
+ java_util_ArrayList = nullptr;
+ java_util_Collections = nullptr;
+ java_nio_DirectByteBuffer = nullptr;
+ libcore_reflect_AnnotationFactory = nullptr;
+ libcore_reflect_AnnotationMember = nullptr;
+ libcore_util_EmptyArray = nullptr;
+ org_apache_harmony_dalvik_ddmc_Chunk = nullptr;
+ org_apache_harmony_dalvik_ddmc_DdmServer = nullptr;
+
+ dalvik_system_VMRuntime_runFinalization = nullptr;
+ java_lang_Boolean_valueOf = nullptr;
+ java_lang_Byte_valueOf = nullptr;
+ java_lang_Character_valueOf = nullptr;
+ java_lang_ClassLoader_loadClass = nullptr;
+ java_lang_ClassNotFoundException_init = nullptr;
+ java_lang_Daemons_requestHeapTrim = nullptr;
+ java_lang_Daemons_start = nullptr;
+ java_lang_Daemons_stop = nullptr;
+ java_lang_Double_valueOf = nullptr;
+ java_lang_Float_valueOf = nullptr;
+ java_lang_Integer_valueOf = nullptr;
+ java_lang_invoke_MethodHandle_invoke = nullptr;
+ java_lang_invoke_MethodHandle_invokeExact = nullptr;
+ java_lang_invoke_MethodHandles_lookup = nullptr;
+ java_lang_invoke_MethodHandles_Lookup_findConstructor = nullptr;
+ java_lang_Long_valueOf = nullptr;
+ java_lang_ref_FinalizerReference_add = nullptr;
+ java_lang_ref_ReferenceQueue_add = nullptr;
+ java_lang_reflect_Parameter_init = nullptr;
+ java_lang_reflect_Proxy_invoke = nullptr;
+ java_lang_Runtime_nativeLoad = nullptr;
+ java_lang_Short_valueOf = nullptr;
+ java_lang_String_charAt = nullptr;
+ java_lang_System_runFinalization = nullptr;
+ java_lang_Thread_dispatchUncaughtException = nullptr;
+ java_lang_Thread_init = nullptr;
+ java_lang_Thread_run = nullptr;
+ java_lang_ThreadGroup_removeThread = nullptr;
+ java_nio_DirectByteBuffer_init = nullptr;
+ libcore_reflect_AnnotationFactory_createAnnotation = nullptr;
+ libcore_reflect_AnnotationMember_init = nullptr;
+ org_apache_harmony_dalvik_ddmc_DdmServer_broadcast = nullptr;
+ org_apache_harmony_dalvik_ddmc_DdmServer_dispatch = nullptr;
+
+ dalvik_system_BaseDexClassLoader_pathList = nullptr;
+ dalvik_system_DexFile_cookie = nullptr;
+ dalvik_system_DexFile_fileName = nullptr;
+ dalvik_system_DexPathList_dexElements = nullptr;
+ dalvik_system_DexPathList__Element_dexFile = nullptr;
+ java_lang_reflect_Executable_artMethod = nullptr;
+ java_lang_reflect_Proxy_h = nullptr;
+ java_lang_Thread_daemon = nullptr;
+ java_lang_Thread_group = nullptr;
+ java_lang_Thread_lock = nullptr;
+ java_lang_Thread_name = nullptr;
+ java_lang_Thread_priority = nullptr;
+ java_lang_Thread_nativePeer = nullptr;
+ java_lang_ThreadGroup_groups = nullptr;
+ java_lang_ThreadGroup_ngroups = nullptr;
+ java_lang_ThreadGroup_mainThreadGroup = nullptr;
+ java_lang_ThreadGroup_name = nullptr;
+ java_lang_ThreadGroup_parent = nullptr;
+ java_lang_ThreadGroup_systemThreadGroup = nullptr;
+ java_lang_Throwable_cause = nullptr;
+ java_lang_Throwable_detailMessage = nullptr;
+ java_lang_Throwable_stackTrace = nullptr;
+ java_lang_Throwable_stackState = nullptr;
+ java_lang_Throwable_suppressedExceptions = nullptr;
+ java_nio_DirectByteBuffer_capacity = nullptr;
+ java_nio_DirectByteBuffer_effectiveDirectAddress = nullptr;
+ java_util_ArrayList_array = nullptr;
+ java_util_ArrayList_size = nullptr;
+ java_util_Collections_EMPTY_LIST = nullptr;
+ libcore_util_EmptyArray_STACK_TRACE_ELEMENT = nullptr;
+ org_apache_harmony_dalvik_ddmc_Chunk_data = nullptr;
+ org_apache_harmony_dalvik_ddmc_Chunk_length = nullptr;
+ org_apache_harmony_dalvik_ddmc_Chunk_offset = nullptr;
+ org_apache_harmony_dalvik_ddmc_Chunk_type = nullptr;
+}
+
ObjPtr<mirror::Class> WellKnownClasses::ToClass(jclass global_jclass) {
auto ret = ObjPtr<mirror::Class>::DownCast(Thread::Current()->DecodeJObject(global_jclass));
DCHECK(!ret.IsNull());
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index 2f2f1ad..7deef63 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -37,6 +37,9 @@
public:
static void Init(JNIEnv* env); // Run before native methods are registered.
static void LateInit(JNIEnv* env); // Run after native methods are registered.
+
+ static void Clear();
+
static ArtMethod* StringInitToStringFactory(ArtMethod* method);
static uint32_t StringInitToEntryPoint(ArtMethod* method);
diff --git a/test/088-monitor-verification/src/Main.java b/test/088-monitor-verification/src/Main.java
index 13a96c7..f5cbc2a 100644
--- a/test/088-monitor-verification/src/Main.java
+++ b/test/088-monitor-verification/src/Main.java
@@ -39,6 +39,7 @@
ensureJitCompiled(Main.class, "constantLock");
ensureJitCompiled(Main.class, "notExcessiveNesting");
ensureJitCompiled(Main.class, "notNested");
+ ensureJitCompiled(TwoPath.class, "twoPath");
Main m = new Main();
diff --git a/test/164-resolution-trampoline-dex-cache/expected.txt b/test/164-resolution-trampoline-dex-cache/expected.txt
new file mode 100644
index 0000000..d549cb1
--- /dev/null
+++ b/test/164-resolution-trampoline-dex-cache/expected.txt
@@ -0,0 +1,3 @@
+JNI_OnLoad called
+Base.foo() on MostDerived
+MostDerived.test(.) done.
diff --git a/test/164-resolution-trampoline-dex-cache/info.txt b/test/164-resolution-trampoline-dex-cache/info.txt
new file mode 100644
index 0000000..4e4d82a
--- /dev/null
+++ b/test/164-resolution-trampoline-dex-cache/info.txt
@@ -0,0 +1,3 @@
+Regression test for artQuickResolutionTrampoline() erroneously storing an
+ArtMethod to a DexCache for a MethodId referencing a class missing from the
+associated ClassTable. This discrepancy then led to a crash when JITting.
diff --git a/test/164-resolution-trampoline-dex-cache/profile b/test/164-resolution-trampoline-dex-cache/profile
new file mode 100644
index 0000000..d232ff2
--- /dev/null
+++ b/test/164-resolution-trampoline-dex-cache/profile
@@ -0,0 +1 @@
+LMostDerived;->test(Ljava/lang/Class;)V
diff --git a/test/164-resolution-trampoline-dex-cache/run b/test/164-resolution-trampoline-dex-cache/run
new file mode 100644
index 0000000..5e77cd5
--- /dev/null
+++ b/test/164-resolution-trampoline-dex-cache/run
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (C) 2017 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.
+
+# Use the --secondary switch to add secondary dex file to class path.
+# Make sure we compile the required method using speed-profile compiler filter.
+# Enable JIT through runtime option to avoid the compiler filter set by --jit.
+exec ${RUN} "${@}" --secondary \
+ -Xcompiler-option --compiler-filter=speed-profile --profile \
+ --runtime-option -Xusejit:true
diff --git a/test/164-resolution-trampoline-dex-cache/src-ex/MostDerived.java b/test/164-resolution-trampoline-dex-cache/src-ex/MostDerived.java
new file mode 100644
index 0000000..0c85528
--- /dev/null
+++ b/test/164-resolution-trampoline-dex-cache/src-ex/MostDerived.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 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 MostDerived extends Derived {
+ public static void test(Class main) {
+ // The defining class loader of MostDerived (MDCL) is also the initiating loader of
+ // superclass Derived but delegates the loading to its parent class loader (PCL) which
+ // defines both Derived and Base. Thus Derived.class is recorded in MDCL's ClassTable
+ // but the Base.class is not because the Base's initiating loader is PCL. This is the
+ // case when loading the MostDerived class and remains the case after resolving the
+ // "invoke-super Derived.foo(.)" called from from MostDerived.foo(.). When that
+ // invoke-super is executed from AOT-compiled code, it goes through the .bss ArtMethod*
+ // entry and on first execution goes through the resolution method. After resolving to
+ // the Base.foo(.), the artQuickResolutionTrampoline() used to erroneously fill the
+ // Base.foo(.) entry in the MostDerived's DexCache which is wrong as the referenced
+ // class Base is not in the associated, i.e. MDCL's, ClassTable.
+ new MostDerived().foo(main);
+ try {
+ // This discrepancy then used to crash when resolving the Base.foo(.) method
+ // for JIT compilation of another method.
+ main.getDeclaredMethod("ensureJitCompiled", Class.class, String.class).invoke(
+ null, MostDerived.class, "bar");
+ } catch (Throwable t) {
+ t.printStackTrace(System.out);
+ }
+ System.out.println("MostDerived.test(.) done.");
+ }
+
+ public void foo(Class main) {
+ super.foo(main);
+ }
+
+ public void bar(Class main) {
+ Base b = this;
+ b.foo(main);
+ }
+}
diff --git a/test/164-resolution-trampoline-dex-cache/src/Base.java b/test/164-resolution-trampoline-dex-cache/src/Base.java
new file mode 100644
index 0000000..d4ef78b
--- /dev/null
+++ b/test/164-resolution-trampoline-dex-cache/src/Base.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 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 Base {
+ public void foo(Class main) {
+ System.out.println("Base.foo() on " + getClass().getName());
+ }
+}
diff --git a/test/164-resolution-trampoline-dex-cache/src/Derived.java b/test/164-resolution-trampoline-dex-cache/src/Derived.java
new file mode 100644
index 0000000..acca9ce
--- /dev/null
+++ b/test/164-resolution-trampoline-dex-cache/src/Derived.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2017 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 Derived extends Base {
+}
diff --git a/test/164-resolution-trampoline-dex-cache/src/Main.java b/test/164-resolution-trampoline-dex-cache/src/Main.java
new file mode 100644
index 0000000..b4731ae
--- /dev/null
+++ b/test/164-resolution-trampoline-dex-cache/src/Main.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+public class Main {
+ public static String TEST_NAME = "164-resolution-trampoline-dex-cache";
+
+ public static void main(String[] args) {
+ // Load the test JNI for ensureJitCompiled(). Note that classes Main loaded
+ // by other class loaders do not have the binding, so we need to pass the
+ // current Main.class around and call ensureJitCompiled() via reflection.
+ System.loadLibrary(args[0]);
+ try {
+ String dex_location = System.getenv("DEX_LOCATION");
+ ClassLoader systemLoader = ClassLoader.getSystemClassLoader().getParent();
+ ClassLoader baseLoader = getClassLoaderFor(dex_location, systemLoader, /* ex */ false);
+ ClassLoader mainLoader = getClassLoaderFor(dex_location, baseLoader, /* ex */ true);
+
+ Class<?> tc = Class.forName("MostDerived", true, mainLoader);
+ Method m = tc.getDeclaredMethod("test", Class.class);
+ m.invoke(null, Main.class);
+ } catch (Throwable t) {
+ t.printStackTrace(System.out);
+ }
+ }
+
+ public static ClassLoader getClassLoaderFor(String location, ClassLoader parent, boolean ex)
+ throws Exception {
+ try {
+ Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader");
+ Constructor<?> ctor =
+ class_loader_class.getConstructor(String.class, ClassLoader.class);
+ String path = location + "/" + TEST_NAME + (ex ? "-ex.jar" : ".jar");
+ return (ClassLoader)ctor.newInstance(path, parent);
+ } catch (ClassNotFoundException e) {
+ // Running on RI. Use URLClassLoader.
+ String url = "file://" + location + (ex ? "/classes-ex/" : "/classes/");
+ return new java.net.URLClassLoader(
+ new java.net.URL[] { new java.net.URL(url) }, parent);
+ }
+ }
+
+ public static native void ensureJitCompiled(Class<?> klass, String method_name);
+}
diff --git a/test/1930-monitor-info/expected.txt b/test/1930-monitor-info/expected.txt
new file mode 100644
index 0000000..b43f1b2
--- /dev/null
+++ b/test/1930-monitor-info/expected.txt
@@ -0,0 +1,31 @@
+Running with single thread.
+Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testSingleThread], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testSingleThread], owner: main, entryCount: 1, waiters: [], notify_waiters: [] }
+Running with single thread in native.
+Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testSingleThread], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testSingleThread], owner: main, entryCount: 1, waiters: [], notify_waiters: [] }
+Lock twice
+Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwice], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwice], owner: main, entryCount: 1, waiters: [], notify_waiters: [] }
+Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwice], owner: main, entryCount: 2, waiters: [], notify_waiters: [] }
+Lock twice native
+Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNative], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNative], owner: main, entryCount: 1, waiters: [], notify_waiters: [] }
+Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNative], owner: main, entryCount: 2, waiters: [], notify_waiters: [] }
+Lock twice Java then native
+Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceJN], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceJN], owner: main, entryCount: 1, waiters: [], notify_waiters: [] }
+Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceJN], owner: main, entryCount: 2, waiters: [], notify_waiters: [] }
+Lock twice native then Java
+Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNJ], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Pre-lock[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNJ], owner: main, entryCount: 1, waiters: [], notify_waiters: [] }
+Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockedTwiceNJ], owner: main, entryCount: 2, waiters: [], notify_waiters: [] }
+lock with wait
+Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockWait], owner: main, entryCount: 1, waiters: [Test1930 Thread - testLockWait], notify_waiters: [] }
+Thread[Test1930 Thread - testLockWait]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockWait], owner: Test1930 Thread - testLockWait, entryCount: 1, waiters: [], notify_waiters: [] }
+Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testLockWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Wait for notify.
+Thread[Test1930 Thread - testLockWait]: MonitorUsage{ monitor: NamedLock[Test1930 - testNotifyWait], owner: Test1930 Thread - testLockWait, entryCount: 1, waiters: [], notify_waiters: [] }
+Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testNotifyWait], owner: main, entryCount: 1, waiters: [Test1930 Thread - testLockWait], notify_waiters: [Test1930 Thread - testLockWait] }
+Thread[Test1930 Thread - testLockWait]: MonitorUsage{ monitor: NamedLock[Test1930 - testNotifyWait], owner: Test1930 Thread - testLockWait, entryCount: 1, waiters: [], notify_waiters: [] }
+Thread[main]: MonitorUsage{ monitor: NamedLock[Test1930 - testNotifyWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
diff --git a/test/1930-monitor-info/info.txt b/test/1930-monitor-info/info.txt
new file mode 100644
index 0000000..8e19edc
--- /dev/null
+++ b/test/1930-monitor-info/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that the GetObjectMonitorUsage function works correctly.
diff --git a/test/1930-monitor-info/monitor.cc b/test/1930-monitor-info/monitor.cc
new file mode 100644
index 0000000..7f97c05
--- /dev/null
+++ b/test/1930-monitor-info/monitor.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pthread.h>
+
+#include <cstdio>
+#include <iostream>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "jvmti.h"
+
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1930MonitorInfo {
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1930_executeLockedNative(JNIEnv* env,
+ jclass klass,
+ jobject run,
+ jobject l) {
+ ScopedLocalRef<jclass> runnable(env, env->FindClass("java/lang/Runnable"));
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ jmethodID method = env->GetMethodID(runnable.get(), "run", "()V");
+
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ jmethodID printMethod = env->GetStaticMethodID(klass, "printPreLock", "(Ljava/lang/Object;)V");
+ if (env->ExceptionCheck()) {
+ return;
+ }
+
+ env->CallStaticVoidMethod(klass, printMethod, l);
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ if (env->MonitorEnter(l) != 0) {
+ return;
+ }
+ env->CallVoidMethod(run, method);
+ env->MonitorExit(l);
+}
+
+} // namespace Test1930MonitorInfo
+} // namespace art
diff --git a/test/1930-monitor-info/run b/test/1930-monitor-info/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1930-monitor-info/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+./default-run "$@" --jvmti
diff --git a/test/1930-monitor-info/src/Main.java b/test/1930-monitor-info/src/Main.java
new file mode 100644
index 0000000..3328461
--- /dev/null
+++ b/test/1930-monitor-info/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1930.run();
+ }
+}
diff --git a/test/1930-monitor-info/src/art/Monitors.java b/test/1930-monitor-info/src/art/Monitors.java
new file mode 100644
index 0000000..b28a3ee
--- /dev/null
+++ b/test/1930-monitor-info/src/art/Monitors.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.*;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Monitors {
+ public native static void setupMonitorEvents(
+ Class<?> method_klass,
+ Method monitor_contended_enter_event,
+ Method monitor_contended_entered_event,
+ Method monitor_wait_event,
+ Method monitor_waited_event,
+ Class<?> lock_klass,
+ Thread thr);
+ public native static void stopMonitorEvents();
+
+ public static class NamedLock {
+ public final String name;
+ public NamedLock(String name) {
+ this.name = name;
+ }
+ public String toString() {
+ return String.format("NamedLock[%s]", name);
+ }
+ }
+
+ public static final class MonitorUsage {
+ public final Object monitor;
+ public final Thread owner;
+ public final int entryCount;
+ public final Thread[] waiters;
+ public final Thread[] notifyWaiters;
+
+ public MonitorUsage(
+ Object monitor,
+ Thread owner,
+ int entryCount,
+ Thread[] waiters,
+ Thread[] notifyWaiters) {
+ this.monitor = monitor;
+ this.entryCount = entryCount;
+ this.owner = owner;
+ this.waiters = waiters;
+ this.notifyWaiters = notifyWaiters;
+ }
+
+ private static String toNameList(Thread[] ts) {
+ return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray());
+ }
+
+ public String toString() {
+ return String.format(
+ "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }",
+ monitor,
+ (owner != null) ? owner.getName() : "<NULL>",
+ entryCount,
+ toNameList(waiters),
+ toNameList(notifyWaiters));
+ }
+ }
+
+ public static native MonitorUsage getObjectMonitorUsage(Object monitor);
+ public static native Object getCurrentContendedMonitor(Thread thr);
+
+ public static class TestException extends Error {
+ public TestException() { super(); }
+ public TestException(String s) { super(s); }
+ public TestException(String s, Throwable c) { super(s, c); }
+ }
+
+ public static class LockController {
+ private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT }
+
+ public final Object lock;
+ public final long timeout;
+ private final AtomicStampedReference<Action> action;
+ private volatile Thread runner = null;
+ private volatile boolean started = false;
+ private volatile boolean held = false;
+ private static final AtomicInteger cnt = new AtomicInteger(0);
+ private volatile Throwable exe;
+
+ public LockController(Object lock) {
+ this(lock, 10 * 1000);
+ }
+ public LockController(Object lock, long timeout) {
+ this.lock = lock;
+ this.timeout = timeout;
+ this.action = new AtomicStampedReference(Action.HOLD, 0);
+ this.exe = null;
+ }
+
+ public boolean IsWorkerThread(Thread thd) {
+ return Objects.equals(runner, thd);
+ }
+
+ public boolean IsLocked() {
+ checkException();
+ return held;
+ }
+
+ public void checkException() {
+ if (exe != null) {
+ throw new TestException("Exception thrown by other thread!", exe);
+ }
+ }
+
+ private void setAction(Action a) {
+ int stamp = action.getStamp();
+ // Wait for it to be HOLD before updating.
+ while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) {
+ stamp = action.getStamp();
+ }
+ }
+
+ public synchronized void suspendWorker() throws Exception {
+ checkException();
+ if (runner == null) {
+ throw new TestException("We don't have any runner holding " + lock);
+ }
+ Suspension.suspend(runner);
+ }
+
+ public Object getWorkerContendedMonitor() throws Exception {
+ checkException();
+ if (runner == null) {
+ return null;
+ }
+ return getCurrentContendedMonitor(runner);
+ }
+
+ public synchronized void DoLock() {
+ if (IsLocked()) {
+ throw new Error("lock is already acquired or being acquired.");
+ }
+ if (runner != null) {
+ throw new Error("Already have thread!");
+ }
+ runner = new Thread(() -> {
+ started = true;
+ try {
+ synchronized (lock) {
+ held = true;
+ int[] stamp_h = new int[] { -1 };
+ Action cur_action = Action.HOLD;
+ try {
+ while (true) {
+ cur_action = action.get(stamp_h);
+ int stamp = stamp_h[0];
+ if (cur_action == Action.RELEASE) {
+ // The other thread will deal with reseting action.
+ break;
+ }
+ try {
+ switch (cur_action) {
+ case HOLD:
+ Thread.yield();
+ break;
+ case NOTIFY:
+ lock.notify();
+ break;
+ case NOTIFY_ALL:
+ lock.notifyAll();
+ break;
+ case TIMED_WAIT:
+ lock.wait(timeout);
+ break;
+ case WAIT:
+ lock.wait();
+ break;
+ default:
+ throw new Error("Unknown action " + action);
+ }
+ } finally {
+ // reset action back to hold if it isn't something else.
+ action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1);
+ }
+ }
+ } catch (Exception e) {
+ throw new TestException("Got an error while performing action " + cur_action, e);
+ }
+ }
+ } finally {
+ held = false;
+ started = false;
+ }
+ }, "Locker thread " + cnt.getAndIncrement() + " for " + lock);
+ // Make sure we can get any exceptions this throws.
+ runner.setUncaughtExceptionHandler((t, e) -> { exe = e; });
+ runner.start();
+ }
+
+ public void waitForLockToBeHeld() throws Exception {
+ while (true) {
+ if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) {
+ return;
+ }
+ }
+ }
+
+ public synchronized void waitForNotifySleep() throws Exception {
+ if (runner == null) {
+ throw new Error("No thread trying to lock!");
+ }
+ do {
+ checkException();
+ } while (!started ||
+ !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner));
+ }
+
+ public synchronized void waitForContendedSleep() throws Exception {
+ if (runner == null) {
+ throw new Error("No thread trying to lock!");
+ }
+ do {
+ checkException();
+ } while (!started ||
+ runner.getState() != Thread.State.BLOCKED ||
+ !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner));
+ }
+
+ public synchronized void DoNotify() {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.NOTIFY);
+ }
+
+ public synchronized void DoNotifyAll() {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.NOTIFY_ALL);
+ }
+
+ public synchronized void DoTimedWait() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.TIMED_WAIT);
+ }
+
+ public synchronized void DoWait() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.WAIT);
+ }
+
+ public synchronized void interruptWorker() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ runner.interrupt();
+ }
+
+ public synchronized void waitForActionToFinish() throws Exception {
+ checkException();
+ while (action.getReference() != Action.HOLD) { checkException(); }
+ }
+
+ public synchronized void DoUnlock() throws Exception {
+ Error throwing = null;
+ if (!IsLocked()) {
+ // We might just be racing some exception that was thrown by the worker thread. Cache the
+ // exception, we will throw one from the worker before this one.
+ throwing = new Error("Not locked!");
+ }
+ setAction(Action.RELEASE);
+ Thread run = runner;
+ runner = null;
+ while (held) {}
+ run.join();
+ action.set(Action.HOLD, 0);
+ // Make sure to throw any exception that occurred since it might not have unlocked due to our
+ // request.
+ checkException();
+ DoCleanup();
+ if (throwing != null) {
+ throw throwing;
+ }
+ }
+
+ public synchronized void DoCleanup() throws Exception {
+ if (runner != null) {
+ Thread run = runner;
+ runner = null;
+ while (held) {}
+ run.join();
+ }
+ action.set(Action.HOLD, 0);
+ exe = null;
+ }
+ }
+}
+
diff --git a/test/1930-monitor-info/src/art/Suspension.java b/test/1930-monitor-info/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1930-monitor-info/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+public class Suspension {
+ // Suspends a thread using jvmti.
+ public native static void suspend(Thread thr);
+
+ // Resumes a thread using jvmti.
+ public native static void resume(Thread thr);
+
+ public native static boolean isSuspended(Thread thr);
+
+ public native static int[] suspendList(Thread... threads);
+ public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1930-monitor-info/src/art/Test1930.java b/test/1930-monitor-info/src/art/Test1930.java
new file mode 100644
index 0000000..a7fa1c7
--- /dev/null
+++ b/test/1930-monitor-info/src/art/Test1930.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+
+public class Test1930 {
+ public static final int NUM_RETRY = 100;
+ private static void testSingleThread() {
+ Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testSingleThread");
+ executeLocked(() -> { printMonitorUsage(lk); }, lk);
+ }
+ private static void testSingleThreadNative() {
+ Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testSingleThread");
+ executeLockedNative(() -> { printMonitorUsage(lk); }, lk);
+ }
+
+ private static void testLockedTwice() {
+ final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testLockedTwice");
+ executeLocked(() -> { executeLocked(() -> { printMonitorUsage(lk); }, lk); }, lk);
+ }
+
+ private static void testLockedTwiceNJ() {
+ final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testLockedTwiceNJ");
+ executeLockedNative(() -> { executeLockedNative(() -> { printMonitorUsage(lk); }, lk); }, lk);
+ }
+
+ private static void testLockedTwiceJN() {
+ final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testLockedTwiceJN");
+ executeLockedNative(() -> { executeLockedNative(() -> { printMonitorUsage(lk); }, lk); }, lk);
+ }
+
+ private static void testLockedTwiceNative() {
+ final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testLockedTwiceNative");
+ executeLockedNative(() -> { executeLockedNative(() -> { printMonitorUsage(lk); }, lk); }, lk);
+ }
+
+ public final static class ThreadSignaler {
+ public volatile boolean signal = false;
+ }
+
+ private static void testLockWait() throws Exception {
+ final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testLockWait");
+ final Semaphore sem = new Semaphore(0);
+ final Thread t = new Thread(() -> {
+ sem.release();
+ synchronized (lk) {
+ printMonitorUsage(lk);
+ }
+ }, "Test1930 Thread - testLockWait");
+ synchronized (lk) {
+ t.start();
+ // Wait for the other thread to actually start.
+ sem.acquire();
+ // Wait for the other thread to go to sleep trying to get the mutex. This might take a (short)
+ // time since we try spinning first for better performance.
+ boolean found_wait = false;
+ for (long i = 0; i < NUM_RETRY; i++) {
+ if (Arrays.asList(Monitors.getObjectMonitorUsage(lk).waiters).contains(t)) {
+ found_wait = true;
+ break;
+ } else {
+ Thread.sleep(500);
+ Thread.yield();
+ }
+ }
+ if (!found_wait) {
+ System.out.println("other thread doesn't seem to be waiting.");
+ }
+ printMonitorUsage(lk);
+ }
+ t.join();
+ printMonitorUsage(lk);
+ }
+
+ private static void testNotifyWait() throws Exception {
+ final Monitors.NamedLock lk = new Monitors.NamedLock("Test1930 - testNotifyWait");
+ final Semaphore sem = new Semaphore(0);
+ Thread t = new Thread(() -> {
+ synchronized (lk) {
+ printMonitorUsage(lk);
+ sem.release();
+ try {
+ lk.wait();
+ } catch (Exception e) {
+ throw new Error("Error waiting!", e);
+ }
+ printMonitorUsage(lk);
+ }
+ }, "Test1930 Thread - testLockWait");
+ t.start();
+ sem.acquire();
+ synchronized (lk) {
+ printMonitorUsage(lk);
+ lk.notifyAll();
+ }
+ t.join();
+ printMonitorUsage(lk);
+ }
+
+ public static void run() throws Exception {
+ // Single threaded tests.
+ System.out.println("Running with single thread.");
+ testSingleThread();
+ System.out.println("Running with single thread in native.");
+ testSingleThreadNative();
+ System.out.println("Lock twice");
+ testLockedTwice();
+ System.out.println("Lock twice native");
+ testLockedTwiceNative();
+ System.out.println("Lock twice Java then native");
+ testLockedTwiceJN();
+ System.out.println("Lock twice native then Java");
+ testLockedTwiceNJ();
+
+ // Mutli threaded tests.
+ System.out.println("lock with wait");
+ testLockWait();
+ System.out.println("Wait for notify.");
+ testNotifyWait();
+ }
+
+ public static void printPreLock(Object lock) {
+ System.out.println(String.format("Pre-lock[%s]: %s",
+ Thread.currentThread().getName(), Monitors.getObjectMonitorUsage(lock)));
+ }
+
+ public static void executeLocked(Runnable r, Object lock) {
+ printPreLock(lock);
+ synchronized (lock) {
+ r.run();
+ }
+ }
+
+ public native static void executeLockedNative(Runnable r, Object m);
+ public static void printMonitorUsage(Object m) {
+ System.out.println(String.format("Thread[%s]: %s",
+ Thread.currentThread().getName(), Monitors.getObjectMonitorUsage(m)));
+ }
+}
diff --git a/test/1931-monitor-events/expected.txt b/test/1931-monitor-events/expected.txt
new file mode 100644
index 0000000..33a9bd3
--- /dev/null
+++ b/test/1931-monitor-events/expected.txt
@@ -0,0 +1,29 @@
+Testing contended locking.
+Locker thread 1 for NamedLock[Lock testLock] contended-LOCKING NamedLock[Lock testLock]
+Locker thread 1 for NamedLock[Lock testLock] LOCKED NamedLock[Lock testLock]
+Testing monitor wait.
+Locker thread 2 for NamedLock[Lock testWait] start-monitor-wait NamedLock[Lock testWait] timeout: 0
+Locker thread 2 for NamedLock[Lock testWait] monitor-waited NamedLock[Lock testWait] timed_out: false
+Testing monitor timed wait.
+Locker thread 4 for NamedLock[Lock testTimedWait] start-monitor-wait NamedLock[Lock testTimedWait] timeout: 3600000
+Locker thread 4 for NamedLock[Lock testTimedWait] monitor-waited NamedLock[Lock testTimedWait] timed_out: false
+Testing monitor timed with timeout.
+Waiting for 10 seconds.
+Locker thread 6 for NamedLock[Lock testTimedWaitTimeout] start-monitor-wait NamedLock[Lock testTimedWaitTimeout] timeout: 10000
+Locker thread 6 for NamedLock[Lock testTimedWaitTimeout] monitor-waited NamedLock[Lock testTimedWaitTimeout] timed_out: true
+Wait finished with timeout.
+Waiting on an unlocked monitor.
+Unlocked wait thread: start-monitor-wait NamedLock[Lock testUnlockedWait] timeout: 0
+Caught exception: java.lang.reflect.InvocationTargetException
+ Caused by: class java.lang.IllegalMonitorStateException
+Waiting with an illegal argument (negative timeout)
+Locker thread 7 for NamedLock[Lock testIllegalWait] start-monitor-wait NamedLock[Lock testIllegalWait] timeout: -100
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: Got an error while performing action TIMED_WAIT
+ Caused by: class java.lang.IllegalArgumentException
+Interrupt a monitor being waited on.
+Locker thread 8 for NamedLock[Lock testInteruptWait] start-monitor-wait NamedLock[Lock testInteruptWait] timeout: 0
+Locker thread 8 for NamedLock[Lock testInteruptWait] monitor-waited NamedLock[Lock testInteruptWait] timed_out: false
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: Got an error while performing action WAIT
+ Caused by: class java.lang.InterruptedException
diff --git a/test/1931-monitor-events/info.txt b/test/1931-monitor-events/info.txt
new file mode 100644
index 0000000..ae07c53
--- /dev/null
+++ b/test/1931-monitor-events/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that the basic monitor-events work as we expect them to.
diff --git a/test/1931-monitor-events/run b/test/1931-monitor-events/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1931-monitor-events/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+./default-run "$@" --jvmti
diff --git a/test/1931-monitor-events/src/Main.java b/test/1931-monitor-events/src/Main.java
new file mode 100644
index 0000000..81c9d2c
--- /dev/null
+++ b/test/1931-monitor-events/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1931.run();
+ }
+}
diff --git a/test/1931-monitor-events/src/art/Monitors.java b/test/1931-monitor-events/src/art/Monitors.java
new file mode 100644
index 0000000..b28a3ee
--- /dev/null
+++ b/test/1931-monitor-events/src/art/Monitors.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.*;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Monitors {
+ public native static void setupMonitorEvents(
+ Class<?> method_klass,
+ Method monitor_contended_enter_event,
+ Method monitor_contended_entered_event,
+ Method monitor_wait_event,
+ Method monitor_waited_event,
+ Class<?> lock_klass,
+ Thread thr);
+ public native static void stopMonitorEvents();
+
+ public static class NamedLock {
+ public final String name;
+ public NamedLock(String name) {
+ this.name = name;
+ }
+ public String toString() {
+ return String.format("NamedLock[%s]", name);
+ }
+ }
+
+ public static final class MonitorUsage {
+ public final Object monitor;
+ public final Thread owner;
+ public final int entryCount;
+ public final Thread[] waiters;
+ public final Thread[] notifyWaiters;
+
+ public MonitorUsage(
+ Object monitor,
+ Thread owner,
+ int entryCount,
+ Thread[] waiters,
+ Thread[] notifyWaiters) {
+ this.monitor = monitor;
+ this.entryCount = entryCount;
+ this.owner = owner;
+ this.waiters = waiters;
+ this.notifyWaiters = notifyWaiters;
+ }
+
+ private static String toNameList(Thread[] ts) {
+ return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray());
+ }
+
+ public String toString() {
+ return String.format(
+ "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }",
+ monitor,
+ (owner != null) ? owner.getName() : "<NULL>",
+ entryCount,
+ toNameList(waiters),
+ toNameList(notifyWaiters));
+ }
+ }
+
+ public static native MonitorUsage getObjectMonitorUsage(Object monitor);
+ public static native Object getCurrentContendedMonitor(Thread thr);
+
+ public static class TestException extends Error {
+ public TestException() { super(); }
+ public TestException(String s) { super(s); }
+ public TestException(String s, Throwable c) { super(s, c); }
+ }
+
+ public static class LockController {
+ private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT }
+
+ public final Object lock;
+ public final long timeout;
+ private final AtomicStampedReference<Action> action;
+ private volatile Thread runner = null;
+ private volatile boolean started = false;
+ private volatile boolean held = false;
+ private static final AtomicInteger cnt = new AtomicInteger(0);
+ private volatile Throwable exe;
+
+ public LockController(Object lock) {
+ this(lock, 10 * 1000);
+ }
+ public LockController(Object lock, long timeout) {
+ this.lock = lock;
+ this.timeout = timeout;
+ this.action = new AtomicStampedReference(Action.HOLD, 0);
+ this.exe = null;
+ }
+
+ public boolean IsWorkerThread(Thread thd) {
+ return Objects.equals(runner, thd);
+ }
+
+ public boolean IsLocked() {
+ checkException();
+ return held;
+ }
+
+ public void checkException() {
+ if (exe != null) {
+ throw new TestException("Exception thrown by other thread!", exe);
+ }
+ }
+
+ private void setAction(Action a) {
+ int stamp = action.getStamp();
+ // Wait for it to be HOLD before updating.
+ while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) {
+ stamp = action.getStamp();
+ }
+ }
+
+ public synchronized void suspendWorker() throws Exception {
+ checkException();
+ if (runner == null) {
+ throw new TestException("We don't have any runner holding " + lock);
+ }
+ Suspension.suspend(runner);
+ }
+
+ public Object getWorkerContendedMonitor() throws Exception {
+ checkException();
+ if (runner == null) {
+ return null;
+ }
+ return getCurrentContendedMonitor(runner);
+ }
+
+ public synchronized void DoLock() {
+ if (IsLocked()) {
+ throw new Error("lock is already acquired or being acquired.");
+ }
+ if (runner != null) {
+ throw new Error("Already have thread!");
+ }
+ runner = new Thread(() -> {
+ started = true;
+ try {
+ synchronized (lock) {
+ held = true;
+ int[] stamp_h = new int[] { -1 };
+ Action cur_action = Action.HOLD;
+ try {
+ while (true) {
+ cur_action = action.get(stamp_h);
+ int stamp = stamp_h[0];
+ if (cur_action == Action.RELEASE) {
+ // The other thread will deal with reseting action.
+ break;
+ }
+ try {
+ switch (cur_action) {
+ case HOLD:
+ Thread.yield();
+ break;
+ case NOTIFY:
+ lock.notify();
+ break;
+ case NOTIFY_ALL:
+ lock.notifyAll();
+ break;
+ case TIMED_WAIT:
+ lock.wait(timeout);
+ break;
+ case WAIT:
+ lock.wait();
+ break;
+ default:
+ throw new Error("Unknown action " + action);
+ }
+ } finally {
+ // reset action back to hold if it isn't something else.
+ action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1);
+ }
+ }
+ } catch (Exception e) {
+ throw new TestException("Got an error while performing action " + cur_action, e);
+ }
+ }
+ } finally {
+ held = false;
+ started = false;
+ }
+ }, "Locker thread " + cnt.getAndIncrement() + " for " + lock);
+ // Make sure we can get any exceptions this throws.
+ runner.setUncaughtExceptionHandler((t, e) -> { exe = e; });
+ runner.start();
+ }
+
+ public void waitForLockToBeHeld() throws Exception {
+ while (true) {
+ if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) {
+ return;
+ }
+ }
+ }
+
+ public synchronized void waitForNotifySleep() throws Exception {
+ if (runner == null) {
+ throw new Error("No thread trying to lock!");
+ }
+ do {
+ checkException();
+ } while (!started ||
+ !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner));
+ }
+
+ public synchronized void waitForContendedSleep() throws Exception {
+ if (runner == null) {
+ throw new Error("No thread trying to lock!");
+ }
+ do {
+ checkException();
+ } while (!started ||
+ runner.getState() != Thread.State.BLOCKED ||
+ !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner));
+ }
+
+ public synchronized void DoNotify() {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.NOTIFY);
+ }
+
+ public synchronized void DoNotifyAll() {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.NOTIFY_ALL);
+ }
+
+ public synchronized void DoTimedWait() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.TIMED_WAIT);
+ }
+
+ public synchronized void DoWait() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.WAIT);
+ }
+
+ public synchronized void interruptWorker() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ runner.interrupt();
+ }
+
+ public synchronized void waitForActionToFinish() throws Exception {
+ checkException();
+ while (action.getReference() != Action.HOLD) { checkException(); }
+ }
+
+ public synchronized void DoUnlock() throws Exception {
+ Error throwing = null;
+ if (!IsLocked()) {
+ // We might just be racing some exception that was thrown by the worker thread. Cache the
+ // exception, we will throw one from the worker before this one.
+ throwing = new Error("Not locked!");
+ }
+ setAction(Action.RELEASE);
+ Thread run = runner;
+ runner = null;
+ while (held) {}
+ run.join();
+ action.set(Action.HOLD, 0);
+ // Make sure to throw any exception that occurred since it might not have unlocked due to our
+ // request.
+ checkException();
+ DoCleanup();
+ if (throwing != null) {
+ throw throwing;
+ }
+ }
+
+ public synchronized void DoCleanup() throws Exception {
+ if (runner != null) {
+ Thread run = runner;
+ runner = null;
+ while (held) {}
+ run.join();
+ }
+ action.set(Action.HOLD, 0);
+ exe = null;
+ }
+ }
+}
+
diff --git a/test/1931-monitor-events/src/art/Suspension.java b/test/1931-monitor-events/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1931-monitor-events/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+public class Suspension {
+ // Suspends a thread using jvmti.
+ public native static void suspend(Thread thr);
+
+ // Resumes a thread using jvmti.
+ public native static void resume(Thread thr);
+
+ public native static boolean isSuspended(Thread thr);
+
+ public native static int[] suspendList(Thread... threads);
+ public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1931-monitor-events/src/art/Test1931.java b/test/1931-monitor-events/src/art/Test1931.java
new file mode 100644
index 0000000..ccefede
--- /dev/null
+++ b/test/1931-monitor-events/src/art/Test1931.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.*;
+import java.util.ListIterator;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class Test1931 {
+ public static void printStackTrace(Throwable t) {
+ System.out.println("Caught exception: " + t);
+ for (Throwable c = t.getCause(); c != null; c = c.getCause()) {
+ System.out.println("\tCaused by: " +
+ (Test1931.class.getPackage().equals(c.getClass().getPackage())
+ ? c.toString() : c.getClass().toString()));
+ }
+ }
+
+ public static void handleMonitorEnter(Thread thd, Object lock) {
+ System.out.println(thd.getName() + " contended-LOCKING " + lock);
+ }
+
+ public static void handleMonitorEntered(Thread thd, Object lock) {
+ System.out.println(thd.getName() + " LOCKED " + lock);
+ }
+ public static void handleMonitorWait(Thread thd, Object lock, long timeout) {
+ System.out.println(thd.getName() + " start-monitor-wait " + lock + " timeout: " + timeout);
+ }
+
+ public static void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) {
+ System.out.println(thd.getName() + " monitor-waited " + lock + " timed_out: " + timed_out);
+ }
+
+ public static void run() throws Exception {
+ Monitors.setupMonitorEvents(
+ Test1931.class,
+ Test1931.class.getDeclaredMethod("handleMonitorEnter", Thread.class, Object.class),
+ Test1931.class.getDeclaredMethod("handleMonitorEntered", Thread.class, Object.class),
+ Test1931.class.getDeclaredMethod("handleMonitorWait",
+ Thread.class, Object.class, Long.TYPE),
+ Test1931.class.getDeclaredMethod("handleMonitorWaited",
+ Thread.class, Object.class, Boolean.TYPE),
+ Monitors.NamedLock.class,
+ null);
+
+ System.out.println("Testing contended locking.");
+ testLock(new Monitors.NamedLock("Lock testLock"));
+
+ System.out.println("Testing monitor wait.");
+ testWait(new Monitors.NamedLock("Lock testWait"));
+
+ System.out.println("Testing monitor timed wait.");
+ testTimedWait(new Monitors.NamedLock("Lock testTimedWait"));
+
+ System.out.println("Testing monitor timed with timeout.");
+ testTimedWaitTimeout(new Monitors.NamedLock("Lock testTimedWaitTimeout"));
+
+ // TODO It would be good (but annoying) to do this with jasmin/smali in order to test if it's
+ // different without the reflection.
+ System.out.println("Waiting on an unlocked monitor.");
+ testUnlockedWait(new Monitors.NamedLock("Lock testUnlockedWait"));
+
+ System.out.println("Waiting with an illegal argument (negative timeout)");
+ testIllegalWait(new Monitors.NamedLock("Lock testIllegalWait"));
+
+ System.out.println("Interrupt a monitor being waited on.");
+ testInteruptWait(new Monitors.NamedLock("Lock testInteruptWait"));
+ }
+
+ public static void testInteruptWait(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ try {
+ controller1.interruptWorker();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printStackTrace(e);
+ }
+ controller1.DoCleanup();
+ }
+
+ public static void testIllegalWait(final Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk, /*timed_wait time*/-100);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller1.DoTimedWait();
+ controller1.waitForNotifySleep();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printStackTrace(e);
+ }
+ controller1.DoCleanup();
+ }
+
+ public static void testUnlockedWait(final Monitors.NamedLock lk) throws Exception {
+ synchronized (lk) {
+ Thread thd = new Thread(() -> {
+ try {
+ Method m = Object.class.getDeclaredMethod("wait");
+ m.invoke(lk);
+ } catch (Exception e) {
+ printStackTrace(e);
+ }
+ }, "Unlocked wait thread:");
+ thd.start();
+ thd.join();
+ }
+ }
+
+ public static void testLock(Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller2.DoLock();
+ if (controller2.IsLocked()) {
+ throw new Exception("c2 was able to gain lock while it was held by c1");
+ }
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testWait(Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoNotifyAll();
+ controller2.DoUnlock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ }
+
+ public static void testTimedWait(Monitors.NamedLock lk) throws Exception {
+ // Time to wait (1 hour). We will wake it up before timeout.
+ final long millis = 60l * 60l * 1000l;
+ Monitors.LockController controller1 = new Monitors.LockController(lk, millis);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoTimedWait();
+ controller1.waitForNotifySleep();
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoNotifyAll();
+ controller2.DoUnlock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ }
+
+ public static void testTimedWaitTimeout(Monitors.NamedLock lk) throws Exception {
+ // Time to wait (10 seconds). We will wait for the timeout.
+ final long millis = 10l * 1000l;
+ Monitors.LockController controller1 = new Monitors.LockController(lk, millis);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ System.out.println("Waiting for 10 seconds.");
+ controller1.DoTimedWait();
+ controller1.waitForNotifySleep();
+ controller1.DoUnlock();
+ System.out.println("Wait finished with timeout.");
+ }
+}
diff --git a/test/1932-monitor-events-misc/check b/test/1932-monitor-events-misc/check
new file mode 100644
index 0000000..8a84388
--- /dev/null
+++ b/test/1932-monitor-events-misc/check
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# Copyright (C) 2017 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.
+
+# The RI sends an extra event that art doesn't. Add it to the expected output.
+if [[ "$TEST_RUNTIME" == "jvm" ]]; then
+ patch -p0 expected.txt < jvm-expected.patch >/dev/null
+fi
+
+./default-check "$@"
diff --git a/test/1932-monitor-events-misc/expected.txt b/test/1932-monitor-events-misc/expected.txt
new file mode 100644
index 0000000..b33aa7d
--- /dev/null
+++ b/test/1932-monitor-events-misc/expected.txt
@@ -0,0 +1,104 @@
+Testing contended locking where lock is released before callback ends.
+Locker thread 1 for NamedLock[Lock testLockUncontend] contended-LOCKING NamedLock[Lock testLockUncontend]
+Releasing NamedLock[Lock testLockUncontend] during monitorEnter event.
+Locker thread 1 for NamedLock[Lock testLockUncontend] LOCKED NamedLock[Lock testLockUncontend]
+Testing throwing exceptions in monitor_enter
+Locker thread 3 for NamedLock[Lock testLockThrowEnter] contended-LOCKING NamedLock[Lock testLockThrowEnter]
+Throwing exception in MonitorEnter
+Locker thread 3 for NamedLock[Lock testLockThrowEnter] LOCKED NamedLock[Lock testLockThrowEnter]
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorEnter of NamedLock[Lock testLockThrowEnter]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowEnter], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exceptions in monitor_entered
+Locker thread 5 for NamedLock[Lock testLockThrowEntered] contended-LOCKING NamedLock[Lock testLockThrowEntered]
+Locker thread 5 for NamedLock[Lock testLockThrowEntered] LOCKED NamedLock[Lock testLockThrowEntered]
+Throwing exception in MonitorEntered
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[Lock testLockThrowEntered]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowEntered], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exceptions in both monitorEnter & MonitorEntered
+Locker thread 7 for NamedLock[Lock testLockThrowBoth] contended-LOCKING NamedLock[Lock testLockThrowBoth]
+Throwing exception in MonitorEnter
+Locker thread 7 for NamedLock[Lock testLockThrowBoth] LOCKED NamedLock[Lock testLockThrowBoth]
+Throwing exception in MonitorEntered
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[Lock testLockThrowBoth]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testLockThrowBoth], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWait event
+Locker thread 8 for NamedLock[Lock testThrowWait] start-monitor-wait NamedLock[Lock testThrowWait] timeout: 0
+Throwing exception in MonitorWait
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during MonitorWait of NamedLock[Lock testThrowWait]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWait event with illegal aruments
+Locker thread 9 for NamedLock[Lock testThrowIllegalWait] start-monitor-wait NamedLock[Lock testThrowIllegalWait] timeout: -100000
+Throwing exception in MonitorWait timeout = -100000
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorWait of NamedLock[Lock testThrowIllegalWait]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowIllegalWait], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event
+Locker thread 10 for NamedLock[Lock testThrowWaited] start-monitor-wait NamedLock[Lock testThrowWaited] timeout: 0
+Locker thread 10 for NamedLock[Lock testThrowWaited] monitor-waited NamedLock[Lock testThrowWaited] timed_out: false
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaited]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaited], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event caused by timeout
+Locker thread 12 for NamedLock[Lock testThrowWaitedTimeout] start-monitor-wait NamedLock[Lock testThrowWaitedTimeout] timeout: 5000
+Locker thread 12 for NamedLock[Lock testThrowWaitedTimeout] monitor-waited NamedLock[Lock testThrowWaitedTimeout] timed_out: true
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaitedTimeout]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaitedTimeout], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing throwing exception in MonitorWaited event caused by interrupt
+Locker thread 13 for NamedLock[Lock testThrowWaitedInterrupt] start-monitor-wait NamedLock[Lock testThrowWaitedInterrupt] timeout: 0
+Locker thread 13 for NamedLock[Lock testThrowWaitedInterrupt] monitor-waited NamedLock[Lock testThrowWaitedInterrupt] timed_out: false
+Throwing exception in MonitorWaited
+Caught exception: art.Monitors$TestException: Exception thrown by other thread!
+ Caused by: art.Monitors$TestException: throwing exception during monitorWaited of NamedLock[Lock testThrowWaitedInterrupt]
+lock state is: MonitorUsage{ monitor: NamedLock[Lock testThrowWaitedInterrupt], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing ObjectMonitorInfo inside of events
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] contended-LOCKING NamedLock[Lock testMonitorInfoInEvents]
+Monitor usage in MonitorEnter: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 14 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] LOCKED NamedLock[Lock testMonitorInfoInEvents]
+Monitor usage in MonitorEntered: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] start-monitor-wait NamedLock[Lock testMonitorInfoInEvents] timeout: 0
+Monitor usage in MonitorWait: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents], entryCount: 1, waiters: [], notify_waiters: [] }
+Locker thread 15 for NamedLock[Lock testMonitorInfoInEvents] monitor-waited NamedLock[Lock testMonitorInfoInEvents] timed_out: false
+Monitor usage in MonitorWaited: MonitorUsage{ monitor: NamedLock[Lock testMonitorInfoInEvents], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+Testing that the monitor can be stolen during the MonitorWaited event.
+Locker thread 17 for NamedLock[test testWaitEnterInterleaving] start-monitor-wait NamedLock[test testWaitEnterInterleaving] timeout: 0
+Locker thread 17 for NamedLock[test testWaitEnterInterleaving] monitor-waited NamedLock[test testWaitEnterInterleaving] timed_out: false
+locking controller3 in controller2 MonitorWaited event
+Controller3 now holds the lock the monitor wait will try to re-acquire
+Testing that we can lock and release the monitor in the MonitorWait event
+Locker thread 20 for NamedLock[test testWaitMonitorEnter] start-monitor-wait NamedLock[test testWaitMonitorEnter] timeout: 0
+In wait monitor usage: MonitorUsage{ monitor: NamedLock[test testWaitMonitorEnter], owner: Locker thread 20 for NamedLock[test testWaitMonitorEnter], entryCount: 1, waiters: [], notify_waiters: [] }
+In wait monitor usage sync: MonitorUsage{ monitor: NamedLock[test testWaitMonitorEnter], owner: Locker thread 20 for NamedLock[test testWaitMonitorEnter], entryCount: 2, waiters: [], notify_waiters: [] }
+Locker thread 20 for NamedLock[test testWaitMonitorEnter] monitor-waited NamedLock[test testWaitMonitorEnter] timed_out: false
+Testing that we can lock and release the monitor in the MonitorWaited event
+Locker thread 22 for NamedLock[test testWaitedMonitorEnter] start-monitor-wait NamedLock[test testWaitedMonitorEnter] timeout: 0
+Locker thread 22 for NamedLock[test testWaitedMonitorEnter] monitor-waited NamedLock[test testWaitedMonitorEnter] timed_out: false
+In waited monitor usage: MonitorUsage{ monitor: NamedLock[test testWaitedMonitorEnter], owner: <NULL>, entryCount: 0, waiters: [], notify_waiters: [] }
+In waited monitor usage sync: MonitorUsage{ monitor: NamedLock[test testWaitedMonitorEnter], owner: Locker thread 22 for NamedLock[test testWaitedMonitorEnter], entryCount: 1, waiters: [], notify_waiters: [] }
+Testing we can perform recursive lock in MonitorEntered
+Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock] contended-LOCKING NamedLock[test testRecursiveMontiorEnteredLock]
+Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock] LOCKED NamedLock[test testRecursiveMontiorEnteredLock]
+In MonitorEntered usage: MonitorUsage{ monitor: NamedLock[test testRecursiveMontiorEnteredLock], owner: Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock], entryCount: 1, waiters: [], notify_waiters: [] }
+In MonitorEntered sync: MonitorUsage{ monitor: NamedLock[test testRecursiveMontiorEnteredLock], owner: Locker thread 25 for NamedLock[test testRecursiveMontiorEnteredLock], entryCount: 2, waiters: [], notify_waiters: [] }
+Testing the lock state if MonitorEnter throws in a native method
+NativeLockStateThrowEnter thread contended-LOCKING NamedLock[test testNativeLockStateThrowEnter]
+Unlocking controller1 in MonitorEnter
+Throwing exception in MonitorEnter
+NativeLockStateThrowEnter thread LOCKED NamedLock[test testNativeLockStateThrowEnter]
+MonitorEnter returned: -1
+Lock state is: MonitorUsage{ monitor: NamedLock[test testNativeLockStateThrowEnter], owner: NativeLockStateThrowEnter thread, entryCount: 1, waiters: [], notify_waiters: [] }
+Caught exception: art.Monitors$TestException: throwing exception during monitorEnter of NamedLock[test testNativeLockStateThrowEnter]
+Testing the lock state if MonitorEntered throws in a native method
+NativeLockStateThrowEntered thread contended-LOCKING NamedLock[test testNativeLockStateThrowEntered]
+Unlocking controller1 in MonitorEnter
+NativeLockStateThrowEntered thread LOCKED NamedLock[test testNativeLockStateThrowEntered]
+Throwing exception in MonitorEntered
+MonitorEnter returned: -1
+Lock state is: MonitorUsage{ monitor: NamedLock[test testNativeLockStateThrowEntered], owner: NativeLockStateThrowEntered thread, entryCount: 1, waiters: [], notify_waiters: [] }
+Caught exception: art.Monitors$TestException: throwing exception during monitorEntered of NamedLock[test testNativeLockStateThrowEntered]
diff --git a/test/1932-monitor-events-misc/info.txt b/test/1932-monitor-events-misc/info.txt
new file mode 100644
index 0000000..674ef56
--- /dev/null
+++ b/test/1932-monitor-events-misc/info.txt
@@ -0,0 +1,4 @@
+Tests jvmti monitor events in odd situations.
+
+Checks that the JVMTI monitor events are correctly dispatched and handled for
+many odd situations.
diff --git a/test/1932-monitor-events-misc/jvm-expected.patch b/test/1932-monitor-events-misc/jvm-expected.patch
new file mode 100644
index 0000000..f6b2285
--- /dev/null
+++ b/test/1932-monitor-events-misc/jvm-expected.patch
@@ -0,0 +1,2 @@
+29a30
+> Locker thread 8 for NamedLock[Lock testThrowWait] monitor-waited NamedLock[Lock testThrowWait] timed_out: false
diff --git a/test/1932-monitor-events-misc/monitor_misc.cc b/test/1932-monitor-events-misc/monitor_misc.cc
new file mode 100644
index 0000000..842c550
--- /dev/null
+++ b/test/1932-monitor-events-misc/monitor_misc.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <pthread.h>
+
+#include <cstdio>
+#include <iostream>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "jvmti.h"
+
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1932MonitorEventsMisc {
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test1932_doNativeLockPrint(JNIEnv* env,
+ jclass klass,
+ jobject lock) {
+ // jobject atomic_boolean) {
+ // ScopedLocalRef<jclass> atomic_klass(env, env->FindClass("java/util/concurrent/AtomicBoolean"));
+ // if (env->ExceptionCheck()) {
+ // return;
+ // }
+ // jmethodID atomic_set = env->GetMethodID(atomic_klass.get(), "set", "(z)V");
+ jmethodID print_state = env->GetStaticMethodID(
+ klass, "printLockState", "(Lart/Monitors$NamedLock;Ljava/lang/Object;I)V");
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ jint res = env->MonitorEnter(lock);
+ ScopedLocalRef<jobject> exc(env, env->ExceptionOccurred());
+ env->ExceptionClear();
+ env->CallStaticVoidMethod(klass, print_state, lock, exc.get(), res);
+ env->MonitorExit(lock);
+}
+
+} // namespace Test1932MonitorEventsMisc
+} // namespace art
diff --git a/test/1932-monitor-events-misc/run b/test/1932-monitor-events-misc/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1932-monitor-events-misc/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+./default-run "$@" --jvmti
diff --git a/test/1932-monitor-events-misc/src/Main.java b/test/1932-monitor-events-misc/src/Main.java
new file mode 100644
index 0000000..0100074
--- /dev/null
+++ b/test/1932-monitor-events-misc/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1932.run();
+ }
+}
diff --git a/test/1932-monitor-events-misc/src/art/Monitors.java b/test/1932-monitor-events-misc/src/art/Monitors.java
new file mode 100644
index 0000000..b28a3ee
--- /dev/null
+++ b/test/1932-monitor-events-misc/src/art/Monitors.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.*;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Monitors {
+ public native static void setupMonitorEvents(
+ Class<?> method_klass,
+ Method monitor_contended_enter_event,
+ Method monitor_contended_entered_event,
+ Method monitor_wait_event,
+ Method monitor_waited_event,
+ Class<?> lock_klass,
+ Thread thr);
+ public native static void stopMonitorEvents();
+
+ public static class NamedLock {
+ public final String name;
+ public NamedLock(String name) {
+ this.name = name;
+ }
+ public String toString() {
+ return String.format("NamedLock[%s]", name);
+ }
+ }
+
+ public static final class MonitorUsage {
+ public final Object monitor;
+ public final Thread owner;
+ public final int entryCount;
+ public final Thread[] waiters;
+ public final Thread[] notifyWaiters;
+
+ public MonitorUsage(
+ Object monitor,
+ Thread owner,
+ int entryCount,
+ Thread[] waiters,
+ Thread[] notifyWaiters) {
+ this.monitor = monitor;
+ this.entryCount = entryCount;
+ this.owner = owner;
+ this.waiters = waiters;
+ this.notifyWaiters = notifyWaiters;
+ }
+
+ private static String toNameList(Thread[] ts) {
+ return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray());
+ }
+
+ public String toString() {
+ return String.format(
+ "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }",
+ monitor,
+ (owner != null) ? owner.getName() : "<NULL>",
+ entryCount,
+ toNameList(waiters),
+ toNameList(notifyWaiters));
+ }
+ }
+
+ public static native MonitorUsage getObjectMonitorUsage(Object monitor);
+ public static native Object getCurrentContendedMonitor(Thread thr);
+
+ public static class TestException extends Error {
+ public TestException() { super(); }
+ public TestException(String s) { super(s); }
+ public TestException(String s, Throwable c) { super(s, c); }
+ }
+
+ public static class LockController {
+ private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT }
+
+ public final Object lock;
+ public final long timeout;
+ private final AtomicStampedReference<Action> action;
+ private volatile Thread runner = null;
+ private volatile boolean started = false;
+ private volatile boolean held = false;
+ private static final AtomicInteger cnt = new AtomicInteger(0);
+ private volatile Throwable exe;
+
+ public LockController(Object lock) {
+ this(lock, 10 * 1000);
+ }
+ public LockController(Object lock, long timeout) {
+ this.lock = lock;
+ this.timeout = timeout;
+ this.action = new AtomicStampedReference(Action.HOLD, 0);
+ this.exe = null;
+ }
+
+ public boolean IsWorkerThread(Thread thd) {
+ return Objects.equals(runner, thd);
+ }
+
+ public boolean IsLocked() {
+ checkException();
+ return held;
+ }
+
+ public void checkException() {
+ if (exe != null) {
+ throw new TestException("Exception thrown by other thread!", exe);
+ }
+ }
+
+ private void setAction(Action a) {
+ int stamp = action.getStamp();
+ // Wait for it to be HOLD before updating.
+ while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) {
+ stamp = action.getStamp();
+ }
+ }
+
+ public synchronized void suspendWorker() throws Exception {
+ checkException();
+ if (runner == null) {
+ throw new TestException("We don't have any runner holding " + lock);
+ }
+ Suspension.suspend(runner);
+ }
+
+ public Object getWorkerContendedMonitor() throws Exception {
+ checkException();
+ if (runner == null) {
+ return null;
+ }
+ return getCurrentContendedMonitor(runner);
+ }
+
+ public synchronized void DoLock() {
+ if (IsLocked()) {
+ throw new Error("lock is already acquired or being acquired.");
+ }
+ if (runner != null) {
+ throw new Error("Already have thread!");
+ }
+ runner = new Thread(() -> {
+ started = true;
+ try {
+ synchronized (lock) {
+ held = true;
+ int[] stamp_h = new int[] { -1 };
+ Action cur_action = Action.HOLD;
+ try {
+ while (true) {
+ cur_action = action.get(stamp_h);
+ int stamp = stamp_h[0];
+ if (cur_action == Action.RELEASE) {
+ // The other thread will deal with reseting action.
+ break;
+ }
+ try {
+ switch (cur_action) {
+ case HOLD:
+ Thread.yield();
+ break;
+ case NOTIFY:
+ lock.notify();
+ break;
+ case NOTIFY_ALL:
+ lock.notifyAll();
+ break;
+ case TIMED_WAIT:
+ lock.wait(timeout);
+ break;
+ case WAIT:
+ lock.wait();
+ break;
+ default:
+ throw new Error("Unknown action " + action);
+ }
+ } finally {
+ // reset action back to hold if it isn't something else.
+ action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1);
+ }
+ }
+ } catch (Exception e) {
+ throw new TestException("Got an error while performing action " + cur_action, e);
+ }
+ }
+ } finally {
+ held = false;
+ started = false;
+ }
+ }, "Locker thread " + cnt.getAndIncrement() + " for " + lock);
+ // Make sure we can get any exceptions this throws.
+ runner.setUncaughtExceptionHandler((t, e) -> { exe = e; });
+ runner.start();
+ }
+
+ public void waitForLockToBeHeld() throws Exception {
+ while (true) {
+ if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) {
+ return;
+ }
+ }
+ }
+
+ public synchronized void waitForNotifySleep() throws Exception {
+ if (runner == null) {
+ throw new Error("No thread trying to lock!");
+ }
+ do {
+ checkException();
+ } while (!started ||
+ !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner));
+ }
+
+ public synchronized void waitForContendedSleep() throws Exception {
+ if (runner == null) {
+ throw new Error("No thread trying to lock!");
+ }
+ do {
+ checkException();
+ } while (!started ||
+ runner.getState() != Thread.State.BLOCKED ||
+ !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner));
+ }
+
+ public synchronized void DoNotify() {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.NOTIFY);
+ }
+
+ public synchronized void DoNotifyAll() {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.NOTIFY_ALL);
+ }
+
+ public synchronized void DoTimedWait() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.TIMED_WAIT);
+ }
+
+ public synchronized void DoWait() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.WAIT);
+ }
+
+ public synchronized void interruptWorker() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ runner.interrupt();
+ }
+
+ public synchronized void waitForActionToFinish() throws Exception {
+ checkException();
+ while (action.getReference() != Action.HOLD) { checkException(); }
+ }
+
+ public synchronized void DoUnlock() throws Exception {
+ Error throwing = null;
+ if (!IsLocked()) {
+ // We might just be racing some exception that was thrown by the worker thread. Cache the
+ // exception, we will throw one from the worker before this one.
+ throwing = new Error("Not locked!");
+ }
+ setAction(Action.RELEASE);
+ Thread run = runner;
+ runner = null;
+ while (held) {}
+ run.join();
+ action.set(Action.HOLD, 0);
+ // Make sure to throw any exception that occurred since it might not have unlocked due to our
+ // request.
+ checkException();
+ DoCleanup();
+ if (throwing != null) {
+ throw throwing;
+ }
+ }
+
+ public synchronized void DoCleanup() throws Exception {
+ if (runner != null) {
+ Thread run = runner;
+ runner = null;
+ while (held) {}
+ run.join();
+ }
+ action.set(Action.HOLD, 0);
+ exe = null;
+ }
+ }
+}
+
diff --git a/test/1932-monitor-events-misc/src/art/Suspension.java b/test/1932-monitor-events-misc/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1932-monitor-events-misc/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+public class Suspension {
+ // Suspends a thread using jvmti.
+ public native static void suspend(Thread thr);
+
+ // Resumes a thread using jvmti.
+ public native static void resume(Thread thr);
+
+ public native static boolean isSuspended(Thread thr);
+
+ public native static int[] suspendList(Thread... threads);
+ public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1932-monitor-events-misc/src/art/Test1932.java b/test/1932-monitor-events-misc/src/art/Test1932.java
new file mode 100644
index 0000000..7f66884
--- /dev/null
+++ b/test/1932-monitor-events-misc/src/art/Test1932.java
@@ -0,0 +1,623 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+import java.util.concurrent.Semaphore;
+
+public class Test1932 {
+ public static final boolean PRINT_FULL_STACK_TRACE = false;
+ public static final boolean INCLUDE_ANDROID_ONLY_TESTS = false;
+
+ public static interface MonitorHandler {
+ public default void handleMonitorEnter(Thread thd, Object lock) {}
+ public default void handleMonitorEntered(Thread thd, Object lock) {}
+ public default void handleMonitorWait(Thread thd, Object lock, long timeout) {}
+ public default void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) {}
+ }
+
+ public static volatile MonitorHandler HANDLER = null;
+
+ public static void run() throws Exception {
+ Monitors.setupMonitorEvents(
+ Test1932.class,
+ Test1932.class.getDeclaredMethod("handleMonitorEnter", Thread.class, Object.class),
+ Test1932.class.getDeclaredMethod("handleMonitorEntered", Thread.class, Object.class),
+ Test1932.class.getDeclaredMethod("handleMonitorWait",
+ Thread.class, Object.class, Long.TYPE),
+ Test1932.class.getDeclaredMethod("handleMonitorWaited",
+ Thread.class, Object.class, Boolean.TYPE),
+ Monitors.NamedLock.class,
+ null);
+
+ System.out.println("Testing contended locking where lock is released before callback ends.");
+ testLockUncontend(new Monitors.NamedLock("Lock testLockUncontend"));
+
+ System.out.println("Testing throwing exceptions in monitor_enter");
+ testLockThrowEnter(new Monitors.NamedLock("Lock testLockThrowEnter"));
+
+ System.out.println("Testing throwing exceptions in monitor_entered");
+ testLockThrowEntered(new Monitors.NamedLock("Lock testLockThrowEntered"));
+
+ System.out.println("Testing throwing exceptions in both monitorEnter & MonitorEntered");
+ testLockThrowBoth(new Monitors.NamedLock("Lock testLockThrowBoth"));
+
+ // This exposes a difference between the RI and ART. On the RI this test will cause a
+ // JVMTI_EVENT_MONITOR_WAITED event to be sent even though we threw an exception during the
+ // JVMTI_EVENT_MONITOR_WAIT. See b/65558434.
+ System.out.println("Testing throwing exception in MonitorWait event");
+ testThrowWait(new Monitors.NamedLock("Lock testThrowWait"));
+
+ System.out.println("Testing throwing exception in MonitorWait event with illegal aruments");
+ testThrowIllegalWait(new Monitors.NamedLock("Lock testThrowIllegalWait"));
+
+ System.out.println("Testing throwing exception in MonitorWaited event");
+ testThrowWaited(new Monitors.NamedLock("Lock testThrowWaited"));
+
+ System.out.println("Testing throwing exception in MonitorWaited event caused by timeout");
+ testThrowWaitedTimeout(new Monitors.NamedLock("Lock testThrowWaitedTimeout"));
+
+ System.out.println("Testing throwing exception in MonitorWaited event caused by interrupt");
+ testThrowWaitedInterrupt(new Monitors.NamedLock("Lock testThrowWaitedInterrupt"));
+
+ System.out.println("Testing ObjectMonitorInfo inside of events");
+ testMonitorInfoInEvents(new Monitors.NamedLock("Lock testMonitorInfoInEvents"));
+
+ System.out.println("Testing that the monitor can be stolen during the MonitorWaited event.");
+ testWaitEnterInterleaving(new Monitors.NamedLock("test testWaitEnterInterleaving"));
+
+ // TODO We keep this here since it works on android but it's not clear it's behavior we want to
+ // support long term or at all.
+ if (INCLUDE_ANDROID_ONLY_TESTS) {
+ System.out.println(
+ "Testing that the monitor can be still held by notifier during the MonitorWaited " +
+ "event. NB This doesn't work on the RI.");
+ testWaitExitInterleaving(new Monitors.NamedLock("test testWaitExitInterleaving"));
+ }
+
+ System.out.println(
+ "Testing that we can lock and release the monitor in the MonitorWait event");
+ testWaitMonitorEnter(new Monitors.NamedLock("test testWaitMonitorEnter"));
+
+ System.out.println(
+ "Testing that we can lock and release the monitor in the MonitorWaited event");
+ testWaitedMonitorEnter(new Monitors.NamedLock("test testWaitedMonitorEnter"));
+
+ System.out.println("Testing we can perform recursive lock in MonitorEntered");
+ testRecursiveMontiorEnteredLock(new Monitors.NamedLock("test testRecursiveMontiorEnteredLock"));
+
+ System.out.println("Testing the lock state if MonitorEnter throws in a native method");
+ testNativeLockStateThrowEnter(new Monitors.NamedLock("test testNativeLockStateThrowEnter"));
+
+ System.out.println("Testing the lock state if MonitorEntered throws in a native method");
+ testNativeLockStateThrowEntered(new Monitors.NamedLock("test testNativeLockStateThrowEntered"));
+ }
+
+ public static native void doNativeLockPrint(Monitors.NamedLock lk);
+ public static void printLockState(Monitors.NamedLock lk, Object exception, int res) {
+ System.out.println(
+ "MonitorEnter returned: " + res + "\n" +
+ "Lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ printExceptions((Throwable)exception);
+ }
+
+ public static void testNativeLockStateThrowEnter(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread t, Object l) {
+ System.out.println("Unlocking controller1 in MonitorEnter");
+ try {
+ controller1.DoUnlock();
+ } catch (Exception e) {
+ throw new Monitors.TestException("Exception unlocking monitor in MonitorEnter " + l, e);
+ }
+ System.out.println("Throwing exception in MonitorEnter");
+ throw new Monitors.TestException("throwing exception during monitorEnter of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ Thread native_thd = new Thread(() -> {
+ try {
+ doNativeLockPrint(lk);
+ } catch (Throwable e) {
+ System.out.println("Unhandled exception: " + e);
+ e.printStackTrace();
+ }
+ }, "NativeLockStateThrowEnter thread");
+ native_thd.start();
+ native_thd.join();
+ }
+
+ public static void testNativeLockStateThrowEntered(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread t, Object l) {
+ System.out.println("Unlocking controller1 in MonitorEnter");
+ try {
+ controller1.DoUnlock();
+ } catch (Exception e) {
+ throw new Monitors.TestException("Exception unlocking monitor in MonitorEnter " + l, e);
+ }
+ }
+ @Override public void handleMonitorEntered(Thread t, Object l) {
+ System.out.println("Throwing exception in MonitorEntered");
+ throw new Monitors.TestException("throwing exception during monitorEntered of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ Thread native_thd = new Thread(() -> {
+ try {
+ doNativeLockPrint(lk);
+ } catch (Throwable e) {
+ System.out.println("Unhandled exception: " + e);
+ e.printStackTrace();
+ }
+ }, "NativeLockStateThrowEntered thread");
+ native_thd.start();
+ native_thd.join();
+ }
+
+ public static void testRecursiveMontiorEnteredLock(final Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEntered(Thread thd, Object l) {
+ try {
+ System.out.println("In MonitorEntered usage: " + Monitors.getObjectMonitorUsage(lk));
+ synchronized (lk) {
+ System.out.println("In MonitorEntered sync: " + Monitors.getObjectMonitorUsage(lk));
+ }
+ } catch (Exception e) {
+ throw new Monitors.TestException("error while recursive locking!", e);
+ }
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testWaitedMonitorEnter(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ try {
+ // make sure that controller2 has acutally unlocked everything, we can be sent earlier
+ // than that on ART.
+ while (controller2.IsLocked()) {}
+ System.out.println("In waited monitor usage: " + Monitors.getObjectMonitorUsage(lk));
+ synchronized (lk) {
+ System.out.println(
+ "In waited monitor usage sync: " + Monitors.getObjectMonitorUsage(lk));
+ }
+ } catch (Exception e) {
+ throw new Monitors.TestException("error while doing unlock in other thread!", e);
+ }
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoNotifyAll();
+ controller2.DoUnlock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ }
+
+ public static void testWaitMonitorEnter(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+ try {
+ System.out.println("In wait monitor usage: " + Monitors.getObjectMonitorUsage(lk));
+ synchronized (lk) {
+ System.out.println("In wait monitor usage sync: " + Monitors.getObjectMonitorUsage(lk));
+ }
+ } catch (Exception e) {
+ throw new Monitors.TestException("error while doing unlock in other thread!", e);
+ }
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoNotifyAll();
+ controller2.DoUnlock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ }
+
+ // NB This test cannot be run on the RI. It deadlocks. Leaving for documentation.
+ public static void testWaitExitInterleaving(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ System.out.println("un-locking controller1 in controller2 MonitorWaited event");
+ try {
+ controller1.DoUnlock();
+ } catch (Exception e) {
+ throw new Monitors.TestException("error while doing unlock in other thread!", e);
+ }
+ }
+ };
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoWait();
+ controller2.waitForNotifySleep();
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoNotifyAll();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testWaitEnterInterleaving(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ final Monitors.LockController controller3 = new Monitors.LockController(lk);
+ final Semaphore unlocked_sem = new Semaphore(0);
+ final Semaphore continue_sem = new Semaphore(0);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ System.out.println("locking controller3 in controller2 MonitorWaited event");
+ try {
+ unlocked_sem.acquire();
+ controller3.DoLock();
+ controller3.waitForLockToBeHeld();
+ System.out.println(
+ "Controller3 now holds the lock the monitor wait will try to re-acquire");
+ continue_sem.release();
+ } catch (Exception e) {
+ throw new Monitors.TestException("error while doing unlock in other thread!", e);
+ }
+ }
+ };
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoWait();
+ controller2.waitForNotifySleep();
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoNotifyAll();
+ controller1.DoUnlock();
+ // Wait for controller3 to have locked.
+ // We cannot use waitForLockToBeHeld since we could race with the HANDLER waitForLockToBeHeld
+ // function.
+ unlocked_sem.release();
+ continue_sem.acquire();
+ controller3.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testMonitorInfoInEvents(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread thd, Object l) {
+ System.out.println("Monitor usage in MonitorEnter: " + Monitors.getObjectMonitorUsage(l));
+ }
+ @Override public void handleMonitorEntered(Thread thd, Object l) {
+ System.out.println("Monitor usage in MonitorEntered: " + Monitors.getObjectMonitorUsage(l));
+ }
+ @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+ System.out.println("Monitor usage in MonitorWait: " + Monitors.getObjectMonitorUsage(l));
+ }
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ // make sure that controller1 has acutally unlocked everything, we can be sent earlier than
+ // that on ART.
+ while (controller1.IsLocked()) {}
+ System.out.println("Monitor usage in MonitorWaited: " + Monitors.getObjectMonitorUsage(l));
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoWait();
+ controller2.waitForNotifySleep();
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoNotifyAll();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testThrowWaitedInterrupt(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ System.out.println("Throwing exception in MonitorWaited");
+ throw new Monitors.TestException("throwing exception during monitorWaited of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ controller1.interruptWorker();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller1.DoCleanup();
+ }
+ }
+
+ public static void testThrowWaitedTimeout(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk, 5 * 1000);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ System.out.println("Throwing exception in MonitorWaited");
+ throw new Monitors.TestException("throwing exception during monitorWaited of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller1.DoTimedWait();
+ controller1.waitForNotifySleep();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller1.DoCleanup();
+ }
+ }
+
+ public static void testThrowWaited(Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWaited(Thread thd, Object l, boolean timeout) {
+ System.out.println("Throwing exception in MonitorWaited");
+ throw new Monitors.TestException("throwing exception during monitorWaited of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoNotifyAll();
+ controller2.DoUnlock();
+ try {
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller1.DoCleanup();
+ }
+ }
+
+ public static void testThrowWait(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+ System.out.println("Throwing exception in MonitorWait");
+ throw new Monitors.TestException("throwing exception during MonitorWait of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller1.DoCleanup();
+ }
+ }
+
+ public static void testThrowIllegalWait(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk, -100000);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorWait(Thread thd, Object l, long timeout) {
+ System.out.println("Throwing exception in MonitorWait timeout = " + timeout);
+ throw new Monitors.TestException("throwing exception during monitorWait of " + l);
+ }
+ };
+ try {
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoTimedWait();
+ controller1.waitForLockToBeHeld();
+ controller1.DoUnlock();
+ System.out.println("No Exception thrown!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller1.DoCleanup();
+ }
+ }
+
+ public static void testLockUncontend(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread thd, Object lock) {
+ if (controller1.IsLocked()) {
+ System.out.println("Releasing " + lk + " during monitorEnter event.");
+ try {
+ controller1.DoUnlock();
+ } catch (Exception e) {
+ throw new Error("Unable to unlock controller1", e);
+ }
+ } else {
+ throw new Error("controller1 does not seem to hold the lock!");
+ }
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ // This will call handleMonitorEnter but will release during the callback.
+ controller2.DoLock();
+ controller2.waitForLockToBeHeld();
+ if (controller1.IsLocked()) {
+ throw new Error("controller1 still holds the lock somehow!");
+ }
+ controller2.DoUnlock();
+ }
+
+ public static void testLockThrowEnter(Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread t, Object l) {
+ System.out.println("Throwing exception in MonitorEnter");
+ throw new Monitors.TestException("throwing exception during monitorEnter of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ System.out.println("Did not get an exception!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller2.DoCleanup();
+ }
+ }
+
+ public static void testLockThrowEntered(Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEntered(Thread t, Object l) {
+ System.out.println("Throwing exception in MonitorEntered");
+ throw new Monitors.TestException("throwing exception during monitorEntered of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ System.out.println("Did not get an exception!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller2.DoCleanup();
+ }
+ }
+
+ public static void testLockThrowBoth(Monitors.NamedLock lk) throws Exception {
+ Monitors.LockController controller1 = new Monitors.LockController(lk);
+ Monitors.LockController controller2 = new Monitors.LockController(lk);
+ HANDLER = new MonitorHandler() {
+ @Override public void handleMonitorEnter(Thread t, Object l) {
+ System.out.println("Throwing exception in MonitorEnter");
+ throw new Monitors.TestException("throwing exception during monitorEnter of " + l);
+ }
+ @Override public void handleMonitorEntered(Thread t, Object l) {
+ System.out.println("Throwing exception in MonitorEntered");
+ throw new Monitors.TestException("throwing exception during monitorEntered of " + l);
+ }
+ };
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ try {
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ System.out.println("Did not get an exception!");
+ } catch (Monitors.TestException e) {
+ printExceptions(e);
+ System.out.println("lock state is: " + Monitors.getObjectMonitorUsage(lk));
+ controller2.DoCleanup();
+ }
+ }
+
+ public static void printExceptions(Throwable t) {
+ System.out.println("Caught exception: " + t);
+ for (Throwable c = t.getCause(); c != null; c = c.getCause()) {
+ System.out.println("\tCaused by: " +
+ (Test1932.class.getPackage().equals(c.getClass().getPackage())
+ ? c.toString() : c.getClass().toString()));
+ }
+ if (PRINT_FULL_STACK_TRACE) {
+ t.printStackTrace();
+ }
+ }
+
+ public static void handleMonitorEnter(Thread thd, Object lock) {
+ System.out.println(thd.getName() + " contended-LOCKING " + lock);
+ if (HANDLER != null) {
+ HANDLER.handleMonitorEnter(thd, lock);
+ }
+ }
+
+ public static void handleMonitorEntered(Thread thd, Object lock) {
+ System.out.println(thd.getName() + " LOCKED " + lock);
+ if (HANDLER != null) {
+ HANDLER.handleMonitorEntered(thd, lock);
+ }
+ }
+ public static void handleMonitorWait(Thread thd, Object lock, long timeout) {
+ System.out.println(thd.getName() + " start-monitor-wait " + lock + " timeout: " + timeout);
+ if (HANDLER != null) {
+ HANDLER.handleMonitorWait(thd, lock, timeout);
+ }
+ }
+
+ public static void handleMonitorWaited(Thread thd, Object lock, boolean timed_out) {
+ System.out.println(thd.getName() + " monitor-waited " + lock + " timed_out: " + timed_out);
+ if (HANDLER != null) {
+ HANDLER.handleMonitorWaited(thd, lock, timed_out);
+ }
+ }
+}
diff --git a/test/1933-monitor-current-contended/expected.txt b/test/1933-monitor-current-contended/expected.txt
new file mode 100644
index 0000000..d27c21c
--- /dev/null
+++ b/test/1933-monitor-current-contended/expected.txt
@@ -0,0 +1,6 @@
+No contention
+current thread is contending for monitor: null
+Normal contended monitor
+c2 is contending for monitor: NamedLock[test testNormalContendedMonitor]
+Waiting on a monitor
+c1 is contending for monitor: NamedLock[test testNormalWaitMonitor]
diff --git a/test/1933-monitor-current-contended/info.txt b/test/1933-monitor-current-contended/info.txt
new file mode 100644
index 0000000..674ef56
--- /dev/null
+++ b/test/1933-monitor-current-contended/info.txt
@@ -0,0 +1,4 @@
+Tests jvmti monitor events in odd situations.
+
+Checks that the JVMTI monitor events are correctly dispatched and handled for
+many odd situations.
diff --git a/test/1933-monitor-current-contended/run b/test/1933-monitor-current-contended/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/1933-monitor-current-contended/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2017 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.
+
+./default-run "$@" --jvmti
diff --git a/test/1933-monitor-current-contended/src/Main.java b/test/1933-monitor-current-contended/src/Main.java
new file mode 100644
index 0000000..3f2bbcd
--- /dev/null
+++ b/test/1933-monitor-current-contended/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ art.Test1933.run();
+ }
+}
diff --git a/test/1933-monitor-current-contended/src/art/Monitors.java b/test/1933-monitor-current-contended/src/art/Monitors.java
new file mode 100644
index 0000000..b28a3ee
--- /dev/null
+++ b/test/1933-monitor-current-contended/src/art/Monitors.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.atomic.*;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class Monitors {
+ public native static void setupMonitorEvents(
+ Class<?> method_klass,
+ Method monitor_contended_enter_event,
+ Method monitor_contended_entered_event,
+ Method monitor_wait_event,
+ Method monitor_waited_event,
+ Class<?> lock_klass,
+ Thread thr);
+ public native static void stopMonitorEvents();
+
+ public static class NamedLock {
+ public final String name;
+ public NamedLock(String name) {
+ this.name = name;
+ }
+ public String toString() {
+ return String.format("NamedLock[%s]", name);
+ }
+ }
+
+ public static final class MonitorUsage {
+ public final Object monitor;
+ public final Thread owner;
+ public final int entryCount;
+ public final Thread[] waiters;
+ public final Thread[] notifyWaiters;
+
+ public MonitorUsage(
+ Object monitor,
+ Thread owner,
+ int entryCount,
+ Thread[] waiters,
+ Thread[] notifyWaiters) {
+ this.monitor = monitor;
+ this.entryCount = entryCount;
+ this.owner = owner;
+ this.waiters = waiters;
+ this.notifyWaiters = notifyWaiters;
+ }
+
+ private static String toNameList(Thread[] ts) {
+ return Arrays.toString(Arrays.stream(ts).map((Thread t) -> t.getName()).toArray());
+ }
+
+ public String toString() {
+ return String.format(
+ "MonitorUsage{ monitor: %s, owner: %s, entryCount: %d, waiters: %s, notify_waiters: %s }",
+ monitor,
+ (owner != null) ? owner.getName() : "<NULL>",
+ entryCount,
+ toNameList(waiters),
+ toNameList(notifyWaiters));
+ }
+ }
+
+ public static native MonitorUsage getObjectMonitorUsage(Object monitor);
+ public static native Object getCurrentContendedMonitor(Thread thr);
+
+ public static class TestException extends Error {
+ public TestException() { super(); }
+ public TestException(String s) { super(s); }
+ public TestException(String s, Throwable c) { super(s, c); }
+ }
+
+ public static class LockController {
+ private static enum Action { HOLD, RELEASE, NOTIFY, NOTIFY_ALL, WAIT, TIMED_WAIT }
+
+ public final Object lock;
+ public final long timeout;
+ private final AtomicStampedReference<Action> action;
+ private volatile Thread runner = null;
+ private volatile boolean started = false;
+ private volatile boolean held = false;
+ private static final AtomicInteger cnt = new AtomicInteger(0);
+ private volatile Throwable exe;
+
+ public LockController(Object lock) {
+ this(lock, 10 * 1000);
+ }
+ public LockController(Object lock, long timeout) {
+ this.lock = lock;
+ this.timeout = timeout;
+ this.action = new AtomicStampedReference(Action.HOLD, 0);
+ this.exe = null;
+ }
+
+ public boolean IsWorkerThread(Thread thd) {
+ return Objects.equals(runner, thd);
+ }
+
+ public boolean IsLocked() {
+ checkException();
+ return held;
+ }
+
+ public void checkException() {
+ if (exe != null) {
+ throw new TestException("Exception thrown by other thread!", exe);
+ }
+ }
+
+ private void setAction(Action a) {
+ int stamp = action.getStamp();
+ // Wait for it to be HOLD before updating.
+ while (!action.compareAndSet(Action.HOLD, a, stamp, stamp + 1)) {
+ stamp = action.getStamp();
+ }
+ }
+
+ public synchronized void suspendWorker() throws Exception {
+ checkException();
+ if (runner == null) {
+ throw new TestException("We don't have any runner holding " + lock);
+ }
+ Suspension.suspend(runner);
+ }
+
+ public Object getWorkerContendedMonitor() throws Exception {
+ checkException();
+ if (runner == null) {
+ return null;
+ }
+ return getCurrentContendedMonitor(runner);
+ }
+
+ public synchronized void DoLock() {
+ if (IsLocked()) {
+ throw new Error("lock is already acquired or being acquired.");
+ }
+ if (runner != null) {
+ throw new Error("Already have thread!");
+ }
+ runner = new Thread(() -> {
+ started = true;
+ try {
+ synchronized (lock) {
+ held = true;
+ int[] stamp_h = new int[] { -1 };
+ Action cur_action = Action.HOLD;
+ try {
+ while (true) {
+ cur_action = action.get(stamp_h);
+ int stamp = stamp_h[0];
+ if (cur_action == Action.RELEASE) {
+ // The other thread will deal with reseting action.
+ break;
+ }
+ try {
+ switch (cur_action) {
+ case HOLD:
+ Thread.yield();
+ break;
+ case NOTIFY:
+ lock.notify();
+ break;
+ case NOTIFY_ALL:
+ lock.notifyAll();
+ break;
+ case TIMED_WAIT:
+ lock.wait(timeout);
+ break;
+ case WAIT:
+ lock.wait();
+ break;
+ default:
+ throw new Error("Unknown action " + action);
+ }
+ } finally {
+ // reset action back to hold if it isn't something else.
+ action.compareAndSet(cur_action, Action.HOLD, stamp, stamp+1);
+ }
+ }
+ } catch (Exception e) {
+ throw new TestException("Got an error while performing action " + cur_action, e);
+ }
+ }
+ } finally {
+ held = false;
+ started = false;
+ }
+ }, "Locker thread " + cnt.getAndIncrement() + " for " + lock);
+ // Make sure we can get any exceptions this throws.
+ runner.setUncaughtExceptionHandler((t, e) -> { exe = e; });
+ runner.start();
+ }
+
+ public void waitForLockToBeHeld() throws Exception {
+ while (true) {
+ if (IsLocked() && Objects.equals(runner, Monitors.getObjectMonitorUsage(lock).owner)) {
+ return;
+ }
+ }
+ }
+
+ public synchronized void waitForNotifySleep() throws Exception {
+ if (runner == null) {
+ throw new Error("No thread trying to lock!");
+ }
+ do {
+ checkException();
+ } while (!started ||
+ !Arrays.asList(Monitors.getObjectMonitorUsage(lock).notifyWaiters).contains(runner));
+ }
+
+ public synchronized void waitForContendedSleep() throws Exception {
+ if (runner == null) {
+ throw new Error("No thread trying to lock!");
+ }
+ do {
+ checkException();
+ } while (!started ||
+ runner.getState() != Thread.State.BLOCKED ||
+ !Arrays.asList(Monitors.getObjectMonitorUsage(lock).waiters).contains(runner));
+ }
+
+ public synchronized void DoNotify() {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.NOTIFY);
+ }
+
+ public synchronized void DoNotifyAll() {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.NOTIFY_ALL);
+ }
+
+ public synchronized void DoTimedWait() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.TIMED_WAIT);
+ }
+
+ public synchronized void DoWait() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ setAction(Action.WAIT);
+ }
+
+ public synchronized void interruptWorker() throws Exception {
+ if (!IsLocked()) {
+ throw new Error("Not locked");
+ }
+ runner.interrupt();
+ }
+
+ public synchronized void waitForActionToFinish() throws Exception {
+ checkException();
+ while (action.getReference() != Action.HOLD) { checkException(); }
+ }
+
+ public synchronized void DoUnlock() throws Exception {
+ Error throwing = null;
+ if (!IsLocked()) {
+ // We might just be racing some exception that was thrown by the worker thread. Cache the
+ // exception, we will throw one from the worker before this one.
+ throwing = new Error("Not locked!");
+ }
+ setAction(Action.RELEASE);
+ Thread run = runner;
+ runner = null;
+ while (held) {}
+ run.join();
+ action.set(Action.HOLD, 0);
+ // Make sure to throw any exception that occurred since it might not have unlocked due to our
+ // request.
+ checkException();
+ DoCleanup();
+ if (throwing != null) {
+ throw throwing;
+ }
+ }
+
+ public synchronized void DoCleanup() throws Exception {
+ if (runner != null) {
+ Thread run = runner;
+ runner = null;
+ while (held) {}
+ run.join();
+ }
+ action.set(Action.HOLD, 0);
+ exe = null;
+ }
+ }
+}
+
diff --git a/test/1933-monitor-current-contended/src/art/Suspension.java b/test/1933-monitor-current-contended/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1933-monitor-current-contended/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+public class Suspension {
+ // Suspends a thread using jvmti.
+ public native static void suspend(Thread thr);
+
+ // Resumes a thread using jvmti.
+ public native static void resume(Thread thr);
+
+ public native static boolean isSuspended(Thread thr);
+
+ public native static int[] suspendList(Thread... threads);
+ public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1933-monitor-current-contended/src/art/Test1933.java b/test/1933-monitor-current-contended/src/art/Test1933.java
new file mode 100644
index 0000000..e21c395
--- /dev/null
+++ b/test/1933-monitor-current-contended/src/art/Test1933.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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 art;
+
+public class Test1933 {
+ public static void run() throws Exception {
+ System.out.println("No contention");
+ testNoContention(new Monitors.NamedLock("test testNoContention"));
+
+ System.out.println("Normal contended monitor");
+ testNormalContendedMonitor(new Monitors.NamedLock("test testNormalContendedMonitor"));
+
+ System.out.println("Waiting on a monitor");
+ testNormalWaitMonitor(new Monitors.NamedLock("test testNormalWaitMonitor"));
+ }
+
+ public static void testNormalWaitMonitor(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller1.DoWait();
+ controller1.waitForNotifySleep();
+ System.out.println("c1 is contending for monitor: " + controller1.getWorkerContendedMonitor());
+ synchronized (lk) {
+ lk.notifyAll();
+ }
+ controller1.DoUnlock();
+ }
+
+ public static void testNormalContendedMonitor(final Monitors.NamedLock lk) throws Exception {
+ final Monitors.LockController controller1 = new Monitors.LockController(lk);
+ final Monitors.LockController controller2 = new Monitors.LockController(lk);
+ controller1.DoLock();
+ controller1.waitForLockToBeHeld();
+ controller2.DoLock();
+ controller2.waitForContendedSleep();
+ System.out.println("c2 is contending for monitor: " + controller2.getWorkerContendedMonitor());
+ controller1.DoUnlock();
+ controller2.waitForLockToBeHeld();
+ controller2.DoUnlock();
+ }
+
+ public static void testNoContention(final Monitors.NamedLock lk) throws Exception {
+ synchronized (lk) {
+ System.out.println("current thread is contending for monitor: " +
+ Monitors.getCurrentContendedMonitor(null));
+ }
+ }
+}
diff --git a/test/449-checker-bce/src/Main.java b/test/449-checker-bce/src/Main.java
index f466eea..60e653c 100644
--- a/test/449-checker-bce/src/Main.java
+++ b/test/449-checker-bce/src/Main.java
@@ -962,6 +962,25 @@
}
}
+ /// CHECK-START: void Main.modArrayIndex5(int[], int) BCE (before)
+ /// CHECK-DAG: BoundsCheck
+ /// CHECK-DAG: ArraySet
+ //
+ /// CHECK-START: void Main.modArrayIndex5(int[], int) BCE (after)
+ /// CHECK-NOT: BoundsCheck
+ /// CHECK-DAG: ArraySet
+ public static void modArrayIndex5(int[] x, int i) {
+ while (true) {
+ int xi = i % x.length;
+ if (xi < 0)
+ break;
+ if (i >= x.length)
+ break;
+ x[xi] = i;
+ i++;
+ }
+ }
+
/// CHECK-START: void Main.bubbleSort(int[]) GVN (before)
/// CHECK: BoundsCheck
/// CHECK: ArrayGet
@@ -1690,6 +1709,21 @@
sieve(20);
+ int[] x1 = new int[10];
+ int[] x2 = new int[10];
+ int[] x3 = new int[10];
+ modArrayIndex5(x1, -1);
+ modArrayIndex5(x2, 0);
+ modArrayIndex5(x3, 5);
+ for (int i = 0; i < 10; i++) {
+ int e1 = 0;
+ int e2 = i;
+ int e3 = i < 5 ? 0 : i;
+ if (x1[i] != e1 || x2[i] != e2 || x3[i] != e3) {
+ System.out.println("modarray failed!");
+ }
+ }
+
int[] array = {5, 2, 3, 7, 0, 1, 6, 4};
bubbleSort(array);
for (int i = 0; i < 8; i++) {
diff --git a/test/476-checker-ctor-fence-redun-elim/expected.txt b/test/476-checker-ctor-fence-redun-elim/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/476-checker-ctor-fence-redun-elim/expected.txt
diff --git a/test/476-checker-ctor-fence-redun-elim/info.txt b/test/476-checker-ctor-fence-redun-elim/info.txt
new file mode 100644
index 0000000..46d62f7
--- /dev/null
+++ b/test/476-checker-ctor-fence-redun-elim/info.txt
@@ -0,0 +1,2 @@
+Tests to ensure constructor fences (after new-instance, new-array, or final fields) are properly
+merged together by the compiler when they are redundant.
diff --git a/test/476-checker-ctor-fence-redun-elim/src/Main.java b/test/476-checker-ctor-fence-redun-elim/src/Main.java
new file mode 100644
index 0000000..05f2f7c
--- /dev/null
+++ b/test/476-checker-ctor-fence-redun-elim/src/Main.java
@@ -0,0 +1,844 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+// Baseline class. This has no final fields, so there are no additional freezes
+// in its constructor.
+//
+// The new-instance itself always has 1 freeze for the happens-before on the object header
+// write (i.e. [obj.class = X] happens-before any access to obj).
+//
+// Total freezes for "new Base()": 1.
+class Base {
+ int w0;
+ int w1;
+ int w2;
+ int w3;
+
+ @Override
+ public String toString() {
+ return getClass().getName() + "(" + baseString() + ")";
+ }
+
+ protected String baseString() {
+ return String.format("w0: %d, w1: %d, w2: %d, w3: %d", w0, w1, w2, w3);
+ }
+}
+
+// This has a final field in its constructor, so there must be a field freeze
+// at the end of <init>.
+//
+// Total freezes for "new OneFinal()": 2.
+class OneFinal extends Base {
+ final int x;
+ OneFinal(int x) {
+ this.x = x;
+ }
+
+ @Override
+ protected String baseString() {
+ return String.format("%s, x: %d", super.baseString(), x);
+ }
+}
+
+class Assert {
+ public static void stringEquals(String expected, Object actual) {
+ stringEquals$noinline$(expected, actual);
+ }
+
+ // Forbid compiler from inlining this to avoid overly clever optimizations.
+ private static void stringEquals$noinline$(String expected, Object actual) {
+ String actualStr = Main.valueToString(actual);
+ if (!expected.equals(actualStr)) {
+ throw new AssertionError("Expected: " + expected + ", actual: " + actualStr);
+ }
+ }
+}
+
+interface Test {
+ public void exercise();
+ public void check();
+}
+
+class TestOneFinal implements Test {
+ // Initialize at least once before actual test.
+ public static Object external;
+
+ /// CHECK-START: void TestOneFinal.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-NOT: ConstructorFence
+ /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>]
+
+ /// CHECK-START: void TestOneFinal.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-NOT: ConstructorFence
+ /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>]
+ @Override
+ public void exercise() {
+ Base b = new OneFinal(1);
+ // 1 store, 2 freezes.
+
+ // Stores to 'b' do not escape b.
+ b.w0 = 1;
+ b.w1 = 2;
+ b.w2 = 3;
+
+ // Publish the result to a global so that it is not LSE-eliminated.
+ external = b;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("OneFinal(w0: 1, w1: 2, w2: 3, w3: 0, x: 1)", external);
+ }
+}
+
+// This has a final field in its constructor, so there must be a field freeze
+// at the end of <init>. The previous base class's freezes accumulate on top
+// of this one.
+//
+// Total freezes for "new TwoFinal()": 3.
+class TwoFinal extends OneFinal {
+ final int y;
+ TwoFinal(int x, int y) {
+ super(x);
+ this.y = y;
+ }
+
+ @Override
+ protected String baseString() {
+ return String.format("%s, y: %d", super.baseString(), y);
+ }
+}
+
+// This has a final field in its constructor, so there must be a field freeze
+// at the end of <init>. The previous base class's freezes accumulate on top
+// of this one.
+//
+// Total freezes for "new ThreeFinal()": 4.
+class ThreeFinal extends TwoFinal {
+ final int z;
+ ThreeFinal(int x, int y, int z) {
+ super(x, y);
+ this.z = z;
+ }
+
+ @Override
+ protected String baseString() {
+ return String.format("%s, z: %d", super.baseString(), z);
+ }
+}
+
+class TestThreeFinal implements Test {
+ // Initialize at least once before actual test.
+ public static Object external;
+
+ /// CHECK-START: void TestThreeFinal.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-NOT: ConstructorFence
+ /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>]
+
+ /// CHECK-START: void TestThreeFinal.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-NOT: ConstructorFence
+ /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>]
+ @Override
+ public void exercise() {
+ Base b = new ThreeFinal(1, 1, 2);
+ // 3 store, 4 freezes.
+
+ // Stores to 'b' do not escape b.
+ b.w0 = 3;
+
+ // Publish the result to a global so that it is not LSE-eliminated.
+ external = b;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("ThreeFinal(w0: 3, w1: 0, w2: 0, w3: 0, x: 1, y: 1, z: 2)", external);
+ }
+}
+
+// Ensure "freezes" between multiple new-instances are optimized out.
+class TestMultiAlloc implements Test {
+ public static Object external;
+ public static Object external2;
+
+ /// CHECK-START: void TestMultiAlloc.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+ /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>]
+ /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>]
+
+ /// CHECK-START: void TestMultiAlloc.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>,<<NewInstance>>]
+ /// CHECK-NOT: ConstructorFence
+ /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>]
+ /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>]
+ @Override
+ public void exercise() {
+ // 1 freeze
+ Base b = new Base();
+ // 1 freeze
+ Base b2 = new Base();
+
+ // Merge 2 freezes above into 1 constructor fence.
+ external = b;
+ external2 = b2;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external2);
+ }
+}
+
+// Ensure "freezes" between multiple new-instances are optimized out.
+class TestThreeFinalTwice implements Test {
+ // Initialize at least once before actual test.
+ public static Object external;
+ public static Object external2;
+
+ /// CHECK-START: void TestThreeFinalTwice.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: ConstructorFence [<<NewInstance>>]
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+ /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>]
+ /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>]
+
+ /// CHECK-START: void TestThreeFinalTwice.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>,<<NewInstance>>]
+ /// CHECK-NOT: ConstructorFence
+ /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>]
+ /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>]
+ @Override
+ public void exercise() {
+ Base b = new ThreeFinal(1, 1, 2);
+ // 3 store, 4 freezes.
+
+ // Stores to 'b' do not escape b.
+ b.w0 = 3;
+
+ Base b2 = new ThreeFinal(4, 5, 6);
+ // 3 store, 4 freezes.
+
+ // Stores to 'b2' do not escape b2.
+ b2.w0 = 7;
+
+ // Publish the result to a global so that it is not LSE-eliminated.
+ // Publishing is done at the end to give freezes above a chance to merge.
+ external = b;
+ external2 = b2;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("ThreeFinal(w0: 3, w1: 0, w2: 0, w3: 0, x: 1, y: 1, z: 2)", external);
+ Assert.stringEquals("ThreeFinal(w0: 7, w1: 0, w2: 0, w3: 0, x: 4, y: 5, z: 6)", external2);
+ }
+}
+
+class TestNonEscaping {
+ // Prevent constant folding.
+ static boolean test;
+
+ static Object external;
+ static Object external2;
+ static Object external3;
+ static Object external4;
+
+ static class Invoke implements Test {
+ /// CHECK-START: void TestNonEscaping$Invoke.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK: InvokeStaticOrDirect
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+
+ /// CHECK-START: void TestNonEscaping$Invoke.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: InvokeStaticOrDirect
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>,<<NewInstance>>]
+ /// CHECK-NOT: ConstructorFence
+ @Override
+ public void exercise() {
+ Base b = new Base();
+
+ // b cannot possibly escape into this invoke because it hasn't escaped onto the heap earlier,
+ // and the invoke doesn't take it as a parameter.
+ noEscape$noinline$();
+
+ // Remove the Constructor Fence for b, merging into the fence for b2.
+ Base b2 = new Base();
+
+ // Do not LSE-eliminate b,b2
+ external = b;
+ external2 = b2;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external2);
+ }
+ }
+
+ public static int[] array = new int[1];
+ static Base base = new Base();
+
+ static class Store implements Test {
+ /// CHECK-START: void TestNonEscaping$Store.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: ArraySet
+ /// CHECK-DAG: StaticFieldSet
+ /// CHECK-DAG: InstanceFieldSet
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+
+ /// CHECK-START: void TestNonEscaping$Store.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance
+ /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>,<<NewInstance>>]
+ /// CHECK-NOT: ConstructorFence
+ @Override
+ public void exercise() {
+ Base b = new Base();
+
+ // Stores of inputs other than the fence target do not publish 'b'.
+ array[0] = b.w0; // aput
+ external = array; // sput
+ base.w0 = b.w0; // iput
+
+ // Remove the Constructor Fence for b, merging into the fence for b2.
+ Base b2 = new Base();
+
+ // Do not LSE-eliminate b,b2
+ external3 = b;
+ external4 = b2;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("[0]", array);
+ Assert.stringEquals("[0]", external);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", base);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4);
+ }
+ }
+
+ private static void noEscape$noinline$() {
+ }
+}
+
+class TestDontOptimizeAcrossBlocks implements Test {
+ // Prevent constant folding.
+ static boolean test;
+
+ static Object external;
+ static Object external3;
+
+ /// CHECK-START: void TestDontOptimizeAcrossBlocks.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+ /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>]
+ /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>]
+
+ /// CHECK-START: void TestDontOptimizeAcrossBlocks.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+ /// CHECK-DAG: StaticFieldSet [<<External:l\d+>>,<<NewInstance>>]
+ /// CHECK-DAG: StaticFieldSet [<<External2:l\d+>>,<<NewInstance2>>]
+ @Override
+ public void exercise() {
+ Base b = new Base();
+
+ // Do not move constructor fence across this block, even though 'b' is not published yet.
+ if (test) {
+ external = null;
+ }
+
+ Base b2 = new Base();
+ external = b2;
+ external3 = b;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("false", test);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3);
+ }
+}
+
+class TestDontOptimizeAcrossEscape {
+ // Prevent constant folding.
+ static boolean test;
+
+ static Object external;
+ static Object external2;
+ static Object external3;
+ static Object external4;
+
+ static class Invoke implements Test {
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$Invoke.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK: InvokeStaticOrDirect
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$Invoke.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK: InvokeStaticOrDirect
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+ @Override
+ public void exercise() {
+ Base b = new Base();
+ // Do not optimize across invokes into which the fence target escapes.
+ invoke$noinline$(b);
+
+ Base b2 = new Base();
+
+ // Do not LSE-eliminate b,b2
+ external = b;
+ external2 = b2;
+ }
+
+ private static void invoke$noinline$(Object b) {
+ // Even though 'b' does not escape this method, we conservatively assume all parameters
+ // of an invoke escape.
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external2);
+ }
+ }
+
+ public static Object[] array = new Object[3];
+ static Base base = new Base();
+
+ static class InstanceEscaper {
+ public Object holder;
+
+ @Override
+ public String toString() {
+ return getClass().getName() + "(" + baseString() + ")";
+ }
+
+ protected String baseString() {
+ return String.format("holder: %s", Main.valueToString(holder));
+ }
+ }
+
+ static InstanceEscaper instanceEscaper = new InstanceEscaper();
+
+ static class StoreIput implements Test {
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreIput.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: InstanceFieldSet
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreIput.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+ @Override
+ public void exercise() {
+ Base b = new Base();
+
+ // A store of 'b' into another instance will publish 'b'.
+ instanceEscaper.holder = b;
+
+ // Do not remove any constructor fences above.
+ Base b2 = new Base();
+
+ // Do not LSE-eliminate b,b2
+ external3 = b;
+ external4 = b2;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals(
+ "TestDontOptimizeAcrossEscape$InstanceEscaper(holder: Base(w0: 0, w1: 0, w2: 0, w3: 0))",
+ instanceEscaper);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4);
+ }
+ }
+
+ static class StoreAput implements Test {
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreAput.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: ArraySet
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreAput.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+ @Override
+ public void exercise() {
+ Base b = new Base();
+
+ // A store of 'b' into another array will publish 'b'.
+ array[0] = b; // aput
+
+ // Do not remove any constructor fences above.
+ Base b2 = new Base();
+
+ // Do not LSE-eliminate b,b2
+ external3 = b;
+ external4 = b2;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("[Base(w0: 0, w1: 0, w2: 0, w3: 0),<null>,<null>]", array);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4);
+ }
+ }
+
+ static class StoreSput implements Test {
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreSput.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: StaticFieldSet
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$StoreSput.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+ @Override
+ public void exercise() {
+ Base b = new Base();
+
+ // A store of 'b' into a static will publish 'b'.
+ external = b;
+
+ // Do not remove any constructor fences above.
+ Base b2 = new Base();
+
+ // Do not LSE-eliminate b,b2
+ external3 = b;
+ external4 = b2;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4);
+ }
+ }
+
+ static class Deopt implements Test {
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$Deopt.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: Deoptimize
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$Deopt.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+ @Override
+ public void exercise() {
+ Base b = new Base();
+
+ // An array access generates a Deopt to avoid doing bounds check.
+ array[0] = external; // aput
+ array[1] = external; // aput
+ array[2] = external; // aput
+
+ // Do not remove any constructor fences above.
+ Base b2 = new Base();
+
+ // Do not LSE-eliminate b,b2
+ external3 = b;
+ external4 = b2;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("[Base(w0: 0, w1: 0, w2: 0, w3: 0),"
+ + "Base(w0: 0, w1: 0, w2: 0, w3: 0),"
+ + "Base(w0: 0, w1: 0, w2: 0, w3: 0)]",
+ array);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4);
+ }
+ }
+
+ static class Select implements Test {
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$Select.exercise() constructor_fence_redundancy_elimination (before)
+ /// CHECK: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: Select
+ /// CHECK: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$Select.exercise() constructor_fence_redundancy_elimination (after)
+ /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: <<NewInstance2:l\d+>> NewInstance
+ /// CHECK-DAG: ConstructorFence [<<NewInstance2>>]
+ /// CHECK-NOT: ConstructorFence
+ @Override
+ public void exercise() {
+ Base b = new Base();
+
+ boolean localTest = test;
+ Object localExternal = external3;
+
+ // Selecting 'b' creates an alias, which we conservatively assume escapes immediately.
+ external = localTest ? b : localExternal;
+
+ // Do not remove any constructor fences above.
+ Base b2 = new Base();
+
+ // Do not LSE-eliminate b,b2
+ external3 = b;
+ external4 = b2;
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external3);
+ Assert.stringEquals("Base(w0: 0, w1: 0, w2: 0, w3: 0)", external4);
+ }
+ }
+
+ static class MakeBoundTypeTest implements Test {
+ public static Object makeBoundType;
+ public static Object makeBoundTypeSub;
+
+ @Override
+ public void exercise() {
+ // Note: MakeBoundType is special and we have to call the constructor directly
+ // to prevent inlining it.
+ try {
+ makeBoundType = exerciseNewInstance(MakeBoundType.class, 123);
+ makeBoundTypeSub = exerciseNewInstance(MakeBoundTypeSub.class, 123);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void check() {
+ Assert.stringEquals(
+ "TestDontOptimizeAcrossEscape$MakeBoundTypeTest$MakeBoundType(abcdefgh: 123, x: 2)",
+ makeBoundType);
+ Assert.stringEquals(
+ "TestDontOptimizeAcrossEscape$MakeBoundTypeTest$MakeBoundTypeSub(abcdefgh: 123, x: 1)",
+ makeBoundTypeSub);
+ }
+
+ // Make a new instance of 'klass'.
+ private static <T> T exerciseNewInstance(Class<T> klass, int params) throws Exception {
+ return klass.cast(klass.getDeclaredConstructor(int.class).newInstance(params));
+ }
+
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$MakeBoundTypeTest$MakeBoundType.<init>(int) constructor_fence_redundancy_elimination (before)
+ /// CHECK-DAG: <<This:l\d+>> ParameterValue
+ /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: BoundType
+ /// CHECK-DAG: ConstructorFence [<<This>>]
+ /// CHECK-NOT: ConstructorFence
+
+ /// CHECK-START: void TestDontOptimizeAcrossEscape$MakeBoundTypeTest$MakeBoundType.<init>(int) constructor_fence_redundancy_elimination (after)
+ /// CHECK-DAG: <<This:l\d+>> ParameterValue
+ /// CHECK-DAG: <<NewInstance:l\d+>> NewInstance
+ /// CHECK: ConstructorFence [<<NewInstance>>]
+ /// CHECK-DAG: BoundType
+ /// CHECK-DAG: ConstructorFence [<<This>>]
+ /// CHECK-NOT: ConstructorFence
+ static class MakeBoundType {
+ final int abcdefgh;
+ int x;
+
+ MakeBoundType(int param) {
+ abcdefgh = param;
+
+ Base b = new Base();
+ // constructor-fence(b)
+
+ if (this instanceof MakeBoundTypeSub) {
+ // Create a "BoundType(this)" which prevents
+ // a merged constructor-fence(this, b)
+ x = 1;
+ } else {
+ x = 2;
+ }
+
+ // publish(b).
+ external = b;
+
+ // constructor-fence(this)
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + "(" + baseString() + ")";
+ }
+
+ protected String baseString() {
+ return String.format("abcdefgh: %d, x: %d", abcdefgh, x);
+ }
+ }
+
+ static class MakeBoundTypeSub extends MakeBoundType {
+ MakeBoundTypeSub(int xyz) {
+ super(xyz);
+ }
+ }
+ }
+}
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ // Ensure that all of this code does not get optimized out into a no-op
+ // by actually running the code with reflection, then validating
+ // the result by asserting it against a string.
+ Class<? extends Test>[] testClasses = new Class[] {
+ TestOneFinal.class,
+ TestThreeFinal.class,
+ TestMultiAlloc.class,
+ TestThreeFinalTwice.class,
+ TestNonEscaping.Invoke.class,
+ TestNonEscaping.Store.class,
+ TestDontOptimizeAcrossBlocks.class,
+ TestDontOptimizeAcrossEscape.Invoke.class,
+ TestDontOptimizeAcrossEscape.StoreIput.class,
+ TestDontOptimizeAcrossEscape.StoreAput.class,
+ TestDontOptimizeAcrossEscape.StoreSput.class,
+ TestDontOptimizeAcrossEscape.Deopt.class,
+ TestDontOptimizeAcrossEscape.Select.class,
+ TestDontOptimizeAcrossEscape.MakeBoundTypeTest.class,
+ };
+
+ for (Class<? extends Test> klass : testClasses) {
+ exerciseTestClass(klass);
+ }
+ }
+
+ /**
+ * Invoke Test#exercise(), then Test#check().
+ * @throws AssertionError if test fails.
+ */
+ private static void exerciseTestClass(Class<? extends Test> klass) throws Exception {
+ Test instance = klass.cast(klass.getDeclaredConstructor().newInstance());
+
+ // Use reflection as a best-effort to avoid compiler optimizations (e.g. inlining).
+ instance.getClass().getDeclaredMethod("exercise").invoke(instance);
+ instance.getClass().getDeclaredMethod("check").invoke(instance);
+ }
+
+ // Print an object, with special handling for array and null.
+ public static String valueToString(Object val) {
+ if (val == null) {
+ return "<null>";
+ }
+ if (val.getClass().isArray()) {
+ String fmt = "[";
+ int length = Array.getLength(val);
+ for (int i = 0; i < length; ++i) {
+ Object arrayElement = Array.get(val, i);
+ fmt += valueToString(arrayElement);
+
+ if (i != length - 1) {
+ fmt += ",";
+ }
+ }
+ fmt += "]";
+
+ return fmt;
+ }
+
+ return val.toString();
+ }
+}
diff --git a/test/530-checker-lse-ctor-fences/smali/Smali.smali b/test/530-checker-lse-ctor-fences/smali/Smali.smali
new file mode 100644
index 0000000..856af96
--- /dev/null
+++ b/test/530-checker-lse-ctor-fences/smali/Smali.smali
@@ -0,0 +1,97 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+.class public LSmali;
+.super Ljava/lang/Object;
+
+## CHECK-START: double Smali.calcCircleAreaOrCircumference(double, boolean) load_store_elimination (before)
+## CHECK: NewInstance
+## CHECK: InstanceFieldSet
+## CHECK: ConstructorFence
+## CHECK: InstanceFieldGet
+
+## CHECK-START: double Smali.calcCircleAreaOrCircumference(double, boolean) load_store_elimination (after)
+## CHECK: NewInstance
+## CHECK-NOT: ConstructorFence
+
+# The object allocation will not be eliminated by LSE because of aliased stores.
+# However the object is still a singleton, so it never escapes the current thread.
+# There should not be a constructor fence here after LSE.
+
+.method public static calcCircleAreaOrCircumference(DZ)D
+ .registers 7
+
+ # CalcCircleAreaOrCircumference calc =
+ # new CalcCircleAreaOrCircumference(
+ # area_or_circumference ? CalcCircleAreaOrCircumference.TYPE_AREA :
+ # CalcCircleAreaOrCircumference.TYPE_CIRCUMFERENCE);
+
+ # if (area_or_circumference) {
+ # // Area
+ # calc.value = Math.PI * Math.PI * radius;
+ # } else {
+ # // Circumference
+ # calc.value = 2 * Math.PI * radius;
+ # }
+
+ # Please note that D8 would merge the iput togother which looks like :
+
+ # if (area_or_circumference) {
+ # // Area
+ # tmp = Math.PI * Math.PI * radius;
+ # } else {
+ # // Circumference
+ # tmp = 2 * Math.PI * radius;
+ # }
+ # calc.value = tmp;
+
+ # which makes the LSE valid and defeat the purpose of this test.
+
+ new-instance v0, LCalcCircleAreaOrCircumference;
+
+ if-eqz p2, :cond_15
+
+ const/4 v1, 0x0
+
+ :goto_5
+ invoke-direct {v0, v1}, LCalcCircleAreaOrCircumference;-><init>(I)V
+
+ if-eqz p2, :cond_17
+
+ const-wide v2, 0x4023bd3cc9be45deL # 9.869604401089358
+
+ mul-double/2addr v2, p0
+
+ iput-wide v2, v0, LCalcCircleAreaOrCircumference;->value:D
+
+ :goto_12
+ iget-wide v2, v0, LCalcCircleAreaOrCircumference;->value:D
+
+ return-wide v2
+
+ :cond_15
+ const/4 v1, 0x1
+
+ goto :goto_5
+
+ :cond_17
+ const-wide v2, 0x401921fb54442d18L # 6.283185307179586
+
+ mul-double/2addr v2, p0
+
+ iput-wide v2, v0, LCalcCircleAreaOrCircumference;->value:D
+
+ goto :goto_12
+.end method
+
diff --git a/test/530-checker-lse-ctor-fences/src/Main.java b/test/530-checker-lse-ctor-fences/src/Main.java
index 7755875..c3796ea 100644
--- a/test/530-checker-lse-ctor-fences/src/Main.java
+++ b/test/530-checker-lse-ctor-fences/src/Main.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import java.lang.reflect.Method;
// This base class has a single final field;
// the constructor should have one fence.
@@ -112,14 +113,7 @@
return new Ellipse(vertex, covertex).getArea();
}
- /// CHECK-START: double Main.calcCircleAreaOrCircumference(double, boolean) load_store_elimination (before)
- /// CHECK: NewInstance
- /// CHECK: InstanceFieldSet
- /// CHECK: ConstructorFence
- /// CHECK: InstanceFieldGet
-
/// CHECK-START: double Main.calcCircleAreaOrCircumference(double, boolean) load_store_elimination (after)
- /// CHECK: NewInstance
/// CHECK-NOT: ConstructorFence
//
@@ -143,6 +137,16 @@
return calc.value;
}
+ static double calcCircleAreaOrCircumferenceSmali(double radius, boolean area_or_circumference) {
+ try {
+ Class<?> c = Class.forName("Smali");
+ Method m = c.getMethod("calcCircleAreaOrCircumference", double.class, boolean.class);
+ return (Double) m.invoke(null, radius, area_or_circumference);
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
/// CHECK-START: Circle Main.makeCircle(double) load_store_elimination (after)
/// CHECK: NewInstance
/// CHECK: ConstructorFence
@@ -184,6 +188,7 @@
assertDoubleEquals(Math.PI * Math.PI * Math.PI, calcCircleArea(Math.PI));
assertDoubleEquals(Math.PI * Math.PI * Math.PI, calcEllipseArea(Math.PI, Math.PI));
assertDoubleEquals(2 * Math.PI * Math.PI, calcCircleAreaOrCircumference(Math.PI, false));
+ assertDoubleEquals(2 * Math.PI * Math.PI, calcCircleAreaOrCircumferenceSmali(Math.PI, false));
assertInstanceOf(makeCircle(Math.PI), Circle.class);
}
diff --git a/test/552-checker-sharpening/src/Main.java b/test/552-checker-sharpening/src/Main.java
index 7408e6d..1f1920c 100644
--- a/test/552-checker-sharpening/src/Main.java
+++ b/test/552-checker-sharpening/src/Main.java
@@ -214,27 +214,27 @@
/// CHECK-START-X86: java.lang.String Main.$noinline$getBootImageString() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadString load_kind:{{BootImageAddress|BssEntry}}
+ /// CHECK: LoadString load_kind:{{BootImageAddress|BootImageInternTable}}
/// CHECK-START-X86_64: java.lang.String Main.$noinline$getBootImageString() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadString load_kind:{{BootImageAddress|BssEntry}}
+ /// CHECK: LoadString load_kind:{{BootImageAddress|BootImageInternTable}}
/// CHECK-START-ARM: java.lang.String Main.$noinline$getBootImageString() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadString load_kind:{{BootImageAddress|BssEntry}}
+ /// CHECK: LoadString load_kind:{{BootImageAddress|BootImageInternTable}}
/// CHECK-START-ARM64: java.lang.String Main.$noinline$getBootImageString() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadString load_kind:{{BootImageAddress|BssEntry}}
+ /// CHECK: LoadString load_kind:{{BootImageAddress|BootImageInternTable}}
/// CHECK-START-MIPS: java.lang.String Main.$noinline$getBootImageString() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadString load_kind:{{BootImageAddress|BssEntry}}
+ /// CHECK: LoadString load_kind:{{BootImageAddress|BootImageInternTable}}
/// CHECK-START-MIPS64: java.lang.String Main.$noinline$getBootImageString() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadString load_kind:{{BootImageAddress|BssEntry}}
+ /// CHECK: LoadString load_kind:{{BootImageAddress|BootImageInternTable}}
public static String $noinline$getBootImageString() {
// Prevent inlining to avoid the string comparison being optimized away.
@@ -277,27 +277,27 @@
/// CHECK-START-X86: java.lang.Class Main.$noinline$getStringClass() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadClass load_kind:{{BootImageAddress|BssEntry}} class_name:java.lang.String
+ /// CHECK: LoadClass load_kind:{{BootImageAddress|BootImageClassTable}} class_name:java.lang.String
/// CHECK-START-X86_64: java.lang.Class Main.$noinline$getStringClass() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadClass load_kind:{{BootImageAddress|BssEntry}} class_name:java.lang.String
+ /// CHECK: LoadClass load_kind:{{BootImageAddress|BootImageClassTable}} class_name:java.lang.String
/// CHECK-START-ARM: java.lang.Class Main.$noinline$getStringClass() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadClass load_kind:{{BootImageAddress|BssEntry}} class_name:java.lang.String
+ /// CHECK: LoadClass load_kind:{{BootImageAddress|BootImageClassTable}} class_name:java.lang.String
/// CHECK-START-ARM64: java.lang.Class Main.$noinline$getStringClass() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadClass load_kind:{{BootImageAddress|BssEntry}} class_name:java.lang.String
+ /// CHECK: LoadClass load_kind:{{BootImageAddress|BootImageClassTable}} class_name:java.lang.String
/// CHECK-START-MIPS: java.lang.Class Main.$noinline$getStringClass() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadClass load_kind:{{BootImageAddress|BssEntry}} class_name:java.lang.String
+ /// CHECK: LoadClass load_kind:{{BootImageAddress|BootImageClassTable}} class_name:java.lang.String
/// CHECK-START-MIPS64: java.lang.Class Main.$noinline$getStringClass() sharpening (after)
// Note: load kind depends on PIC/non-PIC
- /// CHECK: LoadClass load_kind:{{BootImageAddress|BssEntry}} class_name:java.lang.String
+ /// CHECK: LoadClass load_kind:{{BootImageAddress|BootImageClassTable}} class_name:java.lang.String
public static Class<?> $noinline$getStringClass() {
// Prevent inlining to avoid the string comparison being optimized away.
diff --git a/test/623-checker-loop-regressions/src/Main.java b/test/623-checker-loop-regressions/src/Main.java
index 056ed91..418be30 100644
--- a/test/623-checker-loop-regressions/src/Main.java
+++ b/test/623-checker-loop-regressions/src/Main.java
@@ -473,6 +473,30 @@
return y;
}
+ // b/65478356: sum up 2-dim array.
+ static int sum(int[][] a) {
+ int sum = 0;
+ for (int y = 0; y < a.length; y++) {
+ int[] aa = a[y];
+ for (int x = 0; x < aa.length; x++) {
+ sum += aa[x];
+ }
+ }
+ return sum;
+ }
+
+ // Large loop body should not break unrolling computation.
+ static void largeBody(int[] x) {
+ for (int i = 0; i < 100; i++) {
+ x[i] = x[i] * 1 + x[i] * 2 + x[i] * 3 + x[i] * 4 + x[i] * 5 + x[i] * 6 +
+ x[i] * 7 + x[i] * 8 + x[i] * 9 + x[i] * 10 + x[i] * 11 + x[i] * 12 +
+ x[i] * 13 + x[i] * 14 + x[i] * 15 + x[i] * 1 + x[i] * 2 + x[i] * 3 + x[i] * 4 +
+ x[i] * 5 + x[i] * 6 + x[i] * 7 + x[i] * 8 + x[i] * 9 + x[i] * 10 + x[i] * 11 +
+ x[i] * 12 + x[i] * 13 + x[i] * 14 + x[i] * 15 + x[i] * 1 + x[i] * 2 + x[i] * 3 +
+ x[i] * 4 + x[i] * 5;
+ }
+ }
+
public static void main(String[] args) {
expectEquals(10, earlyExitFirst(-1));
for (int i = 0; i <= 10; i++) {
@@ -613,6 +637,19 @@
}
expectEquals(2, verify);
+ int[][] x = new int[128][128];
+ for (int i = 0; i < 128; i++) {
+ for (int j = 0; j < 128; j++) {
+ x[i][j] = -i - j;
+ }
+ }
+ expectEquals(-2080768, sum(x));
+
+ largeBody(f);
+ for (int i = 0; i < 100; i++) {
+ expectEquals(2805, f[i]);
+ }
+
System.out.println("passed");
}
diff --git a/test/656-checker-simd-opt/smali/Smali.smali b/test/656-checker-simd-opt/smali/Smali.smali
new file mode 100644
index 0000000..802bcab
--- /dev/null
+++ b/test/656-checker-simd-opt/smali/Smali.smali
@@ -0,0 +1,121 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+.class public LSmali;
+.super Ljava/lang/Object;
+
+## CHECK-START: void Smali.stencilSubInt(int[], int[], int) loop_optimization (before)
+## CHECK-DAG: <<PAR3:i\d+>> ParameterValue loop:none
+## CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none
+## CHECK-DAG: <<Sub1:i\d+>> Sub [<<PAR3>>,<<CP1>>] loop:none
+## CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+## CHECK-DAG: <<Sub2:i\d+>> Sub [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get1:i\d+>> ArrayGet [{{l\d+}},<<Sub2>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get2:i\d+>> ArrayGet [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add1:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add2:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get3:i\d+>> ArrayGet [{{l\d+}},<<Add2>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add3:i\d+>> Add [<<Add1>>,<<Get3>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Add3>>] loop:<<Loop>> outer_loop:none
+
+## CHECK-START-ARM64: void Smali.stencilSubInt(int[], int[], int) loop_optimization (after)
+## CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none
+## CHECK-DAG: <<CP2:i\d+>> IntConstant 2 loop:none
+## CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+## CHECK-DAG: <<Add1:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get1:d\d+>> VecLoad [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get2:d\d+>> VecLoad [{{l\d+}},<<Add1>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add2:d\d+>> VecAdd [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add3:i\d+>> Add [<<Phi>>,<<CP2>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get3:d\d+>> VecLoad [{{l\d+}},<<Add3>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add4:d\d+>> VecAdd [<<Add2>>,<<Get3>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: VecStore [{{l\d+}},<<Add1>>,<<Add4>>] loop:<<Loop>> outer_loop:none
+.method public static stencilSubInt([I[II)V
+ .registers 7
+
+ const/4 v0, 0x1
+
+ move v1, v0
+
+ :goto_2
+ sub-int v2, p2, v0
+
+ if-ge v1, v2, :cond_17
+
+ sub-int v2, v1, v0
+ aget v2, p1, v2
+ aget v3, p1, v1
+ add-int/2addr v2, v3
+ add-int v3, v1, v0
+ aget v3, p1, v3
+ add-int/2addr v2, v3
+ aput v2, p0, v1
+ add-int/lit8 v1, v1, 0x1
+
+ goto :goto_2
+
+ :cond_17
+ return-void
+.end method
+
+## CHECK-START: void Smali.stencilAddInt(int[], int[], int) loop_optimization (before)
+## CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none
+## CHECK-DAG: <<CM1:i\d+>> IntConstant -1 loop:none
+## CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+## CHECK-DAG: <<Add1:i\d+>> Add [<<Phi>>,<<CM1>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get1:i\d+>> ArrayGet [{{l\d+}},<<Add1>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get2:i\d+>> ArrayGet [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add2:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add3:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get3:i\d+>> ArrayGet [{{l\d+}},<<Add3>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add4:i\d+>> Add [<<Add2>>,<<Get3>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Add4>>] loop:<<Loop>> outer_loop:none
+
+## CHECK-START-ARM64: void Smali.stencilAddInt(int[], int[], int) loop_optimization (after)
+## CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none
+## CHECK-DAG: <<CP2:i\d+>> IntConstant 2 loop:none
+## CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+## CHECK-DAG: <<Add1:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get1:d\d+>> VecLoad [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get2:d\d+>> VecLoad [{{l\d+}},<<Add1>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add2:d\d+>> VecAdd [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add3:i\d+>> Add [<<Phi>>,<<CP2>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Get3:d\d+>> VecLoad [{{l\d+}},<<Add3>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: <<Add4:d\d+>> VecAdd [<<Add2>>,<<Get3>>] loop:<<Loop>> outer_loop:none
+## CHECK-DAG: VecStore [{{l\d+}},<<Add1>>,<<Add4>>] loop:<<Loop>> outer_loop:none
+.method public static stencilAddInt([I[II)V
+ .registers 6
+
+ const/4 v0, 0x1
+
+ :goto_1
+ add-int/lit8 v1, p2, -0x1
+
+ if-ge v0, v1, :cond_16
+
+ add-int/lit8 v1, v0, -0x1
+ aget v1, p1, v1
+ aget v2, p1, v0
+ add-int/2addr v1, v2
+ add-int/lit8 v2, v0, 0x1
+ aget v2, p1, v2
+ add-int/2addr v1, v2
+ aput v1, p0, v0
+ add-int/lit8 v0, v0, 0x1
+
+ goto :goto_1
+
+ :cond_16
+ return-void
+.end method
diff --git a/test/656-checker-simd-opt/src/Main.java b/test/656-checker-simd-opt/src/Main.java
index 794c9b6..091633f 100644
--- a/test/656-checker-simd-opt/src/Main.java
+++ b/test/656-checker-simd-opt/src/Main.java
@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
/**
* Tests for SIMD related optimizations.
@@ -46,19 +48,6 @@
}
}
- /// CHECK-START: void Main.stencil(int[], int[], int) loop_optimization (before)
- /// CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none
- /// CHECK-DAG: <<CM1:i\d+>> IntConstant -1 loop:none
- /// CHECK-DAG: <<Phi:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
- /// CHECK-DAG: <<Add1:i\d+>> Add [<<Phi>>,<<CM1>>] loop:<<Loop>> outer_loop:none
- /// CHECK-DAG: <<Get1:i\d+>> ArrayGet [{{l\d+}},<<Add1>>] loop:<<Loop>> outer_loop:none
- /// CHECK-DAG: <<Get2:i\d+>> ArrayGet [{{l\d+}},<<Phi>>] loop:<<Loop>> outer_loop:none
- /// CHECK-DAG: <<Add2:i\d+>> Add [<<Get1>>,<<Get2>>] loop:<<Loop>> outer_loop:none
- /// CHECK-DAG: <<Add3:i\d+>> Add [<<Phi>>,<<CP1>>] loop:<<Loop>> outer_loop:none
- /// CHECK-DAG: <<Get3:i\d+>> ArrayGet [{{l\d+}},<<Add3>>] loop:<<Loop>> outer_loop:none
- /// CHECK-DAG: <<Add4:i\d+>> Add [<<Add2>>,<<Get3>>] loop:<<Loop>> outer_loop:none
- /// CHECK-DAG: ArraySet [{{l\d+}},<<Phi>>,<<Add4>>] loop:<<Loop>> outer_loop:none
- //
/// CHECK-START-ARM64: void Main.stencil(int[], int[], int) loop_optimization (after)
/// CHECK-DAG: <<CP1:i\d+>> IntConstant 1 loop:none
/// CHECK-DAG: <<CP2:i\d+>> IntConstant 2 loop:none
@@ -77,6 +66,32 @@
}
}
+ private static void stencilAddInt(int[] a, int[] b, int n) {
+ try {
+ Class<?> c = Class.forName("Smali");
+ Method m = c.getMethod("stencilAddInt",
+ Array.newInstance(int.class, 1).getClass(),
+ Array.newInstance(int.class, 1).getClass(),
+ int.class);
+ m.invoke(null, a, b, n);
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
+ private static void stencilSubInt(int[] a, int[] b, int n) {
+ try {
+ Class<?> c = Class.forName("Smali");
+ Method m = c.getMethod("stencilSubInt",
+ Array.newInstance(int.class, 1).getClass(),
+ Array.newInstance(int.class, 1).getClass(),
+ int.class);
+ m.invoke(null, a, b, n);
+ } catch (Exception ex) {
+ throw new Error(ex);
+ }
+ }
+
public static void main(String[] args) {
float[] x = new float[100];
float[] y = new float[100];
@@ -89,17 +104,47 @@
expectEquals(5.0f, x[i]);
expectEquals(2.0f, y[i]);
}
- int[] a = new int[100];
- int[] b = new int[100];
- for (int i = 0; i < 100; i++) {
- a[i] = 0;
- b[i] = i;
+ {
+ int[] a = new int[100];
+ int[] b = new int[100];
+ for (int i = 0; i < 100; i++) {
+ a[i] = 0;
+ b[i] = i;
+ }
+ stencil(a, b, 100);
+ for (int i = 1; i < 99; i++) {
+ int e = i + i + i;
+ expectEquals(e, a[i]);
+ expectEquals(i, b[i]);
+ }
}
- stencil(a, b, 100);
- for (int i = 1; i < 99; i++) {
- int e = i + i + i;
- expectEquals(e, a[i]);
- expectEquals(i, b[i]);
+ {
+ int[] a = new int[100];
+ int[] b = new int[100];
+ for (int i = 0; i < 100; i++) {
+ a[i] = 0;
+ b[i] = i;
+ }
+ stencilSubInt(a, b, 100);
+ for (int i = 1; i < 99; i++) {
+ int e = i + i + i;
+ expectEquals(e, a[i]);
+ expectEquals(i, b[i]);
+ }
+ }
+ {
+ int[] a = new int[100];
+ int[] b = new int[100];
+ for (int i = 0; i < 100; i++) {
+ a[i] = 0;
+ b[i] = i;
+ }
+ stencilAddInt(a, b, 100);
+ for (int i = 1; i < 99; i++) {
+ int e = i + i + i;
+ expectEquals(e, a[i]);
+ expectEquals(i, b[i]);
+ }
}
System.out.println("passed");
}
diff --git a/test/661-checker-simd-reduc/src/Main.java b/test/661-checker-simd-reduc/src/Main.java
index 8208a9e..71eb3cd 100644
--- a/test/661-checker-simd-reduc/src/Main.java
+++ b/test/661-checker-simd-reduc/src/Main.java
@@ -59,6 +59,7 @@
/// CHECK-DAG: <<Get:i\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
//
/// CHECK-START-ARM64: int Main.reductionInt(int[]) loop_optimization (after)
/// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none
@@ -88,6 +89,7 @@
/// CHECK-DAG: <<Get:j\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
//
/// CHECK-START-ARM64: long Main.reductionLong(long[]) loop_optimization (after)
/// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none
@@ -142,6 +144,7 @@
/// CHECK-DAG: <<Get:i\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
//
/// CHECK-START-ARM64: int Main.reductionIntM1(int[]) loop_optimization (after)
/// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none
@@ -172,6 +175,7 @@
/// CHECK-DAG: <<Get:j\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
//
/// CHECK-START-ARM64: long Main.reductionLongM1(long[]) loop_optimization (after)
/// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none
@@ -225,6 +229,7 @@
/// CHECK-DAG: <<Get:i\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Sub [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
//
/// CHECK-START-ARM64: int Main.reductionMinusInt(int[]) loop_optimization (after)
/// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none
@@ -254,6 +259,7 @@
/// CHECK-DAG: <<Get:j\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Sub [<<Phi2>>,<<Get>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
//
/// CHECK-START-ARM64: long Main.reductionMinusLong(long[]) loop_optimization (after)
/// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none
@@ -308,6 +314,7 @@
/// CHECK-DAG: <<Get:i\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: InvokeStaticOrDirect [<<Phi2>>,<<Get>>] intrinsic:MathMinIntInt loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
//
/// CHECK-START-ARM64: int Main.reductionMinInt(int[]) loop_optimization (after)
/// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none
@@ -370,6 +377,7 @@
/// CHECK-DAG: <<Get:i\d+>> ArrayGet [{{l\d+}},<<Phi1>>] loop:<<Loop>> outer_loop:none
/// CHECK-DAG: InvokeStaticOrDirect [<<Phi2>>,<<Get>>] intrinsic:MathMaxIntInt loop:<<Loop>> outer_loop:none
/// CHECK-DAG: Add [<<Phi1>>,<<Cons1>>] loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi2>>] loop:none
//
/// CHECK-START-ARM64: int Main.reductionMaxInt(int[]) loop_optimization (after)
/// CHECK-DAG: <<Cons0:i\d+>> IntConstant 0 loop:none
diff --git a/test/913-heaps/expected.txt b/test/913-heaps/expected.txt
index 6144881..57c2dc6 100644
--- a/test/913-heaps/expected.txt
+++ b/test/913-heaps/expected.txt
@@ -5,43 +5,43 @@
root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=136, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
root@root --(thread)--> 3000@0 [size=136, length=-1]
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780000, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780004, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780001, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780000, length=-1]
1@1000 --(field@2)--> 2@1000 [size=16, length=-1]
1@1000 --(field@3)--> 3@1001 [size=24, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780003, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780000, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780001, length=-1]
3@1001 --(field@4)--> 4@1000 [size=16, length=-1]
3@1001 --(field@5)--> 5@1002 [size=36, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780000, length=-1]
500@0 --(array-element@1)--> 2@1000 [size=16, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780002, length=-1]
5@1002 --(field@10)--> 1@1000 [size=16, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
5@1002 --(field@9)--> 6@1000 [size=16, length=-1]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780000, length=-1]
---
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780000, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780004, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780001, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780000, length=-1]
1@1000 --(field@2)--> 2@1000 [size=16, length=-1]
1@1000 --(field@3)--> 3@1001 [size=24, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780003, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780000, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780001, length=-1]
3@1001 --(field@4)--> 4@1000 [size=16, length=-1]
3@1001 --(field@5)--> 5@1002 [size=36, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780000, length=-1]
500@0 --(array-element@1)--> 2@1000 [size=16, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780002, length=-1]
5@1002 --(field@10)--> 1@1000 [size=16, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
5@1002 --(field@9)--> 6@1000 [size=16, length=-1]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780000, length=-1]
---
root@root --(jni-global)--> 1@1000 [size=16, length=-1]
root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1]
@@ -52,51 +52,51 @@
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
root@root --(thread)--> 1@1000 [size=16, length=-1]
root@root --(thread)--> 3000@0 [size=136, length=-1]
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780005, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780009, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780006, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780005, length=-1]
1@1000 --(field@2)--> 2@1000 [size=16, length=-1]
1@1000 --(field@3)--> 3@1001 [size=24, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780008, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780005, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780006, length=-1]
3@1001 --(field@4)--> 4@1000 [size=16, length=-1]
3@1001 --(field@5)--> 5@1002 [size=36, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780005, length=-1]
500@0 --(array-element@1)--> 2@1000 [size=16, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780007, length=-1]
5@1002 --(field@10)--> 1@1000 [size=16, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
5@1002 --(field@9)--> 6@1000 [size=16, length=-1]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780005, length=-1]
---
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780005, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780009, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780006, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780005, length=-1]
1@1000 --(field@2)--> 2@1000 [size=16, length=-1]
1@1000 --(field@3)--> 3@1001 [size=24, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780008, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780005, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780006, length=-1]
3@1001 --(field@4)--> 4@1000 [size=16, length=-1]
3@1001 --(field@5)--> 5@1002 [size=36, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780005, length=-1]
500@0 --(array-element@1)--> 2@1000 [size=16, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780007, length=-1]
5@1002 --(field@10)--> 1@1000 [size=16, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
5@1002 --(field@9)--> 6@1000 [size=16, length=-1]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780005, length=-1]
---
root@root --(thread)--> 3000@0 [size=136, length=-1]
---
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780011, length=-1]
---
root@root --(thread)--> 3000@0 [size=136, length=-1]
---
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780016, length=-1]
---
root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1]
@@ -104,8 +104,8 @@
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
root@root --(thread)--> 3000@0 [size=136, length=-1]
---
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780020, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780021, length=-1]
3@1001 --(field@4)--> 4@1000 [size=16, length=-1]
3@1001 --(field@5)--> 5@1002 [size=36, length=-1]
---
@@ -119,8 +119,8 @@
root@root --(thread)--> 1@1000 [size=16, length=-1]
root@root --(thread)--> 3000@0 [size=136, length=-1]
---
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780025, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780026, length=-1]
3@1001 --(field@4)--> 4@1000 [size=16, length=-1]
3@1001 --(field@5)--> 5@1002 [size=36, length=-1]
---
@@ -202,43 +202,43 @@
root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=136, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
root@root --(thread)--> 3000@0 [size=136, length=-1]
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780050, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780054, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780051, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780050, length=-1]
1@1000 --(field@2)--> 2@1000 [size=16, length=-1]
1@1000 --(field@3)--> 3@1001 [size=24, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780053, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780050, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780051, length=-1]
3@1001 --(field@4)--> 4@1000 [size=16, length=-1]
3@1001 --(field@5)--> 5@1002 [size=36, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780050, length=-1]
500@0 --(array-element@1)--> 2@1000 [size=16, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780052, length=-1]
5@1002 --(field@10)--> 1@1000 [size=16, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
5@1002 --(field@9)--> 6@1000 [size=16, length=-1]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780050, length=-1]
---
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780050, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780054, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780051, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780050, length=-1]
1@1000 --(field@2)--> 2@1000 [size=16, length=-1]
1@1000 --(field@3)--> 3@1001 [size=24, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780053, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780050, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780051, length=-1]
3@1001 --(field@4)--> 4@1000 [size=16, length=-1]
3@1001 --(field@5)--> 5@1002 [size=36, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780050, length=-1]
500@0 --(array-element@1)--> 2@1000 [size=16, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780052, length=-1]
5@1002 --(field@10)--> 1@1000 [size=16, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
5@1002 --(field@9)--> 6@1000 [size=16, length=-1]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780050, length=-1]
---
root@root --(jni-global)--> 1@1000 [size=16, length=-1]
root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 1@1000 [size=16, length=-1]
@@ -249,99 +249,99 @@
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
root@root --(thread)--> 1@1000 [size=16, length=-1]
root@root --(thread)--> 3000@0 [size=136, length=-1]
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780055, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780059, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780056, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780055, length=-1]
1@1000 --(field@2)--> 2@1000 [size=16, length=-1]
1@1000 --(field@3)--> 3@1001 [size=24, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780058, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780055, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780056, length=-1]
3@1001 --(field@4)--> 4@1000 [size=16, length=-1]
3@1001 --(field@5)--> 5@1002 [size=36, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780055, length=-1]
500@0 --(array-element@1)--> 2@1000 [size=16, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780057, length=-1]
5@1002 --(field@10)--> 1@1000 [size=16, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
5@1002 --(field@9)--> 6@1000 [size=16, length=-1]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780055, length=-1]
---
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780055, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780059, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780056, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780055, length=-1]
1@1000 --(field@2)--> 2@1000 [size=16, length=-1]
1@1000 --(field@3)--> 3@1001 [size=24, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780058, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780055, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780056, length=-1]
3@1001 --(field@4)--> 4@1000 [size=16, length=-1]
3@1001 --(field@5)--> 5@1002 [size=36, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780055, length=-1]
500@0 --(array-element@1)--> 2@1000 [size=16, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780057, length=-1]
5@1002 --(field@10)--> 1@1000 [size=16, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
5@1002 --(field@9)--> 6@1000 [size=16, length=-1]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780055, length=-1]
---
---- tagged classes
root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=136, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
root@root --(thread)--> 3000@0 [size=136, length=-1]
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780060, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780064, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780061, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780063, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780061, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780062, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
---
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780060, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780064, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780061, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780063, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780061, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780062, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
---
root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=136, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=136, length=-1]
root@root --(thread)--> 3000@0 [size=136, length=-1]
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780065, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780069, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780066, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780065, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780068, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780065, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780066, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780065, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780067, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780065, length=-1]
---
-1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
-1002@0 --(interface)--> 2001@0 [size=124, length=-1]
-1002@0 --(superclass)--> 1001@0 [size=123, length=-1]
-1@1000 --(class)--> 1000@0 [size=123, length=-1]
-2001@0 --(interface)--> 2000@0 [size=124, length=-1]
-2@1000 --(class)--> 1000@0 [size=123, length=-1]
-3@1001 --(class)--> 1001@0 [size=123, length=-1]
-4@1000 --(class)--> 1000@0 [size=123, length=-1]
-5@1002 --(class)--> 1002@0 [size=123, length=-1]
+1001@0 --(superclass)--> 1000@0 [size=123456780065, length=-1]
+1002@0 --(interface)--> 2001@0 [size=123456780069, length=-1]
+1002@0 --(superclass)--> 1001@0 [size=123456780066, length=-1]
+1@1000 --(class)--> 1000@0 [size=123456780065, length=-1]
+2001@0 --(interface)--> 2000@0 [size=123456780068, length=-1]
+2@1000 --(class)--> 1000@0 [size=123456780065, length=-1]
+3@1001 --(class)--> 1001@0 [size=123456780066, length=-1]
+4@1000 --(class)--> 1000@0 [size=123456780065, length=-1]
+5@1002 --(class)--> 1002@0 [size=123456780067, length=-1]
5@1002 --(field@8)--> 500@0 [size=20, length=2]
-6@1000 --(class)--> 1000@0 [size=123, length=-1]
+6@1000 --(class)--> 1000@0 [size=123456780065, length=-1]
---
---- untagged classes
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 30])--> 1@1000 [size=16, length=-1]
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index f7862c7..00a7ea7 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -19,6 +19,7 @@
#include <cstdio>
#include <cstring>
#include <iostream>
+#include <map>
#include <sstream>
#include <vector>
@@ -50,6 +51,36 @@
JvmtiErrorToException(env, jvmti_env, ret);
}
+// Collect sizes of objects (classes) ahead of time, to be able to normalize.
+struct ClassData {
+ jlong size; // Size as reported by GetObjectSize.
+ jlong serial; // Computed serial that should be printed instead of the size.
+};
+
+// Stores a map from tags to ClassData.
+static std::map<jlong, ClassData> sClassData;
+static size_t sClassDataSerial = 0;
+// Large enough number that a collision with a test object is unlikely.
+static constexpr jlong kClassDataSerialBase = 123456780000;
+
+// Register a class (or general object) in the class-data map. The serial number is determined by
+// the order of calls to this function (so stable Java code leads to stable numbering).
+extern "C" JNIEXPORT void JNICALL Java_art_Test913_registerClass(
+ JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag, jobject obj) {
+ ClassData data;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetObjectSize(obj, &data.size))) {
+ return;
+ }
+ data.serial = kClassDataSerialBase + sClassDataSerial++;
+ // Remove old element, if it exists.
+ auto old = sClassData.find(tag);
+ if (old != sClassData.end()) {
+ sClassData.erase(old);
+ }
+ // Now insert the new mapping.
+ sClassData.insert(std::pair<jlong, ClassData>(tag, data));
+}
+
class IterationConfig {
public:
IterationConfig() {}
@@ -195,11 +226,17 @@
}
jlong adapted_size = size;
- if (*tag_ptr >= 1000) {
+ if (*tag_ptr != 0) {
// This is a class or interface, the size of which will be dependent on the architecture.
// Do not print the size, but detect known values and "normalize" for the golden file.
- if ((sizeof(void*) == 4 && size == 172) || (sizeof(void*) == 8 && size == 224)) {
- adapted_size = 123;
+ auto it = sClassData.find(*tag_ptr);
+ if (it != sClassData.end()) {
+ const ClassData& class_data = it->second;
+ if (class_data.size == size) {
+ adapted_size = class_data.serial;
+ } else {
+ adapted_size = 0xDEADDEAD;
+ }
}
}
diff --git a/test/913-heaps/src/art/Test913.java b/test/913-heaps/src/art/Test913.java
index b999001..4fffa88 100644
--- a/test/913-heaps/src/art/Test913.java
+++ b/test/913-heaps/src/art/Test913.java
@@ -404,17 +404,22 @@
private static void tagClasses(Verifier v) {
setTag(A.class, 1000);
+ registerClass(1000, A.class);
setTag(B.class, 1001);
+ registerClass(1001, B.class);
v.add("1001@0", "1000@0"); // B.class --(superclass)--> A.class.
setTag(C.class, 1002);
+ registerClass(1002, C.class);
v.add("1002@0", "1001@0"); // C.class --(superclass)--> B.class.
v.add("1002@0", "2001@0"); // C.class --(interface)--> I2.class.
setTag(I1.class, 2000);
+ registerClass(2000, I1.class);
setTag(I2.class, 2001);
+ registerClass(2001, I2.class);
v.add("2001@0", "2000@0"); // I2.class --(interface)--> I1.class.
}
@@ -751,4 +756,6 @@
public static native String followReferencesPrimitiveFields(Object initialObject);
private static native void iterateThroughHeapExt();
+
+ private static native void registerClass(long tag, Object obj);
}
diff --git a/test/Android.bp b/test/Android.bp
index 2a88af1..d56c0b5 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -250,6 +250,7 @@
"ti-agent/common_helper.cc",
"ti-agent/frame_pop_helper.cc",
"ti-agent/locals_helper.cc",
+ "ti-agent/monitors_helper.cc",
"ti-agent/redefinition_helper.cc",
"ti-agent/suspension_helper.cc",
"ti-agent/stack_trace_helper.cc",
@@ -299,7 +300,9 @@
"1922-owned-monitors-info/owned_monitors.cc",
"1924-frame-pop-toggle/frame_pop_toggle.cc",
"1926-missed-frame-pop/frame_pop_missed.cc",
- "1927-exception-event/exception_event.cc"
+ "1927-exception-event/exception_event.cc",
+ "1930-monitor-info/monitor.cc",
+ "1932-monitor-events-misc/monitor_misc.cc"
],
shared_libs: [
"libbase",
@@ -349,6 +352,7 @@
],
shared_libs: [
"libbase",
+ "slicer",
],
header_libs: ["libopenjdkjvmti_headers"],
}
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 90e2600..c16c487 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -422,10 +422,7 @@
if [[ "$JVMTI_REDEFINE_STRESS" = "y" ]]; then
# We really cannot do this on RI so don't both passing it in that case.
if [[ "$USE_JVM" = "n" ]]; then
- file_1=$(mktemp --tmpdir=${DEX_LOCATION})
- file_2=$(mktemp --tmpdir=${DEX_LOCATION})
- # TODO Remove need for DEXTER_BINARY!
- agent_args="${agent_args},redefine,${DEXTER_BINARY},${file_1},${file_2}"
+ agent_args="${agent_args},redefine"
fi
fi
if [[ "$JVMTI_FIELD_STRESS" = "y" ]]; then
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 84758c9..252d13a 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -100,6 +100,14 @@
"description": ["This test sometimes runs out of memory initializing the boot classpath."]
},
{
+ "tests": "164-resolution-trampoline-dex-cache",
+ "variant": "interp-ac | interpreter",
+ "description": ["This test requires AOT mixed with JIT and enables the JIT by the ",
+ "runtime option -Xusejit:true. This conflicts with -Xint passed for ",
+ "interpreter configurations (interp-ac | interpreter). The 'jit' ",
+ "configuration succeeds even though it does not test anything useful."]
+ },
+ {
"tests": ["908-gc-start-finish",
"913-heaps"],
"variant": "gcstress",
diff --git a/test/run-test b/test/run-test
index 9996986..79f3d1e 100755
--- a/test/run-test
+++ b/test/run-test
@@ -810,6 +810,7 @@
good="no"
good_build="yes"
good_run="yes"
+export TEST_RUNTIME="${runtime}"
if [ "$dev_mode" = "yes" ]; then
"./${build}" $build_args 2>&1
build_exit="$?"
diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc
index 7280102..4ca2d5d 100644
--- a/test/ti-agent/jvmti_helper.cc
+++ b/test/ti-agent/jvmti_helper.cc
@@ -49,8 +49,8 @@
.can_get_bytecodes = 1,
.can_get_synthetic_attribute = 1,
.can_get_owned_monitor_info = 0,
- .can_get_current_contended_monitor = 0,
- .can_get_monitor_info = 0,
+ .can_get_current_contended_monitor = 1,
+ .can_get_monitor_info = 1,
.can_pop_frame = 0,
.can_redefine_classes = 1,
.can_signal_thread = 0,
diff --git a/test/ti-agent/monitors_helper.cc b/test/ti-agent/monitors_helper.cc
new file mode 100644
index 0000000..81d4cdc
--- /dev/null
+++ b/test/ti-agent/monitors_helper.cc
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include <vector>
+
+#include "jvmti_helper.h"
+#include "jni_helper.h"
+#include "test_env.h"
+#include "scoped_local_ref.h"
+
+namespace art {
+namespace common_monitors {
+
+extern "C" JNIEXPORT jobject JNICALL Java_art_Monitors_getCurrentContendedMonitor(
+ JNIEnv* env, jclass, jthread thr) {
+ jobject out = nullptr;
+ JvmtiErrorToException(env, jvmti_env, jvmti_env->GetCurrentContendedMonitor(thr, &out));
+ return out;
+}
+
+extern "C" JNIEXPORT jobject JNICALL Java_art_Monitors_getObjectMonitorUsage(
+ JNIEnv* env, jclass, jobject obj) {
+ ScopedLocalRef<jclass> klass(env, env->FindClass("art/Monitors$MonitorUsage"));
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+ jmethodID constructor = env->GetMethodID(
+ klass.get(),
+ "<init>",
+ "(Ljava/lang/Object;Ljava/lang/Thread;I[Ljava/lang/Thread;[Ljava/lang/Thread;)V");
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+ jvmtiMonitorUsage usage;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetObjectMonitorUsage(obj, &usage))) {
+ return nullptr;
+ }
+ jobjectArray wait = CreateObjectArray(env, usage.waiter_count, "java/lang/Thread",
+ [&](jint i) { return usage.waiters[i]; });
+ if (env->ExceptionCheck()) {
+ jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(usage.waiters));
+ jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(usage.notify_waiters));
+ return nullptr;
+ }
+ jobjectArray notify_wait = CreateObjectArray(env, usage.notify_waiter_count, "java/lang/Thread",
+ [&](jint i) { return usage.notify_waiters[i]; });
+ if (env->ExceptionCheck()) {
+ jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(usage.waiters));
+ jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(usage.notify_waiters));
+ return nullptr;
+ }
+ return env->NewObject(klass.get(), constructor,
+ obj, usage.owner, usage.entry_count, wait, notify_wait);
+}
+
+struct MonitorsData {
+ jclass test_klass;
+ jmethodID monitor_enter;
+ jmethodID monitor_entered;
+ jmethodID monitor_wait;
+ jmethodID monitor_waited;
+ jclass monitor_klass;
+};
+
+static void monitorEnterCB(jvmtiEnv* jvmti,
+ JNIEnv* jnienv,
+ jthread thr,
+ jobject obj) {
+ MonitorsData* data = nullptr;
+ if (JvmtiErrorToException(jnienv, jvmti,
+ jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+ return;
+ }
+ if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+ return;
+ }
+ jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_enter, thr, obj);
+}
+static void monitorEnteredCB(jvmtiEnv* jvmti,
+ JNIEnv* jnienv,
+ jthread thr,
+ jobject obj) {
+ MonitorsData* data = nullptr;
+ if (JvmtiErrorToException(jnienv, jvmti,
+ jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+ return;
+ }
+ if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+ return;
+ }
+ jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_entered, thr, obj);
+}
+static void monitorWaitCB(jvmtiEnv* jvmti,
+ JNIEnv* jnienv,
+ jthread thr,
+ jobject obj,
+ jlong timeout) {
+ MonitorsData* data = nullptr;
+ if (JvmtiErrorToException(jnienv, jvmti,
+ jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+ return;
+ }
+ if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+ return;
+ }
+ jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_wait, thr, obj, timeout);
+}
+static void monitorWaitedCB(jvmtiEnv* jvmti,
+ JNIEnv* jnienv,
+ jthread thr,
+ jobject obj,
+ jboolean timed_out) {
+ MonitorsData* data = nullptr;
+ if (JvmtiErrorToException(jnienv, jvmti,
+ jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+ return;
+ }
+ if (!jnienv->IsInstanceOf(obj, data->monitor_klass)) {
+ return;
+ }
+ jnienv->CallStaticVoidMethod(data->test_klass, data->monitor_waited, thr, obj, timed_out);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_art_Monitors_setupMonitorEvents(
+ JNIEnv* env,
+ jclass,
+ jclass test_klass,
+ jobject monitor_enter,
+ jobject monitor_entered,
+ jobject monitor_wait,
+ jobject monitor_waited,
+ jclass monitor_klass,
+ jthread thr) {
+ MonitorsData* data = nullptr;
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->Allocate(sizeof(MonitorsData),
+ reinterpret_cast<unsigned char**>(&data)))) {
+ return;
+ }
+ jvmtiCapabilities caps;
+ memset(&caps, 0, sizeof(caps));
+ caps.can_generate_monitor_events = 1;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) {
+ return;
+ }
+
+ memset(data, 0, sizeof(MonitorsData));
+ data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(test_klass));
+ data->monitor_enter = env->FromReflectedMethod(monitor_enter);
+ data->monitor_entered = env->FromReflectedMethod(monitor_entered);
+ data->monitor_wait = env->FromReflectedMethod(monitor_wait);
+ data->monitor_waited = env->FromReflectedMethod(monitor_waited);
+ data->monitor_klass = reinterpret_cast<jclass>(env->NewGlobalRef(monitor_klass));
+ MonitorsData* old_data = nullptr;
+ if (JvmtiErrorToException(env, jvmti_env,
+ jvmti_env->GetEnvironmentLocalStorage(
+ reinterpret_cast<void**>(&old_data)))) {
+ return;
+ } else if (old_data != nullptr && old_data->test_klass != nullptr) {
+ ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+ env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
+ return;
+ }
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
+ return;
+ }
+
+ jvmtiEventCallbacks cb;
+ memset(&cb, 0, sizeof(cb));
+ cb.MonitorContendedEnter = monitorEnterCB;
+ cb.MonitorContendedEntered = monitorEnteredCB;
+ cb.MonitorWait = monitorWaitCB;
+ cb.MonitorWaited = monitorWaitedCB;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {
+ return;
+ }
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTER, thr))) {
+ return;
+ }
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_ENABLE, JVMTI_EVENT_MONITOR_CONTENDED_ENTERED, thr))) {
+ return;
+ }
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAIT, thr))) {
+ return;
+ }
+ if (JvmtiErrorToException(env,
+ jvmti_env,
+ jvmti_env->SetEventNotificationMode(
+ JVMTI_ENABLE, JVMTI_EVENT_MONITOR_WAITED, thr))) {
+ return;
+ }
+}
+
+} // namespace common_monitors
+} // namespace art
+
diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc
index 5d7c2f3..6e29e36 100644
--- a/test/ti-stress/stress.cc
+++ b/test/ti-stress/stress.cc
@@ -28,15 +28,31 @@
#include "jvmti.h"
#include "utils.h"
+#pragma clang diagnostic push
+// slicer defines its own CHECK. b/65422458
+#pragma push_macro("CHECK")
+#undef CHECK
+
+// Slicer's headers have code that triggers these warnings. b/65298177
+#pragma clang diagnostic ignored "-Wunused-parameter"
+#pragma clang diagnostic ignored "-Wsign-compare"
+#include "code_ir.h"
+#include "control_flow_graph.h"
+#include "dex_ir.h"
+#include "dex_ir_builder.h"
+#include "instrumentation.h"
+#include "reader.h"
+#include "writer.h"
+
+#pragma pop_macro("CHECK")
+#pragma clang diagnostic pop
+
namespace art {
// Should we do a 'full_rewrite' with this test?
static constexpr bool kDoFullRewrite = true;
struct StressData {
- std::string dexter_cmd;
- std::string out_temp_dex;
- std::string in_temp_dex;
bool vm_class_loader_initialized;
bool trace_stress;
bool redefine_stress;
@@ -44,51 +60,60 @@
bool step_stress;
};
-static void WriteToFile(const std::string& fname, jint data_len, const unsigned char* data) {
- std::ofstream file(fname, std::ios::binary | std::ios::out | std::ios::trunc);
- file.write(reinterpret_cast<const char*>(data), data_len);
- file.flush();
-}
-
-static bool ReadIntoBuffer(const std::string& fname, /*out*/std::vector<unsigned char>* data) {
- std::ifstream file(fname, std::ios::binary | std::ios::in);
- file.seekg(0, std::ios::end);
- size_t len = file.tellg();
- data->resize(len);
- file.seekg(0);
- file.read(reinterpret_cast<char*>(data->data()), len);
- return len != 0;
-}
-
-// TODO rewrite later.
-static bool DoExtractClassFromData(StressData* data,
- const std::string& class_name,
+static bool DoExtractClassFromData(jvmtiEnv* env,
+ const std::string& descriptor,
jint in_len,
const unsigned char* in_data,
- /*out*/std::vector<unsigned char>* dex) {
- // Write the dex file into a temporary file.
- WriteToFile(data->in_temp_dex, in_len, in_data);
- // Clear out file so even if something suppresses the exit value we will still detect dexter
- // failure.
- WriteToFile(data->out_temp_dex, 0, nullptr);
- // Have dexter do the extraction.
- std::vector<std::string> args;
- args.push_back(data->dexter_cmd);
- if (kDoFullRewrite) {
- args.push_back("-x");
- args.push_back("full_rewrite");
- }
- args.push_back("-e");
- args.push_back(class_name);
- args.push_back("-o");
- args.push_back(data->out_temp_dex);
- args.push_back(data->in_temp_dex);
- std::string error;
- if (ExecAndReturnCode(args, &error) != 0) {
- LOG(ERROR) << "unable to execute dexter: " << error;
+ /*out*/jint* out_len,
+ /*out*/unsigned char** out_data) {
+ dex::Reader reader(in_data, in_len);
+ dex::u4 class_idx = reader.FindClassIndex(descriptor.c_str());
+ if (class_idx != dex::kNoIndex) {
+ reader.CreateClassIr(class_idx);
+ } else {
+ LOG(ERROR) << "ERROR: Can't find class " << descriptor;
return false;
}
- return ReadIntoBuffer(data->out_temp_dex, dex);
+ auto dex_ir = reader.GetIr();
+
+ if (kDoFullRewrite) {
+ for (auto& ir_method : dex_ir->encoded_methods) {
+ if (ir_method->code != nullptr) {
+ lir::CodeIr code_ir(ir_method.get(), dex_ir);
+ lir::ControlFlowGraph cfg_compact(&code_ir, false);
+ lir::ControlFlowGraph cfg_verbose(&code_ir, true);
+ code_ir.Assemble();
+ }
+ }
+ }
+ dex::Writer writer(dex_ir);
+
+ struct Allocator : public dex::Writer::Allocator {
+ explicit Allocator(jvmtiEnv* jvmti_env) : jvmti_env_(jvmti_env) {}
+ virtual void* Allocate(size_t size) {
+ unsigned char* out = nullptr;
+ if (JVMTI_ERROR_NONE != jvmti_env_->Allocate(size, &out)) {
+ return nullptr;
+ } else {
+ return out;
+ }
+ }
+ virtual void Free(void* ptr) {
+ jvmti_env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
+ }
+ private:
+ jvmtiEnv* jvmti_env_;
+ };
+ Allocator alloc(env);
+ size_t res_len;
+ unsigned char* res = writer.CreateImage(&alloc, &res_len);
+ if (res != nullptr) {
+ *out_data = res;
+ *out_len = res_len;
+ return true;
+ } else {
+ return false;
+ }
}
class ScopedThreadInfo {
@@ -615,10 +640,10 @@
jint* new_class_data_len,
unsigned char** new_class_data) {
std::vector<unsigned char> out;
- std::string name_str(name);
- // Make the jvmti semi-descriptor into the java style descriptor (though with $ for inner
- // classes).
- std::replace(name_str.begin(), name_str.end(), '/', '.');
+ // Make the jvmti semi-descriptor into the full descriptor.
+ std::string name_str("L");
+ name_str += name;
+ name_str += ";";
StressData* data = nullptr;
CHECK_EQ(jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)),
JVMTI_ERROR_NONE);
@@ -626,15 +651,11 @@
LOG(WARNING) << "Ignoring load of class " << name << " because VMClassLoader is not yet "
<< "initialized. Transforming this class could cause spurious test failures.";
return;
- } else if (DoExtractClassFromData(data, name_str, class_data_len, class_data, /*out*/ &out)) {
+ } else if (DoExtractClassFromData(jvmti, name_str, class_data_len, class_data,
+ /*out*/ new_class_data_len, /*out*/ new_class_data)) {
LOG(INFO) << "Extracted class: " << name;
- unsigned char* new_data;
- CHECK_EQ(JVMTI_ERROR_NONE, jvmti->Allocate(out.size(), &new_data));
- memcpy(new_data, out.data(), out.size());
- *new_class_data_len = static_cast<jint>(out.size());
- *new_class_data = new_data;
} else {
- std::cerr << "Unable to extract class " << name_str << std::endl;
+ std::cerr << "Unable to extract class " << name << std::endl;
*new_class_data_len = 0;
*new_class_data = nullptr;
}
@@ -653,7 +674,7 @@
}
// Options are
-// jvmti-stress,[redefine,${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2},][trace,][field]
+// jvmti-stress,[redefine,][trace,][field]
static void ReadOptions(StressData* data, char* options) {
std::string ops(options);
CHECK_EQ(GetOption(ops), "jvmti-stress") << "Options should start with jvmti-stress";
@@ -668,12 +689,6 @@
data->field_stress = true;
} else if (cur == "redefine") {
data->redefine_stress = true;
- ops = AdvanceOption(ops);
- data->dexter_cmd = GetOption(ops);
- ops = AdvanceOption(ops);
- data->in_temp_dex = GetOption(ops);
- ops = AdvanceOption(ops);
- data->out_temp_dex = GetOption(ops);
} else {
LOG(FATAL) << "Unknown option: " << GetOption(ops);
}
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index 8262910..f4926aa 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -152,7 +152,7 @@
* ignored otherwise.
*/
private static void printFields(Doc doc, Query query, String id, boolean diff,
- List<FieldValue> current, List<FieldValue> baseline) {
+ Iterable<FieldValue> current, Iterable<FieldValue> baseline) {
if (!diff) {
// To avoid having to special case when diff is disabled, always diff
diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java
index fd226c2..1a8f018 100644
--- a/tools/ahat/src/ObjectsHandler.java
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -37,10 +37,9 @@
@Override
public void handle(Doc doc, Query query) throws IOException {
int id = query.getInt("id", 0);
- int depth = query.getInt("depth", 0);
String className = query.get("class", null);
String heapName = query.get("heap", null);
- Site site = mSnapshot.getSite(id, depth);
+ Site site = mSnapshot.getSite(id);
List<AhatInstance> insts = new ArrayList<AhatInstance>();
site.getObjects(heapName, className, insts);
diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java
index 7a831d3..543eaa3 100644
--- a/tools/ahat/src/SiteHandler.java
+++ b/tools/ahat/src/SiteHandler.java
@@ -21,6 +21,7 @@
import com.android.ahat.heapdump.Site;
import com.android.ahat.heapdump.Sort;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -39,8 +40,7 @@
@Override
public void handle(Doc doc, Query query) throws IOException {
int id = query.getInt("id", 0);
- int depth = query.getInt("depth", 0);
- Site site = mSnapshot.getSite(id, depth);
+ Site site = mSnapshot.getSite(id);
doc.title("Site");
doc.big(Summarizer.summarize(site));
@@ -49,7 +49,8 @@
SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
doc.section("Sites Called from Here");
- List<Site> children = site.getChildren();
+ List<Site> children = new ArrayList<Site>(site.getChildren());
+
if (children.isEmpty()) {
doc.println(DocString.text("(none)"));
} else {
@@ -99,8 +100,8 @@
String className = info.getClassName();
SizeTable.row(doc, info.numBytes, baseinfo.numBytes,
DocString.link(
- DocString.formattedUri("objects?id=%d&depth=%d&heap=%s&class=%s",
- site.getId(), site.getDepth(), info.heap.getName(), className),
+ DocString.formattedUri("objects?id=%d&heap=%s&class=%s",
+ site.getId(), info.heap.getName(), className),
DocString.format("%,14d", info.numInstances)),
DocString.delta(false, false, info.numInstances, baseinfo.numInstances),
DocString.text(info.heap.getName()),
diff --git a/tools/ahat/src/Summarizer.java b/tools/ahat/src/Summarizer.java
index 3e9da31..50b2e4b 100644
--- a/tools/ahat/src/Summarizer.java
+++ b/tools/ahat/src/Summarizer.java
@@ -130,7 +130,7 @@
if (site.getLineNumber() > 0) {
text.append(":").append(Integer.toString(site.getLineNumber()));
}
- URI uri = DocString.formattedUri("site?id=%d&depth=%d", site.getId(), site.getDepth());
+ URI uri = DocString.formattedUri("site?id=%d", site.getId());
return DocString.link(uri, text);
}
}
diff --git a/tools/ahat/src/heapdump/AhatArrayInstance.java b/tools/ahat/src/heapdump/AhatArrayInstance.java
index 6d4485d..8d23276 100644
--- a/tools/ahat/src/heapdump/AhatArrayInstance.java
+++ b/tools/ahat/src/heapdump/AhatArrayInstance.java
@@ -58,8 +58,7 @@
}
@Override public Value get(int index) {
- AhatInstance obj = insts[index];
- return obj == null ? null : new Value(insts[index]);
+ return Value.pack(insts[index]);
}
};
break;
@@ -73,7 +72,7 @@
}
@Override public Value get(int index) {
- return new Value(chars[index]);
+ return Value.pack(chars[index]);
}
};
break;
@@ -87,7 +86,7 @@
}
@Override public Value get(int index) {
- return new Value(bytes[index]);
+ return Value.pack(bytes[index]);
}
};
break;
@@ -100,8 +99,7 @@
}
@Override public Value get(int index) {
- Object obj = values[index];
- return obj == null ? null : new Value(obj);
+ return Value.pack(values[index]);
}
};
break;
@@ -130,7 +128,7 @@
}
@Override
- ReferenceIterator getReferences() {
+ Iterable<Reference> getReferences() {
// The list of references will be empty if this is a primitive array.
List<Reference> refs = Collections.emptyList();
if (!mValues.isEmpty()) {
@@ -155,7 +153,7 @@
};
}
}
- return new ReferenceIterator(refs);
+ return new SkipNullsIterator(refs);
}
@Override public boolean isArrayInstance() {
diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java
index 2115923..f7d8431 100644
--- a/tools/ahat/src/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -19,12 +19,16 @@
import com.android.tools.perflib.heap.ClassInstance;
import com.android.tools.perflib.heap.Instance;
import java.awt.image.BufferedImage;
-import java.util.AbstractList;
-import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
+import java.util.NoSuchElementException;
public class AhatClassInstance extends AhatInstance {
- private FieldValue[] mFieldValues;
+ // Instance fields of the object. These are stored in order of the instance
+ // field descriptors from the class object, starting with this class first,
+ // followed by the super class, and so on. We store the values separate from
+ // the field types and names to save memory.
+ private Value[] mFields;
public AhatClassInstance(long id) {
super(id);
@@ -35,18 +39,14 @@
ClassInstance classInst = (ClassInstance)inst;
List<ClassInstance.FieldValue> fieldValues = classInst.getValues();
- mFieldValues = new FieldValue[fieldValues.size()];
- for (int i = 0; i < mFieldValues.length; i++) {
- ClassInstance.FieldValue field = fieldValues.get(i);
- String name = field.getField().getName();
- String type = field.getField().getType().toString();
- Value value = snapshot.getValue(field.getValue());
- mFieldValues[i] = new FieldValue(name, type, value);
+ mFields = new Value[fieldValues.size()];
+ for (int i = 0; i < mFields.length; i++) {
+ mFields[i] = snapshot.getValue(fieldValues.get(i).getValue());
}
}
@Override public Value getField(String fieldName) {
- for (FieldValue field : mFieldValues) {
+ for (FieldValue field : getInstanceFields()) {
if (fieldName.equals(field.name)) {
return field.value;
}
@@ -90,36 +90,21 @@
/**
* Returns the list of class instance fields for this instance.
*/
- public List<FieldValue> getInstanceFields() {
- return Arrays.asList(mFieldValues);
+ public Iterable<FieldValue> getInstanceFields() {
+ return new InstanceFieldIterator(mFields, getClassObj());
}
@Override
- ReferenceIterator getReferences() {
- List<Reference> refs = new AbstractList<Reference>() {
- @Override
- public int size() {
- return mFieldValues.length;
- }
-
- @Override
- public Reference get(int index) {
- FieldValue field = mFieldValues[index];
- Value value = field.value;
- if (value != null && value.isAhatInstance()) {
- boolean strong = !field.name.equals("referent")
- || !isInstanceOfClass("java.lang.ref.Reference");
- AhatInstance ref = value.asAhatInstance();
- return new Reference(AhatClassInstance.this, "." + field.name, ref, strong);
- }
- return null;
- }
- };
- return new ReferenceIterator(refs);
+ Iterable<Reference> getReferences() {
+ if (isInstanceOfClass("java.lang.ref.Reference")) {
+ return new WeakReferentReferenceIterator();
+ }
+ return new StrongReferenceIterator();
}
/**
- * Returns true if this is an instance of a class with the given name.
+ * Returns true if this is an instance of a (subclass of a) class with the
+ * given name.
*/
private boolean isInstanceOfClass(String className) {
AhatClassObj cls = getClassObj();
@@ -262,4 +247,131 @@
bitmap.setRGB(0, 0, info.width, info.height, abgr, 0, info.width);
return bitmap;
}
+
+ private static class InstanceFieldIterator implements Iterable<FieldValue>,
+ Iterator<FieldValue> {
+ // The complete list of instance field values to iterate over, including
+ // superclass field values.
+ private Value[] mValues;
+ private int mValueIndex;
+
+ // The list of field descriptors specific to the current class in the
+ // class hierarchy, not including superclass field descriptors.
+ // mFields and mFieldIndex are reset each time we walk up to the next
+ // superclass in the call hierarchy.
+ private Field[] mFields;
+ private int mFieldIndex;
+ private AhatClassObj mNextClassObj;
+
+ public InstanceFieldIterator(Value[] values, AhatClassObj classObj) {
+ mValues = values;
+ mFields = classObj.getInstanceFields();
+ mValueIndex = 0;
+ mFieldIndex = 0;
+ mNextClassObj = classObj.getSuperClassObj();
+ }
+
+ @Override
+ public boolean hasNext() {
+ // If we have reached the end of the fields in the current class,
+ // continue walking up the class hierarchy to get superclass fields as
+ // well.
+ while (mFieldIndex == mFields.length && mNextClassObj != null) {
+ mFields = mNextClassObj.getInstanceFields();
+ mFieldIndex = 0;
+ mNextClassObj = mNextClassObj.getSuperClassObj();
+ }
+ return mFieldIndex < mFields.length;
+ }
+
+ @Override
+ public FieldValue next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ Field field = mFields[mFieldIndex++];
+ Value value = mValues[mValueIndex++];
+ return new FieldValue(field.name, field.type, value);
+ }
+
+ @Override
+ public Iterator<FieldValue> iterator() {
+ return this;
+ }
+ }
+
+ /**
+ * A Reference iterator that iterates over the fields of this instance
+ * assuming all field references are strong references.
+ */
+ private class StrongReferenceIterator implements Iterable<Reference>,
+ Iterator<Reference> {
+ private Iterator<FieldValue> mIter = getInstanceFields().iterator();
+ private Reference mNext = null;
+
+ @Override
+ public boolean hasNext() {
+ while (mNext == null && mIter.hasNext()) {
+ FieldValue field = mIter.next();
+ if (field.value != null && field.value.isAhatInstance()) {
+ AhatInstance ref = field.value.asAhatInstance();
+ mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, true);
+ }
+ }
+ return mNext != null;
+ }
+
+ @Override
+ public Reference next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ Reference next = mNext;
+ mNext = null;
+ return next;
+ }
+
+ @Override
+ public Iterator<Reference> iterator() {
+ return this;
+ }
+ }
+
+ /**
+ * A Reference iterator that iterates over the fields of a subclass of
+ * java.lang.ref.Reference, where the 'referent' field is considered weak.
+ */
+ private class WeakReferentReferenceIterator implements Iterable<Reference>,
+ Iterator<Reference> {
+ private Iterator<FieldValue> mIter = getInstanceFields().iterator();
+ private Reference mNext = null;
+
+ @Override
+ public boolean hasNext() {
+ while (mNext == null && mIter.hasNext()) {
+ FieldValue field = mIter.next();
+ if (field.value != null && field.value.isAhatInstance()) {
+ boolean strong = !field.name.equals("referent");
+ AhatInstance ref = field.value.asAhatInstance();
+ mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, strong);
+ }
+ }
+ return mNext != null;
+ }
+
+ @Override
+ public Reference next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ Reference next = mNext;
+ mNext = null;
+ return next;
+ }
+
+ @Override
+ public Iterator<Reference> iterator() {
+ return this;
+ }
+ }
}
diff --git a/tools/ahat/src/heapdump/AhatClassObj.java b/tools/ahat/src/heapdump/AhatClassObj.java
index 052d7a8..08c7097 100644
--- a/tools/ahat/src/heapdump/AhatClassObj.java
+++ b/tools/ahat/src/heapdump/AhatClassObj.java
@@ -17,7 +17,6 @@
package com.android.ahat.heapdump;
import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
import com.android.tools.perflib.heap.Instance;
import java.util.AbstractList;
import java.util.Arrays;
@@ -30,6 +29,7 @@
private AhatClassObj mSuperClassObj;
private AhatInstance mClassLoader;
private FieldValue[] mStaticFieldValues;
+ private Field[] mInstanceFields;
public AhatClassObj(long id) {
super(id);
@@ -51,15 +51,22 @@
mClassLoader = snapshot.findInstance(loader.getId());
}
- Collection<Map.Entry<Field, Object>> fieldValues = classObj.getStaticFieldValues().entrySet();
+ Collection<Map.Entry<com.android.tools.perflib.heap.Field, Object>> fieldValues
+ = classObj.getStaticFieldValues().entrySet();
mStaticFieldValues = new FieldValue[fieldValues.size()];
int index = 0;
- for (Map.Entry<Field, Object> field : fieldValues) {
+ for (Map.Entry<com.android.tools.perflib.heap.Field, Object> field : fieldValues) {
String name = field.getKey().getName();
String type = field.getKey().getType().toString();
Value value = snapshot.getValue(field.getValue());
mStaticFieldValues[index++] = new FieldValue(name, type, value);
}
+
+ com.android.tools.perflib.heap.Field[] fields = classObj.getFields();
+ mInstanceFields = new Field[fields.length];
+ for (int i = 0; i < fields.length; i++) {
+ mInstanceFields[i] = new Field(fields[i].getName(), fields[i].getType().toString());
+ }
}
/**
@@ -90,8 +97,15 @@
return Arrays.asList(mStaticFieldValues);
}
+ /**
+ * Returns the fields of instances of this class.
+ */
+ public Field[] getInstanceFields() {
+ return mInstanceFields;
+ }
+
@Override
- ReferenceIterator getReferences() {
+ Iterable<Reference> getReferences() {
List<Reference> refs = new AbstractList<Reference>() {
@Override
public int size() {
@@ -108,7 +122,7 @@
return null;
}
};
- return new ReferenceIterator(refs);
+ return new SkipNullsIterator(refs);
}
@Override public boolean isClassObj() {
diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/heapdump/AhatInstance.java
index 39a844a..0e78558 100644
--- a/tools/ahat/src/heapdump/AhatInstance.java
+++ b/tools/ahat/src/heapdump/AhatInstance.java
@@ -72,6 +72,7 @@
* snapshot.findInstance have been initialized yet.
*/
void initialize(AhatSnapshot snapshot, Instance inst, Site site) {
+ site.addInstance(this);
mSize = new Size(inst.getSize(), 0);
mHeap = snapshot.getHeap(inst.getHeap().getName());
@@ -147,7 +148,7 @@
* Returns an iterator over the references this AhatInstance has to other
* AhatInstances.
*/
- abstract ReferenceIterator getReferences();
+ abstract Iterable<Reference> getReferences();
/**
* Returns true if this instance is marked as a root instance.
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
index 2b3e056..8b4c679 100644
--- a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
@@ -29,10 +29,6 @@
baseline.setBaseline(this);
}
- @Override public Size getSize() {
- return Size.ZERO;
- }
-
@Override public Size getRetainedSize(AhatHeap heap) {
return Size.ZERO;
}
@@ -68,4 +64,8 @@
@Override public AhatInstance getClassLoader() {
return getBaseline().asClassObj().getClassLoader().getBaseline();
}
+
+ @Override public Field[] getInstanceFields() {
+ return getBaseline().asClassObj().getInstanceFields();
+ }
}
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
index d797b11..9abc952 100644
--- a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
@@ -65,8 +65,8 @@
}
@Override
- ReferenceIterator getReferences() {
+ Iterable<Reference> getReferences() {
List<Reference> refs = Collections.emptyList();
- return new ReferenceIterator(refs);
+ return refs;
}
}
diff --git a/tools/ahat/src/heapdump/AhatSnapshot.java b/tools/ahat/src/heapdump/AhatSnapshot.java
index 7df78c5..1b2cf3c 100644
--- a/tools/ahat/src/heapdump/AhatSnapshot.java
+++ b/tools/ahat/src/heapdump/AhatSnapshot.java
@@ -35,7 +35,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -48,9 +47,6 @@
// List of all ahat instances stored in increasing order by id.
private final List<AhatInstance> mInstances = new ArrayList<AhatInstance>();
- // Map from class name to class object.
- private final Map<String, AhatClassObj> mClasses = new HashMap<String, AhatClassObj>();
-
private final List<AhatHeap> mHeaps = new ArrayList<AhatHeap>();
private AhatSnapshot mBaseline = this;
@@ -113,7 +109,6 @@
} else if (inst instanceof ClassObj) {
AhatClassObj classObj = new AhatClassObj(id);
mInstances.add(classObj);
- mClasses.put(((ClassObj)inst).getClassName(), classObj);
}
return true;
}
@@ -145,8 +140,7 @@
if (stack != null) {
frames = stack.getFrames();
}
- Site site = mRootSite.add(frames, frames == null ? 0 : frames.length, ahat);
- ahat.initialize(this, inst, site);
+ ahat.initialize(this, inst, mRootSite.getSite(frames));
Long registeredNativeSize = registeredNative.get(inst);
if (registeredNativeSize != null) {
@@ -177,7 +171,7 @@
heap.addToSize(superRoot.getRetainedSize(heap));
}
- mRootSite.computeObjectsInfos(mHeaps.size());
+ mRootSite.prepareForUse(0, mHeaps.size());
}
/**
@@ -213,15 +207,6 @@
}
/**
- * Returns the class object for the class with given name.
- * Returns null if there is no class object for the given name.
- * Note: This method is exposed for testing purposes.
- */
- public AhatClassObj findClass(String name) {
- return mClasses.get(name);
- }
-
- /**
* Returns the heap with the given name, if any.
* Returns null if no heap with the given name could be found.
*/
@@ -260,19 +245,11 @@
return mRootSite;
}
- // Get the site associated with the given id and depth.
+ // Get the site associated with the given id.
// Returns the root site if no such site found.
- public Site getSite(int id, int depth) {
- AhatInstance obj = findInstance(id);
- if (obj == null) {
- return mRootSite;
- }
-
- Site site = obj.getSite();
- for (int i = 0; i < depth && site.getParent() != null; i++) {
- site = site.getParent();
- }
- return site;
+ public Site getSite(long id) {
+ Site site = mRootSite.findSite(id);
+ return site == null ? mRootSite : site;
}
// Return the Value for the given perflib value object.
@@ -280,7 +257,7 @@
if (value instanceof Instance) {
value = findInstance(((Instance)value).getId());
}
- return value == null ? null : new Value(value);
+ return Value.pack(value);
}
public void setBaseline(AhatSnapshot baseline) {
diff --git a/tools/ahat/src/heapdump/Diff.java b/tools/ahat/src/heapdump/Diff.java
index 489f709..98c7e58 100644
--- a/tools/ahat/src/heapdump/Diff.java
+++ b/tools/ahat/src/heapdump/Diff.java
@@ -333,7 +333,7 @@
// Add placeholders to their corresponding sites.
// This requires the sites have already been diffed.
for (AhatInstance placeholder : placeholders) {
- placeholder.getBaseline().getSite().getBaseline().addPlaceHolderInstance(placeholder);
+ placeholder.getBaseline().getSite().getBaseline().addInstance(placeholder);
}
}
}
diff --git a/tools/ahat/src/heapdump/DiffFields.java b/tools/ahat/src/heapdump/DiffFields.java
index dd73456..e3c671f 100644
--- a/tools/ahat/src/heapdump/DiffFields.java
+++ b/tools/ahat/src/heapdump/DiffFields.java
@@ -17,7 +17,6 @@
package com.android.ahat.heapdump;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -29,14 +28,19 @@
public class DiffFields {
/**
* Return the result of diffing two collections of field values.
- * The input collections 'current' and 'baseline' are not modified by this function.
*/
- public static List<DiffedFieldValue> diff(Collection<FieldValue> current,
- Collection<FieldValue> baseline) {
- List<FieldValue> currentSorted = new ArrayList<FieldValue>(current);
+ public static List<DiffedFieldValue> diff(Iterable<FieldValue> current,
+ Iterable<FieldValue> baseline) {
+ List<FieldValue> currentSorted = new ArrayList<FieldValue>();
+ for (FieldValue field : current) {
+ currentSorted.add(field);
+ }
Collections.sort(currentSorted, FOR_DIFF);
- List<FieldValue> baselineSorted = new ArrayList<FieldValue>(baseline);
+ List<FieldValue> baselineSorted = new ArrayList<FieldValue>();
+ for (FieldValue field : baseline) {
+ baselineSorted.add(field);
+ }
Collections.sort(baselineSorted, FOR_DIFF);
// Merge the two lists to form the diffed list of fields.
diff --git a/tools/ahat/src/heapdump/DominatorReferenceIterator.java b/tools/ahat/src/heapdump/DominatorReferenceIterator.java
index ce2e6ef..0b99e49 100644
--- a/tools/ahat/src/heapdump/DominatorReferenceIterator.java
+++ b/tools/ahat/src/heapdump/DominatorReferenceIterator.java
@@ -25,11 +25,11 @@
*/
class DominatorReferenceIterator implements Iterator<AhatInstance>,
Iterable<AhatInstance> {
- private ReferenceIterator mIter;
+ private Iterator<Reference> mIter;
private AhatInstance mNext;
- public DominatorReferenceIterator(ReferenceIterator iter) {
- mIter = iter;
+ public DominatorReferenceIterator(Iterable<Reference> iter) {
+ mIter = iter.iterator();
mNext = null;
}
diff --git a/tools/ahat/src/heapdump/Field.java b/tools/ahat/src/heapdump/Field.java
new file mode 100644
index 0000000..01f87c7
--- /dev/null
+++ b/tools/ahat/src/heapdump/Field.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 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.heapdump;
+
+public class Field {
+ public final String name;
+ public final String type;
+
+ public Field(String name, String type) {
+ this.name = name;
+ this.type = type;
+ }
+}
diff --git a/tools/ahat/src/heapdump/ReferenceIterator.java b/tools/ahat/src/heapdump/ReferenceIterator.java
deleted file mode 100644
index a707fb2..0000000
--- a/tools/ahat/src/heapdump/ReferenceIterator.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 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.heapdump;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-class ReferenceIterator implements Iterator<Reference>,
- Iterable<Reference> {
- private List<Reference> mRefs;
- private int mLength;
- private int mNextIndex;
- private Reference mNext;
-
- /**
- * Construct a ReferenceIterator that iterators over the given list of
- * references. Elements of the given list of references may be null, in
- * which case the ReferenceIterator will skip over them.
- */
- public ReferenceIterator(List<Reference> refs) {
- mRefs = refs;
- mLength = refs.size();
- mNextIndex = 0;
- mNext = null;
- }
-
- @Override
- public boolean hasNext() {
- while (mNext == null && mNextIndex < mLength) {
- mNext = mRefs.get(mNextIndex);
- mNextIndex++;
- }
- return mNext != null;
- }
-
- @Override
- public Reference next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
- Reference next = mNext;
- mNext = null;
- return next;
- }
-
- @Override
- public Iterator<Reference> iterator() {
- return this;
- }
-}
diff --git a/tools/ahat/src/heapdump/Site.java b/tools/ahat/src/heapdump/Site.java
index f0fc5d2..82931f0 100644
--- a/tools/ahat/src/heapdump/Site.java
+++ b/tools/ahat/src/heapdump/Site.java
@@ -19,6 +19,7 @@
import com.android.tools.perflib.heap.StackFrame;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -33,16 +34,20 @@
private String mFilename;
private int mLineNumber;
- // To identify this site, we pick a stack trace that includes the site.
- // mId is the id of an object allocated at that stack trace, and mDepth
- // is the number of calls between this site and the innermost site of
- // allocation of the object with mId.
- // For the root site, mId is 0 and mDepth is 0.
- private long mId;
- private int mDepth;
+ // A unique id to identify this site with. The id is chosen based on a
+ // depth first traversal of the complete site tree, which gives it the
+ // following desired properties:
+ // * The id can easily be represented in a URL.
+ // * The id is determined by the hprof file, so that the same id can be used
+ // across different instances for viewing the same hprof file.
+ // * A binary search can be used to find a site by id from the root site in
+ // log time.
+ //
+ // The id is set by prepareForUse after the complete site tree is constructed.
+ private long mId = -1;
// The total size of objects allocated in this site (including child sites),
- // organized by heap index. Computed as part of computeObjectsInfos.
+ // organized by heap index. Computed as part of prepareForUse.
private Size[] mSizesByHeap;
// List of child sites.
@@ -99,18 +104,15 @@
* Construct a root site.
*/
public Site(String name) {
- this(null, name, "", "", 0, 0, 0);
+ this(null, name, "", "", 0);
}
- public Site(Site parent, String method, String signature, String file,
- int line, long id, int depth) {
+ private Site(Site parent, String method, String signature, String file, int line) {
mParent = parent;
mMethodName = method;
mSignature = signature;
mFilename = file;
mLineNumber = line;
- mId = id;
- mDepth = depth;
mChildren = new ArrayList<Site>();
mObjects = new ArrayList<AhatInstance>();
mObjectsInfos = new ArrayList<ObjectsInfo>();
@@ -119,50 +121,63 @@
}
/**
- * Add an instance to this site.
+ * Get a child site of this site.
* Returns the site at which the instance was allocated.
- * @param frames - The list of frames in the stack trace, starting with the inner-most frame.
- * @param depth - The number of frames remaining before the inner-most frame is reached.
+ * @param frames - The list of frames in the stack trace, starting with the
+ * inner-most frame. May be null, in which case this site is
+ * returned.
*/
- Site add(StackFrame[] frames, int depth, AhatInstance inst) {
- return add(this, frames, depth, inst);
+ Site getSite(StackFrame frames[]) {
+ return frames == null ? this : getSite(this, frames);
}
- private static Site add(Site site, StackFrame[] frames, int depth, AhatInstance inst) {
- while (depth > 0) {
- StackFrame next = frames[depth - 1];
+ private static Site getSite(Site site, StackFrame frames[]) {
+ for (int s = frames.length - 1; s >= 0; --s) {
+ StackFrame frame = frames[s];
Site child = null;
for (int i = 0; i < site.mChildren.size(); i++) {
Site curr = site.mChildren.get(i);
- if (curr.mLineNumber == next.getLineNumber()
- && curr.mMethodName.equals(next.getMethodName())
- && curr.mSignature.equals(next.getSignature())
- && curr.mFilename.equals(next.getFilename())) {
+ if (curr.mLineNumber == frame.getLineNumber()
+ && curr.mMethodName.equals(frame.getMethodName())
+ && curr.mSignature.equals(frame.getSignature())
+ && curr.mFilename.equals(frame.getFilename())) {
child = curr;
break;
}
}
if (child == null) {
- child = new Site(site, next.getMethodName(), next.getSignature(),
- next.getFilename(), next.getLineNumber(), inst.getId(), depth - 1);
+ child = new Site(site, frame.getMethodName(), frame.getSignature(),
+ frame.getFilename(), frame.getLineNumber());
site.mChildren.add(child);
}
- depth = depth - 1;
site = child;
}
- site.mObjects.add(inst);
return site;
}
/**
- * Recompute the ObjectsInfos for this and all child sites.
- * This should be done after the sites tree has been formed. It should also
- * be done after dominators computation has been performed to ensure only
- * reachable objects are included in the ObjectsInfos.
- *
- * @param numHeaps - The number of heaps in the heap dump.
+ * Add an instance allocated at this site.
*/
- void computeObjectsInfos(int numHeaps) {
+ void addInstance(AhatInstance inst) {
+ mObjects.add(inst);
+ }
+
+ /**
+ * Prepare this and all child sites for use.
+ * Recomputes site ids, sizes, ObjectInfos for this and all child sites.
+ * This should be called after the sites tree has been formed and after
+ * dominators computation has been performed to ensure only reachable
+ * objects are included in the ObjectsInfos.
+ *
+ * @param id - The smallest id that is allowed to be used for this site or
+ * any of its children.
+ * @param numHeaps - The number of heaps in the heap dump.
+ * @return An id larger than the largest id used for this site or any of its
+ * children.
+ */
+ long prepareForUse(long id, int numHeaps) {
+ mId = id++;
+
// Count up the total sizes by heap.
mSizesByHeap = new Size[numHeaps];
for (int i = 0; i < numHeaps; ++i) {
@@ -183,7 +198,7 @@
// Add objects allocated in child sites.
for (Site child : mChildren) {
- child.computeObjectsInfos(numHeaps);
+ id = child.prepareForUse(id, numHeaps);
for (ObjectsInfo childInfo : child.mObjectsInfos) {
ObjectsInfo info = getObjectsInfo(childInfo.heap, childInfo.classObj);
info.numInstances += childInfo.numInstances;
@@ -193,6 +208,7 @@
mSizesByHeap[i] = mSizesByHeap[i].plus(child.mSizesByHeap[i]);
}
}
+ return id;
}
// Get the size of a site for a specific heap.
@@ -285,22 +301,49 @@
}
/**
- * Returns the id of some object allocated in this site.
+ * Returns the unique id of this site.
*/
public long getId() {
return mId;
}
/**
- * Returns the number of frames between this site and the site where the
- * object with id getId() was allocated.
+ * Find the child site with the given id.
+ * Returns null if no such site was found.
*/
- public int getDepth() {
- return mDepth;
+ public Site findSite(long id) {
+ if (id == mId) {
+ return this;
+ }
+
+ // Binary search over the children to find the right child to search in.
+ int start = 0;
+ int end = mChildren.size();
+ while (start < end) {
+ int mid = start + ((end - start) / 2);
+ Site midSite = mChildren.get(mid);
+ if (id < midSite.mId) {
+ end = mid;
+ } else if (mid + 1 == end) {
+ // This is the last child we could possibly find the desired site in,
+ // so search in this child.
+ return midSite.findSite(id);
+ } else if (id < mChildren.get(mid + 1).mId) {
+ // The desired site has an id between this child's id and the next
+ // child's id, so search in this child.
+ return midSite.findSite(id);
+ } else {
+ start = mid + 1;
+ }
+ }
+ return null;
}
+ /**
+ * Returns an unmodifiable list of this site's immediate children.
+ */
public List<Site> getChildren() {
- return mChildren;
+ return Collections.unmodifiableList(mChildren);
}
void setBaseline(Site baseline) {
@@ -314,13 +357,4 @@
@Override public boolean isPlaceHolder() {
return false;
}
-
- /**
- * Adds a place holder instance to this site and all parent sites.
- */
- void addPlaceHolderInstance(AhatInstance placeholder) {
- for (Site site = this; site != null; site = site.mParent) {
- site.mObjects.add(placeholder);
- }
- }
}
diff --git a/tools/ahat/src/heapdump/SkipNullsIterator.java b/tools/ahat/src/heapdump/SkipNullsIterator.java
new file mode 100644
index 0000000..e99fe5e
--- /dev/null
+++ b/tools/ahat/src/heapdump/SkipNullsIterator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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.heapdump;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * An iterator that skips over nulls.
+ */
+class SkipNullsIterator<T> implements Iterator<T>, Iterable<T> {
+ Iterator<T> mIter;
+ private T mNext;
+
+ public SkipNullsIterator(Iterable<T> iterable) {
+ mIter = iterable.iterator();
+ mNext = null;
+ }
+
+ @Override
+ public boolean hasNext() {
+ while (mNext == null && mIter.hasNext()) {
+ mNext = mIter.next();
+ }
+ return mNext != null;
+ }
+
+ @Override
+ public T next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ T next = mNext;
+ mNext = null;
+ return next;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return this;
+ }
+}
diff --git a/tools/ahat/src/heapdump/SuperRoot.java b/tools/ahat/src/heapdump/SuperRoot.java
index 54410cf..d377113 100644
--- a/tools/ahat/src/heapdump/SuperRoot.java
+++ b/tools/ahat/src/heapdump/SuperRoot.java
@@ -39,8 +39,8 @@
}
@Override
- ReferenceIterator getReferences() {
- List<Reference> refs = new AbstractList<Reference>() {
+ Iterable<Reference> getReferences() {
+ return new AbstractList<Reference>() {
@Override
public int size() {
return mRoots.size();
@@ -52,6 +52,5 @@
return new Reference(null, field, mRoots.get(index), true);
}
};
- return new ReferenceIterator(refs);
}
}
diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/heapdump/Value.java
index c1f3022..7f86c01 100644
--- a/tools/ahat/src/heapdump/Value.java
+++ b/tools/ahat/src/heapdump/Value.java
@@ -20,19 +20,72 @@
* Value represents a field value in a heap dump. The field value is either a
* subclass of AhatInstance or a primitive Java type.
*/
-public class Value {
- private Object mObject;
+public abstract class Value {
+ public static Value pack(AhatInstance value) {
+ return value == null ? null : new InstanceValue(value);
+ }
/**
* Constructs a value from a generic Java Object.
* The Object must either be a boxed Java primitive type or a subclass of
* AhatInstance. The object must not be null.
*/
- public Value(Object object) {
- // TODO: Check that the Object is either an AhatSnapshot or boxed Java
- // primitive type?
- assert object != null;
- mObject = object;
+ public static Value pack(Object object) {
+ if (object == null) {
+ return null;
+ } else if (object instanceof AhatInstance) {
+ return Value.pack((AhatInstance)object);
+ } else if (object instanceof Boolean) {
+ return Value.pack(((Boolean)object).booleanValue());
+ } else if (object instanceof Character) {
+ return Value.pack(((Character)object).charValue());
+ } else if (object instanceof Float) {
+ return Value.pack(((Float)object).floatValue());
+ } else if (object instanceof Double) {
+ return Value.pack(((Double)object).doubleValue());
+ } else if (object instanceof Byte) {
+ return Value.pack(((Byte)object).byteValue());
+ } else if (object instanceof Short) {
+ return Value.pack(((Short)object).shortValue());
+ } else if (object instanceof Integer) {
+ return Value.pack(((Integer)object).intValue());
+ } else if (object instanceof Long) {
+ return Value.pack(((Long)object).longValue());
+ }
+ throw new IllegalArgumentException(
+ "AhatInstance or primitive type required, but got: " + object.toString());
+ }
+
+ public static Value pack(boolean value) {
+ return new BooleanValue(value);
+ }
+
+ public static Value pack(char value) {
+ return new CharValue(value);
+ }
+
+ public static Value pack(float value) {
+ return new FloatValue(value);
+ }
+
+ public static Value pack(double value) {
+ return new DoubleValue(value);
+ }
+
+ public static Value pack(byte value) {
+ return new ByteValue(value);
+ }
+
+ public static Value pack(short value) {
+ return new ShortValue(value);
+ }
+
+ public static Value pack(int value) {
+ return new IntValue(value);
+ }
+
+ public static Value pack(long value) {
+ return new LongValue(value);
}
/**
@@ -40,7 +93,7 @@
* primitive value.
*/
public boolean isAhatInstance() {
- return mObject instanceof AhatInstance;
+ return false;
}
/**
@@ -48,9 +101,6 @@
* Returns null if the Value represents a Java primitive value.
*/
public AhatInstance asAhatInstance() {
- if (isAhatInstance()) {
- return (AhatInstance)mObject;
- }
return null;
}
@@ -58,7 +108,7 @@
* Returns true if the Value is an Integer.
*/
public boolean isInteger() {
- return mObject instanceof Integer;
+ return false;
}
/**
@@ -66,9 +116,6 @@
* Returns null if the Value does not represent an Integer.
*/
public Integer asInteger() {
- if (isInteger()) {
- return (Integer)mObject;
- }
return null;
}
@@ -76,7 +123,7 @@
* Returns true if the Value is an Long.
*/
public boolean isLong() {
- return mObject instanceof Long;
+ return false;
}
/**
@@ -84,9 +131,6 @@
* Returns null if the Value does not represent an Long.
*/
public Long asLong() {
- if (isLong()) {
- return (Long)mObject;
- }
return null;
}
@@ -95,9 +139,6 @@
* Returns null if the Value does not represent a Byte.
*/
public Byte asByte() {
- if (mObject instanceof Byte) {
- return (Byte)mObject;
- }
return null;
}
@@ -106,28 +147,255 @@
* Returns null if the Value does not represent a Char.
*/
public Character asChar() {
- if (mObject instanceof Character) {
- return (Character)mObject;
- }
return null;
}
- public String toString() {
- return mObject.toString();
+ @Override
+ public abstract String toString();
+
+ public Value getBaseline() {
+ return this;
}
public static Value getBaseline(Value value) {
- if (value == null || !value.isAhatInstance()) {
- return value;
- }
- return new Value(value.asAhatInstance().getBaseline());
+ return value == null ? null : value.getBaseline();
}
- @Override public boolean equals(Object other) {
- if (other instanceof Value) {
- Value value = (Value)other;
- return mObject.equals(value.mObject);
+ @Override
+ public abstract boolean equals(Object other);
+
+ private static class BooleanValue extends Value {
+ private boolean mBool;
+
+ BooleanValue(boolean bool) {
+ mBool = bool;
}
- return false;
+
+ @Override
+ public String toString() {
+ return Boolean.toString(mBool);
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof BooleanValue) {
+ BooleanValue value = (BooleanValue)other;
+ return mBool == value.mBool;
+ }
+ return false;
+ }
+ }
+
+ private static class ByteValue extends Value {
+ private byte mByte;
+
+ ByteValue(byte b) {
+ mByte = b;
+ }
+
+ @Override
+ public Byte asByte() {
+ return mByte;
+ }
+
+ @Override
+ public String toString() {
+ return Byte.toString(mByte);
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof ByteValue) {
+ ByteValue value = (ByteValue)other;
+ return mByte == value.mByte;
+ }
+ return false;
+ }
+ }
+
+ private static class CharValue extends Value {
+ private char mChar;
+
+ CharValue(char c) {
+ mChar = c;
+ }
+
+ @Override
+ public Character asChar() {
+ return mChar;
+ }
+
+ @Override
+ public String toString() {
+ return Character.toString(mChar);
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof CharValue) {
+ CharValue value = (CharValue)other;
+ return mChar == value.mChar;
+ }
+ return false;
+ }
+ }
+
+ private static class DoubleValue extends Value {
+ private double mDouble;
+
+ DoubleValue(double d) {
+ mDouble = d;
+ }
+
+ @Override
+ public String toString() {
+ return Double.toString(mDouble);
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof DoubleValue) {
+ DoubleValue value = (DoubleValue)other;
+ return mDouble == value.mDouble;
+ }
+ return false;
+ }
+ }
+
+ private static class FloatValue extends Value {
+ private float mFloat;
+
+ FloatValue(float f) {
+ mFloat = f;
+ }
+
+ @Override
+ public String toString() {
+ return Float.toString(mFloat);
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof FloatValue) {
+ FloatValue value = (FloatValue)other;
+ return mFloat == value.mFloat;
+ }
+ return false;
+ }
+ }
+
+ private static class InstanceValue extends Value {
+ private AhatInstance mInstance;
+
+ InstanceValue(AhatInstance inst) {
+ assert(inst != null);
+ mInstance = inst;
+ }
+
+ @Override
+ public boolean isAhatInstance() {
+ return true;
+ }
+
+ @Override
+ public AhatInstance asAhatInstance() {
+ return mInstance;
+ }
+
+ @Override
+ public String toString() {
+ return mInstance.toString();
+ }
+
+ @Override
+ public Value getBaseline() {
+ return InstanceValue.pack(mInstance.getBaseline());
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof InstanceValue) {
+ InstanceValue value = (InstanceValue)other;
+ return mInstance.equals(value.mInstance);
+ }
+ return false;
+ }
+ }
+
+ private static class IntValue extends Value {
+ private int mInt;
+
+ IntValue(int i) {
+ mInt = i;
+ }
+
+ @Override
+ public boolean isInteger() {
+ return true;
+ }
+
+ @Override
+ public Integer asInteger() {
+ return mInt;
+ }
+
+ @Override
+ public String toString() {
+ return Integer.toString(mInt);
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof IntValue) {
+ IntValue value = (IntValue)other;
+ return mInt == value.mInt;
+ }
+ return false;
+ }
+ }
+
+ private static class LongValue extends Value {
+ private long mLong;
+
+ LongValue(long l) {
+ mLong = l;
+ }
+
+ @Override
+ public boolean isLong() {
+ return true;
+ }
+
+ @Override
+ public Long asLong() {
+ return mLong;
+ }
+
+ @Override
+ public String toString() {
+ return Long.toString(mLong);
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof LongValue) {
+ LongValue value = (LongValue)other;
+ return mLong == value.mLong;
+ }
+ return false;
+ }
+ }
+
+ private static class ShortValue extends Value {
+ private short mShort;
+
+ ShortValue(short s) {
+ mShort = s;
+ }
+
+ @Override
+ public String toString() {
+ return Short.toString(mShort);
+ }
+
+ @Override public boolean equals(Object other) {
+ if (other instanceof ShortValue) {
+ ShortValue value = (ShortValue)other;
+ return mShort == value.mShort;
+ }
+ return false;
+ }
}
}
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
index 14c09af..333d28c 100644
--- a/tools/ahat/test-dump/Main.java
+++ b/tools/ahat/test-dump/Main.java
@@ -102,12 +102,23 @@
public StackSmasher stackSmasherAdded;
public static String modifiedStaticField;
public int[] modifiedArray;
+ public Object objectAllocatedAtKnownSite1;
+ public Object objectAllocatedAtKnownSite2;
+
+ private void allocateObjectAtKnownSite1() {
+ objectAllocatedAtKnownSite1 = new Object();
+ allocateObjectAtKnownSite2();
+ }
+
+ private void allocateObjectAtKnownSite2() {
+ objectAllocatedAtKnownSite2 = new Object();
+ }
DumpedStuff(boolean baseline) {
- int N = baseline ? 400000 : 1000000;
- bigArray = new byte[N];
- for (int i = 0; i < N; i++) {
- bigArray[i] = (byte)((i*i) & 0xFF);
+ int n = baseline ? 400000 : 1000000;
+ bigArray = new byte[n];
+ for (int i = 0; i < n; i++) {
+ bigArray[i] = (byte)((i * i) & 0xFF);
}
// 0x12345, 50000, and 0xABCDABCD are arbitrary values.
@@ -127,7 +138,9 @@
modifiedObject.modifiedRefField = baseline ? "A1" : "A2";
modifiedObject.unmodifiedRefField = "B";
modifiedStaticField = baseline ? "C1" : "C2";
- modifiedArray = baseline ? new int[]{0,1,2,3} : new int[]{3,1,2,0};
+ modifiedArray = baseline ? new int[]{0, 1, 2, 3} : new int[]{3, 1, 2, 0};
+
+ allocateObjectAtKnownSite1();
// Deep matching dominator trees shouldn't smash the stack when we try
// to diff them. Make some deep dominator trees to help test it.
diff --git a/tools/ahat/test/DiffFieldsTest.java b/tools/ahat/test/DiffFieldsTest.java
index 6abdd47..7dc519d 100644
--- a/tools/ahat/test/DiffFieldsTest.java
+++ b/tools/ahat/test/DiffFieldsTest.java
@@ -30,26 +30,26 @@
public class DiffFieldsTest {
@Test
public void normalMatchedDiffedFieldValues() {
- FieldValue normal1 = new FieldValue("name", "type", new Value(1));
- FieldValue normal2 = new FieldValue("name", "type", new Value(2));
+ FieldValue normal1 = new FieldValue("name", "type", Value.pack(1));
+ FieldValue normal2 = new FieldValue("name", "type", Value.pack(2));
DiffedFieldValue x = DiffedFieldValue.matched(normal1, normal2);
assertEquals("name", x.name);
assertEquals("type", x.type);
- assertEquals(new Value(1), x.current);
- assertEquals(new Value(2), x.baseline);
+ assertEquals(Value.pack(1), x.current);
+ assertEquals(Value.pack(2), x.baseline);
assertEquals(DiffedFieldValue.Status.MATCHED, x.status);
}
@Test
public void nulledMatchedDiffedFieldValues() {
- FieldValue normal = new FieldValue("name", "type", new Value(1));
+ FieldValue normal = new FieldValue("name", "type", Value.pack(1));
FieldValue nulled = new FieldValue("name", "type", null);
DiffedFieldValue x = DiffedFieldValue.matched(normal, nulled);
assertEquals("name", x.name);
assertEquals("type", x.type);
- assertEquals(new Value(1), x.current);
+ assertEquals(Value.pack(1), x.current);
assertNull(x.baseline);
assertEquals(DiffedFieldValue.Status.MATCHED, x.status);
@@ -57,18 +57,18 @@
assertEquals("name", y.name);
assertEquals("type", y.type);
assertNull(y.current);
- assertEquals(new Value(1), y.baseline);
+ assertEquals(Value.pack(1), y.baseline);
assertEquals(DiffedFieldValue.Status.MATCHED, y.status);
}
@Test
public void normalAddedDiffedFieldValues() {
- FieldValue normal = new FieldValue("name", "type", new Value(1));
+ FieldValue normal = new FieldValue("name", "type", Value.pack(1));
DiffedFieldValue x = DiffedFieldValue.added(normal);
assertEquals("name", x.name);
assertEquals("type", x.type);
- assertEquals(new Value(1), x.current);
+ assertEquals(Value.pack(1), x.current);
assertEquals(DiffedFieldValue.Status.ADDED, x.status);
}
@@ -85,12 +85,12 @@
@Test
public void normalDeletedDiffedFieldValues() {
- FieldValue normal = new FieldValue("name", "type", new Value(1));
+ FieldValue normal = new FieldValue("name", "type", Value.pack(1));
DiffedFieldValue x = DiffedFieldValue.deleted(normal);
assertEquals("name", x.name);
assertEquals("type", x.type);
- assertEquals(new Value(1), x.baseline);
+ assertEquals(Value.pack(1), x.baseline);
assertEquals(DiffedFieldValue.Status.DELETED, x.status);
}
diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/test/InstanceTest.java
index e05782c..63055db 100644
--- a/tools/ahat/test/InstanceTest.java
+++ b/tools/ahat/test/InstanceTest.java
@@ -237,7 +237,7 @@
public void gcRootPath() throws IOException {
TestDump dump = TestDump.getTestDump();
- AhatClassObj main = dump.getAhatSnapshot().findClass("Main");
+ AhatClassObj main = dump.findClass("Main");
AhatInstance gcPathArray = dump.getDumpedAhatInstance("gcPathArray");
Value value = gcPathArray.asArrayInstance().getValue(2);
AhatInstance base = value.asAhatInstance();
@@ -333,7 +333,7 @@
@Test
public void classObjNotABitmap() throws IOException {
TestDump dump = TestDump.getTestDump();
- AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
+ AhatInstance obj = dump.findClass("Main");
assertNull(obj.asBitmap());
}
@@ -348,7 +348,7 @@
@Test
public void classObjToString() throws IOException {
TestDump dump = TestDump.getTestDump();
- AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
+ AhatInstance obj = dump.findClass("Main");
assertEquals("class Main", obj.toString());
}
diff --git a/tools/ahat/test/ObjectHandlerTest.java b/tools/ahat/test/ObjectHandlerTest.java
index cd0ba23..1b8a781 100644
--- a/tools/ahat/test/ObjectHandlerTest.java
+++ b/tools/ahat/test/ObjectHandlerTest.java
@@ -42,7 +42,7 @@
AhatSnapshot snapshot = dump.getAhatSnapshot();
AhatHandler handler = new ObjectHandler(snapshot);
- AhatInstance object = snapshot.findClass("Main");
+ AhatInstance object = dump.findClass("Main");
assertNotNull(object);
TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
@@ -55,7 +55,7 @@
AhatSnapshot snapshot = dump.getAhatSnapshot();
AhatHandler handler = new ObjectHandler(snapshot);
- AhatInstance object = snapshot.findClass("java.lang.String");
+ AhatInstance object = dump.findClass("java.lang.String");
assertNotNull(object);
TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
diff --git a/tools/ahat/test/SiteTest.java b/tools/ahat/test/SiteTest.java
new file mode 100644
index 0000000..dc0fe08
--- /dev/null
+++ b/tools/ahat/test/SiteTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 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.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
+import java.io.IOException;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+public class SiteTest {
+ @Test
+ public void objectsAllocatedAtKnownSites() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatSnapshot snapshot = dump.getAhatSnapshot();
+
+ AhatInstance obj1 = dump.getDumpedAhatInstance("objectAllocatedAtKnownSite1");
+ AhatInstance obj2 = dump.getDumpedAhatInstance("objectAllocatedAtKnownSite2");
+ Site s2 = obj2.getSite();
+ Site s1b = s2.getParent();
+ Site s1a = obj1.getSite();
+ Site s = s1a.getParent();
+
+ // TODO: The following commented out assertion fails due to a problem with
+ // proguard deobfuscation. That bug should be fixed.
+ // assertEquals("Main.java", s.getFilename());
+
+ assertEquals("Main.java", s1a.getFilename());
+ assertEquals("Main.java", s1b.getFilename());
+ assertEquals("Main.java", s2.getFilename());
+
+ assertEquals("allocateObjectAtKnownSite1", s1a.getMethodName());
+ assertEquals("allocateObjectAtKnownSite1", s1b.getMethodName());
+ assertEquals("allocateObjectAtKnownSite2", s2.getMethodName());
+
+ // TODO: The following commented out assertion fails due to a problem with
+ // stack frame line numbers - we don't get different line numbers
+ // for the different sites, so they are indistiguishable. The
+ // problem with line numbers should be understood and fixed.
+ // assertNotSame(s1a, s1b);
+
+ assertSame(s1a.getParent(), s1b.getParent());
+
+ assertSame(s, snapshot.getSite(s.getId()));
+ assertSame(s1a, snapshot.getSite(s1a.getId()));
+ assertSame(s1b, snapshot.getSite(s1b.getId()));
+ assertSame(s2, snapshot.getSite(s2.getId()));
+ }
+}
diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java
index 3dce2dc..db9b256 100644
--- a/tools/ahat/test/TestDump.java
+++ b/tools/ahat/test/TestDump.java
@@ -21,11 +21,14 @@
import com.android.ahat.heapdump.AhatSnapshot;
import com.android.ahat.heapdump.Diff;
import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.Site;
import com.android.ahat.heapdump.Value;
import com.android.tools.perflib.heap.ProguardMap;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
/**
* The TestDump class is used to get an AhatSnapshot for the test-dump
@@ -45,8 +48,10 @@
// fails and don't try to load it again.
private static boolean mTestDumpFailed = false;
- private AhatSnapshot mSnapshot = null;
- private AhatSnapshot mBaseline = null;
+ private AhatSnapshot mSnapshot;
+ private AhatSnapshot mBaseline;
+ private AhatClassObj mMain;
+ private AhatClassObj mBaselineMain;
/**
* Load the test-dump.hprof and test-dump-base.hprof files.
@@ -79,6 +84,12 @@
mSnapshot = AhatSnapshot.fromHprof(new File(hprof), map);
mBaseline = AhatSnapshot.fromHprof(new File(hprofBase), map);
Diff.snapshots(mSnapshot, mBaseline);
+
+ mMain = findClass(mSnapshot, "Main");
+ assert(mMain != null);
+
+ mBaselineMain = findClass(mBaseline, "Main");
+ assert(mBaselineMain != null);
}
/**
@@ -100,7 +111,7 @@
* snapshot for the test-dump program.
*/
public Value getDumpedValue(String name) {
- return getDumpedValue(name, mSnapshot);
+ return getDumpedValue(name, mMain);
}
/**
@@ -108,15 +119,14 @@
* baseline snapshot for the test-dump program.
*/
public Value getBaselineDumpedValue(String name) {
- return getDumpedValue(name, mBaseline);
+ return getDumpedValue(name, mBaselineMain);
}
/**
- * Returns the value of a field in the DumpedStuff instance in the
- * given snapshot for the test-dump program.
+ * Returns the value of a field in the DumpedStuff instance given the Main
+ * class object for the snapshot.
*/
- private Value getDumpedValue(String name, AhatSnapshot snapshot) {
- AhatClassObj main = snapshot.findClass("Main");
+ private static Value getDumpedValue(String name, AhatClassObj main) {
AhatInstance stuff = null;
for (FieldValue field : main.getStaticFieldValues()) {
if ("stuff".equals(field.name)) {
@@ -127,6 +137,33 @@
}
/**
+ * Returns a class object in the given heap dump whose name matches the
+ * given name, or null if no such class object could be found.
+ */
+ private static AhatClassObj findClass(AhatSnapshot snapshot, String name) {
+ Site root = snapshot.getRootSite();
+ Collection<AhatInstance> classes = new ArrayList<AhatInstance>();
+ root.getObjects(null, "java.lang.Class", classes);
+ for (AhatInstance inst : classes) {
+ if (inst.isClassObj()) {
+ AhatClassObj cls = inst.asClassObj();
+ if (name.equals(cls.getName())) {
+ return cls;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a class object in the heap dump whose name matches the given
+ * name, or null if no such class object could be found.
+ */
+ public AhatClassObj findClass(String name) {
+ return findClass(mSnapshot, name);
+ }
+
+ /**
* Returns the value of a non-primitive field in the DumpedStuff instance in
* the snapshot for the test-dump program.
*/
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index a1e3246..cd33a90 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -33,6 +33,7 @@
"com.android.ahat.RootedHandlerTest",
"com.android.ahat.QueryTest",
"com.android.ahat.SiteHandlerTest",
+ "com.android.ahat.SiteTest",
};
}
JUnitCore.main(args);
diff --git a/tools/titrace/Android.bp b/tools/titrace/Android.bp
new file mode 100644
index 0000000..b95ec9d
--- /dev/null
+++ b/tools/titrace/Android.bp
@@ -0,0 +1,77 @@
+//
+// Copyright (C) 2017 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.
+//
+
+// Build variants {target,host} x {debug,ndebug} x {32,64}
+
+cc_defaults {
+ name: "titrace-defaults",
+ host_supported: true,
+ srcs: ["titrace.cc", "instruction_decoder.cc"],
+ defaults: ["art_defaults"],
+
+ // Note that this tool needs to be built for both 32-bit and 64-bit since it requires
+ // to be same ISA as what it is attached to.
+ compile_multilib: "both",
+
+ shared_libs: [
+ "libbase"
+ ],
+ target: {
+ android: {
+ },
+ host: {
+ },
+ },
+ header_libs: [
+ "libopenjdkjvmti_headers",
+ "libart_runtime_headers" // for dex_instruction_list.h only
+ // "libbase_headers",
+ ],
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+ symlink_preferred_arch: true,
+}
+
+art_cc_library {
+ name: "libtitrace",
+ defaults: ["titrace-defaults"],
+ shared_libs: [
+ ],
+}
+
+art_cc_library {
+ name: "libtitraced",
+ defaults: [
+ "art_debug_defaults",
+ "titrace-defaults",
+ ],
+ shared_libs: [
+ ],
+}
+
+//art_cc_test {
+// name: "art_titrace_tests",
+// defaults: [
+// "art_gtest_defaults",
+// ],
+// srcs: ["titrace_test.cc"],
+//}
diff --git a/tools/titrace/README.md b/tools/titrace/README.md
new file mode 100644
index 0000000..a82025b
--- /dev/null
+++ b/tools/titrace/README.md
@@ -0,0 +1,62 @@
+# Titrace
+
+Titrace is a bytecode instruction tracing tool that uses JVMTI and works on both ART and the RI.
+
+# Usage
+### Build
+> `make libtitrace` # or 'make libtitraced' with debugging checks enabled
+
+The libraries will be built for 32-bit, 64-bit, host and target. Below examples assume you want to use the 64-bit version.
+### Command Line
+#### ART
+> `art -Xplugin:$ANDROID_HOST_OUT/lib64/libopenjdkjvmti.so -agentpath:$ANDROID_HOST_OUT/lib64/libtitrace.so -cp tmp/java/helloworld.dex -Xint helloworld`
+
+* `-Xplugin` and `-agentpath` need to be used, otherwise libtitrace agent will fail during init.
+* If using `libartd.so`, make sure to use the debug version of jvmti.
+#### Reference Implementation
+> `java -agentpath:$ANDROID_HOST_OUT/lib64/libtitrace.so helloworld`
+
+Only needs `-agentpath` to be specified.
+### Android Applications
+Replace __com.littleinc.orm_benchmark__ with the name of your application below.
+#### Enable permissions for attaching an agent
+Normal applications require that `debuggable=true` to be set in their AndroidManifest.xml.
+
+By using a *eng* or *userdebug* build of Android, we can override this requirement:
+> `adb root`
+> `adb shell setprop dalvik.vm.dex2oat-flags --debuggable`
+
+Then restart the runtime to pick it up.
+> `adb shell stop && adb shell start`
+
+If this step is skipped, attaching the agent will not succeed.
+#### Deploy agent to device
+The agent must be located in an app-accessible directory.
+
+> `adb push $ANDROID_PRODUCT_OUT/system/lib64/libtitrace.so /data/local/tmp`
+
+Upload to device first (it gets shell/root permissions).
+
+> `adb shell run-as com.littleinc.orm_benchmark 'cp /data/local/tmp/libtitrace.so /data/data/com.littleinc.orm_benchmark/files/libtitrace.so'`
+
+Copy the agent into an app-accessible directory, and make the file owned by the app.
+
+#### Attach agent to application
+
+##### Option 1: Attach the agent before any app code runs.
+> `adb shell am start --attach-agent /data/data/com.littleinc.orm_benchmark/files/libtitrace.so com.littleinc.orm_benchmark/.MainActivity`
+
+Note: To determine the arguments to `am start`, launch the application manually first and then look for this in logcat:
+
+> 09-14 13:28:08.680 7584 8192 I ActivityManager: Start proc 17614:com.littleinc.orm_benchmark/u0a138 for activity **com.littleinc.orm_benchmark/.MainActivity**
+
+##### Option 2: Attach the agent to an already-running app.
+> `adb shell am attach-agent $(pid com.littleinc.orm_benchmark) /data/data/com.littleinc.orm_benchmark/files/libtitrace.so`
+
+### Printing the Results
+All statitics gathered during the trace are printed automatically when the program normally exists. In the case of Android applications, they are always killed, so we need to manually print the results.
+
+> `kill -SIGQUIT $(pid com.littleinc.orm_benchmark)`
+
+Will initiate a dump of the agent (to logcat).
+
diff --git a/tools/titrace/instruction_decoder.cc b/tools/titrace/instruction_decoder.cc
new file mode 100644
index 0000000..5d2f22f
--- /dev/null
+++ b/tools/titrace/instruction_decoder.cc
@@ -0,0 +1,518 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "instruction_decoder.h"
+
+#include "dex_instruction_list.h"
+
+#include <android-base/logging.h>
+
+namespace titrace {
+
+class ClassInstructionDecoder : public InstructionDecoder {
+ public:
+ virtual size_t GetMaximumOpcode() override {
+ return 0xff;
+ }
+
+ virtual const char* GetName(size_t opcode) override {
+ Bytecode::Opcode op = static_cast<Bytecode::Opcode>(opcode);
+ return Bytecode::ToString(op);
+ }
+
+ virtual size_t LocationToOffset(size_t j_location) {
+ return j_location;
+ }
+
+ private:
+ class Bytecode {
+ public:
+ enum Opcode {
+ // Java bytecode opcodes from 0x00 to 0xFF.
+ kNop = 0x00,
+ kAconst_null = 0x01,
+ kIconst_m1 = 0x02,
+ kIconst_0 = 0x03,
+ kIconst_1 = 0x04,
+ kIconst_2 = 0x05,
+ kIconst_3 = 0x06,
+ kIconst_4 = 0x07,
+ kIconst_5 = 0x08,
+ kLconst_0 = 0x09,
+ kLconst_1 = 0x0a,
+ kFconst_0 = 0x0b,
+ kFconst_1 = 0x0c,
+ kFconst_2 = 0x0d,
+ kDconst_0 = 0x0e,
+ kDconst_1 = 0x0f,
+ kBipush = 0x10,
+ kSipush = 0x11,
+ kLdc = 0x12,
+ kLdc_w = 0x13,
+ kLdc2_w = 0x14,
+ kIload = 0x15,
+ kLload = 0x16,
+ kFload = 0x17,
+ kDload = 0x18,
+ kAload = 0x19,
+ kIload_0 = 0x1a,
+ kIload_1 = 0x1b,
+ kIload_2 = 0x1c,
+ kIload_3 = 0x1d,
+ kLload_0 = 0x1e,
+ kLload_1 = 0x1f,
+ kLload_2 = 0x20,
+ kLload_3 = 0x21,
+ kFload_0 = 0x22,
+ kFload_1 = 0x23,
+ kFload_2 = 0x24,
+ kFload_3 = 0x25,
+ kDload_0 = 0x26,
+ kDload_1 = 0x27,
+ kDload_2 = 0x28,
+ kDload_3 = 0x29,
+ kAload_0 = 0x2a,
+ kAload_1 = 0x2b,
+ kAload_2 = 0x2c,
+ kAload_3 = 0x2d,
+ kIaload = 0x2e,
+ kLaload = 0x2f,
+ kFaload = 0x30,
+ kDaload = 0x31,
+ kAaload = 0x32,
+ kBaload = 0x33,
+ kCaload = 0x34,
+ kSaload = 0x35,
+ kIstore = 0x36,
+ kLstore = 0x37,
+ kFstore = 0x38,
+ kDstore = 0x39,
+ kAstore = 0x3a,
+ kIstore_0 = 0x3b,
+ kIstore_1 = 0x3c,
+ kIstore_2 = 0x3d,
+ kIstore_3 = 0x3e,
+ kLstore_0 = 0x3f,
+ kLstore_1 = 0x40,
+ kLstore_2 = 0x41,
+ kLstore_3 = 0x42,
+ kFstore_0 = 0x43,
+ kFstore_1 = 0x44,
+ kFstore_2 = 0x45,
+ kFstore_3 = 0x46,
+ kDstore_0 = 0x47,
+ kDstore_1 = 0x48,
+ kDstore_2 = 0x49,
+ kDstore_3 = 0x4a,
+ kAstore_0 = 0x4b,
+ kAstore_1 = 0x4c,
+ kAstore_2 = 0x4d,
+ kAstore_3 = 0x4e,
+ kIastore = 0x4f,
+ kLastore = 0x50,
+ kFastore = 0x51,
+ kDastore = 0x52,
+ kAastore = 0x53,
+ kBastore = 0x54,
+ kCastore = 0x55,
+ kSastore = 0x56,
+ kPop = 0x57,
+ kPop2 = 0x58,
+ kDup = 0x59,
+ kDup_x1 = 0x5a,
+ kDup_x2 = 0x5b,
+ kDup2 = 0x5c,
+ kDup2_x1 = 0x5d,
+ kDup2_x2 = 0x5e,
+ kSwap = 0x5f,
+ kIadd = 0x60,
+ kLadd = 0x61,
+ kFadd = 0x62,
+ kDadd = 0x63,
+ kIsub = 0x64,
+ kLsub = 0x65,
+ kFsub = 0x66,
+ kDsub = 0x67,
+ kImul = 0x68,
+ kLmul = 0x69,
+ kFmul = 0x6a,
+ kDmul = 0x6b,
+ kIdiv = 0x6c,
+ kLdiv = 0x6d,
+ kFdiv = 0x6e,
+ kDdiv = 0x6f,
+ kIrem = 0x70,
+ kLrem = 0x71,
+ kFrem = 0x72,
+ kDrem = 0x73,
+ kIneg = 0x74,
+ kLneg = 0x75,
+ kFneg = 0x76,
+ kDneg = 0x77,
+ kIshl = 0x78,
+ kLshl = 0x79,
+ kIshr = 0x7a,
+ kLshr = 0x7b,
+ kIushr = 0x7c,
+ kLushr = 0x7d,
+ kIand = 0x7e,
+ kLand = 0x7f,
+ kIor = 0x80,
+ kLor = 0x81,
+ kIxor = 0x82,
+ kLxor = 0x83,
+ kIinc = 0x84,
+ kI2l = 0x85,
+ kI2f = 0x86,
+ kI2d = 0x87,
+ kL2i = 0x88,
+ kL2f = 0x89,
+ kL2d = 0x8a,
+ kF2i = 0x8b,
+ kF2l = 0x8c,
+ kF2d = 0x8d,
+ kD2i = 0x8e,
+ kD2l = 0x8f,
+ kD2f = 0x90,
+ kI2b = 0x91,
+ kI2c = 0x92,
+ kI2s = 0x93,
+ kLcmp = 0x94,
+ kFcmpl = 0x95,
+ kFcmpg = 0x96,
+ kDcmpl = 0x97,
+ kDcmpg = 0x98,
+ kIfeq = 0x99,
+ kIfne = 0x9a,
+ kIflt = 0x9b,
+ kIfge = 0x9c,
+ kIfgt = 0x9d,
+ kIfle = 0x9e,
+ kIf_icmpeq = 0x9f,
+ kIf_icmpne = 0xa0,
+ kIf_icmplt = 0xa1,
+ kIf_icmpge = 0xa2,
+ kIf_icmpgt = 0xa3,
+ kIf_icmple = 0xa4,
+ kIf_acmpeq = 0xa5,
+ kIf_acmpne = 0xa6,
+ kGoto = 0xa7,
+ kJsr = 0xa8,
+ kRet = 0xa9,
+ kTableswitch = 0xaa,
+ kLookupswitch = 0xab,
+ kIreturn = 0xac,
+ kLreturn = 0xad,
+ kFreturn = 0xae,
+ kDreturn = 0xaf,
+ kAreturn = 0xb0,
+ kReturn = 0xb1,
+ kGetstatic = 0xb2,
+ kPutstatic = 0xb3,
+ kGetfield = 0xb4,
+ kPutfield = 0xb5,
+ kInvokevirtual = 0xb6,
+ kInvokespecial = 0xb7,
+ kInvokestatic = 0xb8,
+ kInvokeinterface = 0xb9,
+ kInvokedynamic = 0xba,
+ kNew = 0xbb,
+ kNewarray = 0xbc,
+ kAnewarray = 0xbd,
+ kArraylength = 0xbe,
+ kAthrow = 0xbf,
+ kCheckcast = 0xc0,
+ kInstanceof = 0xc1,
+ kMonitorenter = 0xc2,
+ kMonitorexit = 0xc3,
+ kWide = 0xc4,
+ kMultianewarray = 0xc5,
+ kIfnull = 0xc6,
+ kIfnonnull = 0xc7,
+ kGoto_w = 0xc8,
+ kJsr_w = 0xc9,
+ kBreakpoint = 0xca,
+ // Instructions 0xcb-0xfd are undefined.
+ kImpdep1 = 0xfe,
+ kImpdep2 = 0xff,
+ };
+
+ static const char* ToString(Bytecode::Opcode op) {
+ switch (op) {
+ case kNop: return "nop";
+ case kAconst_null: return "aconst_null";
+ case kIconst_m1: return "iconst_m1";
+ case kIconst_0: return "iconst_0";
+ case kIconst_1: return "iconst_1";
+ case kIconst_2: return "iconst_2";
+ case kIconst_3: return "iconst_3";
+ case kIconst_4: return "iconst_4";
+ case kIconst_5: return "iconst_5";
+ case kLconst_0: return "lconst_0";
+ case kLconst_1: return "lconst_1";
+ case kFconst_0: return "fconst_0";
+ case kFconst_1: return "fconst_1";
+ case kFconst_2: return "fconst_2";
+ case kDconst_0: return "dconst_0";
+ case kDconst_1: return "dconst_1";
+ case kBipush: return "bipush";
+ case kSipush: return "sipush";
+ case kLdc: return "ldc";
+ case kLdc_w: return "ldc_w";
+ case kLdc2_w: return "ldc2_w";
+ case kIload: return "iload";
+ case kLload: return "lload";
+ case kFload: return "fload";
+ case kDload: return "dload";
+ case kAload: return "aload";
+ case kIload_0: return "iload_0";
+ case kIload_1: return "iload_1";
+ case kIload_2: return "iload_2";
+ case kIload_3: return "iload_3";
+ case kLload_0: return "lload_0";
+ case kLload_1: return "lload_1";
+ case kLload_2: return "lload_2";
+ case kLload_3: return "lload_3";
+ case kFload_0: return "fload_0";
+ case kFload_1: return "fload_1";
+ case kFload_2: return "fload_2";
+ case kFload_3: return "fload_3";
+ case kDload_0: return "dload_0";
+ case kDload_1: return "dload_1";
+ case kDload_2: return "dload_2";
+ case kDload_3: return "dload_3";
+ case kAload_0: return "aload_0";
+ case kAload_1: return "aload_1";
+ case kAload_2: return "aload_2";
+ case kAload_3: return "aload_3";
+ case kIaload: return "iaload";
+ case kLaload: return "laload";
+ case kFaload: return "faload";
+ case kDaload: return "daload";
+ case kAaload: return "aaload";
+ case kBaload: return "baload";
+ case kCaload: return "caload";
+ case kSaload: return "saload";
+ case kIstore: return "istore";
+ case kLstore: return "lstore";
+ case kFstore: return "fstore";
+ case kDstore: return "dstore";
+ case kAstore: return "astore";
+ case kIstore_0: return "istore_0";
+ case kIstore_1: return "istore_1";
+ case kIstore_2: return "istore_2";
+ case kIstore_3: return "istore_3";
+ case kLstore_0: return "lstore_0";
+ case kLstore_1: return "lstore_1";
+ case kLstore_2: return "lstore_2";
+ case kLstore_3: return "lstore_3";
+ case kFstore_0: return "fstore_0";
+ case kFstore_1: return "fstore_1";
+ case kFstore_2: return "fstore_2";
+ case kFstore_3: return "fstore_3";
+ case kDstore_0: return "dstore_0";
+ case kDstore_1: return "dstore_1";
+ case kDstore_2: return "dstore_2";
+ case kDstore_3: return "dstore_3";
+ case kAstore_0: return "astore_0";
+ case kAstore_1: return "astore_1";
+ case kAstore_2: return "astore_2";
+ case kAstore_3: return "astore_3";
+ case kIastore: return "iastore";
+ case kLastore: return "lastore";
+ case kFastore: return "fastore";
+ case kDastore: return "dastore";
+ case kAastore: return "aastore";
+ case kBastore: return "bastore";
+ case kCastore: return "castore";
+ case kSastore: return "sastore";
+ case kPop: return "pop";
+ case kPop2: return "pop2";
+ case kDup: return "dup";
+ case kDup_x1: return "dup_x1";
+ case kDup_x2: return "dup_x2";
+ case kDup2: return "dup2";
+ case kDup2_x1: return "dup2_x1";
+ case kDup2_x2: return "dup2_x2";
+ case kSwap: return "swap";
+ case kIadd: return "iadd";
+ case kLadd: return "ladd";
+ case kFadd: return "fadd";
+ case kDadd: return "dadd";
+ case kIsub: return "isub";
+ case kLsub: return "lsub";
+ case kFsub: return "fsub";
+ case kDsub: return "dsub";
+ case kImul: return "imul";
+ case kLmul: return "lmul";
+ case kFmul: return "fmul";
+ case kDmul: return "dmul";
+ case kIdiv: return "idiv";
+ case kLdiv: return "ldiv";
+ case kFdiv: return "fdiv";
+ case kDdiv: return "ddiv";
+ case kIrem: return "irem";
+ case kLrem: return "lrem";
+ case kFrem: return "frem";
+ case kDrem: return "drem";
+ case kIneg: return "ineg";
+ case kLneg: return "lneg";
+ case kFneg: return "fneg";
+ case kDneg: return "dneg";
+ case kIshl: return "ishl";
+ case kLshl: return "lshl";
+ case kIshr: return "ishr";
+ case kLshr: return "lshr";
+ case kIushr: return "iushr";
+ case kLushr: return "lushr";
+ case kIand: return "iand";
+ case kLand: return "land";
+ case kIor: return "ior";
+ case kLor: return "lor";
+ case kIxor: return "ixor";
+ case kLxor: return "lxor";
+ case kIinc: return "iinc";
+ case kI2l: return "i2l";
+ case kI2f: return "i2f";
+ case kI2d: return "i2d";
+ case kL2i: return "l2i";
+ case kL2f: return "l2f";
+ case kL2d: return "l2d";
+ case kF2i: return "f2i";
+ case kF2l: return "f2l";
+ case kF2d: return "f2d";
+ case kD2i: return "d2i";
+ case kD2l: return "d2l";
+ case kD2f: return "d2f";
+ case kI2b: return "i2b";
+ case kI2c: return "i2c";
+ case kI2s: return "i2s";
+ case kLcmp: return "lcmp";
+ case kFcmpl: return "fcmpl";
+ case kFcmpg: return "fcmpg";
+ case kDcmpl: return "dcmpl";
+ case kDcmpg: return "dcmpg";
+ case kIfeq: return "ifeq";
+ case kIfne: return "ifne";
+ case kIflt: return "iflt";
+ case kIfge: return "ifge";
+ case kIfgt: return "ifgt";
+ case kIfle: return "ifle";
+ case kIf_icmpeq: return "if_icmpeq";
+ case kIf_icmpne: return "if_icmpne";
+ case kIf_icmplt: return "if_icmplt";
+ case kIf_icmpge: return "if_icmpge";
+ case kIf_icmpgt: return "if_icmpgt";
+ case kIf_icmple: return "if_icmple";
+ case kIf_acmpeq: return "if_acmpeq";
+ case kIf_acmpne: return "if_acmpne";
+ case kGoto: return "goto";
+ case kJsr: return "jsr";
+ case kRet: return "ret";
+ case kTableswitch: return "tableswitch";
+ case kLookupswitch: return "lookupswitch";
+ case kIreturn: return "ireturn";
+ case kLreturn: return "lreturn";
+ case kFreturn: return "freturn";
+ case kDreturn: return "dreturn";
+ case kAreturn: return "areturn";
+ case kReturn: return "return";
+ case kGetstatic: return "getstatic";
+ case kPutstatic: return "putstatic";
+ case kGetfield: return "getfield";
+ case kPutfield: return "putfield";
+ case kInvokevirtual: return "invokevirtual";
+ case kInvokespecial: return "invokespecial";
+ case kInvokestatic: return "invokestatic";
+ case kInvokeinterface: return "invokeinterface";
+ case kInvokedynamic: return "invokedynamic";
+ case kNew: return "new";
+ case kNewarray: return "newarray";
+ case kAnewarray: return "anewarray";
+ case kArraylength: return "arraylength";
+ case kAthrow: return "athrow";
+ case kCheckcast: return "checkcast";
+ case kInstanceof: return "instanceof";
+ case kMonitorenter: return "monitorenter";
+ case kMonitorexit: return "monitorexit";
+ case kWide: return "wide";
+ case kMultianewarray: return "multianewarray";
+ case kIfnull: return "ifnull";
+ case kIfnonnull: return "ifnonnull";
+ case kGoto_w: return "goto_w";
+ case kJsr_w: return "jsr_w";
+ case kBreakpoint: return "breakpoint";
+ case kImpdep1: return "impdep1";
+ case kImpdep2: return "impdep2";
+ default: LOG(FATAL) << "Unknown opcode " << op;
+ }
+ return "";
+ }
+ };
+};
+
+class DexInstructionDecoder : public InstructionDecoder {
+ public:
+ virtual size_t GetMaximumOpcode() override {
+ return 0xff;
+ }
+
+ virtual const char* GetName(size_t opcode) override {
+ Bytecode::Opcode op = static_cast<Bytecode::Opcode>(opcode);
+ return Bytecode::ToString(op);
+ }
+
+ virtual size_t LocationToOffset(size_t j_location) {
+ // dex pc is uint16_t*, but offset needs to be in bytes.
+ return j_location * (sizeof(uint16_t) / sizeof(uint8_t));
+ }
+
+ private:
+ class Bytecode {
+ public:
+ enum Opcode {
+#define MAKE_ENUM_DEFINITION(opcode, instruction_code, name, format, index, flags, extended_flags, verifier_flags) \
+ instruction_code = opcode,
+DEX_INSTRUCTION_LIST(MAKE_ENUM_DEFINITION)
+#undef MAKE_ENUM_DEFINITION
+ };
+
+ static_assert(static_cast<uint32_t>(Bytecode::Opcode::NOP) == 0, "");
+ static_assert(static_cast<uint32_t>(Bytecode::Opcode::MOVE) == 1, "");
+
+ static const char* ToString(Bytecode::Opcode op) {
+ switch (op) {
+#define MAKE_ENUM_DEFINITION(opcode, instruction_code, name, format, index, flags, extended_flags, verifier_flags) \
+ case instruction_code: return (name);
+DEX_INSTRUCTION_LIST(MAKE_ENUM_DEFINITION)
+#undef MAKE_ENUM_DEFINITION
+ default: LOG(FATAL) << "Unknown opcode " << op;
+ }
+ return "";
+ }
+ };
+};
+
+InstructionDecoder* InstructionDecoder::NewInstance(InstructionFileFormat file_format) {
+ switch (file_format) {
+ case InstructionFileFormat::kClass:
+ return new ClassInstructionDecoder();
+ case InstructionFileFormat::kDex:
+ return new DexInstructionDecoder();
+ default:
+ return nullptr;
+ }
+}
+} // namespace titrace
diff --git a/tools/titrace/instruction_decoder.h b/tools/titrace/instruction_decoder.h
new file mode 100644
index 0000000..34be2e9
--- /dev/null
+++ b/tools/titrace/instruction_decoder.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+
+#ifndef ART_TOOLS_TITRACE_INSTRUCTION_DECODER_H_
+#define ART_TOOLS_TITRACE_INSTRUCTION_DECODER_H_
+
+#include <stddef.h>
+
+namespace titrace {
+
+enum class InstructionFileFormat {
+ kClass,
+ kDex
+};
+
+class InstructionDecoder {
+ public:
+ virtual size_t GetMaximumOpcode() = 0;
+ virtual const char* GetName(size_t opcode) = 0;
+ virtual size_t LocationToOffset(size_t j_location) = 0;
+
+ virtual ~InstructionDecoder() {}
+
+ static InstructionDecoder* NewInstance(InstructionFileFormat file_format);
+};
+
+} // namespace titrace
+
+#endif // ART_TOOLS_TITRACE_INSTRUCTION_DECODER_H_
diff --git a/tools/titrace/titrace.cc b/tools/titrace/titrace.cc
new file mode 100644
index 0000000..bf1218b
--- /dev/null
+++ b/tools/titrace/titrace.cc
@@ -0,0 +1,309 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "instruction_decoder.h"
+
+#include <android-base/logging.h>
+#include <atomic>
+#include <jni.h>
+#include <jvmti.h>
+#include <map>
+#include <memory>
+#include <mutex>
+
+// We could probably return a JNI_ERR here but lets crash instead if something fails.
+#define CHECK_JVMTI_ERROR(jvmti, errnum) \
+ CHECK_EQ(JVMTI_ERROR_NONE, (errnum)) << GetJvmtiErrorString((jvmti), (errnum)) << (" ")
+
+namespace titrace {
+
+static const char* GetJvmtiErrorString(jvmtiEnv* jvmti, jvmtiError errnum) {
+ char* errnum_str = nullptr;
+ jvmti->GetErrorName(errnum, /*out*/ &errnum_str);
+ if (errnum_str == nullptr) {
+ return "Unknown";
+ }
+
+ return errnum_str;
+}
+
+// Type-safe wrapper for JVMTI-allocated memory.
+// Deallocates with jvmtiEnv::Deallocate.
+template <typename T>
+struct TiMemory {
+ explicit TiMemory(jvmtiEnv* env, T* mem, size_t size) : env_(env), mem_(mem), size_(size) {
+ }
+
+ ~TiMemory() {
+ if (mem_ != nullptr) {
+ env_->Deallocate(static_cast<unsigned char*>(mem_));
+ }
+ mem_ = nullptr;
+ }
+
+ TiMemory(const TiMemory& other) = delete;
+ TiMemory(TiMemory&& other) {
+ env_ = other.env_;
+ mem_ = other.mem_;
+ size_ = other.size_;
+
+ if (this != &other) {
+ other.env_ = nullptr;
+ other.mem_ = nullptr;
+ other.size_ = 0u;
+ }
+ }
+
+ TiMemory& operator=(TiMemory&& other) {
+ if (mem_ != other.mem_) {
+ TiMemory::~TiMemory();
+ }
+ new (this) TiMemory(std::move(other));
+ return *this;
+ }
+
+ T* GetMemory() {
+ return mem_;
+ }
+
+ size_t Size() {
+ return size_ / sizeof(T);
+ }
+
+ private:
+ jvmtiEnv* env_;
+ T* mem_;
+ size_t size_;
+};
+
+struct MethodBytecode {
+ explicit MethodBytecode(jvmtiEnv* env, unsigned char* memory, jint size)
+ : bytecode_(env, memory, static_cast<size_t>(size)) {
+ }
+
+ TiMemory<uint8_t> bytecode_;
+};
+
+struct TraceStatistics {
+ static void Initialize(jvmtiEnv* jvmti) {
+ TraceStatistics& stats = GetSingleton();
+
+ bool is_ri = true;
+ {
+ jvmtiError error;
+ char* value_ptr;
+ error = jvmti->GetSystemProperty("java.vm.name", /*out*/ &value_ptr);
+ CHECK_JVMTI_ERROR(jvmti, error) << "Failed to get property 'java.vm.name'";
+ CHECK(value_ptr != nullptr) << "Returned property was null for 'java.vm.name'";
+
+ if (strcmp("Dalvik", value_ptr) == 0) {
+ is_ri = false;
+ }
+ }
+
+ InstructionFileFormat format =
+ is_ri ? InstructionFileFormat::kClass : InstructionFileFormat::kDex;
+ stats.instruction_decoder_.reset(InstructionDecoder::NewInstance(format));
+
+ CHECK_GE(arraysize(stats.instruction_counter_),
+ stats.instruction_decoder_->GetMaximumOpcode());
+ }
+
+ static TraceStatistics& GetSingleton() {
+ static TraceStatistics stats;
+ return stats;
+ }
+
+ void Log() {
+ LOG(INFO) << "================================================";
+ LOG(INFO) << " TI Trace // Summary ";
+ LOG(INFO) << "++++++++++++++++++++++++++++++++++++++++++++++++";
+ LOG(INFO) << " * Single step counter: " << single_step_counter_;
+ LOG(INFO) << "+++++++++++ Instructions Count ++++++++++++";
+
+ size_t total = single_step_counter_;
+ for (size_t i = 0; i < arraysize(instruction_counter_); ++i) {
+ size_t inst_count = instruction_counter_[i];
+ if (inst_count > 0) {
+ const char* opcode_name = instruction_decoder_->GetName(i);
+ LOG(INFO) << " * " << opcode_name << "(op:" << i << "), count: " << inst_count
+ << ", % of total: " << (100.0 * inst_count / total);
+ }
+ }
+
+ LOG(INFO) << "------------------------------------------------";
+ }
+
+ void OnSingleStep(jvmtiEnv* jvmti_env, jmethodID method, jlocation location) {
+ // Counters do not need a happens-before.
+ // Use the weakest memory order simply to avoid tearing.
+ single_step_counter_.fetch_add(1u, std::memory_order_relaxed);
+
+ MethodBytecode& bytecode = LookupBytecode(jvmti_env, method);
+
+ // Decode jlocation value that depends on the bytecode format.
+ size_t actual_location = instruction_decoder_->LocationToOffset(static_cast<size_t>(location));
+
+ // Decode the exact instruction and increment its counter.
+ CHECK_LE(actual_location, bytecode.bytecode_.Size());
+ RecordInstruction(bytecode.bytecode_.GetMemory() + actual_location);
+ }
+
+ private:
+ void RecordInstruction(const uint8_t* instruction) {
+ uint8_t opcode = instruction[0];
+ // Counters do not need a happens-before.
+ // Use the weakest memory order simply to avoid tearing.
+ instruction_counter_[opcode].fetch_add(1u, std::memory_order_relaxed);
+ }
+
+ MethodBytecode& LookupBytecode(jvmtiEnv* jvmti_env, jmethodID method) {
+ jvmtiError error;
+ std::lock_guard<std::mutex> lock(bytecode_cache_mutex_);
+
+ auto it = bytecode_cache_.find(method);
+ if (it == bytecode_cache_.end()) {
+ jint bytecode_count_ptr = 0;
+ unsigned char* bytecodes_ptr = nullptr;
+
+ error = jvmti_env->GetBytecodes(method, &bytecode_count_ptr, &bytecodes_ptr);
+ CHECK_JVMTI_ERROR(jvmti_env, error) << "Failed to get bytecodes for method " << method;
+ CHECK(bytecodes_ptr != nullptr) << "Bytecode ptr was null for method " << method;
+ CHECK_GE(bytecode_count_ptr, 0) << "Bytecode size too small for method " << method;
+
+ // std::pair<iterator, bool inserted>
+ auto&& pair = bytecode_cache_.insert(
+ std::make_pair(method, MethodBytecode(jvmti_env, bytecodes_ptr, bytecode_count_ptr)));
+ it = pair.first;
+ }
+
+ // Returning the address is safe. if map is resized, the contents will not move.
+ return it->second;
+ }
+
+ std::unique_ptr<InstructionDecoder> instruction_decoder_;
+
+ std::atomic<size_t> single_step_counter_{0u}; // NOLINT [readability/braces] [4] [whitespace/braces] [5]
+ std::atomic<size_t> instruction_counter_[256]{}; // NOLINT [whitespace/braces] [5]
+
+ // Cache the bytecode to avoid calling into JVMTI repeatedly.
+ // TODO: invalidate if the bytecode was updated?
+ std::map<jmethodID, MethodBytecode> bytecode_cache_;
+ // bytecode cache is thread-safe.
+ std::mutex bytecode_cache_mutex_;
+};
+
+struct EventCallbacks {
+ static void SingleStep(jvmtiEnv* jvmti_env,
+ JNIEnv* jni_env ATTRIBUTE_UNUSED,
+ jthread thread ATTRIBUTE_UNUSED,
+ jmethodID method,
+ jlocation location) {
+ TraceStatistics& stats = TraceStatistics::GetSingleton();
+ stats.OnSingleStep(jvmti_env, method, location);
+ }
+
+ // Use "kill -SIGQUIT" to generate a data dump request.
+ // Useful when running an android app since it doesn't go through
+ // a normal Agent_OnUnload.
+ static void DataDumpRequest(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED) {
+ TraceStatistics& stats = TraceStatistics::GetSingleton();
+ stats.Log();
+ }
+};
+
+} // namespace titrace
+
+// Late attachment (e.g. 'am attach-agent').
+JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char* options, void* reserved) {
+ return Agent_OnLoad(vm, options, reserved);
+}
+
+// Early attachment (e.g. 'java -agent[lib|path]:filename.so').
+JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm,
+ char* /* options */,
+ void* /* reserved */) {
+ using namespace titrace; // NOLINT [build/namespaces] [5]
+
+ android::base::InitLogging(/* argv */nullptr);
+
+ jvmtiEnv* jvmti = nullptr;
+ {
+ jint res = 0;
+ res = jvm->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_1);
+
+ if (res != JNI_OK || jvmti == nullptr) {
+ LOG(FATAL) << "Unable to access JVMTI, error code " << res;
+ }
+ }
+
+ LOG(INFO) << "Agent_OnLoad: Hello World";
+
+ {
+ // Initialize our instruction file-format decoder.
+ TraceStatistics::Initialize(jvmti);
+ }
+
+ jvmtiError error{}; // NOLINT [readability/braces] [4] [whitespace/braces] [5]
+
+ // Set capabilities.
+ {
+ jvmtiCapabilities caps = {};
+ caps.can_generate_single_step_events = 1;
+ caps.can_get_bytecodes = 1;
+
+ error = jvmti->AddCapabilities(&caps);
+ CHECK_JVMTI_ERROR(jvmti, error)
+ << "Unable to get necessary JVMTI capabilities";
+ }
+
+ // Set callbacks.
+ {
+ jvmtiEventCallbacks callbacks = {};
+ callbacks.SingleStep = &EventCallbacks::SingleStep;
+ callbacks.DataDumpRequest = &EventCallbacks::DataDumpRequest;
+
+ error = jvmti->SetEventCallbacks(&callbacks,
+ static_cast<jint>(sizeof(callbacks)));
+ CHECK_JVMTI_ERROR(jvmti, error) << "Unable to set event callbacks";
+ }
+
+ // Enable events notification.
+ {
+ error = jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_SINGLE_STEP,
+ nullptr /* all threads */);
+ CHECK_JVMTI_ERROR(jvmti, error)
+ << "Failed to enable SINGLE_STEP notification";
+
+ error = jvmti->SetEventNotificationMode(JVMTI_ENABLE,
+ JVMTI_EVENT_DATA_DUMP_REQUEST,
+ nullptr /* all threads */);
+ CHECK_JVMTI_ERROR(jvmti, error)
+ << "Failed to enable DATA_DUMP_REQUEST notification";
+ }
+
+ return JNI_OK;
+}
+
+// Note: This is not called for normal Android apps,
+// use "kill -SIGQUIT" instead to generate a data dump request.
+JNIEXPORT void JNICALL Agent_OnUnload(JavaVM* vm ATTRIBUTE_UNUSED) {
+ using namespace titrace; // NOLINT [build/namespaces] [5]
+ LOG(INFO) << "Agent_OnUnload: Goodbye";
+
+ TraceStatistics::GetSingleton().Log();
+}
+