diff options
62 files changed, 1417 insertions, 185 deletions
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 0b50619a66..958c1a6fdb 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -183,10 +183,13 @@ class SuspendCheckSlowPathX86 : public SlowPathCode { : SlowPathCode(instruction), successor_(successor) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { + LocationSummary* locations = instruction_->GetLocations(); CodeGeneratorX86* x86_codegen = down_cast<CodeGeneratorX86*>(codegen); __ Bind(GetEntryLabel()); + SaveLiveRegisters(codegen, locations); // only saves full width XMM for SIMD x86_codegen->InvokeRuntime(kQuickTestSuspend, instruction_, instruction_->GetDexPc(), this); CheckEntrypointTypes<kQuickTestSuspend, void, void>(); + RestoreLiveRegisters(codegen, locations); // only saves full width XMM for SIMD if (successor_ == nullptr) { __ jmp(GetReturnLabel()); } else { @@ -963,12 +966,20 @@ size_t CodeGeneratorX86::RestoreCoreRegister(size_t stack_index, uint32_t reg_id } size_t CodeGeneratorX86::SaveFloatingPointRegister(size_t stack_index, uint32_t reg_id) { - __ movsd(Address(ESP, stack_index), XmmRegister(reg_id)); + if (GetGraph()->HasSIMD()) { + __ movupd(Address(ESP, stack_index), XmmRegister(reg_id)); + } else { + __ movsd(Address(ESP, stack_index), XmmRegister(reg_id)); + } return GetFloatingPointSpillSlotSize(); } size_t CodeGeneratorX86::RestoreFloatingPointRegister(size_t stack_index, uint32_t reg_id) { - __ movsd(XmmRegister(reg_id), Address(ESP, stack_index)); + if (GetGraph()->HasSIMD()) { + __ movupd(XmmRegister(reg_id), Address(ESP, stack_index)); + } else { + __ movsd(XmmRegister(reg_id), Address(ESP, stack_index)); + } return GetFloatingPointSpillSlotSize(); } @@ -5699,7 +5710,12 @@ void InstructionCodeGeneratorX86::VisitParallelMove(HParallelMove* instruction) void LocationsBuilderX86::VisitSuspendCheck(HSuspendCheck* instruction) { LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kCallOnSlowPath); - locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty()); // No caller-save registers. + // In suspend check slow path, usually there are no caller-save registers at all. + // If SIMD instructions are present, however, we force spilling all live SIMD + // registers in full width (since the runtime only saves/restores lower part). + locations->SetCustomSlowPathCallerSaves(GetGraph()->HasSIMD() + ? RegisterSet::AllFpu() + : RegisterSet::Empty()); } void InstructionCodeGeneratorX86::VisitSuspendCheck(HSuspendCheck* instruction) { diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h index 65ee383b54..ca3a9eadd2 100644 --- a/compiler/optimizing/code_generator_x86.h +++ b/compiler/optimizing/code_generator_x86.h @@ -348,8 +348,9 @@ class CodeGeneratorX86 : public CodeGenerator { } size_t GetFloatingPointSpillSlotSize() const OVERRIDE { - // 8 bytes == 2 words for each spill. - return 2 * kX86WordSize; + return GetGraph()->HasSIMD() + ? 4 * kX86WordSize // 16 bytes == 4 words for each spill + : 2 * kX86WordSize; // 8 bytes == 2 words for each spill } HGraphVisitor* GetLocationBuilder() OVERRIDE { diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index 08f1adfcff..c106d9b06e 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -140,10 +140,13 @@ class SuspendCheckSlowPathX86_64 : public SlowPathCode { : SlowPathCode(instruction), successor_(successor) {} void EmitNativeCode(CodeGenerator* codegen) OVERRIDE { + LocationSummary* locations = instruction_->GetLocations(); CodeGeneratorX86_64* x86_64_codegen = down_cast<CodeGeneratorX86_64*>(codegen); __ Bind(GetEntryLabel()); + SaveLiveRegisters(codegen, locations); // only saves full width XMM for SIMD x86_64_codegen->InvokeRuntime(kQuickTestSuspend, instruction_, instruction_->GetDexPc(), this); CheckEntrypointTypes<kQuickTestSuspend, void, void>(); + RestoreLiveRegisters(codegen, locations); // only saves full width XMM for SIMD if (successor_ == nullptr) { __ jmp(GetReturnLabel()); } else { @@ -1158,13 +1161,21 @@ size_t CodeGeneratorX86_64::RestoreCoreRegister(size_t stack_index, uint32_t reg } size_t CodeGeneratorX86_64::SaveFloatingPointRegister(size_t stack_index, uint32_t reg_id) { - __ movsd(Address(CpuRegister(RSP), stack_index), XmmRegister(reg_id)); - return kX86_64WordSize; + if (GetGraph()->HasSIMD()) { + __ movupd(Address(CpuRegister(RSP), stack_index), XmmRegister(reg_id)); + } else { + __ movsd(Address(CpuRegister(RSP), stack_index), XmmRegister(reg_id)); + } + return GetFloatingPointSpillSlotSize(); } size_t CodeGeneratorX86_64::RestoreFloatingPointRegister(size_t stack_index, uint32_t reg_id) { - __ movsd(XmmRegister(reg_id), Address(CpuRegister(RSP), stack_index)); - return kX86_64WordSize; + if (GetGraph()->HasSIMD()) { + __ movupd(XmmRegister(reg_id), Address(CpuRegister(RSP), stack_index)); + } else { + __ movsd(XmmRegister(reg_id), Address(CpuRegister(RSP), stack_index)); + } + return GetFloatingPointSpillSlotSize(); } void CodeGeneratorX86_64::InvokeRuntime(QuickEntrypointEnum entrypoint, @@ -5152,7 +5163,12 @@ void InstructionCodeGeneratorX86_64::VisitParallelMove(HParallelMove* instructio void LocationsBuilderX86_64::VisitSuspendCheck(HSuspendCheck* instruction) { LocationSummary* locations = new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kCallOnSlowPath); - locations->SetCustomSlowPathCallerSaves(RegisterSet::Empty()); // No caller-save registers. + // In suspend check slow path, usually there are no caller-save registers at all. + // If SIMD instructions are present, however, we force spilling all live SIMD + // registers in full width (since the runtime only saves/restores lower part). + locations->SetCustomSlowPathCallerSaves(GetGraph()->HasSIMD() + ? RegisterSet::AllFpu() + : RegisterSet::Empty()); } void InstructionCodeGeneratorX86_64::VisitSuspendCheck(HSuspendCheck* instruction) { diff --git a/compiler/optimizing/code_generator_x86_64.h b/compiler/optimizing/code_generator_x86_64.h index 376c3ce381..c8336dabd9 100644 --- a/compiler/optimizing/code_generator_x86_64.h +++ b/compiler/optimizing/code_generator_x86_64.h @@ -326,7 +326,9 @@ class CodeGeneratorX86_64 : public CodeGenerator { } size_t GetFloatingPointSpillSlotSize() const OVERRIDE { - return kX86_64WordSize; + return GetGraph()->HasSIMD() + ? 2 * kX86_64WordSize // 16 bytes == 2 x86_64 words for each spill + : 1 * kX86_64WordSize; // 8 bytes == 1 x86_64 words for each spill } HGraphVisitor* GetLocationBuilder() OVERRIDE { diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc index 62f5114e59..9550a53333 100644 --- a/compiler/optimizing/inliner.cc +++ b/compiler/optimizing/inliner.cc @@ -292,7 +292,18 @@ ArtMethod* HInliner::TryCHADevirtualization(ArtMethod* resolved_method) { return nullptr; } PointerSize pointer_size = caller_compilation_unit_.GetClassLinker()->GetImagePointerSize(); - return resolved_method->GetSingleImplementation(pointer_size); + ArtMethod* single_impl = resolved_method->GetSingleImplementation(pointer_size); + if (single_impl == nullptr) { + return nullptr; + } + if (single_impl->IsProxyMethod()) { + // Proxy method is a generic invoker that's not worth + // devirtualizing/inlining. It also causes issues when the proxy + // method is in another dex file if we try to rewrite invoke-interface to + // invoke-virtual because a proxy method doesn't have a real dex file. + return nullptr; + } + return single_impl; } bool HInliner::TryInline(HInvoke* invoke_instruction) { @@ -1021,11 +1032,23 @@ bool HInliner::TryInlineAndReplace(HInvoke* invoke_instruction, HBasicBlock* bb_cursor = invoke_instruction->GetBlock(); if (!TryBuildAndInline(invoke_instruction, method, receiver_type, &return_replacement)) { if (invoke_instruction->IsInvokeInterface()) { + DCHECK(!method->IsProxyMethod()); // Turn an invoke-interface into an invoke-virtual. An invoke-virtual is always // better than an invoke-interface because: // 1) In the best case, the interface call has one more indirection (to fetch the IMT). // 2) We will not go to the conflict trampoline with an invoke-virtual. // TODO: Consider sharpening once it is not dependent on the compiler driver. + + if (method->IsDefault() && !method->IsCopied()) { + // Changing to invoke-virtual cannot be done on an original default method + // since it's not in any vtable. Devirtualization by exact type/inline-cache + // always uses a method in the iftable which is never an original default + // method. + // On the other hand, inlining an original default method by CHA is fine. + DCHECK(cha_devirtualize); + return false; + } + const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile(); uint32_t dex_method_index = FindMethodIndexIn( method, caller_dex_file, invoke_instruction->GetDexMethodIndex()); diff --git a/compiler/optimizing/locations.h b/compiler/optimizing/locations.h index 091b58a63d..d391f6913c 100644 --- a/compiler/optimizing/locations.h +++ b/compiler/optimizing/locations.h @@ -417,6 +417,7 @@ std::ostream& operator<<(std::ostream& os, const Location::Policy& rhs); class RegisterSet : public ValueObject { public: static RegisterSet Empty() { return RegisterSet(); } + static RegisterSet AllFpu() { return RegisterSet(0, -1); } void Add(Location loc) { if (loc.IsRegister()) { @@ -462,6 +463,7 @@ class RegisterSet : public ValueObject { private: RegisterSet() : core_registers_(0), floating_point_registers_(0) {} + RegisterSet(uint32_t core, uint32_t fp) : core_registers_(core), floating_point_registers_(fp) {} uint32_t core_registers_; uint32_t floating_point_registers_; diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc index 020e4463d4..ec706e6694 100644 --- a/compiler/optimizing/nodes.cc +++ b/compiler/optimizing/nodes.cc @@ -2046,6 +2046,9 @@ HInstruction* HGraph::InlineInto(HGraph* outer_graph, HInvoke* invoke) { if (HasTryCatch()) { outer_graph->SetHasTryCatch(true); } + if (HasSIMD()) { + outer_graph->SetHasSIMD(true); + } HInstruction* return_value = nullptr; if (GetBlocks().size() == 3) { diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 542b218cf8..6881d8f6ae 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -323,6 +323,7 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { temporaries_vreg_slots_(0), has_bounds_checks_(false), has_try_catch_(false), + has_simd_(false), has_loops_(false), has_irreducible_loops_(false), debuggable_(debuggable), @@ -560,6 +561,9 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { bool HasTryCatch() const { return has_try_catch_; } void SetHasTryCatch(bool value) { has_try_catch_ = value; } + bool HasSIMD() const { return has_simd_; } + void SetHasSIMD(bool value) { has_simd_ = value; } + bool HasLoops() const { return has_loops_; } void SetHasLoops(bool value) { has_loops_ = value; } @@ -652,6 +656,11 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { // false positives. bool has_try_catch_; + // Flag whether SIMD instructions appear in the graph. If true, the + // code generators may have to be more careful spilling the wider + // contents of SIMD registers. + bool has_simd_; + // Flag whether there are any loops in the graph. We can skip loop // optimization if it's false. It's only best effort to keep it up // to date in the presence of code elimination so there might be false diff --git a/compiler/optimizing/register_allocation_resolver.cc b/compiler/optimizing/register_allocation_resolver.cc index 8a9c1ccaff..0d33b49fdb 100644 --- a/compiler/optimizing/register_allocation_resolver.cc +++ b/compiler/optimizing/register_allocation_resolver.cc @@ -299,11 +299,13 @@ void RegisterAllocationResolver::ConnectSiblings(LiveInterval* interval) { // Currently, we spill unconditionnally the current method in the code generators. && !interval->GetDefinedBy()->IsCurrentMethod()) { // We spill eagerly, so move must be at definition. - InsertMoveAfter(interval->GetDefinedBy(), - interval->ToLocation(), - interval->NeedsTwoSpillSlots() - ? Location::DoubleStackSlot(interval->GetParent()->GetSpillSlot()) - : Location::StackSlot(interval->GetParent()->GetSpillSlot())); + Location loc; + switch (interval->NumberOfSpillSlotsNeeded()) { + case 1: loc = Location::StackSlot(interval->GetParent()->GetSpillSlot()); break; + case 2: loc = Location::DoubleStackSlot(interval->GetParent()->GetSpillSlot()); break; + default: LOG(FATAL) << "Unexpected number of spill slots"; UNREACHABLE(); + } + InsertMoveAfter(interval->GetDefinedBy(), interval->ToLocation(), loc); } UsePosition* use = current->GetFirstUse(); EnvUsePosition* env_use = current->GetFirstEnvironmentUse(); @@ -459,9 +461,11 @@ void RegisterAllocationResolver::ConnectSplitSiblings(LiveInterval* interval, location_source = defined_by->GetLocations()->Out(); } else { DCHECK(defined_by->IsCurrentMethod()); - location_source = parent->NeedsTwoSpillSlots() - ? Location::DoubleStackSlot(parent->GetSpillSlot()) - : Location::StackSlot(parent->GetSpillSlot()); + switch (parent->NumberOfSpillSlotsNeeded()) { + case 1: location_source = Location::StackSlot(parent->GetSpillSlot()); break; + case 2: location_source = Location::DoubleStackSlot(parent->GetSpillSlot()); break; + default: LOG(FATAL) << "Unexpected number of spill slots"; UNREACHABLE(); + } } } else { DCHECK(source != nullptr); diff --git a/compiler/optimizing/register_allocator_graph_color.cc b/compiler/optimizing/register_allocator_graph_color.cc index 9064f865c3..87f709f63d 100644 --- a/compiler/optimizing/register_allocator_graph_color.cc +++ b/compiler/optimizing/register_allocator_graph_color.cc @@ -1029,7 +1029,7 @@ void RegisterAllocatorGraphColor::AllocateSpillSlotForCatchPhi(HInstruction* ins interval->SetSpillSlot(previous_phi->GetLiveInterval()->GetSpillSlot()); } else { interval->SetSpillSlot(catch_phi_spill_slot_counter_); - catch_phi_spill_slot_counter_ += interval->NeedsTwoSpillSlots() ? 2 : 1; + catch_phi_spill_slot_counter_ += interval->NumberOfSpillSlotsNeeded(); } } } @@ -1996,43 +1996,48 @@ void RegisterAllocatorGraphColor::ColorSpillSlots(ArenaVector<LiveInterval*>* in bool is_interval_beginning; size_t position; std::tie(position, is_interval_beginning, parent_interval) = *it; - - bool needs_two_slots = parent_interval->NeedsTwoSpillSlots(); + size_t number_of_spill_slots_needed = parent_interval->NumberOfSpillSlotsNeeded(); if (is_interval_beginning) { DCHECK(!parent_interval->HasSpillSlot()); DCHECK_EQ(position, parent_interval->GetStart()); - // Find a free stack slot. + // Find first available free stack slot(s). size_t slot = 0; - for (; taken.IsBitSet(slot) || (needs_two_slots && taken.IsBitSet(slot + 1)); ++slot) { - // Skip taken slots. + for (; ; ++slot) { + bool found = true; + for (size_t s = slot, u = slot + number_of_spill_slots_needed; s < u; s++) { + if (taken.IsBitSet(s)) { + found = false; + break; // failure + } + } + if (found) { + break; // success + } } + parent_interval->SetSpillSlot(slot); - *num_stack_slots_used = std::max(*num_stack_slots_used, - needs_two_slots ? slot + 1 : slot + 2); - if (needs_two_slots && *num_stack_slots_used % 2 != 0) { + *num_stack_slots_used = std::max(*num_stack_slots_used, slot + number_of_spill_slots_needed); + if (number_of_spill_slots_needed > 1 && *num_stack_slots_used % 2 != 0) { // The parallel move resolver requires that there be an even number of spill slots // allocated for pair value types. ++(*num_stack_slots_used); } - taken.SetBit(slot); - if (needs_two_slots) { - taken.SetBit(slot + 1); + for (size_t s = slot, u = slot + number_of_spill_slots_needed; s < u; s++) { + taken.SetBit(s); } } else { DCHECK_EQ(position, parent_interval->GetLastSibling()->GetEnd()); DCHECK(parent_interval->HasSpillSlot()); - // Free up the stack slot used by this interval. + // Free up the stack slot(s) used by this interval. size_t slot = parent_interval->GetSpillSlot(); - DCHECK(taken.IsBitSet(slot)); - DCHECK(!needs_two_slots || taken.IsBitSet(slot + 1)); - taken.ClearBit(slot); - if (needs_two_slots) { - taken.ClearBit(slot + 1); + for (size_t s = slot, u = slot + number_of_spill_slots_needed; s < u; s++) { + DCHECK(taken.IsBitSet(s)); + taken.ClearBit(s); } } } diff --git a/compiler/optimizing/register_allocator_linear_scan.cc b/compiler/optimizing/register_allocator_linear_scan.cc index 6354e76ec8..ab8d540359 100644 --- a/compiler/optimizing/register_allocator_linear_scan.cc +++ b/compiler/optimizing/register_allocator_linear_scan.cc @@ -1125,36 +1125,31 @@ void RegisterAllocatorLinearScan::AllocateSpillSlotFor(LiveInterval* interval) { LOG(FATAL) << "Unexpected type for interval " << interval->GetType(); } - // Find an available spill slot. + // Find first available spill slots. + size_t number_of_spill_slots_needed = parent->NumberOfSpillSlotsNeeded(); size_t slot = 0; for (size_t e = spill_slots->size(); slot < e; ++slot) { - if ((*spill_slots)[slot] <= parent->GetStart()) { - if (!parent->NeedsTwoSpillSlots()) { - // One spill slot is sufficient. - break; - } - if (slot == e - 1 || (*spill_slots)[slot + 1] <= parent->GetStart()) { - // Two spill slots are available. + bool found = true; + for (size_t s = slot, u = std::min(slot + number_of_spill_slots_needed, e); s < u; s++) { + if ((*spill_slots)[s] > parent->GetStart()) { + found = false; // failure break; } } + if (found) { + break; // success + } } + // Need new spill slots? + size_t upper = slot + number_of_spill_slots_needed; + if (upper > spill_slots->size()) { + spill_slots->resize(upper); + } + // Set slots to end. size_t end = interval->GetLastSibling()->GetEnd(); - if (parent->NeedsTwoSpillSlots()) { - if (slot + 2u > spill_slots->size()) { - // We need a new spill slot. - spill_slots->resize(slot + 2u, end); - } - (*spill_slots)[slot] = end; - (*spill_slots)[slot + 1] = end; - } else { - if (slot == spill_slots->size()) { - // We need a new spill slot. - spill_slots->push_back(end); - } else { - (*spill_slots)[slot] = end; - } + for (size_t s = slot; s < upper; s++) { + (*spill_slots)[s] = end; } // Note that the exact spill slot location will be computed when we resolve, @@ -1180,7 +1175,7 @@ void RegisterAllocatorLinearScan::AllocateSpillSlotForCatchPhi(HPhi* phi) { // TODO: Reuse spill slots when intervals of phis from different catch // blocks do not overlap. interval->SetSpillSlot(catch_phi_spill_slots_); - catch_phi_spill_slots_ += interval->NeedsTwoSpillSlots() ? 2 : 1; + catch_phi_spill_slots_ += interval->NumberOfSpillSlotsNeeded(); } } diff --git a/compiler/optimizing/ssa_liveness_analysis.cc b/compiler/optimizing/ssa_liveness_analysis.cc index e8e12e1a55..c0a045c33e 100644 --- a/compiler/optimizing/ssa_liveness_analysis.cc +++ b/compiler/optimizing/ssa_liveness_analysis.cc @@ -469,8 +469,8 @@ bool LiveInterval::SameRegisterKind(Location other) const { } } -bool LiveInterval::NeedsTwoSpillSlots() const { - return type_ == Primitive::kPrimLong || type_ == Primitive::kPrimDouble; +size_t LiveInterval::NumberOfSpillSlotsNeeded() const { + return (type_ == Primitive::kPrimLong || type_ == Primitive::kPrimDouble) ? 2 : 1; } Location LiveInterval::ToLocation() const { @@ -494,10 +494,10 @@ Location LiveInterval::ToLocation() const { if (defined_by->IsConstant()) { return defined_by->GetLocations()->Out(); } else if (GetParent()->HasSpillSlot()) { - if (NeedsTwoSpillSlots()) { - return Location::DoubleStackSlot(GetParent()->GetSpillSlot()); - } else { - return Location::StackSlot(GetParent()->GetSpillSlot()); + switch (NumberOfSpillSlotsNeeded()) { + case 1: return Location::StackSlot(GetParent()->GetSpillSlot()); + case 2: return Location::DoubleStackSlot(GetParent()->GetSpillSlot()); + default: LOG(FATAL) << "Unexpected number of spill slots"; UNREACHABLE(); } } else { return Location(); diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h index 340d0ccefe..e9dffc1fac 100644 --- a/compiler/optimizing/ssa_liveness_analysis.h +++ b/compiler/optimizing/ssa_liveness_analysis.h @@ -762,9 +762,9 @@ class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { // Returns kNoRegister otherwise. int FindHintAtDefinition() const; - // Returns whether the interval needs two (Dex virtual register size `kVRegSize`) - // slots for spilling. - bool NeedsTwoSpillSlots() const; + // Returns the number of required spilling slots (measured as a multiple of the + // Dex virtual register size `kVRegSize`). + size_t NumberOfSpillSlotsNeeded() const; bool IsFloatingPoint() const { return type_ == Primitive::kPrimFloat || type_ == Primitive::kPrimDouble; diff --git a/disassembler/disassembler_x86.cc b/disassembler/disassembler_x86.cc index a289433af5..77ed3c6a22 100644 --- a/disassembler/disassembler_x86.cc +++ b/disassembler/disassembler_x86.cc @@ -832,6 +832,24 @@ DISASSEMBLER_ENTRY(cmp, store = true; immediate_bytes = 1; break; + case 0x74: + case 0x75: + case 0x76: + if (prefix[2] == 0x66) { + src_reg_file = dst_reg_file = SSE; + prefix[2] = 0; // clear prefix now it's served its purpose as part of the opcode + } else { + src_reg_file = dst_reg_file = MMX; + } + switch (*instr) { + case 0x74: opcode1 = "pcmpeqb"; break; + case 0x75: opcode1 = "pcmpeqw"; break; + case 0x76: opcode1 = "pcmpeqd"; break; + } + prefix[2] = 0; + has_modrm = true; + load = true; + break; case 0x7C: if (prefix[0] == 0xF2) { opcode1 = "haddps"; diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc index 52f3b52ee2..1a8a614a4a 100644 --- a/profman/profile_assistant_test.cc +++ b/profman/profile_assistant_test.cc @@ -22,6 +22,7 @@ #include "exec_utils.h" #include "jit/profile_compilation_info.h" #include "mirror/class-inl.h" +#include "obj_ptr-inl.h" #include "profile_assistant.h" #include "scoped_thread_state_change-inl.h" #include "utils.h" @@ -140,7 +141,8 @@ class ProfileAssistantTest : public CommonRuntimeTest { return true; } - bool CreateAndDump(const std::string& input_file_contents, std::string* output_file_contents) { + bool CreateAndDump(const std::string& input_file_contents, + std::string* output_file_contents) { ScratchFile profile_file; EXPECT_TRUE(CreateProfile(input_file_contents, profile_file.GetFilename(), @@ -156,7 +158,7 @@ class ProfileAssistantTest : public CommonRuntimeTest { ScopedObjectAccess soa(self); StackHandleScope<1> hs(self); Handle<mirror::ClassLoader> h_loader( - hs.NewHandle(self->DecodeJObject(class_loader)->AsClassLoader())); + hs.NewHandle(ObjPtr<mirror::ClassLoader>::DownCast(self->DecodeJObject(class_loader)))); return class_linker->FindClass(self, clazz.c_str(), h_loader); } @@ -442,6 +444,44 @@ TEST_F(ProfileAssistantTest, TestProfileCreationAllMatch) { ASSERT_EQ(output_file_contents, expected_contents); } +TEST_F(ProfileAssistantTest, TestProfileCreationGenerateMethods) { + // Class names put here need to be in sorted order. + std::vector<std::string> class_names = { + "Ljava/lang/Math;->*", + }; + std::string input_file_contents; + std::string expected_contents; + for (std::string& class_name : class_names) { + input_file_contents += class_name + std::string("\n"); + expected_contents += DescriptorToDot(class_name.c_str()) + + std::string("\n"); + } + std::string output_file_contents; + ScratchFile profile_file; + EXPECT_TRUE(CreateProfile(input_file_contents, + profile_file.GetFilename(), + GetLibCoreDexFileNames()[0])); + ProfileCompilationInfo info; + profile_file.GetFile()->ResetOffset(); + ASSERT_TRUE(info.Load(GetFd(profile_file))); + // Verify that the profile has matching methods. + ScopedObjectAccess soa(Thread::Current()); + ObjPtr<mirror::Class> klass = GetClass(nullptr, "Ljava/lang/Math;"); + ASSERT_TRUE(klass != nullptr); + size_t method_count = 0; + for (ArtMethod& method : klass->GetMethods(kRuntimePointerSize)) { + if (!method.IsCopied() && method.GetCodeItem() != nullptr) { + ++method_count; + ProfileCompilationInfo::OfflineProfileMethodInfo pmi; + ASSERT_TRUE(info.GetMethod(method.GetDexFile()->GetLocation(), + method.GetDexFile()->GetLocationChecksum(), + method.GetDexMethodIndex(), + &pmi)); + } + } + EXPECT_GT(method_count, 0u); +} + TEST_F(ProfileAssistantTest, TestProfileCreationOneNotMatched) { // Class names put here need to be in sorted order. std::vector<std::string> class_names = { diff --git a/profman/profman.cc b/profman/profman.cc index f7316cc129..fdb9a75a6f 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -120,7 +120,6 @@ NO_RETURN static void Usage(const char *fmt, ...) { UsageError(""); UsageError(" --create-profile-from=<filename>: creates a profile from a list of classes."); UsageError(""); - UsageError(""); UsageError(" --dex-location=<string>: location string to use with corresponding"); UsageError(" apk-fd to find dex files"); UsageError(""); @@ -140,6 +139,7 @@ static constexpr uint16_t kDefaultTestProfileClassRatio = 5; // Separators used when parsing human friendly representation of profiles. static const std::string kMethodSep = "->"; static const std::string kMissingTypesMarker = "missing_types"; +static const std::string kClassAllMethods = "*"; static constexpr char kProfileParsingInlineChacheSep = '+'; static constexpr char kProfileParsingTypeSep = ','; static constexpr char kProfileParsingFirstCharInSignature = '('; @@ -630,6 +630,7 @@ class ProfMan FINAL { // "LTestInline;->inlinePolymorphic(LSuper;)I+LSubA;,LSubB;,LSubC;". // "LTestInline;->inlineMissingTypes(LSuper;)I+missing_types". // "LTestInline;->inlineNoInlineCaches(LSuper;)I". + // "LTestInline;->*". // The method and classes are searched only in the given dex files. bool ProcessLine(const std::vector<std::unique_ptr<const DexFile>>& dex_files, const std::string& line, @@ -650,8 +651,8 @@ class ProfMan FINAL { return false; } - if (method_str.empty()) { - // No method to add. Just add the class. + if (method_str.empty() || method_str == kClassAllMethods) { + // Start by adding the class. std::set<DexCacheResolvedClasses> resolved_class_set; const DexFile* dex_file = class_ref.dex_file; const auto& dex_resolved_classes = resolved_class_set.emplace( @@ -659,7 +660,27 @@ class ProfMan FINAL { dex_file->GetBaseLocation(), dex_file->GetLocationChecksum()); dex_resolved_classes.first->AddClass(class_ref.type_index); - profile->AddMethodsAndClasses(std::vector<ProfileMethodInfo>(), resolved_class_set); + std::vector<ProfileMethodInfo> methods; + if (method_str == kClassAllMethods) { + // Add all of the methods. + const DexFile::ClassDef* class_def = dex_file->FindClassDef(class_ref.type_index); + const uint8_t* class_data = dex_file->GetClassData(*class_def); + if (class_data != nullptr) { + ClassDataItemIterator it(*dex_file, class_data); + while (it.HasNextStaticField() || it.HasNextInstanceField()) { + it.Next(); + } + while (it.HasNextDirectMethod() || it.HasNextVirtualMethod()) { + if (it.GetMethodCodeItemOffset() != 0) { + // Add all of the methods that have code to the profile. + const uint32_t method_idx = it.GetMemberIndex(); + methods.push_back(ProfileMethodInfo(dex_file, method_idx)); + } + it.Next(); + } + } + } + profile->AddMethodsAndClasses(methods, resolved_class_set); return true; } diff --git a/runtime/arch/mips64/instruction_set_features_mips64_test.cc b/runtime/arch/mips64/instruction_set_features_mips64_test.cc index 563200ff76..0ba0bd4c15 100644 --- a/runtime/arch/mips64/instruction_set_features_mips64_test.cc +++ b/runtime/arch/mips64/instruction_set_features_mips64_test.cc @@ -20,7 +20,7 @@ namespace art { -TEST(Mips64InstructionSetFeaturesTest, Mips64Features) { +TEST(Mips64InstructionSetFeaturesTest, Mips64FeaturesFromDefaultVariant) { std::string error_msg; std::unique_ptr<const InstructionSetFeatures> mips64_features( InstructionSetFeatures::FromVariant(kMips64, "default", &error_msg)); @@ -31,4 +31,20 @@ TEST(Mips64InstructionSetFeaturesTest, Mips64Features) { EXPECT_EQ(mips64_features->AsBitmap(), 1U); } +TEST(Mips64InstructionSetFeaturesTest, Mips64FeaturesFromR6Variant) { + std::string error_msg; + std::unique_ptr<const InstructionSetFeatures> mips64r6_features( + InstructionSetFeatures::FromVariant(kMips64, "mips64r6", &error_msg)); + ASSERT_TRUE(mips64r6_features.get() != nullptr) << error_msg; + EXPECT_EQ(mips64r6_features->GetInstructionSet(), kMips64); + EXPECT_TRUE(mips64r6_features->Equals(mips64r6_features.get())); + EXPECT_STREQ("msa", mips64r6_features->GetFeatureString().c_str()); + EXPECT_EQ(mips64r6_features->AsBitmap(), 1U); + + std::unique_ptr<const InstructionSetFeatures> mips64_default_features( + InstructionSetFeatures::FromVariant(kMips64, "default", &error_msg)); + ASSERT_TRUE(mips64_default_features.get() != nullptr) << error_msg; + EXPECT_TRUE(mips64r6_features->Equals(mips64_default_features.get())); +} + } // namespace art diff --git a/runtime/art_method.h b/runtime/art_method.h index 2248c3bd9d..8f09cc6d03 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -691,7 +691,7 @@ class ArtMethod FINAL { // Pointer to JNI function registered to this method, or a function to resolve the JNI function, // or the profiling data for non-native methods, or an ImtConflictTable, or the - // single-implementation of an abstract method. + // single-implementation of an abstract/interface method. void* data_; // Method dispatch from quick compiled code invokes this pointer which may cause bridging into diff --git a/runtime/base/scoped_flock.cc b/runtime/base/scoped_flock.cc index d4bb56b62a..5394e53fa3 100644 --- a/runtime/base/scoped_flock.cc +++ b/runtime/base/scoped_flock.cc @@ -116,7 +116,10 @@ ScopedFlock::ScopedFlock() { } ScopedFlock::~ScopedFlock() { if (file_.get() != nullptr) { int flock_result = TEMP_FAILURE_RETRY(flock(file_->Fd(), LOCK_UN)); - CHECK_EQ(0, flock_result); + if (flock_result != 0) { + PLOG(FATAL) << "Unable to unlock file " << file_->GetPath(); + UNREACHABLE(); + } int close_result = -1; if (file_->ReadOnlyMode()) { close_result = file_->Close(); diff --git a/runtime/cha.cc b/runtime/cha.cc index eaba01b2ce..7948c29e5d 100644 --- a/runtime/cha.cc +++ b/runtime/cha.cc @@ -210,7 +210,7 @@ void ClassHierarchyAnalysis::VerifyNonSingleImplementation(mirror::Class* verify } } -void ClassHierarchyAnalysis::CheckSingleImplementationInfo( +void ClassHierarchyAnalysis::CheckVirtualMethodSingleImplementationInfo( Handle<mirror::Class> klass, ArtMethod* virtual_method, ArtMethod* method_in_super, @@ -290,8 +290,9 @@ void ClassHierarchyAnalysis::CheckSingleImplementationInfo( // A non-abstract method overrides an abstract method. if (method_in_super->GetSingleImplementation(pointer_size) == nullptr) { // Abstract method_in_super has no implementation yet. - // We need to grab cha_lock_ for further checking/updating due to possible - // races. + // We need to grab cha_lock_ since there may be multiple class linking + // going on that can check/modify the single-implementation flag/method + // of method_in_super. MutexLock cha_mu(Thread::Current(), *Locks::cha_lock_); if (!method_in_super->HasSingleImplementation()) { return; @@ -362,6 +363,55 @@ void ClassHierarchyAnalysis::CheckSingleImplementationInfo( } } +void ClassHierarchyAnalysis::CheckInterfaceMethodSingleImplementationInfo( + Handle<mirror::Class> klass, + ArtMethod* interface_method, + ArtMethod* implementation_method, + std::unordered_set<ArtMethod*>& invalidated_single_impl_methods, + PointerSize pointer_size) { + DCHECK(klass->IsInstantiable()); + DCHECK(interface_method->IsAbstract() || interface_method->IsDefault()); + + if (!interface_method->HasSingleImplementation()) { + return; + } + + if (implementation_method->IsAbstract()) { + // An instantiable class doesn't supply an implementation for + // interface_method. Invoking the interface method on the class will throw + // AbstractMethodError. This is an uncommon case, so we simply treat + // interface_method as not having single-implementation. + invalidated_single_impl_methods.insert(interface_method); + return; + } + + // We need to grab cha_lock_ since there may be multiple class linking going + // on that can check/modify the single-implementation flag/method of + // interface_method. + MutexLock cha_mu(Thread::Current(), *Locks::cha_lock_); + // Do this check again after we grab cha_lock_. + if (!interface_method->HasSingleImplementation()) { + return; + } + + ArtMethod* single_impl = interface_method->GetSingleImplementation(pointer_size); + if (single_impl == nullptr) { + // implementation_method becomes the first implementation for + // interface_method. + interface_method->SetSingleImplementation(implementation_method, pointer_size); + // Keep interface_method's single-implementation status. + return; + } + DCHECK(!single_impl->IsAbstract()); + if (single_impl->GetDeclaringClass() == implementation_method->GetDeclaringClass()) { + // Same implementation. Since implementation_method may be a copy of a default + // method, we need to check the declaring class for equality. + return; + } + // Another implementation for interface_method. + invalidated_single_impl_methods.insert(interface_method); +} + void ClassHierarchyAnalysis::InitSingleImplementationFlag(Handle<mirror::Class> klass, ArtMethod* method, PointerSize pointer_size) { @@ -382,6 +432,7 @@ void ClassHierarchyAnalysis::InitSingleImplementationFlag(Handle<mirror::Class> // Rare case, but we do accept it (such as 800-smali/smali/b_26143249.smali). // Do not attempt to devirtualize it. method->SetHasSingleImplementation(false); + DCHECK(method->GetSingleImplementation(pointer_size) == nullptr); } else { // Abstract method starts with single-implementation flag set and null // implementation method. @@ -396,9 +447,15 @@ void ClassHierarchyAnalysis::InitSingleImplementationFlag(Handle<mirror::Class> } void ClassHierarchyAnalysis::UpdateAfterLoadingOf(Handle<mirror::Class> klass) { + PointerSize image_pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); if (klass->IsInterface()) { + for (ArtMethod& method : klass->GetDeclaredVirtualMethods(image_pointer_size)) { + DCHECK(method.IsAbstract() || method.IsDefault()); + InitSingleImplementationFlag(klass, &method, image_pointer_size); + } return; } + mirror::Class* super_class = klass->GetSuperClass(); if (super_class == nullptr) { return; @@ -408,7 +465,6 @@ void ClassHierarchyAnalysis::UpdateAfterLoadingOf(Handle<mirror::Class> klass) { // is invalidated by linking `klass`. std::unordered_set<ArtMethod*> invalidated_single_impl_methods; - PointerSize image_pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); // Do an entry-by-entry comparison of vtable contents with super's vtable. for (int32_t i = 0; i < super_class->GetVTableLength(); ++i) { ArtMethod* method = klass->GetVTableEntry(i, image_pointer_size); @@ -418,33 +474,59 @@ void ClassHierarchyAnalysis::UpdateAfterLoadingOf(Handle<mirror::Class> klass) { if (method->IsAbstract() && klass->IsInstantiable()) { // An instantiable class that inherits an abstract method is treated as // supplying an implementation that throws AbstractMethodError. - CheckSingleImplementationInfo(klass, - method, - method_in_super, - invalidated_single_impl_methods, - image_pointer_size); + CheckVirtualMethodSingleImplementationInfo(klass, + method, + method_in_super, + invalidated_single_impl_methods, + image_pointer_size); } continue; } InitSingleImplementationFlag(klass, method, image_pointer_size); - CheckSingleImplementationInfo(klass, - method, - method_in_super, - invalidated_single_impl_methods, - image_pointer_size); + CheckVirtualMethodSingleImplementationInfo(klass, + method, + method_in_super, + invalidated_single_impl_methods, + image_pointer_size); } - // For new virtual methods that don't override. for (int32_t i = super_class->GetVTableLength(); i < klass->GetVTableLength(); ++i) { ArtMethod* method = klass->GetVTableEntry(i, image_pointer_size); InitSingleImplementationFlag(klass, method, image_pointer_size); } - Runtime* const runtime = Runtime::Current(); + if (klass->IsInstantiable()) { + auto* iftable = klass->GetIfTable(); + const size_t ifcount = klass->GetIfTableCount(); + for (size_t i = 0; i < ifcount; ++i) { + mirror::Class* interface = iftable->GetInterface(i); + for (size_t j = 0, count = iftable->GetMethodArrayCount(i); j < count; ++j) { + ArtMethod* interface_method = interface->GetVirtualMethod(j, image_pointer_size); + mirror::PointerArray* method_array = iftable->GetMethodArray(i); + ArtMethod* implementation_method = + method_array->GetElementPtrSize<ArtMethod*>(j, image_pointer_size); + DCHECK(implementation_method != nullptr) << klass->PrettyClass(); + CheckInterfaceMethodSingleImplementationInfo(klass, + interface_method, + implementation_method, + invalidated_single_impl_methods, + image_pointer_size); + } + } + } + + InvalidateSingleImplementationMethods(invalidated_single_impl_methods); +} + +void ClassHierarchyAnalysis::InvalidateSingleImplementationMethods( + std::unordered_set<ArtMethod*>& invalidated_single_impl_methods) { if (!invalidated_single_impl_methods.empty()) { + Runtime* const runtime = Runtime::Current(); Thread *self = Thread::Current(); // Method headers for compiled code to be invalidated. std::unordered_set<OatQuickMethodHeader*> dependent_method_headers; + PointerSize image_pointer_size = + Runtime::Current()->GetClassLinker()->GetImagePointerSize(); { // We do this under cha_lock_. Committing code also grabs this lock to diff --git a/runtime/cha.h b/runtime/cha.h index a56a752d8c..99c49d2bca 100644 --- a/runtime/cha.h +++ b/runtime/cha.h @@ -117,11 +117,13 @@ class ClassHierarchyAnalysis { PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); + // Check/update single-implementation info when one virtual method + // overrides another. // `virtual_method` in `klass` overrides `method_in_super`. - // This will invalidate some assumptions on single-implementation. + // This may invalidate some assumptions on single-implementation. // Append methods that should have their single-implementation flag invalidated // to `invalidated_single_impl_methods`. - void CheckSingleImplementationInfo( + void CheckVirtualMethodSingleImplementationInfo( Handle<mirror::Class> klass, ArtMethod* virtual_method, ArtMethod* method_in_super, @@ -129,6 +131,23 @@ class ClassHierarchyAnalysis { PointerSize pointer_size) REQUIRES_SHARED(Locks::mutator_lock_); + // Check/update single-implementation info when one method + // implements an interface method. + // `implementation_method` in `klass` implements `interface_method`. + // Append `interface_method` to `invalidated_single_impl_methods` + // if `interface_method` gets a new implementation. + void CheckInterfaceMethodSingleImplementationInfo( + Handle<mirror::Class> klass, + ArtMethod* interface_method, + ArtMethod* implementation_method, + std::unordered_set<ArtMethod*>& invalidated_single_impl_methods, + PointerSize pointer_size) + REQUIRES_SHARED(Locks::mutator_lock_); + + void InvalidateSingleImplementationMethods( + std::unordered_set<ArtMethod*>& invalidated_single_impl_methods) + REQUIRES_SHARED(Locks::mutator_lock_); + // For all methods in vtable slot at `verify_index` of `verify_class` and its // superclasses, single-implementation status should be false, except if the // method is `excluded_method`. diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index 9f04e598eb..b421810113 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -618,7 +618,7 @@ struct ClassExtOffsets : public CheckOffsets<mirror::ClassExt> { ClassExtOffsets() : CheckOffsets<mirror::ClassExt>(false, "Ldalvik/system/ClassExt;") { addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_dex_caches_), "obsoleteDexCaches"); addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_methods_), "obsoleteMethods"); - addOffset(OFFSETOF_MEMBER(mirror::ClassExt, original_dex_file_bytes_), "originalDexFile"); + addOffset(OFFSETOF_MEMBER(mirror::ClassExt, original_dex_file_), "originalDexFile"); addOffset(OFFSETOF_MEMBER(mirror::ClassExt, verify_error_), "verifyError"); } }; diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc index 7136f101aa..d2ab41d409 100644 --- a/runtime/gc/collector/concurrent_copying.cc +++ b/runtime/gc/collector/concurrent_copying.cc @@ -2171,9 +2171,12 @@ mirror::Object* ConcurrentCopying::Copy(mirror::Object* from_ref) { fall_back_to_non_moving = true; to_ref = heap_->non_moving_space_->Alloc(Thread::Current(), obj_size, &non_moving_space_bytes_allocated, nullptr, &dummy); - CHECK(to_ref != nullptr) << "Fall-back non-moving space allocation failed for a " - << obj_size << " byte object in region type " - << region_space_->GetRegionType(from_ref); + if (UNLIKELY(to_ref == nullptr)) { + LOG(FATAL_WITHOUT_ABORT) << "Fall-back non-moving space allocation failed for a " + << obj_size << " byte object in region type " + << region_space_->GetRegionType(from_ref); + LOG(FATAL) << "Object address=" << from_ref << " type=" << from_ref->PrettyTypeOf(); + } bytes_allocated = non_moving_space_bytes_allocated; // Mark it in the mark bitmap. accounting::ContinuousSpaceBitmap* mark_bitmap = diff --git a/runtime/mirror/class_ext.cc b/runtime/mirror/class_ext.cc index 5dc3aca094..94e4b88f6c 100644 --- a/runtime/mirror/class_ext.cc +++ b/runtime/mirror/class_ext.cc @@ -117,9 +117,9 @@ void ClassExt::SetVerifyError(ObjPtr<Object> err) { } } -void ClassExt::SetOriginalDexFileBytes(ObjPtr<ByteArray> bytes) { +void ClassExt::SetOriginalDexFile(ObjPtr<Object> bytes) { DCHECK(!Runtime::Current()->IsActiveTransaction()); - SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_bytes_), bytes); + SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_), bytes); } void ClassExt::SetClass(ObjPtr<Class> dalvik_system_ClassExt) { diff --git a/runtime/mirror/class_ext.h b/runtime/mirror/class_ext.h index fac955a45e..708665d46b 100644 --- a/runtime/mirror/class_ext.h +++ b/runtime/mirror/class_ext.h @@ -60,11 +60,11 @@ class MANAGED ClassExt : public Object { OFFSET_OF_OBJECT_MEMBER(ClassExt, obsolete_methods_)); } - ByteArray* GetOriginalDexFileBytes() REQUIRES_SHARED(Locks::mutator_lock_) { - return GetFieldObject<ByteArray>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_bytes_)); + Object* GetOriginalDexFile() REQUIRES_SHARED(Locks::mutator_lock_) { + return GetFieldObject<Object>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_)); } - void SetOriginalDexFileBytes(ObjPtr<ByteArray> bytes) REQUIRES_SHARED(Locks::mutator_lock_); + void SetOriginalDexFile(ObjPtr<Object> bytes) REQUIRES_SHARED(Locks::mutator_lock_); void SetObsoleteArrays(ObjPtr<PointerArray> methods, ObjPtr<ObjectArray<DexCache>> dex_caches) REQUIRES_SHARED(Locks::mutator_lock_); @@ -89,7 +89,7 @@ class MANAGED ClassExt : public Object { HeapReference<PointerArray> obsolete_methods_; - HeapReference<ByteArray> original_dex_file_bytes_; + HeapReference<Object> original_dex_file_; // The saved verification error of this class. HeapReference<Object> verify_error_; diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc index 2d1b25ed26..38fd1d4af3 100644 --- a/runtime/openjdkjvmti/ti_class.cc +++ b/runtime/openjdkjvmti/ti_class.cc @@ -259,7 +259,7 @@ struct ClassCallback : public art::ClassLoadCallback { } // Actually set the ClassExt's original bytes once we have actually succeeded. - ext->SetOriginalDexFileBytes(arr.Get()); + ext->SetOriginalDexFile(arr.Get()); // Set the return values *final_class_def = &dex_file->GetClassDef(0); *final_dex_file = dex_file.release(); diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 9c1d6ef0a5..7faddfb0f9 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -469,7 +469,7 @@ void Redefiner::RecordFailure(jvmtiError result, result_ = result; } -art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFileBytes() { +art::mirror::Object* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFile() { // If we have been specifically given a new set of bytes use that if (original_dex_file_.size() != 0) { return art::mirror::ByteArray::AllocateAndFill( @@ -481,24 +481,21 @@ art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFi // See if we already have one set. art::ObjPtr<art::mirror::ClassExt> ext(GetMirrorClass()->GetExtData()); if (!ext.IsNull()) { - art::ObjPtr<art::mirror::ByteArray> old_original_bytes(ext->GetOriginalDexFileBytes()); - if (!old_original_bytes.IsNull()) { + art::ObjPtr<art::mirror::Object> old_original_dex_file(ext->GetOriginalDexFile()); + if (!old_original_dex_file.IsNull()) { // We do. Use it. - return old_original_bytes.Ptr(); + return old_original_dex_file.Ptr(); } } - // Copy the current dex_file - const art::DexFile& current_dex_file = GetMirrorClass()->GetDexFile(); + // return the current dex_cache which has the dex file in it. + art::ObjPtr<art::mirror::DexCache> current_dex_cache(GetMirrorClass()->GetDexCache()); // TODO Handle this or make it so it cannot happen. - if (current_dex_file.NumClassDefs() != 1) { + if (current_dex_cache->GetDexFile()->NumClassDefs() != 1) { LOG(WARNING) << "Current dex file has more than one class in it. Calling RetransformClasses " << "on this class might fail if no transformations are applied to it!"; } - return art::mirror::ByteArray::AllocateAndFill( - driver_->self_, - reinterpret_cast<const signed char*>(current_dex_file.Begin()), - current_dex_file.Size()); + return current_dex_cache.Ptr(); } struct CallbackCtx { @@ -847,9 +844,9 @@ class RedefinitionDataHolder { return art::down_cast<art::mirror::Class*>(GetSlot(klass_index, kSlotMirrorClass)); } - art::mirror::ByteArray* GetOriginalDexFileBytes(jint klass_index) const + art::mirror::Object* GetOriginalDexFile(jint klass_index) const REQUIRES_SHARED(art::Locks::mutator_lock_) { - return art::down_cast<art::mirror::ByteArray*>(GetSlot(klass_index, kSlotOrigDexFile)); + return art::down_cast<art::mirror::Object*>(GetSlot(klass_index, kSlotOrigDexFile)); } void SetSourceClassLoader(jint klass_index, art::mirror::ClassLoader* loader) @@ -872,7 +869,7 @@ class RedefinitionDataHolder { REQUIRES_SHARED(art::Locks::mutator_lock_) { SetSlot(klass_index, kSlotMirrorClass, klass); } - void SetOriginalDexFileBytes(jint klass_index, art::mirror::ByteArray* bytes) + void SetOriginalDexFile(jint klass_index, art::mirror::Object* bytes) REQUIRES_SHARED(art::Locks::mutator_lock_) { SetSlot(klass_index, kSlotOrigDexFile, bytes); } @@ -985,9 +982,9 @@ class RedefinitionDataIter { art::mirror::Class* GetMirrorClass() const REQUIRES_SHARED(art::Locks::mutator_lock_) { return holder_.GetMirrorClass(idx_); } - art::mirror::ByteArray* GetOriginalDexFileBytes() const + art::mirror::Object* GetOriginalDexFile() const REQUIRES_SHARED(art::Locks::mutator_lock_) { - return holder_.GetOriginalDexFileBytes(idx_); + return holder_.GetOriginalDexFile(idx_); } int32_t GetIndex() const { return idx_; @@ -1010,9 +1007,9 @@ class RedefinitionDataIter { void SetMirrorClass(art::mirror::Class* klass) REQUIRES_SHARED(art::Locks::mutator_lock_) { holder_.SetMirrorClass(idx_, klass); } - void SetOriginalDexFileBytes(art::mirror::ByteArray* bytes) + void SetOriginalDexFile(art::mirror::Object* bytes) REQUIRES_SHARED(art::Locks::mutator_lock_) { - holder_.SetOriginalDexFileBytes(idx_, bytes); + holder_.SetOriginalDexFile(idx_, bytes); } private: @@ -1138,8 +1135,8 @@ bool Redefiner::ClassRedefinition::FinishRemainingAllocations( } // We won't always need to set this field. - cur_data->SetOriginalDexFileBytes(AllocateOrGetOriginalDexFileBytes()); - if (cur_data->GetOriginalDexFileBytes() == nullptr) { + cur_data->SetOriginalDexFile(AllocateOrGetOriginalDexFile()); + if (cur_data->GetOriginalDexFile() == nullptr) { driver_->self_->AssertPendingOOMException(); driver_->self_->ClearException(); RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate array for original dex file"); @@ -1285,7 +1282,7 @@ jvmtiError Redefiner::Run() { art::mirror::Class* klass = data.GetMirrorClass(); // TODO Rewrite so we don't do a stack walk for each and every class. redef.FindAndAllocateObsoleteMethods(klass); - redef.UpdateClass(klass, data.GetNewDexCache(), data.GetOriginalDexFileBytes()); + redef.UpdateClass(klass, data.GetNewDexCache(), data.GetOriginalDexFile()); } // TODO We should check for if any of the redefined methods are intrinsic methods here and, if any // are, force a full-world deoptimization before finishing redefinition. If we don't do this then @@ -1365,7 +1362,7 @@ void Redefiner::ClassRedefinition::UpdateFields(art::ObjPtr<art::mirror::Class> void Redefiner::ClassRedefinition::UpdateClass( art::ObjPtr<art::mirror::Class> mclass, art::ObjPtr<art::mirror::DexCache> new_dex_cache, - art::ObjPtr<art::mirror::ByteArray> original_dex_file) { + art::ObjPtr<art::mirror::Object> original_dex_file) { DCHECK_EQ(dex_file_->NumClassDefs(), 1u); const art::DexFile::ClassDef& class_def = dex_file_->GetClassDef(0); UpdateMethods(mclass, new_dex_cache, class_def); @@ -1379,7 +1376,7 @@ void Redefiner::ClassRedefinition::UpdateClass( mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_.c_str()))); art::ObjPtr<art::mirror::ClassExt> ext(mclass->GetExtData()); CHECK(!ext.IsNull()); - ext->SetOriginalDexFileBytes(original_dex_file); + ext->SetOriginalDexFile(original_dex_file); } // This function does all (java) allocations we need to do for the Class being redefined. diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index 4313a9476e..6c09d46e89 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -137,7 +137,7 @@ class Redefiner { REQUIRES_SHARED(art::Locks::mutator_lock_); // This may return nullptr with a OOME pending if allocation fails. - art::mirror::ByteArray* AllocateOrGetOriginalDexFileBytes() + art::mirror::Object* AllocateOrGetOriginalDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_); void RecordFailure(jvmtiError e, const std::string& err) { @@ -196,7 +196,7 @@ class Redefiner { void UpdateClass(art::ObjPtr<art::mirror::Class> mclass, art::ObjPtr<art::mirror::DexCache> new_dex_cache, - art::ObjPtr<art::mirror::ByteArray> original_dex_file) + art::ObjPtr<art::mirror::Object> original_dex_file) REQUIRES(art::Locks::mutator_lock_); void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_); diff --git a/runtime/openjdkjvmti/transform.cc b/runtime/openjdkjvmti/transform.cc index bd52cbb7f9..06aecbaee3 100644 --- a/runtime/openjdkjvmti/transform.cc +++ b/runtime/openjdkjvmti/transform.cc @@ -150,16 +150,27 @@ jvmtiError Transformer::GetDexDataForRetransformation(ArtJvmTiEnv* env, art::Handle<art::mirror::Class> klass, /*out*/jint* dex_data_len, /*out*/unsigned char** dex_data) { - art::StackHandleScope<2> hs(art::Thread::Current()); + art::StackHandleScope<3> hs(art::Thread::Current()); art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->GetExtData())); if (!ext.IsNull()) { - art::Handle<art::mirror::ByteArray> orig_dex(hs.NewHandle(ext->GetOriginalDexFileBytes())); + art::Handle<art::mirror::Object> orig_dex(hs.NewHandle(ext->GetOriginalDexFile())); if (!orig_dex.IsNull()) { - *dex_data_len = static_cast<jint>(orig_dex->GetLength()); - return CopyDataIntoJvmtiBuffer(env, - reinterpret_cast<const unsigned char*>(orig_dex->GetData()), - *dex_data_len, - /*out*/dex_data); + if (orig_dex->IsArrayInstance()) { + DCHECK(orig_dex->GetClass()->GetComponentType()->IsPrimitiveByte()); + art::Handle<art::mirror::ByteArray> orig_dex_bytes( + hs.NewHandle(art::down_cast<art::mirror::ByteArray*>(orig_dex->AsArray()))); + *dex_data_len = static_cast<jint>(orig_dex_bytes->GetLength()); + return CopyDataIntoJvmtiBuffer( + env, + reinterpret_cast<const unsigned char*>(orig_dex_bytes->GetData()), + *dex_data_len, + /*out*/dex_data); + } else { + DCHECK(orig_dex->IsDexCache()); + const art::DexFile* dex_file = orig_dex->AsDexCache()->GetDexFile(); + *dex_data_len = static_cast<jint>(dex_file->Size()); + return CopyDataIntoJvmtiBuffer(env, dex_file->Begin(), dex_file->Size(), /*out*/dex_data); + } } } // TODO De-quicken the dex file before passing it to the agents. diff --git a/test/080-oom-throw/run b/test/080-oom-throw/run new file mode 100644 index 0000000000..eb473782a5 --- /dev/null +++ b/test/080-oom-throw/run @@ -0,0 +1,17 @@ +#!/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. + +exec ${RUN} $@ --runtime-option -Xmx16m diff --git a/test/080-oom-throw/src/Main.java b/test/080-oom-throw/src/Main.java index a6c18b75fc..3d5d0629f3 100644 --- a/test/080-oom-throw/src/Main.java +++ b/test/080-oom-throw/src/Main.java @@ -114,13 +114,13 @@ public class Main { static Object[] holder; public static void blowup() throws Exception { - int size = 32 * 1024 * 1024; + int size = 2 * 1024 * 1024; for (int i = 0; i < holder.length; ) { try { holder[i] = new char[size]; i++; } catch (OutOfMemoryError oome) { - size = size / 2; + size = size / 16; if (size == 0) { break; } diff --git a/test/527-checker-array-access-split/src/Main.java b/test/527-checker-array-access-split/src/Main.java index 3de900a3a9..a5caa7bce0 100644 --- a/test/527-checker-array-access-split/src/Main.java +++ b/test/527-checker-array-access-split/src/Main.java @@ -327,17 +327,17 @@ public class Main { // check. /// CHECK-START-ARM64: int Main.canMergeAfterBCE1() instruction_simplifier_arm64 (before) - /// CHECK: <<Const1:i\d+>> IntConstant 1 + /// CHECK: <<Const7:i\d+>> IntConstant 7 /// CHECK: <<Array:l\d+>> NewArray /// CHECK: <<Index:i\d+>> Phi /// CHECK: If // -------------- Loop /// CHECK: <<ArrayGet:i\d+>> ArrayGet [<<Array>>,<<Index>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGet>>,<<Const1>>] - /// CHECK: ArraySet [<<Array>>,<<Index>>,<<Add>>] + /// CHECK: <<Div:i\d+>> Div [<<ArrayGet>>,<<Const7>>] + /// CHECK: ArraySet [<<Array>>,<<Index>>,<<Div>>] /// CHECK-START-ARM64: int Main.canMergeAfterBCE1() instruction_simplifier_arm64 (after) - /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Const7:i\d+>> IntConstant 7 /// CHECK-DAG: <<DataOffset:i\d+>> IntConstant 12 /// CHECK: <<Array:l\d+>> NewArray /// CHECK: <<Index:i\d+>> Phi @@ -345,12 +345,12 @@ public class Main { // -------------- Loop /// CHECK: <<Address1:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] /// CHECK-NEXT: <<ArrayGet:i\d+>> ArrayGet [<<Address1>>,<<Index>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGet>>,<<Const1>>] + /// CHECK: <<Div:i\d+>> Div [<<ArrayGet>>,<<Const7>>] /// CHECK: <<Address2:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] - /// CHECK-NEXT: ArraySet [<<Address2>>,<<Index>>,<<Add>>] + /// CHECK-NEXT: ArraySet [<<Address2>>,<<Index>>,<<Div>>] /// CHECK-START-ARM64: int Main.canMergeAfterBCE1() GVN$after_arch (after) - /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Const7:i\d+>> IntConstant 7 /// CHECK-DAG: <<DataOffset:i\d+>> IntConstant 12 /// CHECK: <<Array:l\d+>> NewArray /// CHECK: <<Index:i\d+>> Phi @@ -358,23 +358,23 @@ public class Main { // -------------- Loop /// CHECK: <<Address:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] /// CHECK: <<ArrayGet:i\d+>> ArrayGet [<<Address>>,<<Index>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGet>>,<<Const1>>] + /// CHECK: <<Div:i\d+>> Div [<<ArrayGet>>,<<Const7>>] /// CHECK-NOT: IntermediateAddress - /// CHECK: ArraySet [<<Address>>,<<Index>>,<<Add>>] + /// CHECK: ArraySet [<<Address>>,<<Index>>,<<Div>>] /// CHECK-START-ARM: int Main.canMergeAfterBCE1() instruction_simplifier_arm (before) - /// CHECK: <<Const1:i\d+>> IntConstant 1 + /// CHECK: <<Const7:i\d+>> IntConstant 7 /// CHECK: <<Array:l\d+>> NewArray /// CHECK: <<Index:i\d+>> Phi /// CHECK: If // -------------- Loop /// CHECK: <<ArrayGet:i\d+>> ArrayGet [<<Array>>,<<Index>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGet>>,<<Const1>>] - /// CHECK: ArraySet [<<Array>>,<<Index>>,<<Add>>] + /// CHECK: <<Div:i\d+>> Div [<<ArrayGet>>,<<Const7>>] + /// CHECK: ArraySet [<<Array>>,<<Index>>,<<Div>>] /// CHECK-START-ARM: int Main.canMergeAfterBCE1() instruction_simplifier_arm (after) - /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Const7:i\d+>> IntConstant 7 /// CHECK-DAG: <<DataOffset:i\d+>> IntConstant 12 /// CHECK: <<Array:l\d+>> NewArray /// CHECK: <<Index:i\d+>> Phi @@ -382,12 +382,12 @@ public class Main { // -------------- Loop /// CHECK: <<Address1:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] /// CHECK-NEXT: <<ArrayGet:i\d+>> ArrayGet [<<Address1>>,<<Index>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGet>>,<<Const1>>] + /// CHECK: <<Div:i\d+>> Div [<<ArrayGet>>,<<Const7>>] /// CHECK: <<Address2:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] - /// CHECK-NEXT: ArraySet [<<Address2>>,<<Index>>,<<Add>>] + /// CHECK-NEXT: ArraySet [<<Address2>>,<<Index>>,<<Div>>] /// CHECK-START-ARM: int Main.canMergeAfterBCE1() GVN$after_arch (after) - /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 + /// CHECK-DAG: <<Const7:i\d+>> IntConstant 7 /// CHECK-DAG: <<DataOffset:i\d+>> IntConstant 12 /// CHECK: <<Array:l\d+>> NewArray /// CHECK: <<Index:i\d+>> Phi @@ -395,14 +395,14 @@ public class Main { // -------------- Loop /// CHECK: <<Address:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] /// CHECK: <<ArrayGet:i\d+>> ArrayGet [<<Address>>,<<Index>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGet>>,<<Const1>>] + /// CHECK: <<Div:i\d+>> Div [<<ArrayGet>>,<<Const7>>] /// CHECK-NOT: IntermediateAddress - /// CHECK: ArraySet [<<Address>>,<<Index>>,<<Add>>] + /// CHECK: ArraySet [<<Address>>,<<Index>>,<<Div>>] public static int canMergeAfterBCE1() { - int[] array = {0, 1, 2, 3}; + int[] array = {0, 7, 14, 21}; for (int i = 0; i < array.length; i++) { - array[i] = array[i] + 1; + array[i] = array[i] / 7; } return array[array.length - 1]; } @@ -421,8 +421,8 @@ public class Main { /// CHECK-DAG: <<Index1:i\d+>> Add [<<Index>>,<<Const1>>] /// CHECK-DAG: <<ArrayGetI:i\d+>> ArrayGet [<<Array>>,<<Index>>] /// CHECK-DAG: <<ArrayGetI1:i\d+>> ArrayGet [<<Array>>,<<Index1>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGetI>>,<<ArrayGetI1>>] - /// CHECK: ArraySet [<<Array>>,<<Index1>>,<<Add>>] + /// CHECK: <<Shl:i\d+>> Shl [<<ArrayGetI>>,<<ArrayGetI1>>] + /// CHECK: ArraySet [<<Array>>,<<Index1>>,<<Shl>>] // Note that we do not care that the `DataOffset` is `12`. But if we do not // specify it and any other `IntConstant` appears before that instruction, @@ -441,9 +441,9 @@ public class Main { /// CHECK-DAG: <<ArrayGetI:i\d+>> ArrayGet [<<Address1>>,<<Index>>] /// CHECK-DAG: <<Address2:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] /// CHECK-DAG: <<ArrayGetI1:i\d+>> ArrayGet [<<Address2>>,<<Index1>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGetI>>,<<ArrayGetI1>>] + /// CHECK: <<Shl:i\d+>> Shl [<<ArrayGetI>>,<<ArrayGetI1>>] /// CHECK: <<Address3:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] - /// CHECK: ArraySet [<<Address3>>,<<Index1>>,<<Add>>] + /// CHECK: ArraySet [<<Address3>>,<<Index1>>,<<Shl>>] /// CHECK-START-ARM64: int Main.canMergeAfterBCE2() GVN$after_arch (after) /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 @@ -456,8 +456,8 @@ public class Main { /// CHECK-DAG: <<Address:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] /// CHECK-DAG: <<ArrayGetI:i\d+>> ArrayGet [<<Address>>,<<Index>>] /// CHECK-DAG: <<ArrayGetI1:i\d+>> ArrayGet [<<Address>>,<<Index1>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGetI>>,<<ArrayGetI1>>] - /// CHECK: ArraySet [<<Address>>,<<Index1>>,<<Add>>] + /// CHECK: <<Shl:i\d+>> Shl [<<ArrayGetI>>,<<ArrayGetI1>>] + /// CHECK: ArraySet [<<Address>>,<<Index1>>,<<Shl>>] // There should be only one intermediate address computation in the loop. @@ -475,8 +475,8 @@ public class Main { /// CHECK-DAG: <<Index1:i\d+>> Add [<<Index>>,<<Const1>>] /// CHECK-DAG: <<ArrayGetI:i\d+>> ArrayGet [<<Array>>,<<Index>>] /// CHECK-DAG: <<ArrayGetI1:i\d+>> ArrayGet [<<Array>>,<<Index1>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGetI>>,<<ArrayGetI1>>] - /// CHECK: ArraySet [<<Array>>,<<Index1>>,<<Add>>] + /// CHECK: <<Shl:i\d+>> Shl [<<ArrayGetI>>,<<ArrayGetI1>>] + /// CHECK: ArraySet [<<Array>>,<<Index1>>,<<Shl>>] /// CHECK-START-ARM: int Main.canMergeAfterBCE2() instruction_simplifier_arm (after) /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 @@ -490,9 +490,9 @@ public class Main { /// CHECK-DAG: <<ArrayGetI:i\d+>> ArrayGet [<<Address1>>,<<Index>>] /// CHECK-DAG: <<Address2:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] /// CHECK-DAG: <<ArrayGetI1:i\d+>> ArrayGet [<<Address2>>,<<Index1>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGetI>>,<<ArrayGetI1>>] + /// CHECK: <<Shl:i\d+>> Shl [<<ArrayGetI>>,<<ArrayGetI1>>] /// CHECK: <<Address3:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] - /// CHECK: ArraySet [<<Address3>>,<<Index1>>,<<Add>>] + /// CHECK: ArraySet [<<Address3>>,<<Index1>>,<<Shl>>] /// CHECK-START-ARM: int Main.canMergeAfterBCE2() GVN$after_arch (after) /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 @@ -505,17 +505,17 @@ public class Main { /// CHECK-DAG: <<Address:i\d+>> IntermediateAddress [<<Array>>,<<DataOffset>>] /// CHECK-DAG: <<ArrayGetI:i\d+>> ArrayGet [<<Address>>,<<Index>>] /// CHECK-DAG: <<ArrayGetI1:i\d+>> ArrayGet [<<Address>>,<<Index1>>] - /// CHECK: <<Add:i\d+>> Add [<<ArrayGetI>>,<<ArrayGetI1>>] - /// CHECK: ArraySet [<<Address>>,<<Index1>>,<<Add>>] + /// CHECK: <<Shl:i\d+>> Shl [<<ArrayGetI>>,<<ArrayGetI1>>] + /// CHECK: ArraySet [<<Address>>,<<Index1>>,<<Shl>>] /// CHECK-START-ARM: int Main.canMergeAfterBCE2() GVN$after_arch (after) /// CHECK: IntermediateAddress /// CHECK-NOT: IntermediateAddress public static int canMergeAfterBCE2() { - int[] array = {0, 1, 2, 3}; + int[] array = {64, 8, 4, 2 }; for (int i = 0; i < array.length - 1; i++) { - array[i + 1] = array[i] + array[i + 1]; + array[i + 1] = array[i] << array[i + 1]; } return array[array.length - 1]; } @@ -571,8 +571,8 @@ public class Main { accrossGC(array, 0); assertIntEquals(125, array[0]); - assertIntEquals(4, canMergeAfterBCE1()); - assertIntEquals(6, canMergeAfterBCE2()); + assertIntEquals(3, canMergeAfterBCE1()); + assertIntEquals(1048576, canMergeAfterBCE2()); assertIntEquals(18, checkLongFloatDouble()); } diff --git a/test/616-cha-abstract/src/Main.java b/test/616-cha-abstract/src/Main.java index e1d7db170d..b33f575dec 100644 --- a/test/616-cha-abstract/src/Main.java +++ b/test/616-cha-abstract/src/Main.java @@ -39,8 +39,8 @@ class Main2 extends Main1 { } public class Main { - static Main1 sMain1; - static Main1 sMain2; + static Base sMain1; + static Base sMain2; static boolean sIsOptimizing = true; static boolean sHasJIT = true; diff --git a/test/616-cha-interface-default/expected.txt b/test/616-cha-interface-default/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/616-cha-interface-default/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/616-cha-interface-default/info.txt b/test/616-cha-interface-default/info.txt new file mode 100644 index 0000000000..11baa1f0f2 --- /dev/null +++ b/test/616-cha-interface-default/info.txt @@ -0,0 +1,2 @@ +Test for Class Hierarchy Analysis (CHA) on interface method. +Test it under multidex configuration to check cross-dex inlining. diff --git a/test/616-cha-interface-default/multidex.jpp b/test/616-cha-interface-default/multidex.jpp new file mode 100644 index 0000000000..b0d200ea38 --- /dev/null +++ b/test/616-cha-interface-default/multidex.jpp @@ -0,0 +1,3 @@ +Main: + @@com.android.jack.annotations.ForceInMainDex + class Main diff --git a/test/616-cha-interface-default/run b/test/616-cha-interface-default/run new file mode 100644 index 0000000000..d8b4f0d26c --- /dev/null +++ b/test/616-cha-interface-default/run @@ -0,0 +1,18 @@ +#!/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. + +# Run without an app image to prevent the classes to be loaded at startup. +exec ${RUN} "${@}" --no-app-image diff --git a/test/616-cha-interface-default/src-multidex/Base.java b/test/616-cha-interface-default/src-multidex/Base.java new file mode 100644 index 0000000000..2cbcb500c4 --- /dev/null +++ b/test/616-cha-interface-default/src-multidex/Base.java @@ -0,0 +1,41 @@ +/* + * 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. + */ + +interface Base { + default public int foo(int i) { + if (i != 1) { + return -2; + } + return i + 10; + } + + // Test default method that's not inlined. + default public int $noinline$bar() { + System.out.print(""); + System.out.print(""); + System.out.print(""); + System.out.print(""); + System.out.print(""); + System.out.print(""); + System.out.print(""); + System.out.print(""); + return -1; + } + + default void printError(String msg) { + System.out.println(msg); + } +} diff --git a/test/616-cha-interface-default/src/Main.java b/test/616-cha-interface-default/src/Main.java new file mode 100644 index 0000000000..951607d2cf --- /dev/null +++ b/test/616-cha-interface-default/src/Main.java @@ -0,0 +1,176 @@ +/* + * 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 Main1 implements Base { +} + +class Main2 extends Main1 { + public void foobar() {} +} + +class Main3 implements Base { + public int foo(int i) { + if (i != 3) { + printError("error3"); + } + return -(i + 10); + } +} + +public class Main { + static Base sMain1; + static Base sMain2; + static Base sMain3; + + static boolean sIsOptimizing = true; + static boolean sHasJIT = true; + static volatile boolean sOtherThreadStarted; + + private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) { + if (hasSingleImplementation(clazz, method_name) != b) { + System.out.println(clazz + "." + method_name + + " doesn't have single implementation value of " + b); + } + } + + static int getValue(Class<?> cls) { + if (cls == Main1.class || cls == Main2.class) { + return 1; + } + return 3; + } + + // sMain1.foo()/sMain2.foo() will be always be Base.foo() before Main3 is loaded/linked. + // So sMain1.foo() can be devirtualized to Base.foo() and be inlined. + // After Dummy.createMain3() which links in Main3, live testImplement() on stack + // should be deoptimized. + static void testImplement(boolean createMain3, boolean wait, boolean setHasJIT) { + if (setHasJIT) { + if (isInterpreted()) { + sHasJIT = false; + } + return; + } + + if (createMain3 && (sIsOptimizing || sHasJIT)) { + assertIsManaged(); + } + + if (sMain1.foo(getValue(sMain1.getClass())) != 11) { + System.out.println("11 expected."); + } + if (sMain1.$noinline$bar() != -1) { + System.out.println("-1 expected."); + } + if (sMain2.foo(getValue(sMain2.getClass())) != 11) { + System.out.println("11 expected."); + } + + if (createMain3) { + // Wait for the other thread to start. + while (!sOtherThreadStarted); + // Create an Main2 instance and assign it to sMain2. + // sMain1 is kept the same. + sMain3 = Dummy.createMain3(); + // Wake up the other thread. + synchronized(Main.class) { + Main.class.notify(); + } + } else if (wait) { + // This is the other thread. + synchronized(Main.class) { + sOtherThreadStarted = true; + // Wait for Main2 to be linked and deoptimization is triggered. + try { + Main.class.wait(); + } catch (Exception e) { + } + } + } + + // There should be a deoptimization here right after Main3 is linked by + // calling Dummy.createMain3(), even though sMain1 didn't change. + // The behavior here would be different if inline-cache is used, which + // doesn't deoptimize since sMain1 still hits the type cache. + if (sMain1.foo(getValue(sMain1.getClass())) != 11) { + System.out.println("11 expected."); + } + if ((createMain3 || wait) && sHasJIT && !sIsOptimizing) { + // This method should be deoptimized right after Main3 is created. + assertIsInterpreted(); + } + + if (sMain3 != null) { + if (sMain3.foo(getValue(sMain3.getClass())) != -13) { + System.out.println("-13 expected."); + } + } + } + + // Test scenarios under which CHA-based devirtualization happens, + // and class loading that implements a method can invalidate compiled code. + public static void main(String[] args) { + System.loadLibrary(args[0]); + + if (isInterpreted()) { + sIsOptimizing = false; + } + + // sMain1 is an instance of Main1. + // sMain2 is an instance of Main2. + // Neither Main1 nor Main2 override default method Base.foo(). + // Main3 hasn't bee loaded yet. + sMain1 = new Main1(); + sMain2 = new Main2(); + + ensureJitCompiled(Main.class, "testImplement"); + testImplement(false, false, true); + + if (sHasJIT && !sIsOptimizing) { + assertSingleImplementation(Base.class, "foo", true); + assertSingleImplementation(Main1.class, "foo", true); + } else { + // Main3 is verified ahead-of-time so it's linked in already. + } + + // Create another thread that also calls sMain1.foo(). + // Try to test suspend and deopt another thread. + new Thread() { + public void run() { + testImplement(false, true, false); + } + }.start(); + + // This will create Main3 instance in the middle of testImplement(). + testImplement(true, false, false); + assertSingleImplementation(Base.class, "foo", false); + assertSingleImplementation(Main1.class, "foo", true); + assertSingleImplementation(sMain3.getClass(), "foo", true); + } + + private static native void ensureJitCompiled(Class<?> itf, String method_name); + private static native void assertIsInterpreted(); + private static native void assertIsManaged(); + private static native boolean isInterpreted(); + private static native boolean hasSingleImplementation(Class<?> clazz, String method_name); +} + +// Put createMain3() in another class to avoid class loading due to verifier. +class Dummy { + static Base createMain3() { + return new Main3(); + } +} diff --git a/test/616-cha-interface/expected.txt b/test/616-cha-interface/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/616-cha-interface/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/616-cha-interface/info.txt b/test/616-cha-interface/info.txt new file mode 100644 index 0000000000..1fd330afd4 --- /dev/null +++ b/test/616-cha-interface/info.txt @@ -0,0 +1 @@ +Test for Class Hierarchy Analysis (CHA) on interface method. diff --git a/test/616-cha-interface/run b/test/616-cha-interface/run new file mode 100644 index 0000000000..d8b4f0d26c --- /dev/null +++ b/test/616-cha-interface/run @@ -0,0 +1,18 @@ +#!/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. + +# Run without an app image to prevent the classes to be loaded at startup. +exec ${RUN} "${@}" --no-app-image diff --git a/test/616-cha-interface/src/Main.java b/test/616-cha-interface/src/Main.java new file mode 100644 index 0000000000..3c9349663d --- /dev/null +++ b/test/616-cha-interface/src/Main.java @@ -0,0 +1,173 @@ +/* + * 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. + */ + +interface Base { + void foo(int i); + void $noinline$bar(); +} + +class Main1 implements Base { + public void foo(int i) { + if (i != 1) { + printError("error1"); + } + } + + // Test rewriting invoke-interface into invoke-virtual when inlining fails. + public void $noinline$bar() { + System.out.print(""); + System.out.print(""); + System.out.print(""); + System.out.print(""); + System.out.print(""); + System.out.print(""); + System.out.print(""); + System.out.print(""); + } + + void printError(String msg) { + System.out.println(msg); + } +} + +class Main2 extends Main1 { + public void foo(int i) { + if (i != 2) { + printError("error2"); + } + } +} + +public class Main { + static Base sMain1; + static Base sMain2; + + static boolean sIsOptimizing = true; + static boolean sHasJIT = true; + static volatile boolean sOtherThreadStarted; + + private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) { + if (hasSingleImplementation(clazz, method_name) != b) { + System.out.println(clazz + "." + method_name + + " doesn't have single implementation value of " + b); + } + } + + // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked. + // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined. + // After Dummy.createMain2() which links in Main2, live testImplement() on stack + // should be deoptimized. + static void testImplement(boolean createMain2, boolean wait, boolean setHasJIT) { + if (setHasJIT) { + if (isInterpreted()) { + sHasJIT = false; + } + return; + } + + if (createMain2 && (sIsOptimizing || sHasJIT)) { + assertIsManaged(); + } + + sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); + sMain1.$noinline$bar(); + + if (createMain2) { + // Wait for the other thread to start. + while (!sOtherThreadStarted); + // Create an Main2 instance and assign it to sMain2. + // sMain1 is kept the same. + sMain2 = Dummy.createMain2(); + // Wake up the other thread. + synchronized(Main.class) { + Main.class.notify(); + } + } else if (wait) { + // This is the other thread. + synchronized(Main.class) { + sOtherThreadStarted = true; + // Wait for Main2 to be linked and deoptimization is triggered. + try { + Main.class.wait(); + } catch (Exception e) { + } + } + } + + // There should be a deoptimization here right after Main2 is linked by + // calling Dummy.createMain2(), even though sMain1 didn't change. + // The behavior here would be different if inline-cache is used, which + // doesn't deoptimize since sMain1 still hits the type cache. + sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); + if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) { + // This method should be deoptimized right after Main2 is created. + assertIsInterpreted(); + } + + if (sMain2 != null) { + sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2); + } + } + + // Test scenarios under which CHA-based devirtualization happens, + // and class loading that overrides a method can invalidate compiled code. + public static void main(String[] args) { + System.loadLibrary(args[0]); + + if (isInterpreted()) { + sIsOptimizing = false; + } + + // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet. + sMain1 = new Main1(); + + ensureJitCompiled(Main.class, "testImplement"); + testImplement(false, false, true); + + if (sHasJIT && !sIsOptimizing) { + assertSingleImplementation(Base.class, "foo", true); + assertSingleImplementation(Main1.class, "foo", true); + } else { + // Main2 is verified ahead-of-time so it's linked in already. + } + + // Create another thread that also calls sMain1.foo(). + // Try to test suspend and deopt another thread. + new Thread() { + public void run() { + testImplement(false, true, false); + } + }.start(); + + // This will create Main2 instance in the middle of testImplement(). + testImplement(true, false, false); + assertSingleImplementation(Base.class, "foo", false); + assertSingleImplementation(Main1.class, "foo", false); + } + + private static native void ensureJitCompiled(Class<?> itf, String method_name); + private static native void assertIsInterpreted(); + private static native void assertIsManaged(); + private static native boolean isInterpreted(); + private static native boolean hasSingleImplementation(Class<?> clazz, String method_name); +} + +// Put createMain2() in another class to avoid class loading due to verifier. +class Dummy { + static Main1 createMain2() { + return new Main2(); + } +} diff --git a/test/616-cha-miranda/expected.txt b/test/616-cha-miranda/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/616-cha-miranda/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/616-cha-miranda/info.txt b/test/616-cha-miranda/info.txt new file mode 100644 index 0000000000..c46f33f613 --- /dev/null +++ b/test/616-cha-miranda/info.txt @@ -0,0 +1 @@ +Test for Class Hierarchy Analysis (CHA) on miranda method. diff --git a/test/616-cha-miranda/run b/test/616-cha-miranda/run new file mode 100644 index 0000000000..d8b4f0d26c --- /dev/null +++ b/test/616-cha-miranda/run @@ -0,0 +1,18 @@ +#!/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. + +# Run without an app image to prevent the classes to be loaded at startup. +exec ${RUN} "${@}" --no-app-image diff --git a/test/616-cha-miranda/src/Main.java b/test/616-cha-miranda/src/Main.java new file mode 100644 index 0000000000..e548482eb3 --- /dev/null +++ b/test/616-cha-miranda/src/Main.java @@ -0,0 +1,163 @@ +/* + * 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. + */ + +interface Iface { + public void foo(int i); +} + +abstract class Base implements Iface { + // Iface.foo(int) will be added as a miranda method. + + void printError(String msg) { + System.out.println(msg); + } +} + +class Main1 extends Base { + public void foo(int i) { + if (i != 1) { + printError("error1"); + } + } +} + +class Main2 extends Main1 { + public void foo(int i) { + if (i != 2) { + printError("error2"); + } + } +} + +public class Main { + static Base sMain1; + static Base sMain2; + + static boolean sIsOptimizing = true; + static boolean sHasJIT = true; + static volatile boolean sOtherThreadStarted; + + private static void assertSingleImplementation(Class<?> clazz, String method_name, boolean b) { + if (hasSingleImplementation(clazz, method_name) != b) { + System.out.println(clazz + "." + method_name + + " doesn't have single implementation value of " + b); + } + } + + // sMain1.foo() will be always be Main1.foo() before Main2 is loaded/linked. + // So sMain1.foo() can be devirtualized to Main1.foo() and be inlined. + // After Dummy.createMain2() which links in Main2, live testOverride() on stack + // should be deoptimized. + static void testOverride(boolean createMain2, boolean wait, boolean setHasJIT) { + if (setHasJIT) { + if (isInterpreted()) { + sHasJIT = false; + } + return; + } + + if (createMain2 && (sIsOptimizing || sHasJIT)) { + assertIsManaged(); + } + + sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); + + if (createMain2) { + // Wait for the other thread to start. + while (!sOtherThreadStarted); + // Create an Main2 instance and assign it to sMain2. + // sMain1 is kept the same. + sMain2 = Dummy.createMain2(); + // Wake up the other thread. + synchronized(Main.class) { + Main.class.notify(); + } + } else if (wait) { + // This is the other thread. + synchronized(Main.class) { + sOtherThreadStarted = true; + // Wait for Main2 to be linked and deoptimization is triggered. + try { + Main.class.wait(); + } catch (Exception e) { + } + } + } + + // There should be a deoptimization here right after Main2 is linked by + // calling Dummy.createMain2(), even though sMain1 didn't change. + // The behavior here would be different if inline-cache is used, which + // doesn't deoptimize since sMain1 still hits the type cache. + sMain1.foo(sMain1.getClass() == Main1.class ? 1 : 2); + if ((createMain2 || wait) && sHasJIT && !sIsOptimizing) { + // This method should be deoptimized right after Main2 is created. + assertIsInterpreted(); + } + + if (sMain2 != null) { + sMain2.foo(sMain2.getClass() == Main1.class ? 1 : 2); + } + } + + // Test scenarios under which CHA-based devirtualization happens, + // and class loading that overrides a method can invalidate compiled code. + public static void main(String[] args) { + System.loadLibrary(args[0]); + + if (isInterpreted()) { + sIsOptimizing = false; + } + + // sMain1 is an instance of Main1. Main2 hasn't bee loaded yet. + sMain1 = new Main1(); + + ensureJitCompiled(Main.class, "testOverride"); + testOverride(false, false, true); + + if (sHasJIT && !sIsOptimizing) { + assertSingleImplementation(Base.class, "foo", true); + assertSingleImplementation(Main1.class, "foo", true); + } else { + // Main2 is verified ahead-of-time so it's linked in already. + } + + // Create another thread that also calls sMain1.foo(). + // Try to test suspend and deopt another thread. + new Thread() { + public void run() { + testOverride(false, true, false); + } + }.start(); + + // This will create Main2 instance in the middle of testOverride(). + testOverride(true, false, false); + assertSingleImplementation(Base.class, "foo", false); + assertSingleImplementation(Main1.class, "foo", false); + } + + private static native void ensureJitCompiled(Class<?> itf, String method_name); + private static native void assertIsInterpreted(); + private static native void assertIsManaged(); + private static native boolean isInterpreted(); + private static native boolean hasSingleImplementation(Class<?> clazz, String method_name); +} + +// Put createMain2() in another class to avoid class loading due to verifier. +class Dummy { + static Main1 createMain2() { + return new Main2(); + } +} diff --git a/test/616-cha-proxy-method-inline/expected.txt b/test/616-cha-proxy-method-inline/expected.txt new file mode 100644 index 0000000000..6a5618ebc6 --- /dev/null +++ b/test/616-cha-proxy-method-inline/expected.txt @@ -0,0 +1 @@ +JNI_OnLoad called diff --git a/test/616-cha-proxy-method-inline/info.txt b/test/616-cha-proxy-method-inline/info.txt new file mode 100644 index 0000000000..012685547c --- /dev/null +++ b/test/616-cha-proxy-method-inline/info.txt @@ -0,0 +1 @@ +Test for Class Hierarchy Analysis (CHA) on inlining a cross-dex proxy method. diff --git a/test/616-cha-proxy-method-inline/multidex.jpp b/test/616-cha-proxy-method-inline/multidex.jpp new file mode 100644 index 0000000000..b0d200ea38 --- /dev/null +++ b/test/616-cha-proxy-method-inline/multidex.jpp @@ -0,0 +1,3 @@ +Main: + @@com.android.jack.annotations.ForceInMainDex + class Main diff --git a/test/616-cha-proxy-method-inline/run b/test/616-cha-proxy-method-inline/run new file mode 100644 index 0000000000..d8b4f0d26c --- /dev/null +++ b/test/616-cha-proxy-method-inline/run @@ -0,0 +1,18 @@ +#!/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. + +# Run without an app image to prevent the classes to be loaded at startup. +exec ${RUN} "${@}" --no-app-image diff --git a/test/616-cha-proxy-method-inline/src-multidex/Foo.java b/test/616-cha-proxy-method-inline/src-multidex/Foo.java new file mode 100644 index 0000000000..9deca3e646 --- /dev/null +++ b/test/616-cha-proxy-method-inline/src-multidex/Foo.java @@ -0,0 +1,19 @@ +/* + * 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. + */ + +interface Foo { + public Object bar(Object obj); +} diff --git a/test/616-cha-proxy-method-inline/src/Main.java b/test/616-cha-proxy-method-inline/src/Main.java new file mode 100644 index 0000000000..be7bc820b3 --- /dev/null +++ b/test/616-cha-proxy-method-inline/src/Main.java @@ -0,0 +1,70 @@ +/* + * 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.Method; +import java.lang.reflect.InvocationTargetException; + +class DebugProxy implements java.lang.reflect.InvocationHandler { + private Object obj; + static Class<?>[] interfaces = {Foo.class}; + + public static Object newInstance(Object obj) { + return java.lang.reflect.Proxy.newProxyInstance( + Foo.class.getClassLoader(), + interfaces, + new DebugProxy(obj)); + } + + private DebugProxy(Object obj) { + this.obj = obj; + } + + public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { + Object result; + if (obj == null) { + return null; + } + try { + System.out.println("before invoking method " + m.getName()); + result = m.invoke(obj, args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } catch (Exception e) { + throw new RuntimeException("unexpected invocation exception: " + e.getMessage()); + } finally { + System.out.println("after invoking method " + m.getName()); + } + return result; + } +} + +public class Main { + public static void call(Foo foo) { + if (foo == null) { + return; + } + foo.bar(null); + } + + public static void main(String[] args) { + System.loadLibrary(args[0]); + Foo foo = (Foo)DebugProxy.newInstance(null); + ensureJitCompiled(Main.class, "call"); + call(foo); + } + + private static native void ensureJitCompiled(Class<?> itf, String method_name); +} diff --git a/test/981-dedup-original-dex/expected.txt b/test/981-dedup-original-dex/expected.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/test/981-dedup-original-dex/expected.txt diff --git a/test/981-dedup-original-dex/info.txt b/test/981-dedup-original-dex/info.txt new file mode 100644 index 0000000000..62696e00d7 --- /dev/null +++ b/test/981-dedup-original-dex/info.txt @@ -0,0 +1,4 @@ +Tests basic functions in the jvmti plugin. + +This checks that we do not needlessly duplicate the contents of retransformed +classes original dex files. diff --git a/test/981-dedup-original-dex/run b/test/981-dedup-original-dex/run new file mode 100755 index 0000000000..e92b873956 --- /dev/null +++ b/test/981-dedup-original-dex/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/981-dedup-original-dex/src/Main.java b/test/981-dedup-original-dex/src/Main.java new file mode 100644 index 0000000000..cd3f007532 --- /dev/null +++ b/test/981-dedup-original-dex/src/Main.java @@ -0,0 +1,139 @@ +/* + * 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. + */ + +import java.lang.reflect.Field; +import java.util.Base64; + +import dalvik.system.ClassExt; + +public class Main { + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] DEX_BYTES_1 = Base64.getDecoder().decode( + "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" + + "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" + + "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" + + "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA="); + + /** + * base64 encoded class/dex file for + * class Transform2 { + * public void sayHi() { + * System.out.println("Goodbye2"); + * } + * } + */ + private static final byte[] DEX_BYTES_2 = Base64.getDecoder().decode( + "ZGV4CjAzNQAjXDED2iflQ3NXbPtBRVjQVMqoDU9nDz/QAgAAcAAAAHhWNBIAAAAAAAAAADACAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACwAQAAIAEAAGIB" + + "AABqAQAAdAEAAIIBAACZAQAArQEAAMEBAADVAQAA5gEAAOkBAADtAQAAAQIAAAYCAAAPAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAACECAAAA" + + "AAAAAQABAAEAAAAWAgAABAAAAHAQAwAAAA4AAwABAAIAAAAbAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAIR29vZGJ5ZTIADExUcmFuc2Zvcm0yOwAVTGphdmEvaW8vUHJp" + + "bnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEv" + + "bGFuZy9TeXN0ZW07AA9UcmFuc2Zvcm0yLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTQuMzAA" + + "A291dAAHcHJpbnRsbgAFc2F5SGkAAQAHDgADAAcOhwAAAAEBAICABKACAQG4AgANAAAAAAAAAAEA" + + "AAAAAAAAAQAAAA4AAABwAAAAAgAAAAYAAACoAAAAAwAAAAIAAADAAAAABAAAAAEAAADYAAAABQAA" + + "AAQAAADgAAAABgAAAAEAAAAAAQAAASAAAAIAAAAgAQAAARAAAAEAAABcAQAAAiAAAA4AAABiAQAA" + + "AyAAAAIAAAAWAgAAACAAAAEAAAAhAgAAABAAAAEAAAAwAgAA"); + + public static void main(String[] args) { + try { + doTest(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void assertSame(Object a, Object b) throws Exception { + if (a != b) { + throw new AssertionError("'" + (a != null ? a.toString() : "null") + "' is not the same as " + + "'" + (b != null ? b.toString() : "null") + "'"); + } + } + + private static Object getObjectField(Object o, String name) throws Exception { + return getObjectField(o, o.getClass(), name); + } + + private static Object getObjectField(Object o, Class<?> type, String name) throws Exception { + Field f = type.getDeclaredField(name); + f.setAccessible(true); + return f.get(o); + } + + private static Object getOriginalDexFile(Class<?> k) throws Exception { + ClassExt ext_data_object = (ClassExt) getObjectField(k, "extData"); + if (ext_data_object == null) { + return null; + } + + return getObjectField(ext_data_object, "originalDexFile"); + } + + public static void doTest() throws Exception { + // Make sure both of these are loaded prior to transformations being added so they have the same + // original dex files. + Transform t1 = new Transform(); + Transform2 t2 = new Transform2(); + + assertSame(null, getOriginalDexFile(t1.getClass())); + assertSame(null, getOriginalDexFile(t2.getClass())); + assertSame(null, getOriginalDexFile(Main.class)); + + addCommonTransformationResult("Transform", new byte[0], DEX_BYTES_1); + addCommonTransformationResult("Transform2", new byte[0], DEX_BYTES_2); + enableCommonRetransformation(true); + doCommonClassRetransformation(Transform.class, Transform2.class); + + assertSame(getOriginalDexFile(t1.getClass()), getOriginalDexFile(t2.getClass())); + assertSame(null, getOriginalDexFile(Main.class)); + // Make sure that the original dex file is a DexCache object. + assertSame(getOriginalDexFile(t1.getClass()).getClass(), Class.forName("java.lang.DexCache")); + + // Check that we end up with a byte[] if we do a direct RedefineClasses + enableCommonRetransformation(false); + doCommonClassRedefinition(Transform.class, new byte[0], DEX_BYTES_1); + assertSame((new byte[0]).getClass(), getOriginalDexFile(t1.getClass()).getClass()); + } + + // Transforms the class + private static native void doCommonClassRetransformation(Class<?>... target); + private static native void doCommonClassRedefinition(Class<?> target, + byte[] class_file, + byte[] dex_file); + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/981-dedup-original-dex/src/Transform.java b/test/981-dedup-original-dex/src/Transform.java new file mode 100644 index 0000000000..3c97907ddc --- /dev/null +++ b/test/981-dedup-original-dex/src/Transform.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +class Transform { + public void sayHi() { + System.out.println("hello"); + } +} diff --git a/test/981-dedup-original-dex/src/Transform2.java b/test/981-dedup-original-dex/src/Transform2.java new file mode 100644 index 0000000000..eb22842184 --- /dev/null +++ b/test/981-dedup-original-dex/src/Transform2.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +class Transform2 { + public void sayHi() { + System.out.println("hello2"); + } +} diff --git a/test/knownfailures.json b/test/knownfailures.json index fd2a3178aa..2de34ca44f 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -362,10 +362,5 @@ "description": ["Disable 638-checker-inline-caches temporarily until a fix", "arrives."], "bug": "http://b/36371709" - }, - { - "tests": "080-oom-throw", - "bug": "http://b/36501991", - "variant": "interpreter & target" } ] diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py index 16ddb09c4b..9b9997004b 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -928,6 +928,8 @@ def main(): build_command += ' -j' build_command += ' -C ' + env.ANDROID_BUILD_TOP build_command += ' ' + build_targets + # Add 'dist' to avoid Jack issues b/36169180. + build_command += ' dist' if subprocess.call(build_command.split()): sys.exit(1) if user_requested_test: diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index fddae3af02..8cb14bdfc5 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -121,6 +121,7 @@ static AgentLib agents[] = { { "943-private-recursive-jit", common_redefine::OnLoad, nullptr }, { "944-transform-classloaders", common_redefine::OnLoad, nullptr }, { "945-obsolete-native", common_redefine::OnLoad, nullptr }, + { "981-dedup-original-dex", common_retransform::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { |