diff options
author | 2018-01-30 07:51:10 +0000 | |
---|---|---|
committer | 2018-02-22 12:45:02 +0000 | |
commit | b8b9387c0d7420b463a9ca11bf18ae86f3ed0a65 (patch) | |
tree | d7fba6a12a1d074d8d0c1a80d1eae0fd9a41b5ed | |
parent | ae3c5cc582442e3493aa23155f2ee2f6b4613923 (diff) |
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
-rw-r--r-- | libdexfile/dex/dex_instruction.h | 2 | ||||
-rw-r--r-- | runtime/method_handles-inl.h | 55 | ||||
-rw-r--r-- | runtime/method_handles.cc | 138 | ||||
-rw-r--r-- | runtime/method_handles.h | 3 | ||||
-rw-r--r-- | runtime/mirror/method_handle_impl.h | 4 | ||||
-rw-r--r-- | runtime/mirror/method_type.cc | 47 | ||||
-rw-r--r-- | runtime/mirror/method_type.h | 32 | ||||
-rw-r--r-- | runtime/mirror/var_handle.cc | 89 | ||||
-rw-r--r-- | runtime/mirror/var_handle.h | 13 | ||||
-rwxr-xr-x | test/713-varhandle-invokers/build | 20 | ||||
-rw-r--r-- | test/713-varhandle-invokers/expected.txt | 4 | ||||
-rw-r--r-- | test/713-varhandle-invokers/info.txt | 1 | ||||
-rw-r--r-- | test/713-varhandle-invokers/src/Main.java | 440 |
13 files changed, 803 insertions, 45 deletions
diff --git a/libdexfile/dex/dex_instruction.h b/libdexfile/dex/dex_instruction.h index c9533656d3..3155c14048 100644 --- a/libdexfile/dex/dex_instruction.h +++ b/libdexfile/dex/dex_instruction.h @@ -741,7 +741,7 @@ class VarArgsInstructionOperands FINAL : public InstructionOperands { // 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 2bc71f4b5e..41c8384e0d 100644 --- a/runtime/method_handles-inl.h +++ b/runtime/method_handles-inl.h @@ -33,10 +33,9 @@ namespace art { 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, } } +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 @@ bool PerformConversions(Thread* self, 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 @@ bool PerformConversions(Thread* self, } } else { JValue value; - if (Primitive::Is64BitType(from_type)) { value.SetJ(getter->GetLong()); } else if (from_type == Primitive::kPrimNot) { @@ -114,13 +124,11 @@ bool PerformConversions(Thread* self, } 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 @@ bool PerformConversions(Thread* self, } } } - 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 9b21e1d9bf..82370c4631 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 @@ inline bool IsInvokeTransform(const mirror::MethodHandle::Kind handle_kind) { || 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 MethodHandleFieldAccess(Thread* self, } } +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 @@ static inline bool MethodHandleInvokeInternal(Thread* self, 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 @@ static inline bool MethodHandleInvokeExactInternal( } // 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 3b1bf2ee66..7e60a5c170 100644 --- a/runtime/method_handles.h +++ b/runtime/method_handles.h @@ -123,7 +123,8 @@ bool PerformConversions(Thread* self, 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 a1bc976a25..3b0002c2af 100644 --- a/runtime/mirror/method_handle_impl.h +++ b/runtime/mirror/method_handle_impl.h @@ -48,6 +48,8 @@ class MANAGED MethodHandle : public Object { kInvokeInterface, kInvokeTransform, kInvokeCallSiteTransform, + kInvokeVarHandle, + kInvokeVarHandleExact, kInstanceGet, kInstancePut, kStaticGet, @@ -55,7 +57,7 @@ class MANAGED MethodHandle : public Object { 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 4b8dfacec6..6ac5012ca3 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 @@ mirror::MethodType* MethodType::Create(Thread* const self, 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 @@ size_t MethodType::NumberOfVRegs() REQUIRES_SHARED(Locks::mutator_lock_) { 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 @@ bool MethodType::IsExactMatch(mirror::MethodType* target) REQUIRES_SHARED(Locks: 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::string MethodType::PrettyDescriptor() REQUIRES_SHARED(Locks::mutator_lock_) 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 a9f3c9c116..edd991092a 100644 --- a/runtime/mirror/method_type.h +++ b/runtime/mirror/method_type.h @@ -31,12 +31,16 @@ namespace mirror { // 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 @@ class MANAGED MethodType : public Object { 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 @@ class MANAGED MethodType : public Object { // 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 @@ class MANAGED MethodType : public Object { 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 85d06f03fe..a79c0a26d0 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 @@ bool VarHandle::IsMethodTypeCompatible(AccessMode access_mode, MethodType* metho 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 @@ const char* VarHandle::GetReturnTypeDescriptor(const char* accessor_name) { } } +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 6565af7f06..d46d900a8d 100644 --- a/runtime/mirror/var_handle.h +++ b/runtime/mirror/var_handle.h @@ -28,6 +28,8 @@ namespace art { template<class T> class Handle; class InstructionOperands; +enum class Intrinsics; + struct VarHandleOffsets; struct FieldVarHandleOffsets; struct ArrayElementVarHandleOffsets; @@ -106,6 +108,13 @@ class MANAGED VarHandle : public Object { 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 @@ class MANAGED VarHandle : public Object { // 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 0000000000..09d376bbac --- /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 0000000000..a62d3662e1 --- /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 0000000000..453c9ffb7c --- /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 0000000000..6a5ed4ff9e --- /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(); + } +} |