ART: Support for VarHandle invokers
Enables VarHandles to invoked by a MethodHandle.
Bug: b/65872996
Test: art/test/run-test --host 713
Change-Id: I4672dd50654396c2b45bd212a523698cf22879eb
diff --git a/libdexfile/dex/dex_instruction.h b/libdexfile/dex/dex_instruction.h
index c953365..3155c14 100644
--- a/libdexfile/dex/dex_instruction.h
+++ b/libdexfile/dex/dex_instruction.h
@@ -741,7 +741,7 @@
// existing InstructionOperands instance.
class NoReceiverInstructionOperands FINAL : public InstructionOperands {
public:
- explicit NoReceiverInstructionOperands(InstructionOperands* inner)
+ explicit NoReceiverInstructionOperands(const InstructionOperands* const inner)
: InstructionOperands(inner->GetNumberOfOperands() - 1), inner_(inner) {}
~NoReceiverInstructionOperands() {}
uint32_t GetOperand(size_t operand_index) const OVERRIDE;
diff --git a/runtime/method_handles-inl.h b/runtime/method_handles-inl.h
index 2bc71f4..41c8384 100644
--- a/runtime/method_handles-inl.h
+++ b/runtime/method_handles-inl.h
@@ -33,10 +33,9 @@
inline bool ConvertArgumentValue(Handle<mirror::MethodType> callsite_type,
Handle<mirror::MethodType> callee_type,
- int index,
+ ObjPtr<mirror::Class> from_class,
+ ObjPtr<mirror::Class> to_class,
JValue* value) REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::Class> from_class(callsite_type->GetPTypes()->GetWithoutChecks(index));
- ObjPtr<mirror::Class> to_class(callee_type->GetPTypes()->GetWithoutChecks(index));
if (from_class == to_class) {
return true;
}
@@ -55,6 +54,17 @@
}
}
+inline bool ConvertArgumentValue(Handle<mirror::MethodType> callsite_type,
+ Handle<mirror::MethodType> callee_type,
+ int index,
+ JValue* value) REQUIRES_SHARED(Locks::mutator_lock_) {
+ return ConvertArgumentValue(callsite_type,
+ callee_type,
+ callsite_type->GetPTypes()->GetWithoutChecks(index),
+ callee_type->GetPTypes()->GetWithoutChecks(index),
+ value);
+}
+
inline bool ConvertReturnValue(Handle<mirror::MethodType> callsite_type,
Handle<mirror::MethodType> callee_type,
JValue* value) REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -84,16 +94,17 @@
Handle<mirror::MethodType> callee_type,
G* getter,
S* setter,
- int32_t num_conversions) REQUIRES_SHARED(Locks::mutator_lock_) {
+ int32_t start_index,
+ int32_t end_index) REQUIRES_SHARED(Locks::mutator_lock_) {
StackHandleScope<2> hs(self);
Handle<mirror::ObjectArray<mirror::Class>> from_types(hs.NewHandle(callsite_type->GetPTypes()));
Handle<mirror::ObjectArray<mirror::Class>> to_types(hs.NewHandle(callee_type->GetPTypes()));
- for (int32_t i = 0; i < num_conversions; ++i) {
+ for (int32_t i = start_index; i < end_index; ++i) {
ObjPtr<mirror::Class> from(from_types->GetWithoutChecks(i));
- ObjPtr<mirror::Class> to(to_types->GetWithoutChecks(i));
- const Primitive::Type from_type = from_types->GetWithoutChecks(i)->GetPrimitiveType();
- const Primitive::Type to_type = to_types->GetWithoutChecks(i)->GetPrimitiveType();
+ ObjPtr<mirror::Class> to(to_types->GetWithoutChecks(i - start_index));
+ const Primitive::Type from_type = from->GetPrimitiveType();
+ const Primitive::Type to_type = to->GetPrimitiveType();
if (from == to) {
// Easy case - the types are identical. Nothing left to do except to pass
// the arguments along verbatim.
@@ -106,7 +117,6 @@
}
} else {
JValue value;
-
if (Primitive::Is64BitType(from_type)) {
value.SetJ(getter->GetLong());
} else if (from_type == Primitive::kPrimNot) {
@@ -114,13 +124,11 @@
} else {
value.SetI(getter->Get());
}
-
// Caveat emptor - ObjPtr's not guaranteed valid after this call.
- if (!ConvertArgumentValue(callsite_type, callee_type, i, &value)) {
+ if (!ConvertArgumentValue(callsite_type, callee_type, from, to, &value)) {
DCHECK(self->IsExceptionPending());
return false;
}
-
if (Primitive::Is64BitType(to_type)) {
setter->SetLong(value.GetJ());
} else if (to_type == Primitive::kPrimNot) {
@@ -130,10 +138,31 @@
}
}
}
-
return true;
}
+template <typename G, typename S>
+bool PerformConversions(Thread* self,
+ Handle<mirror::MethodType> callsite_type,
+ Handle<mirror::MethodType> callee_type,
+ G* getter,
+ S* setter,
+ int32_t num_conversions)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return PerformConversions(self, callsite_type, callee_type, getter, setter, 0, num_conversions);
+}
+
+template <typename G, typename S>
+bool PerformConversions(Thread* self,
+ Handle<mirror::MethodType> callsite_type,
+ Handle<mirror::MethodType> callee_type,
+ G* getter,
+ S* setter)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ int32_t num_conversions = callee_type->GetPTypes()->GetLength();
+ return PerformConversions(self, callsite_type, callee_type, getter, setter, 0, num_conversions);
+}
+
} // namespace art
#endif // ART_RUNTIME_METHOD_HANDLES_INL_H_
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
index 9b21e1d..82370c4 100644
--- a/runtime/method_handles.cc
+++ b/runtime/method_handles.cc
@@ -24,6 +24,7 @@
#include "mirror/emulated_stack_frame.h"
#include "mirror/method_handle_impl-inl.h"
#include "mirror/method_type.h"
+#include "mirror/var_handle.h"
#include "reflection-inl.h"
#include "reflection.h"
#include "well_known_classes.h"
@@ -365,6 +366,11 @@
|| handle_kind == mirror::MethodHandle::Kind::kInvokeCallSiteTransform);
}
+inline bool IsInvokeVarHandle(const mirror::MethodHandle::Kind handle_kind) {
+ return (handle_kind == mirror::MethodHandle::Kind::kInvokeVarHandle ||
+ handle_kind == mirror::MethodHandle::Kind::kInvokeVarHandleExact);
+}
+
inline bool IsFieldAccess(mirror::MethodHandle::Kind handle_kind) {
return (handle_kind >= mirror::MethodHandle::Kind::kFirstAccessorKind
&& handle_kind <= mirror::MethodHandle::Kind::kLastAccessorKind);
@@ -957,6 +963,118 @@
}
}
+bool DoVarHandleInvokeTranslationUnchecked(Thread* self,
+ ShadowFrame& shadow_frame,
+ mirror::VarHandle::AccessMode access_mode,
+ Handle<mirror::VarHandle> vh,
+ Handle<mirror::MethodType> vh_type,
+ Handle<mirror::MethodType> callsite_type,
+ const InstructionOperands* const operands,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK_EQ(operands->GetNumberOfOperands(), static_cast<uint32_t>(vh_type->GetNumberOfPTypes()));
+ DCHECK_EQ(operands->GetNumberOfOperands(),
+ static_cast<uint32_t>(callsite_type->GetNumberOfPTypes()));
+ const size_t vreg_count = vh_type->NumberOfVRegs();
+ ShadowFrameAllocaUniquePtr accessor_frame =
+ CREATE_SHADOW_FRAME(vreg_count, nullptr, shadow_frame.GetMethod(), shadow_frame.GetDexPC());
+ ShadowFrameGetter getter(shadow_frame, operands);
+ static const uint32_t kFirstAccessorReg = 0;
+ ShadowFrameSetter setter(accessor_frame.get(), kFirstAccessorReg);
+ if (!PerformConversions(self, callsite_type, vh_type, &getter, &setter)) {
+ return false;
+ }
+ RangeInstructionOperands accessor_operands(kFirstAccessorReg, kFirstAccessorReg + vreg_count);
+ if (!vh->Access(access_mode, accessor_frame.get(), &accessor_operands, result)) {
+ return false;
+ }
+ return ConvertReturnValue(callsite_type, vh_type, result);
+}
+
+bool DoVarHandleInvokeTranslation(Thread* self,
+ ShadowFrame& shadow_frame,
+ bool invokeExact,
+ Handle<mirror::MethodHandle> method_handle,
+ Handle<mirror::MethodType> callsite_type,
+ const InstructionOperands* const operands,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!invokeExact) {
+ // Exact invokes are checked for compatability higher up. The
+ // non-exact invoke path doesn't have a similar check due to
+ // transformers which have EmulatedStack frame arguments with the
+ // actual method type associated with the frame.
+ if (UNLIKELY(!callsite_type->IsConvertible(method_handle->GetMethodType()))) {
+ ThrowWrongMethodTypeException(method_handle->GetMethodType(), callsite_type.Get());
+ return false;
+ }
+ }
+
+ //
+ // Basic checks that apply in all cases.
+ //
+ StackHandleScope<6> hs(self);
+ Handle<mirror::ObjectArray<mirror::Class>>
+ callsite_ptypes(hs.NewHandle(callsite_type->GetPTypes()));
+ Handle<mirror::ObjectArray<mirror::Class>>
+ mh_ptypes(hs.NewHandle(method_handle->GetMethodType()->GetPTypes()));
+
+ // Check that the first parameter is a VarHandle
+ if (callsite_ptypes->GetLength() < 1 ||
+ !mh_ptypes->Get(0)->IsAssignableFrom(callsite_ptypes->Get(0)) ||
+ mh_ptypes->Get(0) != mirror::VarHandle::StaticClass()) {
+ ThrowWrongMethodTypeException(method_handle->GetMethodType(), callsite_type.Get());
+ return false;
+ }
+
+ // Get the receiver
+ mirror::Object* receiver = shadow_frame.GetVRegReference(operands->GetOperand(0));
+ if (receiver == nullptr) {
+ ThrowNullPointerException("Expected argument 1 to be a non-null VarHandle");
+ return false;
+ }
+
+ // Cast to VarHandle instance
+ Handle<mirror::VarHandle> vh(hs.NewHandle(down_cast<mirror::VarHandle*>(receiver)));
+ DCHECK(mirror::VarHandle::StaticClass()->IsAssignableFrom(vh->GetClass()));
+
+ // Determine the accessor kind to dispatch
+ ArtMethod* target_method = method_handle->GetTargetMethod();
+ int intrinsic_index = target_method->GetIntrinsic();
+ mirror::VarHandle::AccessMode access_mode =
+ mirror::VarHandle::GetAccessModeByIntrinsic(static_cast<Intrinsics>(intrinsic_index));
+ Handle<mirror::MethodType> vh_type =
+ hs.NewHandle(vh->GetMethodTypeForAccessMode(self, access_mode));
+ Handle<mirror::MethodType> mh_invoke_type = hs.NewHandle(
+ mirror::MethodType::CloneWithoutLeadingParameter(self, method_handle->GetMethodType()));
+ if (method_handle->GetHandleKind() == mirror::MethodHandle::Kind::kInvokeVarHandleExact) {
+ if (!mh_invoke_type->IsExactMatch(vh_type.Get())) {
+ ThrowWrongMethodTypeException(vh_type.Get(), mh_invoke_type.Get());
+ return false;
+ }
+ } else {
+ DCHECK_EQ(method_handle->GetHandleKind(), mirror::MethodHandle::Kind::kInvokeVarHandle);
+ if (!mh_invoke_type->IsConvertible(vh_type.Get())) {
+ ThrowWrongMethodTypeException(vh_type.Get(), mh_invoke_type.Get());
+ return false;
+ }
+ }
+
+ Handle<mirror::MethodType> callsite_type_without_varhandle =
+ hs.NewHandle(mirror::MethodType::CloneWithoutLeadingParameter(self, callsite_type.Get()));
+ NoReceiverInstructionOperands varhandle_operands(operands);
+ DCHECK_EQ(static_cast<int32_t>(varhandle_operands.GetNumberOfOperands()),
+ callsite_type_without_varhandle->GetPTypes()->GetLength());
+ return DoVarHandleInvokeTranslationUnchecked(self,
+ shadow_frame,
+ access_mode,
+ vh,
+ vh_type,
+ callsite_type_without_varhandle,
+ &varhandle_operands,
+ result);
+}
+
static inline bool MethodHandleInvokeInternal(Thread* self,
ShadowFrame& shadow_frame,
Handle<mirror::MethodHandle> method_handle,
@@ -981,6 +1099,15 @@
operands,
result);
}
+ if (IsInvokeVarHandle(handle_kind)) {
+ return DoVarHandleInvokeTranslation(self,
+ shadow_frame,
+ /*invokeExact*/ false,
+ method_handle,
+ callsite_type,
+ operands,
+ result);
+ }
return DoInvokePolymorphicMethod(self,
shadow_frame,
method_handle,
@@ -1016,13 +1143,22 @@
}
// Slow-path check.
- if (IsInvokeTransform(handle_kind) || IsCallerTransformer(callsite_type)) {
+ if (IsInvokeTransform(handle_kind) ||
+ IsCallerTransformer(callsite_type)) {
return DoInvokePolymorphicMethod(self,
shadow_frame,
method_handle,
callsite_type,
operands,
result);
+ } else if (IsInvokeVarHandle(handle_kind)) {
+ return DoVarHandleInvokeTranslation(self,
+ shadow_frame,
+ /*invokeExact*/ true,
+ method_handle,
+ callsite_type,
+ operands,
+ result);
}
// On the fast-path. This is equivalent to DoCallPolymoprhic without the conversion paths.
diff --git a/runtime/method_handles.h b/runtime/method_handles.h
index 3b1bf2e..7e60a5c 100644
--- a/runtime/method_handles.h
+++ b/runtime/method_handles.h
@@ -123,7 +123,8 @@
Handle<mirror::MethodType> callee_type,
G* getter,
S* setter,
- int32_t num_conversions) REQUIRES_SHARED(Locks::mutator_lock_);
+ int32_t start_index,
+ int32_t end_index) REQUIRES_SHARED(Locks::mutator_lock_);
// A convenience class that allows for iteration through a list of
// input argument registers. This is used to iterate over input
diff --git a/runtime/mirror/method_handle_impl.h b/runtime/mirror/method_handle_impl.h
index a1bc976..3b0002c 100644
--- a/runtime/mirror/method_handle_impl.h
+++ b/runtime/mirror/method_handle_impl.h
@@ -48,6 +48,8 @@
kInvokeInterface,
kInvokeTransform,
kInvokeCallSiteTransform,
+ kInvokeVarHandle,
+ kInvokeVarHandleExact,
kInstanceGet,
kInstancePut,
kStaticGet,
@@ -55,7 +57,7 @@
kLastValidKind = kStaticPut,
kFirstAccessorKind = kInstanceGet,
kLastAccessorKind = kStaticPut,
- kLastInvokeKind = kInvokeCallSiteTransform
+ kLastInvokeKind = kInvokeVarHandleExact
};
Kind GetHandleKind() REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/mirror/method_type.cc b/runtime/mirror/method_type.cc
index 4b8dfac..6ac5012 100644
--- a/runtime/mirror/method_type.cc
+++ b/runtime/mirror/method_type.cc
@@ -23,13 +23,13 @@
namespace art {
namespace mirror {
-GcRoot<mirror::Class> MethodType::static_class_;
+GcRoot<Class> MethodType::static_class_;
-mirror::MethodType* MethodType::Create(Thread* const self,
- Handle<Class> return_type,
- Handle<ObjectArray<Class>> param_types) {
+MethodType* MethodType::Create(Thread* const self,
+ Handle<Class> return_type,
+ Handle<ObjectArray<Class>> parameter_types) {
StackHandleScope<1> hs(self);
- Handle<mirror::MethodType> mt(
+ Handle<MethodType> mt(
hs.NewHandle(ObjPtr<MethodType>::DownCast(StaticClass()->AllocObject(self))));
// TODO: Do we ever create a MethodType during a transaction ? There doesn't
@@ -38,21 +38,38 @@
mt->SetFieldObject<false>(FormOffset(), nullptr);
mt->SetFieldObject<false>(MethodDescriptorOffset(), nullptr);
mt->SetFieldObject<false>(RTypeOffset(), return_type.Get());
- mt->SetFieldObject<false>(PTypesOffset(), param_types.Get());
+ mt->SetFieldObject<false>(PTypesOffset(), parameter_types.Get());
mt->SetFieldObject<false>(WrapAltOffset(), nullptr);
return mt.Get();
}
+MethodType* MethodType::CloneWithoutLeadingParameter(Thread* const self,
+ ObjPtr<MethodType> method_type) {
+ StackHandleScope<3> hs(self);
+ Handle<Class> rtype = hs.NewHandle(method_type->GetRType());
+ Handle<ObjectArray<Class>> src_ptypes = hs.NewHandle(method_type->GetPTypes());
+ ObjPtr<Class> class_type = Class::GetJavaLangClass();
+ ObjPtr<Class> class_array_type =
+ Runtime::Current()->GetClassLinker()->FindArrayClass(self, &class_type);
+ const int32_t dst_ptypes_count = src_ptypes->GetLength() - 1;
+ Handle<ObjectArray<Class>> dst_ptypes = hs.NewHandle(
+ ObjectArray<Class>::Alloc(self, class_array_type, dst_ptypes_count));
+ for (int32_t i = 0; i < dst_ptypes_count; ++i) {
+ dst_ptypes->Set(i, src_ptypes->Get(i + 1));
+ }
+ return Create(self, rtype, dst_ptypes);
+}
+
size_t MethodType::NumberOfVRegs() REQUIRES_SHARED(Locks::mutator_lock_) {
- mirror::ObjectArray<Class>* const p_types = GetPTypes();
+ ObjectArray<Class>* const p_types = GetPTypes();
const int32_t p_types_length = p_types->GetLength();
// Initialize |num_vregs| with number of parameters and only increment it for
// types requiring a second vreg.
size_t num_vregs = static_cast<size_t>(p_types_length);
for (int32_t i = 0; i < p_types_length; ++i) {
- mirror::Class* klass = p_types->GetWithoutChecks(i);
+ Class* klass = p_types->GetWithoutChecks(i);
if (klass->IsPrimitiveLong() || klass->IsPrimitiveDouble()) {
++num_vregs;
}
@@ -60,11 +77,11 @@
return num_vregs;
}
-bool MethodType::IsExactMatch(mirror::MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_) {
- mirror::ObjectArray<Class>* const p_types = GetPTypes();
+bool MethodType::IsExactMatch(MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_) {
+ ObjectArray<Class>* const p_types = GetPTypes();
const int32_t params_length = p_types->GetLength();
- mirror::ObjectArray<Class>* const target_p_types = target->GetPTypes();
+ ObjectArray<Class>* const target_p_types = target->GetPTypes();
if (params_length != target_p_types->GetLength()) {
return false;
}
@@ -76,11 +93,11 @@
return GetRType() == target->GetRType();
}
-bool MethodType::IsConvertible(mirror::MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_) {
- mirror::ObjectArray<Class>* const p_types = GetPTypes();
+bool MethodType::IsConvertible(MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_) {
+ ObjectArray<Class>* const p_types = GetPTypes();
const int32_t params_length = p_types->GetLength();
- mirror::ObjectArray<Class>* const target_p_types = target->GetPTypes();
+ ObjectArray<Class>* const target_p_types = target->GetPTypes();
if (params_length != target_p_types->GetLength()) {
return false;
}
@@ -105,7 +122,7 @@
std::ostringstream ss;
ss << "(";
- mirror::ObjectArray<Class>* const p_types = GetPTypes();
+ ObjectArray<Class>* const p_types = GetPTypes();
const int32_t params_length = p_types->GetLength();
for (int32_t i = 0; i < params_length; ++i) {
ss << p_types->GetWithoutChecks(i)->PrettyDescriptor();
diff --git a/runtime/mirror/method_type.h b/runtime/mirror/method_type.h
index a9f3c9c..edd9910 100644
--- a/runtime/mirror/method_type.h
+++ b/runtime/mirror/method_type.h
@@ -31,12 +31,16 @@
// C++ mirror of java.lang.invoke.MethodType
class MANAGED MethodType : public Object {
public:
- static mirror::MethodType* Create(Thread* const self,
- Handle<Class> return_type,
- Handle<ObjectArray<Class>> param_types)
+ static MethodType* Create(Thread* const self,
+ Handle<Class> return_type,
+ Handle<ObjectArray<Class>> param_types)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_);
- static mirror::Class* StaticClass() REQUIRES_SHARED(Locks::mutator_lock_) {
+ static MethodType* CloneWithoutLeadingParameter(Thread* const self,
+ ObjPtr<MethodType> method_type)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ static Class* StaticClass() REQUIRES_SHARED(Locks::mutator_lock_) {
return static_class_.Read();
}
@@ -44,6 +48,10 @@
return GetFieldObject<ObjectArray<Class>>(OFFSET_OF_OBJECT_MEMBER(MethodType, p_types_));
}
+ int GetNumberOfPTypes() REQUIRES_SHARED(Locks::mutator_lock_) {
+ return GetPTypes()->GetLength();
+ }
+
// Number of virtual registers required to hold the parameters for
// this method type.
size_t NumberOfVRegs() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -58,11 +66,11 @@
// Returns true iff. |this| is an exact match for method type |target|, i.e
// iff. they have the same return types and parameter types.
- bool IsExactMatch(mirror::MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_);
+ bool IsExactMatch(MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_);
// Returns true iff. |this| can be converted to match |target| method type, i.e
// iff. they have convertible return types and parameter types.
- bool IsConvertible(mirror::MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_);
+ bool IsConvertible(MethodType* target) REQUIRES_SHARED(Locks::mutator_lock_);
// Returns the pretty descriptor for this method type, suitable for display in
// exception messages and the like.
@@ -89,13 +97,13 @@
return MemberOffset(OFFSETOF_MEMBER(MethodType, wrap_alt_));
}
- HeapReference<mirror::Object> form_; // Unused in the runtime
- HeapReference<mirror::String> method_descriptor_; // Unused in the runtime
- HeapReference<ObjectArray<mirror::Class>> p_types_;
- HeapReference<mirror::Class> r_type_;
- HeapReference<mirror::Object> wrap_alt_; // Unused in the runtime
+ HeapReference<Object> form_; // Unused in the runtime
+ HeapReference<String> method_descriptor_; // Unused in the runtime
+ HeapReference<ObjectArray<Class>> p_types_;
+ HeapReference<Class> r_type_;
+ HeapReference<Object> wrap_alt_; // Unused in the runtime
- static GcRoot<mirror::Class> static_class_; // java.lang.invoke.MethodType.class
+ static GcRoot<Class> static_class_; // java.lang.invoke.MethodType.class
friend struct art::MethodTypeOffsets; // for verifying offset information
DISALLOW_IMPLICIT_CONSTRUCTORS(MethodType);
diff --git a/runtime/mirror/var_handle.cc b/runtime/mirror/var_handle.cc
index 85d06f0..a79c0a2 100644
--- a/runtime/mirror/var_handle.cc
+++ b/runtime/mirror/var_handle.cc
@@ -21,6 +21,7 @@
#include "class-inl.h"
#include "class_linker.h"
#include "gc_root-inl.h"
+#include "intrinsics_enum.h"
#include "jni_internal.h"
#include "jvalue-inl.h"
#include "method_handles.h"
@@ -1463,6 +1464,47 @@
return true;
}
+bool VarHandle::IsInvokerMethodTypeCompatible(AccessMode access_mode,
+ MethodType* method_type) {
+ StackHandleScope<3> hs(Thread::Current());
+ Handle<Class> mt_rtype(hs.NewHandle(method_type->GetRType()));
+ Handle<VarHandle> vh(hs.NewHandle(this));
+ Handle<Class> var_type(hs.NewHandle(vh->GetVarType()));
+ AccessModeTemplate access_mode_template = GetAccessModeTemplate(access_mode);
+
+ // Check return type first.
+ if (mt_rtype->GetPrimitiveType() == Primitive::Type::kPrimVoid) {
+ // The result of the operation will be discarded. The return type
+ // of the VarHandle is immaterial.
+ } else {
+ ObjPtr<Class> vh_rtype(GetReturnType(access_mode_template, var_type.Get()));
+ if (!IsReturnTypeConvertible(vh_rtype, mt_rtype.Get())) {
+ return false;
+ }
+ }
+
+ // Check the number of parameters matches (ignoring the VarHandle parameter).
+ static const int32_t kVarHandleParameters = 1;
+ ObjPtr<Class> vh_ptypes[VarHandle::kMaxAccessorParameters];
+ const int32_t vh_ptypes_count = BuildParameterArray(vh_ptypes,
+ access_mode_template,
+ var_type.Get(),
+ GetCoordinateType0(),
+ GetCoordinateType1());
+ if (vh_ptypes_count != method_type->GetPTypes()->GetLength() - kVarHandleParameters) {
+ return false;
+ }
+
+ // Check the parameter types are compatible (ignoring the VarHandle parameter).
+ ObjPtr<ObjectArray<Class>> mt_ptypes = method_type->GetPTypes();
+ for (int32_t i = 0; i < vh_ptypes_count; ++i) {
+ if (!IsParameterTypeConvertible(mt_ptypes->Get(i + kVarHandleParameters), vh_ptypes[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
MethodType* VarHandle::GetMethodTypeForAccessMode(Thread* self,
ObjPtr<VarHandle> var_handle,
AccessMode access_mode) {
@@ -1537,6 +1579,53 @@
}
}
+VarHandle::AccessMode VarHandle::GetAccessModeByIntrinsic(Intrinsics intrinsic) {
+#define VAR_HANDLE_ACCESS_MODE(V) \
+ V(CompareAndExchange) \
+ V(CompareAndExchangeAcquire) \
+ V(CompareAndExchangeRelease) \
+ V(CompareAndSet) \
+ V(Get) \
+ V(GetAcquire) \
+ V(GetAndAdd) \
+ V(GetAndAddAcquire) \
+ V(GetAndAddRelease) \
+ V(GetAndBitwiseAnd) \
+ V(GetAndBitwiseAndAcquire) \
+ V(GetAndBitwiseAndRelease) \
+ V(GetAndBitwiseOr) \
+ V(GetAndBitwiseOrAcquire) \
+ V(GetAndBitwiseOrRelease) \
+ V(GetAndBitwiseXor) \
+ V(GetAndBitwiseXorAcquire) \
+ V(GetAndBitwiseXorRelease) \
+ V(GetAndSet) \
+ V(GetAndSetAcquire) \
+ V(GetAndSetRelease) \
+ V(GetOpaque) \
+ V(GetVolatile) \
+ V(Set) \
+ V(SetOpaque) \
+ V(SetRelease) \
+ V(SetVolatile) \
+ V(WeakCompareAndSet) \
+ V(WeakCompareAndSetAcquire) \
+ V(WeakCompareAndSetPlain) \
+ V(WeakCompareAndSetRelease)
+ switch (intrinsic) {
+#define INTRINSIC_CASE(Name) \
+ case Intrinsics::kVarHandle ## Name: \
+ return VarHandle::AccessMode::k ## Name;
+ VAR_HANDLE_ACCESS_MODE(INTRINSIC_CASE)
+#undef INTRINSIC_CASE
+#undef VAR_HANDLE_ACCESS_MODE
+ default:
+ break;
+ }
+ LOG(FATAL) << "Unknown VarHandle instrinsic: " << static_cast<int>(intrinsic);
+ UNREACHABLE();
+}
+
bool VarHandle::GetAccessModeByMethodName(const char* method_name, AccessMode* access_mode) {
if (method_name == nullptr) {
return false;
diff --git a/runtime/mirror/var_handle.h b/runtime/mirror/var_handle.h
index 6565af7..d46d900 100644
--- a/runtime/mirror/var_handle.h
+++ b/runtime/mirror/var_handle.h
@@ -28,6 +28,8 @@
template<class T> class Handle;
class InstructionOperands;
+enum class Intrinsics;
+
struct VarHandleOffsets;
struct FieldVarHandleOffsets;
struct ArrayElementVarHandleOffsets;
@@ -106,6 +108,13 @@
bool IsMethodTypeCompatible(AccessMode access_mode, MethodType* method_type)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Returns true if the MethodType specified is compatible with the
+ // specified access_mode if the first parameter of method_type is
+ // ignored. This is useful for comparing MethodType instances when
+ // invoking a VarHandleAccessor via a MethodHandle invoker.
+ bool IsInvokerMethodTypeCompatible(AccessMode access_mode, MethodType* method_type)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Allocates and returns the MethodType associated with the
// AccessMode. No check is made for whether the AccessMode is a
// supported operation so the MethodType can be used when raising a
@@ -126,11 +135,13 @@
// nullptr if accessor_method is not supported.
static const char* GetReturnTypeDescriptor(const char* accessor_method);
+ // Returns the AccessMode corresponding to a VarHandle accessor intrinsic.
+ static AccessMode GetAccessModeByIntrinsic(Intrinsics ordinal);
+
// Returns true and sets access_mode if method_name corresponds to a
// VarHandle access method, such as "setOpaque". Returns false otherwise.
static bool GetAccessModeByMethodName(const char* method_name, AccessMode* access_mode);
-
static mirror::Class* StaticClass() REQUIRES_SHARED(Locks::mutator_lock_);
static void SetClass(Class* klass) REQUIRES_SHARED(Locks::mutator_lock_);
static void ResetClass() REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/test/713-varhandle-invokers/build b/test/713-varhandle-invokers/build
new file mode 100755
index 0000000..09d376b
--- /dev/null
+++ b/test/713-varhandle-invokers/build
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2018 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.
+
+# Make us exit on a failure
+set -e
+
+./default-build "$@" --experimental var-handles
diff --git a/test/713-varhandle-invokers/expected.txt b/test/713-varhandle-invokers/expected.txt
new file mode 100644
index 0000000..a62d366
--- /dev/null
+++ b/test/713-varhandle-invokers/expected.txt
@@ -0,0 +1,4 @@
+fieldVarHandleExactInvokerTest
+fieldVarHandleInvokerTest
+DivergenceExactInvokerTest
+DivergenceInvokerTest
diff --git a/test/713-varhandle-invokers/info.txt b/test/713-varhandle-invokers/info.txt
new file mode 100644
index 0000000..453c9ff
--- /dev/null
+++ b/test/713-varhandle-invokers/info.txt
@@ -0,0 +1 @@
+Tests invocation of VarHandles wrapped with MethodHandles.
diff --git a/test/713-varhandle-invokers/src/Main.java b/test/713-varhandle-invokers/src/Main.java
new file mode 100644
index 0000000..6a5ed4f
--- /dev/null
+++ b/test/713-varhandle-invokers/src/Main.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.VarHandle;
+import java.lang.invoke.WrongMethodTypeException;
+
+public final class Main {
+ static class TestSetupError extends Error {
+ TestSetupError(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ private static void failAssertion(String message) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Test failure: ");
+ sb.append(message);
+ throw new AssertionError(sb.toString());
+ }
+
+ private static void assertUnreachable() throws Throwable {
+ failAssertion("Unreachable");
+ }
+
+ private static void failAssertEquals(Object expected, Object actual) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(expected);
+ sb.append(" != ");
+ sb.append(actual);
+ failAssertion(sb.toString());
+ }
+
+ private static void assertEquals(int expected, int actual) {
+ if (expected != actual) {
+ failAssertEquals(expected, actual);
+ }
+ }
+
+ private static void assertEquals(float expected, float actual) {
+ if (expected != actual) {
+ failAssertEquals(expected, actual);
+ }
+ }
+
+ private static void assertEquals(double expected, double actual) {
+ if (expected != actual) {
+ failAssertEquals(expected, actual);
+ }
+ }
+
+ static class FieldVarHandleExactInvokerTest {
+ private static final VarHandle fieldVarHandle;
+ int field;
+
+ static {
+ try {
+ fieldVarHandle =
+ MethodHandles.lookup()
+ .findVarHandle(
+ FieldVarHandleExactInvokerTest.class, "field", int.class);
+ } catch (Exception e) {
+ throw new TestSetupError("Failed to lookup of field", e);
+ }
+ }
+
+ void run() throws Throwable {
+ System.out.println("fieldVarHandleExactInvokerTest");
+
+ MethodHandle invokerMethodHandle =
+ MethodHandles.varHandleExactInvoker(
+ VarHandle.AccessMode.GET_AND_SET,
+ MethodType.methodType(
+ int.class, FieldVarHandleExactInvokerTest.class, int.class));
+
+ field = 3;
+ assertEquals(3, (int) invokerMethodHandle.invokeExact(fieldVarHandle, this, 4));
+ assertEquals(4, field);
+
+ //
+ // Check invocations with MethodHandle.invokeExact()
+ //
+ try {
+ // Check for unboxing
+ int i =
+ (int)
+ invokerMethodHandle.invokeExact(
+ fieldVarHandle, this, Integer.valueOf(3));
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(4, field);
+ }
+ try {
+ // Check for widening conversion
+ int i = (int) invokerMethodHandle.invokeExact(fieldVarHandle, this, (short) 3);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(4, field);
+ }
+ try {
+ // Check for acceptance of void return type
+ invokerMethodHandle.invokeExact(fieldVarHandle, this, 77);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(4, field);
+ }
+ try {
+ // Check for wider return type
+ long l = (long) invokerMethodHandle.invokeExact(fieldVarHandle, this, 77);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(4, field);
+ }
+ try {
+ // Check null VarHandle instance fails
+ VarHandle vhNull = null;
+ int i = (int) invokerMethodHandle.invokeExact(vhNull, this, 777);
+ assertUnreachable();
+ } catch (NullPointerException expected) {
+ assertEquals(4, field);
+ }
+
+ //
+ // Check invocations with MethodHandle.invoke()
+ //
+
+ // Check for unboxing
+ int i = (int) invokerMethodHandle.invoke(fieldVarHandle, this, Integer.valueOf(3));
+ assertEquals(3, field);
+
+ // Check for unboxing
+ i = (int) invokerMethodHandle.invoke(fieldVarHandle, this, Short.valueOf((short) 4));
+ assertEquals(4, field);
+
+ // Check for widening conversion
+ i = (int) invokerMethodHandle.invoke(fieldVarHandle, this, (short) 23);
+ assertEquals(23, field);
+
+ // Check for acceptance of void return type
+ invokerMethodHandle.invoke(fieldVarHandle, this, 77);
+ assertEquals(77, field);
+
+ // Check for wider return type
+ long l = (long) invokerMethodHandle.invoke(fieldVarHandle, this, 88);
+ assertEquals(88, field);
+
+ try {
+ // Check null VarHandle instance fails
+ VarHandle vhNull = null;
+ i = (int) invokerMethodHandle.invoke(vhNull, this, 888);
+ assertUnreachable();
+ } catch (NullPointerException expected) {
+ assertEquals(88, field);
+ }
+ }
+ }
+
+ static class FieldVarHandleInvokerTest {
+ private static final VarHandle fieldVarHandle;
+ int field;
+
+ static {
+ try {
+ fieldVarHandle =
+ MethodHandles.lookup()
+ .findVarHandle(FieldVarHandleInvokerTest.class, "field", int.class);
+ } catch (Exception e) {
+ throw new TestSetupError("Failed to lookup of field", e);
+ }
+ }
+
+ void run() throws Throwable {
+ System.out.println("fieldVarHandleInvokerTest");
+ MethodHandle invokerMethodHandle =
+ MethodHandles.varHandleInvoker(
+ VarHandle.AccessMode.GET_AND_SET,
+ MethodType.methodType(
+ int.class, FieldVarHandleInvokerTest.class, int.class));
+
+ field = 3;
+ int oldField = (int) invokerMethodHandle.invoke(fieldVarHandle, this, 4);
+ assertEquals(3, oldField);
+ assertEquals(4, field);
+
+ //
+ // Check invocations with MethodHandle.invoke()
+ //
+
+ // Check for unboxing
+ int i = (int) invokerMethodHandle.invoke(fieldVarHandle, this, Integer.valueOf(3));
+ assertEquals(3, field);
+
+ // Check for widening conversion
+ i = (int) invokerMethodHandle.invoke(fieldVarHandle, this, (short) 33);
+ assertEquals(33, field);
+
+ // Check for widening conversion
+ i = (int) invokerMethodHandle.invoke(fieldVarHandle, this, Byte.valueOf((byte) 34));
+ assertEquals(34, field);
+
+ // Check for acceptance of void return type
+ invokerMethodHandle.invoke(fieldVarHandle, this, 77);
+ assertEquals(77, field);
+
+ // Check for wider return type
+ long l = (long) invokerMethodHandle.invoke(fieldVarHandle, this, 88);
+ assertEquals(88, field);
+ try {
+ // Check narrowing conversion fails
+ i = (int) invokerMethodHandle.invoke(fieldVarHandle, this, 3.0);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ }
+ try {
+ // Check reference type fails
+ i = (int) invokerMethodHandle.invoke(fieldVarHandle, this, "Bad");
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ }
+ try {
+ // Check null VarHandle instance fails
+ VarHandle vhNull = null;
+ i = (int) invokerMethodHandle.invoke(vhNull, this, 888);
+ assertUnreachable();
+ } catch (NullPointerException expected) {
+ assertEquals(88, field);
+ }
+
+ //
+ // Check invocations with MethodHandle.invokeExact()
+ //
+ field = -1;
+ try {
+ // Check for unboxing
+ i = (int) invokerMethodHandle.invokeExact(fieldVarHandle, this, Integer.valueOf(3));
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(-1, field);
+ }
+ try {
+ // Check for widening conversion
+ i = (int) invokerMethodHandle.invokeExact(fieldVarHandle, this, (short) 33);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(-1, field);
+ }
+ try {
+ // Check for acceptance of void return type
+ invokerMethodHandle.invokeExact(fieldVarHandle, this, 77);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(-1, field);
+ }
+ try {
+ // Check for wider return type
+ l = (long) invokerMethodHandle.invokeExact(fieldVarHandle, this, 78);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(-1, field);
+ }
+ try {
+ // Check narrowing conversion fails
+ i = (int) invokerMethodHandle.invokeExact(fieldVarHandle, this, 3.0);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ }
+ try {
+ // Check reference type fails
+ i = (int) invokerMethodHandle.invokeExact(fieldVarHandle, this, "Bad");
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ }
+ try {
+ // Check null VarHandle instance fails
+ VarHandle vhNull = null;
+ i = (int) invokerMethodHandle.invokeExact(vhNull, this, 888);
+ assertUnreachable();
+ } catch (NullPointerException expected) {
+ assertEquals(-1, field);
+ }
+ }
+ }
+
+ static class DivergenceExactInvokerTest {
+ private static final VarHandle floatsArrayVarHandle;
+
+ static {
+ try {
+ floatsArrayVarHandle = MethodHandles.arrayElementVarHandle(float[].class);
+ } catch (Exception e) {
+ throw new TestSetupError("Failed to create VarHandle", e);
+ }
+ }
+
+ void run() throws Throwable {
+ System.out.println("DivergenceExactInvokerTest");
+ float[] floatsArray = new float[4];
+ // Exact invoker of an accessor having the form:
+ // float accessor(float[] values, int index, Float current, float replacement)
+ MethodHandle exactInvoker =
+ MethodHandles.varHandleExactInvoker(
+ VarHandle.AccessMode.COMPARE_AND_EXCHANGE,
+ MethodType.methodType(
+ float.class,
+ float[].class,
+ int.class,
+ Float.class,
+ float.class));
+ floatsArray[2] = Float.valueOf(4.0f);
+ // Callsite that is an exact match with exactInvoker.type().
+ try {
+ // exactInvoker.type() is not compatible with floatsArrayVarHandle accessor.
+ float old =
+ (float)
+ exactInvoker.invoke(
+ floatsArrayVarHandle,
+ floatsArray,
+ 2,
+ Float.valueOf(4.0f),
+ 8.0f);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(4.0f, floatsArray[2]);
+ }
+
+ // Callsites that are exact matches with exactInvoker.type()
+ try {
+ // Mismatch between exactInvoker.type() and VarHandle type (Float != float)
+ float old =
+ (float)
+ exactInvoker.invoke(
+ floatsArrayVarHandle, floatsArray, 2, 8.0f, 16.0f);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(4.0f, floatsArray[2]);
+ }
+ try {
+ // short not convertible to Float
+ float old =
+ (float)
+ exactInvoker.invoke(
+ floatsArrayVarHandle, floatsArray, 2, (short) 4, 13.0f);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(4.0f, floatsArray[2]);
+ }
+ try {
+ // int not convertible to Float
+ float old =
+ (float) exactInvoker.invoke(floatsArrayVarHandle, floatsArray, 2, 8, -8.0f);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(4.0f, floatsArray[2]);
+ }
+ }
+ }
+
+ static class DivergenceInvokerTest {
+ private static final VarHandle floatsArrayVarHandle;
+
+ static {
+ try {
+ floatsArrayVarHandle = MethodHandles.arrayElementVarHandle(float[].class);
+ } catch (Exception e) {
+ throw new TestSetupError("Failed to create VarHandle", e);
+ }
+ }
+
+ void run() throws Throwable {
+ System.out.println("DivergenceInvokerTest");
+ float[] floatsArray = new float[4];
+ // Invoker of an accessor having the form:
+ // float accessor(float[] values, int index, Float current, float replacement)
+ MethodHandle invoker =
+ MethodHandles.varHandleInvoker(
+ VarHandle.AccessMode.COMPARE_AND_EXCHANGE,
+ MethodType.methodType(
+ float.class,
+ float[].class,
+ int.class,
+ Float.class,
+ float.class));
+ floatsArray[2] = Float.valueOf(4.0f);
+ // Callsite that is an exact match with invoker.type()
+ float old =
+ (float)
+ invoker.invoke(
+ floatsArrayVarHandle,
+ floatsArray,
+ 2,
+ Float.valueOf(4.0f),
+ 8.0f);
+ assertEquals(4.0f, old);
+ assertEquals(8.0f, floatsArray[2]);
+
+ // Callsite that is convertible match to invoker.type()
+ old = (float) invoker.invoke(floatsArrayVarHandle, floatsArray, 2, 8.0f, 16.0f);
+ assertEquals(8.0f, old);
+ assertEquals(16.0f, floatsArray[2]);
+
+ // Callsites that are not convertible to invoker.type().
+ try {
+ // short is not convertible to Float
+ old = (float) invoker.invoke(floatsArrayVarHandle, floatsArray, 2, (short) 4, 8.0f);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(16.0f, floatsArray[2]);
+ }
+ try {
+ // int is not convertible to Float
+ old = (float) invoker.invoke(floatsArrayVarHandle, floatsArray, 2, 8, -8.0f);
+ assertUnreachable();
+ } catch (WrongMethodTypeException expected) {
+ assertEquals(16.0f, floatsArray[2]);
+ }
+ }
+ }
+
+ public static void main(String[] args) throws Throwable {
+ new FieldVarHandleExactInvokerTest().run();
+ new FieldVarHandleInvokerTest().run();
+ new DivergenceExactInvokerTest().run();
+ new DivergenceInvokerTest().run();
+ }
+}