Merge "Avoid using ThreadList::Dump() in empty checkpoint timeout."
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index c537483..cc7df1c 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -27,6 +27,7 @@
#include <string>
#include <ostream>
+#include "art_method.h"
#include "base/bit_utils.h"
#include "base/dchecked_vector.h"
#include "base/enums.h"
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index f108595..00ad3e3 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -5681,13 +5681,13 @@
void ParallelMoveResolverARMVIXL::Exchange(int mem1, int mem2) {
// TODO(VIXL32): Double check the performance of this implementation.
UseScratchRegisterScope temps(GetAssembler()->GetVIXLAssembler());
- vixl32::Register temp = temps.Acquire();
- vixl32::SRegister temp_s = temps.AcquireS();
+ vixl32::SRegister temp_1 = temps.AcquireS();
+ vixl32::SRegister temp_2 = temps.AcquireS();
- __ Ldr(temp, MemOperand(sp, mem1));
- __ Vldr(temp_s, MemOperand(sp, mem2));
- __ Str(temp, MemOperand(sp, mem2));
- __ Vstr(temp_s, MemOperand(sp, mem1));
+ __ Vldr(temp_1, MemOperand(sp, mem1));
+ __ Vldr(temp_2, MemOperand(sp, mem2));
+ __ Vstr(temp_1, MemOperand(sp, mem2));
+ __ Vstr(temp_2, MemOperand(sp, mem1));
}
void ParallelMoveResolverARMVIXL::EmitSwap(size_t index) {
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index 5cf3c24..36690c0 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -4517,27 +4517,20 @@
locations->SetInAt(0, Location::RequiresRegister());
}
-void InstructionCodeGeneratorMIPS64::VisitPackedSwitch(HPackedSwitch* switch_instr) {
- int32_t lower_bound = switch_instr->GetStartValue();
- int32_t num_entries = switch_instr->GetNumEntries();
- LocationSummary* locations = switch_instr->GetLocations();
- GpuRegister value_reg = locations->InAt(0).AsRegister<GpuRegister>();
- HBasicBlock* default_block = switch_instr->GetDefaultBlock();
-
+void InstructionCodeGeneratorMIPS64::GenPackedSwitchWithCompares(GpuRegister value_reg,
+ int32_t lower_bound,
+ uint32_t num_entries,
+ HBasicBlock* switch_block,
+ HBasicBlock* default_block) {
// Create a set of compare/jumps.
GpuRegister temp_reg = TMP;
- if (IsInt<16>(-lower_bound)) {
- __ Addiu(temp_reg, value_reg, -lower_bound);
- } else {
- __ LoadConst32(AT, -lower_bound);
- __ Addu(temp_reg, value_reg, AT);
- }
+ __ Addiu32(temp_reg, value_reg, -lower_bound);
// Jump to default if index is negative
// Note: We don't check the case that index is positive while value < lower_bound, because in
// this case, index >= num_entries must be true. So that we can save one branch instruction.
__ Bltzc(temp_reg, codegen_->GetLabelOf(default_block));
- const ArenaVector<HBasicBlock*>& successors = switch_instr->GetBlock()->GetSuccessors();
+ const ArenaVector<HBasicBlock*>& successors = switch_block->GetSuccessors();
// Jump to successors[0] if value == lower_bound.
__ Beqzc(temp_reg, codegen_->GetLabelOf(successors[0]));
int32_t last_index = 0;
@@ -4555,11 +4548,66 @@
}
// And the default for any other value.
- if (!codegen_->GoesToNextBlock(switch_instr->GetBlock(), default_block)) {
+ if (!codegen_->GoesToNextBlock(switch_block, default_block)) {
__ Bc(codegen_->GetLabelOf(default_block));
}
}
+void InstructionCodeGeneratorMIPS64::GenTableBasedPackedSwitch(GpuRegister value_reg,
+ int32_t lower_bound,
+ uint32_t num_entries,
+ HBasicBlock* switch_block,
+ HBasicBlock* default_block) {
+ // Create a jump table.
+ std::vector<Mips64Label*> labels(num_entries);
+ const ArenaVector<HBasicBlock*>& successors = switch_block->GetSuccessors();
+ for (uint32_t i = 0; i < num_entries; i++) {
+ labels[i] = codegen_->GetLabelOf(successors[i]);
+ }
+ JumpTable* table = __ CreateJumpTable(std::move(labels));
+
+ // Is the value in range?
+ __ Addiu32(TMP, value_reg, -lower_bound);
+ __ LoadConst32(AT, num_entries);
+ __ Bgeuc(TMP, AT, codegen_->GetLabelOf(default_block));
+
+ // We are in the range of the table.
+ // Load the target address from the jump table, indexing by the value.
+ __ LoadLabelAddress(AT, table->GetLabel());
+ __ Sll(TMP, TMP, 2);
+ __ Daddu(TMP, TMP, AT);
+ __ Lw(TMP, TMP, 0);
+ // Compute the absolute target address by adding the table start address
+ // (the table contains offsets to targets relative to its start).
+ __ Daddu(TMP, TMP, AT);
+ // And jump.
+ __ Jr(TMP);
+ __ Nop();
+}
+
+void InstructionCodeGeneratorMIPS64::VisitPackedSwitch(HPackedSwitch* switch_instr) {
+ int32_t lower_bound = switch_instr->GetStartValue();
+ uint32_t num_entries = switch_instr->GetNumEntries();
+ LocationSummary* locations = switch_instr->GetLocations();
+ GpuRegister value_reg = locations->InAt(0).AsRegister<GpuRegister>();
+ HBasicBlock* switch_block = switch_instr->GetBlock();
+ HBasicBlock* default_block = switch_instr->GetDefaultBlock();
+
+ if (num_entries > kPackedSwitchJumpTableThreshold) {
+ GenTableBasedPackedSwitch(value_reg,
+ lower_bound,
+ num_entries,
+ switch_block,
+ default_block);
+ } else {
+ GenPackedSwitchWithCompares(value_reg,
+ lower_bound,
+ num_entries,
+ switch_block,
+ default_block);
+ }
+}
+
void LocationsBuilderMIPS64::VisitClassTableGet(HClassTableGet*) {
UNIMPLEMENTED(FATAL) << "ClassTableGet is unimplemented on mips64";
}
diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h
index d5811c2..8ac919f 100644
--- a/compiler/optimizing/code_generator_mips64.h
+++ b/compiler/optimizing/code_generator_mips64.h
@@ -217,6 +217,14 @@
Mips64Assembler* GetAssembler() const { return assembler_; }
+ // Compare-and-jump packed switch generates approx. 3 + 2.5 * N 32-bit
+ // instructions for N cases.
+ // Table-based packed switch generates approx. 11 32-bit instructions
+ // and N 32-bit data words for N cases.
+ // At N = 6 they come out as 18 and 17 32-bit words respectively.
+ // We switch to the table-based method starting with 7 cases.
+ static constexpr uint32_t kPackedSwitchJumpTableThreshold = 6;
+
private:
void GenerateClassInitializationCheck(SlowPathCodeMIPS64* slow_path, GpuRegister class_reg);
void GenerateMemoryBarrier(MemBarrierKind kind);
@@ -260,6 +268,16 @@
LocationSummary* locations,
Mips64Label* label);
void HandleGoto(HInstruction* got, HBasicBlock* successor);
+ void GenPackedSwitchWithCompares(GpuRegister value_reg,
+ int32_t lower_bound,
+ uint32_t num_entries,
+ HBasicBlock* switch_block,
+ HBasicBlock* default_block);
+ void GenTableBasedPackedSwitch(GpuRegister value_reg,
+ int32_t lower_bound,
+ uint32_t num_entries,
+ HBasicBlock* switch_block,
+ HBasicBlock* default_block);
Mips64Assembler* const assembler_;
CodeGeneratorMIPS64* const codegen_;
diff --git a/compiler/optimizing/codegen_test.cc b/compiler/optimizing/codegen_test.cc
index ac83bd9..879b4ce 100644
--- a/compiler/optimizing/codegen_test.cc
+++ b/compiler/optimizing/codegen_test.cc
@@ -1041,6 +1041,31 @@
}
}
+#ifdef ART_ENABLE_CODEGEN_arm
+TEST_F(CodegenTest, ARMVIXLParallelMoveResolver) {
+ std::unique_ptr<const ArmInstructionSetFeatures> features(
+ ArmInstructionSetFeatures::FromCppDefines());
+ ArenaPool pool;
+ ArenaAllocator allocator(&pool);
+ HGraph* graph = CreateGraph(&allocator);
+ arm::CodeGeneratorARMVIXL codegen(graph, *features.get(), CompilerOptions());
+
+ codegen.Initialize();
+
+ // This will result in calling EmitSwap -> void ParallelMoveResolverARMVIXL::Exchange(int mem1,
+ // int mem2) which was faulty (before the fix). So previously GPR and FP scratch registers were
+ // used as temps; however GPR scratch register is required for big stack offsets which don't fit
+ // LDR encoding. So the following code is a regression test for that situation.
+ HParallelMove* move = new (graph->GetArena()) HParallelMove(graph->GetArena());
+ move->AddMove(Location::StackSlot(0), Location::StackSlot(8192), Primitive::kPrimInt, nullptr);
+ move->AddMove(Location::StackSlot(8192), Location::StackSlot(0), Primitive::kPrimInt, nullptr);
+ codegen.GetMoveResolver()->EmitNativeCode(move);
+
+ InternalCodeAllocator code_allocator;
+ codegen.Finalize(&code_allocator);
+}
+#endif
+
#ifdef ART_ENABLE_CODEGEN_mips
TEST_F(CodegenTest, MipsClobberRA) {
std::unique_ptr<const MipsInstructionSetFeatures> features_mips(
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index d847879..3b83e95 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -344,6 +344,7 @@
if (actual_method != nullptr) {
bool result = TryInlineAndReplace(invoke_instruction,
actual_method,
+ ReferenceTypeInfo::CreateInvalid(),
/* do_rtp */ true,
cha_devirtualize);
if (result && !invoke_instruction->IsInvokeStaticOrDirect()) {
@@ -471,9 +472,10 @@
HInstruction* receiver = invoke_instruction->InputAt(0);
HInstruction* cursor = invoke_instruction->GetPrevious();
HBasicBlock* bb_cursor = invoke_instruction->GetBlock();
-
+ Handle<mirror::Class> handle = handles_->NewHandle(GetMonomorphicType(classes));
if (!TryInlineAndReplace(invoke_instruction,
resolved_method,
+ ReferenceTypeInfo::Create(handle, /* is_exact */ true),
/* do_rtp */ false,
/* cha_devirtualize */ false)) {
return false;
@@ -591,13 +593,13 @@
break;
}
ArtMethod* method = nullptr;
+
+ Handle<mirror::Class> handle = handles_->NewHandle(classes->Get(i));
if (invoke_instruction->IsInvokeInterface()) {
- method = classes->Get(i)->FindVirtualMethodForInterface(
- resolved_method, pointer_size);
+ method = handle->FindVirtualMethodForInterface(resolved_method, pointer_size);
} else {
DCHECK(invoke_instruction->IsInvokeVirtual());
- method = classes->Get(i)->FindVirtualMethodForVirtual(
- resolved_method, pointer_size);
+ method = handle->FindVirtualMethodForVirtual(resolved_method, pointer_size);
}
HInstruction* receiver = invoke_instruction->InputAt(0);
@@ -605,10 +607,13 @@
HBasicBlock* bb_cursor = invoke_instruction->GetBlock();
dex::TypeIndex class_index = FindClassIndexIn(
- classes->Get(i), caller_dex_file, caller_compilation_unit_.GetDexCache());
+ handle.Get(), caller_dex_file, caller_compilation_unit_.GetDexCache());
HInstruction* return_replacement = nullptr;
if (!class_index.IsValid() ||
- !TryBuildAndInline(invoke_instruction, method, &return_replacement)) {
+ !TryBuildAndInline(invoke_instruction,
+ method,
+ ReferenceTypeInfo::Create(handle, /* is_exact */ true),
+ &return_replacement)) {
all_targets_inlined = false;
} else {
one_target_inlined = true;
@@ -627,7 +632,7 @@
cursor,
bb_cursor,
class_index,
- classes->Get(i),
+ handle.Get(),
invoke_instruction,
deoptimize);
if (deoptimize) {
@@ -792,7 +797,10 @@
HBasicBlock* bb_cursor = invoke_instruction->GetBlock();
HInstruction* return_replacement = nullptr;
- if (!TryBuildAndInline(invoke_instruction, actual_method, &return_replacement)) {
+ if (!TryBuildAndInline(invoke_instruction,
+ actual_method,
+ ReferenceTypeInfo::CreateInvalid(),
+ &return_replacement)) {
return false;
}
@@ -857,13 +865,14 @@
bool HInliner::TryInlineAndReplace(HInvoke* invoke_instruction,
ArtMethod* method,
+ ReferenceTypeInfo receiver_type,
bool do_rtp,
bool cha_devirtualize) {
HInstruction* return_replacement = nullptr;
uint32_t dex_pc = invoke_instruction->GetDexPc();
HInstruction* cursor = invoke_instruction->GetPrevious();
HBasicBlock* bb_cursor = invoke_instruction->GetBlock();
- if (!TryBuildAndInline(invoke_instruction, method, &return_replacement)) {
+ if (!TryBuildAndInline(invoke_instruction, method, receiver_type, &return_replacement)) {
if (invoke_instruction->IsInvokeInterface()) {
// Turn an invoke-interface into an invoke-virtual. An invoke-virtual is always
// better than an invoke-interface because:
@@ -921,6 +930,7 @@
bool HInliner::TryBuildAndInline(HInvoke* invoke_instruction,
ArtMethod* method,
+ ReferenceTypeInfo receiver_type,
HInstruction** return_replacement) {
if (method->IsProxyMethod()) {
VLOG(compiler) << "Method " << method->PrettyMethod()
@@ -997,7 +1007,8 @@
return false;
}
- if (!TryBuildAndInlineHelper(invoke_instruction, method, same_dex_file, return_replacement)) {
+ if (!TryBuildAndInlineHelper(
+ invoke_instruction, method, receiver_type, same_dex_file, return_replacement)) {
return false;
}
@@ -1194,8 +1205,10 @@
bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction,
ArtMethod* resolved_method,
+ ReferenceTypeInfo receiver_type,
bool same_dex_file,
HInstruction** return_replacement) {
+ DCHECK(!(resolved_method->IsStatic() && receiver_type.IsValid()));
ScopedObjectAccess soa(Thread::Current());
const DexFile::CodeItem* code_item = resolved_method->GetCodeItem();
const DexFile& callee_dex_file = *resolved_method->GetDexFile();
@@ -1286,12 +1299,13 @@
}
size_t parameter_index = 0;
+ bool run_rtp = false;
for (HInstructionIterator instructions(callee_graph->GetEntryBlock()->GetInstructions());
!instructions.Done();
instructions.Advance()) {
HInstruction* current = instructions.Current();
if (current->IsParameterValue()) {
- HInstruction* argument = invoke_instruction->InputAt(parameter_index++);
+ HInstruction* argument = invoke_instruction->InputAt(parameter_index);
if (argument->IsNullConstant()) {
current->ReplaceWith(callee_graph->GetNullConstant());
} else if (argument->IsIntConstant()) {
@@ -1305,15 +1319,21 @@
current->ReplaceWith(
callee_graph->GetDoubleConstant(argument->AsDoubleConstant()->GetValue()));
} else if (argument->GetType() == Primitive::kPrimNot) {
- current->SetReferenceTypeInfo(argument->GetReferenceTypeInfo());
+ if (!resolved_method->IsStatic() && parameter_index == 0 && receiver_type.IsValid()) {
+ run_rtp = true;
+ current->SetReferenceTypeInfo(receiver_type);
+ } else {
+ current->SetReferenceTypeInfo(argument->GetReferenceTypeInfo());
+ }
current->AsParameterValue()->SetCanBeNull(argument->CanBeNull());
}
+ ++parameter_index;
}
}
// We have replaced formal arguments with actual arguments. If actual types
// are more specific than the declared ones, run RTP again on the inner graph.
- if (ArgumentTypesMoreSpecific(invoke_instruction, resolved_method)) {
+ if (run_rtp || ArgumentTypesMoreSpecific(invoke_instruction, resolved_method)) {
ReferenceTypePropagation(callee_graph,
dex_compilation_unit.GetDexCache(),
handles_,
@@ -1502,7 +1522,7 @@
ReferenceTypeInfo actual_rti = actual_obj->GetReferenceTypeInfo();
return (actual_rti.IsExact() && !declared_rti.IsExact()) ||
- declared_rti.IsStrictSupertypeOf(actual_rti);
+ declared_rti.IsStrictSupertypeOf(actual_rti);
}
ReferenceTypeInfo HInliner::GetClassRTI(mirror::Class* klass) {
diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h
index 0c64362..4c0b990 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -66,17 +66,20 @@
// a CHA guard needs to be added for the inlining.
bool TryInlineAndReplace(HInvoke* invoke_instruction,
ArtMethod* resolved_method,
+ ReferenceTypeInfo receiver_type,
bool do_rtp,
bool cha_devirtualize)
REQUIRES_SHARED(Locks::mutator_lock_);
bool TryBuildAndInline(HInvoke* invoke_instruction,
ArtMethod* resolved_method,
+ ReferenceTypeInfo receiver_type,
HInstruction** return_replacement)
REQUIRES_SHARED(Locks::mutator_lock_);
bool TryBuildAndInlineHelper(HInvoke* invoke_instruction,
ArtMethod* resolved_method,
+ ReferenceTypeInfo receiver_type,
bool same_dex_file,
HInstruction** return_replacement);
diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc
index 1fb90e5..e9c6615 100644
--- a/compiler/optimizing/intrinsics_mips.cc
+++ b/compiler/optimizing/intrinsics_mips.cc
@@ -2466,6 +2466,94 @@
__ Bind(&done);
}
+// void java.lang.String.getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
+void IntrinsicLocationsBuilderMIPS::VisitStringGetCharsNoCheck(HInvoke* invoke) {
+ LocationSummary* locations = new (arena_) LocationSummary(invoke,
+ LocationSummary::kCallOnMainOnly,
+ kIntrinsified);
+ locations->SetInAt(0, Location::RequiresRegister());
+ locations->SetInAt(1, Location::RequiresRegister());
+ locations->SetInAt(2, Location::RequiresRegister());
+ locations->SetInAt(3, Location::RequiresRegister());
+ locations->SetInAt(4, Location::RequiresRegister());
+
+ // We will call memcpy() to do the actual work. Allocate the temporary
+ // registers to use the correct input registers, and output register.
+ // memcpy() uses the normal MIPS calling convention.
+ InvokeRuntimeCallingConvention calling_convention;
+
+ locations->AddTemp(Location::RegisterLocation(calling_convention.GetRegisterAt(0)));
+ locations->AddTemp(Location::RegisterLocation(calling_convention.GetRegisterAt(1)));
+ locations->AddTemp(Location::RegisterLocation(calling_convention.GetRegisterAt(2)));
+
+ Location outLocation = calling_convention.GetReturnLocation(Primitive::kPrimInt);
+ locations->AddTemp(Location::RegisterLocation(outLocation.AsRegister<Register>()));
+}
+
+void IntrinsicCodeGeneratorMIPS::VisitStringGetCharsNoCheck(HInvoke* invoke) {
+ MipsAssembler* assembler = GetAssembler();
+ LocationSummary* locations = invoke->GetLocations();
+
+ // Check assumption that sizeof(Char) is 2 (used in scaling below).
+ const size_t char_size = Primitive::ComponentSize(Primitive::kPrimChar);
+ DCHECK_EQ(char_size, 2u);
+ const size_t char_shift = Primitive::ComponentSizeShift(Primitive::kPrimChar);
+
+ Register srcObj = locations->InAt(0).AsRegister<Register>();
+ Register srcBegin = locations->InAt(1).AsRegister<Register>();
+ Register srcEnd = locations->InAt(2).AsRegister<Register>();
+ Register dstObj = locations->InAt(3).AsRegister<Register>();
+ Register dstBegin = locations->InAt(4).AsRegister<Register>();
+
+ Register dstPtr = locations->GetTemp(0).AsRegister<Register>();
+ DCHECK_EQ(dstPtr, A0);
+ Register srcPtr = locations->GetTemp(1).AsRegister<Register>();
+ DCHECK_EQ(srcPtr, A1);
+ Register numChrs = locations->GetTemp(2).AsRegister<Register>();
+ DCHECK_EQ(numChrs, A2);
+
+ Register dstReturn = locations->GetTemp(3).AsRegister<Register>();
+ DCHECK_EQ(dstReturn, V0);
+
+ MipsLabel done;
+
+ // Location of data in char array buffer.
+ const uint32_t data_offset = mirror::Array::DataOffset(char_size).Uint32Value();
+
+ // Get offset of value field within a string object.
+ const int32_t value_offset = mirror::String::ValueOffset().Int32Value();
+
+ __ Beq(srcEnd, srcBegin, &done); // No characters to move.
+
+ // Calculate number of characters to be copied.
+ __ Subu(numChrs, srcEnd, srcBegin);
+
+ // Calculate destination address.
+ __ Addiu(dstPtr, dstObj, data_offset);
+ if (IsR6()) {
+ __ Lsa(dstPtr, dstBegin, dstPtr, char_shift);
+ } else {
+ __ Sll(AT, dstBegin, char_shift);
+ __ Addu(dstPtr, dstPtr, AT);
+ }
+
+ // Calculate source address.
+ __ Addiu(srcPtr, srcObj, value_offset);
+ if (IsR6()) {
+ __ Lsa(srcPtr, srcBegin, srcPtr, char_shift);
+ } else {
+ __ Sll(AT, srcBegin, char_shift);
+ __ Addu(srcPtr, srcPtr, AT);
+ }
+
+ // Calculate number of bytes to copy from number of characters.
+ __ Sll(numChrs, numChrs, char_shift);
+
+ codegen_->InvokeRuntime(kQuickMemcpy, invoke, invoke->GetDexPc(), nullptr);
+
+ __ Bind(&done);
+}
+
// Unimplemented intrinsics.
UNIMPLEMENTED_INTRINSIC(MIPS, MathCeil)
@@ -2475,7 +2563,6 @@
UNIMPLEMENTED_INTRINSIC(MIPS, UnsafeCASLong)
UNIMPLEMENTED_INTRINSIC(MIPS, ReferenceGetReferent)
-UNIMPLEMENTED_INTRINSIC(MIPS, StringGetCharsNoCheck)
UNIMPLEMENTED_INTRINSIC(MIPS, SystemArrayCopyChar)
UNIMPLEMENTED_INTRINSIC(MIPS, SystemArrayCopy)
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index afa17ce..8c64d25 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -5762,7 +5762,6 @@
: HInstruction(SideEffectsForArchRuntimeCalls(), dex_pc),
special_input_(HUserRecord<HInstruction*>(current_method)),
string_index_(string_index) {
- SetPackedFlag<kFlagIsInDexCache>(false);
SetPackedField<LoadKindField>(LoadKind::kDexCacheViaMethod);
load_data_.dex_file_ = &dex_file;
}
@@ -5789,7 +5788,6 @@
const DexFile& GetDexFile() const;
dex::StringIndex GetStringIndex() const {
- DCHECK(HasStringReference(GetLoadKind()) || /* For slow paths. */ !IsInDexCache());
return string_index_;
}
@@ -5814,7 +5812,7 @@
load_kind == LoadKind::kJitTableAddress) {
return false;
}
- return !IsInDexCache();
+ return true;
}
bool NeedsDexCacheOfDeclaringClass() const OVERRIDE {
@@ -5828,15 +5826,6 @@
return SideEffects::CanTriggerGC();
}
- bool IsInDexCache() const { return GetPackedFlag<kFlagIsInDexCache>(); }
-
- void MarkInDexCache() {
- SetPackedFlag<kFlagIsInDexCache>(true);
- DCHECK(!NeedsEnvironment());
- RemoveEnvironment();
- SetSideEffects(SideEffects::None());
- }
-
void AddSpecialInput(HInstruction* special_input);
using HInstruction::GetInputRecords; // Keep the const version visible.
@@ -5852,8 +5841,7 @@
DECLARE_INSTRUCTION(LoadString);
private:
- static constexpr size_t kFlagIsInDexCache = kNumberOfGenericPackedBits;
- static constexpr size_t kFieldLoadKind = kFlagIsInDexCache + 1;
+ static constexpr size_t kFieldLoadKind = kNumberOfGenericPackedBits;
static constexpr size_t kFieldLoadKindSize =
MinimumBitsToStore(static_cast<size_t>(LoadKind::kLast));
static constexpr size_t kNumberOfLoadStringPackedBits = kFieldLoadKind + kFieldLoadKindSize;
diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc
index 9fdeccf..ca26c30 100644
--- a/compiler/optimizing/sharpening.cc
+++ b/compiler/optimizing/sharpening.cc
@@ -270,7 +270,6 @@
void HSharpening::ProcessLoadString(HLoadString* load_string) {
DCHECK_EQ(load_string->GetLoadKind(), HLoadString::LoadKind::kDexCacheViaMethod);
- DCHECK(!load_string->IsInDexCache());
const DexFile& dex_file = load_string->GetDexFile();
dex::StringIndex string_index = load_string->GetStringIndex();
diff --git a/compiler/utils/mips/assembler_mips.cc b/compiler/utils/mips/assembler_mips.cc
index 3dcad6a..5e83e82 100644
--- a/compiler/utils/mips/assembler_mips.cc
+++ b/compiler/utils/mips/assembler_mips.cc
@@ -635,6 +635,13 @@
DsFsmInstrRrr(EmitR(0x1f, rt, rd, static_cast<Register>(pos + size - 1), pos, 0x04), rd, rd, rt);
}
+void MipsAssembler::Lsa(Register rd, Register rs, Register rt, int saPlusOne) {
+ CHECK(IsR6());
+ CHECK(1 <= saPlusOne && saPlusOne <= 4) << saPlusOne;
+ int sa = saPlusOne - 1;
+ DsFsmInstrRrr(EmitR(0x0, rs, rt, rd, sa, 0x05), rd, rs, rt);
+}
+
void MipsAssembler::Lb(Register rt, Register rs, uint16_t imm16) {
DsFsmInstrRrr(EmitI(0x20, rs, rt, imm16), rt, rs, rs);
}
diff --git a/compiler/utils/mips/assembler_mips.h b/compiler/utils/mips/assembler_mips.h
index 800dc5f..2fca185 100644
--- a/compiler/utils/mips/assembler_mips.h
+++ b/compiler/utils/mips/assembler_mips.h
@@ -262,6 +262,7 @@
void Srav(Register rd, Register rt, Register rs);
void Ext(Register rd, Register rt, int pos, int size); // R2+
void Ins(Register rd, Register rt, int pos, int size); // R2+
+ void Lsa(Register rd, Register rs, Register rt, int saPlusOne); // R6
void Lb(Register rt, Register rs, uint16_t imm16);
void Lh(Register rt, Register rs, uint16_t imm16);
diff --git a/compiler/utils/mips/assembler_mips32r6_test.cc b/compiler/utils/mips/assembler_mips32r6_test.cc
index a52f519..30667ef 100644
--- a/compiler/utils/mips/assembler_mips32r6_test.cc
+++ b/compiler/utils/mips/assembler_mips32r6_test.cc
@@ -319,6 +319,14 @@
DriverStr(RepeatRR(&mips::MipsAssembler::Bitswap, "bitswap ${reg1}, ${reg2}"), "bitswap");
}
+TEST_F(AssemblerMIPS32r6Test, Lsa) {
+ DriverStr(RepeatRRRIb(&mips::MipsAssembler::Lsa,
+ 2,
+ "lsa ${reg1}, ${reg2}, ${reg3}, {imm}",
+ 1),
+ "lsa");
+}
+
TEST_F(AssemblerMIPS32r6Test, Seleqz) {
DriverStr(RepeatRRR(&mips::MipsAssembler::Seleqz, "seleqz ${reg1}, ${reg2}, ${reg3}"),
"seleqz");
diff --git a/compiler/utils/mips64/assembler_mips64.cc b/compiler/utils/mips64/assembler_mips64.cc
index 5906a71..998f2c7 100644
--- a/compiler/utils/mips64/assembler_mips64.cc
+++ b/compiler/utils/mips64/assembler_mips64.cc
@@ -35,12 +35,14 @@
for (auto& exception_block : exception_blocks_) {
EmitExceptionPoll(&exception_block);
}
+ ReserveJumpTableSpace();
EmitLiterals();
PromoteBranches();
}
void Mips64Assembler::FinalizeInstructions(const MemoryRegion& region) {
EmitBranches();
+ EmitJumpTables();
Assembler::FinalizeInstructions(region);
PatchCFI();
}
@@ -482,6 +484,10 @@
EmitI(0xf, static_cast<GpuRegister>(0), rt, imm16);
}
+void Mips64Assembler::Aui(GpuRegister rt, GpuRegister rs, uint16_t imm16) {
+ EmitI(0xf, rs, rt, imm16);
+}
+
void Mips64Assembler::Dahi(GpuRegister rs, uint16_t imm16) {
EmitI(1, rs, static_cast<GpuRegister>(6), imm16);
}
@@ -1081,6 +1087,20 @@
TemplateLoadConst64(this, rd, value);
}
+void Mips64Assembler::Addiu32(GpuRegister rt, GpuRegister rs, int32_t value) {
+ if (IsInt<16>(value)) {
+ Addiu(rt, rs, value);
+ } else {
+ int16_t high = High16Bits(value);
+ int16_t low = Low16Bits(value);
+ high += (low < 0) ? 1 : 0; // Account for sign extension in addiu.
+ Aui(rt, rs, high);
+ if (low != 0) {
+ Addiu(rt, rt, low);
+ }
+ }
+}
+
void Mips64Assembler::Daddiu64(GpuRegister rt, GpuRegister rs, int64_t value, GpuRegister rtmp) {
if (IsInt<16>(value)) {
Daddiu(rt, rs, value);
@@ -1653,6 +1673,67 @@
FinalizeLabeledBranch(label);
}
+JumpTable* Mips64Assembler::CreateJumpTable(std::vector<Mips64Label*>&& labels) {
+ jump_tables_.emplace_back(std::move(labels));
+ JumpTable* table = &jump_tables_.back();
+ DCHECK(!table->GetLabel()->IsBound());
+ return table;
+}
+
+void Mips64Assembler::ReserveJumpTableSpace() {
+ if (!jump_tables_.empty()) {
+ for (JumpTable& table : jump_tables_) {
+ Mips64Label* label = table.GetLabel();
+ Bind(label);
+
+ // Bulk ensure capacity, as this may be large.
+ size_t orig_size = buffer_.Size();
+ size_t required_capacity = orig_size + table.GetSize();
+ if (required_capacity > buffer_.Capacity()) {
+ buffer_.ExtendCapacity(required_capacity);
+ }
+#ifndef NDEBUG
+ buffer_.has_ensured_capacity_ = true;
+#endif
+
+ // Fill the space with dummy data as the data is not final
+ // until the branches have been promoted. And we shouldn't
+ // be moving uninitialized data during branch promotion.
+ for (size_t cnt = table.GetData().size(), i = 0; i < cnt; i++) {
+ buffer_.Emit<uint32_t>(0x1abe1234u);
+ }
+
+#ifndef NDEBUG
+ buffer_.has_ensured_capacity_ = false;
+#endif
+ }
+ }
+}
+
+void Mips64Assembler::EmitJumpTables() {
+ if (!jump_tables_.empty()) {
+ CHECK(!overwriting_);
+ // Switch from appending instructions at the end of the buffer to overwriting
+ // existing instructions (here, jump tables) in the buffer.
+ overwriting_ = true;
+
+ for (JumpTable& table : jump_tables_) {
+ Mips64Label* table_label = table.GetLabel();
+ uint32_t start = GetLabelLocation(table_label);
+ overwrite_location_ = start;
+
+ for (Mips64Label* target : table.GetData()) {
+ CHECK_EQ(buffer_.Load<uint32_t>(overwrite_location_), 0x1abe1234u);
+ // The table will contain target addresses relative to the table start.
+ uint32_t offset = GetLabelLocation(target) - start;
+ Emit(offset);
+ }
+ }
+
+ overwriting_ = false;
+ }
+}
+
void Mips64Assembler::EmitLiterals() {
if (!literals_.empty()) {
for (Literal& literal : literals_) {
diff --git a/compiler/utils/mips64/assembler_mips64.h b/compiler/utils/mips64/assembler_mips64.h
index 7ef5ab0..a0a1db6 100644
--- a/compiler/utils/mips64/assembler_mips64.h
+++ b/compiler/utils/mips64/assembler_mips64.h
@@ -357,6 +357,36 @@
DISALLOW_COPY_AND_ASSIGN(Literal);
};
+// Jump table: table of labels emitted after the code and before the literals. Similar to literals.
+class JumpTable {
+ public:
+ explicit JumpTable(std::vector<Mips64Label*>&& labels)
+ : label_(), labels_(std::move(labels)) {
+ }
+
+ size_t GetSize() const {
+ return labels_.size() * sizeof(uint32_t);
+ }
+
+ const std::vector<Mips64Label*>& GetData() const {
+ return labels_;
+ }
+
+ Mips64Label* GetLabel() {
+ return &label_;
+ }
+
+ const Mips64Label* GetLabel() const {
+ return &label_;
+ }
+
+ private:
+ Mips64Label label_;
+ std::vector<Mips64Label*> labels_;
+
+ DISALLOW_COPY_AND_ASSIGN(JumpTable);
+};
+
// Slowpath entered when Thread::Current()->_exception is non-null.
class Mips64ExceptionSlowPath {
public:
@@ -388,6 +418,7 @@
overwrite_location_(0),
literals_(arena->Adapter(kArenaAllocAssembler)),
long_literals_(arena->Adapter(kArenaAllocAssembler)),
+ jump_tables_(arena->Adapter(kArenaAllocAssembler)),
last_position_adjustment_(0),
last_old_position_(0),
last_branch_id_(0) {
@@ -480,6 +511,7 @@
void Lwupc(GpuRegister rs, uint32_t imm19); // MIPS64
void Ldpc(GpuRegister rs, uint32_t imm18); // MIPS64
void Lui(GpuRegister rt, uint16_t imm16);
+ void Aui(GpuRegister rt, GpuRegister rs, uint16_t imm16);
void Dahi(GpuRegister rs, uint16_t imm16); // MIPS64
void Dati(GpuRegister rs, uint16_t imm16); // MIPS64
void Sync(uint32_t stype);
@@ -619,6 +651,7 @@
// This function is only used for testing purposes.
void RecordLoadConst64Path(int value);
+ void Addiu32(GpuRegister rt, GpuRegister rs, int32_t value);
void Daddiu64(GpuRegister rt, GpuRegister rs, int64_t value, GpuRegister rtmp = AT); // MIPS64
void Bind(Label* label) OVERRIDE {
@@ -676,6 +709,12 @@
// Load literal using PC-relative loads.
void LoadLiteral(GpuRegister dest_reg, LoadOperandType load_type, Literal* literal);
+ // Create a jump table for the given labels that will be emitted when finalizing.
+ // When the table is emitted, offsets will be relative to the location of the table.
+ // The table location is determined by the location of its label (the label precedes
+ // the table data) and should be loaded using LoadLabelAddress().
+ JumpTable* CreateJumpTable(std::vector<Mips64Label*>&& labels);
+
void Bc(Mips64Label* label);
void Balc(Mips64Label* label);
void Bltc(GpuRegister rs, GpuRegister rt, Mips64Label* label);
@@ -1050,6 +1089,8 @@
const Branch* GetBranch(uint32_t branch_id) const;
void EmitLiterals();
+ void ReserveJumpTableSpace();
+ void EmitJumpTables();
void PromoteBranches();
void EmitBranch(Branch* branch);
void EmitBranches();
@@ -1073,6 +1114,9 @@
ArenaDeque<Literal> literals_;
ArenaDeque<Literal> long_literals_; // 64-bit literals separated for alignment reasons.
+ // Jump table list.
+ ArenaDeque<JumpTable> jump_tables_;
+
// Data for AdjustedPosition(), see the description there.
uint32_t last_position_adjustment_;
uint32_t last_old_position_;
diff --git a/compiler/utils/mips64/assembler_mips64_test.cc b/compiler/utils/mips64/assembler_mips64_test.cc
index 564559f..f2cbebb 100644
--- a/compiler/utils/mips64/assembler_mips64_test.cc
+++ b/compiler/utils/mips64/assembler_mips64_test.cc
@@ -1904,9 +1904,9 @@
DriverStr(expected, "StoreFpuToOffset");
}
-///////////////////////
-// Loading Constants //
-///////////////////////
+//////////////////////////////
+// Loading/adding Constants //
+//////////////////////////////
TEST_F(AssemblerMIPS64Test, LoadConst32) {
// IsUint<16>(value)
@@ -1949,6 +1949,31 @@
DriverStr(expected, "LoadConst32");
}
+TEST_F(AssemblerMIPS64Test, Addiu32) {
+ __ Addiu32(mips64::A1, mips64::A2, -0x8000);
+ __ Addiu32(mips64::A1, mips64::A2, +0);
+ __ Addiu32(mips64::A1, mips64::A2, +0x7FFF);
+ __ Addiu32(mips64::A1, mips64::A2, -0x8001);
+ __ Addiu32(mips64::A1, mips64::A2, +0x8000);
+ __ Addiu32(mips64::A1, mips64::A2, -0x10000);
+ __ Addiu32(mips64::A1, mips64::A2, +0x10000);
+ __ Addiu32(mips64::A1, mips64::A2, +0x12345678);
+
+ const char* expected =
+ "addiu $a1, $a2, -0x8000\n"
+ "addiu $a1, $a2, 0\n"
+ "addiu $a1, $a2, 0x7FFF\n"
+ "aui $a1, $a2, 0xFFFF\n"
+ "addiu $a1, $a1, 0x7FFF\n"
+ "aui $a1, $a2, 1\n"
+ "addiu $a1, $a1, -0x8000\n"
+ "aui $a1, $a2, 0xFFFF\n"
+ "aui $a1, $a2, 1\n"
+ "aui $a1, $a2, 0x1234\n"
+ "addiu $a1, $a1, 0x5678\n";
+ DriverStr(expected, "Addiu32");
+}
+
static uint64_t SignExtend16To64(uint16_t n) {
return static_cast<int16_t>(n);
}
diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc
index 85ae61f..e716cdb 100644
--- a/compiler/verifier_deps_test.cc
+++ b/compiler/verifier_deps_test.cc
@@ -1101,6 +1101,16 @@
"virtual", "Ljava/lang/Integer;", "intValue", "()I", true, "public", "Ljava/lang/Integer;"));
}
+TEST_F(VerifierDepsTest, ArgumentType_ResolvedReferenceArray) {
+ ASSERT_TRUE(VerifyMethod("ArgumentType_ResolvedReferenceArray"));
+ ASSERT_TRUE(HasClass("[Ljava/lang/Thread;", true, "public final abstract"));
+}
+
+TEST_F(VerifierDepsTest, NewArray_Resolved) {
+ ASSERT_TRUE(VerifyMethod("NewArray_Resolved"));
+ ASSERT_TRUE(HasClass("[Ljava/lang/IllegalStateException;", true, "public final abstract"));
+}
+
TEST_F(VerifierDepsTest, EncodeDecode) {
VerifyDexFile();
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index ef03bb3..96976d9 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -134,8 +134,7 @@
// NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here
// without accessing the DexCache and we don't want to do that in release build.
DCHECK_LT(method_index,
- GetInterfaceMethodIfProxy(pointer_size)->GetDeclaringClass()
- ->GetDexCache()->NumResolvedMethods());
+ GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods());
ArtMethod* method = mirror::DexCache::GetElementPtrSize(GetDexCacheResolvedMethods(pointer_size),
method_index,
pointer_size);
@@ -154,8 +153,7 @@
// NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here
// without accessing the DexCache and we don't want to do that in release build.
DCHECK_LT(method_index,
- GetInterfaceMethodIfProxy(pointer_size)->GetDeclaringClass()
- ->GetDexCache()->NumResolvedMethods());
+ GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods());
DCHECK(new_method == nullptr || new_method->GetDeclaringClass() != nullptr);
mirror::DexCache::SetElementPtrSize(GetDexCacheResolvedMethods(pointer_size),
method_index,
@@ -186,8 +184,7 @@
inline mirror::Class* ArtMethod::GetDexCacheResolvedType(dex::TypeIndex type_index,
PointerSize pointer_size) {
if (kWithCheck) {
- mirror::DexCache* dex_cache =
- GetInterfaceMethodIfProxy(pointer_size)->GetDeclaringClass()->GetDexCache();
+ mirror::DexCache* dex_cache = GetInterfaceMethodIfProxy(pointer_size)->GetDexCache();
if (UNLIKELY(type_index.index_ >= dex_cache->NumResolvedTypes())) {
ThrowArrayIndexOutOfBoundsException(type_index.index_, dex_cache->NumResolvedTypes());
return nullptr;
@@ -333,7 +330,7 @@
}
inline const DexFile::CodeItem* ArtMethod::GetCodeItem() {
- return GetDeclaringClass()->GetDexFile().GetCodeItem(GetCodeItemOffset());
+ return GetDexFile()->GetCodeItem(GetCodeItemOffset());
}
inline bool ArtMethod::IsResolvedTypeIdx(dex::TypeIndex type_idx, PointerSize pointer_size) {
@@ -398,11 +395,11 @@
}
inline mirror::DexCache* ArtMethod::GetDexCache() {
- DCHECK(!IsProxyMethod());
- if (UNLIKELY(IsObsolete())) {
- return GetObsoleteDexCache();
- } else {
+ if (LIKELY(!IsObsolete())) {
return GetDeclaringClass()->GetDexCache();
+ } else {
+ DCHECK(!IsProxyMethod());
+ return GetObsoleteDexCache();
}
}
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 3bc6f5d..b38508b 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -27,6 +27,7 @@
#include "invoke_type.h"
#include "method_reference.h"
#include "modifiers.h"
+#include "mirror/dex_cache.h"
#include "mirror/object.h"
#include "obj_ptr.h"
#include "read_barrier_option.h"
@@ -220,6 +221,12 @@
return !IsIntrinsic() && (GetAccessFlags() & kAccObsoleteMethod) != 0;
}
+ void SetIsObsolete() {
+ // TODO We should really support redefining intrinsic if possible.
+ DCHECK(!IsIntrinsic());
+ SetAccessFlags(GetAccessFlags() | kAccObsoleteMethod);
+ }
+
template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
bool IsNative() {
return (GetAccessFlags<kReadBarrierOption>() & kAccNative) != 0;
@@ -250,7 +257,6 @@
}
void SetSkipAccessChecks() {
- DCHECK(!SkipAccessChecks());
AddAccessFlags(kAccSkipAccessChecks);
}
@@ -326,6 +332,7 @@
ALWAYS_INLINE ArtMethod* GetDexCacheResolvedMethod(uint16_t method_index,
PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
+
ALWAYS_INLINE void SetDexCacheResolvedMethod(uint16_t method_index,
ArtMethod* new_method,
PointerSize pointer_size)
diff --git a/runtime/base/hash_set.h b/runtime/base/hash_set.h
index f24a862..a22efcf 100644
--- a/runtime/base/hash_set.h
+++ b/runtime/base/hash_set.h
@@ -672,6 +672,8 @@
T* data_; // Backing storage.
double min_load_factor_;
double max_load_factor_;
+
+ ART_FRIEND_TEST(InternTableTest, CrossHash);
};
template <class T, class EmptyFn, class HashFn, class Pred, class Alloc>
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 255ad71..2adeb8c 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -60,6 +60,7 @@
kUnexpectedSignalLock,
kThreadSuspendCountLock,
kAbortLock,
+ kJdwpAdbStateLock,
kJdwpSocketLock,
kRegionSpaceRegionLock,
kRosAllocGlobalLock,
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index a11257f..5fc5f1a 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -81,9 +81,6 @@
Handle<mirror::DexCache> dex_cache(hs.NewHandle(declaring_class->GetDexCache()));
const DexFile& dex_file = *dex_cache->GetDexFile();
string = ResolveString(dex_file, string_idx, dex_cache);
- if (string != nullptr) {
- DCHECK_EQ(dex_cache->GetResolvedString(string_idx), string);
- }
}
return string.Ptr();
}
@@ -192,20 +189,15 @@
return dex_cache->GetResolvedField(field_idx, image_pointer_size_);
}
-inline ArtField* ClassLinker::GetResolvedField(uint32_t field_idx,
- ObjPtr<mirror::Class> field_declaring_class) {
- return GetResolvedField(field_idx, MakeObjPtr(field_declaring_class->GetDexCache()));
-}
-
inline ArtField* ClassLinker::ResolveField(uint32_t field_idx,
ArtMethod* referrer,
bool is_static) {
Thread::PoisonObjectPointersIfDebug();
ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass();
- ArtField* resolved_field = GetResolvedField(field_idx, declaring_class);
+ ArtField* resolved_field = GetResolvedField(field_idx, referrer->GetDexCache());
if (UNLIKELY(resolved_field == nullptr)) {
StackHandleScope<2> hs(Thread::Current());
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(declaring_class->GetDexCache()));
+ Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache()));
Handle<mirror::ClassLoader> class_loader(hs.NewHandle(declaring_class->GetClassLoader()));
const DexFile& dex_file = *dex_cache->GetDexFile();
resolved_field = ResolveField(dex_file, field_idx, dex_cache, class_loader, is_static);
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 9b25303..6ef882a 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -327,8 +327,6 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
- ArtField* GetResolvedField(uint32_t field_idx, ObjPtr<mirror::Class> field_declaring_class)
- REQUIRES_SHARED(Locks::mutator_lock_);
ArtField* GetResolvedField(uint32_t field_idx, ObjPtr<mirror::DexCache> dex_cache)
REQUIRES_SHARED(Locks::mutator_lock_);
ArtField* ResolveField(uint32_t field_idx, ArtMethod* referrer, bool is_static)
diff --git a/runtime/common_dex_operations.h b/runtime/common_dex_operations.h
new file mode 100644
index 0000000..6693eef
--- /dev/null
+++ b/runtime/common_dex_operations.h
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_RUNTIME_COMMON_DEX_OPERATIONS_H_
+#define ART_RUNTIME_COMMON_DEX_OPERATIONS_H_
+
+#include "art_field.h"
+#include "art_method.h"
+#include "class_linker.h"
+#include "interpreter/unstarted_runtime.h"
+#include "runtime.h"
+#include "stack.h"
+#include "thread.h"
+
+namespace art {
+
+namespace interpreter {
+ void ArtInterpreterToInterpreterBridge(Thread* self,
+ const DexFile::CodeItem* code_item,
+ ShadowFrame* shadow_frame,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ void ArtInterpreterToCompiledCodeBridge(Thread* self,
+ ArtMethod* caller,
+ const DexFile::CodeItem* code_item,
+ ShadowFrame* shadow_frame,
+ JValue* result);
+} // namespace interpreter
+
+inline void PerformCall(Thread* self,
+ const DexFile::CodeItem* code_item,
+ ArtMethod* caller_method,
+ const size_t first_dest_reg,
+ ShadowFrame* callee_frame,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (LIKELY(Runtime::Current()->IsStarted())) {
+ ArtMethod* target = callee_frame->GetMethod();
+ if (ClassLinker::ShouldUseInterpreterEntrypoint(
+ target,
+ target->GetEntryPointFromQuickCompiledCode())) {
+ interpreter::ArtInterpreterToInterpreterBridge(self, code_item, callee_frame, result);
+ } else {
+ interpreter::ArtInterpreterToCompiledCodeBridge(
+ self, caller_method, code_item, callee_frame, result);
+ }
+ } else {
+ interpreter::UnstartedRuntime::Invoke(self, code_item, callee_frame, result, first_dest_reg);
+ }
+}
+
+template<Primitive::Type field_type>
+static ALWAYS_INLINE void DoFieldGetCommon(Thread* self,
+ const ShadowFrame& shadow_frame,
+ ObjPtr<mirror::Object> obj,
+ ArtField* field,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ field->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
+
+ // Report this field access to instrumentation if needed.
+ instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+ if (UNLIKELY(instrumentation->HasFieldReadListeners())) {
+ StackHandleScope<1> hs(self);
+ // Wrap in handle wrapper in case the listener does thread suspension.
+ HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj));
+ ObjPtr<mirror::Object> this_object;
+ if (!field->IsStatic()) {
+ this_object = obj;
+ }
+ instrumentation->FieldReadEvent(self,
+ this_object.Ptr(),
+ shadow_frame.GetMethod(),
+ shadow_frame.GetDexPC(),
+ field);
+ }
+
+ switch (field_type) {
+ case Primitive::kPrimBoolean:
+ result->SetZ(field->GetBoolean(obj));
+ break;
+ case Primitive::kPrimByte:
+ result->SetB(field->GetByte(obj));
+ break;
+ case Primitive::kPrimChar:
+ result->SetC(field->GetChar(obj));
+ break;
+ case Primitive::kPrimShort:
+ result->SetS(field->GetShort(obj));
+ break;
+ case Primitive::kPrimInt:
+ result->SetI(field->GetInt(obj));
+ break;
+ case Primitive::kPrimLong:
+ result->SetJ(field->GetLong(obj));
+ break;
+ case Primitive::kPrimNot:
+ result->SetL(field->GetObject(obj));
+ break;
+ case Primitive::kPrimVoid:
+ LOG(FATAL) << "Unreachable " << field_type;
+ break;
+ }
+}
+
+template<Primitive::Type field_type, bool do_assignability_check, bool transaction_active>
+ALWAYS_INLINE bool DoFieldPutCommon(Thread* self,
+ const ShadowFrame& shadow_frame,
+ ObjPtr<mirror::Object> obj,
+ ArtField* field,
+ const JValue& value)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ field->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
+
+ // Report this field access to instrumentation if needed. Since we only have the offset of
+ // the field from the base of the object, we need to look for it first.
+ instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+ if (UNLIKELY(instrumentation->HasFieldWriteListeners())) {
+ StackHandleScope<1> hs(self);
+ // Wrap in handle wrapper in case the listener does thread suspension.
+ HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj));
+ ObjPtr<mirror::Object> this_object = field->IsStatic() ? nullptr : obj;
+ instrumentation->FieldWriteEvent(self, this_object.Ptr(),
+ shadow_frame.GetMethod(),
+ shadow_frame.GetDexPC(),
+ field,
+ value);
+ }
+
+ switch (field_type) {
+ case Primitive::kPrimBoolean:
+ field->SetBoolean<transaction_active>(obj, value.GetZ());
+ break;
+ case Primitive::kPrimByte:
+ field->SetByte<transaction_active>(obj, value.GetB());
+ break;
+ case Primitive::kPrimChar:
+ field->SetChar<transaction_active>(obj, value.GetC());
+ break;
+ case Primitive::kPrimShort:
+ field->SetShort<transaction_active>(obj, value.GetS());
+ break;
+ case Primitive::kPrimInt:
+ field->SetInt<transaction_active>(obj, value.GetI());
+ break;
+ case Primitive::kPrimLong:
+ field->SetLong<transaction_active>(obj, value.GetJ());
+ break;
+ case Primitive::kPrimNot: {
+ ObjPtr<mirror::Object> reg = value.GetL();
+ if (do_assignability_check && reg != nullptr) {
+ // FieldHelper::GetType can resolve classes, use a handle wrapper which will restore the
+ // object in the destructor.
+ ObjPtr<mirror::Class> field_class;
+ {
+ StackHandleScope<2> hs(self);
+ HandleWrapperObjPtr<mirror::Object> h_reg(hs.NewHandleWrapper(®));
+ HandleWrapperObjPtr<mirror::Object> h_obj(hs.NewHandleWrapper(&obj));
+ field_class = field->GetType<true>();
+ }
+ if (!reg->VerifierInstanceOf(field_class.Ptr())) {
+ // This should never happen.
+ std::string temp1, temp2, temp3;
+ self->ThrowNewExceptionF("Ljava/lang/InternalError;",
+ "Put '%s' that is not instance of field '%s' in '%s'",
+ reg->GetClass()->GetDescriptor(&temp1),
+ field_class->GetDescriptor(&temp2),
+ field->GetDeclaringClass()->GetDescriptor(&temp3));
+ return false;
+ }
+ }
+ field->SetObj<transaction_active>(obj, reg);
+ break;
+ }
+ case Primitive::kPrimVoid: {
+ LOG(FATAL) << "Unreachable " << field_type;
+ break;
+ }
+ }
+ return true;
+}
+
+} // namespace art
+
+#endif // ART_RUNTIME_COMMON_DEX_OPERATIONS_H_
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index f6eeffc..14c9c21 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -563,7 +563,7 @@
HandleWrapperObjPtr<mirror::Object> h_this(hs2.NewHandleWrapper(this_object));
Handle<mirror::Class> h_referring_class(hs2.NewHandle(referrer->GetDeclaringClass()));
const dex::TypeIndex method_type_idx =
- h_referring_class->GetDexFile().GetMethodId(method_idx).class_idx_;
+ referrer->GetDexFile()->GetMethodId(method_idx).class_idx_;
mirror::Class* method_reference_class = class_linker->ResolveType(method_type_idx, referrer);
if (UNLIKELY(method_reference_class == nullptr)) {
// Bad type idx.
@@ -673,8 +673,7 @@
size_t expected_size) {
ScopedAssertNoThreadSuspension ants(__FUNCTION__);
ArtField* resolved_field =
- referrer->GetDeclaringClass()->GetDexCache()->GetResolvedField(field_idx,
- kRuntimePointerSize);
+ referrer->GetDexCache()->GetResolvedField(field_idx, kRuntimePointerSize);
if (UNLIKELY(resolved_field == nullptr)) {
return nullptr;
}
@@ -733,7 +732,7 @@
}
mirror::Class* referring_class = referrer->GetDeclaringClass();
ArtMethod* resolved_method =
- referring_class->GetDexCache()->GetResolvedMethod(method_idx, kRuntimePointerSize);
+ referrer->GetDexCache()->GetResolvedMethod(method_idx, kRuntimePointerSize);
if (UNLIKELY(resolved_method == nullptr)) {
return nullptr;
}
@@ -759,9 +758,9 @@
} else if (type == kSuper) {
// TODO This lookup is rather slow.
dex::TypeIndex method_type_idx =
- referring_class->GetDexFile().GetMethodId(method_idx).class_idx_;
+ referrer->GetDexFile()->GetMethodId(method_idx).class_idx_;
mirror::Class* method_reference_class =
- referring_class->GetDexCache()->GetResolvedType(method_type_idx);
+ referrer->GetDexCache()->GetResolvedType(method_type_idx);
if (method_reference_class == nullptr) {
// Need to do full type resolution...
return nullptr;
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 03ef962..4ea1130 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -557,8 +557,10 @@
}
Instrumentation::InstrumentationLevel Instrumentation::GetCurrentInstrumentationLevel() const {
- if (interpreter_stubs_installed_) {
+ if (interpreter_stubs_installed_ && interpret_only_) {
return InstrumentationLevel::kInstrumentWithInterpreter;
+ } else if (interpreter_stubs_installed_) {
+ return InstrumentationLevel::kInstrumentWithInterpreterAndJit;
} else if (entry_exit_stubs_installed_) {
return InstrumentationLevel::kInstrumentWithInstrumentationStubs;
} else {
@@ -566,6 +568,14 @@
}
}
+bool Instrumentation::RequiresInstrumentationInstallation(InstrumentationLevel new_level) const {
+ // We need to reinstall instrumentation if we go to a different level or if the current level is
+ // kInstrumentWithInterpreterAndJit since that level does not force all code to always use the
+ // interpreter and so we might have started running optimized code again.
+ return new_level == InstrumentationLevel::kInstrumentWithInterpreterAndJit ||
+ GetCurrentInstrumentationLevel() != new_level;
+}
+
void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desired_level) {
// Store the instrumentation level for this key or remove it.
if (desired_level == InstrumentationLevel::kInstrumentNothing) {
@@ -585,8 +595,7 @@
interpret_only_ = (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) ||
forced_interpret_only_;
- InstrumentationLevel current_level = GetCurrentInstrumentationLevel();
- if (requested_level == current_level) {
+ if (!RequiresInstrumentationInstallation(requested_level)) {
// We're already set.
return;
}
@@ -595,7 +604,7 @@
Locks::mutator_lock_->AssertExclusiveHeld(self);
Locks::thread_list_lock_->AssertNotHeld(self);
if (requested_level > InstrumentationLevel::kInstrumentNothing) {
- if (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) {
+ if (requested_level >= InstrumentationLevel::kInstrumentWithInterpreterAndJit) {
interpreter_stubs_installed_ = true;
entry_exit_stubs_installed_ = true;
} else {
@@ -842,7 +851,8 @@
void Instrumentation::DisableDeoptimization(const char* key) {
CHECK_EQ(deoptimization_enabled_, true);
// If we deoptimized everything, undo it.
- if (interpreter_stubs_installed_) {
+ InstrumentationLevel level = GetCurrentInstrumentationLevel();
+ if (level == InstrumentationLevel::kInstrumentWithInterpreter) {
UndeoptimizeEverything(key);
}
// Undeoptimized selected methods.
@@ -869,6 +879,14 @@
return !deoptimization_enabled_ && !interpreter_stubs_installed_;
}
+// TODO we don't check deoptimization_enabled_ because currently there isn't really any support for
+// multiple users of instrumentation. Since this is just a temporary state anyway pending work to
+// ensure that the current_method doesn't get kept across suspend points this should be okay.
+// TODO Remove once b/33630159 is resolved.
+void Instrumentation::ReJitEverything(const char* key) {
+ ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInterpreterAndJit);
+}
+
void Instrumentation::DeoptimizeEverything(const char* key) {
CHECK(deoptimization_enabled_);
ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInterpreter);
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 1e5fcf2..05c0aaa 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -133,6 +133,9 @@
enum class InstrumentationLevel {
kInstrumentNothing, // execute without instrumentation
kInstrumentWithInstrumentationStubs, // execute with instrumentation entry/exit stubs
+ kInstrumentWithInterpreterAndJit, // execute with interpreter initially and later the JIT
+ // (if it is enabled). This level is special in that it
+ // always requires re-instrumentation.
kInstrumentWithInterpreter // execute with interpreter
};
@@ -163,6 +166,13 @@
}
bool ShouldNotifyMethodEnterExitEvents() const REQUIRES_SHARED(Locks::mutator_lock_);
+ // Executes everything with the interpreter/jit (if available).
+ void ReJitEverything(const char* key)
+ REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
+ REQUIRES(!Locks::thread_list_lock_,
+ !Locks::classlinker_classes_lock_,
+ !deoptimized_methods_lock_);
+
// Executes everything with interpreter.
void DeoptimizeEverything(const char* key)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
@@ -432,9 +442,13 @@
return alloc_entrypoints_instrumented_;
}
- private:
InstrumentationLevel GetCurrentInstrumentationLevel() const;
+ private:
+ // Returns true if moving to the given instrumentation level requires the installation of stubs.
+ // False otherwise.
+ bool RequiresInstrumentationInstallation(InstrumentationLevel new_level) const;
+
// Does the job of installing or removing instrumentation code within methods.
// In order to support multiple clients using instrumentation at the same time,
// the caller must pass a unique key (a string) identifying it so we remind which
diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc
index 9c05d3c..3e19146 100644
--- a/runtime/intern_table.cc
+++ b/runtime/intern_table.cc
@@ -319,7 +319,9 @@
if (kIsDebugBuild) {
Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
}
- return static_cast<size_t>(root.Read<kWithoutReadBarrier>()->GetHashCode());
+ // An additional cast to prevent undesired sign extension.
+ return static_cast<size_t>(
+ static_cast<uint32_t>(root.Read<kWithoutReadBarrier>()->GetHashCode()));
}
bool InternTable::StringHashEquals::operator()(const GcRoot<mirror::String>& a,
diff --git a/runtime/intern_table.h b/runtime/intern_table.h
index f661d9f..68454fb 100644
--- a/runtime/intern_table.h
+++ b/runtime/intern_table.h
@@ -163,7 +163,11 @@
NO_THREAD_SAFETY_ANALYSIS;
// Utf8String can be used for lookup.
- std::size_t operator()(const Utf8String& key) const { return key.GetHash(); }
+ std::size_t operator()(const Utf8String& key) const {
+ // A cast to prevent undesired sign extension.
+ return static_cast<uint32_t>(key.GetHash());
+ }
+
bool operator()(const GcRoot<mirror::String>& a, const Utf8String& b) const
NO_THREAD_SAFETY_ANALYSIS;
};
@@ -217,6 +221,8 @@
// We call AddNewTable when we create the zygote to reduce private dirty pages caused by
// modifying the zygote intern table. The back of table is modified when strings are interned.
std::vector<UnorderedSet> tables_;
+
+ ART_FRIEND_TEST(InternTableTest, CrossHash);
};
// Insert if non null, otherwise return null. Must be called holding the mutator lock.
@@ -276,6 +282,7 @@
gc::WeakRootState weak_root_state_ GUARDED_BY(Locks::intern_table_lock_);
friend class Transaction;
+ ART_FRIEND_TEST(InternTableTest, CrossHash);
DISALLOW_COPY_AND_ASSIGN(InternTable);
};
diff --git a/runtime/intern_table_test.cc b/runtime/intern_table_test.cc
index b91d946..3991d65 100644
--- a/runtime/intern_table_test.cc
+++ b/runtime/intern_table_test.cc
@@ -16,6 +16,7 @@
#include "intern_table.h"
+#include "base/hash_set.h"
#include "common_runtime_test.h"
#include "mirror/object.h"
#include "handle_scope-inl.h"
@@ -62,6 +63,25 @@
EXPECT_EQ(2U, t.Size());
}
+// Check if table indexes match on 64 and 32 bit machines.
+// This is done by ensuring hash values are the same on every machine and limited to 32-bit wide.
+// Otherwise cross compilation can cause a table to be filled on host using one indexing algorithm
+// and later on a device with different sizeof(size_t) can use another indexing algorithm.
+// Thus the table may provide wrong data.
+TEST_F(InternTableTest, CrossHash) {
+ ScopedObjectAccess soa(Thread::Current());
+ InternTable t;
+
+ // A string that has a negative hash value.
+ GcRoot<mirror::String> str(mirror::String::AllocFromModifiedUtf8(soa.Self(), "00000000"));
+
+ MutexLock mu(Thread::Current(), *Locks::intern_table_lock_);
+ for (InternTable::Table::UnorderedSet& table : t.strong_interns_.tables_) {
+ // The negative hash value shall be 32-bit wide on every host.
+ ASSERT_TRUE(IsUint<32>(table.hashfn_(str)));
+ }
+}
+
class TestPredicate : public IsMarkedVisitor {
public:
mirror::Object* IsMarked(mirror::Object* s) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index a09e71b..ca26207 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -32,7 +32,6 @@
#include "reflection.h"
#include "reflection-inl.h"
#include "stack.h"
-#include "unstarted_runtime.h"
#include "verifier/method_verifier.h"
#include "well_known_classes.h"
@@ -43,60 +42,6 @@
ThrowNullPointerExceptionFromDexPC();
}
-template<Primitive::Type field_type>
-static ALWAYS_INLINE void DoFieldGetCommon(Thread* self,
- const ShadowFrame& shadow_frame,
- ObjPtr<mirror::Object>& obj,
- ArtField* field,
- JValue* result)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- field->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
-
- // Report this field access to instrumentation if needed.
- instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
- if (UNLIKELY(instrumentation->HasFieldReadListeners())) {
- StackHandleScope<1> hs(self);
- // Wrap in handle wrapper in case the listener does thread suspension.
- HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj));
- ObjPtr<mirror::Object> this_object;
- if (!field->IsStatic()) {
- this_object = obj;
- }
- instrumentation->FieldReadEvent(self,
- this_object.Ptr(),
- shadow_frame.GetMethod(),
- shadow_frame.GetDexPC(),
- field);
- }
-
- switch (field_type) {
- case Primitive::kPrimBoolean:
- result->SetZ(field->GetBoolean(obj));
- break;
- case Primitive::kPrimByte:
- result->SetB(field->GetByte(obj));
- break;
- case Primitive::kPrimChar:
- result->SetC(field->GetChar(obj));
- break;
- case Primitive::kPrimShort:
- result->SetS(field->GetShort(obj));
- break;
- case Primitive::kPrimInt:
- result->SetI(field->GetInt(obj));
- break;
- case Primitive::kPrimLong:
- result->SetJ(field->GetLong(obj));
- break;
- case Primitive::kPrimNot:
- result->SetL(field->GetObject(obj));
- break;
- default:
- LOG(FATAL) << "Unreachable: " << field_type;
- UNREACHABLE();
- }
-}
-
template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check>
bool DoFieldGet(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,
uint16_t inst_data) {
@@ -184,48 +129,6 @@
#undef EXPLICIT_DO_FIELD_GET_ALL_TEMPLATE_DECL
#undef EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL
-// Helper for getters in invoke-polymorphic.
-inline static void DoFieldGetForInvokePolymorphic(Thread* self,
- const ShadowFrame& shadow_frame,
- ObjPtr<mirror::Object>& obj,
- ArtField* field,
- Primitive::Type field_type,
- JValue* result)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- switch (field_type) {
- case Primitive::kPrimBoolean:
- DoFieldGetCommon<Primitive::kPrimBoolean>(self, shadow_frame, obj, field, result);
- break;
- case Primitive::kPrimByte:
- DoFieldGetCommon<Primitive::kPrimByte>(self, shadow_frame, obj, field, result);
- break;
- case Primitive::kPrimChar:
- DoFieldGetCommon<Primitive::kPrimChar>(self, shadow_frame, obj, field, result);
- break;
- case Primitive::kPrimShort:
- DoFieldGetCommon<Primitive::kPrimShort>(self, shadow_frame, obj, field, result);
- break;
- case Primitive::kPrimInt:
- DoFieldGetCommon<Primitive::kPrimInt>(self, shadow_frame, obj, field, result);
- break;
- case Primitive::kPrimLong:
- DoFieldGetCommon<Primitive::kPrimLong>(self, shadow_frame, obj, field, result);
- break;
- case Primitive::kPrimFloat:
- DoFieldGetCommon<Primitive::kPrimInt>(self, shadow_frame, obj, field, result);
- break;
- case Primitive::kPrimDouble:
- DoFieldGetCommon<Primitive::kPrimLong>(self, shadow_frame, obj, field, result);
- break;
- case Primitive::kPrimNot:
- DoFieldGetCommon<Primitive::kPrimNot>(self, shadow_frame, obj, field, result);
- break;
- case Primitive::kPrimVoid:
- LOG(FATAL) << "Unreachable: " << field_type;
- UNREACHABLE();
- }
-}
-
// Handles iget-quick, iget-wide-quick and iget-object-quick instructions.
// Returns true on success, otherwise throws an exception and returns false.
template<Primitive::Type field_type>
@@ -300,42 +203,6 @@
EXPLICIT_DO_IGET_QUICK_TEMPLATE_DECL(Primitive::kPrimNot); // iget-object-quick.
#undef EXPLICIT_DO_IGET_QUICK_TEMPLATE_DECL
-static JValue GetFieldValue(const ShadowFrame& shadow_frame,
- Primitive::Type field_type,
- uint32_t vreg)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- JValue field_value;
- switch (field_type) {
- case Primitive::kPrimBoolean:
- field_value.SetZ(static_cast<uint8_t>(shadow_frame.GetVReg(vreg)));
- break;
- case Primitive::kPrimByte:
- field_value.SetB(static_cast<int8_t>(shadow_frame.GetVReg(vreg)));
- break;
- case Primitive::kPrimChar:
- field_value.SetC(static_cast<uint16_t>(shadow_frame.GetVReg(vreg)));
- break;
- case Primitive::kPrimShort:
- field_value.SetS(static_cast<int16_t>(shadow_frame.GetVReg(vreg)));
- break;
- case Primitive::kPrimInt:
- case Primitive::kPrimFloat:
- field_value.SetI(shadow_frame.GetVReg(vreg));
- break;
- case Primitive::kPrimLong:
- case Primitive::kPrimDouble:
- field_value.SetJ(shadow_frame.GetVRegLong(vreg));
- break;
- case Primitive::kPrimNot:
- field_value.SetL(shadow_frame.GetVRegReference(vreg));
- break;
- case Primitive::kPrimVoid:
- LOG(FATAL) << "Unreachable: " << field_type;
- UNREACHABLE();
- }
- return field_value;
-}
-
template<Primitive::Type field_type>
static JValue GetFieldValue(const ShadowFrame& shadow_frame, uint32_t vreg)
REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -369,82 +236,6 @@
return field_value;
}
-template<Primitive::Type field_type, bool do_assignability_check, bool transaction_active>
-static inline bool DoFieldPutCommon(Thread* self,
- const ShadowFrame& shadow_frame,
- ObjPtr<mirror::Object>& obj,
- ArtField* f,
- const JValue& value)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- f->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
-
- // Report this field access to instrumentation if needed. Since we only have the offset of
- // the field from the base of the object, we need to look for it first.
- instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
- if (UNLIKELY(instrumentation->HasFieldWriteListeners())) {
- StackHandleScope<1> hs(self);
- // Wrap in handle wrapper in case the listener does thread suspension.
- HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj));
- ObjPtr<mirror::Object> this_object = f->IsStatic() ? nullptr : obj;
- instrumentation->FieldWriteEvent(self, this_object.Ptr(),
- shadow_frame.GetMethod(),
- shadow_frame.GetDexPC(),
- f,
- value);
- }
-
- switch (field_type) {
- case Primitive::kPrimBoolean:
- f->SetBoolean<transaction_active>(obj, value.GetZ());
- break;
- case Primitive::kPrimByte:
- f->SetByte<transaction_active>(obj, value.GetB());
- break;
- case Primitive::kPrimChar:
- f->SetChar<transaction_active>(obj, value.GetC());
- break;
- case Primitive::kPrimShort:
- f->SetShort<transaction_active>(obj, value.GetS());
- break;
- case Primitive::kPrimInt:
- f->SetInt<transaction_active>(obj, value.GetI());
- break;
- case Primitive::kPrimLong:
- f->SetLong<transaction_active>(obj, value.GetJ());
- break;
- case Primitive::kPrimNot: {
- ObjPtr<mirror::Object> reg = value.GetL();
- if (do_assignability_check && reg != nullptr) {
- // FieldHelper::GetType can resolve classes, use a handle wrapper which will restore the
- // object in the destructor.
- ObjPtr<mirror::Class> field_class;
- {
- StackHandleScope<2> hs(self);
- HandleWrapperObjPtr<mirror::Object> h_reg(hs.NewHandleWrapper(®));
- HandleWrapperObjPtr<mirror::Object> h_obj(hs.NewHandleWrapper(&obj));
- field_class = f->GetType<true>();
- }
- if (!reg->VerifierInstanceOf(field_class.Ptr())) {
- // This should never happen.
- std::string temp1, temp2, temp3;
- self->ThrowNewExceptionF("Ljava/lang/InternalError;",
- "Put '%s' that is not instance of field '%s' in '%s'",
- reg->GetClass()->GetDescriptor(&temp1),
- field_class->GetDescriptor(&temp2),
- f->GetDeclaringClass()->GetDescriptor(&temp3));
- return false;
- }
- }
- f->SetObj<transaction_active>(obj, reg);
- break;
- }
- default:
- LOG(FATAL) << "Unreachable: " << field_type;
- UNREACHABLE();
- }
- return true;
-}
-
template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check,
bool transaction_active>
bool DoFieldPut(Thread* self, const ShadowFrame& shadow_frame, const Instruction* inst,
@@ -511,46 +302,6 @@
#undef EXPLICIT_DO_FIELD_PUT_ALL_TEMPLATE_DECL
#undef EXPLICIT_DO_FIELD_PUT_TEMPLATE_DECL
-// Helper for setters in invoke-polymorphic.
-bool DoFieldPutForInvokePolymorphic(Thread* self,
- ShadowFrame& shadow_frame,
- ObjPtr<mirror::Object>& obj,
- ArtField* field,
- Primitive::Type field_type,
- const JValue& value)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- static const bool kDoCheckAssignability = false;
- static const bool kTransaction = false;
- switch (field_type) {
- case Primitive::kPrimBoolean:
- return DoFieldPutCommon<Primitive::kPrimBoolean, kDoCheckAssignability, kTransaction>(
- self, shadow_frame, obj, field, value);
- case Primitive::kPrimByte:
- return DoFieldPutCommon<Primitive::kPrimByte, kDoCheckAssignability, kTransaction>(
- self, shadow_frame, obj, field, value);
- case Primitive::kPrimChar:
- return DoFieldPutCommon<Primitive::kPrimChar, kDoCheckAssignability, kTransaction>(
- self, shadow_frame, obj, field, value);
- case Primitive::kPrimShort:
- return DoFieldPutCommon<Primitive::kPrimShort, kDoCheckAssignability, kTransaction>(
- self, shadow_frame, obj, field, value);
- case Primitive::kPrimInt:
- case Primitive::kPrimFloat:
- return DoFieldPutCommon<Primitive::kPrimInt, kDoCheckAssignability, kTransaction>(
- self, shadow_frame, obj, field, value);
- case Primitive::kPrimLong:
- case Primitive::kPrimDouble:
- return DoFieldPutCommon<Primitive::kPrimLong, kDoCheckAssignability, kTransaction>(
- self, shadow_frame, obj, field, value);
- case Primitive::kPrimNot:
- return DoFieldPutCommon<Primitive::kPrimNot, kDoCheckAssignability, kTransaction>(
- self, shadow_frame, obj, field, value);
- case Primitive::kPrimVoid:
- LOG(FATAL) << "Unreachable: " << field_type;
- UNREACHABLE();
- }
-}
-
template<Primitive::Type field_type, bool transaction_active>
bool DoIPutQuick(const ShadowFrame& shadow_frame, const Instruction* inst, uint16_t inst_data) {
ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
@@ -697,36 +448,6 @@
uint32_t vregC) REQUIRES_SHARED(Locks::mutator_lock_);
template <bool is_range>
-static ALWAYS_INLINE bool DoCallPolymorphic(ArtMethod* called_method,
- Handle<mirror::MethodType> callsite_type,
- Handle<mirror::MethodType> target_type,
- Thread* self,
- ShadowFrame& shadow_frame,
- JValue* result,
- uint32_t (&arg)[Instruction::kMaxVarArgRegs],
- uint32_t vregC,
- const MethodHandleKind handle_kind)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
-template <bool is_range>
-static ALWAYS_INLINE bool DoCallTransform(ArtMethod* called_method,
- Handle<mirror::MethodType> callsite_type,
- Handle<mirror::MethodType> callee_type,
- Thread* self,
- ShadowFrame& shadow_frame,
- Handle<mirror::MethodHandleImpl> receiver,
- JValue* result,
- uint32_t (&arg)[Instruction::kMaxVarArgRegs],
- uint32_t vregC) REQUIRES_SHARED(Locks::mutator_lock_);
-
-ALWAYS_INLINE void PerformCall(Thread* self,
- const DexFile::CodeItem* code_item,
- ArtMethod* caller_method,
- const size_t first_dest_reg,
- ShadowFrame* callee_frame,
- JValue* result) REQUIRES_SHARED(Locks::mutator_lock_);
-
-template <bool is_range>
ALWAYS_INLINE void CopyRegisters(ShadowFrame& caller_frame,
ShadowFrame* callee_frame,
const uint32_t (&arg)[Instruction::kMaxVarArgRegs],
@@ -798,55 +519,12 @@
}
}
-inline static bool IsInvokeExact(const DexFile& dex_file, int invoke_method_idx) {
- // This check uses string comparison as it needs less code and data
- // to do than fetching the associated ArtMethod from the DexCache
- // and checking against ArtMethods in the well known classes. The
- // verifier needs to perform a more rigorous check.
- const char* method_name = dex_file.GetMethodName(dex_file.GetMethodId(invoke_method_idx));
- bool is_invoke_exact = (0 == strcmp(method_name, "invokeExact"));
- DCHECK(is_invoke_exact || (0 == strcmp(method_name, "invoke")));
- return is_invoke_exact;
-}
-
-inline static ObjPtr<mirror::Class> GetAndInitializeDeclaringClass(Thread* self, ArtField* field)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- // Method handle invocations on static fields should ensure class is
- // initialized. This usually happens when an instance is constructed
- // or class members referenced, but this is not guaranteed when
- // looking up method handles.
- ObjPtr<mirror::Class> klass = field->GetDeclaringClass();
- if (UNLIKELY(!klass->IsInitialized())) {
- StackHandleScope<1> hs(self);
- HandleWrapperObjPtr<mirror::Class> h(hs.NewHandleWrapper(&klass));
- if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, h, true, true)) {
- DCHECK(self->IsExceptionPending());
- return nullptr;
- }
- }
- return klass;
-}
-
-// Returns true iff. the callsite type for a polymorphic invoke is transformer
-// like, i.e that it has a single input argument whose type is
-// dalvik.system.EmulatedStackFrame.
-static inline bool IsCallerTransformer(Handle<mirror::MethodType> callsite_type)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::ObjectArray<mirror::Class>> param_types(callsite_type->GetPTypes());
- if (param_types->GetLength() == 1) {
- ObjPtr<mirror::Class> param(param_types->GetWithoutChecks(0));
- return param == WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_EmulatedStackFrame);
- }
-
- return false;
-}
-
template<bool is_range, bool do_access_check>
-inline bool DoInvokePolymorphic(Thread* self,
- ShadowFrame& shadow_frame,
- const Instruction* inst,
- uint16_t inst_data,
- JValue* result)
+bool DoInvokePolymorphic(Thread* self,
+ ShadowFrame& shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data,
+ JValue* result)
REQUIRES_SHARED(Locks::mutator_lock_) {
// Invoke-polymorphic instructions always take a receiver. i.e, they are never static.
const uint32_t vRegC = (is_range) ? inst->VRegC_4rcc() : inst->VRegC_45cc();
@@ -857,15 +535,10 @@
// and provides sane return result in error cases.
result->SetJ(0);
- // Determine if this invocation is MethodHandle.invoke() or
- // MethodHandle.invokeExact().
- bool is_invoke_exact = IsInvokeExact(shadow_frame.GetMethod()->GetDeclaringClass()->GetDexFile(),
- invoke_method_idx);
-
// The invoke_method_idx here is the name of the signature polymorphic method that
// was symbolically invoked in bytecode (say MethodHandle.invoke or MethodHandle.invokeExact)
// and not the method that we'll dispatch to in the end.
- StackHandleScope<6> hs(self);
+ StackHandleScope<5> hs(self);
Handle<mirror::MethodHandleImpl> method_handle(hs.NewHandle(
ObjPtr<mirror::MethodHandleImpl>::DownCast(
MakeObjPtr(shadow_frame.GetVRegReference(vRegC)))));
@@ -877,7 +550,7 @@
}
// The vRegH value gives the index of the proto_id associated with this
- // signature polymorphic callsite.
+ // signature polymorphic call site.
const uint32_t callsite_proto_id = (is_range) ? inst->VRegH_4rcc() : inst->VRegH_45cc();
// Call through to the classlinker and ask it to resolve the static type associated
@@ -896,224 +569,43 @@
return false;
}
- const MethodHandleKind handle_kind = method_handle->GetHandleKind();
- Handle<mirror::MethodType> handle_type(hs.NewHandle(method_handle->GetMethodType()));
- CHECK(handle_type.Get() != nullptr);
- {
- // We need to check the nominal type of the handle in addition to the
- // real type. The "nominal" type is present when MethodHandle.asType is
- // called any handle, and results in the declared type of the handle
- // changing.
- ObjPtr<mirror::MethodType> nominal_type(method_handle->GetNominalType());
- ObjPtr<mirror::MethodType> check_type(nullptr);
- if (LIKELY(nominal_type.Ptr() == nullptr)) {
- check_type.Assign(handle_type.Get());
- } else {
- check_type.Assign(nominal_type.Ptr());
- }
+ ArtMethod* invoke_method =
+ class_linker->ResolveMethod<ClassLinker::kForceICCECheck>(self,
+ invoke_method_idx,
+ shadow_frame.GetMethod(),
+ kVirtual);
- if (is_invoke_exact) {
- if (UNLIKELY(!callsite_type->IsExactMatch(check_type.Ptr()))) {
- ThrowWrongMethodTypeException(check_type.Ptr(), callsite_type.Get());
- return false;
- }
- } else if (!IsInvokeTransform(handle_kind)) {
- if (UNLIKELY(!IsCallerTransformer(callsite_type) &&
- !callsite_type->IsConvertible(check_type.Ptr()))) {
- ThrowWrongMethodTypeException(check_type.Ptr(), callsite_type.Get());
- return false;
- }
- }
- }
-
- uint32_t arg[Instruction::kMaxVarArgRegs] = {};
- uint32_t first_src_reg = 0;
+ // There is a common dispatch method for method handles that takes
+ // arguments either from a range or an array of arguments depending
+ // on whether the DEX instruction is invoke-polymorphic/range or
+ // invoke-polymorphic. The array here is for the latter.
+ uint32_t args[Instruction::kMaxVarArgRegs] = {};
if (is_range) {
- first_src_reg = (inst->VRegC_4rcc() + 1);
+ // VRegC is the register holding the method handle. Arguments passed
+ // to the method handle's target do not include the method handle.
+ uint32_t first_arg = inst->VRegC_4rcc() + 1;
+ return DoInvokePolymorphic<is_range, do_access_check>(self,
+ invoke_method,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args /* unused */,
+ first_arg,
+ result);
} else {
- inst->GetVarArgs(arg, inst_data);
- arg[0] = arg[1];
- arg[1] = arg[2];
- arg[2] = arg[3];
- arg[3] = arg[4];
- arg[4] = 0;
- first_src_reg = arg[0];
- }
-
- if (IsInvoke(handle_kind)) {
- // Get the method we're actually invoking along with the kind of
- // invoke that is desired. We don't need to perform access checks at this
- // point because they would have been performed on our behalf at the point
- // of creation of the method handle.
- ArtMethod* called_method = method_handle->GetTargetMethod();
- CHECK(called_method != nullptr);
-
- if (handle_kind == kInvokeVirtual || handle_kind == kInvokeInterface) {
- // TODO: Unfortunately, we have to postpone dynamic receiver based checks
- // because the receiver might be cast or might come from an emulated stack
- // frame, which means that it is unknown at this point. We perform these
- // checks inside DoCallPolymorphic right before we do the actual invoke.
- } else if (handle_kind == kInvokeDirect) {
- // String constructors are a special case, they are replaced with StringFactory
- // methods.
- if (called_method->IsConstructor() && called_method->GetDeclaringClass()->IsStringClass()) {
- DCHECK(handle_type->GetRType()->IsStringClass());
- called_method = WellKnownClasses::StringInitToStringFactory(called_method);
- }
- } else if (handle_kind == kInvokeSuper) {
- ObjPtr<mirror::Class> declaring_class = called_method->GetDeclaringClass();
-
- // Note that we're not dynamically dispatching on the type of the receiver
- // here. We use the static type of the "receiver" object that we've
- // recorded in the method handle's type, which will be the same as the
- // special caller that was specified at the point of lookup.
- ObjPtr<mirror::Class> referrer_class = handle_type->GetPTypes()->Get(0);
- if (!declaring_class->IsInterface()) {
- ObjPtr<mirror::Class> super_class = referrer_class->GetSuperClass();
- uint16_t vtable_index = called_method->GetMethodIndex();
- DCHECK(super_class != nullptr);
- DCHECK(super_class->HasVTable());
- // Note that super_class is a super of referrer_class and called_method
- // will always be declared by super_class (or one of its super classes).
- DCHECK_LT(vtable_index, super_class->GetVTableLength());
- called_method = super_class->GetVTableEntry(vtable_index, kRuntimePointerSize);
- } else {
- called_method = referrer_class->FindVirtualMethodForInterfaceSuper(
- called_method, kRuntimePointerSize);
- }
-
- CHECK(called_method != nullptr);
- }
-
- if (IsInvokeTransform(handle_kind)) {
- // There are two cases here - method handles representing regular
- // transforms and those representing call site transforms. Method
- // handles for call site transforms adapt their MethodType to match
- // the call site. For these, the |callee_type| is the same as the
- // |callsite_type|. The VarargsCollector is such a tranform, its
- // method type depends on the call site, ie. x(a) or x(a, b), or
- // x(a, b, c). The VarargsCollector invokes a variable arity method
- // with the arity arguments in an array.
- Handle<mirror::MethodType> callee_type =
- (handle_kind == kInvokeCallSiteTransform) ? callsite_type : handle_type;
- return DoCallTransform<is_range>(called_method,
- callsite_type,
- callee_type,
- self,
- shadow_frame,
- method_handle /* receiver */,
- result,
- arg,
- first_src_reg);
- } else {
- return DoCallPolymorphic<is_range>(called_method,
- callsite_type,
- handle_type,
- self,
- shadow_frame,
- result,
- arg,
- first_src_reg,
- handle_kind);
- }
- } else {
- DCHECK(!is_range);
- ArtField* field = method_handle->GetTargetField();
- Primitive::Type field_type = field->GetTypeAsPrimitiveType();
-
- switch (handle_kind) {
- case kInstanceGet: {
- ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(first_src_reg);
- DoFieldGetForInvokePolymorphic(self, shadow_frame, obj, field, field_type, result);
- if (!ConvertReturnValue(callsite_type, handle_type, result)) {
- DCHECK(self->IsExceptionPending());
- return false;
- }
- return true;
- }
- case kStaticGet: {
- ObjPtr<mirror::Object> obj = GetAndInitializeDeclaringClass(self, field);
- if (obj == nullptr) {
- DCHECK(self->IsExceptionPending());
- return false;
- }
- DoFieldGetForInvokePolymorphic(self, shadow_frame, obj, field, field_type, result);
- if (!ConvertReturnValue(callsite_type, handle_type, result)) {
- DCHECK(self->IsExceptionPending());
- return false;
- }
- return true;
- }
- case kInstancePut: {
- JValue value = GetFieldValue(shadow_frame, field_type, arg[1]);
- if (!ConvertArgumentValue(callsite_type, handle_type, 1, &value)) {
- DCHECK(self->IsExceptionPending());
- return false;
- }
- ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(first_src_reg);
- return DoFieldPutForInvokePolymorphic(self, shadow_frame, obj, field, field_type, value);
- }
- case kStaticPut: {
- JValue value = GetFieldValue(shadow_frame, field_type, arg[0]);
- if (!ConvertArgumentValue(callsite_type, handle_type, 0, &value)) {
- DCHECK(self->IsExceptionPending());
- return false;
- }
- ObjPtr<mirror::Object> obj = field->GetDeclaringClass();
- return DoFieldPutForInvokePolymorphic(self, shadow_frame, obj, field, field_type, value);
- }
- default:
- LOG(FATAL) << "Unreachable: " << handle_kind;
- UNREACHABLE();
- }
- }
-}
-
-// Calculate the number of ins for a proxy or native method, where we
-// can't just look at the code item.
-static inline size_t GetInsForProxyOrNativeMethod(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(method->IsNative() || method->IsProxyMethod());
-
- method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
- size_t num_ins = 0;
- // Separate accounting for the receiver, which isn't a part of the
- // shorty.
- if (!method->IsStatic()) {
- ++num_ins;
- }
-
- uint32_t shorty_len = 0;
- const char* shorty = method->GetShorty(&shorty_len);
- for (size_t i = 1; i < shorty_len; ++i) {
- const char c = shorty[i];
- ++num_ins;
- if (c == 'J' || c == 'D') {
- ++num_ins;
- }
- }
-
- return num_ins;
-}
-
-inline void PerformCall(Thread* self,
- const DexFile::CodeItem* code_item,
- ArtMethod* caller_method,
- const size_t first_dest_reg,
- ShadowFrame* callee_frame,
- JValue* result) {
- if (LIKELY(Runtime::Current()->IsStarted())) {
- ArtMethod* target = callee_frame->GetMethod();
- if (ClassLinker::ShouldUseInterpreterEntrypoint(
- target,
- target->GetEntryPointFromQuickCompiledCode())) {
- ArtInterpreterToInterpreterBridge(self, code_item, callee_frame, result);
- } else {
- ArtInterpreterToCompiledCodeBridge(
- self, caller_method, code_item, callee_frame, result);
- }
- } else {
- UnstartedRuntime::Invoke(self, code_item, callee_frame, result, first_dest_reg);
+ // Get the register arguments for the invoke.
+ inst->GetVarArgs(args, inst_data);
+ // Drop the first register which is the method handle performing the invoke.
+ memcpy(args, args + 1, sizeof(args[0]) * (Instruction::kMaxVarArgRegs - 1));
+ args[Instruction::kMaxVarArgRegs - 1] = 0;
+ return DoInvokePolymorphic<is_range, do_access_check>(self,
+ invoke_method,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ args[0],
+ result);
}
}
@@ -1139,217 +631,6 @@
}
}
-template <bool is_range>
-static inline bool DoCallPolymorphic(ArtMethod* called_method,
- Handle<mirror::MethodType> callsite_type,
- Handle<mirror::MethodType> target_type,
- Thread* self,
- ShadowFrame& shadow_frame,
- JValue* result,
- uint32_t (&arg)[Instruction::kMaxVarArgRegs],
- uint32_t first_src_reg,
- const MethodHandleKind handle_kind) {
- // Compute method information.
- const DexFile::CodeItem* code_item = called_method->GetCodeItem();
-
- // Number of registers for the callee's call frame. Note that for non-exact
- // invokes, we always derive this information from the callee method. We
- // cannot guarantee during verification that the number of registers encoded
- // in the invoke is equal to the number of ins for the callee. This is because
- // some transformations (such as boxing a long -> Long or wideining an
- // int -> long will change that number.
- uint16_t num_regs;
- size_t num_input_regs;
- size_t first_dest_reg;
- if (LIKELY(code_item != nullptr)) {
- num_regs = code_item->registers_size_;
- first_dest_reg = num_regs - code_item->ins_size_;
- num_input_regs = code_item->ins_size_;
- // Parameter registers go at the end of the shadow frame.
- DCHECK_NE(first_dest_reg, (size_t)-1);
- } else {
- // No local regs for proxy and native methods.
- DCHECK(called_method->IsNative() || called_method->IsProxyMethod());
- num_regs = num_input_regs = GetInsForProxyOrNativeMethod(called_method);
- first_dest_reg = 0;
- }
-
- // Allocate shadow frame on the stack.
- ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
- CREATE_SHADOW_FRAME(num_regs, &shadow_frame, called_method, /* dex pc */ 0);
- ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
-
- // Whether this polymorphic invoke was issued by a transformer method.
- bool is_caller_transformer = false;
- // Thread might be suspended during PerformArgumentConversions due to the
- // allocations performed during boxing.
- {
- ScopedStackedShadowFramePusher pusher(
- self, new_shadow_frame, StackedShadowFrameType::kShadowFrameUnderConstruction);
- if (callsite_type->IsExactMatch(target_type.Get())) {
- // This is an exact invoke, we can take the fast path of just copying all
- // registers without performing any argument conversions.
- CopyRegisters<is_range>(shadow_frame,
- new_shadow_frame,
- arg,
- first_src_reg,
- first_dest_reg,
- num_input_regs);
- } else {
- // This includes the case where we're entering this invoke-polymorphic
- // from a transformer method. In that case, the callsite_type will contain
- // a single argument of type dalvik.system.EmulatedStackFrame. In that
- // case, we'll have to unmarshal the EmulatedStackFrame into the
- // new_shadow_frame and perform argument conversions on it.
- if (IsCallerTransformer(callsite_type)) {
- is_caller_transformer = true;
- // The emulated stack frame is the first and only argument when we're coming
- // through from a transformer.
- ObjPtr<mirror::EmulatedStackFrame> emulated_stack_frame(
- reinterpret_cast<mirror::EmulatedStackFrame*>(
- shadow_frame.GetVRegReference(first_src_reg)));
- if (!emulated_stack_frame->WriteToShadowFrame(self,
- target_type,
- first_dest_reg,
- new_shadow_frame)) {
- DCHECK(self->IsExceptionPending());
- result->SetL(0);
- return false;
- }
- } else if (!ConvertAndCopyArgumentsFromCallerFrame<is_range>(self,
- callsite_type,
- target_type,
- shadow_frame,
- first_src_reg,
- first_dest_reg,
- arg,
- new_shadow_frame)) {
- DCHECK(self->IsExceptionPending());
- result->SetL(0);
- return false;
- }
- }
- }
-
- // See TODO in DoInvokePolymorphic : We need to perform this dynamic, receiver
- // based dispatch right before we perform the actual call, because the
- // receiver isn't known very early.
- if (handle_kind == kInvokeVirtual || handle_kind == kInvokeInterface) {
- ObjPtr<mirror::Object> receiver(new_shadow_frame->GetVRegReference(first_dest_reg));
- ObjPtr<mirror::Class> declaring_class(called_method->GetDeclaringClass());
- // Verify that _vRegC is an object reference and of the type expected by
- // the receiver.
- if (!VerifyObjectIsClass(receiver, declaring_class)) {
- DCHECK(self->IsExceptionPending());
- return false;
- }
-
- called_method = receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(
- called_method, kRuntimePointerSize);
- }
-
- PerformCall(self, code_item, shadow_frame.GetMethod(), first_dest_reg, new_shadow_frame, result);
- if (self->IsExceptionPending()) {
- return false;
- }
-
- // If the caller of this signature polymorphic method was a transformer,
- // we need to copy the result back out to the emulated stack frame.
- if (is_caller_transformer) {
- StackHandleScope<2> hs(self);
- Handle<mirror::EmulatedStackFrame> emulated_stack_frame(
- hs.NewHandle(reinterpret_cast<mirror::EmulatedStackFrame*>(
- shadow_frame.GetVRegReference(first_src_reg))));
- Handle<mirror::MethodType> emulated_stack_type(hs.NewHandle(emulated_stack_frame->GetType()));
- JValue local_result;
- local_result.SetJ(result->GetJ());
-
- if (ConvertReturnValue(emulated_stack_type, target_type, &local_result)) {
- emulated_stack_frame->SetReturnValue(self, local_result);
- return true;
- } else {
- DCHECK(self->IsExceptionPending());
- return false;
- }
- } else {
- return ConvertReturnValue(callsite_type, target_type, result);
- }
-}
-
-template <bool is_range>
-static inline bool DoCallTransform(ArtMethod* called_method,
- Handle<mirror::MethodType> callsite_type,
- Handle<mirror::MethodType> callee_type,
- Thread* self,
- ShadowFrame& shadow_frame,
- Handle<mirror::MethodHandleImpl> receiver,
- JValue* result,
- uint32_t (&arg)[Instruction::kMaxVarArgRegs],
- uint32_t first_src_reg) {
- // This can be fixed to two, because the method we're calling here
- // (MethodHandle.transformInternal) doesn't have any locals and the signature
- // is known :
- //
- // private MethodHandle.transformInternal(EmulatedStackFrame sf);
- //
- // This means we need only two vregs :
- // - One for the receiver object.
- // - One for the only method argument (an EmulatedStackFrame).
- static constexpr size_t kNumRegsForTransform = 2;
-
- const DexFile::CodeItem* code_item = called_method->GetCodeItem();
- DCHECK(code_item != nullptr);
- DCHECK_EQ(kNumRegsForTransform, code_item->registers_size_);
- DCHECK_EQ(kNumRegsForTransform, code_item->ins_size_);
-
- ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
- CREATE_SHADOW_FRAME(kNumRegsForTransform, &shadow_frame, called_method, /* dex pc */ 0);
- ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
-
- StackHandleScope<1> hs(self);
- MutableHandle<mirror::EmulatedStackFrame> sf(hs.NewHandle<mirror::EmulatedStackFrame>(nullptr));
- if (IsCallerTransformer(callsite_type)) {
- // If we're entering this transformer from another transformer, we can pass
- // through the handle directly to the callee, instead of having to
- // instantiate a new stack frame based on the shadow frame.
- sf.Assign(reinterpret_cast<mirror::EmulatedStackFrame*>(
- shadow_frame.GetVRegReference(first_src_reg)));
- } else {
- sf.Assign(mirror::EmulatedStackFrame::CreateFromShadowFrameAndArgs<is_range>(
- self,
- callsite_type,
- callee_type,
- shadow_frame,
- first_src_reg,
- arg));
-
- // Something went wrong while creating the emulated stack frame, we should
- // throw the pending exception.
- if (sf.Get() == nullptr) {
- DCHECK(self->IsExceptionPending());
- return false;
- }
- }
-
- new_shadow_frame->SetVRegReference(0, receiver.Get());
- new_shadow_frame->SetVRegReference(1, sf.Get());
-
- PerformCall(self,
- code_item,
- shadow_frame.GetMethod(),
- 0 /* first dest reg */,
- new_shadow_frame,
- result);
- if (self->IsExceptionPending()) {
- return false;
- }
-
- // If the called transformer method we called has returned a value, then we
- // need to copy it back to |result|.
- sf->GetReturnValue(self, result);
- return ConvertReturnValue(callsite_type, callee_type, result);
-}
-
template <bool is_range,
bool do_assignability_check>
static inline bool DoCallCommon(ArtMethod* called_method,
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 423f054..aeb438f 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -33,6 +33,7 @@
#include "base/logging.h"
#include "base/macros.h"
#include "class_linker-inl.h"
+#include "common_dex_operations.h"
#include "common_throws.h"
#include "dex_file-inl.h"
#include "dex_instruction-inl.h"
@@ -48,6 +49,7 @@
#include "obj_ptr.h"
#include "stack.h"
#include "thread.h"
+#include "unstarted_runtime.h"
#include "well_known_classes.h"
namespace art {
@@ -153,8 +155,10 @@
// Performs a signature polymorphic invoke (invoke-polymorphic/invoke-polymorphic-range).
template<bool is_range, bool do_access_check>
-bool DoInvokePolymorphic(Thread* self, ShadowFrame& shadow_frame,
- const Instruction* inst, uint16_t inst_data,
+bool DoInvokePolymorphic(Thread* self,
+ ShadowFrame& shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data,
JValue* result);
// Handles invoke-virtual-quick and invoke-virtual-quick-range instructions.
@@ -251,17 +255,16 @@
}
}
ArtMethod* method = shadow_frame.GetMethod();
- ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass();
// MethodVerifier refuses methods with string_idx out of bounds.
DCHECK_LT(string_idx.index_ % mirror::DexCache::kDexCacheStringCacheSize,
- declaring_class->GetDexFile().NumStringIds());
+ method->GetDexFile()->NumStringIds());
ObjPtr<mirror::String> string_ptr =
- mirror::StringDexCachePair::Lookup(declaring_class->GetDexCache()->GetStrings(),
+ mirror::StringDexCachePair::Lookup(method->GetDexCache()->GetStrings(),
string_idx.index_,
mirror::DexCache::kDexCacheStringCacheSize).Read();
if (UNLIKELY(string_ptr == nullptr)) {
StackHandleScope<1> hs(self);
- Handle<mirror::DexCache> dex_cache(hs.NewHandle(declaring_class->GetDexCache()));
+ Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache()));
string_ptr = Runtime::Current()->GetClassLinker()->ResolveString(*method->GetDexFile(),
string_idx,
dex_cache);
diff --git a/runtime/jdwp/jdwp_adb.cc b/runtime/jdwp/jdwp_adb.cc
index 0eff2ab..d8869ad 100644
--- a/runtime/jdwp/jdwp_adb.cc
+++ b/runtime/jdwp/jdwp_adb.cc
@@ -24,6 +24,7 @@
#include "base/logging.h"
#include "jdwp/jdwp_priv.h"
+#include "thread-inl.h"
#ifdef ART_TARGET_ANDROID
#include "cutils/sockets.h"
@@ -57,7 +58,9 @@
struct JdwpAdbState : public JdwpNetStateBase {
public:
- explicit JdwpAdbState(JdwpState* state) : JdwpNetStateBase(state) {
+ explicit JdwpAdbState(JdwpState* state)
+ : JdwpNetStateBase(state),
+ state_lock_("JdwpAdbState lock", kJdwpAdbStateLock) {
control_sock_ = -1;
shutting_down_ = false;
@@ -77,20 +80,23 @@
}
}
- virtual bool Accept();
+ virtual bool Accept() REQUIRES(!state_lock_);
virtual bool Establish(const JdwpOptions*) {
return false;
}
- virtual void Shutdown() {
- shutting_down_ = true;
-
- int control_sock = this->control_sock_;
- int local_clientSock = this->clientSock;
-
- /* clear these out so it doesn't wake up and try to reuse them */
- this->control_sock_ = this->clientSock = -1;
+ virtual void Shutdown() REQUIRES(!state_lock_) {
+ int control_sock;
+ int local_clientSock;
+ {
+ MutexLock mu(Thread::Current(), state_lock_);
+ shutting_down_ = true;
+ control_sock = this->control_sock_;
+ local_clientSock = this->clientSock;
+ /* clear these out so it doesn't wake up and try to reuse them */
+ this->control_sock_ = this->clientSock = -1;
+ }
if (local_clientSock != -1) {
shutdown(local_clientSock, SHUT_RDWR);
@@ -103,13 +109,27 @@
WakePipe();
}
- virtual bool ProcessIncoming();
+ virtual bool ProcessIncoming() REQUIRES(!state_lock_);
private:
- int ReceiveClientFd();
+ int ReceiveClientFd() REQUIRES(!state_lock_);
- int control_sock_;
- bool shutting_down_;
+ bool IsDown() REQUIRES(!state_lock_) {
+ MutexLock mu(Thread::Current(), state_lock_);
+ return shutting_down_;
+ }
+
+ int ControlSock() REQUIRES(!state_lock_) {
+ MutexLock mu(Thread::Current(), state_lock_);
+ if (shutting_down_) {
+ CHECK_EQ(control_sock_, -1);
+ }
+ return control_sock_;
+ }
+
+ int control_sock_ GUARDED_BY(state_lock_);
+ bool shutting_down_ GUARDED_BY(state_lock_);
+ Mutex state_lock_;
socklen_t control_addr_len_;
union {
@@ -162,12 +182,13 @@
cmsg->cmsg_type = SCM_RIGHTS;
(reinterpret_cast<int*>(CMSG_DATA(cmsg)))[0] = -1;
- int rc = TEMP_FAILURE_RETRY(recvmsg(control_sock_, &msg, 0));
+ int rc = TEMP_FAILURE_RETRY(recvmsg(ControlSock(), &msg, 0));
if (rc <= 0) {
if (rc == -1) {
- PLOG(WARNING) << "Receiving file descriptor from ADB failed (socket " << control_sock_ << ")";
+ PLOG(WARNING) << "Receiving file descriptor from ADB failed (socket " << ControlSock() << ")";
}
+ MutexLock mu(Thread::Current(), state_lock_);
close(control_sock_);
control_sock_ = -1;
return -1;
@@ -189,23 +210,29 @@
/* first, ensure that we get a connection to the ADB daemon */
retry:
- if (shutting_down_) {
+ if (IsDown()) {
return false;
}
- if (control_sock_ == -1) {
+ if (ControlSock() == -1) {
int sleep_ms = 500;
const int sleep_max_ms = 2*1000;
char buff[5];
- control_sock_ = socket(PF_UNIX, SOCK_STREAM, 0);
- if (control_sock_ < 0) {
+ int sock = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
PLOG(ERROR) << "Could not create ADB control socket";
return false;
}
-
- if (!MakePipe()) {
- return false;
+ {
+ MutexLock mu(Thread::Current(), state_lock_);
+ control_sock_ = sock;
+ if (shutting_down_) {
+ return false;
+ }
+ if (!MakePipe()) {
+ return false;
+ }
}
snprintf(buff, sizeof(buff), "%04x", getpid());
@@ -225,11 +252,12 @@
* up after a few minutes in case somebody ships an app with
* the debuggable flag set.
*/
- int ret = connect(control_sock_, &control_addr_.controlAddrPlain, control_addr_len_);
+ int ret = connect(ControlSock(), &control_addr_.controlAddrPlain, control_addr_len_);
if (!ret) {
+ int control_sock = ControlSock();
#ifdef ART_TARGET_ANDROID
- if (!socket_peer_is_trusted(control_sock_)) {
- if (shutdown(control_sock_, SHUT_RDWR)) {
+ if (control_sock < 0 || !socket_peer_is_trusted(control_sock)) {
+ if (control_sock >= 0 && shutdown(control_sock, SHUT_RDWR)) {
PLOG(ERROR) << "trouble shutting down socket";
}
return false;
@@ -237,7 +265,7 @@
#endif
/* now try to send our pid to the ADB daemon */
- ret = TEMP_FAILURE_RETRY(send(control_sock_, buff, 4, 0));
+ ret = TEMP_FAILURE_RETRY(send(control_sock, buff, 4, 0));
if (ret >= 0) {
VLOG(jdwp) << StringPrintf("PID sent as '%.*s' to ADB", 4, buff);
break;
@@ -256,7 +284,7 @@
if (sleep_ms > sleep_max_ms) {
sleep_ms = sleep_max_ms;
}
- if (shutting_down_) {
+ if (IsDown()) {
return false;
}
}
@@ -264,9 +292,13 @@
VLOG(jdwp) << "trying to receive file descriptor from ADB";
/* now we can receive a client file descriptor */
- clientSock = ReceiveClientFd();
- if (shutting_down_) {
- return false; // suppress logs and additional activity
+ int sock = ReceiveClientFd();
+ {
+ MutexLock mu(Thread::Current(), state_lock_);
+ clientSock = sock;
+ if (shutting_down_) {
+ return false; // suppress logs and additional activity
+ }
}
if (clientSock == -1) {
if (++retryCount > 5) {
@@ -314,7 +346,7 @@
FD_ZERO(&readfds);
/* configure fds; note these may get zapped by another thread */
- fd = control_sock_;
+ fd = ControlSock();
if (fd >= 0) {
FD_SET(fd, &readfds);
if (maxfd < fd) {
@@ -368,13 +400,14 @@
VLOG(jdwp) << "Got wake-up signal, bailing out of select";
goto fail;
}
- if (control_sock_ >= 0 && FD_ISSET(control_sock_, &readfds)) {
+ int control_sock = ControlSock();
+ if (control_sock >= 0 && FD_ISSET(control_sock, &readfds)) {
int sock = ReceiveClientFd();
if (sock >= 0) {
LOG(INFO) << "Ignoring second debugger -- accepting and dropping";
close(sock);
} else {
- CHECK_EQ(control_sock_, -1);
+ CHECK_EQ(ControlSock(), -1);
/*
* Remote side most likely went away, so our next read
* on clientSock will fail and throw us out of the loop.
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index f43e30d..6336cdd 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -594,6 +594,9 @@
VLOG(jit) << "JIT discarded jitted code due to invalid single-implementation assumptions.";
return nullptr;
}
+ DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsDebuggable())
+ << "Should not be using cha on debuggable apps/runs!";
+
for (ArtMethod* single_impl : cha_single_implementation_list) {
Runtime::Current()->GetClassHierarchyAnalysis()->AddDependency(
single_impl, method, method_header);
@@ -645,6 +648,69 @@
return CodeCacheSizeLocked();
}
+// This notifies the code cache that the given method has been redefined and that it should remove
+// any cached information it has on the method. All threads must be suspended before calling this
+// method. The compiled code for the method (if there is any) must not be in any threads call stack.
+void JitCodeCache::NotifyMethodRedefined(ArtMethod* method) {
+ MutexLock mu(Thread::Current(), lock_);
+ if (method->IsNative()) {
+ return;
+ }
+ ProfilingInfo* info = method->GetProfilingInfo(kRuntimePointerSize);
+ if (info != nullptr) {
+ auto profile = std::find(profiling_infos_.begin(), profiling_infos_.end(), info);
+ DCHECK(profile != profiling_infos_.end());
+ profiling_infos_.erase(profile);
+ }
+ method->SetProfilingInfo(nullptr);
+ ScopedCodeCacheWrite ccw(code_map_.get());
+ for (auto code_iter = method_code_map_.begin();
+ code_iter != method_code_map_.end();
+ ++code_iter) {
+ if (code_iter->second == method) {
+ FreeCode(code_iter->first);
+ method_code_map_.erase(code_iter);
+ }
+ }
+ auto code_map = osr_code_map_.find(method);
+ if (code_map != osr_code_map_.end()) {
+ osr_code_map_.erase(code_map);
+ }
+}
+
+// This invalidates old_method. Once this function returns one can no longer use old_method to
+// execute code unless it is fixed up. This fixup will happen later in the process of installing a
+// class redefinition.
+// TODO We should add some info to ArtMethod to note that 'old_method' has been invalidated and
+// shouldn't be used since it is no longer logically in the jit code cache.
+// TODO We should add DCHECKS that validate that the JIT is paused when this method is entered.
+void JitCodeCache::MoveObsoleteMethod(ArtMethod* old_method, ArtMethod* new_method) {
+ MutexLock mu(Thread::Current(), lock_);
+ // Update ProfilingInfo to the new one and remove it from the old_method.
+ if (old_method->GetProfilingInfo(kRuntimePointerSize) != nullptr) {
+ DCHECK_EQ(old_method->GetProfilingInfo(kRuntimePointerSize)->GetMethod(), old_method);
+ ProfilingInfo* info = old_method->GetProfilingInfo(kRuntimePointerSize);
+ old_method->SetProfilingInfo(nullptr);
+ // Since the JIT should be paused and all threads suspended by the time this is called these
+ // checks should always pass.
+ DCHECK(!info->IsInUseByCompiler());
+ new_method->SetProfilingInfo(info);
+ info->method_ = new_method;
+ }
+ // Update method_code_map_ to point to the new method.
+ for (auto& it : method_code_map_) {
+ if (it.second == old_method) {
+ it.second = new_method;
+ }
+ }
+ // Update osr_code_map_ to point to the new method.
+ auto code_map = osr_code_map_.find(old_method);
+ if (code_map != osr_code_map_.end()) {
+ osr_code_map_.Put(new_method, code_map->second);
+ osr_code_map_.erase(old_method);
+ }
+}
+
size_t JitCodeCache::CodeCacheSizeLocked() {
return used_memory_for_code_;
}
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index d97742d..b5e3176 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -75,6 +75,10 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!lock_);
+ void NotifyMethodRedefined(ArtMethod* method)
+ REQUIRES(Locks::mutator_lock_)
+ REQUIRES(!lock_);
+
// Notify to the code cache that the compiler wants to use the
// profiling info of `method` to drive optimizations,
// and therefore ensure the returned profiling info object is not
@@ -219,6 +223,11 @@
void DisallowInlineCacheAccess() REQUIRES(!lock_);
void BroadcastForInlineCacheAccess() REQUIRES(!lock_);
+ // Notify the code cache that the method at the pointer 'old_method' is being moved to the pointer
+ // 'new_method' since it is being made obsolete.
+ void MoveObsoleteMethod(ArtMethod* old_method, ArtMethod* new_method)
+ REQUIRES(!lock_) REQUIRES(Locks::mutator_lock_);
+
private:
// Take ownership of maps.
JitCodeCache(MemMap* code_map,
diff --git a/runtime/jit/profiling_info.h b/runtime/jit/profiling_info.h
index 9902bb5..9fbf2e3 100644
--- a/runtime/jit/profiling_info.h
+++ b/runtime/jit/profiling_info.h
@@ -128,7 +128,9 @@
const uint32_t number_of_inline_caches_;
// Method this profiling info is for.
- ArtMethod* const method_;
+ // Not 'const' as JVMTI introduces obsolete methods that we implement by creating new ArtMethods.
+ // See JitCodeCache::MoveObsoleteMethod.
+ ArtMethod* method_;
// Whether the ArtMethod is currently being compiled. This flag
// is implicitly guarded by the JIT code cache lock.
diff --git a/runtime/method_handles-inl.h b/runtime/method_handles-inl.h
index 1240792..08b8ad9 100644
--- a/runtime/method_handles-inl.h
+++ b/runtime/method_handles-inl.h
@@ -134,36 +134,6 @@
return true;
}
-template <bool is_range>
-bool ConvertAndCopyArgumentsFromCallerFrame(Thread* self,
- Handle<mirror::MethodType> callsite_type,
- Handle<mirror::MethodType> callee_type,
- const ShadowFrame& caller_frame,
- uint32_t first_src_reg,
- uint32_t first_dest_reg,
- const uint32_t (&arg)[Instruction::kMaxVarArgRegs],
- ShadowFrame* callee_frame)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::ObjectArray<mirror::Class>> from_types(callsite_type->GetPTypes());
- ObjPtr<mirror::ObjectArray<mirror::Class>> to_types(callee_type->GetPTypes());
-
- const int32_t num_method_params = from_types->GetLength();
- if (to_types->GetLength() != num_method_params) {
- ThrowWrongMethodTypeException(callee_type.Get(), callsite_type.Get());
- return false;
- }
-
- ShadowFrameGetter<is_range> getter(first_src_reg, arg, caller_frame);
- ShadowFrameSetter setter(callee_frame, first_dest_reg);
-
- return PerformConversions<ShadowFrameGetter<is_range>, ShadowFrameSetter>(self,
- callsite_type,
- callee_type,
- &getter,
- &setter,
- num_method_params);
-}
-
} // namespace art
#endif // ART_RUNTIME_METHOD_HANDLES_INL_H_
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
index da510ce..99886e5 100644
--- a/runtime/method_handles.cc
+++ b/runtime/method_handles.cc
@@ -18,8 +18,12 @@
#include "android-base/stringprintf.h"
+#include "common_dex_operations.h"
#include "jvalue.h"
#include "jvalue-inl.h"
+#include "mirror/emulated_stack_frame.h"
+#include "mirror/method_handle_impl.h"
+#include "mirror/method_type.h"
#include "reflection.h"
#include "reflection-inl.h"
#include "well_known_classes.h"
@@ -282,4 +286,822 @@
}
}
+namespace {
+
+template <bool is_range>
+inline void CopyArgumentsFromCallerFrame(const ShadowFrame& caller_frame,
+ ShadowFrame* callee_frame,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ const size_t first_dst_reg,
+ const size_t num_regs)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ for (size_t i = 0; i < num_regs; ++i) {
+ size_t dst_reg = first_dst_reg + i;
+ size_t src_reg = is_range ? (first_arg + i) : args[i];
+ // Uint required, so that sign extension does not make this wrong on 64-bit systems
+ uint32_t src_value = caller_frame.GetVReg(src_reg);
+ ObjPtr<mirror::Object> o = caller_frame.GetVRegReference<kVerifyNone>(src_reg);
+ // If both register locations contains the same value, the register probably holds a reference.
+ // Note: As an optimization, non-moving collectors leave a stale reference value
+ // in the references array even after the original vreg was overwritten to a non-reference.
+ if (src_value == reinterpret_cast<uintptr_t>(o.Ptr())) {
+ callee_frame->SetVRegReference(dst_reg, o.Ptr());
+ } else {
+ callee_frame->SetVReg(dst_reg, src_value);
+ }
+ }
+}
+
+template <bool is_range>
+inline bool ConvertAndCopyArgumentsFromCallerFrame(
+ Thread* self,
+ Handle<mirror::MethodType> callsite_type,
+ Handle<mirror::MethodType> callee_type,
+ const ShadowFrame& caller_frame,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ uint32_t first_dst_reg,
+ ShadowFrame* callee_frame)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ObjPtr<mirror::ObjectArray<mirror::Class>> from_types(callsite_type->GetPTypes());
+ ObjPtr<mirror::ObjectArray<mirror::Class>> to_types(callee_type->GetPTypes());
+
+ const int32_t num_method_params = from_types->GetLength();
+ if (to_types->GetLength() != num_method_params) {
+ ThrowWrongMethodTypeException(callee_type.Get(), callsite_type.Get());
+ return false;
+ }
+
+ ShadowFrameGetter<is_range> getter(first_arg, args, caller_frame);
+ ShadowFrameSetter setter(callee_frame, first_dst_reg);
+
+ return PerformConversions<ShadowFrameGetter<is_range>, ShadowFrameSetter>(self,
+ callsite_type,
+ callee_type,
+ &getter,
+ &setter,
+ num_method_params);
+}
+
+inline bool IsMethodHandleInvokeExact(const ArtMethod* const method) {
+ if (method == jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandle_invokeExact)) {
+ return true;
+ } else {
+ DCHECK_EQ(method, jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandle_invoke));
+ return false;
+ }
+}
+
+inline bool IsInvoke(const mirror::MethodHandle::Kind handle_kind) {
+ return handle_kind <= mirror::MethodHandle::Kind::kLastInvokeKind;
+}
+
+inline bool IsInvokeTransform(const mirror::MethodHandle::Kind handle_kind) {
+ return (handle_kind == mirror::MethodHandle::Kind::kInvokeTransform
+ || handle_kind == mirror::MethodHandle::Kind::kInvokeCallSiteTransform);
+}
+
+inline bool IsFieldAccess(mirror::MethodHandle::Kind handle_kind) {
+ return (handle_kind >= mirror::MethodHandle::Kind::kFirstAccessorKind
+ && handle_kind <= mirror::MethodHandle::Kind::kLastAccessorKind);
+}
+
+// Calculate the number of ins for a proxy or native method, where we
+// can't just look at the code item.
+static inline size_t GetInsForProxyOrNativeMethod(ArtMethod* method)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(method->IsNative() || method->IsProxyMethod());
+
+ method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
+ size_t num_ins = 0;
+ // Separate accounting for the receiver, which isn't a part of the
+ // shorty.
+ if (!method->IsStatic()) {
+ ++num_ins;
+ }
+
+ uint32_t shorty_len = 0;
+ const char* shorty = method->GetShorty(&shorty_len);
+ for (size_t i = 1; i < shorty_len; ++i) {
+ const char c = shorty[i];
+ ++num_ins;
+ if (c == 'J' || c == 'D') {
+ ++num_ins;
+ }
+ }
+
+ return num_ins;
+}
+
+// Returns true iff. the callsite type for a polymorphic invoke is transformer
+// like, i.e that it has a single input argument whose type is
+// dalvik.system.EmulatedStackFrame.
+static inline bool IsCallerTransformer(Handle<mirror::MethodType> callsite_type)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ObjPtr<mirror::ObjectArray<mirror::Class>> param_types(callsite_type->GetPTypes());
+ if (param_types->GetLength() == 1) {
+ ObjPtr<mirror::Class> param(param_types->GetWithoutChecks(0));
+ return param == WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_EmulatedStackFrame);
+ }
+
+ return false;
+}
+
+template <bool is_range>
+static inline bool DoCallPolymorphic(ArtMethod* called_method,
+ Handle<mirror::MethodType> callsite_type,
+ Handle<mirror::MethodType> target_type,
+ Thread* self,
+ ShadowFrame& shadow_frame,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ JValue* result,
+ const mirror::MethodHandle::Kind handle_kind)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Compute method information.
+ const DexFile::CodeItem* code_item = called_method->GetCodeItem();
+
+ // Number of registers for the callee's call frame. Note that for non-exact
+ // invokes, we always derive this information from the callee method. We
+ // cannot guarantee during verification that the number of registers encoded
+ // in the invoke is equal to the number of ins for the callee. This is because
+ // some transformations (such as boxing a long -> Long or wideining an
+ // int -> long will change that number.
+ uint16_t num_regs;
+ size_t num_input_regs;
+ size_t first_dest_reg;
+ if (LIKELY(code_item != nullptr)) {
+ num_regs = code_item->registers_size_;
+ first_dest_reg = num_regs - code_item->ins_size_;
+ num_input_regs = code_item->ins_size_;
+ // Parameter registers go at the end of the shadow frame.
+ DCHECK_NE(first_dest_reg, (size_t)-1);
+ } else {
+ // No local regs for proxy and native methods.
+ DCHECK(called_method->IsNative() || called_method->IsProxyMethod());
+ num_regs = num_input_regs = GetInsForProxyOrNativeMethod(called_method);
+ first_dest_reg = 0;
+ }
+
+ // Allocate shadow frame on the stack.
+ ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
+ CREATE_SHADOW_FRAME(num_regs, &shadow_frame, called_method, /* dex pc */ 0);
+ ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
+
+ // Whether this polymorphic invoke was issued by a transformer method.
+ bool is_caller_transformer = false;
+ // Thread might be suspended during PerformArgumentConversions due to the
+ // allocations performed during boxing.
+ {
+ ScopedStackedShadowFramePusher pusher(
+ self, new_shadow_frame, StackedShadowFrameType::kShadowFrameUnderConstruction);
+ if (callsite_type->IsExactMatch(target_type.Get())) {
+ // This is an exact invoke, we can take the fast path of just copying all
+ // registers without performing any argument conversions.
+ CopyArgumentsFromCallerFrame<is_range>(shadow_frame,
+ new_shadow_frame,
+ args,
+ first_arg,
+ first_dest_reg,
+ num_input_regs);
+ } else {
+ // This includes the case where we're entering this invoke-polymorphic
+ // from a transformer method. In that case, the callsite_type will contain
+ // a single argument of type dalvik.system.EmulatedStackFrame. In that
+ // case, we'll have to unmarshal the EmulatedStackFrame into the
+ // new_shadow_frame and perform argument conversions on it.
+ if (IsCallerTransformer(callsite_type)) {
+ is_caller_transformer = true;
+ // The emulated stack frame is the first and only argument when we're coming
+ // through from a transformer.
+ size_t first_arg_register = (is_range) ? first_arg : args[0];
+ ObjPtr<mirror::EmulatedStackFrame> emulated_stack_frame(
+ reinterpret_cast<mirror::EmulatedStackFrame*>(
+ shadow_frame.GetVRegReference(first_arg_register)));
+ if (!emulated_stack_frame->WriteToShadowFrame(self,
+ target_type,
+ first_dest_reg,
+ new_shadow_frame)) {
+ DCHECK(self->IsExceptionPending());
+ result->SetL(0);
+ return false;
+ }
+ } else if (!ConvertAndCopyArgumentsFromCallerFrame<is_range>(self,
+ callsite_type,
+ target_type,
+ shadow_frame,
+ args,
+ first_arg,
+ first_dest_reg,
+ new_shadow_frame)) {
+ DCHECK(self->IsExceptionPending());
+ result->SetL(0);
+ return false;
+ }
+ }
+ }
+
+ // See TODO in DoInvokePolymorphic : We need to perform this dynamic, receiver
+ // based dispatch right before we perform the actual call, because the
+ // receiver isn't known very early.
+ if (handle_kind == mirror::MethodHandle::Kind::kInvokeVirtual ||
+ handle_kind == mirror::MethodHandle::Kind::kInvokeInterface) {
+ ObjPtr<mirror::Object> receiver(new_shadow_frame->GetVRegReference(first_dest_reg));
+ ObjPtr<mirror::Class> declaring_class(called_method->GetDeclaringClass());
+ // Verify that _vRegC is an object reference and of the type expected by
+ // the receiver.
+ if (!VerifyObjectIsClass(receiver, declaring_class)) {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
+
+ called_method = receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(
+ called_method, kRuntimePointerSize);
+ }
+
+ PerformCall(self, code_item, shadow_frame.GetMethod(), first_dest_reg, new_shadow_frame, result);
+ if (self->IsExceptionPending()) {
+ return false;
+ }
+
+ // If the caller of this signature polymorphic method was a transformer,
+ // we need to copy the result back out to the emulated stack frame.
+ if (is_caller_transformer) {
+ StackHandleScope<2> hs(self);
+ size_t first_callee_register = is_range ? (first_arg) : args[0];
+ Handle<mirror::EmulatedStackFrame> emulated_stack_frame(
+ hs.NewHandle(reinterpret_cast<mirror::EmulatedStackFrame*>(
+ shadow_frame.GetVRegReference(first_callee_register))));
+ Handle<mirror::MethodType> emulated_stack_type(hs.NewHandle(emulated_stack_frame->GetType()));
+ JValue local_result;
+ local_result.SetJ(result->GetJ());
+
+ if (ConvertReturnValue(emulated_stack_type, target_type, &local_result)) {
+ emulated_stack_frame->SetReturnValue(self, local_result);
+ return true;
+ } else {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
+ } else {
+ return ConvertReturnValue(callsite_type, target_type, result);
+ }
+}
+
+template <bool is_range>
+static inline bool DoCallTransform(ArtMethod* called_method,
+ Handle<mirror::MethodType> callsite_type,
+ Handle<mirror::MethodType> callee_type,
+ Thread* self,
+ ShadowFrame& shadow_frame,
+ Handle<mirror::MethodHandleImpl> receiver,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // This can be fixed to two, because the method we're calling here
+ // (MethodHandle.transformInternal) doesn't have any locals and the signature
+ // is known :
+ //
+ // private MethodHandle.transformInternal(EmulatedStackFrame sf);
+ //
+ // This means we need only two vregs :
+ // - One for the receiver object.
+ // - One for the only method argument (an EmulatedStackFrame).
+ static constexpr size_t kNumRegsForTransform = 2;
+
+ const DexFile::CodeItem* code_item = called_method->GetCodeItem();
+ DCHECK(code_item != nullptr);
+ DCHECK_EQ(kNumRegsForTransform, code_item->registers_size_);
+ DCHECK_EQ(kNumRegsForTransform, code_item->ins_size_);
+
+ ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
+ CREATE_SHADOW_FRAME(kNumRegsForTransform, &shadow_frame, called_method, /* dex pc */ 0);
+ ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
+
+ StackHandleScope<1> hs(self);
+ MutableHandle<mirror::EmulatedStackFrame> sf(hs.NewHandle<mirror::EmulatedStackFrame>(nullptr));
+ if (IsCallerTransformer(callsite_type)) {
+ // If we're entering this transformer from another transformer, we can pass
+ // through the handle directly to the callee, instead of having to
+ // instantiate a new stack frame based on the shadow frame.
+ size_t first_callee_register = is_range ? first_arg : args[0];
+ sf.Assign(reinterpret_cast<mirror::EmulatedStackFrame*>(
+ shadow_frame.GetVRegReference(first_callee_register)));
+ } else {
+ sf.Assign(mirror::EmulatedStackFrame::CreateFromShadowFrameAndArgs<is_range>(self,
+ callsite_type,
+ callee_type,
+ shadow_frame,
+ first_arg,
+ args));
+
+ // Something went wrong while creating the emulated stack frame, we should
+ // throw the pending exception.
+ if (sf.Get() == nullptr) {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
+ }
+
+ new_shadow_frame->SetVRegReference(0, receiver.Get());
+ new_shadow_frame->SetVRegReference(1, sf.Get());
+
+ PerformCall(self,
+ code_item,
+ shadow_frame.GetMethod(),
+ 0 /* first destination register */,
+ new_shadow_frame,
+ result);
+ if (self->IsExceptionPending()) {
+ return false;
+ }
+
+ // If the called transformer method we called has returned a value, then we
+ // need to copy it back to |result|.
+ sf->GetReturnValue(self, result);
+ return ConvertReturnValue(callsite_type, callee_type, result);
+}
+
+inline static ObjPtr<mirror::Class> GetAndInitializeDeclaringClass(Thread* self, ArtField* field)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Method handle invocations on static fields should ensure class is
+ // initialized. This usually happens when an instance is constructed
+ // or class members referenced, but this is not guaranteed when
+ // looking up method handles.
+ ObjPtr<mirror::Class> klass = field->GetDeclaringClass();
+ if (UNLIKELY(!klass->IsInitialized())) {
+ StackHandleScope<1> hs(self);
+ HandleWrapperObjPtr<mirror::Class> h(hs.NewHandleWrapper(&klass));
+ if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, h, true, true)) {
+ DCHECK(self->IsExceptionPending());
+ return nullptr;
+ }
+ }
+ return klass;
+}
+
+template <bool is_range>
+bool DoInvokePolymorphicUnchecked(Thread* self,
+ ShadowFrame& shadow_frame,
+ Handle<mirror::MethodHandleImpl> method_handle,
+ Handle<mirror::MethodType> callsite_type,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ StackHandleScope<1> hs(self);
+ Handle<mirror::MethodType> handle_type(hs.NewHandle(method_handle->GetMethodType()));
+ const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind();
+ if (IsInvoke(handle_kind)) {
+ // Get the method we're actually invoking along with the kind of
+ // invoke that is desired. We don't need to perform access checks at this
+ // point because they would have been performed on our behalf at the point
+ // of creation of the method handle.
+ ArtMethod* called_method = method_handle->GetTargetMethod();
+ CHECK(called_method != nullptr);
+
+ if (handle_kind == mirror::MethodHandle::Kind::kInvokeVirtual ||
+ handle_kind == mirror::MethodHandle::Kind::kInvokeInterface) {
+ // TODO: Unfortunately, we have to postpone dynamic receiver based checks
+ // because the receiver might be cast or might come from an emulated stack
+ // frame, which means that it is unknown at this point. We perform these
+ // checks inside DoCallPolymorphic right before we do the actual invoke.
+ } else if (handle_kind == mirror::MethodHandle::Kind::kInvokeDirect) {
+ // String constructors are a special case, they are replaced with StringFactory
+ // methods.
+ if (called_method->IsConstructor() && called_method->GetDeclaringClass()->IsStringClass()) {
+ DCHECK(handle_type->GetRType()->IsStringClass());
+ called_method = WellKnownClasses::StringInitToStringFactory(called_method);
+ }
+ } else if (handle_kind == mirror::MethodHandle::Kind::kInvokeSuper) {
+ ObjPtr<mirror::Class> declaring_class = called_method->GetDeclaringClass();
+
+ // Note that we're not dynamically dispatching on the type of the receiver
+ // here. We use the static type of the "receiver" object that we've
+ // recorded in the method handle's type, which will be the same as the
+ // special caller that was specified at the point of lookup.
+ ObjPtr<mirror::Class> referrer_class = handle_type->GetPTypes()->Get(0);
+ if (!declaring_class->IsInterface()) {
+ ObjPtr<mirror::Class> super_class = referrer_class->GetSuperClass();
+ uint16_t vtable_index = called_method->GetMethodIndex();
+ DCHECK(super_class != nullptr);
+ DCHECK(super_class->HasVTable());
+ // Note that super_class is a super of referrer_class and called_method
+ // will always be declared by super_class (or one of its super classes).
+ DCHECK_LT(vtable_index, super_class->GetVTableLength());
+ called_method = super_class->GetVTableEntry(vtable_index, kRuntimePointerSize);
+ } else {
+ called_method = referrer_class->FindVirtualMethodForInterfaceSuper(
+ called_method, kRuntimePointerSize);
+ }
+ CHECK(called_method != nullptr);
+ }
+ if (IsInvokeTransform(handle_kind)) {
+ // There are two cases here - method handles representing regular
+ // transforms and those representing call site transforms. Method
+ // handles for call site transforms adapt their MethodType to match
+ // the call site. For these, the |callee_type| is the same as the
+ // |callsite_type|. The VarargsCollector is such a tranform, its
+ // method type depends on the call site, ie. x(a) or x(a, b), or
+ // x(a, b, c). The VarargsCollector invokes a variable arity method
+ // with the arity arguments in an array.
+ Handle<mirror::MethodType> callee_type =
+ (handle_kind == mirror::MethodHandle::Kind::kInvokeCallSiteTransform) ? callsite_type
+ : handle_type;
+ return DoCallTransform<is_range>(called_method,
+ callsite_type,
+ callee_type,
+ self,
+ shadow_frame,
+ method_handle /* receiver */,
+ args,
+ first_arg,
+ result);
+
+ } else {
+ return DoCallPolymorphic<is_range>(called_method,
+ callsite_type,
+ handle_type,
+ self,
+ shadow_frame,
+ args,
+ first_arg,
+ result,
+ handle_kind);
+ }
+ } else {
+ LOG(FATAL) << "Unreachable: " << handle_kind;
+ UNREACHABLE();
+ }
+}
+
+// Helper for getters in invoke-polymorphic.
+inline static void DoFieldGetForInvokePolymorphic(Thread* self,
+ const ShadowFrame& shadow_frame,
+ ObjPtr<mirror::Object>& obj,
+ ArtField* field,
+ Primitive::Type field_type,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ switch (field_type) {
+ case Primitive::kPrimBoolean:
+ DoFieldGetCommon<Primitive::kPrimBoolean>(self, shadow_frame, obj, field, result);
+ break;
+ case Primitive::kPrimByte:
+ DoFieldGetCommon<Primitive::kPrimByte>(self, shadow_frame, obj, field, result);
+ break;
+ case Primitive::kPrimChar:
+ DoFieldGetCommon<Primitive::kPrimChar>(self, shadow_frame, obj, field, result);
+ break;
+ case Primitive::kPrimShort:
+ DoFieldGetCommon<Primitive::kPrimShort>(self, shadow_frame, obj, field, result);
+ break;
+ case Primitive::kPrimInt:
+ DoFieldGetCommon<Primitive::kPrimInt>(self, shadow_frame, obj, field, result);
+ break;
+ case Primitive::kPrimLong:
+ DoFieldGetCommon<Primitive::kPrimLong>(self, shadow_frame, obj, field, result);
+ break;
+ case Primitive::kPrimFloat:
+ DoFieldGetCommon<Primitive::kPrimInt>(self, shadow_frame, obj, field, result);
+ break;
+ case Primitive::kPrimDouble:
+ DoFieldGetCommon<Primitive::kPrimLong>(self, shadow_frame, obj, field, result);
+ break;
+ case Primitive::kPrimNot:
+ DoFieldGetCommon<Primitive::kPrimNot>(self, shadow_frame, obj, field, result);
+ break;
+ case Primitive::kPrimVoid:
+ LOG(FATAL) << "Unreachable: " << field_type;
+ UNREACHABLE();
+ }
+}
+
+// Helper for setters in invoke-polymorphic.
+template <bool do_assignability_check>
+inline bool DoFieldPutForInvokePolymorphic(Thread* self,
+ ShadowFrame& shadow_frame,
+ ObjPtr<mirror::Object>& obj,
+ ArtField* field,
+ Primitive::Type field_type,
+ const JValue& value)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ static const bool kTransaction = false;
+ switch (field_type) {
+ case Primitive::kPrimBoolean:
+ return DoFieldPutCommon<Primitive::kPrimBoolean, do_assignability_check, kTransaction>(
+ self, shadow_frame, obj, field, value);
+ case Primitive::kPrimByte:
+ return DoFieldPutCommon<Primitive::kPrimByte, do_assignability_check, kTransaction>(
+ self, shadow_frame, obj, field, value);
+ case Primitive::kPrimChar:
+ return DoFieldPutCommon<Primitive::kPrimChar, do_assignability_check, kTransaction>(
+ self, shadow_frame, obj, field, value);
+ case Primitive::kPrimShort:
+ return DoFieldPutCommon<Primitive::kPrimShort, do_assignability_check, kTransaction>(
+ self, shadow_frame, obj, field, value);
+ case Primitive::kPrimInt:
+ case Primitive::kPrimFloat:
+ return DoFieldPutCommon<Primitive::kPrimInt, do_assignability_check, kTransaction>(
+ self, shadow_frame, obj, field, value);
+ case Primitive::kPrimLong:
+ case Primitive::kPrimDouble:
+ return DoFieldPutCommon<Primitive::kPrimLong, do_assignability_check, kTransaction>(
+ self, shadow_frame, obj, field, value);
+ case Primitive::kPrimNot:
+ return DoFieldPutCommon<Primitive::kPrimNot, do_assignability_check, kTransaction>(
+ self, shadow_frame, obj, field, value);
+ case Primitive::kPrimVoid:
+ LOG(FATAL) << "Unreachable: " << field_type;
+ UNREACHABLE();
+ }
+}
+
+static JValue GetValueFromShadowFrame(const ShadowFrame& shadow_frame,
+ Primitive::Type field_type,
+ uint32_t vreg)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ JValue field_value;
+ switch (field_type) {
+ case Primitive::kPrimBoolean:
+ field_value.SetZ(static_cast<uint8_t>(shadow_frame.GetVReg(vreg)));
+ break;
+ case Primitive::kPrimByte:
+ field_value.SetB(static_cast<int8_t>(shadow_frame.GetVReg(vreg)));
+ break;
+ case Primitive::kPrimChar:
+ field_value.SetC(static_cast<uint16_t>(shadow_frame.GetVReg(vreg)));
+ break;
+ case Primitive::kPrimShort:
+ field_value.SetS(static_cast<int16_t>(shadow_frame.GetVReg(vreg)));
+ break;
+ case Primitive::kPrimInt:
+ case Primitive::kPrimFloat:
+ field_value.SetI(shadow_frame.GetVReg(vreg));
+ break;
+ case Primitive::kPrimLong:
+ case Primitive::kPrimDouble:
+ field_value.SetJ(shadow_frame.GetVRegLong(vreg));
+ break;
+ case Primitive::kPrimNot:
+ field_value.SetL(shadow_frame.GetVRegReference(vreg));
+ break;
+ case Primitive::kPrimVoid:
+ LOG(FATAL) << "Unreachable: " << field_type;
+ UNREACHABLE();
+ }
+ return field_value;
+}
+
+template <bool is_range, bool do_conversions, bool do_assignability_check>
+bool DoInvokePolymorphicFieldAccess(Thread* self,
+ ShadowFrame& shadow_frame,
+ Handle<mirror::MethodHandleImpl> method_handle,
+ Handle<mirror::MethodType> callsite_type,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ StackHandleScope<1> hs(self);
+ Handle<mirror::MethodType> handle_type(hs.NewHandle(method_handle->GetMethodType()));
+ const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind();
+ ArtField* field = method_handle->GetTargetField();
+ Primitive::Type field_type = field->GetTypeAsPrimitiveType();
+
+ switch (handle_kind) {
+ case mirror::MethodHandle::kInstanceGet: {
+ size_t obj_reg = is_range ? first_arg : args[0];
+ ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(obj_reg);
+ DoFieldGetForInvokePolymorphic(self, shadow_frame, obj, field, field_type, result);
+ if (do_conversions && !ConvertReturnValue(callsite_type, handle_type, result)) {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
+ return true;
+ }
+ case mirror::MethodHandle::kStaticGet: {
+ ObjPtr<mirror::Object> obj = GetAndInitializeDeclaringClass(self, field);
+ if (obj == nullptr) {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
+ DoFieldGetForInvokePolymorphic(self, shadow_frame, obj, field, field_type, result);
+ if (do_conversions && !ConvertReturnValue(callsite_type, handle_type, result)) {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
+ return true;
+ }
+ case mirror::MethodHandle::kInstancePut: {
+ size_t obj_reg = is_range ? first_arg : args[0];
+ size_t value_reg = is_range ? (first_arg + 1) : args[1];
+ JValue value = GetValueFromShadowFrame(shadow_frame, field_type, value_reg);
+ if (do_conversions && !ConvertArgumentValue(callsite_type, handle_type, 1, &value)) {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
+ ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(obj_reg);
+ return DoFieldPutForInvokePolymorphic<do_assignability_check>(self,
+ shadow_frame,
+ obj,
+ field,
+ field_type,
+ value);
+ }
+ case mirror::MethodHandle::kStaticPut: {
+ ObjPtr<mirror::Object> obj = GetAndInitializeDeclaringClass(self, field);
+ if (obj == nullptr) {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
+ size_t value_reg = is_range ? first_arg : args[0];
+ JValue value = GetValueFromShadowFrame(shadow_frame, field_type, value_reg);
+ if (do_conversions && !ConvertArgumentValue(callsite_type, handle_type, 0, &value)) {
+ DCHECK(self->IsExceptionPending());
+ return false;
+ }
+ return DoFieldPutForInvokePolymorphic<do_assignability_check>(self,
+ shadow_frame,
+ obj,
+ field,
+ field_type,
+ value);
+ }
+ default:
+ LOG(FATAL) << "Unreachable: " << handle_kind;
+ UNREACHABLE();
+ }
+}
+
+template <bool is_range, bool do_assignability_check>
+static inline bool DoInvokePolymorphicNonExact(Thread* self,
+ ShadowFrame& shadow_frame,
+ Handle<mirror::MethodHandleImpl> method_handle,
+ Handle<mirror::MethodType> callsite_type,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind();
+ ObjPtr<mirror::MethodType> handle_type(method_handle->GetMethodType());
+ CHECK(handle_type != nullptr);
+
+ if (!IsInvokeTransform(handle_kind)) {
+ if (UNLIKELY(!IsCallerTransformer(callsite_type) &&
+ !callsite_type->IsConvertible(handle_type.Ptr()))) {
+ ThrowWrongMethodTypeException(handle_type.Ptr(), callsite_type.Get());
+ return false;
+ }
+ }
+
+ if (IsFieldAccess(handle_kind)) {
+ if (UNLIKELY(callsite_type->IsExactMatch(handle_type.Ptr()))) {
+ const bool do_convert = false;
+ return DoInvokePolymorphicFieldAccess<is_range, do_convert, do_assignability_check>(
+ self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ } else {
+ const bool do_convert = true;
+ return DoInvokePolymorphicFieldAccess<is_range, do_convert, do_assignability_check>(
+ self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ }
+ }
+
+ if (UNLIKELY(callsite_type->IsExactMatch(handle_type.Ptr()))) {
+ return DoInvokePolymorphicUnchecked<is_range>(self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ } else {
+ return DoInvokePolymorphicUnchecked<is_range>(self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ }
+}
+
+template <bool is_range, bool do_assignability_check>
+bool DoInvokePolymorphicExact(Thread* self,
+ ShadowFrame& shadow_frame,
+ Handle<mirror::MethodHandleImpl> method_handle,
+ Handle<mirror::MethodType> callsite_type,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // We need to check the nominal type of the handle in addition to the
+ // real type. The "nominal" type is present when MethodHandle.asType is
+ // called any handle, and results in the declared type of the handle
+ // changing.
+ ObjPtr<mirror::MethodType> nominal_type(method_handle->GetNominalType());
+ if (UNLIKELY(nominal_type != nullptr)) {
+ if (UNLIKELY(!callsite_type->IsExactMatch(nominal_type.Ptr()))) {
+ ThrowWrongMethodTypeException(nominal_type.Ptr(), callsite_type.Get());
+ return false;
+ }
+ return DoInvokePolymorphicNonExact<is_range, do_assignability_check>(self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ }
+
+ ObjPtr<mirror::MethodType> handle_type(method_handle->GetMethodType());
+ if (UNLIKELY(!callsite_type->IsExactMatch(handle_type.Ptr()))) {
+ ThrowWrongMethodTypeException(handle_type.Ptr(), callsite_type.Get());
+ return false;
+ }
+
+ const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind();
+ if (IsFieldAccess(handle_kind)) {
+ const bool do_convert = false;
+ return DoInvokePolymorphicFieldAccess<is_range, do_convert, do_assignability_check>(
+ self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ }
+
+ return DoInvokePolymorphicUnchecked<is_range>(self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+}
+
+} // namespace
+
+template <bool is_range, bool do_assignability_check>
+bool DoInvokePolymorphic(Thread* self,
+ ArtMethod* invoke_method,
+ ShadowFrame& shadow_frame,
+ Handle<mirror::MethodHandleImpl> method_handle,
+ Handle<mirror::MethodType> callsite_type,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (IsMethodHandleInvokeExact(invoke_method)) {
+ return DoInvokePolymorphicExact<is_range, do_assignability_check>(self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ } else {
+ return DoInvokePolymorphicNonExact<is_range, do_assignability_check>(self,
+ shadow_frame,
+ method_handle,
+ callsite_type,
+ args,
+ first_arg,
+ result);
+ }
+}
+
+#define EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(_is_range, _do_assignability_check) \
+template REQUIRES_SHARED(Locks::mutator_lock_) \
+bool DoInvokePolymorphic<_is_range, _do_assignability_check>( \
+ Thread* self, \
+ ArtMethod* invoke_method, \
+ ShadowFrame& shadow_frame, \
+ Handle<mirror::MethodHandleImpl> method_handle, \
+ Handle<mirror::MethodType> callsite_type, \
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs], \
+ uint32_t first_arg, \
+ JValue* result)
+
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(true, true);
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(true, false);
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(false, true);
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(false, false);
+#undef EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL
+
} // namespace art
diff --git a/runtime/method_handles.h b/runtime/method_handles.h
index d0a4902..734d7c7 100644
--- a/runtime/method_handles.h
+++ b/runtime/method_handles.h
@@ -23,48 +23,16 @@
#include "handle.h"
#include "jvalue.h"
#include "mirror/class.h"
-#include "mirror/method_type.h"
namespace art {
namespace mirror {
+ class MethodHandleImpl;
class MethodType;
-}
+} // mirror
class ShadowFrame;
-// Defines the behaviour of a given method handle. The behaviour
-// of a handle of a given kind is identical to the dex bytecode behaviour
-// of the equivalent instruction.
-//
-// NOTE: These must be kept in sync with the constants defined in
-// java.lang.invoke.MethodHandle.
-enum MethodHandleKind {
- kInvokeVirtual = 0,
- kInvokeSuper,
- kInvokeDirect,
- kInvokeStatic,
- kInvokeInterface,
- kInvokeTransform,
- kInvokeCallSiteTransform,
- kInstanceGet,
- kInstancePut,
- kStaticGet,
- kStaticPut,
- kLastValidKind = kStaticPut,
- kLastInvokeKind = kInvokeCallSiteTransform
-};
-
-// Whether the given method handle kind is some variant of an invoke.
-inline bool IsInvoke(const MethodHandleKind handle_kind) {
- return handle_kind <= kLastInvokeKind;
-}
-
-// Whether the given method handle kind is some variant of a tranform.
-inline bool IsInvokeTransform(const MethodHandleKind handle_kind) {
- return handle_kind == kInvokeTransform || handle_kind == kInvokeCallSiteTransform;
-}
-
// Returns true if there is a possible conversion from |from| to |to|
// for a MethodHandle parameter.
bool IsParameterTypeConvertible(ObjPtr<mirror::Class> from,
@@ -158,19 +126,6 @@
S* setter,
int32_t num_conversions) REQUIRES_SHARED(Locks::mutator_lock_);
-// A convenience wrapper around |PerformConversions|, for the case where
-// the setter and getter are both ShadowFrame based.
-template <bool is_range>
-bool ConvertAndCopyArgumentsFromCallerFrame(Thread* self,
- Handle<mirror::MethodType> callsite_type,
- Handle<mirror::MethodType> callee_type,
- const ShadowFrame& caller_frame,
- uint32_t first_src_reg,
- uint32_t first_dest_reg,
- const uint32_t (&arg)[Instruction::kMaxVarArgRegs],
- ShadowFrame* callee_frame)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
// A convenience class that allows for iteration through a list of
// input argument registers |arg| for non-range invokes or a list of
// consecutive registers starting with a given based for range
@@ -178,7 +133,8 @@
//
// This is used to iterate over input arguments while performing standard
// argument conversions.
-template <bool is_range> class ShadowFrameGetter {
+template <bool is_range>
+class ShadowFrameGetter {
public:
ShadowFrameGetter(size_t first_src_reg,
const uint32_t (&arg)[Instruction::kMaxVarArgRegs],
@@ -246,6 +202,17 @@
size_t arg_index_;
};
+template <bool is_range, bool do_assignability_check>
+bool DoInvokePolymorphic(Thread* self,
+ ArtMethod* invoke_method,
+ ShadowFrame& shadow_frame,
+ Handle<mirror::MethodHandleImpl> method_handle,
+ Handle<mirror::MethodType> callsite_type,
+ const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+ uint32_t first_arg,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
} // namespace art
#endif // ART_RUNTIME_METHOD_HANDLES_H_
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index ec265e5..6f88cc5 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -19,7 +19,6 @@
#include "array.h"
#include "art_field.h"
-#include "art_method.h"
#include "class.h"
#include "dex_file_types.h"
#include "object.h"
@@ -27,6 +26,7 @@
namespace art {
+class ArtMethod;
struct DexCacheOffsets;
class DexFile;
class ImageWriter;
diff --git a/runtime/mirror/method_handle_impl.h b/runtime/mirror/method_handle_impl.h
index 5ea82b5..abe999a 100644
--- a/runtime/mirror/method_handle_impl.h
+++ b/runtime/mirror/method_handle_impl.h
@@ -32,6 +32,37 @@
// C++ mirror of java.lang.invoke.MethodHandle
class MANAGED MethodHandle : public Object {
public:
+ // Defines the behaviour of a given method handle. The behaviour
+ // of a handle of a given kind is identical to the dex bytecode behaviour
+ // of the equivalent instruction.
+ //
+ // NOTE: These must be kept in sync with the constants defined in
+ // java.lang.invoke.MethodHandle.
+ enum Kind {
+ kInvokeVirtual = 0,
+ kInvokeSuper,
+ kInvokeDirect,
+ kInvokeStatic,
+ kInvokeInterface,
+ kInvokeTransform,
+ kInvokeCallSiteTransform,
+ kInstanceGet,
+ kInstancePut,
+ kStaticGet,
+ kStaticPut,
+ kLastValidKind = kStaticPut,
+ kFirstAccessorKind = kInstanceGet,
+ kLastAccessorKind = kStaticPut,
+ kLastInvokeKind = kInvokeCallSiteTransform
+ };
+
+ Kind GetHandleKind() REQUIRES_SHARED(Locks::mutator_lock_) {
+ const int32_t handle_kind = GetField32(OFFSET_OF_OBJECT_MEMBER(MethodHandle, handle_kind_));
+ DCHECK(handle_kind >= 0 &&
+ handle_kind <= static_cast<int32_t>(Kind::kLastValidKind));
+ return static_cast<Kind>(handle_kind);
+ }
+
mirror::MethodType* GetMethodType() REQUIRES_SHARED(Locks::mutator_lock_) {
return GetFieldObject<mirror::MethodType>(OFFSET_OF_OBJECT_MEMBER(MethodHandle, method_type_));
}
@@ -50,13 +81,6 @@
GetField64(OFFSET_OF_OBJECT_MEMBER(MethodHandle, art_field_or_method_)));
}
- MethodHandleKind GetHandleKind() REQUIRES_SHARED(Locks::mutator_lock_) {
- const int32_t handle_kind = GetField32(OFFSET_OF_OBJECT_MEMBER(MethodHandle, handle_kind_));
-
- DCHECK(handle_kind >= 0 && handle_kind <= MethodHandleKind::kLastValidKind);
- return static_cast<MethodHandleKind>(handle_kind);
- }
-
static mirror::Class* StaticClass() REQUIRES_SHARED(Locks::mutator_lock_);
private:
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index 6d29ed3..354410e 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -97,6 +97,12 @@
OFFSET_OF_OBJECT_MEMBER(Object, monitor_), old_val.GetValue(), new_val.GetValue());
}
+inline bool Object::CasLockWordWeakAcquire(LockWord old_val, LockWord new_val) {
+ // Force use of non-transactional mode and do not check.
+ return CasFieldWeakAcquire32<false, false>(
+ OFFSET_OF_OBJECT_MEMBER(Object, monitor_), old_val.GetValue(), new_val.GetValue());
+}
+
inline bool Object::CasLockWordWeakRelease(LockWord old_val, LockWord new_val) {
// Force use of non-transactional mode and do not check.
return CasFieldWeakRelease32<false, false>(
@@ -759,6 +765,24 @@
}
template<bool kTransactionActive, bool kCheckTransaction, VerifyObjectFlags kVerifyFlags>
+inline bool Object::CasFieldWeakAcquire32(MemberOffset field_offset,
+ int32_t old_value, int32_t new_value) {
+ if (kCheckTransaction) {
+ DCHECK_EQ(kTransactionActive, Runtime::Current()->IsActiveTransaction());
+ }
+ if (kTransactionActive) {
+ Runtime::Current()->RecordWriteField32(this, field_offset, old_value, true);
+ }
+ if (kVerifyFlags & kVerifyThis) {
+ VerifyObject(this);
+ }
+ uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value();
+ AtomicInteger* atomic_addr = reinterpret_cast<AtomicInteger*>(raw_addr);
+
+ return atomic_addr->CompareExchangeWeakAcquire(old_value, new_value);
+}
+
+template<bool kTransactionActive, bool kCheckTransaction, VerifyObjectFlags kVerifyFlags>
inline bool Object::CasFieldWeakRelease32(MemberOffset field_offset,
int32_t old_value, int32_t new_value) {
if (kCheckTransaction) {
diff --git a/runtime/mirror/object.h b/runtime/mirror/object.h
index 67b5ddb..db58a60 100644
--- a/runtime/mirror/object.h
+++ b/runtime/mirror/object.h
@@ -153,6 +153,8 @@
REQUIRES_SHARED(Locks::mutator_lock_);
bool CasLockWordWeakRelaxed(LockWord old_val, LockWord new_val)
REQUIRES_SHARED(Locks::mutator_lock_);
+ bool CasLockWordWeakAcquire(LockWord old_val, LockWord new_val)
+ REQUIRES_SHARED(Locks::mutator_lock_);
bool CasLockWordWeakRelease(LockWord old_val, LockWord new_val)
REQUIRES_SHARED(Locks::mutator_lock_);
uint32_t GetLockOwnerThreadId();
@@ -460,6 +462,12 @@
template<bool kTransactionActive, bool kCheckTransaction = true,
VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+ bool CasFieldWeakAcquire32(MemberOffset field_offset, int32_t old_value,
+ int32_t new_value) ALWAYS_INLINE
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ template<bool kTransactionActive, bool kCheckTransaction = true,
+ VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
bool CasFieldWeakRelease32(MemberOffset field_offset, int32_t old_value,
int32_t new_value) ALWAYS_INLINE
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 222eb5c..893abd5 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -161,7 +161,7 @@
}
LockWord fat(this, lw.GCState());
// Publish the updated lock word, which may race with other threads.
- bool success = GetObject()->CasLockWordWeakSequentiallyConsistent(lw, fat);
+ bool success = GetObject()->CasLockWordWeakRelease(lw, fat);
// Lock profiling.
if (success && owner_ != nullptr && lock_profiling_threshold_ != 0) {
// Do not abort on dex pc errors. This can easily happen when we want to dump a stack trace on
@@ -879,13 +879,16 @@
StackHandleScope<1> hs(self);
Handle<mirror::Object> h_obj(hs.NewHandle(obj));
while (true) {
- LockWord lock_word = h_obj->GetLockWord(true);
+ // We initially read the lockword with ordinary Java/relaxed semantics. When stronger
+ // semantics are needed, we address it below. Since GetLockWord bottoms out to a relaxed load,
+ // we can fix it later, in an infrequently executed case, with a fence.
+ LockWord lock_word = h_obj->GetLockWord(false);
switch (lock_word.GetState()) {
case LockWord::kUnlocked: {
+ // No ordering required for preceding lockword read, since we retest.
LockWord thin_locked(LockWord::FromThinLockId(thread_id, 0, lock_word.GCState()));
- if (h_obj->CasLockWordWeakSequentiallyConsistent(lock_word, thin_locked)) {
+ if (h_obj->CasLockWordWeakAcquire(lock_word, thin_locked)) {
AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */);
- // CasLockWord enforces more than the acquire ordering we need here.
return h_obj.Get(); // Success!
}
continue; // Go again.
@@ -893,19 +896,22 @@
case LockWord::kThinLocked: {
uint32_t owner_thread_id = lock_word.ThinLockOwner();
if (owner_thread_id == thread_id) {
+ // No ordering required for initial lockword read.
// We own the lock, increase the recursion count.
uint32_t new_count = lock_word.ThinLockCount() + 1;
if (LIKELY(new_count <= LockWord::kThinLockMaxCount)) {
LockWord thin_locked(LockWord::FromThinLockId(thread_id,
new_count,
lock_word.GCState()));
+ // Only this thread pays attention to the count. Thus there is no need for stronger
+ // than relaxed memory ordering.
if (!kUseReadBarrier) {
- h_obj->SetLockWord(thin_locked, true);
+ h_obj->SetLockWord(thin_locked, false /* volatile */);
AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */);
return h_obj.Get(); // Success!
} else {
// Use CAS to preserve the read barrier state.
- if (h_obj->CasLockWordWeakSequentiallyConsistent(lock_word, thin_locked)) {
+ if (h_obj->CasLockWordWeakRelaxed(lock_word, thin_locked)) {
AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */);
return h_obj.Get(); // Success!
}
@@ -922,20 +928,28 @@
// Contention.
contention_count++;
Runtime* runtime = Runtime::Current();
- if (contention_count <= runtime->GetMaxSpinsBeforeThinkLockInflation()) {
+ if (contention_count <= runtime->GetMaxSpinsBeforeThinLockInflation()) {
// TODO: Consider switching the thread state to kBlocked when we are yielding.
// Use sched_yield instead of NanoSleep since NanoSleep can wait much longer than the
// parameter you pass in. This can cause thread suspension to take excessively long
// and make long pauses. See b/16307460.
+ // TODO: We should literally spin first, without sched_yield. Sched_yield either does
+ // nothing (at significant expense), or guarantees that we wait at least microseconds.
+ // If the owner is running, I would expect the median lock hold time to be hundreds
+ // of nanoseconds or less.
sched_yield();
} else {
contention_count = 0;
+ // No ordering required for initial lockword read. Install rereads it anyway.
InflateThinLocked(self, h_obj, lock_word, 0);
}
}
continue; // Start from the beginning.
}
case LockWord::kFatLocked: {
+ // We should have done an acquire read of the lockword initially, to ensure
+ // visibility of the monitor data structure. Use an explicit fence instead.
+ QuasiAtomic::ThreadFenceAcquire();
Monitor* mon = lock_word.FatLockMonitor();
if (trylock) {
return mon->TryLock(self) ? h_obj.Get() : nullptr;
@@ -946,6 +960,8 @@
}
case LockWord::kHashCode:
// Inflate with the existing hashcode.
+ // Again no ordering required for initial lockword read, since we don't rely
+ // on the visibility of any prior computation.
Inflate(self, nullptr, h_obj.Get(), lock_word.GetHashCode());
continue; // Start from the beginning.
default: {
@@ -988,13 +1004,16 @@
}
if (!kUseReadBarrier) {
DCHECK_EQ(new_lw.ReadBarrierState(), 0U);
+ // TODO: This really only needs memory_order_release, but we currently have
+ // no way to specify that. In fact there seem to be no legitimate uses of SetLockWord
+ // with a final argument of true. This slows down x86 and ARMv7, but probably not v8.
h_obj->SetLockWord(new_lw, true);
AtraceMonitorUnlock();
// Success!
return true;
} else {
// Use CAS to preserve the read barrier state.
- if (h_obj->CasLockWordWeakSequentiallyConsistent(lock_word, new_lw)) {
+ if (h_obj->CasLockWordWeakRelease(lock_word, new_lw)) {
AtraceMonitorUnlock();
// Success!
return true;
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index 68815e7..926819d 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -39,7 +39,10 @@
#include "base/logging.h"
#include "events-inl.h"
#include "gc/allocation_listener.h"
+#include "gc/heap.h"
#include "instrumentation.h"
+#include "jit/jit.h"
+#include "jit/jit_code_cache.h"
#include "jni_env_ext-inl.h"
#include "jvmti_allocator.h"
#include "mirror/class.h"
@@ -53,6 +56,143 @@
using android::base::StringPrintf;
+// This visitor walks thread stacks and allocates and sets up the obsolete methods. It also does
+// some basic sanity checks that the obsolete method is sane.
+class ObsoleteMethodStackVisitor : public art::StackVisitor {
+ protected:
+ ObsoleteMethodStackVisitor(
+ art::Thread* thread,
+ art::LinearAlloc* allocator,
+ const std::unordered_set<art::ArtMethod*>& obsoleted_methods,
+ /*out*/std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps,
+ /*out*/bool* success,
+ /*out*/std::string* error_msg)
+ : StackVisitor(thread,
+ /*context*/nullptr,
+ StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ allocator_(allocator),
+ obsoleted_methods_(obsoleted_methods),
+ obsolete_maps_(obsolete_maps),
+ success_(success),
+ is_runtime_frame_(false),
+ error_msg_(error_msg) {
+ *success_ = true;
+ }
+
+ ~ObsoleteMethodStackVisitor() OVERRIDE {}
+
+ public:
+ // Returns true if we successfully installed obsolete methods on this thread, filling
+ // obsolete_maps_ with the translations if needed. Returns false and fills error_msg if we fail.
+ // The stack is cleaned up when we fail.
+ static bool UpdateObsoleteFrames(
+ art::Thread* thread,
+ art::LinearAlloc* allocator,
+ const std::unordered_set<art::ArtMethod*>& obsoleted_methods,
+ /*out*/std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps,
+ /*out*/std::string* error_msg) REQUIRES(art::Locks::mutator_lock_) {
+ bool success = true;
+ ObsoleteMethodStackVisitor visitor(thread,
+ allocator,
+ obsoleted_methods,
+ obsolete_maps,
+ &success,
+ error_msg);
+ visitor.WalkStack();
+ if (!success) {
+ RestoreFrames(thread, *obsolete_maps, error_msg);
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ static void RestoreFrames(
+ art::Thread* thread ATTRIBUTE_UNUSED,
+ const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsolete_maps ATTRIBUTE_UNUSED,
+ std::string* error_msg)
+ REQUIRES(art::Locks::mutator_lock_) {
+ LOG(FATAL) << "Restoring stack frames is not yet supported. Error was: " << *error_msg;
+ }
+
+ bool VisitFrame() OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+ art::ArtMethod* old_method = GetMethod();
+ // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt
+ // works through runtime methods.
+ bool prev_was_runtime_frame_ = is_runtime_frame_;
+ is_runtime_frame_ = old_method->IsRuntimeMethod();
+ if (obsoleted_methods_.find(old_method) != obsoleted_methods_.end()) {
+ // The check below works since when we deoptimize we set shadow frames for all frames until a
+ // native/runtime transition and for those set the return PC to a function that will complete
+ // the deoptimization. This does leave us with the unfortunate side-effect that frames just
+ // below runtime frames cannot be deoptimized at the moment.
+ // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt
+ // works through runtime methods.
+ // TODO b/33616143
+ if (!IsShadowFrame() && prev_was_runtime_frame_) {
+ *error_msg_ = StringPrintf("Deoptimization failed due to runtime method in stack.");
+ *success_ = false;
+ return false;
+ }
+ // We cannot ensure that the right dex file is used in inlined frames so we don't support
+ // redefining them.
+ DCHECK(!IsInInlinedFrame()) << "Inlined frames are not supported when using redefinition";
+ // TODO We should really support intrinsic obsolete methods.
+ // TODO We should really support redefining intrinsics.
+ // We don't support intrinsics so check for them here.
+ DCHECK(!old_method->IsIntrinsic());
+ art::ArtMethod* new_obsolete_method = nullptr;
+ auto obsolete_method_pair = obsolete_maps_->find(old_method);
+ if (obsolete_method_pair == obsolete_maps_->end()) {
+ // Create a new Obsolete Method and put it in the list.
+ art::Runtime* runtime = art::Runtime::Current();
+ art::ClassLinker* cl = runtime->GetClassLinker();
+ auto ptr_size = cl->GetImagePointerSize();
+ const size_t method_size = art::ArtMethod::Size(ptr_size);
+ auto* method_storage = allocator_->Alloc(GetThread(), method_size);
+ if (method_storage == nullptr) {
+ *success_ = false;
+ *error_msg_ = StringPrintf("Unable to allocate storage for obsolete version of '%s'",
+ old_method->PrettyMethod().c_str());
+ return false;
+ }
+ new_obsolete_method = new (method_storage) art::ArtMethod();
+ new_obsolete_method->CopyFrom(old_method, ptr_size);
+ DCHECK_EQ(new_obsolete_method->GetDeclaringClass(), old_method->GetDeclaringClass());
+ new_obsolete_method->SetIsObsolete();
+ obsolete_maps_->insert({old_method, new_obsolete_method});
+ // Update JIT Data structures to point to the new method.
+ art::jit::Jit* jit = art::Runtime::Current()->GetJit();
+ if (jit != nullptr) {
+ // Notify the JIT we are making this obsolete method. It will update the jit's internal
+ // structures to keep track of the new obsolete method.
+ jit->GetCodeCache()->MoveObsoleteMethod(old_method, new_obsolete_method);
+ }
+ } else {
+ new_obsolete_method = obsolete_method_pair->second;
+ }
+ DCHECK(new_obsolete_method != nullptr);
+ SetMethod(new_obsolete_method);
+ }
+ return true;
+ }
+
+ private:
+ // The linear allocator we should use to make new methods.
+ art::LinearAlloc* allocator_;
+ // The set of all methods which could be obsoleted.
+ const std::unordered_set<art::ArtMethod*>& obsoleted_methods_;
+ // A map from the original to the newly allocated obsolete method for frames on this thread. The
+ // values in this map must be added to the obsolete_methods_ (and obsolete_dex_caches_) fields of
+ // the redefined classes ClassExt by the caller.
+ std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps_;
+ bool* success_;
+ // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt
+ // works through runtime methods.
+ bool is_runtime_frame_;
+ std::string* error_msg_;
+};
+
// Moves dex data to an anonymous, read-only mmap'd region.
std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& original_location,
jint data_len,
@@ -76,6 +216,8 @@
return map;
}
+// TODO This should handle doing multiple classes at once so we need to do less cleanup when things
+// go wrong.
jvmtiError Redefiner::RedefineClass(ArtJvmTiEnv* env,
art::Runtime* runtime,
art::Thread* self,
@@ -116,6 +258,9 @@
*error_msg = os.str();
return ERR(INVALID_CLASS_FORMAT);
}
+ // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we
+ // are going to redefine.
+ art::jit::ScopedJitSuspend suspend_jit;
// Get shared mutator lock.
art::ScopedObjectAccess soa(self);
art::StackHandleScope<1> hs(self);
@@ -296,6 +441,107 @@
return true;
}
+struct CallbackCtx {
+ Redefiner* const r;
+ art::LinearAlloc* allocator;
+ std::unordered_map<art::ArtMethod*, art::ArtMethod*> obsolete_map;
+ std::unordered_set<art::ArtMethod*> obsolete_methods;
+ bool success;
+ std::string* error_msg;
+
+ CallbackCtx(Redefiner* self, art::LinearAlloc* alloc, std::string* error)
+ : r(self), allocator(alloc), success(true), error_msg(error) {}
+};
+
+void DoRestoreObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS {
+ CallbackCtx* data = reinterpret_cast<CallbackCtx*>(vdata);
+ ObsoleteMethodStackVisitor::RestoreFrames(t, data->obsolete_map, data->error_msg);
+}
+
+void DoAllocateObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS {
+ CallbackCtx* data = reinterpret_cast<CallbackCtx*>(vdata);
+ if (data->success) {
+ // Don't do anything if we already failed once.
+ data->success = ObsoleteMethodStackVisitor::UpdateObsoleteFrames(t,
+ data->allocator,
+ data->obsolete_methods,
+ &data->obsolete_map,
+ data->error_msg);
+ }
+}
+
+// This creates any ArtMethod* structures needed for obsolete methods and ensures that the stack is
+// updated so they will be run.
+bool Redefiner::FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) {
+ art::ScopedAssertNoThreadSuspension ns("No thread suspension during thread stack walking");
+ art::mirror::ClassExt* ext = art_klass->GetExtData();
+ CHECK(ext->GetObsoleteMethods() != nullptr);
+ CallbackCtx ctx(this, art_klass->GetClassLoader()->GetAllocator(), error_msg_);
+ // Add all the declared methods to the map
+ for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+ ctx.obsolete_methods.insert(&m);
+ }
+ for (art::ArtMethod* old_method : ctx.obsolete_methods) {
+ if (old_method->IsIntrinsic()) {
+ *error_msg_ = StringPrintf("Method '%s' is intrinsic and cannot be made obsolete!",
+ old_method->PrettyMethod().c_str());
+ return false;
+ }
+ }
+ {
+ art::MutexLock mu(self_, *art::Locks::thread_list_lock_);
+ art::ThreadList* list = art::Runtime::Current()->GetThreadList();
+ list->ForEach(DoAllocateObsoleteMethodsCallback, static_cast<void*>(&ctx));
+ if (!ctx.success) {
+ list->ForEach(DoRestoreObsoleteMethodsCallback, static_cast<void*>(&ctx));
+ return false;
+ }
+ }
+ FillObsoleteMethodMap(art_klass, ctx.obsolete_map);
+ return true;
+}
+
+// Fills the obsolete method map in the art_klass's extData. This is so obsolete methods are able to
+// figure out their DexCaches.
+void Redefiner::FillObsoleteMethodMap(
+ art::mirror::Class* art_klass,
+ const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes) {
+ int32_t index = 0;
+ art::mirror::ClassExt* ext_data = art_klass->GetExtData();
+ art::mirror::PointerArray* obsolete_methods = ext_data->GetObsoleteMethods();
+ art::mirror::ObjectArray<art::mirror::DexCache>* obsolete_dex_caches =
+ ext_data->GetObsoleteDexCaches();
+ int32_t num_method_slots = obsolete_methods->GetLength();
+ // Find the first empty index.
+ for (; index < num_method_slots; index++) {
+ if (obsolete_methods->GetElementPtrSize<art::ArtMethod*>(
+ index, art::kRuntimePointerSize) == nullptr) {
+ break;
+ }
+ }
+ // Make sure we have enough space.
+ CHECK_GT(num_method_slots, static_cast<int32_t>(obsoletes.size() + index));
+ CHECK(obsolete_dex_caches->Get(index) == nullptr);
+ // Fill in the map.
+ for (auto& obs : obsoletes) {
+ obsolete_methods->SetElementPtrSize(index, obs.second, art::kRuntimePointerSize);
+ obsolete_dex_caches->Set(index, art_klass->GetDexCache());
+ index++;
+ }
+}
+
+// TODO It should be possible to only deoptimize the specific obsolete methods.
+// TODO ReJitEverything can (sort of) fail. In certain cases it will skip deoptimizing some frames.
+// If one of these frames is an obsolete method we have a problem. b/33616143
+// TODO This shouldn't be necessary once we can ensure that the current method is not kept in
+// registers across suspend points.
+// TODO Pending b/33630159
+void Redefiner::EnsureObsoleteMethodsAreDeoptimized() {
+ art::ScopedAssertNoThreadSuspension nts("Deoptimizing everything!");
+ art::instrumentation::Instrumentation* i = runtime_->GetInstrumentation();
+ i->ReJitEverything("libOpenJkdJvmti - Class Redefinition");
+}
+
jvmtiError Redefiner::Run() {
art::StackHandleScope<5> hs(self_);
// TODO We might want to have a global lock (or one based on the class being redefined at least)
@@ -329,6 +575,13 @@
}
// Get the mirror class now that we aren't allocating anymore.
art::Handle<art::mirror::Class> art_class(hs.NewHandle(GetMirrorClass()));
+ // Disable GC and wait for it to be done if we are a moving GC. This is fine since we are done
+ // allocating so no deadlocks.
+ art::gc::Heap* heap = runtime_->GetHeap();
+ if (heap->IsGcConcurrentAndMoving()) {
+ // GC moving objects can cause deadlocks as we are deoptimizing the stack.
+ heap->IncrementDisableMovingGC(self_);
+ }
// Enable assertion that this thread isn't interrupted during this installation.
// After this we will need to do real cleanup in case of failure. Prior to this we could simply
// return and would let everything get cleaned up or harmlessly leaked.
@@ -338,6 +591,11 @@
self_->TransitionFromRunnableToSuspended(art::ThreadState::kNative);
runtime_->GetThreadList()->SuspendAll(
"Final installation of redefined Class!", /*long_suspend*/true);
+ // TODO We need to invalidate all breakpoints in the redefined class with the debugger.
+ // TODO We need to deal with any instrumentation/debugger deoptimized_methods_.
+ // TODO We need to update all debugger MethodIDs so they note the method they point to is
+ // obsolete or implement some other well defined semantics.
+ // TODO We need to decide on & implement semantics for JNI jmethodids when we redefine methods.
// TODO Might want to move this into a different type.
// Now we reach the part where we must do active cleanup if something fails.
// TODO We should really Retry if this fails instead of simply aborting.
@@ -345,11 +603,15 @@
art::ObjPtr<art::mirror::LongArray> original_dex_file_cookie(nullptr);
if (!UpdateJavaDexFile(java_dex_file.Get(),
new_dex_file_cookie.Get(),
- &original_dex_file_cookie)) {
+ &original_dex_file_cookie) ||
+ !FindAndAllocateObsoleteMethods(art_class.Get())) {
// Release suspendAll
runtime_->GetThreadList()->ResumeAll();
// Get back shared mutator lock as expected for return.
self_->TransitionFromSuspendedToRunnable();
+ if (heap->IsGcConcurrentAndMoving()) {
+ heap->DecrementDisableMovingGC(self_);
+ }
return result_;
}
if (!UpdateClass(art_class.Get(), new_dex_cache.Get())) {
@@ -359,26 +621,34 @@
runtime_->GetThreadList()->ResumeAll();
// Get back shared mutator lock as expected for return.
self_->TransitionFromSuspendedToRunnable();
+ if (heap->IsGcConcurrentAndMoving()) {
+ heap->DecrementDisableMovingGC(self_);
+ }
return result_;
}
- // Update the ClassObjects Keep the old DexCache (and other stuff) around so we can restore
- // functions/fields.
- // Verify the new Class.
- // Failure then undo updates to class
- // Do stack walks and allocate obsolete methods
- // Shrink the obsolete method maps if possible?
- // TODO find appropriate class loader. Allocate new dex files array. Pause all java treads.
- // Replace dex files array. Do stack scan + allocate obsoletes. Remove array if possible.
- // TODO We might want to ensure that all threads are stopped for this!
- // AddDexToClassPath();
- // TODO
- // Release suspendAll
+ // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have
+ // pointers to their ArtMethod's stashed in registers that they then use to attempt to hit the
+ // DexCache.
+ // TODO This can fail (leave some methods optimized) near runtime methods (including
+ // quick-to-interpreter transition function).
+ // TODO We probably don't need this at all once we have a way to ensure that the
+ // current_art_method is never stashed in a (physical) register by the JIT and lost to the
+ // stack-walker.
+ EnsureObsoleteMethodsAreDeoptimized();
+ // TODO Verify the new Class.
+ // TODO Failure then undo updates to class
+ // TODO Shrink the obsolete method maps if possible?
+ // TODO find appropriate class loader.
// TODO Put this into a scoped thing.
runtime_->GetThreadList()->ResumeAll();
// Get back shared mutator lock as expected for return.
self_->TransitionFromSuspendedToRunnable();
- // TODO Do this at a more reasonable place.
+ // TODO Do the dex_file_ release at a more reasonable place. This works but it muddles who really
+ // owns the DexFile.
dex_file_.release();
+ if (heap->IsGcConcurrentAndMoving()) {
+ heap->DecrementDisableMovingGC(self_);
+ }
return OK;
}
@@ -420,19 +690,24 @@
}
const art::DexFile::ProtoId* proto_id = dex_file_->FindProtoId(method_return_idx,
new_type_list);
- CHECK(proto_id != nullptr || old_type_list == nullptr);
// TODO Return false, cleanup.
+ CHECK(proto_id != nullptr || old_type_list == nullptr);
const art::DexFile::MethodId* method_id = dex_file_->FindMethodId(declaring_class_id,
*new_name_id,
*proto_id);
- CHECK(method_id != nullptr);
// TODO Return false, cleanup.
+ CHECK(method_id != nullptr);
uint32_t dex_method_idx = dex_file_->GetIndexForMethodId(*method_id);
method.SetDexMethodIndex(dex_method_idx);
linker->SetEntryPointsToInterpreter(&method);
method.SetCodeItemOffset(dex_file_->FindCodeItemOffset(class_def, dex_method_idx));
method.SetDexCacheResolvedMethods(new_dex_cache->GetResolvedMethods(), image_pointer_size);
method.SetDexCacheResolvedTypes(new_dex_cache->GetResolvedTypes(), image_pointer_size);
+ // Notify the jit that this method is redefined.
+ art::jit::Jit* jit = runtime_->GetJit();
+ if (jit != nullptr) {
+ jit->GetCodeCache()->NotifyMethodRedefined(&method);
+ }
}
return true;
}
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h
index 73cfc2b..9d23ce4 100644
--- a/runtime/openjdkjvmti/ti_redefine.h
+++ b/runtime/openjdkjvmti/ti_redefine.h
@@ -64,6 +64,8 @@
namespace openjdkjvmti {
// Class that can redefine a single class's methods.
+// TODO We should really make this be driven by an outside class so we can do multiple classes at
+// the same time and have less required cleanup.
class Redefiner {
public:
// Redefine the given class with the given dex data. Note this function does not take ownership of
@@ -124,6 +126,14 @@
// in the future. For now we will just take the memory hit.
bool EnsureClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_);
+ // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have
+ // pointers to their ArtMethods stashed in registers that they then use to attempt to hit the
+ // DexCache.
+ void EnsureObsoleteMethodsAreDeoptimized()
+ REQUIRES(art::Locks::mutator_lock_)
+ REQUIRES(!art::Locks::thread_list_lock_,
+ !art::Locks::classlinker_classes_lock_);
+
art::mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(art::Locks::mutator_lock_);
// This finds the java.lang.DexFile we will add the native DexFile to as part of the classpath.
@@ -170,6 +180,13 @@
bool UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
art::ObjPtr<art::mirror::DexCache> new_dex_cache)
REQUIRES(art::Locks::mutator_lock_);
+
+ bool FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass)
+ REQUIRES(art::Locks::mutator_lock_);
+
+ void FillObsoleteMethodMap(art::mirror::Class* art_klass,
+ const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes)
+ REQUIRES(art::Locks::mutator_lock_);
};
} // namespace openjdkjvmti
diff --git a/runtime/runtime.h b/runtime/runtime.h
index d40c631..8fc211c 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -268,7 +268,7 @@
return java_vm_.get();
}
- size_t GetMaxSpinsBeforeThinkLockInflation() const {
+ size_t GetMaxSpinsBeforeThinLockInflation() const {
return max_spins_before_thin_lock_inflation_;
}
diff --git a/runtime/stack.cc b/runtime/stack.cc
index 3fed7c9..f9efc0b 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -618,6 +618,17 @@
return result;
}
+void StackVisitor::SetMethod(ArtMethod* method) {
+ DCHECK(GetMethod() != nullptr);
+ if (cur_shadow_frame_ != nullptr) {
+ cur_shadow_frame_->SetMethod(method);
+ } else {
+ DCHECK(cur_quick_frame_ != nullptr);
+ CHECK(!IsInInlinedFrame()) << "We do not support setting inlined method's ArtMethod!";
+ *cur_quick_frame_ = method;
+ }
+}
+
static void AssertPcIsWithinQuickCode(ArtMethod* method, uintptr_t pc)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (method->IsNative() || method->IsRuntimeMethod() || method->IsProxyMethod()) {
diff --git a/runtime/stack.h b/runtime/stack.h
index b1e99e5..9dceb29 100644
--- a/runtime/stack.h
+++ b/runtime/stack.h
@@ -327,6 +327,12 @@
}
}
+ void SetMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_) {
+ DCHECK(method != nullptr);
+ DCHECK(method_ != nullptr);
+ method_ = method;
+ }
+
ArtMethod* GetMethod() const REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(method_ != nullptr);
return method_;
@@ -610,6 +616,10 @@
ArtMethod* GetMethod() const REQUIRES_SHARED(Locks::mutator_lock_);
+ // Sets this stack frame's method pointer. This requires a full lock of the MutatorLock. This
+ // doesn't work with inlined methods.
+ void SetMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_);
+
ArtMethod* GetOuterMethod() const {
return *GetCurrentQuickFrame();
}
diff --git a/runtime/stack_map.h b/runtime/stack_map.h
index dd7e531..5e556be 100644
--- a/runtime/stack_map.h
+++ b/runtime/stack_map.h
@@ -1093,7 +1093,9 @@
}
CodeInfoEncoding ExtractEncoding() const {
- return CodeInfoEncoding(region_.start());
+ CodeInfoEncoding encoding(region_.start());
+ AssertValidStackMap(encoding);
+ return encoding;
}
bool HasInlineInfo(const CodeInfoEncoding& encoding) const {
@@ -1254,6 +1256,18 @@
uint16_t number_of_dex_registers,
bool dump_stack_maps) const;
+ // Check that the code info has valid stack map and abort if it does not.
+ void AssertValidStackMap(const CodeInfoEncoding& encoding) const {
+ if (region_.size() != 0 && region_.size() < GetStackMapsSize(encoding)) {
+ LOG(FATAL) << region_.size() << "\n"
+ << encoding.header_size << "\n"
+ << encoding.non_header_size << "\n"
+ << encoding.number_of_location_catalog_entries << "\n"
+ << encoding.number_of_stack_maps << "\n"
+ << encoding.stack_map_size_in_bytes;
+ }
+ }
+
private:
MemoryRegion GetStackMaps(const CodeInfoEncoding& encoding) const {
return region_.size() == 0
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index 5f94a1b..c4058d6 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -245,18 +245,18 @@
bool VerifierDeps::IsInClassPath(ObjPtr<mirror::Class> klass) const {
DCHECK(klass != nullptr);
- ObjPtr<mirror::DexCache> dex_cache = klass->GetDexCache();
- if (dex_cache == nullptr) {
- // This is a synthesized class, in this case always an array. They are not
- // defined in the compiled DEX files and therefore are part of the classpath.
- // We do not record dependencies on arrays with component types in
- // the compiled DEX files, as the only thing that might change is their
- // access flags. If we were to change these flags in a breaking way, we would
- // need to enforce full verification again anyways by updating the vdex version.
- DCHECK(klass->IsArrayClass()) << klass->PrettyDescriptor();
- return false;
+ // For array types, we return whether the non-array component type
+ // is in the classpath.
+ while (klass->IsArrayClass()) {
+ klass = klass->GetComponentType();
}
+ if (klass->IsPrimitive()) {
+ return true;
+ }
+
+ ObjPtr<mirror::DexCache> dex_cache = klass->GetDexCache();
+ DCHECK(dex_cache != nullptr);
const DexFile* dex_file = dex_cache->GetDexFile();
DCHECK(dex_file != nullptr);
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 009170c..7b5ced1 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -52,6 +52,7 @@
jclass WellKnownClasses::java_lang_Daemons;
jclass WellKnownClasses::java_lang_Error;
jclass WellKnownClasses::java_lang_ExceptionInInitializerError;
+jclass WellKnownClasses::java_lang_invoke_MethodHandle;
jclass WellKnownClasses::java_lang_IllegalAccessError;
jclass WellKnownClasses::java_lang_NoClassDefFoundError;
jclass WellKnownClasses::java_lang_Object;
@@ -93,6 +94,8 @@
jmethodID WellKnownClasses::java_lang_Double_valueOf;
jmethodID WellKnownClasses::java_lang_Float_valueOf;
jmethodID WellKnownClasses::java_lang_Integer_valueOf;
+jmethodID WellKnownClasses::java_lang_invoke_MethodHandle_invoke;
+jmethodID WellKnownClasses::java_lang_invoke_MethodHandle_invokeExact;
jmethodID WellKnownClasses::java_lang_Long_valueOf;
jmethodID WellKnownClasses::java_lang_ref_FinalizerReference_add;
jmethodID WellKnownClasses::java_lang_ref_ReferenceQueue_add;
@@ -288,6 +291,7 @@
java_lang_Error = CacheClass(env, "java/lang/Error");
java_lang_ExceptionInInitializerError = CacheClass(env, "java/lang/ExceptionInInitializerError");
java_lang_IllegalAccessError = CacheClass(env, "java/lang/IllegalAccessError");
+ java_lang_invoke_MethodHandle = CacheClass(env, "java/lang/invoke/MethodHandle");
java_lang_NoClassDefFoundError = CacheClass(env, "java/lang/NoClassDefFoundError");
java_lang_reflect_Constructor = CacheClass(env, "java/lang/reflect/Constructor");
java_lang_reflect_Executable = CacheClass(env, "java/lang/reflect/Executable");
@@ -321,7 +325,12 @@
java_lang_Daemons_requestHeapTrim = CacheMethod(env, java_lang_Daemons, true, "requestHeapTrim", "()V");
java_lang_Daemons_start = CacheMethod(env, java_lang_Daemons, true, "start", "()V");
java_lang_Daemons_stop = CacheMethod(env, java_lang_Daemons, true, "stop", "()V");
-
+ java_lang_invoke_MethodHandle_invoke =
+ CacheMethod(env, java_lang_invoke_MethodHandle, false,
+ "invoke", "([Ljava/lang/Object;)Ljava/lang/Object;");
+ java_lang_invoke_MethodHandle_invokeExact =
+ CacheMethod(env, java_lang_invoke_MethodHandle, false,
+ "invokeExact", "([Ljava/lang/Object;)Ljava/lang/Object;");
ScopedLocalRef<jclass> java_lang_ref_FinalizerReference(env, env->FindClass("java/lang/ref/FinalizerReference"));
java_lang_ref_FinalizerReference_add = CacheMethod(env, java_lang_ref_FinalizerReference.get(), true, "add", "(Ljava/lang/Object;)V");
ScopedLocalRef<jclass> java_lang_ref_ReferenceQueue(env, env->FindClass("java/lang/ref/ReferenceQueue"));
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index 227996a..371be61 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -63,6 +63,7 @@
static jclass java_lang_Error;
static jclass java_lang_ExceptionInInitializerError;
static jclass java_lang_IllegalAccessError;
+ static jclass java_lang_invoke_MethodHandle;
static jclass java_lang_NoClassDefFoundError;
static jclass java_lang_Object;
static jclass java_lang_OutOfMemoryError;
@@ -103,6 +104,8 @@
static jmethodID java_lang_Double_valueOf;
static jmethodID java_lang_Float_valueOf;
static jmethodID java_lang_Integer_valueOf;
+ static jmethodID java_lang_invoke_MethodHandle_invoke;
+ static jmethodID java_lang_invoke_MethodHandle_invokeExact;
static jmethodID java_lang_Long_valueOf;
static jmethodID java_lang_ref_FinalizerReference_add;
static jmethodID java_lang_ref_ReferenceQueue_add;
diff --git a/test/631-checker-fp-abs/expected.txt b/test/631-checker-fp-abs/expected.txt
new file mode 100644
index 0000000..b0aad4d
--- /dev/null
+++ b/test/631-checker-fp-abs/expected.txt
@@ -0,0 +1 @@
+passed
diff --git a/test/631-checker-fp-abs/info.txt b/test/631-checker-fp-abs/info.txt
new file mode 100644
index 0000000..0a1499e
--- /dev/null
+++ b/test/631-checker-fp-abs/info.txt
@@ -0,0 +1 @@
+Tests on floating-point Math.abs.
diff --git a/test/631-checker-fp-abs/src/Main.java b/test/631-checker-fp-abs/src/Main.java
new file mode 100644
index 0000000..0f85dc6
--- /dev/null
+++ b/test/631-checker-fp-abs/src/Main.java
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+/**
+ * A few tests of Math.abs for floating-point data.
+ *
+ * Note, as a "quality of implementation", rather than pure "spec compliance",
+ * we require that Math.abs() clears the sign bit (but changes nothing else)
+ * for all numbers, including NaN (signaling NaN may become quiet though).
+ */
+public class Main {
+
+ private static final int SPQUIET = 1 << 22;
+ private static final long DPQUIET = 1L << 51;
+
+ public static boolean doThrow = false;
+
+ /// CHECK-START: float Main.$opt$noinline$absSP(float) intrinsics_recognition (after)
+ /// CHECK-DAG: <<Result:f\d+>> InvokeStaticOrDirect intrinsic:MathAbsFloat
+ /// CHECK-DAG: Return [<<Result>>]
+ private static float $opt$noinline$absSP(float f) {
+ if (doThrow) {
+ throw new Error("Something to prevent inlining");
+ }
+ return Math.abs(f);
+ }
+
+ /// CHECK-START: double Main.$opt$noinline$absDP(double) intrinsics_recognition (after)
+ /// CHECK-DAG: <<Result:d\d+>> InvokeStaticOrDirect intrinsic:MathAbsDouble
+ /// CHECK-DAG: Return [<<Result>>]
+ private static double $opt$noinline$absDP(double d) {
+ if (doThrow) {
+ throw new Error("Something to prevent inlining");
+ }
+ return Math.abs(d);
+ }
+
+ public static void main(String args[]) {
+ // A few obvious numbers.
+ for (float f = -100.0f; f < 0.0f; f += 0.5f) {
+ expectEqualsSP(-f, $opt$noinline$absSP(f));
+ }
+ for (float f = 0.0f; f <= 100.0f; f += 0.5f) {
+ expectEqualsSP(f, $opt$noinline$absSP(f));
+ }
+ for (float f = -1.5f; f <= -1.499f; f = Math.nextAfter(f, Float.POSITIVE_INFINITY)) {
+ expectEqualsSP(-f, $opt$noinline$absSP(f));
+ }
+ for (float f = 1.499f; f <= 1.5f; f = Math.nextAfter(f, Float.POSITIVE_INFINITY)) {
+ expectEqualsSP(f, $opt$noinline$absSP(f));
+ }
+
+ // Zero
+ expectEquals32(0, Float.floatToRawIntBits($opt$noinline$absSP(+0.0f)));
+ expectEquals32(0, Float.floatToRawIntBits($opt$noinline$absSP(-0.0f)));
+
+ // Inf.
+ expectEqualsSP(Float.POSITIVE_INFINITY, $opt$noinline$absSP(Float.NEGATIVE_INFINITY));
+ expectEqualsSP(Float.POSITIVE_INFINITY, $opt$noinline$absSP(Float.POSITIVE_INFINITY));
+
+ // A few NaN numbers.
+ int[] spnans = {
+ 0x7f800001,
+ 0x7fa00000,
+ 0x7fc00000,
+ 0x7fffffff,
+ 0xff800001,
+ 0xffa00000,
+ 0xffc00000,
+ 0xffffffff
+ };
+ for (int i = 0; i < spnans.length; i++) {
+ float f = Float.intBitsToFloat(spnans[i]);
+ expectEqualsNaN32(
+ spnans[i] & Integer.MAX_VALUE,
+ Float.floatToRawIntBits($opt$noinline$absSP(f)));
+ }
+
+ // A few obvious numbers.
+ for (double d = -100.0; d < 0.0; d += 0.5) {
+ expectEqualsDP(-d, $opt$noinline$absDP(d));
+ }
+ for (double d = 0.0; d <= 100.0; d += 0.5) {
+ expectEqualsDP(d, $opt$noinline$absDP(d));
+ }
+ for (double d = -1.5d; d <= -1.49999999999d; d = Math.nextAfter(d, Double.POSITIVE_INFINITY)) {
+ expectEqualsDP(-d, $opt$noinline$absDP(d));
+ }
+ for (double d = 1.49999999999d; d <= 1.5; d = Math.nextAfter(d, Double.POSITIVE_INFINITY)) {
+ expectEqualsDP(d, $opt$noinline$absDP(d));
+ }
+
+ // Zero
+ expectEquals64(0L, Double.doubleToRawLongBits($opt$noinline$absDP(+0.0f)));
+ expectEquals64(0L, Double.doubleToRawLongBits($opt$noinline$absDP(-0.0f)));
+
+ // Inf.
+ expectEqualsDP(Double.POSITIVE_INFINITY, $opt$noinline$absDP(Double.NEGATIVE_INFINITY));
+ expectEqualsDP(Double.POSITIVE_INFINITY, $opt$noinline$absDP(Double.POSITIVE_INFINITY));
+
+ // A few NaN numbers.
+ long[] dpnans = {
+ 0x7ff0000000000001L,
+ 0x7ff4000000000000L,
+ 0x7ff8000000000000L,
+ 0x7fffffffffffffffL,
+ 0xfff0000000000001L,
+ 0xfff4000000000000L,
+ 0xfff8000000000000L,
+ 0xffffffffffffffffL
+ };
+ for (int i = 0; i < dpnans.length; i++) {
+ double d = Double.longBitsToDouble(dpnans[i]);
+ expectEqualsNaN64(
+ dpnans[i] & Long.MAX_VALUE,
+ Double.doubleToRawLongBits($opt$noinline$absDP(d)));
+ }
+
+ System.out.println("passed");
+ }
+
+ private static void expectEquals32(int expected, int result) {
+ if (expected != result) {
+ throw new Error("Expected: 0x" + Integer.toHexString(expected)
+ + ", found: 0x" + Integer.toHexString(result));
+ }
+ }
+
+ // We allow that an expected NaN result has become quiet.
+ private static void expectEqualsNaN32(int expected, int result) {
+ if (expected != result && (expected | SPQUIET) != result) {
+ throw new Error("Expected: 0x" + Integer.toHexString(expected)
+ + ", found: 0x" + Integer.toHexString(result));
+ }
+ }
+
+ private static void expectEquals64(long expected, long result) {
+ if (expected != result) {
+ throw new Error("Expected: 0x" + Long.toHexString(expected)
+ + ", found: 0x" + Long.toHexString(result));
+ }
+ }
+
+ // We allow that an expected NaN result has become quiet.
+ private static void expectEqualsNaN64(long expected, long result) {
+ if (expected != result && (expected | DPQUIET) != result) {
+ throw new Error("Expected: 0x" + Long.toHexString(expected)
+ + ", found: 0x" + Long.toHexString(result));
+ }
+ }
+
+ private static void expectEqualsSP(float expected, float result) {
+ if (expected != result) {
+ throw new Error("Expected: " + expected + ", found: " + result);
+ }
+ }
+
+ private static void expectEqualsDP(double expected, double result) {
+ if (expected != result) {
+ throw new Error("Expected: " + expected + ", found: " + result);
+ }
+ }
+}
diff --git a/test/901-hello-ti-agent/run b/test/901-hello-ti-agent/run
index 8079a8c..4379349 100755
--- a/test/901-hello-ti-agent/run
+++ b/test/901-hello-ti-agent/run
@@ -14,14 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=901-hello-ti-agent \
- --android-runtime-option -Xplugin:${plugin}
+ --jvmti
diff --git a/test/902-hello-transformation/run b/test/902-hello-transformation/run
index 94a8b2d..4379349 100755
--- a/test/902-hello-transformation/run
+++ b/test/902-hello-transformation/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
- if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
- else
- other_args=""
- fi
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=902-hello-transformation,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- --android-runtime-option -Xfully-deoptable \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/903-hello-tagging/run b/test/903-hello-tagging/run
index 5e3c0bd..4379349 100755
--- a/test/903-hello-tagging/run
+++ b/test/903-hello-tagging/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
-else
- other_args=""
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=903-hello-tagging,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/904-object-allocation/run b/test/904-object-allocation/run
index 2f7ad21..4379349 100755
--- a/test/904-object-allocation/run
+++ b/test/904-object-allocation/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
-else
- other_args=""
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=904-object-allocation,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/905-object-free/run b/test/905-object-free/run
index 753b742..4379349 100755
--- a/test/905-object-free/run
+++ b/test/905-object-free/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
-else
- other_args=""
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=905-object-free,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/906-iterate-heap/run b/test/906-iterate-heap/run
index 3e135a3..4379349 100755
--- a/test/906-iterate-heap/run
+++ b/test/906-iterate-heap/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
-else
- other_args=""
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=906-iterate-heap,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/907-get-loaded-classes/run b/test/907-get-loaded-classes/run
index 3f5a059..4379349 100755
--- a/test/907-get-loaded-classes/run
+++ b/test/907-get-loaded-classes/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
-else
- other_args=""
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=907-get-loaded-classes,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/908-gc-start-finish/run b/test/908-gc-start-finish/run
index 2fc35f0..4379349 100755
--- a/test/908-gc-start-finish/run
+++ b/test/908-gc-start-finish/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
-else
- other_args=""
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=908-gc-start-finish,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/910-methods/run b/test/910-methods/run
index 4dd2555..4379349 100755
--- a/test/910-methods/run
+++ b/test/910-methods/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
-else
- other_args=""
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=910-methods,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/911-get-stack-trace/run b/test/911-get-stack-trace/run
index 43fc325..4379349 100755
--- a/test/911-get-stack-trace/run
+++ b/test/911-get-stack-trace/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
-else
- other_args=""
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=911-get-stack-trace,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/912-classes/run b/test/912-classes/run
index 64bbb98..4379349 100755
--- a/test/912-classes/run
+++ b/test/912-classes/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
-else
- other_args=""
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=912-classes,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/913-heaps/run b/test/913-heaps/run
index 7bd8cbd..4379349 100755
--- a/test/913-heaps/run
+++ b/test/913-heaps/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
-else
- other_args=""
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=913-heaps,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/914-hello-obsolescence/build b/test/914-hello-obsolescence/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/914-hello-obsolescence/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+./default-build "$@" --experimental agents
diff --git a/test/914-hello-obsolescence/expected.txt b/test/914-hello-obsolescence/expected.txt
new file mode 100644
index 0000000..83efda1
--- /dev/null
+++ b/test/914-hello-obsolescence/expected.txt
@@ -0,0 +1,9 @@
+hello
+Not doing anything here
+goodbye
+hello
+transforming calling function
+goodbye
+Hello - Transformed
+Not doing anything here
+Goodbye - Transformed
diff --git a/test/914-hello-obsolescence/info.txt b/test/914-hello-obsolescence/info.txt
new file mode 100644
index 0000000..c8b892c
--- /dev/null
+++ b/test/914-hello-obsolescence/info.txt
@@ -0,0 +1 @@
+Tests basic obsolete method support
diff --git a/test/914-hello-obsolescence/run b/test/914-hello-obsolescence/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/914-hello-obsolescence/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+./default-run "$@" --experimental agents \
+ --experimental runtime-plugins \
+ --jvmti
diff --git a/test/914-hello-obsolescence/src/Main.java b/test/914-hello-obsolescence/src/Main.java
new file mode 100644
index 0000000..46266ef
--- /dev/null
+++ b/test/914-hello-obsolescence/src/Main.java
@@ -0,0 +1,73 @@
+/*
+ * 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.util.Base64;
+
+public class Main {
+ // class Transform {
+ // public void sayHi(Runnable r) {
+ // System.out.println("Hello - Transformed");
+ // r.run();
+ // System.out.println("Goodbye - Transformed");
+ // }
+ // }
+ private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+ "yv66vgAAADQAJAoACAARCQASABMIABQKABUAFgsAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" +
+ "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" +
+ "KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAAkACgcAHAwAHQAeAQATSGVsbG8gLSBU" +
+ "cmFuc2Zvcm1lZAcAHwwAIAAhBwAiDAAjAAoBABVHb29kYnllIC0gVHJhbnNmb3JtZWQBAAlUcmFu" +
+ "c2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZh" +
+ "L2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZh" +
+ "L2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAABwAIAAAAAAACAAAA" +
+ "CQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAQABAA0ADgABAAsAAAA7AAIA" +
+ "AgAAABeyAAISA7YABCu5AAUBALIAAhIGtgAEsQAAAAEADAAAABIABAAAAAMACAAEAA4ABQAWAAYA" +
+ "AQAPAAAAAgAQ");
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQAYeAMMXgYWxoeSHAS9EWKCCtVRSAGpqZVQAwAAcAAAAHhWNBIAAAAAAAAAALACAAAR" +
+ "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAUAAAD8AAAAAQAAACQBAAAMAgAARAEAAKIB" +
+ "AACqAQAAwQEAANYBAADjAQAA+gEAAA4CAAAkAgAAOAIAAEwCAABcAgAAXwIAAGMCAAB3AgAAfAIA" +
+ "AIUCAACKAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" +
+ "lAEAAAsAAAAGAAAAnAEAAAUAAQANAAAAAAAAAAAAAAAAAAEAEAAAAAEAAgAOAAAAAgAAAAAAAAAD" +
+ "AAAADwAAAAAAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAJ8CAAAAAAAAAQABAAEAAACRAgAABAAAAHAQ" +
+ "AwAAAA4ABAACAAIAAACWAgAAFAAAAGIAAAAbAQIAAABuIAIAEAByEAQAAwBiAAAAGwEBAAAAbiAC" +
+ "ABAADgABAAAAAwAAAAEAAAAEAAY8aW5pdD4AFUdvb2RieWUgLSBUcmFuc2Zvcm1lZAATSGVsbG8g" +
+ "LSBUcmFuc2Zvcm1lZAALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEv" +
+ "bGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJM" +
+ "amF2YS9sYW5nL1N5c3RlbTsADlRyYW5zZm9ybS5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00" +
+ "LjEzAANvdXQAB3ByaW50bG4AA3J1bgAFc2F5SGkAAQAHDgADAQAHDoc8hwAAAAEBAICABMQCAQHc" +
+ "AgAAAA0AAAAAAAAAAQAAAAAAAAABAAAAEQAAAHAAAAACAAAABwAAALQAAAADAAAAAwAAANAAAAAE" +
+ "AAAAAQAAAPQAAAAFAAAABQAAAPwAAAAGAAAAAQAAACQBAAABIAAAAgAAAEQBAAABEAAAAgAAAJQB" +
+ "AAACIAAAEQAAAKIBAAADIAAAAgAAAJECAAAAIAAAAQAAAJ8CAAAAEAAAAQAAALACAAA=");
+
+ public static void main(String[] args) {
+ System.loadLibrary(args[1]);
+ doTest(new Transform());
+ }
+
+ public static void doTest(Transform t) {
+ t.sayHi(() -> { System.out.println("Not doing anything here"); });
+ t.sayHi(() -> {
+ System.out.println("transforming calling function");
+ doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+ });
+ t.sayHi(() -> { System.out.println("Not doing anything here"); });
+ }
+
+ // Transforms the class
+ private static native void doCommonClassRedefinition(Class<?> target,
+ byte[] classfile,
+ byte[] dexfile);
+}
diff --git a/test/914-hello-obsolescence/src/Transform.java b/test/914-hello-obsolescence/src/Transform.java
new file mode 100644
index 0000000..8cda6cd
--- /dev/null
+++ b/test/914-hello-obsolescence/src/Transform.java
@@ -0,0 +1,30 @@
+/*
+ * 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(Runnable r) {
+ // Use lower 'h' to make sure the string will have a different string id
+ // than the transformation (the transformation code is the same except
+ // the actual printed String, which was making the test inacurately passing
+ // in JIT mode when loading the string from the dex cache, as the string ids
+ // of the two different strings were the same).
+ // We know the string ids will be different because lexicographically:
+ // "Hello" < "LTransform;" < "hello".
+ System.out.println("hello");
+ r.run();
+ System.out.println("goodbye");
+ }
+}
diff --git a/test/915-obsolete-2/build b/test/915-obsolete-2/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/915-obsolete-2/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+./default-build "$@" --experimental agents
diff --git a/test/915-obsolete-2/expected.txt b/test/915-obsolete-2/expected.txt
new file mode 100644
index 0000000..04aff3a
--- /dev/null
+++ b/test/915-obsolete-2/expected.txt
@@ -0,0 +1,21 @@
+Pre Start private method call
+hello - private
+Post Start private method call
+Not doing anything here
+Pre Finish private method call
+goodbye - private
+Post Finish private method call
+Pre Start private method call
+hello - private
+Post Start private method call
+transforming calling function
+Pre Finish private method call
+Goodbye - private - Transformed
+Post Finish private method call
+Pre Start private method call - Transformed
+Hello - private - Transformed
+Post Start private method call - Transformed
+Not doing anything here
+Pre Finish private method call - Transformed
+Goodbye - private - Transformed
+Post Finish private method call - Transformed
diff --git a/test/915-obsolete-2/info.txt b/test/915-obsolete-2/info.txt
new file mode 100644
index 0000000..c8b892c
--- /dev/null
+++ b/test/915-obsolete-2/info.txt
@@ -0,0 +1 @@
+Tests basic obsolete method support
diff --git a/test/915-obsolete-2/run b/test/915-obsolete-2/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/915-obsolete-2/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+./default-run "$@" --experimental agents \
+ --experimental runtime-plugins \
+ --jvmti
diff --git a/test/915-obsolete-2/src/Main.java b/test/915-obsolete-2/src/Main.java
new file mode 100644
index 0000000..bbeb726
--- /dev/null
+++ b/test/915-obsolete-2/src/Main.java
@@ -0,0 +1,99 @@
+/*
+ * 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.util.Base64;
+
+public class Main {
+ // class Transform {
+ // private void Start() {
+ // System.out.println("Hello - private - Transformed");
+ // }
+ //
+ // private void Finish() {
+ // System.out.println("Goodbye - private - Transformed");
+ // }
+ //
+ // public void sayHi(Runnable r) {
+ // System.out.println("Pre Start private method call - Transformed");
+ // Start();
+ // System.out.println("Post Start private method call - Transformed");
+ // r.run();
+ // System.out.println("Pre Finish private method call - Transformed");
+ // Finish();
+ // System.out.println("Post Finish private method call - Transformed");
+ // }
+ // }
+ private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+ "yv66vgAAADQAMgoADgAZCQAaABsIABwKAB0AHggAHwgAIAoADQAhCAAiCwAjACQIACUKAA0AJggA" +
+ "JwcAKAcAKQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVTdGFydAEA" +
+ "BkZpbmlzaAEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7KVYBAApTb3VyY2VGaWxlAQAO" +
+ "VHJhbnNmb3JtLmphdmEMAA8AEAcAKgwAKwAsAQAdSGVsbG8gLSBwcml2YXRlIC0gVHJhbnNmb3Jt" +
+ "ZWQHAC0MAC4ALwEAH0dvb2RieWUgLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQBACtQcmUgU3RhcnQg" +
+ "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAATABABACxQb3N0IFN0YXJ0IHByaXZh" +
+ "dGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAcAMAwAMQAQAQAsUHJlIEZpbmlzaCBwcml2YXRl" +
+ "IG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQMABQAEAEALVBvc3QgRmluaXNoIHByaXZhdGUgbWV0" +
+ "aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAEACVRyYW5zZm9ybQEAEGphdmEvbGFuZy9PYmplY3QBABBq" +
+ "YXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9Q" +
+ "cmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABJqYXZhL2xhbmcv" +
+ "UnVubmFibGUBAANydW4AIAANAA4AAAAAAAQAAAAPABAAAQARAAAAHQABAAEAAAAFKrcAAbEAAAAB" +
+ "ABIAAAAGAAEAAAABAAIAEwAQAAEAEQAAACUAAgABAAAACbIAAhIDtgAEsQAAAAEAEgAAAAoAAgAA" +
+ "AAMACAAEAAIAFAAQAAEAEQAAACUAAgABAAAACbIAAhIFtgAEsQAAAAEAEgAAAAoAAgAAAAcACAAI" +
+ "AAEAFQAWAAEAEQAAAGMAAgACAAAAL7IAAhIGtgAEKrcAB7IAAhIItgAEK7kACQEAsgACEgq2AAQq" +
+ "twALsgACEgy2AASxAAAAAQASAAAAIgAIAAAACwAIAAwADAANABQADgAaAA8AIgAQACYAEQAuABIA" +
+ "AQAXAAAAAgAY");
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQCM0QYTJmX+NsZXkImojgSkJtXyuew3oaXcBAAAcAAAAHhWNBIAAAAAAAAAADwEAAAX" +
+ "AAAAcAAAAAcAAADMAAAAAwAAAOgAAAABAAAADAEAAAcAAAAUAQAAAQAAAEwBAABwAwAAbAEAAD4C" +
+ "AABGAgAATgIAAG8CAACOAgAAmwIAALICAADGAgAA3AIAAPACAAAEAwAAMwMAAGEDAACPAwAAvAMA" +
+ "AMMDAADTAwAA1gMAANoDAADuAwAA8wMAAPwDAAABBAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA" +
+ "EAAAABAAAAAGAAAAAAAAABEAAAAGAAAAMAIAABEAAAAGAAAAOAIAAAUAAQATAAAAAAAAAAAAAAAA" +
+ "AAAAAQAAAAAAAAAOAAAAAAABABYAAAABAAIAFAAAAAIAAAAAAAAAAwAAABUAAAAAAAAAAAAAAAIA" +
+ "AAAAAAAADwAAAAAAAAAmBAAAAAAAAAEAAQABAAAACAQAAAQAAABwEAUAAAAOAAMAAQACAAAADQQA" +
+ "AAkAAABiAAAAGwECAAAAbiAEABAADgAAAAMAAQACAAAAEwQAAAkAAABiAAAAGwEDAAAAbiAEABAA" +
+ "DgAAAAQAAgACAAAAGQQAACoAAABiAAAAGwENAAAAbiAEABAAcBACAAIAYgAAABsBCwAAAG4gBAAQ" +
+ "AHIQBgADAGIAAAAbAQwAAABuIAQAEABwEAEAAgBiAAAAGwEKAAAAbiAEABAADgABAAAAAwAAAAEA" +
+ "AAAEAAY8aW5pdD4ABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBUcmFuc2Zvcm1lZAAdSGVs" +
+ "bG8gLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAC0xUcmFuc2Zvcm07ABVMamF2YS9pby9QcmludFN0" +
+ "cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwAUTGphdmEvbGFuZy9SdW5uYWJsZTsAEkxqYXZhL2xh" +
+ "bmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AC1Qb3N0IEZpbmlzaCBwcml2YXRlIG1ldGhv" +
+ "ZCBjYWxsIC0gVHJhbnNmb3JtZWQALFBvc3QgU3RhcnQgcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRy" +
+ "YW5zZm9ybWVkACxQcmUgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAr" +
+ "UHJlIFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAFU3RhcnQADlRyYW5z" +
+ "Zm9ybS5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00LjEzAANvdXQAB3ByaW50bG4AA3J1bgAF" +
+ "c2F5SGkAAQAHDgAHAAcOhwADAAcOhwALAQAHDoc8hzyHPIcAAAADAQCAgATsAgEChAMBAqgDAwHM" +
+ "Aw0AAAAAAAAAAQAAAAAAAAABAAAAFwAAAHAAAAACAAAABwAAAMwAAAADAAAAAwAAAOgAAAAEAAAA" +
+ "AQAAAAwBAAAFAAAABwAAABQBAAAGAAAAAQAAAEwBAAABIAAABAAAAGwBAAABEAAAAgAAADACAAAC" +
+ "IAAAFwAAAD4CAAADIAAABAAAAAgEAAAAIAAAAQAAACYEAAAAEAAAAQAAADwEAAA=");
+
+ public static void main(String[] args) {
+ System.loadLibrary(args[1]);
+ doTest(new Transform());
+ }
+
+ public static void doTest(Transform t) {
+ t.sayHi(() -> { System.out.println("Not doing anything here"); });
+ t.sayHi(() -> {
+ System.out.println("transforming calling function");
+ doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+ });
+ t.sayHi(() -> { System.out.println("Not doing anything here"); });
+ }
+
+ // Transforms the class
+ private static native void doCommonClassRedefinition(Class<?> target,
+ byte[] classfile,
+ byte[] dexfile);
+}
diff --git a/test/915-obsolete-2/src/Transform.java b/test/915-obsolete-2/src/Transform.java
new file mode 100644
index 0000000..e914e29
--- /dev/null
+++ b/test/915-obsolete-2/src/Transform.java
@@ -0,0 +1,35 @@
+/*
+ * 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 {
+ private void Start() {
+ System.out.println("hello - private");
+ }
+
+ private void Finish() {
+ System.out.println("goodbye - private");
+ }
+
+ public void sayHi(Runnable r) {
+ System.out.println("Pre Start private method call");
+ Start();
+ System.out.println("Post Start private method call");
+ r.run();
+ System.out.println("Pre Finish private method call");
+ Finish();
+ System.out.println("Post Finish private method call");
+ }
+}
diff --git a/test/916-obsolete-jit/build b/test/916-obsolete-jit/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/916-obsolete-jit/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+./default-build "$@" --experimental agents
diff --git a/test/916-obsolete-jit/expected.txt b/test/916-obsolete-jit/expected.txt
new file mode 100644
index 0000000..4caefc6
--- /dev/null
+++ b/test/916-obsolete-jit/expected.txt
@@ -0,0 +1,21 @@
+Pre Start private method call
+hello - private
+Post Start private method call
+Not doing anything here
+Pre Finish private method call
+goodbye - private
+Post Finish private method call
+Pre Start private method call
+hello - private
+Post Start private method call
+transforming calling function
+Pre Finish private method call
+Goodbye - private - Transformed
+Post Finish private method call
+pre Start private method call - Transformed
+Hello - private - Transformed
+post Start private method call - Transformed
+Not doing anything here
+pre Finish private method call - Transformed
+Goodbye - private - Transformed
+post Finish private method call - Transformed
diff --git a/test/916-obsolete-jit/info.txt b/test/916-obsolete-jit/info.txt
new file mode 100644
index 0000000..c8b892c
--- /dev/null
+++ b/test/916-obsolete-jit/info.txt
@@ -0,0 +1 @@
+Tests basic obsolete method support
diff --git a/test/916-obsolete-jit/run b/test/916-obsolete-jit/run
new file mode 100755
index 0000000..9056211
--- /dev/null
+++ b/test/916-obsolete-jit/run
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# Copyright 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.
+
+# We are testing the redefinition of compiled code but with jvmti we only allow
+# jitted compiled code so always add the --jit argument.
+if [[ "$@" == *"--jit"* ]]; then
+ other_args=""
+else
+ other_args="--jit"
+fi
+./default-run "$@" --experimental agents \
+ --experimental runtime-plugins \
+ ${other_args} \
+ --jvmti
diff --git a/test/916-obsolete-jit/src/Main.java b/test/916-obsolete-jit/src/Main.java
new file mode 100644
index 0000000..74eb003
--- /dev/null
+++ b/test/916-obsolete-jit/src/Main.java
@@ -0,0 +1,210 @@
+/*
+ * 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.util.function.Consumer;
+import java.lang.reflect.Method;
+import java.util.Base64;
+
+public class Main {
+
+ // import java.util.function.Consumer;
+ //
+ // class Transform {
+ // private void Start(Consumer<String> reporter) {
+ // reporter.accept("Hello - private - Transformed");
+ // }
+ //
+ // private void Finish(Consumer<String> reporter) {
+ // reporter.accept("Goodbye - private - Transformed");
+ // }
+ //
+ // public void sayHi(Runnable r, Consumer<String> reporter) {
+ // reporter.accept("pre Start private method call - Transformed");
+ // Start(reporter);
+ // reporter.accept("post Start private method call - Transformed");
+ // r.run();
+ // reporter.accept("pre Finish private method call - Transformed");
+ // Finish(reporter);
+ // reporter.accept("post Finish private method call - Transformed");
+ // }
+ // }
+ private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+ "yv66vgAAADQAMAoADQAcCAAdCwAeAB8IACAIACEKAAwAIggAIwsAJAAlCAAmCgAMACcIACgHACkH" +
+ "ACoBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFU3RhcnQBACAoTGph" +
+ "dmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjspVgEACVNpZ25hdHVyZQEANChMamF2YS91dGlsL2Z1" +
+ "bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xhbmcvU3RyaW5nOz47KVYBAAZGaW5pc2gBAAVzYXlIaQEA" +
+ "NChMamF2YS9sYW5nL1J1bm5hYmxlO0xqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7KVYBAEgo" +
+ "TGphdmEvbGFuZy9SdW5uYWJsZTtMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xh" +
+ "bmcvU3RyaW5nOz47KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAA4ADwEAHUhlbGxv" +
+ "IC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVkBwArDAAsAC0BAB9Hb29kYnllIC0gcHJpdmF0ZSAtIFRy" +
+ "YW5zZm9ybWVkAQArcHJlIFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAwA" +
+ "EgATAQAscG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQHAC4MAC8A" +
+ "DwEALHByZSBGaW5pc2ggcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAAWABMBAC1w" +
+ "b3N0IEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQBAAlUcmFuc2Zvcm0B" +
+ "ABBqYXZhL2xhbmcvT2JqZWN0AQAbamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyAQAGYWNjZXB0" +
+ "AQAVKExqYXZhL2xhbmcvT2JqZWN0OylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAADAAN" +
+ "AAAAAAAEAAAADgAPAAEAEAAAAB0AAQABAAAABSq3AAGxAAAAAQARAAAABgABAAAAEwACABIAEwAC" +
+ "ABAAAAAlAAIAAgAAAAkrEgK5AAMCALEAAAABABEAAAAKAAIAAAAVAAgAFgAUAAAAAgAVAAIAFgAT" +
+ "AAIAEAAAACUAAgACAAAACSsSBLkAAwIAsQAAAAEAEQAAAAoAAgAAABkACAAaABQAAAACABUAAQAX" +
+ "ABgAAgAQAAAAZQACAAMAAAAxLBIFuQADAgAqLLcABiwSB7kAAwIAK7kACAEALBIJuQADAgAqLLcA" +
+ "CiwSC7kAAwIAsQAAAAEAEQAAACIACAAAAB0ACAAeAA0AHwAVACAAGwAhACMAIgAoACMAMAAkABQA" +
+ "AAACABkAAQAaAAAAAgAb");
+ private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+ "ZGV4CjAzNQBc8wr9PcHqnOR61m+0kimXTSddVMToJPuYBQAAcAAAAHhWNBIAAAAAAAAAAOAEAAAc" +
+ "AAAAcAAAAAYAAADgAAAABAAAAPgAAAAAAAAAAAAAAAcAAAAoAQAAAQAAAGABAAAYBAAAgAEAAHoC" +
+ "AAB9AgAAgAIAAIgCAACOAgAAlgIAALcCAADWAgAA4wIAAAIDAAAWAwAALAMAAEADAABeAwAAfQMA" +
+ "AIQDAACUAwAAlwMAAJsDAACgAwAAqAMAALwDAADrAwAAGQQAAEcEAAB0BAAAeQQAAIAEAAAHAAAA" +
+ "CAAAAAkAAAAKAAAADQAAABAAAAAQAAAABQAAAAAAAAARAAAABQAAAGQCAAASAAAABQAAAGwCAAAR" +
+ "AAAABQAAAHQCAAAAAAAAAgAAAAAAAwAEAAAAAAADAA4AAAAAAAIAGgAAAAIAAAACAAAAAwAAABkA" +
+ "AAAEAAEAEwAAAAAAAAAAAAAAAgAAAAAAAAAPAAAAPAIAAMoEAAAAAAAAAQAAAKgEAAABAAAAuAQA" +
+ "AAEAAQABAAAAhwQAAAQAAABwEAQAAAAOAAMAAgACAAAAjAQAAAcAAAAbAAUAAAByIAYAAgAOAAAA" +
+ "AwACAAIAAACTBAAABwAAABsABgAAAHIgBgACAA4AAAAEAAMAAgAAAJoEAAAiAAAAGwAYAAAAciAG" +
+ "AAMAcCACADEAGwAWAAAAciAGAAMAchAFAAIAGwAXAAAAciAGAAMAcCABADEAGwAVAAAAciAGAAMA" +
+ "DgAAAAAAAAAAAAMAAAAAAAAAAQAAAIABAAACAAAAgAEAAAMAAACIAQAAAQAAAAIAAAACAAAAAwAE" +
+ "AAEAAAAEAAEoAAE8AAY8aW5pdD4ABD47KVYABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBU" +
+ "cmFuc2Zvcm1lZAAdSGVsbG8gLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAC0xUcmFuc2Zvcm07AB1M" +
+ "ZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9s" +
+ "YW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABxMamF2YS91dGlsL2Z1bmN0aW9uL0Nv" +
+ "bnN1bWVyAB1MamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyOwAFU3RhcnQADlRyYW5zZm9ybS5q" +
+ "YXZhAAFWAAJWTAADVkxMAAZhY2NlcHQAEmVtaXR0ZXI6IGphY2stNC4xOQAtcG9zdCBGaW5pc2gg" +
+ "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkACxwb3N0IFN0YXJ0IHByaXZhdGUgbWV0" +
+ "aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAscHJlIEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0g" +
+ "VHJhbnNmb3JtZWQAK3ByZSBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQA" +
+ "A3J1bgAFc2F5SGkABXZhbHVlABMABw4AGQEABw5pABUBAAcOaQAdAgAABw5pPGk8aTxpAAIBARsc" +
+ "BRcAFwwXARcLFwMCAQEbHAYXABcKFwwXARcLFwMAAAMBAICABJADAQKoAwECyAMDAegDDwAAAAAA" +
+ "AAABAAAAAAAAAAEAAAAcAAAAcAAAAAIAAAAGAAAA4AAAAAMAAAAEAAAA+AAAAAUAAAAHAAAAKAEA" +
+ "AAYAAAABAAAAYAEAAAMQAAACAAAAgAEAAAEgAAAEAAAAkAEAAAYgAAABAAAAPAIAAAEQAAADAAAA" +
+ "ZAIAAAIgAAAcAAAAegIAAAMgAAAEAAAAhwQAAAQgAAACAAAAqAQAAAAgAAABAAAAygQAAAAQAAAB" +
+ "AAAA4AQAAA==");
+
+ // A class that we can use to keep track of the output of this test.
+ private static class TestWatcher implements Consumer<String> {
+ private StringBuilder sb;
+ public TestWatcher() {
+ sb = new StringBuilder();
+ }
+
+ @Override
+ public void accept(String s) {
+ sb.append(s);
+ sb.append('\n');
+ }
+
+ public String getOutput() {
+ return sb.toString();
+ }
+
+ public void clear() {
+ sb = new StringBuilder();
+ }
+ }
+
+ public static void main(String[] args) {
+ System.loadLibrary(args[1]);
+ doTest(new Transform(), new TestWatcher());
+ }
+
+ // TODO Workaround to (1) inability to ensure that current_method is not put into a register by
+ // the JIT and/or (2) inability to deoptimize frames near runtime functions.
+ // TODO Fix one/both of these issues.
+ public static void doCall(Runnable r) {
+ r.run();
+ }
+
+ private static boolean interpreting = true;
+ private static boolean retry = false;
+
+ public static void doTest(Transform t, TestWatcher w) {
+ // Get the methods that need to be optimized.
+ Method say_hi_method;
+ Method do_call_method;
+ // Figure out if we can even JIT at all.
+ final boolean has_jit = hasJit();
+ try {
+ say_hi_method = Transform.class.getDeclaredMethod(
+ "sayHi", Runnable.class, Consumer.class);
+ do_call_method = Main.class.getDeclaredMethod("doCall", Runnable.class);
+ } catch (Exception e) {
+ System.out.println("Unable to find methods!");
+ e.printStackTrace();
+ return;
+ }
+ // Makes sure the stack is the way we want it for the test and does the redefinition. It will
+ // set the retry boolean to true if we need to go around again due to a bad stack.
+ Runnable do_redefinition = () -> {
+ if (has_jit &&
+ (Main.isInterpretedFunction(say_hi_method, true) ||
+ Main.isInterpretedFunction(do_call_method, false))) {
+ // Try again. We are not running the right jitted methods/cannot redefine them now.
+ retry = true;
+ } else {
+ // Actually do the redefinition. The stack looks good.
+ retry = false;
+ w.accept("transforming calling function");
+ doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+ }
+ };
+ // This does nothing.
+ Runnable noop = () -> {};
+ // This just prints something out to show we are running the Runnable.
+ Runnable say_nothing = () -> { w.accept("Not doing anything here"); };
+ // This checks to see if we have jitted the methods we are testing.
+ Runnable check_interpreting = () -> {
+ // TODO remove the second check when we remove the doCall function. We need to check that
+ // both of these functions aren't being interpreted because if sayHi is the test doesn't do
+ // anything and if doCall is then there will be a runtime call right above the sayHi
+ // function preventing sayHi from being deoptimized.
+ interpreting = has_jit && (Main.isInterpretedFunction(say_hi_method, true) ||
+ Main.isInterpretedFunction(do_call_method, false));
+ };
+ do {
+ w.clear();
+ // Wait for the methods to be jitted
+ long j = 0;
+ do {
+ for (int i = 0; i < 10000; i++) {
+ t.sayHi(noop, w);
+ j++;
+ // Clear so that we won't OOM if we go around a few times.
+ w.clear();
+ }
+ t.sayHi(check_interpreting, w);
+ if (j >= 1000000) {
+ System.out.println("FAIL: Could not make sayHi be Jitted!");
+ return;
+ }
+ j++;
+ } while(interpreting);
+ // Clear output. Now we try for real.
+ w.clear();
+ // Try and redefine.
+ t.sayHi(say_nothing, w);
+ t.sayHi(do_redefinition, w);
+ t.sayHi(say_nothing, w);
+ } while (retry);
+ // Print output of last run.
+ System.out.print(w.getOutput());
+ }
+
+ private static native boolean hasJit();
+
+ private static native boolean isInterpretedFunction(Method m, boolean require_deoptimizable);
+
+ // Transforms the class
+ private static native void doCommonClassRedefinition(Class<?> target,
+ byte[] classfile,
+ byte[] dexfile);
+}
diff --git a/test/916-obsolete-jit/src/Transform.java b/test/916-obsolete-jit/src/Transform.java
new file mode 100644
index 0000000..f4dcf09
--- /dev/null
+++ b/test/916-obsolete-jit/src/Transform.java
@@ -0,0 +1,43 @@
+/*
+ * 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.util.function.Consumer;
+
+class Transform {
+ private void Start(Consumer<String> reporter) {
+ reporter.accept("hello - private");
+ }
+
+ private void Finish(Consumer<String> reporter) {
+ reporter.accept("goodbye - private");
+ }
+
+ public void sayHi(Runnable r, Consumer<String> reporter) {
+ reporter.accept("Pre Start private method call");
+ Start(reporter);
+ reporter.accept("Post Start private method call");
+ // TODO Revisit with b/33616143
+ // TODO Uncomment this once either b/33630159 or b/33616143 are resolved.
+ // r.run();
+ // TODO This doCall function is a very temporary fix until we get either deoptimization near
+ // runtime frames working, forcing current method to be always read from the stack or both
+ // working.
+ Main.doCall(r);
+ reporter.accept("Pre Finish private method call");
+ Finish(reporter);
+ reporter.accept("Post Finish private method call");
+ }
+}
diff --git a/test/917-fields-transformation/run b/test/917-fields-transformation/run
index a434b63..4379349 100755
--- a/test/917-fields-transformation/run
+++ b/test/917-fields-transformation/run
@@ -14,30 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if [[ "$@" == *"-O"* ]]; then
- agent=libtiagent.so
- plugin=libopenjdkjvmti.so
- lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
- arg="jvm"
-else
- arg="art"
- if [[ "$@" != *"--debuggable"* ]]; then
- other_args=" -Xcompiler-option --debuggable "
- else
- other_args=""
- fi
-fi
-
./default-run "$@" --experimental agents \
--experimental runtime-plugins \
- --runtime-option -agentpath:${agent}=917-fields-transformation,${arg} \
- --android-runtime-option -Xplugin:${plugin} \
- --android-runtime-option -Xfully-deoptable \
- ${other_args} \
- --args ${lib}
+ --jvmti
diff --git a/test/Android.bp b/test/Android.bp
index 2625f56..5a2c902 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -243,6 +243,9 @@
name: "libtiagent-defaults",
defaults: ["libartagent-defaults"],
srcs: [
+ // This is to get the IsInterpreted native method.
+ "common/stack_inspect.cc",
+ "common/runtime_state.cc",
"ti-agent/common_load.cc",
"ti-agent/common_helper.cc",
"901-hello-ti-agent/basics.cc",
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index fdd5b60..ec1f6ba 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -286,6 +286,9 @@
911-get-stack-trace \
912-classes \
913-heaps \
+ 914-hello-obsolescence \
+ 915-obsolete-2 \
+ 916-obsolete-jit \
917-fields-transformation \
ifneq (,$(filter target,$(TARGET_TYPES)))
@@ -539,7 +542,6 @@
# Test 906 iterates the heap filtering with different options. No instances should be created
# between those runs to be able to have precise checks.
# Test 902 hits races with the JIT compiler. b/32821077
-# Test 626-const-class-linking can deadlock with JIT. b/33567581
# Test 629 requires compilation.
# Test 914, 915, 917, & 918 are very sensitive to the exact state of the stack,
# including the jit-inserted runtime frames. This causes them to be somewhat
@@ -548,7 +550,6 @@
# feature for JIT use cases in a way that is resilient to the jit frames.
TEST_ART_BROKEN_JIT_RUN_TESTS := \
137-cfi \
- 626-const-class-linking \
629-vdex-speed \
902-hello-transformation \
904-object-allocation \
diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc
index f26e122..7451cf9 100644
--- a/test/common/runtime_state.cc
+++ b/test/common/runtime_state.cc
@@ -19,6 +19,7 @@
#include "base/enums.h"
#include "base/logging.h"
#include "dex_file-inl.h"
+#include "instrumentation.h"
#include "jit/jit.h"
#include "jit/jit_code_cache.h"
#include "mirror/class-inl.h"
@@ -30,6 +31,16 @@
namespace art {
+// public static native boolean hasJit();
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasJit(JNIEnv*, jclass) {
+ Runtime* runtime = Runtime::Current();
+ return runtime != nullptr
+ && runtime->GetJit() != nullptr
+ && runtime->GetInstrumentation()->GetCurrentInstrumentationLevel() !=
+ instrumentation::Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter;
+}
+
// public static native boolean hasOatFile();
extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasOatFile(JNIEnv* env, jclass cls) {
diff --git a/test/common/stack_inspect.cc b/test/common/stack_inspect.cc
index 4df2d47..df7fa20 100644
--- a/test/common/stack_inspect.cc
+++ b/test/common/stack_inspect.cc
@@ -18,6 +18,7 @@
#include "base/logging.h"
#include "dex_file-inl.h"
+#include "jni_internal.h"
#include "mirror/class-inl.h"
#include "nth_caller_visitor.h"
#include "oat_file.h"
@@ -52,6 +53,89 @@
return IsInterpreted(env, klass, 1);
}
+// public static native boolean isInterpreted(int depth);
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpretedAt(JNIEnv* env,
+ jclass klass,
+ jint depth) {
+ return IsInterpreted(env, klass, depth);
+}
+
+
+// public static native boolean isInterpretedFunction(String smali);
+
+// TODO Remove 'allow_runtime_frames' option once we have deoptimization through runtime frames.
+struct MethodIsInterpretedVisitor : public StackVisitor {
+ public:
+ MethodIsInterpretedVisitor(Thread* thread, ArtMethod* goal, bool require_deoptable)
+ : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+ goal_(goal),
+ method_is_interpreted_(true),
+ method_found_(false),
+ prev_was_runtime_(true),
+ require_deoptable_(require_deoptable) {}
+
+ virtual bool VisitFrame() OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (goal_ == GetMethod()) {
+ method_is_interpreted_ = (require_deoptable_ && prev_was_runtime_) || IsShadowFrame();
+ method_found_ = true;
+ return false;
+ }
+ prev_was_runtime_ = GetMethod()->IsRuntimeMethod();
+ return true;
+ }
+
+ bool IsInterpreted() {
+ return method_is_interpreted_;
+ }
+
+ bool IsFound() {
+ return method_found_;
+ }
+
+ private:
+ const ArtMethod* goal_;
+ bool method_is_interpreted_;
+ bool method_found_;
+ bool prev_was_runtime_;
+ bool require_deoptable_;
+};
+
+// TODO Remove 'require_deoptimizable' option once we have deoptimization through runtime frames.
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpretedFunction(
+ JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject method, jboolean require_deoptimizable) {
+ // Return false if this seems to not be an ART runtime.
+ if (Runtime::Current() == nullptr) {
+ return JNI_FALSE;
+ }
+ if (method == nullptr) {
+ env->ThrowNew(env->FindClass("java/lang/NullPointerException"), "method is null!");
+ return JNI_FALSE;
+ }
+ jmethodID id = env->FromReflectedMethod(method);
+ if (id == nullptr) {
+ env->ThrowNew(env->FindClass("java/lang/Error"), "Unable to interpret method argument!");
+ return JNI_FALSE;
+ }
+ bool result;
+ bool found;
+ {
+ ScopedObjectAccess soa(env);
+ ArtMethod* goal = jni::DecodeArtMethod(id);
+ MethodIsInterpretedVisitor v(soa.Self(), goal, require_deoptimizable);
+ v.WalkStack();
+ bool enters_interpreter = Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(
+ goal->GetEntryPointFromQuickCompiledCode());
+ result = (v.IsInterpreted() || enters_interpreter);
+ found = v.IsFound();
+ }
+ if (!found) {
+ env->ThrowNew(env->FindClass("java/lang/Error"), "Unable to find given method in stack!");
+ return JNI_FALSE;
+ }
+ return result;
+}
+
// public static native void assertIsInterpreted();
extern "C" JNIEXPORT void JNICALL Java_Main_assertIsInterpreted(JNIEnv* env, jclass klass) {
diff --git a/test/etc/default-build b/test/etc/default-build
index 51ae175..e9e3886 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -69,6 +69,7 @@
# Setup experimental flag mappings in a bash associative array.
declare -A JACK_EXPERIMENTAL_ARGS
+JACK_EXPERIMENTAL_ARGS["agents"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
JACK_EXPERIMENTAL_ARGS["default-methods"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
JACK_EXPERIMENTAL_ARGS["lambdas"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
JACK_EXPERIMENTAL_ARGS["method-handles"]="-D jack.java.source.version=1.7 -D jack.android.min-api-level=o-b1"
@@ -76,12 +77,14 @@
declare -A SMALI_EXPERIMENTAL_ARGS
SMALI_EXPERIMENTAL_ARGS["default-methods"]="--api-level 24"
SMALI_EXPERIMENTAL_ARGS["method-handles"]="--api-level 26"
+SMALI_EXPERIMENTAL_ARGS["agents"]="--api-level 26"
declare -A JAVAC_EXPERIMENTAL_ARGS
JAVAC_EXPERIMENTAL_ARGS["default-methods"]="-source 1.8 -target 1.8"
JAVAC_EXPERIMENTAL_ARGS["lambdas"]="-source 1.8 -target 1.8"
JAVAC_EXPERIMENTAL_ARGS["method-handles"]="-source 1.8 -target 1.8"
JAVAC_EXPERIMENTAL_ARGS[${DEFAULT_EXPERIMENT}]="-source 1.7 -target 1.7"
+JAVAC_EXPERIMENTAL_ARGS["agents"]="-source 1.8 -target 1.8"
while true; do
if [ "x$1" = "x--dx-option" ]; then
@@ -126,6 +129,16 @@
# Be sure to get any default arguments if not doing any experiments.
EXPERIMENTAL="${EXPERIMENTAL} ${DEFAULT_EXPERIMENT}"
+if [ "${JACK_SERVER}" = "false" ]; then
+ # Run in single-threaded mode for the continuous buildbot.
+ JACK_ARGS="${JACK_ARGS} -D sched.runner=single-threaded"
+else
+ # Run with 4 threads to reduce memory footprint and thread contention.
+ JACK_ARGS="${JACK_ARGS} -D sched.runner=multi-threaded"
+ JACK_ARGS="${JACK_ARGS} -D sched.runner.thread.kind=fixed"
+ JACK_ARGS="${JACK_ARGS} -D sched.runner.thread.fixed.count=4"
+fi
+
# Add args from the experimental mappings.
for experiment in ${EXPERIMENTAL}; do
JACK_ARGS="${JACK_ARGS} ${JACK_EXPERIMENTAL_ARGS[${experiment}]}"
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 566f7ba..8245947 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -30,6 +30,7 @@
INTERPRETER="n"
JIT="n"
INVOKE_WITH=""
+IS_JVMTI_TEST="n"
ISA=x86
LIBRARY_DIRECTORY="lib"
TEST_DIRECTORY="nativetest"
@@ -59,14 +60,18 @@
EXTERNAL_LOG_TAGS="n" # if y respect externally set ANDROID_LOG_TAGS.
DRY_RUN="n" # if y prepare to run the test but don't run it.
TEST_VDEX="n"
+TEST_IS_NDEBUG="n"
APP_IMAGE="y"
while true; do
if [ "x$1" = "x--quiet" ]; then
QUIET="y"
shift
+ elif [ "x$1" = "x--jvmti" ]; then
+ IS_JVMTI_TEST="y"
+ shift
elif [ "x$1" = "x-O" ]; then
- # Ignore this option.
+ TEST_IS_NDEBUG="y"
shift
elif [ "x$1" = "x--lib" ]; then
shift
@@ -382,6 +387,28 @@
fi
fi
+if [ "$IS_JVMTI_TEST" = "y" ]; then
+ plugin=libopenjdkjvmtid.so
+ agent=libtiagentd.so
+ lib=tiagentd
+ if [[ "$TEST_IS_NDEBUG" = "y" ]]; then
+ agent=libtiagent.so
+ plugin=libopenjdkjvmti.so
+ lib=tiagent
+ fi
+
+ ARGS="${ARGS} ${lib}"
+ if [[ "$USE_JVM" = "y" ]]; then
+ FLAGS="${FLAGS} -agentpath:${agent}=${TEST_NAME},jvm"
+ else
+ FLAGS="${FLAGS} -agentpath:${agent}=${TEST_NAME},art"
+ FLAGS="${FLAGS} -Xplugin:${plugin}"
+ FLAGS="${FLAGS} -Xfully-deoptable"
+ # Always make the compilation be debuggable.
+ COMPILE_FLAGS="${COMPILE_FLAGS} --debuggable"
+ fi
+fi
+
JNI_OPTS="-Xjnigreflimit:512 -Xcheck:jni"
if [ "$RELOCATE" = "y" ]; then
diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc
index 3e2b168..ebf1e46 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -18,8 +18,11 @@
#include <stdio.h>
+#include "art_method.h"
#include "jni.h"
#include "openjdkjvmti/jvmti.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
#include "ti-agent/common_load.h"
#include "utils.h"
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index 3886148..79c17d7 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -66,6 +66,9 @@
{ "911-get-stack-trace", Test911GetStackTrace::OnLoad, nullptr },
{ "912-classes", Test912Classes::OnLoad, nullptr },
{ "913-heaps", Test913Heaps::OnLoad, nullptr },
+ { "914-hello-obsolescence", common_redefine::OnLoad, nullptr },
+ { "915-obsolete-2", common_redefine::OnLoad, nullptr },
+ { "916-obsolete-jit", common_redefine::OnLoad, nullptr },
{ "917-fields-transformation", common_redefine::OnLoad, nullptr },
};