diff options
62 files changed, 2449 insertions, 175 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index dcde5abbca..717403fe35 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -29,6 +29,7 @@ GTEST_DEX_DIRECTORIES := \ GetMethodSignature \ Instrumentation \ Interfaces \ + LambdaInterfaces \ Lookup \ Main \ MultiDex \ @@ -77,6 +78,7 @@ ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex ART_GTEST_oat_test_DEX_DEPS := Main ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY ART_GTEST_proxy_test_DEX_DEPS := Interfaces +ART_GTEST_lambda_proxy_test_DEX_DEPS := LambdaInterfaces ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods ART_GTEST_stub_test_DEX_DEPS := AllFields ART_GTEST_transaction_test_DEX_DEPS := Transaction @@ -97,6 +99,7 @@ ART_GTEST_oat_file_assistant_test_TARGET_DEPS := \ # TODO: document why this is needed. ART_GTEST_proxy_test_HOST_DEPS := $(HOST_CORE_IMAGE_default_no-pic_64) $(HOST_CORE_IMAGE_default_no-pic_32) +ART_GTEST_lambda_proxy_test_HOST_DEPS := $(HOST_CORE_IMAGE_default_no-pic_64) $(HOST_CORE_IMAGE_default_no-pic_32) # The dexdump test requires an image and the dexdump utility. # TODO: rename into dexdump when migration completes @@ -233,6 +236,7 @@ RUNTIME_GTEST_COMMON_SRC_FILES := \ COMPILER_GTEST_COMMON_SRC_FILES := \ runtime/jni_internal_test.cc \ + runtime/lambda_proxy_test.cc \ runtime/proxy_test.cc \ runtime/reflection_test.cc \ compiler/compiled_method_test.cc \ @@ -741,6 +745,7 @@ ART_GTEST_oat_file_assistant_test_HOST_DEPS := ART_GTEST_oat_file_assistant_test_TARGET_DEPS := ART_GTEST_object_test_DEX_DEPS := ART_GTEST_proxy_test_DEX_DEPS := +ART_GTEST_lambda_proxy_test_DEX_DEPS := ART_GTEST_reflection_test_DEX_DEPS := ART_GTEST_stub_test_DEX_DEPS := ART_GTEST_transaction_test_DEX_DEPS := diff --git a/runtime/Android.mk b/runtime/Android.mk index 0b0f0942a3..4f4792a709 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -106,6 +106,7 @@ LIBART_COMMON_SRC_FILES := \ jit/profiling_info.cc \ lambda/art_lambda_method.cc \ lambda/box_table.cc \ + lambda/box_class_table.cc \ lambda/closure.cc \ lambda/closure_builder.cc \ lambda/leaking_allocator.cc \ diff --git a/runtime/arch/arch_test.cc b/runtime/arch/arch_test.cc index d6ba304bd1..771c8b7e2f 100644 --- a/runtime/arch/arch_test.cc +++ b/runtime/arch/arch_test.cc @@ -46,9 +46,15 @@ class ArchTest : public CommonRuntimeTest { } }; +} // namespace art + // Common tests are declared next to the constants. #define ADD_TEST_EQ(x, y) EXPECT_EQ(x, y); #include "asm_support.h" +// Important: Do not include this inside of another namespace, since asm_support.h +// defines its own namespace which must not be nested. + +namespace art { TEST_F(ArchTest, CheckCommonOffsetsAndSizes) { CheckAsmSupportOffsetsAndSizes(); diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index 631b784787..588268d878 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -1045,6 +1045,26 @@ ENTRY art_quick_proxy_invoke_handler DELIVER_PENDING_EXCEPTION END art_quick_proxy_invoke_handler +// Forward call from boxed innate lambda to the underlying lambda closure's target method. + .extern artQuickLambdaProxyInvokeHandler +ENTRY art_quick_lambda_proxy_invoke_handler +// TODO: have a faster handler that doesn't need to set up a frame + SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_R0 + mov r2, r9 @ pass Thread::Current + mov r3, sp @ pass SP + blx artQuickLambdaProxyInvokeHandler @ (Method* proxy method, receiver, Thread*, SP) + ldr r2, [r9, #THREAD_EXCEPTION_OFFSET] @ load Thread::Current()->exception_ + // Tear down the callee-save frame. Skip arg registers. + add sp, #(FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE - FRAME_SIZE_REFS_ONLY_CALLEE_SAVE) + .cfi_adjust_cfa_offset -(FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE - FRAME_SIZE_REFS_ONLY_CALLEE_SAVE) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME + cbnz r2, 1f @ success if no exception is pending + vmov d0, r0, r1 @ store into fpr, for when it's a fpr return... + bx lr @ return on success +1: + DELIVER_PENDING_EXCEPTION +END art_quick_lambda_proxy_invoke_handler + /* * Called to resolve an imt conflict. r12 is a hidden argument that holds the target method's * dex method index. diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index 9ccabad1cc..177873d73f 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -1582,6 +1582,28 @@ ENTRY art_quick_proxy_invoke_handler DELIVER_PENDING_EXCEPTION END art_quick_proxy_invoke_handler + /* + * Called by managed code that is attempting to call a method on a lambda proxy class. On entry + * x0 holds the lambda proxy method and x1 holds the receiver; The frame size of the invoked + * lambda proxy method agrees with a ref and args callee save frame. + */ + .extern artQuickLambdaProxyInvokeHandler +ENTRY art_quick_lambda_proxy_invoke_handler +// TODO: have a faster way to invoke lambda proxies without setting up the whole frame. + SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_X0 + mov x2, xSELF // pass Thread::Current + mov x3, sp // pass SP + bl artQuickLambdaProxyInvokeHandler // (Method* proxy method, receiver, Thread*, SP) + ldr x2, [xSELF, THREAD_EXCEPTION_OFFSET] + cbnz x2, .Lexception_in_lambda_proxy // success if no exception is pending + RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME // Restore frame + fmov d0, x0 // Store result in d0 in case it was float or double + ret // return on success +.Lexception_in_lambda_proxy: + RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME + DELIVER_PENDING_EXCEPTION +END art_quick_lambda_proxy_invoke_handler + /* * Called to resolve an imt conflict. xIP1 is a hidden argument that holds the target method's * dex method index. diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S index 0691f2a620..af79f5ef4d 100644 --- a/runtime/arch/mips/quick_entrypoints_mips.S +++ b/runtime/arch/mips/quick_entrypoints_mips.S @@ -1377,6 +1377,10 @@ ENTRY art_quick_proxy_invoke_handler DELIVER_PENDING_EXCEPTION END art_quick_proxy_invoke_handler +// Forward call from boxed innate lambda to the underlying lambda closure's target method. + .extern artQuickLambdaProxyInvokeHandler +UNIMPLEMENTED art_quick_lambda_proxy_invoke_handler + /* * Called to resolve an imt conflict. t0 is a hidden argument that holds the target method's * dex method index. diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S index 66c8aadf33..5e70a95a2d 100644 --- a/runtime/arch/mips64/quick_entrypoints_mips64.S +++ b/runtime/arch/mips64/quick_entrypoints_mips64.S @@ -1431,6 +1431,10 @@ ENTRY art_quick_proxy_invoke_handler DELIVER_PENDING_EXCEPTION END art_quick_proxy_invoke_handler +// Forward call from boxed innate lambda to the underlying lambda closure's target method. + .extern artQuickLambdaProxyInvokeHandler +UNIMPLEMENTED art_quick_lambda_proxy_invoke_handler + /* * Called to resolve an imt conflict. t0 is a hidden argument that holds the target method's * dex method index. diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index 463c9cf10e..4fb6119ac1 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -1391,6 +1391,149 @@ DEFINE_FUNCTION art_quick_proxy_invoke_handler RETURN_OR_DELIVER_PENDING_EXCEPTION // return or deliver exception END_FUNCTION art_quick_proxy_invoke_handler +#if LAMBDA_INVOKE_USES_LONG +#undef LAMBDA_PROXY_SETUP_FRAME +// We need to always do a 'pop' to readjust the stack, so we have to use the slower call instruction. +#define LAMBDA_PROXY_SETUP_FRAME 1 +#define LAMBDA_INVOKE_REALIGN_STACK_FRAME 1 +#else +#define LAMBDA_INVOKE_REALIGN_STACK_FRAME 0 +#endif + +#define LAMBDA_INVOKE_CALLS_INTO_RUNTIME LAMBDA_INVOKE_REALIGN_STACK_FRAME + +// Forward call from boxed innate lambda to the underlying lambda closure's target method. +DEFINE_FUNCTION art_quick_lambda_proxy_invoke_handler + // This function is always called when the lambda is innate. + // Therefore we can assume the box is to an innate lambda. + // TODO: perhaps there should be a DCHECK to make sure it's innate? + +#if LAMBDA_PROXY_SETUP_FRAME + // Set up a quick frame when debugging so we can see that it's going through a stub. + // An invoke-virtual + a stub invocation is enough of a hint that we *could* be + // going through a lambda proxy. + SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_EAX +#endif + +#if !LAMBDA_INVOKE_CALLS_INTO_RUNTIME + // Rewrite the following 2 arguments, stored on stack frame: + // + // |--------| + // |receiver| <- esp-4 + // |--------| + // | method | <- esp + // |--------| + + // Set up the new correct method receiver (swap object with closure). + // -- The original object is no longer available after this. + // + // (Before) + // ecx == mirror::Object* boxed_lambda; // lambda proxy object. + movl MIRROR_OBJECT_BOXED_INNATE_LAMBDA_CLOSURE_POINTER_OFFSET(%ecx), %ecx + // (After) + // lambda::Closure* closure = boxed_lambda->closure_; + // boxed_lambda = closure; // Overwrite lambda proxy object + // ecx == closure + + // Look up the new correct method target. + // -- The original method target is no longer available after this. + // + // (Before) + // eax == ArtMethod* old_receiver_method; + movl LAMBDA_CLOSURE_METHOD_OFFSET(%ecx), %eax + // (After) + // ArtLambdaMethod* lambda_method_target = closure->lambda_info_; + // eax = lambda_method_target + // + // Set up the correct method target from the lambda info. + movl ART_LAMBDA_METHOD_ART_METHOD_OFFSET(%eax), %eax // Load new receiver method + // (After) + // ArtMethod* target_method = lambda_method_target->target_ + // eax = target_method +#endif + +#if LAMBDA_INVOKE_CALLS_INTO_RUNTIME + PUSH esp // pass SP + pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() + CFI_ADJUST_CFA_OFFSET(4) + PUSH ecx // pass receiver + PUSH eax // pass proxy method + call SYMBOL(artQuickLambdaProxyInvokeHandler) // (proxy method, receiver, Thread*, SP) + movd %eax, %xmm0 // place return value also into floating point return value + movd %edx, %xmm1 + punpckldq %xmm1, %xmm0 + addl LITERAL(16 + FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE - FRAME_SIZE_REFS_ONLY_CALLEE_SAVE), %esp + CFI_ADJUST_CFA_OFFSET(-(16 + FRAME_SIZE_REFS_AND_ARGS_CALLEE_SAVE - FRAME_SIZE_REFS_ONLY_CALLEE_SAVE)) + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME + RETURN_OR_DELIVER_PENDING_EXCEPTION // return or deliver exception +#endif + +#if LAMBDA_INVOKE_USES_LONG && !LAMBDA_INVOKE_REALIGN_STACK_FRAME + // As a temporary workaround, lambda functions look like + // (J[Arg2][Arg3][Arg4]...) + // This means that we can't just pass in the lambda as a 32-bit pointer + // We pad the arguments with an extra 32-bit "0" where Arg2 used to be instead. + + // Required arguments for a lambda method: + // + // Arg0 = eax = method + // Arg1 = ecx = closure (hi) + // Arg2 = edx = closure (lo) + // Arg3 = ebx = <?> (first user-defined argument) + + // Transformation diagram: + // + // Arg0 Arg1 Arg2 Arg3 ... ArgN + // | | \ \ \ + // | | \ \ \ + // Arg0 Arg1 0x00 Arg2 Arg3 ... ArgN + // /\ + // (inserted) + PUSH ebx // Move out Arg3 into Arg4, and also for all K>3 ArgK into ArgK+1 + mov %edx, %ebx // Move out Arg2 into Arg3 + xor %edx, %edx // Clear closure 32-bit low register + + // XX: Does this work at all ? This probably breaks the visitors (*and* its unaligned). + + // FIXME: call into the runtime and do a proxy-like-invoke + // using a ShadowFrame quick visitor, and then use ArtMethod::Invoke + // to call into the actual method (which will take care of fixing up alignment). + // Trying to realign in the assembly itself won't actually work + // since then the visitor will unwind incorrectly (unless we also fixed up the ManagedStack). +#endif + + // TODO: avoid extra indirect load by subclass ArtLambdaMethod from ArtMethod. + + // Forward the call to the overwritten receiver method. + // -- Arguments [2,N] are left completely untouched since the signature is otherwise identical. +#if LAMBDA_PROXY_SETUP_FRAME + #if LAMBDA_INVOKE_CALLS_INTO_RUNTIME + // Have to call into runtime in order to re-align the stack frame to 16 bytes. + int3 + #else + // Just call into the method directly. Don't worry about realigning. + call *ART_METHOD_QUICK_CODE_OFFSET_32(%eax) // (new method, new receiver, old args...) + + // The stack frame was manually adjusted, so make sure we have a pop here to fix it back. + #if LAMBDA_INVOKE_USES_LONG && !LAMBDA_INVOKE_REALIGN_STACK_FRAME + + POP ecx // OK: ecx is scratch register after the call. + // XX: use 'add esp, 4' instead if we need to keep the register? This way we get cleaner CFI. + #endif + #endif + RESTORE_REFS_ONLY_CALLEE_SAVE_FRAME + +#else + // Do not use 'call' here since the stack visitors wouldn't know how to visit this frame. + jmp *ART_METHOD_QUICK_CODE_OFFSET_32(%eax) // tailcall (new method, new receiver, old args...) +#endif + +#if LAMBDA_PROXY_SETUP_FRAME + ret +#endif + +END_FUNCTION art_quick_lambda_proxy_invoke_handler + /* * Called to resolve an imt conflict. xmm7 is a hidden argument that holds the target method's * dex method index. diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index 17d277e1fd..0a54aa34ca 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -1297,7 +1297,6 @@ DEFINE_FUNCTION art_quick_set64_static RETURN_IF_EAX_ZERO // return or deliver exception END_FUNCTION art_quick_set64_static - DEFINE_FUNCTION art_quick_proxy_invoke_handler SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_RDI @@ -1309,6 +1308,60 @@ DEFINE_FUNCTION art_quick_proxy_invoke_handler RETURN_OR_DELIVER_PENDING_EXCEPTION END_FUNCTION art_quick_proxy_invoke_handler +// Forward call from boxed innate lambda to the underlying lambda closure's target method. +DEFINE_FUNCTION art_quick_lambda_proxy_invoke_handler + // This function is always called when the lambda is innate. + // Therefore we can assume the box is to an innate lambda. + // TODO: perhaps there should be a DCHECK to make sure it's innate? + +#if LAMBDA_PROXY_SETUP_FRAME + // Set up a quick frame when debugging so we can see that it's going through a stub. + // Our stack traces will contain the quick lambda proxy hander. + // Note that we *must* go through the handler (when spilling) otherwise we won't know how + // to move the spilled GC references from the caller to this stub. + SETUP_REFS_AND_ARGS_CALLEE_SAVE_FRAME_WITH_METHOD_IN_RDI + + movq %gs:THREAD_SELF_OFFSET, %rdx // Pass Thread::Current(). + movq %rsp, %rcx // Pass SP. + call SYMBOL(artQuickLambdaProxyInvokeHandler) // (proxy method, receiver, Thread*, SP) + RESTORE_REFS_AND_ARGS_CALLEE_SAVE_FRAME + movq %rax, %xmm0 // Copy return value in case of float returns. + RETURN_OR_DELIVER_PENDING_EXCEPTION +#else + // Set up the new correct method receiver (swap object with closure). + // -- The original object is no longer available after this. + // + // (Before) + // rsi == mirror::Object* boxed_lambda; // lambda proxy object. + movq MIRROR_OBJECT_BOXED_INNATE_LAMBDA_CLOSURE_POINTER_OFFSET(%rsi), %rsi + // (After) + // lambda::Closure* closure = boxed_lambda->closure_; // Overwrite receiver object. + // rsi == closure + + // Look up the new correct method target. + // -- The original method target is no longer available after this. + movq LAMBDA_CLOSURE_METHOD_OFFSET(%rsi), %rdi // Overwrite old receiver method. + // (After) + // ArtLambdaMethod* lambda_method_target = closure->lambda_info_; + // rdi == lambda_method_target + + // TODO: avoid extra indirect load by subclass ArtLambdaMethod from ArtMethod. + + // Set up the correct method target from the lambda info. + movq ART_LAMBDA_METHOD_ART_METHOD_OFFSET(%rdi), %rdi // Write new receiver method. + // (After) + // ArtMethod* method_target = lambda_method_target->target_; + // rdi == method_target + + // Forward the call to the overwritten receiver method. + // -- Arguments [2,N] are left completely untouched since the signature is otherwise identical. + // Do not use 'call' here since the stack would be misaligned (8b instead of 16b). + // Also the stack visitors wouldn't know how to visit this frame if we used a call. + jmp *ART_METHOD_QUICK_CODE_OFFSET_64(%rdi) // tailcall (new method, new receiver, old args...) +#endif + +END_FUNCTION art_quick_lambda_proxy_invoke_handler + /* * Called to resolve an imt conflict. * rax is a hidden argument that holds the target method's dex method index. diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h index 4166e22daa..ab42d0ec95 100644 --- a/runtime/art_field-inl.h +++ b/runtime/art_field-inl.h @@ -255,7 +255,7 @@ inline void ArtField::SetObject(mirror::Object* object, mirror::Object* l) { inline const char* ArtField::GetName() SHARED_REQUIRES(Locks::mutator_lock_) { uint32_t field_index = GetDexFieldIndex(); - if (UNLIKELY(GetDeclaringClass()->IsProxyClass())) { + if (UNLIKELY(GetDeclaringClass()->IsAnyProxyClass())) { DCHECK(IsStatic()); DCHECK_LT(field_index, 2U); return field_index == 0 ? "interfaces" : "throws"; @@ -266,7 +266,7 @@ inline const char* ArtField::GetName() SHARED_REQUIRES(Locks::mutator_lock_) { inline const char* ArtField::GetTypeDescriptor() SHARED_REQUIRES(Locks::mutator_lock_) { uint32_t field_index = GetDexFieldIndex(); - if (UNLIKELY(GetDeclaringClass()->IsProxyClass())) { + if (UNLIKELY(GetDeclaringClass()->IsAnyProxyClass())) { DCHECK(IsStatic()); DCHECK_LT(field_index, 2U); // 0 == Class[] interfaces; 1 == Class[][] throws; @@ -290,8 +290,8 @@ template <bool kResolve> inline mirror::Class* ArtField::GetType() { const uint32_t field_index = GetDexFieldIndex(); auto* declaring_class = GetDeclaringClass(); - if (UNLIKELY(declaring_class->IsProxyClass())) { - return ProxyFindSystemClass(GetTypeDescriptor()); + if (UNLIKELY(declaring_class->IsAnyProxyClass())) { + return AnyProxyFindSystemClass(GetTypeDescriptor()); } auto* dex_cache = declaring_class->GetDexCache(); const DexFile* const dex_file = dex_cache->GetDexFile(); diff --git a/runtime/art_field.cc b/runtime/art_field.cc index 3737e0ddee..3ac563a789 100644 --- a/runtime/art_field.cc +++ b/runtime/art_field.cc @@ -69,8 +69,8 @@ ArtField* ArtField::FindStaticFieldWithOffset(mirror::Class* klass, uint32_t fie return nullptr; } -mirror::Class* ArtField::ProxyFindSystemClass(const char* descriptor) { - DCHECK(GetDeclaringClass()->IsProxyClass()); +mirror::Class* ArtField::AnyProxyFindSystemClass(const char* descriptor) { + DCHECK(GetDeclaringClass()->IsAnyProxyClass()); return Runtime::Current()->GetClassLinker()->FindSystemClass(Thread::Current(), descriptor); } diff --git a/runtime/art_field.h b/runtime/art_field.h index a943a34174..4ebe6fbab4 100644 --- a/runtime/art_field.h +++ b/runtime/art_field.h @@ -191,7 +191,9 @@ class ArtField FINAL { } private: - mirror::Class* ProxyFindSystemClass(const char* descriptor) + mirror::Class* AnyProxyFindSystemClass(const char* descriptor) + SHARED_REQUIRES(Locks::mutator_lock_); + mirror::Class* LambdaProxyFindSystemClass(const char* descriptor) SHARED_REQUIRES(Locks::mutator_lock_); mirror::Class* ResolveGetType(uint32_t type_idx) SHARED_REQUIRES(Locks::mutator_lock_); mirror::String* ResolveGetStringName(Thread* self, const DexFile& dex_file, uint32_t string_idx, diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index cf548ada33..b6e811f7b2 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -292,7 +292,7 @@ inline const char* ArtMethod::GetDeclaringClassDescriptor() { } inline const char* ArtMethod::GetShorty(uint32_t* out_length) { - DCHECK(!IsProxyMethod()); + DCHECK(!IsProxyMethod() || IsLambdaProxyMethod()); // OK: lambda proxies use parent dex cache. const DexFile* dex_file = GetDexFile(); return dex_file->GetMethodShorty(dex_file->GetMethodId(GetDexMethodIndex()), out_length); } @@ -354,10 +354,31 @@ inline const DexFile::ProtoId& ArtMethod::GetPrototype() { } inline const DexFile::TypeList* ArtMethod::GetParameterTypeList() { - DCHECK(!IsProxyMethod()); + // XX: Do proxy methods have a dex file? not sure. const DexFile* dex_file = GetDexFile(); - const DexFile::ProtoId& proto = dex_file->GetMethodPrototype( - dex_file->GetMethodId(GetDexMethodIndex())); + const DexFile::MethodId* method_id = nullptr; + + if (kIsDebugBuild) { + if (UNLIKELY(IsProxyMethod())) { + // Proxy method case. + CHECK(IsLambdaProxyMethod()) << "Cannot GetParameterTypeList for java.lang.reflect.Proxy"; + + // + // We do not have a method ID, so look up one of the supers we overrode, + // it will have the same exact parameter type list as we do. + + // Lambda proxy classes have the dex cache from their single interface parent. + // Proxy classes have multiple interface parents, so they use the root dexcache instead. + // + // For lambda proxy classes only, get the type list data from the parent. + // (code happens to look the same as the usual non-proxy path). + } + } + + method_id = &dex_file->GetMethodId(GetDexMethodIndex()); + DCHECK(method_id != nullptr); + + const DexFile::ProtoId& proto = dex_file->GetMethodPrototype(*method_id); return dex_file->GetProtoParameters(proto); } @@ -397,12 +418,20 @@ inline mirror::ClassLoader* ArtMethod::GetClassLoader() { } inline mirror::DexCache* ArtMethod::GetDexCache() { - DCHECK(!IsProxyMethod()); + DCHECK(!IsProxyMethod() || IsLambdaProxyMethod()); // OK: lambda proxies use parent dex cache. return GetDeclaringClass()->GetDexCache(); } inline bool ArtMethod::IsProxyMethod() { - return GetDeclaringClass()->IsProxyClass(); + return GetDeclaringClass()->IsAnyProxyClass(); +} + +inline bool ArtMethod::IsReflectProxyMethod() { + return GetDeclaringClass()->IsReflectProxyClass(); +} + +inline bool ArtMethod::IsLambdaProxyMethod() { + return GetDeclaringClass()->IsLambdaProxyClass(); } inline ArtMethod* ArtMethod::GetInterfaceMethodIfProxy(size_t pointer_size) { @@ -448,9 +477,9 @@ template<typename RootVisitorType> void ArtMethod::VisitRoots(RootVisitorType& visitor, size_t pointer_size) { ArtMethod* interface_method = nullptr; mirror::Class* klass = declaring_class_.Read(); - if (UNLIKELY(klass != nullptr && klass->IsProxyClass())) { + if (UNLIKELY(klass != nullptr && klass->IsAnyProxyClass())) { // For normal methods, dex cache shortcuts will be visited through the declaring class. - // However, for proxies we need to keep the interface method alive, so we visit its roots. + // However, for any proxies we need to keep the interface method alive, so we visit its roots. interface_method = mirror::DexCache::GetElementPtrSize( GetDexCacheResolvedMethods(pointer_size), GetDexMethodIndex(), diff --git a/runtime/art_method.h b/runtime/art_method.h index 5a2d6c36ed..98f5aeeb4c 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -171,8 +171,16 @@ class ArtMethod FINAL { return (GetAccessFlags() & kAccSynthetic) != 0; } + // Does this method live on a declaring class that is itself any proxy class? + // -- Returns true for both java.lang.reflect.Proxy and java.lang.LambdaProxy subclasses. bool IsProxyMethod() SHARED_REQUIRES(Locks::mutator_lock_); + // Does this method live in a java.lang.reflect.Proxy subclass? + bool IsReflectProxyMethod() SHARED_REQUIRES(Locks::mutator_lock_); + + // Does this method live in a java.lang.LambdaProxy subclass? + bool IsLambdaProxyMethod() SHARED_REQUIRES(Locks::mutator_lock_); + bool IsPreverified() { return (GetAccessFlags() & kAccPreverified) != 0; } @@ -274,7 +282,15 @@ class ArtMethod FINAL { uint32_t name_and_signature_idx) SHARED_REQUIRES(Locks::mutator_lock_); - void Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result, const char* shorty) + // Invoke this method, passing all the virtual registers in args. + // -- args_size must be the size in bytes (not size in words)! + // -- shorty must be the method shorty (i.e. it includes the return type). + // The result is set when the method finishes execution successfully. + void Invoke(Thread* self, + uint32_t* args, + uint32_t args_size, // NOTE: size in bytes + /*out*/JValue* result, + const char* shorty) SHARED_REQUIRES(Locks::mutator_lock_); const void* GetEntryPointFromQuickCompiledCode() { @@ -428,6 +444,9 @@ class ArtMethod FINAL { mirror::DexCache* GetDexCache() SHARED_REQUIRES(Locks::mutator_lock_); + // Returns the current method ('this') if this is a regular, non-proxy method. + // Otherwise, when this class is a proxy (IsProxyMethod), look-up the original interface's + // method (that the proxy is "overriding") and return that. ALWAYS_INLINE ArtMethod* GetInterfaceMethodIfProxy(size_t pointer_size) SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/runtime/asm_support.h b/runtime/asm_support.h index b548dfb639..785a9be42d 100644 --- a/runtime/asm_support.h +++ b/runtime/asm_support.h @@ -19,9 +19,12 @@ #if defined(__cplusplus) #include "art_method.h" +#include "lambda/art_lambda_method.h" +#include "lambda/closure.h" #include "gc/allocator/rosalloc.h" #include "lock_word.h" #include "mirror/class.h" +#include "mirror/lambda_proxy.h" #include "mirror/string.h" #include "runtime.h" #include "thread.h" @@ -49,6 +52,8 @@ #define ADD_TEST_EQ(x, y) CHECK_EQ(x, y); #endif +namespace art { + static inline void CheckAsmSupportOffsetsAndSizes() { #else #define ADD_TEST_EQ(x, y) @@ -298,9 +303,80 @@ ADD_TEST_EQ(ROSALLOC_SLOT_NEXT_OFFSET, static_cast<int32_t>(art::gc::allocator::RosAlloc::RunSlotNextOffset())) // Assert this so that we can avoid zeroing the next field by installing the class pointer. ADD_TEST_EQ(ROSALLOC_SLOT_NEXT_OFFSET, MIRROR_OBJECT_CLASS_OFFSET) +// Working with raw lambdas (lambda::Closure) in raw memory: +// +// |---------------------| +// | ArtLambdaMethod* | <-- pointer to lambda art method, has the info like the size. +// |---------------------| <-- 'data offset' +// | [ Dynamic Size ] | <-- OPTIONAL: only if the ArtLambdaMethod::dynamic_size_ is true. +// |---------------------| +// | Captured Variables | +// | ... | +// |---------------------| <-- total length determined by "dynamic size" if it is present, +// otherwise by the ArtLambdaMethod::static_size_ + +// Offset from start of lambda::Closure to the ArtLambdaMethod*. +#define LAMBDA_CLOSURE_METHOD_OFFSET 0 +ADD_TEST_EQ(static_cast<size_t>(LAMBDA_CLOSURE_METHOD_OFFSET), + offsetof(art::lambda::ClosureStorage, lambda_info_)) +// Offset from the start of lambda::Closure to the data (captured vars or dynamic size). +#define LAMBDA_CLOSURE_DATA_OFFSET __SIZEOF_POINTER__ +ADD_TEST_EQ(static_cast<size_t>(LAMBDA_CLOSURE_DATA_OFFSET), + offsetof(art::lambda::ClosureStorage, captured_)) +// Offsets to captured variables intentionally omitted as it needs a runtime branch. + +// The size of a lambda closure after it's been compressed down for storage. +// -- Although a lambda closure is a virtual register pair (64-bit), we only need 32-bit +// to track the pointer when we are on 32-bit architectures. +// Both the compiler and the runtime therefore compress the closure down for 32-bit archs. +#define LAMBDA_CLOSURE_COMPRESSED_POINTER_SIZE __SIZEOF_POINTER__ +ADD_TEST_EQ(static_cast<size_t>(LAMBDA_CLOSURE_COMPRESSED_POINTER_SIZE), + sizeof(art::lambda::Closure*)) + +// Working with boxed innate lambdas (as a mirror::Object) in raw memory: +// --- Note that this layout only applies to lambdas originally made with create-lambda. +// --- Boxing a lambda created from a new-instance instruction is simply the original object. +// +// |---------------------| +// | object header | +// |---------------------| +// | lambda::Closure* | <-- long on 64-bit, int on 32-bit +// |---------------------| +#define MIRROR_OBJECT_BOXED_INNATE_LAMBDA_CLOSURE_POINTER_OFFSET (MIRROR_OBJECT_HEADER_SIZE) +ADD_TEST_EQ(static_cast<size_t>(MIRROR_OBJECT_BOXED_INNATE_LAMBDA_CLOSURE_POINTER_OFFSET), + art::mirror::LambdaProxy::GetInstanceFieldOffsetClosure().SizeValue()) + // Equivalent to (private) offsetof(art::mirror::LambdaProxy, closure_)) + +// Working with boxed innate lambdas (as a mirror::Object) in raw memory: +// --- Note that this layout only applies to lambdas originally made with create-lambda. +// --- Boxing a lambda created from a new-instance instruction is simply the original object. +// +// |---------------------| +// | object header | +// |---------------------| +// | lambda::Closure* | <-- long on 64-bit, int on 32-bit +// |---------------------| +#define ART_LAMBDA_METHOD_ART_METHOD_OFFSET (0) +ADD_TEST_EQ(static_cast<size_t>(ART_LAMBDA_METHOD_ART_METHOD_OFFSET), + art::lambda::ArtLambdaMethod::GetArtMethodOffset()) + +#if defined(NDEBUG) +// Release should be faaast. So just jump directly to the lambda method. +#define LAMBDA_PROXY_SETUP_FRAME 0 +#else +// Debug can be slower, and we want to get better stack traces. Set up a frame. +#define LAMBDA_PROXY_SETUP_FRAME 1 +#endif + +// For WIP implementation, lambda types are all "longs" +// which means on a 32-bit implementation we need to fill the argument with 32-bit 0s +// whenever we invoke a method with a lambda in it. +// TODO: remove all usages of this once we go to a proper \LambdaType; system. +#define LAMBDA_INVOKE_USES_LONG 1 #if defined(__cplusplus) } // End of CheckAsmSupportOffsets. +} // namespace art #endif #endif // ART_RUNTIME_ASM_SUPPORT_H_ diff --git a/runtime/base/allocator.h b/runtime/base/allocator.h index 969f5b953f..e2ade07555 100644 --- a/runtime/base/allocator.h +++ b/runtime/base/allocator.h @@ -53,6 +53,7 @@ enum AllocatorTag { kAllocatorTagClassTable, kAllocatorTagInternTable, kAllocatorTagLambdaBoxTable, + kAllocatorTagLambdaProxyClassBoxTable, kAllocatorTagMaps, kAllocatorTagLOS, kAllocatorTagSafeMap, diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc index 70bd398415..6ca56f53f6 100644 --- a/runtime/base/mutex.cc +++ b/runtime/base/mutex.cc @@ -65,6 +65,7 @@ Mutex* Locks::thread_suspend_count_lock_ = nullptr; Mutex* Locks::trace_lock_ = nullptr; Mutex* Locks::unexpected_signal_lock_ = nullptr; Mutex* Locks::lambda_table_lock_ = nullptr; +Mutex* Locks::lambda_class_table_lock_ = nullptr; Uninterruptible Roles::uninterruptible_; struct AllMutexData { @@ -954,6 +955,7 @@ void Locks::Init() { DCHECK(trace_lock_ != nullptr); DCHECK(unexpected_signal_lock_ != nullptr); DCHECK(lambda_table_lock_ != nullptr); + DCHECK(lambda_class_table_lock_ != nullptr); } else { // Create global locks in level order from highest lock level to lowest. LockLevel current_lock_level = kInstrumentEntrypointsLock; @@ -1072,6 +1074,10 @@ void Locks::Init() { DCHECK(lambda_table_lock_ == nullptr); lambda_table_lock_ = new Mutex("lambda table lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kLambdaClassTableLock); + DCHECK(lambda_class_table_lock_ == nullptr); + lambda_class_table_lock_ = new Mutex("lambda class table lock", current_lock_level); + UPDATE_CURRENT_LOCK_LEVEL(kAbortLock); DCHECK(abort_lock_ == nullptr); abort_lock_ = new Mutex("abort lock", current_lock_level, true); diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h index d4c9057ab3..e2d7062f83 100644 --- a/runtime/base/mutex.h +++ b/runtime/base/mutex.h @@ -60,6 +60,7 @@ enum LockLevel { kUnexpectedSignalLock, kThreadSuspendCountLock, kAbortLock, + kLambdaClassTableLock, kLambdaTableLock, kJdwpSocketLock, kRegionSpaceRegionLock, @@ -692,6 +693,10 @@ class Locks { // Allow reader-writer mutual exclusion on the boxed table of lambda objects. // TODO: this should be a RW mutex lock, except that ConditionVariables don't work with it. static Mutex* lambda_table_lock_ ACQUIRED_AFTER(mutator_lock_); + + // Allow reader-writer mutual exclusion on the boxed table of lambda proxy classes. + // TODO: this should be a RW mutex lock, except that ConditionVariables don't work with it. + static Mutex* lambda_class_table_lock_ ACQUIRED_AFTER(lambda_table_lock_); }; class Roles { diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 2dd2a83888..8a0d8d4522 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -56,6 +56,7 @@ #include "interpreter/interpreter.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" +#include "lambda/box_class_table.h" #include "leb128.h" #include "linear_alloc.h" #include "mirror/class.h" @@ -64,6 +65,7 @@ #include "mirror/dex_cache-inl.h" #include "mirror/field.h" #include "mirror/iftable-inl.h" +#include "mirror/lambda_proxy.h" #include "mirror/method.h" #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" @@ -581,6 +583,9 @@ bool ClassLinker::InitWithoutImage(std::vector<std::unique_ptr<const DexFile>> b // Create java.lang.reflect.Proxy root. SetClassRoot(kJavaLangReflectProxy, FindSystemClass(self, "Ljava/lang/reflect/Proxy;")); + // Create java.lang.LambdaProxy root. + SetClassRoot(kJavaLangLambdaProxy, FindSystemClass(self, "Ljava/lang/LambdaProxy;")); + // Create java.lang.reflect.Field.class root. auto* class_root = FindSystemClass(self, "Ljava/lang/reflect/Field;"); CHECK(class_root != nullptr); @@ -1257,6 +1262,7 @@ void ClassLinker::DeleteClassLoader(Thread* self, const ClassLoaderData& data) { } delete data.allocator; delete data.class_table; + delete data.lambda_box_class_table; } mirror::PointerArray* ClassLinker::AllocPointerArray(Thread* self, size_t length) { @@ -1898,8 +1904,10 @@ const OatFile::OatMethod ClassLinker::FindOatMethodFor(ArtMethod* method, bool* // Special case to get oat code without overwriting a trampoline. const void* ClassLinker::GetQuickOatCodeFor(ArtMethod* method) { CHECK(method->IsInvokable()) << PrettyMethod(method); - if (method->IsProxyMethod()) { + if (method->IsReflectProxyMethod()) { return GetQuickProxyInvokeHandler(); + } else if (method->IsLambdaProxyMethod()) { + return GetQuickLambdaProxyInvokeHandler(); } bool found; OatFile::OatMethod oat_method = FindOatMethodFor(method, &found); @@ -3257,7 +3265,7 @@ mirror::Class* ClassLinker::CreateProxyClass(ScopedObjectAccessAlreadyRunnable& klass->SetName(soa.Decode<mirror::String*>(name)); klass->SetDexCache(GetClassRoot(kJavaLangReflectProxy)->GetDexCache()); mirror::Class::SetStatus(klass, mirror::Class::kStatusIdx, self); - std::string descriptor(GetDescriptorForProxy(klass.Get())); + std::string descriptor(GetDescriptorForAnyProxy(klass.Get())); const size_t hash = ComputeModifiedUtf8Hash(descriptor.c_str()); // Needs to be before we insert the class so that the allocator field is set. @@ -3377,23 +3385,228 @@ mirror::Class* ClassLinker::CreateProxyClass(ScopedObjectAccessAlreadyRunnable& decoded_name->ToModifiedUtf8().c_str())); CHECK_EQ(PrettyField(klass->GetStaticField(1)), throws_field_name); - CHECK_EQ(klass.Get()->GetInterfaces(), + CHECK_EQ(klass.Get()->GetInterfacesForAnyProxy(), + soa.Decode<mirror::ObjectArray<mirror::Class>*>(interfaces)); + CHECK_EQ(klass.Get()->GetThrowsForAnyProxy(), + soa.Decode<mirror::ObjectArray<mirror::ObjectArray<mirror::Class>>*>(throws)); + } + return klass.Get(); +} + +mirror::Class* ClassLinker::CreateLambdaProxyClass(ScopedObjectAccessAlreadyRunnable& soa, + jstring name, + jobjectArray interfaces, + jobject loader, + jobjectArray methods, + jobjectArray throws, + bool* already_exists) { + DCHECK(already_exists != nullptr); + *already_exists = false; + + Thread* self = soa.Self(); + StackHandleScope<10> hs(self); + + // Allocate a new java.lang.Class object for a mirror::Proxy. + MutableHandle<mirror::Class> klass = + hs.NewHandle(AllocClass(self, GetClassRoot(kJavaLangClass), sizeof(mirror::Class))); + if (klass.Get() == nullptr) { + CHECK(self->IsExceptionPending()); // OOME. + return nullptr; + } + DCHECK(klass->GetClass() != nullptr); + klass->SetObjectSize(sizeof(mirror::LambdaProxy)); + + // Set the class access flags incl. preverified, so we do not try to set the flag on the methods. + klass->SetAccessFlags(kAccClassIsLambdaProxy | kAccPublic | kAccFinal | kAccPreverified); + klass->SetClassLoader(soa.Decode<mirror::ClassLoader*>(loader)); + DCHECK_EQ(klass->GetPrimitiveType(), Primitive::kPrimNot); + klass->SetName(soa.Decode<mirror::String*>(name)); + klass->SetDexCache(GetClassRoot(kJavaLangLambdaProxy)->GetDexCache()); + // Set the status to be just before after loading it, but before anything is resolved. + mirror::Class::SetStatus(klass, mirror::Class::kStatusIdx, self); + // Convert "foo.bar.baz" string to "Lfoo/bar/baz;" + std::string type_descriptor(GetDescriptorForAnyProxy(klass.Get())); + + mirror::Class* existing; + { + const size_t hash = ComputeModifiedUtf8Hash(type_descriptor.c_str()); + + // Insert the class before loading the fields as the field roots + // (ArtField::declaring_class_) are only visited from the class + // table. There can't be any suspend points between inserting the + // class and setting the field arrays below. + existing = InsertClass(type_descriptor.c_str(), klass.Get(), hash); + } + if (UNLIKELY(existing != nullptr)) { + // We had already made the lambda proxy previously. Return it. + + *already_exists = true; + return existing; + // Let the GC clean up the class we had already allocated but isn't being used. + } + + // Needs to be after we insert the class so that the allocator field is set. + LinearAlloc* const allocator = GetOrCreateAllocatorForClassLoader(klass->GetClassLoader()); + + // Instance fields are inherited, but we add a couple of static fields... + LengthPrefixedArray<ArtField>* sfields = + AllocArtFieldArray(self, allocator, mirror::LambdaProxy::kStaticFieldCount); + klass->SetSFieldsPtr(sfields); + + // 1. Create a static field 'interfaces' that holds the _declared_ interfaces implemented by + // our proxy, so Class.getInterfaces doesn't return the flattened set. + // -- private static java.lang.Class[] interfaces; // list of declared interfaces + ArtField& interfaces_sfield = sfields->At(mirror::LambdaProxy::kStaticFieldIndexInterfaces); + interfaces_sfield.SetDexFieldIndex(mirror::LambdaProxy::kStaticFieldIndexInterfaces); + interfaces_sfield.SetDeclaringClass(klass.Get()); + interfaces_sfield.SetAccessFlags(kAccStatic | kAccPublic | kAccFinal); + + // 2. Create a static field 'throws' that holds the classes of exceptions thrown by our methods. + // This is returned by java.lang.reflect.Method#getExceptionTypes() + // --- private static java.lang.Class[][] throws; // maps vtable id to list of classes. + ArtField& throws_sfield = sfields->At(mirror::LambdaProxy::kStaticFieldIndexThrows); + throws_sfield.SetDexFieldIndex(mirror::LambdaProxy::kStaticFieldIndexThrows); + throws_sfield.SetDeclaringClass(klass.Get()); + throws_sfield.SetAccessFlags(kAccStatic | kAccPublic | kAccFinal); + + // Set up the Constructor method. + { + // Lambda proxies have 1 direct method, the constructor. + static constexpr size_t kNumDirectMethods = 1; + LengthPrefixedArray<ArtMethod>* directs = AllocArtMethodArray(self, + allocator, + kNumDirectMethods); + // Currently AllocArtMethodArray cannot return null, but the OOM logic is left there in case we + // want to throw OOM in the future. + if (UNLIKELY(directs == nullptr)) { + self->AssertPendingOOMException(); + return nullptr; + } + klass->SetDirectMethodsPtr(directs); + CreateLambdaProxyConstructor(klass, klass->GetDirectMethodUnchecked(0, image_pointer_size_)); + } + + // Create virtual method using specified prototypes. + auto h_methods = hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Method>*>(methods)); + DCHECK_EQ(h_methods->GetClass(), mirror::Method::ArrayClass()) + << PrettyClass(h_methods->GetClass()); + const size_t num_virtual_methods = h_methods->GetLength(); + auto* virtuals = AllocArtMethodArray(self, allocator, num_virtual_methods); + // Currently AllocArtMethodArray cannot return null, but the OOM logic is left there in case we + // want to throw OOM in the future. + if (UNLIKELY(virtuals == nullptr)) { + self->AssertPendingOOMException(); + return nullptr; + } + klass->SetVirtualMethodsPtr(virtuals); + size_t abstract_methods = 0; + for (size_t i = 0; i < num_virtual_methods; ++i) { + ArtMethod* virtual_method = klass->GetVirtualMethodUnchecked(i, image_pointer_size_); + ArtMethod* prototype = h_methods->Get(i)->GetArtMethod(); + if (UNLIKELY((prototype->GetAccessFlags() & kAccDefault) != 0)) { + UNIMPLEMENTED(FATAL) << "Lambda proxies don't support default methods yet"; + } + if (prototype->IsAbstract()) { + abstract_methods++; + } + VLOG(class_linker) << "Creating lambda proxy method for " << PrettyMethod(prototype); + + CreateLambdaProxyMethod(klass, prototype, virtual_method); + DCHECK(virtual_method->GetDeclaringClass() != nullptr); + DCHECK(prototype->GetDeclaringClass() != nullptr); + } + // Ignore any methods from Object and default methods, it doesn't matter. + // Sanity check that the prototype interface is indeed compatible with lambdas. + DCHECK_EQ(abstract_methods, 1u) + << "Interface must be a single-abstract-method type" << PrettyClass(klass.Get()); + + // The super class is java.lang.LambdaProxy + klass->SetSuperClass(GetClassRoot(kJavaLangLambdaProxy)); + // Now effectively in the loaded state. + mirror::Class::SetStatus(klass, mirror::Class::kStatusLoaded, self); + self->AssertNoPendingException(); + + MutableHandle<mirror::Class> new_class = hs.NewHandle<mirror::Class>(nullptr); + { + // Must hold lock on object when resolved. + ObjectLock<mirror::Class> resolution_lock(self, klass); + // Link the fields and virtual methods, creating vtable and iftables. + // The new class will replace the old one in the class table. + Handle<mirror::ObjectArray<mirror::Class>> h_interfaces( + hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Class>*>(interfaces))); + + { + DCHECK_EQ(1, h_interfaces->GetLength()) << "Lambda proxies must implement 1 interface only"; + mirror::Class* single_abstract_interface = h_interfaces->Get(0); + DCHECK(single_abstract_interface != nullptr); + + // Use the dex cache from the interface, which will enable most of the + // dex-using mechanisms on the class and its methods will work. + klass->SetDexCache(single_abstract_interface->GetDexCache()); + } + + if (!LinkClass(self, type_descriptor.c_str(), klass, h_interfaces, &new_class)) { + mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + return nullptr; + } + } + CHECK(klass->IsRetired()); + CHECK_NE(klass.Get(), new_class.Get()); + klass.Assign(new_class.Get()); + + CHECK_EQ(interfaces_sfield.GetDeclaringClass(), klass.Get()); + interfaces_sfield.SetObject<false>(klass.Get(), + soa.Decode<mirror::ObjectArray<mirror::Class>*>(interfaces)); + + CHECK_EQ(throws_sfield.GetDeclaringClass(), klass.Get()); + throws_sfield.SetObject<false>( + klass.Get(), soa.Decode<mirror::ObjectArray<mirror::ObjectArray<mirror::Class> >*>(throws)); + + { + // Lock on klass is released. Lock new class object. + ObjectLock<mirror::Class> initialization_lock(self, klass); + mirror::Class::SetStatus(klass, mirror::Class::kStatusInitialized, self); + } + + // Sanity checks + if (kIsDebugBuild) { + CHECK(klass->GetIFieldsPtr() == nullptr); + CheckLambdaProxyConstructor(klass->GetDirectMethod(0, image_pointer_size_)); + + for (size_t i = 0; i < num_virtual_methods; ++i) { + ArtMethod* virtual_method = klass->GetVirtualMethodUnchecked(i, image_pointer_size_); + ArtMethod* prototype = h_methods->Get(i++)->GetArtMethod(); + CheckLambdaProxyMethod(virtual_method, prototype); + } + + StackHandleScope<1> hs2(self); + Handle<mirror::String> decoded_name = hs2.NewHandle(soa.Decode<mirror::String*>(name)); + std::string interfaces_field_name(StringPrintf("java.lang.Class[] %s.interfaces", + decoded_name->ToModifiedUtf8().c_str())); + CHECK_EQ(PrettyField(klass->GetStaticField(0)), interfaces_field_name); + + std::string throws_field_name(StringPrintf("java.lang.Class[][] %s.throws", + decoded_name->ToModifiedUtf8().c_str())); + CHECK_EQ(PrettyField(klass->GetStaticField(1)), throws_field_name); + + CHECK_EQ(klass.Get()->GetInterfacesForAnyProxy(), soa.Decode<mirror::ObjectArray<mirror::Class>*>(interfaces)); - CHECK_EQ(klass.Get()->GetThrows(), + CHECK_EQ(klass.Get()->GetThrowsForAnyProxy(), soa.Decode<mirror::ObjectArray<mirror::ObjectArray<mirror::Class>>*>(throws)); } return klass.Get(); } -std::string ClassLinker::GetDescriptorForProxy(mirror::Class* proxy_class) { - DCHECK(proxy_class->IsProxyClass()); +std::string ClassLinker::GetDescriptorForAnyProxy(mirror::Class* proxy_class) { + DCHECK(proxy_class != nullptr); + DCHECK(proxy_class->IsAnyProxyClass()); mirror::String* name = proxy_class->GetName(); DCHECK(name != nullptr); return DotToDescriptor(name->ToModifiedUtf8().c_str()); } ArtMethod* ClassLinker::FindMethodForProxy(mirror::Class* proxy_class, ArtMethod* proxy_method) { - DCHECK(proxy_class->IsProxyClass()); + DCHECK(proxy_class->IsAnyProxyClass()); DCHECK(proxy_method->IsProxyMethod()); { Thread* const self = Thread::Current(); @@ -3421,7 +3634,7 @@ ArtMethod* ClassLinker::FindMethodForProxy(mirror::Class* proxy_class, ArtMethod void ClassLinker::CreateProxyConstructor(Handle<mirror::Class> klass, ArtMethod* out) { // Create constructor for Proxy that must initialize the method. - CHECK_EQ(GetClassRoot(kJavaLangReflectProxy)->NumDirectMethods(), 16u); + CHECK_EQ(GetClassRoot(kJavaLangReflectProxy)->NumDirectMethods(), 18u); ArtMethod* proxy_constructor = GetClassRoot(kJavaLangReflectProxy)->GetDirectMethodUnchecked( 2, image_pointer_size_); // Ensure constructor is in dex cache so that we can use the dex cache to look up the overridden @@ -3437,6 +3650,38 @@ void ClassLinker::CreateProxyConstructor(Handle<mirror::Class> klass, ArtMethod* out->SetDeclaringClass(klass.Get()); } +void ClassLinker::CreateLambdaProxyConstructor(Handle<mirror::Class> klass, + /*out*/ArtMethod* method_constructor) { + DCHECK(klass.Get() != nullptr); + DCHECK(method_constructor != nullptr); + + // Create constructor for Proxy that must initialize the method. + // Lambda proxy superclass only has 1 direct method, the constructor (<init>()V) + CHECK_EQ(GetClassRoot(kJavaLangLambdaProxy)->NumDirectMethods(), + mirror::LambdaProxy::kDirectMethodCount); + // Get the constructor method. + ArtMethod* proxy_constructor = GetClassRoot(kJavaLangLambdaProxy)->GetDirectMethodUnchecked( + mirror::LambdaProxy::kDirectMethodIndexConstructor, + image_pointer_size_); + + // Verify constructor method is indeed a constructor. + CHECK(proxy_constructor != nullptr); + + // Ensure constructor is in dex cache so that we can use the dex cache to look up the overridden + // constructor method. + GetClassRoot(kJavaLangLambdaProxy)->GetDexCache()->SetResolvedMethod( + proxy_constructor->GetDexMethodIndex(), + proxy_constructor, + image_pointer_size_); + + // Clone the existing constructor of LambdaProxy + // (our constructor would just invoke it so steal its code_ too). + method_constructor->CopyFrom(proxy_constructor, image_pointer_size_); + // Make this constructor public and fix the class to be our LambdaProxy version + method_constructor->SetAccessFlags((method_constructor->GetAccessFlags() & ~kAccProtected) | kAccPublic); + method_constructor->SetDeclaringClass(klass.Get()); +} + void ClassLinker::CheckProxyConstructor(ArtMethod* constructor) const { CHECK(constructor->IsConstructor()); auto* np = constructor->GetInterfaceMethodIfProxy(image_pointer_size_); @@ -3445,6 +3690,14 @@ void ClassLinker::CheckProxyConstructor(ArtMethod* constructor) const { DCHECK(constructor->IsPublic()); } +void ClassLinker::CheckLambdaProxyConstructor(ArtMethod* constructor) const { + CHECK(constructor->IsConstructor()); + auto* np = constructor->GetInterfaceMethodIfProxy(image_pointer_size_); + CHECK_STREQ(np->GetName(), "<init>"); + CHECK_STREQ(np->GetSignature().ToString().c_str(), "()V"); + DCHECK(constructor->IsPublic()); +} + void ClassLinker::CreateProxyMethod(Handle<mirror::Class> klass, ArtMethod* prototype, ArtMethod* out) { // Ensure prototype is in dex cache so that we can use the dex cache to look up the overridden @@ -3456,6 +3709,7 @@ void ClassLinker::CreateProxyMethod(Handle<mirror::Class> klass, ArtMethod* prot dex_cache->SetResolvedMethod( prototype->GetDexMethodIndex(), prototype, image_pointer_size_); } + // We steal everything from the prototype (such as DexCache, invoke stub, etc.) then specialize // as necessary DCHECK(out != nullptr); @@ -3471,6 +3725,42 @@ void ClassLinker::CreateProxyMethod(Handle<mirror::Class> klass, ArtMethod* prot out->SetEntryPointFromQuickCompiledCode(GetQuickProxyInvokeHandler()); } +void ClassLinker::CreateLambdaProxyMethod(Handle<mirror::Class> klass, + ArtMethod* prototype, + ArtMethod* out) { + DCHECK(prototype != nullptr); + DCHECK(out != nullptr); + + // DO NOT go through the proxy invoke handler for the default methods. They have no idea + // how to handle the raw closure, so they must get the regular object when invoked. + CHECK_EQ(prototype->GetAccessFlags() & kAccDefault, 0u) << "Default methods must not be proxied"; + + // Ensure prototype is in dex cache so that we can use the dex cache to look up the overridden + // prototype method + auto* dex_cache = prototype->GetDeclaringClass()->GetDexCache(); + // Avoid dirtying the dex cache unless we need to. + if (dex_cache->GetResolvedMethod(prototype->GetDexMethodIndex(), image_pointer_size_) != + prototype) { + dex_cache->SetResolvedMethod( + prototype->GetDexMethodIndex(), prototype, image_pointer_size_); + } + // We steal everything from the prototype (such as DexCache, invoke stub, etc.) then specialize + // as necessary + out->CopyFrom(prototype, image_pointer_size_); + + // Set class to be the concrete proxy class and clear the abstract flag, modify exceptions to + // the intersection of throw exceptions as defined in Proxy + out->SetDeclaringClass(klass.Get()); + out->SetAccessFlags((out->GetAccessFlags() & ~kAccAbstract) | kAccFinal); + + // Setting the entry point isn't safe for AOT since ASLR loads it anywhere at runtime. + CHECK(!Runtime::Current()->IsAotCompiler()); + + // At runtime the method looks like a reference and argument saving method, clone the code + // related parameters from this method. + out->SetEntryPointFromQuickCompiledCode(GetQuickLambdaProxyInvokeHandler()); +} + void ClassLinker::CheckProxyMethod(ArtMethod* method, ArtMethod* prototype) const { // Basic sanity CHECK(!prototype->IsFinal()); @@ -3492,6 +3782,11 @@ void ClassLinker::CheckProxyMethod(ArtMethod* method, ArtMethod* prototype) cons prototype->GetReturnType(true /* resolve */, image_pointer_size_)); } +void ClassLinker::CheckLambdaProxyMethod(ArtMethod* method, ArtMethod* prototype) const { + // same as above. + return CheckProxyMethod(method, prototype); +} + bool ClassLinker::CanWeInitializeClass(mirror::Class* klass, bool can_init_statics, bool can_init_parents) { if (can_init_statics && can_init_parents) { @@ -4123,7 +4418,9 @@ ClassTable* ClassLinker::InsertClassTableForClassLoader(mirror::ClassLoader* cla class_loader->SetClassTable(data.class_table); // Should have been set when we registered the dex file. data.allocator = class_loader->GetAllocator(); - CHECK(data.allocator != nullptr); + CHECK(class_loader->GetLambdaProxyCache() == nullptr); + data.lambda_box_class_table = new lambda::BoxClassTable(); + class_loader->SetLambdaProxyCache(data.lambda_box_class_table); class_loaders_.push_back(data); } return class_table; @@ -6566,6 +6863,7 @@ const char* ClassLinker::GetClassRootDescriptor(ClassRoot class_root) { "Ljava/lang/reflect/Field;", "Ljava/lang/reflect/Method;", "Ljava/lang/reflect/Proxy;", + "Ljava/lang/LambdaProxy;", "[Ljava/lang/String;", "[Ljava/lang/reflect/Constructor;", "[Ljava/lang/reflect/Field;", diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 29aac312c1..f073cd8170 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -40,6 +40,11 @@ namespace space { class ImageSpace; } // namespace space } // namespace gc + +namespace lambda { + class BoxClassTable; +} // namespace lambda + namespace mirror { class ClassLoader; class DexCache; @@ -82,6 +87,7 @@ class ClassLinker { kJavaLangReflectField, kJavaLangReflectMethod, kJavaLangReflectProxy, + kJavaLangLambdaProxy, kJavaLangStringArrayClass, kJavaLangReflectConstructorArrayClass, kJavaLangReflectFieldArrayClass, @@ -424,12 +430,46 @@ class ClassLinker { jobjectArray methods, jobjectArray throws) SHARED_REQUIRES(Locks::mutator_lock_); - std::string GetDescriptorForProxy(mirror::Class* proxy_class) + + // Get the long type descriptor, e.g. "LProxyName$1234;" for the requested proxy class. + static std::string GetDescriptorForAnyProxy(mirror::Class* proxy_class) SHARED_REQUIRES(Locks::mutator_lock_); ArtMethod* FindMethodForProxy(mirror::Class* proxy_class, ArtMethod* proxy_method) REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_); + // Create a lambda proxy class. + // -- Nominally used when boxing an innate lambda, since that has no corresponding class. + // + // * name must be a fully-qualified class name (and dotted), e.g. "java.lang.Runnable" + // * interfaces is an array of java.lang.Class for interfaces that will be the supertype + // (note that there must be exactly 1 element here for a lambda interface since lambda + // types can only target 1 interface). + // * loader must be a java.lang.ClassLoader where the proxy class will be created + // * methods must be an array of java.lang.reflect.Method that consists of the + // deduplicated methods from all of the interfaces specified. + // * throws must be an array of java.lang.Class[] where each index corresponds to that of + // methods, and it signifies the "throws" keyword of each method + // (this is not directly used by the runtime itself, but it is available via reflection). + // + // Returns a non-null pointer to a class upon success, otherwise null and throws an exception. + // + // If the class was already created previously (with the same name but potentially different + // parameters), already_exists is set to true; otherwise already_exists is set to false. + // The already_exists value is undefined when an exception was thrown. + // + // Sidenote: interfaces is an array to simplify the libcore code which creates a Java + // array in an attempt to reduce code duplication. + // TODO: this should probably also take the target single-abstract-method as well. + mirror::Class* CreateLambdaProxyClass(ScopedObjectAccessAlreadyRunnable& soa, + jstring name, + jobjectArray interfaces, + jobject loader, + jobjectArray methods, + jobjectArray throws, + /*out*/bool* already_exists) + SHARED_REQUIRES(Locks::mutator_lock_); + // Get the oat code for a method when its class isn't yet initialized const void* GetQuickOatCodeFor(ArtMethod* method) SHARED_REQUIRES(Locks::mutator_lock_); @@ -573,6 +613,7 @@ class ClassLinker { jweak weak_root; // Weak root to enable class unloading. ClassTable* class_table; LinearAlloc* allocator; + lambda::BoxClassTable* lambda_box_class_table; }; // Ensures that the supertype of 'klass' ('supertype') is verified. Returns false and throws @@ -907,8 +948,12 @@ class ClassLinker { void CheckProxyConstructor(ArtMethod* constructor) const SHARED_REQUIRES(Locks::mutator_lock_); + void CheckLambdaProxyConstructor(ArtMethod* constructor) const + SHARED_REQUIRES(Locks::mutator_lock_); void CheckProxyMethod(ArtMethod* method, ArtMethod* prototype) const SHARED_REQUIRES(Locks::mutator_lock_); + void CheckLambdaProxyMethod(ArtMethod* method, ArtMethod* prototype) const + SHARED_REQUIRES(Locks::mutator_lock_); // For use by ImageWriter to find DexCaches for its roots ReaderWriterMutex* DexLock() @@ -926,9 +971,19 @@ class ClassLinker { void CreateProxyConstructor(Handle<mirror::Class> klass, ArtMethod* out) SHARED_REQUIRES(Locks::mutator_lock_); + + // Copy the constructor from java.lang.LambdaProxy into the 'klass'. + // The copy is written into 'method_constructor'. + void CreateLambdaProxyConstructor(Handle<mirror::Class> klass, + /*out*/ArtMethod* method_constructor) + SHARED_REQUIRES(Locks::mutator_lock_); + void CreateProxyMethod(Handle<mirror::Class> klass, ArtMethod* prototype, ArtMethod* out) SHARED_REQUIRES(Locks::mutator_lock_); + void CreateLambdaProxyMethod(Handle<mirror::Class> klass, ArtMethod* prototype, ArtMethod* out) + SHARED_REQUIRES(Locks::mutator_lock_); + // Ensures that methods have the kAccPreverified bit set. We use the kAccPreverfied bit on the // class access flags to determine whether this has been done before. void EnsurePreverifiedMethods(Handle<mirror::Class> c) @@ -940,7 +995,10 @@ class ClassLinker { // Returns null if not found. ClassTable* ClassTableForClassLoader(mirror::ClassLoader* class_loader) SHARED_REQUIRES(Locks::mutator_lock_, Locks::classlinker_classes_lock_); - // Insert a new class table if not found. + + // Insert a new class table if not found. Uses bootclasspath if class_loader is null. + // Returns either the existing table, or the new one if there wasn't one previously + // (the return value is always non-null). ClassTable* InsertClassTableForClassLoader(mirror::ClassLoader* class_loader) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(Locks::classlinker_classes_lock_); diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index 2c086c59f0..4a9db1d50d 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -31,6 +31,7 @@ #include "mirror/class-inl.h" #include "mirror/dex_cache.h" #include "mirror/field.h" +#include "mirror/lambda_proxy.h" #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" #include "mirror/proxy.h" @@ -552,6 +553,7 @@ struct ClassLoaderOffsets : public CheckOffsets<mirror::ClassLoader> { ClassLoaderOffsets() : CheckOffsets<mirror::ClassLoader>(false, "Ljava/lang/ClassLoader;") { addOffset(OFFSETOF_MEMBER(mirror::ClassLoader, allocator_), "allocator"); addOffset(OFFSETOF_MEMBER(mirror::ClassLoader, class_table_), "classTable"); + addOffset(OFFSETOF_MEMBER(mirror::ClassLoader, lambda_proxy_cache_), "lambdaProxyCache"); addOffset(OFFSETOF_MEMBER(mirror::ClassLoader, packages_), "packages"); addOffset(OFFSETOF_MEMBER(mirror::ClassLoader, parent_), "parent"); addOffset(OFFSETOF_MEMBER(mirror::ClassLoader, proxyCache_), "proxyCache"); @@ -564,6 +566,13 @@ struct ProxyOffsets : public CheckOffsets<mirror::Proxy> { }; }; +struct LambdaProxyOffsets : public CheckOffsets<mirror::LambdaProxy> { + LambdaProxyOffsets() : CheckOffsets<mirror::LambdaProxy>(false, "Ljava/lang/LambdaProxy;") { + addOffset(OFFSETOF_MEMBER(mirror::LambdaProxy, closure_), "closure"); + }; +}; + + struct DexCacheOffsets : public CheckOffsets<mirror::DexCache> { DexCacheOffsets() : CheckOffsets<mirror::DexCache>(false, "Ljava/lang/DexCache;") { addOffset(OFFSETOF_MEMBER(mirror::DexCache, dex_), "dex"); @@ -639,6 +648,7 @@ TEST_F(ClassLinkerTest, ValidateFieldOrderOfJavaCppUnionClasses) { EXPECT_TRUE(StackTraceElementOffsets().Check()); EXPECT_TRUE(ClassLoaderOffsets().Check()); EXPECT_TRUE(ProxyOffsets().Check()); + EXPECT_TRUE(LambdaProxyOffsets().Check()); EXPECT_TRUE(DexCacheOffsets().Check()); EXPECT_TRUE(ReferenceOffsets().Check()); EXPECT_TRUE(FinalizerReferenceOffsets().Check()); diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index f705a50d55..e84313cb9a 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -55,7 +55,6 @@ int main(int argc, char **argv) { // Gtests can be very noisy. For example, an executable with multiple tests will trigger native // bridge warnings. The following line reduces the minimum log severity to ERROR and suppresses // everything else. In case you want to see all messages, comment out the line. - setenv("ANDROID_LOG_TAGS", "*:e", 1); art::InitLogging(argv); LOG(::art::INFO) << "Running main() from common_runtime_test.cc..."; diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc index 87e29ae3c3..2a92226f4a 100644 --- a/runtime/entrypoints/entrypoint_utils.cc +++ b/runtime/entrypoints/entrypoint_utils.cc @@ -313,7 +313,7 @@ JValue InvokeProxyInvocationHandler(ScopedObjectAccessAlreadyRunnable& soa, cons reinterpret_cast<uintptr_t>(virtual_methods)) / method_size; CHECK_LT(throws_index, static_cast<int>(num_virtuals)); mirror::ObjectArray<mirror::Class>* declared_exceptions = - proxy_class->GetThrows()->Get(throws_index); + proxy_class->GetThrowsForAnyProxy()->Get(throws_index); mirror::Class* exception_class = exception->GetClass(); bool declares_exception = false; for (int32_t i = 0; i < declared_exceptions->GetLength() && !declares_exception; i++) { diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index abf9ac49e6..8c2dc3e42a 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -23,9 +23,12 @@ #include "entrypoints/runtime_asm_entrypoints.h" #include "gc/accounting/card_table-inl.h" #include "interpreter/interpreter.h" +#include "lambda/closure.h" +#include "lambda/art_lambda_method.h" #include "method_reference.h" #include "mirror/class-inl.h" #include "mirror/dex_cache-inl.h" +#include "mirror/lambda_proxy.h" #include "mirror/method.h" #include "mirror/object-inl.h" #include "mirror/object_array-inl.h" @@ -294,7 +297,8 @@ class QuickArgumentVisitor { // 1st GPR. static mirror::Object* GetProxyThisObject(ArtMethod** sp) SHARED_REQUIRES(Locks::mutator_lock_) { - CHECK((*sp)->IsProxyMethod()); + // TODO: Lambda proxies only set up a frame when debugging + CHECK((*sp)->IsReflectProxyMethod() || ((*sp)->IsLambdaProxyMethod() /*&& kIsDebugBuild*/)); CHECK_GT(kNumQuickGprArgs, 0u); constexpr uint32_t kThisGprIndex = 0u; // 'this' is in the 1st GPR. size_t this_arg_offset = kQuickCalleeSaveFrame_RefAndArgs_Gpr1Offset + @@ -834,8 +838,9 @@ void BuildQuickArgumentVisitor::FixupReferences() { extern "C" uint64_t artQuickProxyInvokeHandler( ArtMethod* proxy_method, mirror::Object* receiver, Thread* self, ArtMethod** sp) SHARED_REQUIRES(Locks::mutator_lock_) { - DCHECK(proxy_method->IsProxyMethod()) << PrettyMethod(proxy_method); - DCHECK(receiver->GetClass()->IsProxyClass()) << PrettyMethod(proxy_method); + DCHECK(proxy_method->GetDeclaringClass()->IsReflectProxyClass()) << PrettyMethod(proxy_method); + DCHECK(proxy_method->IsReflectProxyMethod()) << PrettyMethod(proxy_method); + DCHECK(receiver->GetClass()->IsReflectProxyClass()) << PrettyMethod(proxy_method); // Ensure we don't get thread suspension until the object arguments are safely in jobjects. const char* old_cause = self->StartAssertNoThreadSuspension("Adding to IRT proxy object arguments"); @@ -878,6 +883,175 @@ extern "C" uint64_t artQuickProxyInvokeHandler( return result.GetJ(); } +extern "C" uint64_t artQuickLambdaProxyInvokeHandler( + ArtMethod* proxy_method, mirror::LambdaProxy* receiver, Thread* self, ArtMethod** sp) + SHARED_REQUIRES(Locks::mutator_lock_) { + using lambda::ShortyFieldType; + + DCHECK(proxy_method->GetDeclaringClass()->IsLambdaProxyClass()) << PrettyMethod(proxy_method); + DCHECK(proxy_method->IsLambdaProxyMethod()) << PrettyMethod(proxy_method); + DCHECK(receiver->GetClass()->IsLambdaProxyClass()) << PrettyMethod(proxy_method); + + lambda::Closure* lambda_closure = receiver->GetClosure(); + DCHECK(lambda_closure != nullptr); // Should've NPEd during the invoke-interface. + // Learned lambdas have their own implementation of the SAM, they must not go through here. + DCHECK(lambda_closure->GetLambdaInfo()->IsInnateLambda()); + ArtMethod* target_method = lambda_closure->GetTargetMethod(); + + // Lambda targets are always static. + // TODO: This should really be a target_method->IsLambda(), once we add the access flag. + CHECK(target_method->IsStatic()) << PrettyMethod(proxy_method) << " " + << PrettyMethod(target_method); + + // Ensure we don't get thread suspension until the object arguments are safely in jobjects. + const char* old_cause = + self->StartAssertNoThreadSuspension("Adding to IRT/SF lambda proxy object arguments"); + // Register the top of the managed stack, making stack crawlable. + DCHECK_EQ((*sp), proxy_method) << PrettyMethod(proxy_method); + self->VerifyStack(); + // Start new JNI local reference state. + JNIEnvExt* env = self->GetJniEnv(); + ScopedObjectAccessUnchecked soa(env); + + // Placing arguments into args vector and remove the receiver. + ArtMethod* non_proxy_method = proxy_method->GetInterfaceMethodIfProxy(sizeof(void*)); + CHECK(!non_proxy_method->IsStatic()) << PrettyMethod(proxy_method) << " " + << PrettyMethod(non_proxy_method); + uint32_t shorty_len = 0; + const char* shorty = non_proxy_method->GetShorty(/*out*/&shorty_len); + + std::vector<jvalue> args; + // Make a quick visitor so we can restore the refs incase they move after a GC. + BuildQuickArgumentVisitor local_ref_visitor(sp, + false /*is_static*/, + shorty, + shorty_len, + &soa, + /*out*/&args); + local_ref_visitor.VisitArguments(); + + static_assert(lambda::kClosureIsStoredAsLong, + "Need to update this code once closures are no " + "longer treated as a 'long' in quick abi"); + + // Allocate one vreg more than usual because we need to convert our + // receiver Object (1 vreg) into a long (2 vregs). + // TODO: Ugly... move to traits instead? + const uint32_t first_arg_reg = ShortyFieldType(ShortyFieldType::kLambda).GetVirtualRegisterCount() + - ShortyFieldType(ShortyFieldType::kObject).GetVirtualRegisterCount(); + const uint32_t num_vregs = lambda_closure->GetLambdaInfo()->GetArgumentVRegCount(); + DCHECK_GE(num_vregs, first_arg_reg); + if (kIsDebugBuild) { + const char* method_shorty = non_proxy_method->GetShorty(); + DCHECK_NE(*method_shorty, '\0') << method_shorty; + const char* arg_shorty = method_shorty + 1; // Skip return type. + + // Proxy method should have an object (1 vreg) receiver, + // Lambda method should have a lambda (2 vregs) receiver. + // -- All other args are the same as before. + // -- Make sure vreg count is what we thought it was. + uint32_t non_proxy_num_vregs = + ShortyFieldType::CountVirtualRegistersRequired(arg_shorty) // doesn't count receiver + + ShortyFieldType(ShortyFieldType::kObject).GetVirtualRegisterCount(); // implicit receiver + + CHECK_EQ(non_proxy_num_vregs + first_arg_reg, num_vregs) + << PrettyMethod(non_proxy_method) << " " << PrettyMethod(lambda_closure->GetTargetMethod()); + } + + ShadowFrameAllocaUniquePtr shadow_frame = CREATE_SHADOW_FRAME(num_vregs, + /*link*/nullptr, + target_method, + /*dex_pc*/0); + + // Copy our proxy method caller's arguments into this ShadowFrame. + BuildQuickShadowFrameVisitor local_sf_visitor(sp, + /*is_static*/false, + shorty, + shorty_len, + shadow_frame.get(), + first_arg_reg); + + local_sf_visitor.VisitArguments(); + // Now fix up the arguments, with each ArgK being a vreg: + + // (Before): + // Arg0 = proxy receiver (LambdaProxy) + // Arg1 = first-user defined argument + // Arg2 = second user-defined argument + // .... + // ArgN = ... + + // (After) + // Arg0 = closure (hi) + // Arg1 = closure (lo) = 0x00 on 32-bit + // Arg2 = <?> (first user-defined argument) + // Arg3 = <?> (first user-defined argument) + // ... + // argN+1 = ... + + // Transformation diagram: + /* + Arg0 Arg2 Arg3 ... ArgN + | \ \ \ + | \ \ \ + ClHi ClLo Arg2 Arg3 ... ArgN: + */ + + // 1) memmove vregs 1-N into 2-N+1 + uint32_t* shadow_frame_vregs = shadow_frame->GetVRegArgs(/*i*/0); + if (lambda::kClosureIsStoredAsLong || + sizeof(void*) != sizeof(mirror::CompressedReference<mirror::LambdaProxy>)) { + // Suspending here would be very bad since we are doing a raw memmove + + // Move the primitive vregs over. + { + size_t shadow_frame_vregs_size = num_vregs; + memmove(shadow_frame_vregs + first_arg_reg, + shadow_frame_vregs, + shadow_frame_vregs_size - first_arg_reg); + } + + // Move the reference vregs over. + if (LIKELY(shadow_frame->HasReferenceArray())) { + uint32_t* shadow_frame_references = shadow_frame_vregs + num_vregs; + size_t shadow_frame_references_size = num_vregs; + memmove(shadow_frame_references + first_arg_reg, + shadow_frame_references, + shadow_frame_references_size - first_arg_reg); + } + + static_assert(lambda::kClosureSupportsReadBarrier == false, + "Using this memmove code with a read barrier GC seems like it could be unsafe."); + + static_assert(sizeof(mirror::CompressedReference<mirror::LambdaProxy>) == sizeof(uint32_t), + "This block of code assumes a compressed reference fits into exactly 1 vreg"); + } + // 2) replace proxy receiver with lambda + shadow_frame->SetVRegLong(0, static_cast<int64_t>(reinterpret_cast<uintptr_t>(lambda_closure))); + + // OK: After we do the invoke, the target method takes over managing the arguments + // and we won't ever access the shadow frame again (if any references moved). + self->EndAssertNoThreadSuspension(old_cause); + + // The shadow frame vreg contents are now 'owned' by the Invoke method, and + // will be managed by it during a GC despite being a raw uint32_t array. + // We however have no guarantee that it is updated on the way out, so do not read out of the + // shadow frame after this call. + JValue result; + target_method->Invoke(self, + shadow_frame_vregs, + num_vregs * sizeof(uint32_t), + /*out*/&result, + target_method->GetShorty()); + + // Restore references on the proxy caller stack frame which might have moved. + // -- This is necessary because the QuickFrameInfo is just the generic runtime "RefsAndArgs" + // which means that the regular stack visitor wouldn't know how to GC-move any references + // that we spilled ourselves in the proxy stub. + local_ref_visitor.FixupReferences(); + return result.GetJ(); +} + // Read object references held in arguments from quick frames and place in a JNI local references, // so they don't get garbage collected. class RememberForGcArgumentVisitor FINAL : public QuickArgumentVisitor { diff --git a/runtime/entrypoints/runtime_asm_entrypoints.h b/runtime/entrypoints/runtime_asm_entrypoints.h index 2842c5a5a6..1ef75855ab 100644 --- a/runtime/entrypoints/runtime_asm_entrypoints.h +++ b/runtime/entrypoints/runtime_asm_entrypoints.h @@ -17,6 +17,10 @@ #ifndef ART_RUNTIME_ENTRYPOINTS_RUNTIME_ASM_ENTRYPOINTS_H_ #define ART_RUNTIME_ENTRYPOINTS_RUNTIME_ASM_ENTRYPOINTS_H_ +// Define entry points to assembly routines. +// All extern "C" functions here are defined in a corresponding assembly-only file. +// The exact file paths are runtime/arch/$ISA/quick_entrypoints_$ISA.s + namespace art { #ifndef BUILDING_LIBART @@ -52,6 +56,13 @@ static inline const void* GetQuickProxyInvokeHandler() { return reinterpret_cast<const void*>(art_quick_proxy_invoke_handler); } +// Return the address of quick stub code for handling transitions into the lambda proxy +// invoke handler. +extern "C" void art_quick_lambda_proxy_invoke_handler(); +static inline const void* GetQuickLambdaProxyInvokeHandler() { + return reinterpret_cast<const void*>(art_quick_lambda_proxy_invoke_handler); +} + // Return the address of quick stub code for resolving a method at first call. extern "C" void art_quick_resolution_trampoline(ArtMethod*); static inline const void* GetQuickResolutionStub() { diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index da9a79e1a2..07f0628ee2 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -672,8 +672,8 @@ std::string Heap::SafeGetClassDescriptor(mirror::Class* klass) { return result; } else if (UNLIKELY(klass->IsPrimitive<kVerifyNone>())) { return Primitive::Descriptor(klass->GetPrimitiveType<kVerifyNone>()); - } else if (UNLIKELY(klass->IsProxyClass<kVerifyNone>())) { - return Runtime::Current()->GetClassLinker()->GetDescriptorForProxy(klass); + } else if (UNLIKELY(klass->IsAnyProxyClass<kVerifyNone>())) { + return Runtime::Current()->GetClassLinker()->GetDescriptorForAnyProxy(klass); } else { mirror::DexCache* dex_cache = klass->GetDexCache<kVerifyNone>(); if (!IsValidContinuousSpaceObjectAddress(dex_cache)) { diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h index 9f6699f730..2de8e7e6be 100644 --- a/runtime/interpreter/interpreter_common.h +++ b/runtime/interpreter/interpreter_common.h @@ -888,12 +888,56 @@ static inline bool DoBoxLambda(Thread* self, ShadowFrame& shadow_frame, const In return false; } + StackHandleScope<1> hs{self}; // NOLINT: [readability/braces] [4]; + + // Use the lambda method's class loader since it's close enough. + // TODO: create-lambda should capture the current method's class loader and use that instead. + // TODO: Do we want create-lambda to work for static methods outside of the declaring class? + // --> then we need to store a classloader in the lambda method. otherwise we don't + // because it would always use the declaring class's class loader. + // TODO: add a GetClassLoader to the lambda closure which knows how to do this, + // don't hardcode this here. + Handle<ClassLoader> current_class_loader = hs.NewHandle( + lambda_closure->GetTargetMethod()->GetDeclaringClass()->GetClassLoader()); + + // TODO: get the type ID from the instruction + std::string class_name; + { + // Temporary hack to read the interface corresponding to a box-lambda. + // TODO: The box-lambda should encode the type ID instead, so we don't need to do this. + { + // Do a hack where we read from const-string the interface name + mirror::Object* string_reference = shadow_frame.GetVRegReference(vreg_target_object); + + CHECK(string_reference != nullptr) + << "box-lambda needs the type name stored in string vA (target), but it was null"; + + CHECK(string_reference->IsString()) + << "box-lambda needs the type name stored in string vA (target)"; + + mirror::String* as_string = string_reference->AsString(); + class_name = as_string->ToModifiedUtf8(); + } + + // Trigger class loading of the functional interface. + // TODO: This should actually be done by the create-lambda... + if (Runtime::Current()->GetClassLinker() + ->FindClass(self, class_name.c_str(), current_class_loader) == nullptr) { + CHECK(self->IsExceptionPending()); + self->AssertPendingException(); + return false; + } + } + mirror::Object* closure_as_object = - Runtime::Current()->GetLambdaBoxTable()->BoxLambda(lambda_closure); + Runtime::Current()->GetLambdaBoxTable()->BoxLambda(lambda_closure, + class_name.c_str(), + current_class_loader.Get()); // Failed to box the lambda, an exception was raised. if (UNLIKELY(closure_as_object == nullptr)) { CHECK(self->IsExceptionPending()); + shadow_frame.SetVRegReference(vreg_target_object, nullptr); return false; } diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc index bf95a0e46f..11a8c2e636 100644 --- a/runtime/interpreter/interpreter_switch_impl.cc +++ b/runtime/interpreter/interpreter_switch_impl.cc @@ -102,6 +102,8 @@ JValue ExecuteSwitchImpl(Thread* self, const DexFile::CodeItem* code_item, size_t lambda_captured_variable_index = 0; while (true) { dex_pc = inst->GetDexPc(insns); + DCHECK_LE(dex_pc, code_item->insns_size_in_code_units_) + << "Dex PC overflowed code item size; missing return instruction?"; shadow_frame.SetDexPC(dex_pc); TraceExecution(shadow_frame, inst, dex_pc); inst_data = inst->Fetch16(0); diff --git a/runtime/lambda/art_lambda_method.cc b/runtime/lambda/art_lambda_method.cc index 6f9f8bbb59..0690cd1a31 100644 --- a/runtime/lambda/art_lambda_method.cc +++ b/runtime/lambda/art_lambda_method.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "art_method-inl.h" #include "lambda/art_lambda_method.h" #include "base/logging.h" @@ -73,5 +74,12 @@ ArtLambdaMethod::ArtLambdaMethod(ArtMethod* target_method, } } +size_t ArtLambdaMethod::GetArgumentVRegCount() const { + DCHECK(GetArtMethod()->IsStatic()); // Instance methods don't have receiver in shorty. + const char* method_shorty = GetArtMethod()->GetShorty(); + DCHECK_NE(*method_shorty, '\0') << method_shorty; + return ShortyFieldType::CountVirtualRegistersRequired(method_shorty + 1); // skip return type +} + } // namespace lambda } // namespace art diff --git a/runtime/lambda/art_lambda_method.h b/runtime/lambda/art_lambda_method.h index ea13eb7af6..a858bf945d 100644 --- a/runtime/lambda/art_lambda_method.h +++ b/runtime/lambda/art_lambda_method.h @@ -90,6 +90,17 @@ class ArtLambdaMethod { return strlen(captured_variables_shorty_); } + // Return the offset in bytes from the start of ArtLambdaMethod to the method_. + // -- Only should be used by assembly (stubs) support code and compiled code. + static constexpr size_t GetArtMethodOffset() { + return offsetof(ArtLambdaMethod, method_); + } + + // Calculate how many vregs all the arguments will use when doing an invoke. + // (Most primitives are 1 vregs, double/long are 2, reference is 1, lambda is 2). + // -- This is used to know how big to set up shadow frame when invoking into the target method. + size_t GetArgumentVRegCount() const SHARED_REQUIRES(Locks::mutator_lock_); + private: // TODO: ArtMethod, or at least the entry points should be inlined into this struct // to avoid an extra indirect load when doing invokes. diff --git a/runtime/lambda/box_class_table-inl.h b/runtime/lambda/box_class_table-inl.h new file mode 100644 index 0000000000..2fc34a7b2c --- /dev/null +++ b/runtime/lambda/box_class_table-inl.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 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_LAMBDA_BOX_CLASS_TABLE_INL_H_ +#define ART_RUNTIME_LAMBDA_BOX_CLASS_TABLE_INL_H_ + +#include "lambda/box_class_table.h" +#include "thread.h" + +namespace art { +namespace lambda { + +template <typename Visitor> +inline void BoxClassTable::VisitRoots(const Visitor& visitor) { + MutexLock mu(Thread::Current(), *Locks::lambda_class_table_lock_); + for (std::pair<UnorderedMapKeyType, ValueType>& key_value : map_) { + ValueType& gc_root = key_value.second; + visitor.VisitRoot(gc_root.AddressWithoutBarrier()); + } +} + +} // namespace lambda +} // namespace art + +#endif // ART_RUNTIME_LAMBDA_BOX_CLASS_TABLE_INL_H_ diff --git a/runtime/lambda/box_class_table.cc b/runtime/lambda/box_class_table.cc new file mode 100644 index 0000000000..1e49886b95 --- /dev/null +++ b/runtime/lambda/box_class_table.cc @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "lambda/box_class_table.h" + +#include "base/mutex.h" +#include "common_throws.h" +#include "gc_root-inl.h" +#include "lambda/closure.h" +#include "lambda/leaking_allocator.h" +#include "mirror/method.h" +#include "mirror/object-inl.h" +#include "thread.h" + +#include <string> +#include <vector> + +namespace art { +namespace lambda { + +// Create the lambda proxy class given the name of the lambda interface (e.g. Ljava/lang/Runnable;) +// Also needs a proper class loader (or null for bootclasspath) where the proxy will be created +// into. +// +// The class must **not** have already been created. +// Returns a non-null ptr on success, otherwise returns null and has an exception set. +static mirror::Class* CreateClass(Thread* self, + const std::string& class_name, + const Handle<mirror::ClassLoader>& class_loader) + SHARED_REQUIRES(Locks::mutator_lock_) { + ScopedObjectAccessUnchecked soa(self); + StackHandleScope<2> hs(self); + + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + + // Find the java.lang.Class for our class name (from the class loader). + Handle<mirror::Class> lambda_interface = + hs.NewHandle(class_linker->FindClass(self, class_name.c_str(), class_loader)); + // TODO: use LookupClass in a loop + // TODO: DCHECK That this doesn't actually cause the class to be loaded, + // since the create-lambda should've loaded it already + DCHECK(lambda_interface.Get() != nullptr) << "CreateClass with class_name=" << class_name; + DCHECK(lambda_interface->IsInterface()) << "CreateClass with class_name=" << class_name; + jobject lambda_interface_class = soa.AddLocalReference<jobject>(lambda_interface.Get()); + + // Look up java.lang.reflect.Proxy#getLambdaProxyClass method. + Handle<mirror::Class> java_lang_reflect_proxy = + hs.NewHandle(class_linker->FindSystemClass(soa.Self(), "Ljava/lang/reflect/Proxy;")); + jclass java_lang_reflect_proxy_class = + soa.AddLocalReference<jclass>(java_lang_reflect_proxy.Get()); + DCHECK(java_lang_reflect_proxy.Get() != nullptr); + + jmethodID proxy_factory_method_id = + soa.Env()->GetStaticMethodID(java_lang_reflect_proxy_class, + "getLambdaProxyClass", + "(Ljava/lang/ClassLoader;Ljava/lang/Class;)Ljava/lang/Class;"); + DCHECK(!soa.Env()->ExceptionCheck()); + + // Call into the java code to do the hard work of figuring out which methods and throws + // our lambda interface proxy needs to implement. It then calls back into the class linker + // on our behalf to make the proxy itself. + jobject generated_lambda_proxy_class = + soa.Env()->CallStaticObjectMethod(java_lang_reflect_proxy_class, + proxy_factory_method_id, + class_loader.ToJObject(), + lambda_interface_class); + + // This can throw in which case we return null. Caller must handle. + return soa.Decode<mirror::Class*>(generated_lambda_proxy_class); +} + +BoxClassTable::BoxClassTable() { +} + +BoxClassTable::~BoxClassTable() { + // Don't need to do anything, classes are deleted automatically by GC + // when the classloader is deleted. + // + // Our table will not outlive the classloader since the classloader owns it. +} + +mirror::Class* BoxClassTable::GetOrCreateBoxClass(const char* class_name, + const Handle<mirror::ClassLoader>& class_loader) { + DCHECK(class_name != nullptr); + + Thread* self = Thread::Current(); + + std::string class_name_str = class_name; + + { + MutexLock mu(self, *Locks::lambda_class_table_lock_); + + // Attempt to look up this class, it's possible it was already created previously. + // If this is the case we *must* return the same class as before to maintain + // referential equality between box instances. + // + // In managed code: + // Functional f = () -> 5; // vF = create-lambda + // Object a = f; // vA = box-lambda vA + // Object b = f; // vB = box-lambda vB + // assert(a.getClass() == b.getClass()) + // assert(a == b) + ValueType value = FindBoxedClass(class_name_str); + if (!value.IsNull()) { + return value.Read(); + } + } + + // Otherwise we need to generate a class ourselves and insert it into the hash map + + // Release the table lock here, which implicitly allows other threads to suspend + // (since the GC callbacks will not block on trying to acquire our lock). + // We also don't want to call into the class linker with the lock held because + // our lock level is lower. + self->AllowThreadSuspension(); + + // Create a lambda proxy class, within the specified class loader. + mirror::Class* lambda_proxy_class = CreateClass(self, class_name_str, class_loader); + + // There are no thread suspension points after this, so we don't need to put it into a handle. + ScopedAssertNoThreadSuspension soants{self, "BoxClassTable::GetOrCreateBoxClass"}; // NOLINT: [readability/braces] [4] + + if (UNLIKELY(lambda_proxy_class == nullptr)) { + // Most likely an OOM has occurred. + CHECK(self->IsExceptionPending()); + return nullptr; + } + + { + MutexLock mu(self, *Locks::lambda_class_table_lock_); + + // Possible, but unlikely, that someone already came in and made a proxy class + // on another thread. + ValueType value = FindBoxedClass(class_name_str); + if (UNLIKELY(!value.IsNull())) { + DCHECK_EQ(lambda_proxy_class, value.Read()); + return value.Read(); + } + + // Otherwise we made a brand new proxy class. + // The class itself is cleaned up by the GC (e.g. class unloading) later. + + // Actually insert into the table. + map_.Insert({std::move(class_name_str), ValueType(lambda_proxy_class)}); + } + + return lambda_proxy_class; +} + +BoxClassTable::ValueType BoxClassTable::FindBoxedClass(const std::string& class_name) const { + auto map_iterator = map_.Find(class_name); + if (map_iterator != map_.end()) { + const std::pair<UnorderedMapKeyType, ValueType>& key_value_pair = *map_iterator; + const ValueType& value = key_value_pair.second; + + DCHECK(!value.IsNull()); // Never store null boxes. + return value; + } + + return ValueType(nullptr); +} + +void BoxClassTable::EmptyFn::MakeEmpty(std::pair<UnorderedMapKeyType, ValueType>& item) const { + item.first.clear(); + + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); + item.second = ValueType(); // Also clear the GC root. +} + +bool BoxClassTable::EmptyFn::IsEmpty(const std::pair<UnorderedMapKeyType, ValueType>& item) const { + bool is_empty = item.first.empty(); + DCHECK_EQ(item.second.IsNull(), is_empty); + + return is_empty; +} + +bool BoxClassTable::EqualsFn::operator()(const UnorderedMapKeyType& lhs, + const UnorderedMapKeyType& rhs) const { + // Be damn sure the classes don't just move around from under us. + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); + + // Being the same class name isn't enough, must also have the same class loader. + // When we are in the same class loader, classes are equal via the pointer. + return lhs == rhs; +} + +size_t BoxClassTable::HashFn::operator()(const UnorderedMapKeyType& key) const { + return std::hash<std::string>()(key); +} + +} // namespace lambda +} // namespace art diff --git a/runtime/lambda/box_class_table.h b/runtime/lambda/box_class_table.h new file mode 100644 index 0000000000..17e10265f1 --- /dev/null +++ b/runtime/lambda/box_class_table.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015 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_LAMBDA_BOX_CLASS_TABLE_H_ +#define ART_RUNTIME_LAMBDA_BOX_CLASS_TABLE_H_ + +#include "base/allocator.h" +#include "base/hash_map.h" +#include "gc_root.h" +#include "base/macros.h" +#include "base/mutex.h" +#include "object_callbacks.h" + +#include <stdint.h> + +namespace art { + +class ArtMethod; // forward declaration +template<class T> class Handle; // forward declaration + +namespace mirror { +class Class; // forward declaration +class ClassLoader; // forward declaration +class LambdaProxy; // forward declaration +class Object; // forward declaration +} // namespace mirror + +namespace lambda { +struct Closure; // forward declaration + +/* + * Store a table of boxed lambdas. This is required to maintain object referential equality + * when a lambda is re-boxed. + * + * Conceptually, we store a mapping of Class Name -> Weak Reference<Class>. + * When too many objects get GCd, we shrink the underlying table to use less space. + */ +class BoxClassTable FINAL { + public: + // TODO: This should take a LambdaArtMethod instead, read class name from that. + // Note: null class_loader means bootclasspath. + mirror::Class* GetOrCreateBoxClass(const char* class_name, + const Handle<mirror::ClassLoader>& class_loader) + REQUIRES(!Locks::lambda_class_table_lock_, !Roles::uninterruptible_) + SHARED_REQUIRES(Locks::mutator_lock_); + + // Sweep strong references to lambda class boxes. Update the addresses if the objects + // have been moved, and delete them from the table if the objects have been cleaned up. + template <typename Visitor> + void VisitRoots(const Visitor& visitor) + NO_THREAD_SAFETY_ANALYSIS // for object marking requiring heap bitmap lock + REQUIRES(!Locks::lambda_class_table_lock_) + SHARED_REQUIRES(Locks::mutator_lock_); + + BoxClassTable(); + ~BoxClassTable(); + + private: + // We only store strong GC roots in our table. + using ValueType = GcRoot<mirror::Class>; + + // Attempt to look up the class in the map, or return null if it's not there yet. + ValueType FindBoxedClass(const std::string& class_name) const + SHARED_REQUIRES(Locks::lambda_class_table_lock_); + + // Store the key as a string so that we can have our own copy of the class name. + using UnorderedMapKeyType = std::string; + + // EmptyFn implementation for art::HashMap + struct EmptyFn { + void MakeEmpty(std::pair<UnorderedMapKeyType, ValueType>& item) const + NO_THREAD_SAFETY_ANALYSIS; + // SHARED_REQUIRES(Locks::mutator_lock_); + + bool IsEmpty(const std::pair<UnorderedMapKeyType, ValueType>& item) const; + }; + + // HashFn implementation for art::HashMap + struct HashFn { + size_t operator()(const UnorderedMapKeyType& key) const + NO_THREAD_SAFETY_ANALYSIS; + // SHARED_REQUIRES(Locks::mutator_lock_); + }; + + // EqualsFn implementation for art::HashMap + struct EqualsFn { + bool operator()(const UnorderedMapKeyType& lhs, const UnorderedMapKeyType& rhs) const + NO_THREAD_SAFETY_ANALYSIS; + // SHARED_REQUIRES(Locks::mutator_lock_); + }; + + using UnorderedMap = art::HashMap<UnorderedMapKeyType, + ValueType, + EmptyFn, + HashFn, + EqualsFn, + TrackingAllocator<std::pair<UnorderedMapKeyType, ValueType>, + kAllocatorTagLambdaProxyClassBoxTable>>; + + // Map of strong GC roots (lambda interface name -> lambda proxy class) + UnorderedMap map_ GUARDED_BY(Locks::lambda_class_table_lock_); + + // Shrink the map when we get below this load factor. + // (This is an arbitrary value that should be large enough to prevent aggressive map erases + // from shrinking the table too often.) + static constexpr double kMinimumLoadFactor = UnorderedMap::kDefaultMinLoadFactor / 2; + + DISALLOW_COPY_AND_ASSIGN(BoxClassTable); +}; + +} // namespace lambda +} // namespace art + +#endif // ART_RUNTIME_LAMBDA_BOX_CLASS_TABLE_H_ diff --git a/runtime/lambda/box_table.cc b/runtime/lambda/box_table.cc index 9918bb71f3..0032d081c6 100644 --- a/runtime/lambda/box_table.cc +++ b/runtime/lambda/box_table.cc @@ -18,8 +18,10 @@ #include "base/mutex.h" #include "common_throws.h" #include "gc_root-inl.h" +#include "lambda/box_class_table.h" #include "lambda/closure.h" #include "lambda/leaking_allocator.h" +#include "mirror/lambda_proxy.h" #include "mirror/method.h" #include "mirror/object-inl.h" #include "thread.h" @@ -28,12 +30,13 @@ namespace art { namespace lambda { -// Temporarily represent the lambda Closure as its raw bytes in an array. -// TODO: Generate a proxy class for the closure when boxing the first time. -using BoxedClosurePointerType = mirror::ByteArray*; +// All closures are boxed into a subtype of LambdaProxy which implements the lambda's interface. +using BoxedClosurePointerType = mirror::LambdaProxy*; -static mirror::Class* GetBoxedClosureClass() SHARED_REQUIRES(Locks::mutator_lock_) { - return mirror::ByteArray::GetArrayClass(); +// Returns the base class for all boxed closures. +// Note that concrete closure boxes are actually a subtype of mirror::LambdaProxy. +static mirror::Class* GetBoxedClosureBaseClass() SHARED_REQUIRES(Locks::mutator_lock_) { + return Runtime::Current()->GetClassLinker()->GetClassRoot(ClassLinker::kJavaLangLambdaProxy); } namespace { @@ -54,6 +57,14 @@ namespace { return closure; } }; + + struct DeleterForClosure { + void operator()(Closure* closure) const { + ClosureAllocator::Delete(closure); + } + }; + + using UniqueClosurePtr = std::unique_ptr<Closure, DeleterForClosure>; } // namespace BoxTable::BoxTable() @@ -75,7 +86,9 @@ BoxTable::~BoxTable() { } } -mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { +mirror::Object* BoxTable::BoxLambda(const ClosureType& closure, + const char* class_name, + mirror::ClassLoader* class_loader) { Thread* self = Thread::Current(); { @@ -91,7 +104,7 @@ mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { // Functional f = () -> 5; // vF = create-lambda // Object a = f; // vA = box-lambda vA // Object b = f; // vB = box-lambda vB - // assert(a == f) + // assert(a == b) ValueType value = FindBoxedLambda(closure); if (!value.IsNull()) { return value.Read(); @@ -100,30 +113,62 @@ mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { // Otherwise we need to box ourselves and insert it into the hash map } - // Release the lambda table lock here, so that thread suspension is allowed. + // Convert the Closure into a managed object instance, whose supertype of java.lang.LambdaProxy. - // Convert the Closure into a managed byte[] which will serve - // as the temporary 'boxed' version of the lambda. This is good enough - // to check all the basic object identities that a boxed lambda must retain. - // It's also good enough to contain all the captured primitive variables. - - // TODO: Boxing an innate lambda (i.e. made with create-lambda) should make a proxy class // TODO: Boxing a learned lambda (i.e. made with unbox-lambda) should return the original object - BoxedClosurePointerType closure_as_array_object = - mirror::ByteArray::Alloc(self, closure->GetSize()); + StackHandleScope<2> hs{self}; // NOLINT: [readability/braces] [4] - // There are no thread suspension points after this, so we don't need to put it into a handle. + Handle<mirror::ClassLoader> class_loader_handle = hs.NewHandle(class_loader); + + // Release the lambda table lock here, so that thread suspension is allowed. + self->AllowThreadSuspension(); + + lambda::BoxClassTable* lambda_box_class_table; + + // Find the lambda box class table, which can be in the system class loader if classloader is null + if (class_loader == nullptr) { + ScopedObjectAccessUnchecked soa(self); + mirror::ClassLoader* system_class_loader = + soa.Decode<mirror::ClassLoader*>(Runtime::Current()->GetSystemClassLoader()); + lambda_box_class_table = system_class_loader->GetLambdaProxyCache(); + } else { + lambda_box_class_table = class_loader_handle->GetLambdaProxyCache(); + // OK: can't be deleted while we hold a handle to the class loader. + } + DCHECK(lambda_box_class_table != nullptr); - if (UNLIKELY(closure_as_array_object == nullptr)) { + Handle<mirror::Class> closure_class(hs.NewHandle( + lambda_box_class_table->GetOrCreateBoxClass(class_name, class_loader_handle))); + if (UNLIKELY(closure_class.Get() == nullptr)) { // Most likely an OOM has occurred. - CHECK(self->IsExceptionPending()); + self->AssertPendingException(); return nullptr; } - // Write the raw closure data into the byte[]. - closure->CopyTo(closure_as_array_object->GetRawData(sizeof(uint8_t), // component size - 0 /*index*/), // index - closure_as_array_object->GetLength()); + BoxedClosurePointerType closure_as_object = nullptr; + UniqueClosurePtr closure_table_copy; + // Create an instance of the class, and assign the pointer to the closure into it. + { + closure_as_object = down_cast<BoxedClosurePointerType>(closure_class->AllocObject(self)); + if (UNLIKELY(closure_as_object == nullptr)) { + self->AssertPendingOOMException(); + return nullptr; + } + + // Make a copy of the closure that we will store in the hash map. + // The proxy instance will also point to this same hash map. + // Note that the closure pointer is cleaned up only after the proxy is GCd. + closure_table_copy.reset(ClosureAllocator::Allocate(closure->GetSize())); + closure_as_object->SetClosure(closure_table_copy.get()); + } + + // There are no thread suspension points after this, so we don't need to put it into a handle. + ScopedAssertNoThreadSuspension soants{self, // NOLINT: [whitespace/braces] [5] + "box lambda table - box lambda - no more suspensions"}; // NOLINT: [whitespace/braces] [5] + + // Write the raw closure data into the proxy instance's copy of the closure. + closure->CopyTo(closure_table_copy.get(), + closure->GetSize()); // The method has been successfully boxed into an object, now insert it into the hash map. { @@ -134,24 +179,21 @@ mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { // we were allocating the object before. ValueType value = FindBoxedLambda(closure); if (UNLIKELY(!value.IsNull())) { - // Let the GC clean up method_as_object at a later time. + // Let the GC clean up closure_as_object at a later time. + // (We will not see this object when sweeping, it wasn't inserted yet.) + closure_as_object->SetClosure(nullptr); return value.Read(); } // Otherwise we need to insert it into the hash map in this thread. - // Make a copy for the box table to keep, in case the closure gets collected from the stack. - // TODO: GC may need to sweep for roots in the box table's copy of the closure. - Closure* closure_table_copy = ClosureAllocator::Allocate(closure->GetSize()); - closure->CopyTo(closure_table_copy, closure->GetSize()); - - // The closure_table_copy needs to be deleted by us manually when we erase it from the map. + // The closure_table_copy is deleted by us manually when we erase it from the map. // Actually insert into the table. - map_.Insert({closure_table_copy, ValueType(closure_as_array_object)}); + map_.Insert({closure_table_copy.release(), ValueType(closure_as_object)}); } - return closure_as_array_object; + return closure_as_object; } bool BoxTable::UnboxLambda(mirror::Object* object, ClosureType* out_closure) { @@ -165,29 +207,35 @@ bool BoxTable::UnboxLambda(mirror::Object* object, ClosureType* out_closure) { mirror::Object* boxed_closure_object = object; - // Raise ClassCastException if object is not instanceof byte[] - if (UNLIKELY(!boxed_closure_object->InstanceOf(GetBoxedClosureClass()))) { - ThrowClassCastException(GetBoxedClosureClass(), boxed_closure_object->GetClass()); + // Raise ClassCastException if object is not instanceof LambdaProxy + if (UNLIKELY(!boxed_closure_object->InstanceOf(GetBoxedClosureBaseClass()))) { + ThrowClassCastException(GetBoxedClosureBaseClass(), boxed_closure_object->GetClass()); return false; } // TODO(iam): We must check that the closure object extends/implements the type - // specified in [type id]. This is not currently implemented since it's always a byte[]. + // specified in [type id]. This is not currently implemented since the type id is unavailable. // If we got this far, the inputs are valid. - // Shuffle the byte[] back into a raw closure, then allocate it, copy, and return it. - BoxedClosurePointerType boxed_closure_as_array = + // Shuffle the java.lang.LambdaProxy back into a raw closure, then allocate it, copy, + // and return it. + BoxedClosurePointerType boxed_closure = down_cast<BoxedClosurePointerType>(boxed_closure_object); - const int8_t* unaligned_interior_closure = boxed_closure_as_array->GetData(); + DCHECK_ALIGNED(boxed_closure->GetClosure(), alignof(Closure)); + const Closure* aligned_interior_closure = boxed_closure->GetClosure(); + DCHECK(aligned_interior_closure != nullptr); + + // TODO: we probably don't need to make a copy here later on, once there's GC support. // Allocate a copy that can "escape" and copy the closure data into that. Closure* unboxed_closure = - LeakingAllocator::MakeFlexibleInstance<Closure>(self, boxed_closure_as_array->GetLength()); + LeakingAllocator::MakeFlexibleInstance<Closure>(self, aligned_interior_closure->GetSize()); + DCHECK_ALIGNED(unboxed_closure, alignof(Closure)); // TODO: don't just memcpy the closure, it's unsafe when we add references to the mix. - memcpy(unboxed_closure, unaligned_interior_closure, boxed_closure_as_array->GetLength()); + memcpy(unboxed_closure, aligned_interior_closure, aligned_interior_closure->GetSize()); - DCHECK_EQ(unboxed_closure->GetSize(), static_cast<size_t>(boxed_closure_as_array->GetLength())); + DCHECK_EQ(unboxed_closure->GetSize(), aligned_interior_closure->GetSize()); *out_closure = unboxed_closure; return true; @@ -236,9 +284,10 @@ void BoxTable::SweepWeakBoxedLambdas(IsMarkedVisitor* visitor) { if (new_value == nullptr) { // The object has been swept away. - const ClosureType& closure = key_value_pair.first; + Closure* closure = key_value_pair.first; // Delete the entry from the map. + // (Remove from map first to avoid accessing dangling pointer). map_iterator = map_.Erase(map_iterator); // Clean up the memory by deleting the closure. @@ -290,7 +339,10 @@ void BoxTable::EmptyFn::MakeEmpty(std::pair<UnorderedMapKeyType, ValueType>& ite } bool BoxTable::EmptyFn::IsEmpty(const std::pair<UnorderedMapKeyType, ValueType>& item) const { - return item.first == nullptr; + bool is_empty = item.first == nullptr; + DCHECK_EQ(item.second.IsNull(), is_empty); + + return is_empty; } bool BoxTable::EqualsFn::operator()(const UnorderedMapKeyType& lhs, diff --git a/runtime/lambda/box_table.h b/runtime/lambda/box_table.h index adb733271e..9dca6ab66b 100644 --- a/runtime/lambda/box_table.h +++ b/runtime/lambda/box_table.h @@ -30,6 +30,9 @@ namespace art { class ArtMethod; // forward declaration namespace mirror { +class Class; // forward declaration +class ClassLoader; // forward declaration +class LambdaProxy; // forward declaration class Object; // forward declaration } // namespace mirror @@ -48,8 +51,11 @@ class BoxTable FINAL { using ClosureType = art::lambda::Closure*; // Boxes a closure into an object. Returns null and throws an exception on failure. - mirror::Object* BoxLambda(const ClosureType& closure) - SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!Locks::lambda_table_lock_); + mirror::Object* BoxLambda(const ClosureType& closure, + const char* class_name, + mirror::ClassLoader* class_loader) + REQUIRES(!Locks::lambda_table_lock_, !Roles::uninterruptible_) + SHARED_REQUIRES(Locks::mutator_lock_); // Unboxes an object back into the lambda. Returns false and throws an exception on failure. bool UnboxLambda(mirror::Object* object, ClosureType* out_closure) @@ -128,7 +134,16 @@ class BoxTable FINAL { TrackingAllocator<std::pair<ClosureType, ValueType>, kAllocatorTagLambdaBoxTable>>; + using ClassMap = art::HashMap<std::string, + GcRoot<mirror::Class>, + EmptyFn, + HashFn, + EqualsFn, + TrackingAllocator<std::pair<ClosureType, ValueType>, + kAllocatorTagLambdaProxyClassBoxTable>>; + UnorderedMap map_ GUARDED_BY(Locks::lambda_table_lock_); + UnorderedMap classes_map_ GUARDED_BY(Locks::lambda_table_lock_); bool allow_new_weaks_ GUARDED_BY(Locks::lambda_table_lock_); ConditionVariable new_weaks_condition_ GUARDED_BY(Locks::lambda_table_lock_); diff --git a/runtime/lambda/closure.cc b/runtime/lambda/closure.cc index 179e4ee7f2..f935e049fd 100644 --- a/runtime/lambda/closure.cc +++ b/runtime/lambda/closure.cc @@ -20,9 +20,6 @@ #include "lambda/art_lambda_method.h" #include "runtime/mirror/object_reference.h" -static constexpr const bool kClosureSupportsReferences = false; -static constexpr const bool kClosureSupportsGarbageCollection = false; - namespace art { namespace lambda { @@ -128,6 +125,10 @@ ArtMethod* Closure::GetTargetMethod() const { return const_cast<ArtMethod*>(lambda_info_->GetArtMethod()); } +ArtLambdaMethod* Closure::GetLambdaInfo() const { + return const_cast<ArtLambdaMethod*>(lambda_info_); +} + uint32_t Closure::GetHashCode() const { // Start with a non-zero constant, a prime number. uint32_t result = 17; diff --git a/runtime/lambda/closure.h b/runtime/lambda/closure.h index 31ff1944d2..38ec063ed2 100644 --- a/runtime/lambda/closure.h +++ b/runtime/lambda/closure.h @@ -33,12 +33,52 @@ namespace lambda { class ArtLambdaMethod; // forward declaration class ClosureBuilder; // forward declaration +// TODO: Remove these constants once closures are supported properly. + +// Does the lambda closure support containing references? If so, all the users of lambdas +// must be updated to also support references. +static constexpr const bool kClosureSupportsReferences = false; +// Does the lambda closure support being garbage collected? If so, all the users of lambdas +// must be updated to also support garbage collection. +static constexpr const bool kClosureSupportsGarbageCollection = false; +// Does the lambda closure support being garbage collected with a read barrier? If so, +// all the users of the lambdas msut also be updated to support read barrier GC. +static constexpr const bool kClosureSupportsReadBarrier = false; + +// Is this closure being stored as a 'long' in shadow frames and the quick ABI? +static constexpr const bool kClosureIsStoredAsLong = true; + + +// Raw memory layout for the lambda closure. +// +// WARNING: +// * This should only be used by the compiler and tests, as they need to offsetof the raw fields. +// * Runtime/interpreter should always access closures through a Closure pointer. +struct ClosureStorage { + // Compile-time known lambda information such as the type descriptor and size. + ArtLambdaMethod* lambda_info_; + + // A contiguous list of captured variables, and possibly the closure size. + // The runtime size can always be determined through GetSize(). + union { + // Read from here if the closure size is static (ArtLambdaMethod::IsStatic) + uint8_t static_variables_[0]; + struct { + // Read from here if the closure size is dynamic (ArtLambdaMethod::IsDynamic) + size_t size_; // The lambda_info_ and the size_ itself is also included as part of the size. + uint8_t variables_[0]; + } dynamic_; + } captured_[0]; + // captured_ will always consist of one array element at runtime. + // Set to [0] so that 'size_' is not counted in sizeof(Closure). +}; + // Inline representation of a lambda closure. // Contains the target method and the set of packed captured variables as a copy. // // The closure itself is logically immutable, although in practice any object references // it (recursively) contains can be moved and updated by the GC. -struct PACKED(sizeof(ArtLambdaMethod*)) Closure { +struct Closure : private ClosureStorage { // Get the size of the Closure in bytes. // This is necessary in order to allocate a large enough area to copy the Closure into. // Do *not* copy the closure with memcpy, since references also need to get moved. @@ -52,6 +92,9 @@ struct PACKED(sizeof(ArtLambdaMethod*)) Closure { // Get the target method, i.e. the method that will be dispatched into with invoke-lambda. ArtMethod* GetTargetMethod() const; + // Get the static lambda info that never changes. + ArtLambdaMethod* GetLambdaInfo() const; + // Calculates the hash code. Value is recomputed each time. uint32_t GetHashCode() const SHARED_REQUIRES(Locks::mutator_lock_); @@ -156,28 +199,15 @@ struct PACKED(sizeof(ArtLambdaMethod*)) Closure { static size_t GetClosureSize(const uint8_t* closure); /////////////////////////////////////////////////////////////////////////////////// - - // Compile-time known lambda information such as the type descriptor and size. - ArtLambdaMethod* lambda_info_; - - // A contiguous list of captured variables, and possibly the closure size. - // The runtime size can always be determined through GetSize(). - union { - // Read from here if the closure size is static (ArtLambdaMethod::IsStatic) - uint8_t static_variables_[0]; - struct { - // Read from here if the closure size is dynamic (ArtLambdaMethod::IsDynamic) - size_t size_; // The lambda_info_ and the size_ itself is also included as part of the size. - uint8_t variables_[0]; - } dynamic_; - } captured_[0]; - // captured_ will always consist of one array element at runtime. - // Set to [0] so that 'size_' is not counted in sizeof(Closure). - - friend class ClosureBuilder; + // NOTE: Actual fields are declared in ClosureStorage. friend class ClosureTest; }; +// ABI guarantees: +// * Closure same size as a ClosureStorage +// * ClosureStorage begins at the same point a Closure would begin. +static_assert(sizeof(Closure) == sizeof(ClosureStorage), "Closure size must match ClosureStorage"); + } // namespace lambda } // namespace art diff --git a/runtime/lambda/closure_builder.cc b/runtime/lambda/closure_builder.cc index 739e965238..7b36042921 100644 --- a/runtime/lambda/closure_builder.cc +++ b/runtime/lambda/closure_builder.cc @@ -75,7 +75,7 @@ void ClosureBuilder::CaptureVariableLambda(Closure* closure) { if (LIKELY(is_dynamic_size_ == false)) { // Write in the extra bytes to store the dynamic size the first time. is_dynamic_size_ = true; - size_ += sizeof(Closure::captured_[0].dynamic_.size_); + size_ += sizeof(ClosureStorage::captured_[0].dynamic_.size_); } // A closure may be sized dynamically, so always query it for the true size. @@ -107,38 +107,40 @@ Closure* ClosureBuilder::CreateInPlace(void* memory, ArtLambdaMethod* target_met << "number of variables captured at runtime does not match " << "number of variables captured at compile time"; - Closure* closure = new (memory) Closure; - closure->lambda_info_ = target_method; + ClosureStorage* closure_storage = new (memory) ClosureStorage; + closure_storage->lambda_info_ = target_method; - static_assert(offsetof(Closure, captured_) == kInitialSize, "wrong initial size"); + static_assert(offsetof(ClosureStorage, captured_) == kInitialSize, "wrong initial size"); size_t written_size; if (UNLIKELY(is_dynamic_size_)) { // The closure size must be set dynamically (i.e. nested lambdas). - closure->captured_[0].dynamic_.size_ = GetSize(); - size_t header_size = offsetof(Closure, captured_[0].dynamic_.variables_); + closure_storage->captured_[0].dynamic_.size_ = GetSize(); + size_t header_size = offsetof(ClosureStorage, captured_[0].dynamic_.variables_); DCHECK_LE(header_size, GetSize()); size_t variables_size = GetSize() - header_size; written_size = WriteValues(target_method, - closure->captured_[0].dynamic_.variables_, + closure_storage->captured_[0].dynamic_.variables_, header_size, variables_size); } else { // The closure size is known statically (i.e. no nested lambdas). DCHECK(GetSize() == target_method->GetStaticClosureSize()); - size_t header_size = offsetof(Closure, captured_[0].static_variables_); + size_t header_size = offsetof(ClosureStorage, captured_[0].static_variables_); DCHECK_LE(header_size, GetSize()); size_t variables_size = GetSize() - header_size; written_size = WriteValues(target_method, - closure->captured_[0].static_variables_, + closure_storage->captured_[0].static_variables_, header_size, variables_size); } - DCHECK_EQ(written_size, closure->GetSize()); + // OK: The closure storage is guaranteed to be the same as a closure. + Closure* closure = reinterpret_cast<Closure*>(closure_storage); + DCHECK_EQ(written_size, closure->GetSize()); return closure; } diff --git a/runtime/lambda/shorty_field_type.h b/runtime/lambda/shorty_field_type.h index 46ddaa9ab3..54bb4d4fe6 100644 --- a/runtime/lambda/shorty_field_type.h +++ b/runtime/lambda/shorty_field_type.h @@ -285,6 +285,39 @@ struct ShortyFieldType : ValueObject { } } + // Get the number of virtual registers necessary to represent this type as a stack local. + inline size_t GetVirtualRegisterCount() const { + if (IsPrimitiveNarrow()) { + return 1; + } else if (IsPrimitiveWide()) { + return 2; + } else if (IsObject()) { + return kObjectReferenceSize / sizeof(uint32_t); + } else if (IsLambda()) { + return 2; + } else { + DCHECK(false) << "unknown shorty field type '" << static_cast<char>(value_) << "'"; + UNREACHABLE(); + } + } + + // Count how many virtual registers would be necessary in order to store this list of shorty + // field types. + inline size_t static CountVirtualRegistersRequired(const char* shorty) { + size_t size = 0; + + while (shorty != nullptr && *shorty != '\0') { + // Each argument appends to the size. + ShortyFieldType shorty_field{*shorty}; // NOLINT [readability/braces] [4] + + size += shorty_field.GetVirtualRegisterCount(); + + ++shorty; + } + + return size; + } + // Implicitly convert to the anonymous nested inner type. Used for exhaustive switch detection. inline operator decltype(kByte)() const { return value_; diff --git a/runtime/lambda/shorty_field_type_test.cc b/runtime/lambda/shorty_field_type_test.cc index 32bade9b56..430e39e94d 100644 --- a/runtime/lambda/shorty_field_type_test.cc +++ b/runtime/lambda/shorty_field_type_test.cc @@ -218,6 +218,56 @@ TEST_F(ShortyFieldTypeTest, TestParseFromFieldTypeDescriptor) { } } // TEST_F +TEST_F(ShortyFieldTypeTest, TestCalculateVRegSize) { + // Make sure the single calculation for each value is correct. + std::pair<size_t, char> expected_actual_single[] = { + // Primitives + { 1u, 'Z' }, + { 1u, 'B' }, + { 1u, 'C' }, + { 1u, 'S' }, + { 1u, 'I' }, + { 1u, 'F' }, + { 2u, 'J' }, + { 2u, 'D' }, + // Non-primitives + { 1u, 'L' }, + { 2u, '\\' }, + }; + + for (auto pair : expected_actual_single) { + SCOPED_TRACE(pair.second); + EXPECT_EQ(pair.first, ShortyFieldType(pair.second).GetVirtualRegisterCount()); + } + + // Make sure we are correctly calculating how many virtual registers a shorty descriptor takes. + std::pair<size_t, const char*> expected_actual[] = { + // Empty list + { 0u, "" }, + // Primitives + { 1u, "Z" }, + { 1u, "B" }, + { 1u, "C" }, + { 1u, "S" }, + { 1u, "I" }, + { 1u, "F" }, + { 2u, "J" }, + { 2u, "D" }, + // Non-primitives + { 1u, "L" }, + { 2u, "\\" }, + // Multiple things at once: + { 10u, "ZBCSIFJD" }, + { 5u, "LLSSI" }, + { 6u, "LLL\\L" } + }; + + for (auto pair : expected_actual) { + SCOPED_TRACE(pair.second); + EXPECT_EQ(pair.first, ShortyFieldType::CountVirtualRegistersRequired(pair.second)); + } +} // TEST_F + // Helper class to probe a shorty's characteristics by minimizing copy-and-paste tests. template <typename T, decltype(ShortyFieldType::kByte) kShortyEnum> struct ShortyTypeCharacteristics { diff --git a/runtime/lambda_proxy_test.cc b/runtime/lambda_proxy_test.cc new file mode 100644 index 0000000000..63d6cccedb --- /dev/null +++ b/runtime/lambda_proxy_test.cc @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <jni.h> +#include <vector> + +#include "art_field-inl.h" +#include "class_linker-inl.h" +#include "compiler_callbacks.h" +#include "common_compiler_test.h" +#include "mirror/field-inl.h" +#include "mirror/lambda_proxy.h" +#include "mirror/method.h" +#include "scoped_thread_state_change.h" + +namespace art { + +// The enclosing class of all the interfaces used by this test. +// -- Defined as a macro to allow for string concatenation. +#define TEST_INTERFACE_ENCLOSING_CLASS_NAME "LambdaInterfaces" +// Generate out "LLambdaInterfaces$<<iface>>;" , replacing <<iface>> with the interface name. +#define MAKE_TEST_INTERFACE_NAME(iface) ("L" TEST_INTERFACE_ENCLOSING_CLASS_NAME "$" iface ";") + +#define ASSERT_NOT_NULL(x) ASSERT_TRUE((x) != nullptr) +#define ASSERT_NULL(x) ASSERT_TRUE((x) == nullptr) +#define EXPECT_NULL(x) EXPECT_TRUE((x) == nullptr) + +class LambdaProxyTest // : public CommonCompilerTest { + : public CommonRuntimeTest { + public: + // Generate a lambda proxy class with the given name and interfaces. This is a simplification from what + // libcore does to fit to our test needs. We do not check for duplicated interfaces or methods and + // we do not declare exceptions. + mirror::Class* GenerateProxyClass(ScopedObjectAccess& soa, + jobject jclass_loader, + const char* class_name, + const std::vector<mirror::Class*>& interfaces) + SHARED_REQUIRES(Locks::mutator_lock_) { + CHECK(class_name != nullptr); + CHECK(jclass_loader != nullptr); + + mirror::Class* java_lang_object = + class_linker_->FindSystemClass(soa.Self(), "Ljava/lang/Object;"); + CHECK(java_lang_object != nullptr); + + jclass java_lang_class = soa.AddLocalReference<jclass>(mirror::Class::GetJavaLangClass()); + + // Builds the interfaces array. + jobjectArray proxy_class_interfaces = soa.Env()->NewObjectArray(interfaces.size(), + java_lang_class, + nullptr); // No initial element. + soa.Self()->AssertNoPendingException(); + for (size_t i = 0; i < interfaces.size(); ++i) { + soa.Env()->SetObjectArrayElement(proxy_class_interfaces, + i, + soa.AddLocalReference<jclass>(interfaces[i])); + } + + // Builds the method array. + jsize methods_count = 3; // Object.equals, Object.hashCode and Object.toString. + for (mirror::Class* interface : interfaces) { + methods_count += interface->NumVirtualMethods(); + } + jobjectArray proxy_class_methods = + soa.Env()->NewObjectArray(methods_count, + soa.AddLocalReference<jclass>(mirror::Method::StaticClass()), + nullptr); // No initial element. + soa.Self()->AssertNoPendingException(); + + jsize array_index = 0; + + // + // Fill the method array with the Object and all the interface's virtual methods. + // + + // Add a method to 'proxy_class_methods' + auto add_method_to_array = [&](ArtMethod* method) SHARED_REQUIRES(Locks::mutator_lock_) { + CHECK(method != nullptr); + soa.Env()->SetObjectArrayElement(proxy_class_methods, + array_index++, + soa.AddLocalReference<jobject>( + mirror::Method::CreateFromArtMethod(soa.Self(), + method)) + ); // NOLINT: [whitespace/parens] [2] + + LOG(DEBUG) << "Add " << PrettyMethod(method) << " to list of methods to generate proxy"; + }; + // Add a method to 'proxy_class_methods' by looking it up from java.lang.Object + auto add_method_to_array_by_lookup = [&](const char* name, const char* method_descriptor) + SHARED_REQUIRES(Locks::mutator_lock_) { + ArtMethod* method = java_lang_object->FindDeclaredVirtualMethod(name, + method_descriptor, + sizeof(void*)); + add_method_to_array(method); + }; + + // Add all methods from Object. + add_method_to_array_by_lookup("equals", "(Ljava/lang/Object;)Z"); + add_method_to_array_by_lookup("hashCode", "()I"); + add_method_to_array_by_lookup("toString", "()Ljava/lang/String;"); + + // Now adds all interfaces virtual methods. + for (mirror::Class* interface : interfaces) { + mirror::Class* next_class = interface; + do { + for (ArtMethod& method : next_class->GetVirtualMethods(sizeof(void*))) { + add_method_to_array(&method); + } + next_class = next_class->GetSuperClass(); + } while (!next_class->IsObjectClass()); + // Skip adding any methods from "Object". + } + CHECK_EQ(array_index, methods_count); + + // Builds an empty exception array. + jobjectArray proxy_class_throws = soa.Env()->NewObjectArray(0 /* length */, + java_lang_class, + nullptr /* initial element*/); + soa.Self()->AssertNoPendingException(); + + bool already_exists; + mirror::Class* proxy_class = + class_linker_->CreateLambdaProxyClass(soa, + soa.Env()->NewStringUTF(class_name), + proxy_class_interfaces, + jclass_loader, + proxy_class_methods, + proxy_class_throws, + /*out*/&already_exists); + + CHECK(!already_exists); + + soa.Self()->AssertNoPendingException(); + return proxy_class; + } + + LambdaProxyTest() { + } + + virtual void SetUp() { + CommonRuntimeTest::SetUp(); + } + + virtual void SetUpRuntimeOptions(RuntimeOptions* options ATTRIBUTE_UNUSED) { + // Do not have any compiler options because we don't want to run as an AOT + // (In particular the lambda proxy class generation isn't currently supported for AOT). + this->callbacks_.reset(); + } + + template <typename THandleScope> + Handle<mirror::Class> GenerateProxyClass(THandleScope& hs, + const char* name, + const std::vector<mirror::Class*>& interfaces) + SHARED_REQUIRES(Locks::mutator_lock_) { + return hs.NewHandle(GenerateProxyClass(*soa_, jclass_loader_, name, interfaces)); + } + + protected: + ScopedObjectAccess* soa_ = nullptr; + jobject jclass_loader_ = nullptr; +}; + +// Creates a lambda proxy class and check ClassHelper works correctly. +TEST_F(LambdaProxyTest, ProxyClassHelper) { + // gLogVerbosity.class_linker = true; // Uncomment to enable class linker logging. + + ASSERT_NOT_NULL(Thread::Current()); + + ScopedObjectAccess soa(Thread::Current()); + soa_ = &soa; + + // Must happen after CommonRuntimeTest finishes constructing the runtime. + jclass_loader_ = LoadDex(TEST_INTERFACE_ENCLOSING_CLASS_NAME); + jobject jclass_loader = jclass_loader_; + + StackHandleScope<4> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader*>(jclass_loader))); + + Handle<mirror::Class> J(hs.NewHandle( + class_linker_->FindClass(soa.Self(), MAKE_TEST_INTERFACE_NAME("J"), class_loader))); + ASSERT_TRUE(J.Get() != nullptr); + + std::vector<mirror::Class*> interfaces; + interfaces.push_back(J.Get()); + Handle<mirror::Class> proxy_class(hs.NewHandle( + GenerateProxyClass(soa, jclass_loader, "$Proxy1234", interfaces))); + interfaces.clear(); // Don't least possibly stale objects in the array as good practice. + ASSERT_TRUE(proxy_class.Get() != nullptr); + ASSERT_TRUE(proxy_class->IsLambdaProxyClass()); + ASSERT_TRUE(proxy_class->IsInitialized()); + + EXPECT_EQ(1U, proxy_class->NumDirectInterfaces()); // LambdaInterfaces$J. + EXPECT_EQ(J.Get(), mirror::Class::GetDirectInterface(soa.Self(), proxy_class, 0)); + std::string temp; + const char* proxy_class_descriptor = proxy_class->GetDescriptor(&temp); + EXPECT_STREQ("L$Proxy1234;", proxy_class_descriptor); + EXPECT_EQ(nullptr, proxy_class->GetSourceFile()); + + // Make sure all the virtual methods are marked as a proxy + for (ArtMethod& method : proxy_class->GetVirtualMethods(sizeof(void*))) { + SCOPED_TRACE(PrettyMethod(&method, /* with_signature */true)); + EXPECT_TRUE(method.IsProxyMethod()); + EXPECT_TRUE(method.IsLambdaProxyMethod()); + EXPECT_FALSE(method.IsReflectProxyMethod()); + } +} + +// Creates a proxy class and check FieldHelper works correctly. +TEST_F(LambdaProxyTest, ProxyFieldHelper) { + // gLogVerbosity.class_linker = true; // Uncomment to enable class linker logging. + + ASSERT_NOT_NULL(Thread::Current()); + + ScopedObjectAccess soa(Thread::Current()); + soa_ = &soa; + + // Must happen after CommonRuntimeTest finishes constructing the runtime. + jclass_loader_ = LoadDex(TEST_INTERFACE_ENCLOSING_CLASS_NAME); + jobject jclass_loader = jclass_loader_; + + StackHandleScope<9> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader*>(jclass_loader))); + + Handle<mirror::Class> I(hs.NewHandle( + class_linker_->FindClass(soa.Self(), MAKE_TEST_INTERFACE_NAME("I"), class_loader))); + ASSERT_NOT_NULL(I.Get()); + + // Create the lambda proxy which implements interfaces "I". + Handle<mirror::Class> proxy_class = GenerateProxyClass(hs, + "$Proxy1234", + { I.Get() }); // Interfaces. + + ASSERT_NOT_NULL(proxy_class.Get()); + EXPECT_TRUE(proxy_class->IsLambdaProxyClass()); + EXPECT_TRUE(proxy_class->IsInitialized()); + EXPECT_NULL(proxy_class->GetIFieldsPtr()); + + LengthPrefixedArray<ArtField>* static_fields = proxy_class->GetSFieldsPtr(); + ASSERT_NOT_NULL(static_fields); + + // Must have "throws" and "interfaces" static fields. + ASSERT_EQ(+mirror::LambdaProxy::kStaticFieldCount, proxy_class->NumStaticFields()); + + static constexpr const char* kInterfacesClassName = "[Ljava/lang/Class;"; + static constexpr const char* kThrowsClassName = "[[Ljava/lang/Class;"; + + // Class for "interfaces" field. + Handle<mirror::Class> interfaces_field_class = + hs.NewHandle(class_linker_->FindSystemClass(soa.Self(), kInterfacesClassName)); + ASSERT_NOT_NULL(interfaces_field_class.Get()); + + // Class for "throws" field. + Handle<mirror::Class> throws_field_class = + hs.NewHandle(class_linker_->FindSystemClass(soa.Self(), kThrowsClassName)); + ASSERT_NOT_NULL(throws_field_class.Get()); + + // Helper to test the static fields for correctness. + auto test_static_field = [&](size_t index, + const char* field_name, + Handle<mirror::Class>& handle_class, + const char* class_name) + SHARED_REQUIRES(Locks::mutator_lock_) { + ArtField* field = &static_fields->At(index); + EXPECT_STREQ(field_name, field->GetName()); + EXPECT_STREQ(class_name, field->GetTypeDescriptor()); + EXPECT_EQ(handle_class.Get(), field->GetType</*kResolve*/true>()) + << "Expected: " << PrettyClass(interfaces_field_class.Get()) << ", " + << "Actual: " << PrettyClass(field->GetType</*kResolve*/true>()) << ", " + << "field_name: " << field_name; + std::string temp; + EXPECT_STREQ("L$Proxy1234;", field->GetDeclaringClass()->GetDescriptor(&temp)); + EXPECT_FALSE(field->IsPrimitiveType()); + }; + + // Test "Class[] interfaces" field. + test_static_field(mirror::LambdaProxy::kStaticFieldIndexInterfaces, + "interfaces", + interfaces_field_class, + kInterfacesClassName); + + // Test "Class[][] throws" field. + test_static_field(mirror::LambdaProxy::kStaticFieldIndexThrows, + "throws", + throws_field_class, + kThrowsClassName); +} + +// Creates two proxy classes and check the art/mirror fields of their static fields. +TEST_F(LambdaProxyTest, CheckArtMirrorFieldsOfProxyStaticFields) { + // gLogVerbosity.class_linker = true; // Uncomment to enable class linker logging. + + ASSERT_NOT_NULL(Thread::Current()); + + ScopedObjectAccess soa(Thread::Current()); + soa_ = &soa; + + // Must happen after CommonRuntimeTest finishes constructing the runtime. + jclass_loader_ = LoadDex(TEST_INTERFACE_ENCLOSING_CLASS_NAME); + jobject jclass_loader = jclass_loader_; + + StackHandleScope<8> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader*>(jclass_loader))); + + Handle<mirror::Class> proxyClass0; + Handle<mirror::Class> proxyClass1; + { + Handle<mirror::Class> L(hs.NewHandle( + class_linker_->FindClass(soa.Self(), MAKE_TEST_INTERFACE_NAME("L"), class_loader))); + ASSERT_TRUE(L.Get() != nullptr); + + std::vector<mirror::Class*> interfaces = { L.Get() }; + proxyClass0 = hs.NewHandle(GenerateProxyClass(soa, jclass_loader, "$Proxy0", interfaces)); + proxyClass1 = hs.NewHandle(GenerateProxyClass(soa, jclass_loader, "$Proxy1", interfaces)); + } + + ASSERT_TRUE(proxyClass0.Get() != nullptr); + ASSERT_TRUE(proxyClass0->IsLambdaProxyClass()); + ASSERT_TRUE(proxyClass0->IsInitialized()); + ASSERT_TRUE(proxyClass1.Get() != nullptr); + ASSERT_TRUE(proxyClass1->IsLambdaProxyClass()); + ASSERT_TRUE(proxyClass1->IsInitialized()); + + LengthPrefixedArray<ArtField>* static_fields0 = proxyClass0->GetSFieldsPtr(); + ASSERT_TRUE(static_fields0 != nullptr); + ASSERT_EQ(2u, static_fields0->size()); + LengthPrefixedArray<ArtField>* static_fields1 = proxyClass1->GetSFieldsPtr(); + ASSERT_TRUE(static_fields1 != nullptr); + ASSERT_EQ(2u, static_fields1->size()); + + EXPECT_EQ(static_fields0->At(0).GetDeclaringClass(), proxyClass0.Get()); + EXPECT_EQ(static_fields0->At(1).GetDeclaringClass(), proxyClass0.Get()); + EXPECT_EQ(static_fields1->At(0).GetDeclaringClass(), proxyClass1.Get()); + EXPECT_EQ(static_fields1->At(1).GetDeclaringClass(), proxyClass1.Get()); + + Handle<mirror::Field> field00 = + hs.NewHandle(mirror::Field::CreateFromArtField(soa.Self(), &static_fields0->At(0), true)); + Handle<mirror::Field> field01 = + hs.NewHandle(mirror::Field::CreateFromArtField(soa.Self(), &static_fields0->At(1), true)); + Handle<mirror::Field> field10 = + hs.NewHandle(mirror::Field::CreateFromArtField(soa.Self(), &static_fields1->At(0), true)); + Handle<mirror::Field> field11 = + hs.NewHandle(mirror::Field::CreateFromArtField(soa.Self(), &static_fields1->At(1), true)); + EXPECT_EQ(field00->GetArtField(), &static_fields0->At(0)); + EXPECT_EQ(field01->GetArtField(), &static_fields0->At(1)); + EXPECT_EQ(field10->GetArtField(), &static_fields1->At(0)); + EXPECT_EQ(field11->GetArtField(), &static_fields1->At(1)); +} + +// TODO: make sure there's a non-abstract implementation of the single-abstract-method on the class. + +} // namespace art diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h index 9e416dc888..a8685b8331 100644 --- a/runtime/mirror/class-inl.h +++ b/runtime/mirror/class-inl.h @@ -695,7 +695,11 @@ inline bool Class::IsClassClass() { } inline const DexFile& Class::GetDexFile() { - return *GetDexCache()->GetDexFile(); + DexCache* dex_cache = GetDexCache(); + DCHECK(dex_cache != nullptr); + const DexFile* dex_file = dex_cache->GetDexFile(); + DCHECK(dex_file != nullptr); + return *dex_file; } inline bool Class::DescriptorEquals(const char* match) { @@ -703,8 +707,8 @@ inline bool Class::DescriptorEquals(const char* match) { return match[0] == '[' && GetComponentType()->DescriptorEquals(match + 1); } else if (IsPrimitive()) { return strcmp(Primitive::Descriptor(GetPrimitiveType()), match) == 0; - } else if (IsProxyClass()) { - return ProxyDescriptorEquals(match); + } else if (IsAnyProxyClass()) { + return AnyProxyDescriptorEquals(match); } else { const DexFile& dex_file = GetDexFile(); const DexFile::TypeId& type_id = dex_file.GetTypeId(GetClassDef()->class_idx_); @@ -720,22 +724,32 @@ inline void Class::AssertInitializedOrInitializingInThread(Thread* self) { } } -inline ObjectArray<Class>* Class::GetInterfaces() { - CHECK(IsProxyClass()); +inline ObjectArray<Class>* Class::GetInterfacesForAnyProxy() { + CHECK(IsAnyProxyClass()); // First static field. auto* field = GetStaticField(0); DCHECK_STREQ(field->GetName(), "interfaces"); MemberOffset field_offset = field->GetOffset(); - return GetFieldObject<ObjectArray<Class>>(field_offset); + ObjectArray<Class>* interfaces_array = GetFieldObject<ObjectArray<Class>>(field_offset); + + CHECK(interfaces_array != nullptr); + if (UNLIKELY(IsLambdaProxyClass())) { + DCHECK_EQ(1, interfaces_array->GetLength()) + << "Lambda proxies cannot have multiple direct interfaces implemented"; + } + return interfaces_array; } -inline ObjectArray<ObjectArray<Class>>* Class::GetThrows() { - CHECK(IsProxyClass()); +inline ObjectArray<ObjectArray<Class>>* Class::GetThrowsForAnyProxy() { + CHECK(IsAnyProxyClass()); // Second static field. auto* field = GetStaticField(1); DCHECK_STREQ(field->GetName(), "throws"); + MemberOffset field_offset = field->GetOffset(); - return GetFieldObject<ObjectArray<ObjectArray<Class>>>(field_offset); + auto* throws_array = GetFieldObject<ObjectArray<ObjectArray<Class>>>(field_offset); + CHECK(throws_array != nullptr); + return throws_array; } inline MemberOffset Class::GetDisableIntrinsicFlagOffset() { @@ -796,8 +810,8 @@ inline uint32_t Class::NumDirectInterfaces() { return 0; } else if (IsArrayClass()) { return 2; - } else if (IsProxyClass()) { - mirror::ObjectArray<mirror::Class>* interfaces = GetInterfaces(); + } else if (IsAnyProxyClass()) { + mirror::ObjectArray<mirror::Class>* interfaces = GetInterfacesForAnyProxy(); return interfaces != nullptr ? interfaces->GetLength() : 0; } else { const DexFile::TypeList* interfaces = GetInterfaceTypeList(); diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc index 05a9039ae9..b2012934a2 100644 --- a/runtime/mirror/class.cc +++ b/runtime/mirror/class.cc @@ -538,6 +538,7 @@ ArtMethod* Class::FindVirtualMethod( ArtMethod* Class::FindClassInitializer(size_t pointer_size) { for (ArtMethod& method : GetDirectMethods(pointer_size)) { + DCHECK(reinterpret_cast<volatile void*>(&method) != nullptr); if (method.IsClassInitializer()) { DCHECK_STREQ(method.GetName(), "<clinit>"); DCHECK_STREQ(method.GetSignature().ToString().c_str(), "()V"); @@ -742,8 +743,8 @@ const char* Class::GetDescriptor(std::string* storage) { return Primitive::Descriptor(GetPrimitiveType()); } else if (IsArrayClass()) { return GetArrayDescriptor(storage); - } else if (IsProxyClass()) { - *storage = Runtime::Current()->GetClassLinker()->GetDescriptorForProxy(this); + } else if (IsAnyProxyClass()) { + *storage = Runtime::Current()->GetClassLinker()->GetDescriptorForAnyProxy(this); return storage->c_str(); } else { const DexFile& dex_file = GetDexFile(); @@ -786,8 +787,10 @@ mirror::Class* Class::GetDirectInterface(Thread* self, Handle<mirror::Class> kla DCHECK_EQ(1U, idx); return class_linker->FindSystemClass(self, "Ljava/io/Serializable;"); } - } else if (klass->IsProxyClass()) { - mirror::ObjectArray<mirror::Class>* interfaces = klass.Get()->GetInterfaces(); + } else if (klass->IsAnyProxyClass()) { + // Proxies don't have a dex cache, so look at the + // interfaces through the magic static field "interfaces" from the proxy class itself. + mirror::ObjectArray<mirror::Class>* interfaces = klass.Get()->GetInterfacesForAnyProxy(); DCHECK(interfaces != nullptr); return interfaces->Get(idx); } else { @@ -826,7 +829,7 @@ const char* Class::GetSourceFile() { std::string Class::GetLocation() { mirror::DexCache* dex_cache = GetDexCache(); - if (dex_cache != nullptr && !IsProxyClass()) { + if (dex_cache != nullptr && !IsAnyProxyClass()) { return dex_cache->GetLocation()->ToModifiedUtf8(); } // Arrays and proxies are generated and have no corresponding dex file location. @@ -944,9 +947,9 @@ Class* Class::CopyOf(Thread* self, int32_t new_length, return new_class->AsClass(); } -bool Class::ProxyDescriptorEquals(const char* match) { - DCHECK(IsProxyClass()); - return Runtime::Current()->GetClassLinker()->GetDescriptorForProxy(this) == match; +bool Class::AnyProxyDescriptorEquals(const char* match) { + DCHECK(IsAnyProxyClass()); + return Runtime::Current()->GetClassLinker()->GetDescriptorForAnyProxy(this) == match; } // TODO: Move this to java_lang_Class.cc? diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index 0ab5b97d72..fcfb4b96e8 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -352,8 +352,16 @@ class MANAGED Class FINAL : public Object { static String* ComputeName(Handle<Class> h_this) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_); + // Is this either a java.lang.reflect.Proxy or a boxed lambda (java.lang.LambdaProxy)? + // -- Most code doesn't need to make the distinction, and this is the preferred thing to check. template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> - bool IsProxyClass() SHARED_REQUIRES(Locks::mutator_lock_) { + bool IsAnyProxyClass() SHARED_REQUIRES(Locks::mutator_lock_) { + return IsReflectProxyClass() || IsLambdaProxyClass(); + } + + // Is this a java.lang.reflect.Proxy ? + template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + bool IsReflectProxyClass() SHARED_REQUIRES(Locks::mutator_lock_) { // Read access flags without using getter as whether something is a proxy can be check in // any loaded state // TODO: switch to a check if the super class is java.lang.reflect.Proxy? @@ -361,6 +369,17 @@ class MANAGED Class FINAL : public Object { return (access_flags & kAccClassIsProxy) != 0; } + // Is this a boxed lambda (java.lang.LambdaProxy)? + template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + bool IsLambdaProxyClass() SHARED_REQUIRES(Locks::mutator_lock_) { + // Read access flags without using getter as whether something is a proxy can be check in + // any loaded state + // TODO: switch to a check if the super class is java.lang.reflect.Proxy? + uint32_t access_flags = GetField32<kVerifyFlags>(OFFSET_OF_OBJECT_MEMBER(Class, access_flags_)); + return (access_flags & kAccClassIsLambdaProxy) != 0; + } + + static MemberOffset PrimitiveTypeOffset() { return OFFSET_OF_OBJECT_MEMBER(Class, primitive_type_); } @@ -677,6 +696,8 @@ class MANAGED Class FINAL : public Object { return MemberOffset(OFFSETOF_MEMBER(Class, super_class_)); } + // Returns the class's ClassLoader. + // A null value is returned if and only if this is a boot classpath class. ClassLoader* GetClassLoader() ALWAYS_INLINE SHARED_REQUIRES(Locks::mutator_lock_); void SetClassLoader(ClassLoader* new_cl) SHARED_REQUIRES(Locks::mutator_lock_); @@ -1076,6 +1097,8 @@ class MANAGED Class FINAL : public Object { bool DescriptorEquals(const char* match) SHARED_REQUIRES(Locks::mutator_lock_); + // Returns the backing DexFile's class definition for this class. + // This returns null if and only if the class has no backing DexFile. const DexFile::ClassDef* GetClassDef() SHARED_REQUIRES(Locks::mutator_lock_); ALWAYS_INLINE uint32_t NumDirectInterfaces() SHARED_REQUIRES(Locks::mutator_lock_); @@ -1102,11 +1125,15 @@ class MANAGED Class FINAL : public Object { size_t pointer_size) SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_); - // For proxy class only. - ObjectArray<Class>* GetInterfaces() SHARED_REQUIRES(Locks::mutator_lock_); + // For any proxy class only. Returns list of directly implemented interfaces. + // The value returned is always non-null. + ObjectArray<Class>* GetInterfacesForAnyProxy() SHARED_REQUIRES(Locks::mutator_lock_); - // For proxy class only. - ObjectArray<ObjectArray<Class>>* GetThrows() SHARED_REQUIRES(Locks::mutator_lock_); + // For any proxy class only. Returns a 2d array of classes. + // -- The 0th dimension correponds to the vtable index. + // -- The 1st dimension is a list of checked exception classes. + // The value returned is always non-null. + ObjectArray<ObjectArray<Class>>* GetThrowsForAnyProxy() SHARED_REQUIRES(Locks::mutator_lock_); // For reference class only. MemberOffset GetDisableIntrinsicFlagOffset() SHARED_REQUIRES(Locks::mutator_lock_); @@ -1194,7 +1221,7 @@ class MANAGED Class FINAL : public Object { IterationRange<StrideIterator<ArtField>> GetIFieldsUnchecked() SHARED_REQUIRES(Locks::mutator_lock_); - bool ProxyDescriptorEquals(const char* match) SHARED_REQUIRES(Locks::mutator_lock_); + bool AnyProxyDescriptorEquals(const char* match) SHARED_REQUIRES(Locks::mutator_lock_); // Check that the pointer size matches the one in the class linker. ALWAYS_INLINE static void CheckPointerSize(size_t pointer_size); diff --git a/runtime/mirror/class_loader-inl.h b/runtime/mirror/class_loader-inl.h index e22ddd7e90..313911706e 100644 --- a/runtime/mirror/class_loader-inl.h +++ b/runtime/mirror/class_loader-inl.h @@ -21,6 +21,7 @@ #include "base/mutex-inl.h" #include "class_table-inl.h" +#include "lambda/box_class_table-inl.h" namespace art { namespace mirror { @@ -35,6 +36,10 @@ inline void ClassLoader::VisitReferences(mirror::Class* klass, const Visitor& vi if (class_table != nullptr) { class_table->VisitRoots(visitor); } + lambda::BoxClassTable* const lambda_box_class_table = GetLambdaProxyCache(); + if (lambda_box_class_table != nullptr) { + lambda_box_class_table->VisitRoots(visitor); + } } } // namespace mirror diff --git a/runtime/mirror/class_loader.h b/runtime/mirror/class_loader.h index c2a65d62e2..9d4fe9654d 100644 --- a/runtime/mirror/class_loader.h +++ b/runtime/mirror/class_loader.h @@ -24,6 +24,12 @@ namespace art { struct ClassLoaderOffsets; class ClassTable; +namespace lambda { + +class BoxClassTable; + +} // namespace lambda + namespace mirror { class Class; @@ -60,6 +66,16 @@ class MANAGED ClassLoader : public Object { reinterpret_cast<uint64_t>(allocator)); } + lambda::BoxClassTable* GetLambdaProxyCache() SHARED_REQUIRES(Locks::mutator_lock_) { + return reinterpret_cast<lambda::BoxClassTable*>( + GetField64(OFFSET_OF_OBJECT_MEMBER(ClassLoader, lambda_proxy_cache_))); + } + + void SetLambdaProxyCache(lambda::BoxClassTable* cache) SHARED_REQUIRES(Locks::mutator_lock_) { + SetField64<false>(OFFSET_OF_OBJECT_MEMBER(ClassLoader, lambda_proxy_cache_), + reinterpret_cast<uint64_t>(cache)); + } + private: // Visit instance fields of the class loader as well as its associated classes. // Null class loader is handled by ClassLinker::VisitClassRoots. @@ -76,6 +92,7 @@ class MANAGED ClassLoader : public Object { uint32_t padding_ ATTRIBUTE_UNUSED; uint64_t allocator_; uint64_t class_table_; + uint64_t lambda_proxy_cache_; friend struct art::ClassLoaderOffsets; // for verifying offset information friend class Object; // For VisitReferences diff --git a/runtime/mirror/field-inl.h b/runtime/mirror/field-inl.h index 8a0daec4c2..49c443ef29 100644 --- a/runtime/mirror/field-inl.h +++ b/runtime/mirror/field-inl.h @@ -57,14 +57,15 @@ inline mirror::Field* Field::CreateFromArtField(Thread* self, ArtField* field, const auto pointer_size = kTransactionActive ? Runtime::Current()->GetClassLinker()->GetImagePointerSize() : sizeof(void*); auto dex_field_index = field->GetDexFieldIndex(); - auto* resolved_field = field->GetDexCache()->GetResolvedField(dex_field_index, pointer_size); - if (field->GetDeclaringClass()->IsProxyClass()) { + if (field->GetDeclaringClass()->IsAnyProxyClass()) { DCHECK(field->IsStatic()); DCHECK_LT(dex_field_index, 2U); // The two static fields (interfaces, throws) of all proxy classes // share the same dex file indices 0 and 1. So, we can't resolve // them in the dex cache. } else { + ArtField* resolved_field = + field->GetDexCache()->GetResolvedField(dex_field_index, pointer_size); if (resolved_field != nullptr) { DCHECK_EQ(resolved_field, field); } else { diff --git a/runtime/mirror/field.cc b/runtime/mirror/field.cc index ff6847cf7a..b02e5b5614 100644 --- a/runtime/mirror/field.cc +++ b/runtime/mirror/field.cc @@ -56,7 +56,7 @@ void Field::VisitRoots(RootVisitor* visitor) { ArtField* Field::GetArtField() { mirror::Class* declaring_class = GetDeclaringClass(); - if (UNLIKELY(declaring_class->IsProxyClass())) { + if (UNLIKELY(declaring_class->IsAnyProxyClass())) { DCHECK(IsStatic()); DCHECK_EQ(declaring_class->NumStaticFields(), 2U); // 0 == Class[] interfaces; 1 == Class[][] throws; diff --git a/runtime/mirror/lambda_proxy.h b/runtime/mirror/lambda_proxy.h new file mode 100644 index 0000000000..cff3a12166 --- /dev/null +++ b/runtime/mirror/lambda_proxy.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 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_MIRROR_LAMBDA_PROXY_H_ +#define ART_RUNTIME_MIRROR_LAMBDA_PROXY_H_ + +#include "lambda/closure.h" +#include "object.h" + +namespace art { + +struct LambdaProxyOffsets; + +namespace mirror { + +// C++ mirror of a lambda proxy. Does not yet have a Java-equivalent source file. +class MANAGED LambdaProxy FINAL : public Object { + public: + // Note that the runtime subclasses generate the following static fields: + + // private static java.lang.Class[] interfaces; // Declared interfaces for the lambda interface. + static constexpr size_t kStaticFieldIndexInterfaces = 0; + // private static java.lang.Class[][] throws; // Maps vtable id to list of classes. + static constexpr size_t kStaticFieldIndexThrows = 1; + static constexpr size_t kStaticFieldCount = 2; // Number of fields total. + + // The offset from the start of 'LambdaProxy' object, to the closure_ field, in bytes. + // -- This is exposed publically in order to avoid exposing 'closure_' publically. + // -- Only meant to be used in stubs and other compiled code, not in runtime. + static inline MemberOffset GetInstanceFieldOffsetClosure() { + return OFFSET_OF_OBJECT_MEMBER(LambdaProxy, closure_); + } + + // Direct methods available on the class: + static constexpr size_t kDirectMethodIndexConstructor = 0; // <init>()V + static constexpr size_t kDirectMethodCount = 1; // Only the constructor. + + // Accessors to the fields: + + // Get the native closure pointer. Usually non-null outside of lambda proxy contexts. + lambda::Closure* GetClosure() SHARED_REQUIRES(Locks::mutator_lock_) { + return reinterpret_cast<lambda::Closure*>( + GetField64(GetInstanceFieldOffsetClosure())); + } + + // Set the native closure pointer. Usually should be non-null outside of lambda proxy contexts. + void SetClosure(lambda::Closure* closure) SHARED_REQUIRES(Locks::mutator_lock_) { + SetField64<false>(GetInstanceFieldOffsetClosure(), + reinterpret_cast<uint64_t>(closure)); + } + + private: + // Instance fields, present in the base class and every generated subclass: + + // private long closure; + union { + lambda::Closure* actual; + uint64_t padding; // Don't trip up GetObjectSize checks, since the Java code has a long. + } closure_; + + // Friends for generating offset tests: + friend struct art::LambdaProxyOffsets; // for verifying offset information + + DISALLOW_IMPLICIT_CONSTRUCTORS(LambdaProxy); +}; + +} // namespace mirror +} // namespace art + +#endif // ART_RUNTIME_MIRROR_LAMBDA_PROXY_H_ diff --git a/runtime/modifiers.h b/runtime/modifiers.h index 9946eabc82..36aa57fac3 100644 --- a/runtime/modifiers.h +++ b/runtime/modifiers.h @@ -54,6 +54,8 @@ static constexpr uint32_t kAccDefault = 0x00400000; // method (run // if any particular method needs to be a default conflict. Used to figure out at runtime if // invoking this method will throw an exception. static constexpr uint32_t kAccDefaultConflict = 0x00800000; // method (runtime) +// Set by the class linker when creating a class that's a subtype of LambdaProxy. +static constexpr uint32_t kAccClassIsLambdaProxy = 0x01000000; // class (dex only) // Special runtime-only flags. // Interface and all its super-interfaces with default methods have been recursively initialized. diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc index 5e423920c0..6cebd4d34b 100644 --- a/runtime/native/java_lang_Class.cc +++ b/runtime/native/java_lang_Class.cc @@ -103,7 +103,7 @@ static jstring Class_getNameNative(JNIEnv* env, jobject javaThis) { static jobjectArray Class_getProxyInterfaces(JNIEnv* env, jobject javaThis) { ScopedFastNativeObjectAccess soa(env); mirror::Class* c = DecodeClass(soa, javaThis); - return soa.AddLocalReference<jobjectArray>(c->GetInterfaces()->Clone(soa.Self())); + return soa.AddLocalReference<jobjectArray>(c->GetInterfacesForAnyProxy()->Clone(soa.Self())); } static mirror::ObjectArray<mirror::Field>* GetDeclaredFields( @@ -489,7 +489,7 @@ static jobject Class_getDeclaredAnnotation(JNIEnv* env, jobject javaThis, jclass ScopedFastNativeObjectAccess soa(env); StackHandleScope<2> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + if (klass->IsAnyProxyClass() || klass->GetDexCache() == nullptr) { return nullptr; } Handle<mirror::Class> annotation_class(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); @@ -501,7 +501,7 @@ static jobjectArray Class_getDeclaredAnnotations(JNIEnv* env, jobject javaThis) ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + if (klass->IsAnyProxyClass() || klass->GetDexCache() == nullptr) { // Return an empty array instead of a null pointer. mirror::Class* annotation_array_class = soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_annotation_Annotation__array); @@ -517,7 +517,7 @@ static jobjectArray Class_getDeclaredClasses(JNIEnv* env, jobject javaThis) { StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); mirror::ObjectArray<mirror::Class>* classes = nullptr; - if (!klass->IsProxyClass() && klass->GetDexCache() != nullptr) { + if (!klass->IsAnyProxyClass() && klass->GetDexCache() != nullptr) { classes = klass->GetDexFile().GetDeclaredClasses(klass); } if (classes == nullptr) { @@ -543,7 +543,7 @@ static jclass Class_getEnclosingClass(JNIEnv* env, jobject javaThis) { ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + if (klass->IsAnyProxyClass() || klass->GetDexCache() == nullptr) { return nullptr; } return soa.AddLocalReference<jclass>(klass->GetDexFile().GetEnclosingClass(klass)); @@ -553,7 +553,7 @@ static jobject Class_getEnclosingConstructorNative(JNIEnv* env, jobject javaThis ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + if (klass->IsAnyProxyClass() || klass->GetDexCache() == nullptr) { return nullptr; } mirror::Object* method = klass->GetDexFile().GetEnclosingMethod(klass); @@ -570,7 +570,7 @@ static jobject Class_getEnclosingMethodNative(JNIEnv* env, jobject javaThis) { ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + if (klass->IsAnyProxyClass() || klass->GetDexCache() == nullptr) { return nullptr; } mirror::Object* method = klass->GetDexFile().GetEnclosingMethod(klass); @@ -587,7 +587,7 @@ static jint Class_getInnerClassFlags(JNIEnv* env, jobject javaThis, jint default ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + if (klass->IsAnyProxyClass() || klass->GetDexCache() == nullptr) { return defaultValue; } uint32_t flags; @@ -601,7 +601,7 @@ static jstring Class_getInnerClassName(JNIEnv* env, jobject javaThis) { ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + if (klass->IsAnyProxyClass() || klass->GetDexCache() == nullptr) { return nullptr; } mirror::String* class_name = nullptr; @@ -615,7 +615,7 @@ static jboolean Class_isAnonymousClass(JNIEnv* env, jobject javaThis) { ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + if (klass->IsAnyProxyClass() || klass->GetDexCache() == nullptr) { return false; } mirror::String* class_name = nullptr; @@ -630,7 +630,7 @@ static jboolean Class_isDeclaredAnnotationPresent(JNIEnv* env, jobject javaThis, ScopedFastNativeObjectAccess soa(env); StackHandleScope<2> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + if (klass->IsAnyProxyClass() || klass->GetDexCache() == nullptr) { return false; } Handle<mirror::Class> annotation_class(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); @@ -641,7 +641,7 @@ static jclass Class_getDeclaringClass(JNIEnv* env, jobject javaThis) { ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis))); - if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) { + if (klass->IsAnyProxyClass() || klass->GetDexCache() == nullptr) { return nullptr; } // Return null for anonymous classes. diff --git a/runtime/native/java_lang_reflect_Field.cc b/runtime/native/java_lang_reflect_Field.cc index aac800a35a..9166eccb0c 100644 --- a/runtime/native/java_lang_reflect_Field.cc +++ b/runtime/native/java_lang_reflect_Field.cc @@ -419,7 +419,7 @@ static jobject Field_getAnnotationNative(JNIEnv* env, jobject javaField, jclass ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); ArtField* field = soa.Decode<mirror::Field*>(javaField)->GetArtField(); - if (field->GetDeclaringClass()->IsProxyClass()) { + if (field->GetDeclaringClass()->IsAnyProxyClass()) { return nullptr; } Handle<mirror::Class> klass(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); @@ -429,7 +429,7 @@ static jobject Field_getAnnotationNative(JNIEnv* env, jobject javaField, jclass static jobjectArray Field_getDeclaredAnnotations(JNIEnv* env, jobject javaField) { ScopedFastNativeObjectAccess soa(env); ArtField* field = soa.Decode<mirror::Field*>(javaField)->GetArtField(); - if (field->GetDeclaringClass()->IsProxyClass()) { + if (field->GetDeclaringClass()->IsAnyProxyClass()) { // Return an empty array instead of a null pointer. mirror::Class* annotation_array_class = soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_annotation_Annotation__array); @@ -443,7 +443,7 @@ static jobjectArray Field_getDeclaredAnnotations(JNIEnv* env, jobject javaField) static jobjectArray Field_getSignatureAnnotation(JNIEnv* env, jobject javaField) { ScopedFastNativeObjectAccess soa(env); ArtField* field = soa.Decode<mirror::Field*>(javaField)->GetArtField(); - if (field->GetDeclaringClass()->IsProxyClass()) { + if (field->GetDeclaringClass()->IsAnyProxyClass()) { return nullptr; } return soa.AddLocalReference<jobjectArray>( @@ -455,7 +455,7 @@ static jboolean Field_isAnnotationPresentNative(JNIEnv* env, jobject javaField, ScopedFastNativeObjectAccess soa(env); StackHandleScope<1> hs(soa.Self()); ArtField* field = soa.Decode<mirror::Field*>(javaField)->GetArtField(); - if (field->GetDeclaringClass()->IsProxyClass()) { + if (field->GetDeclaringClass()->IsAnyProxyClass()) { return false; } Handle<mirror::Class> klass(hs.NewHandle(soa.Decode<mirror::Class*>(annotationType))); diff --git a/runtime/native/java_lang_reflect_Method.cc b/runtime/native/java_lang_reflect_Method.cc index caacba6ec3..7894c9bcfc 100644 --- a/runtime/native/java_lang_reflect_Method.cc +++ b/runtime/native/java_lang_reflect_Method.cc @@ -32,7 +32,7 @@ namespace art { static jobject Method_getAnnotationNative(JNIEnv* env, jobject javaMethod, jclass annotationType) { ScopedFastNativeObjectAccess soa(env); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - if (method->GetDeclaringClass()->IsProxyClass()) { + if (method->GetDeclaringClass()->IsAnyProxyClass()) { return nullptr; } StackHandleScope<1> hs(soa.Self()); @@ -44,7 +44,7 @@ static jobject Method_getAnnotationNative(JNIEnv* env, jobject javaMethod, jclas static jobjectArray Method_getDeclaredAnnotations(JNIEnv* env, jobject javaMethod) { ScopedFastNativeObjectAccess soa(env); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - if (method->GetDeclaringClass()->IsProxyClass()) { + if (method->GetDeclaringClass()->IsAnyProxyClass()) { // Return an empty array instead of a null pointer. mirror::Class* annotation_array_class = soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_annotation_Annotation__array); @@ -67,7 +67,7 @@ static jobject Method_getDefaultValue(JNIEnv* env, jobject javaMethod) { static jobjectArray Method_getExceptionTypes(JNIEnv* env, jobject javaMethod) { ScopedFastNativeObjectAccess soa(env); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - if (method->GetDeclaringClass()->IsProxyClass()) { + if (method->GetDeclaringClass()->IsAnyProxyClass()) { mirror::Class* klass = method->GetDeclaringClass(); int throws_index = -1; size_t i = 0; @@ -79,7 +79,8 @@ static jobjectArray Method_getExceptionTypes(JNIEnv* env, jobject javaMethod) { ++i; } CHECK_NE(throws_index, -1); - mirror::ObjectArray<mirror::Class>* declared_exceptions = klass->GetThrows()->Get(throws_index); + mirror::ObjectArray<mirror::Class>* declared_exceptions = + klass->GetThrowsForAnyProxy()->Get(throws_index); return soa.AddLocalReference<jobjectArray>(declared_exceptions->Clone(soa.Self())); } else { mirror::ObjectArray<mirror::Class>* result_array = @@ -104,7 +105,7 @@ static jobjectArray Method_getExceptionTypes(JNIEnv* env, jobject javaMethod) { static jobjectArray Method_getParameterAnnotationsNative(JNIEnv* env, jobject javaMethod) { ScopedFastNativeObjectAccess soa(env); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - if (method->GetDeclaringClass()->IsProxyClass()) { + if (method->GetDeclaringClass()->IsAnyProxyClass()) { return nullptr; } return soa.AddLocalReference<jobjectArray>(method->GetDexFile()->GetParameterAnnotations(method)); @@ -120,7 +121,7 @@ static jboolean Method_isAnnotationPresentNative(JNIEnv* env, jobject javaMethod jclass annotationType) { ScopedFastNativeObjectAccess soa(env); ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod); - if (method->GetDeclaringClass()->IsProxyClass()) { + if (method->GetDeclaringClass()->IsAnyProxyClass()) { return false; } StackHandleScope<1> hs(soa.Self()); diff --git a/runtime/native/java_lang_reflect_Proxy.cc b/runtime/native/java_lang_reflect_Proxy.cc index 4a6ab404f2..647cec0010 100644 --- a/runtime/native/java_lang_reflect_Proxy.cc +++ b/runtime/native/java_lang_reflect_Proxy.cc @@ -27,15 +27,31 @@ namespace art { static jclass Proxy_generateProxy(JNIEnv* env, jclass, jstring name, jobjectArray interfaces, - jobject loader, jobjectArray methods, jobjectArray throws) { + jobject loader, jobjectArray methods, jobjectArray throws, + jboolean is_lambda_proxy) { ScopedFastNativeObjectAccess soa(env); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); - return soa.AddLocalReference<jclass>(class_linker->CreateProxyClass( - soa, name, interfaces, loader, methods, throws)); + + mirror::Class* proxy_class = nullptr; + + if (UNLIKELY(is_lambda_proxy)) { + bool already_exists; // XX: Perhaps add lambdaProxyCache to java.lang.ClassLoader ? + proxy_class = class_linker->CreateLambdaProxyClass(soa, + name, + interfaces, + loader, + methods, + throws, + /*out*/&already_exists); + } else { + proxy_class = class_linker->CreateProxyClass(soa, name, interfaces, loader, methods, throws); + } + + return soa.AddLocalReference<jclass>(proxy_class); } static JNINativeMethod gMethods[] = { - NATIVE_METHOD(Proxy, generateProxy, "!(Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/ClassLoader;[Ljava/lang/reflect/Method;[[Ljava/lang/Class;)Ljava/lang/Class;"), + NATIVE_METHOD(Proxy, generateProxy, "!(Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/ClassLoader;[Ljava/lang/reflect/Method;[[Ljava/lang/Class;Z)Ljava/lang/Class;"), }; void register_java_lang_reflect_Proxy(JNIEnv* env) { diff --git a/runtime/proxy_test.cc b/runtime/proxy_test.cc index 57472adb64..57aafcc519 100644 --- a/runtime/proxy_test.cc +++ b/runtime/proxy_test.cc @@ -121,7 +121,7 @@ TEST_F(ProxyTest, ProxyClassHelper) { GenerateProxyClass(soa, jclass_loader, "$Proxy1234", interfaces))); interfaces.clear(); // Don't least possibly stale objects in the array as good practice. ASSERT_TRUE(proxy_class.Get() != nullptr); - ASSERT_TRUE(proxy_class->IsProxyClass()); + ASSERT_TRUE(proxy_class->IsReflectProxyClass()); ASSERT_TRUE(proxy_class->IsInitialized()); EXPECT_EQ(2U, proxy_class->NumDirectInterfaces()); // Interfaces$I and Interfaces$J. @@ -157,7 +157,7 @@ TEST_F(ProxyTest, ProxyFieldHelper) { } ASSERT_TRUE(proxyClass.Get() != nullptr); - ASSERT_TRUE(proxyClass->IsProxyClass()); + ASSERT_TRUE(proxyClass->IsReflectProxyClass()); ASSERT_TRUE(proxyClass->IsInitialized()); EXPECT_TRUE(proxyClass->GetIFieldsPtr() == nullptr); @@ -208,10 +208,10 @@ TEST_F(ProxyTest, CheckArtMirrorFieldsOfProxyStaticFields) { } ASSERT_TRUE(proxyClass0.Get() != nullptr); - ASSERT_TRUE(proxyClass0->IsProxyClass()); + ASSERT_TRUE(proxyClass0->IsReflectProxyClass()); ASSERT_TRUE(proxyClass0->IsInitialized()); ASSERT_TRUE(proxyClass1.Get() != nullptr); - ASSERT_TRUE(proxyClass1->IsProxyClass()); + ASSERT_TRUE(proxyClass1->IsReflectProxyClass()); ASSERT_TRUE(proxyClass1->IsInitialized()); LengthPrefixedArray<ArtField>* static_fields0 = proxyClass0->GetSFieldsPtr(); diff --git a/runtime/stack.cc b/runtime/stack.cc index 9098d38bb0..2ff9fd2835 100644 --- a/runtime/stack.cc +++ b/runtime/stack.cc @@ -172,12 +172,23 @@ mirror::Object* StackVisitor::GetThisObject() const { } else { return cur_shadow_frame_->GetVRegReference(0); } - } else if (m->IsProxyMethod()) { + } else if (m->IsReflectProxyMethod()) { if (cur_quick_frame_ != nullptr) { return artQuickGetProxyThisObject(cur_quick_frame_); } else { return cur_shadow_frame_->GetVRegReference(0); } + } else if (m->IsLambdaProxyMethod()) { + if (cur_quick_frame_ != nullptr) { + // XX: Should be safe to return null here, the lambda proxies + // don't set up their own quick frame because they don't need to spill any registers. + // By the time we are executing inside of the final target of the proxy invoke, + // the original 'this' reference is no longer live. + LOG(WARNING) << "Lambda proxies don't have a quick frame, do they?!"; + return nullptr; + } else { + return cur_shadow_frame_->GetVRegReference(0); + } } else { const DexFile::CodeItem* code_item = m->GetCodeItem(); if (code_item == nullptr) { @@ -814,7 +825,27 @@ QuickMethodFrameInfo StackVisitor::GetCurrentQuickFrameInfo() const { // compiled method without any stubs. Therefore the method must have a OatQuickMethodHeader. DCHECK(!method->IsDirect() && !method->IsConstructor()) << "Constructors of proxy classes must have a OatQuickMethodHeader"; - return runtime->GetCalleeSaveMethodFrameInfo(Runtime::kRefsAndArgs); + + if (method->IsReflectProxyMethod()) { + return runtime->GetCalleeSaveMethodFrameInfo(Runtime::kRefsAndArgs); + } else if (method->IsLambdaProxyMethod()) { + // Set this to true later once every stub works without a frame. + // This is currently 'false' because using a closure as a "long" + // requires a quick frame to be set up on 32-bit architectures. + constexpr bool kLambdaProxyStubsSupportFrameless = false; + if (kIsDebugBuild || !kLambdaProxyStubsSupportFrameless) { + // When debugging we always use the 'RefAndArgs' quick frame to allow us + // to see a runtime stub when unwinding. + return runtime->GetCalleeSaveMethodFrameInfo(Runtime::kRefsAndArgs); + } else { + // Lambda proxies don't bother setting up a quick frame for release builds. + LOG(FATAL) << "Requested QuickMethodFrameInfo for a lambda proxy," + << "but it doesn't have one, for method: " << PrettyMethod(method); + UNREACHABLE(); + } + } else { + LOG(FATAL) << "Unknown type of proxy method " << PrettyMethod(method); + } } // The only remaining case is if the method is native and uses the generic JNI stub. diff --git a/test/955-lambda-smali/expected.txt b/test/955-lambda-smali/expected.txt index 16381e4b46..8afe4bcca7 100644 --- a/test/955-lambda-smali/expected.txt +++ b/test/955-lambda-smali/expected.txt @@ -26,3 +26,5 @@ Caught NPE (CaptureVariables) (0-args, 1 captured variable 'D'): value is -Infinity (CaptureVariables) (0-args, 8 captured variable 'ZBCSIJFD'): value is true,R,∂,1000,12345678,3287471278325742,Infinity,-Infinity (CaptureVariables) Caught NPE +(BoxInvoke) Hello boxing world! (0-args, no closure) void +(BoxInvoke) Hello boxing world!(1-args, no closure) returned: 12345678 diff --git a/test/955-lambda-smali/smali/BoxInvoke.smali b/test/955-lambda-smali/smali/BoxInvoke.smali new file mode 100644 index 0000000000..8b53333396 --- /dev/null +++ b/test/955-lambda-smali/smali/BoxInvoke.smali @@ -0,0 +1,103 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +.class public LBoxInvoke; +.super Ljava/lang/Object; + +.method public constructor <init>()V +.registers 1 + invoke-direct {p0}, Ljava/lang/Object;-><init>()V + return-void +.end method + +.method public static run()V + .registers 0 + + invoke-static {}, LBoxInvoke;->testBoxInvoke()V + invoke-static {}, LBoxInvoke;->forceGC()V + + return-void +.end method + +# Test that invoke-virtual works on boxed innate lambdas. +.method public static testBoxInvoke()V + .registers 100 + + # Try invoking 0-arg void return lambda + create-lambda v0, LBoxInvoke;->doHelloWorld0(J)V + const-string v2, "Ljava/lang/Runnable;" + box-lambda v2, v0 # Ljava/lang/Runnable; + invoke-interface {v2}, Ljava/lang/Runnable;->run()V + + # Try invoking 1-arg int return lambda + create-lambda v3, LBoxInvoke;->doHelloWorld1(JLjava/lang/Object;)I + const-string v5, "Ljava/lang/Comparable;" + box-lambda v5, v3 # Ljava/lang/Comparable; + const-string v6, "Hello boxing world!" + invoke-interface {v5, v6}, Ljava/lang/Comparable;->compareTo(Ljava/lang/Object;)I + move-result v7 + sget-object v8, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v8, v7}, Ljava/io/PrintStream;->println(I)V + + return-void + + # TODO: more tests once box-lambda can take a type descriptor. + +.end method + +#TODO: should use a closure type instead of a long. +.method public static doHelloWorld0(J)V + .registers 4 # 1 wide parameters, 2 locals + + const-string v0, "(BoxInvoke) Hello boxing world! (0-args, no closure) void" + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V + + return-void +.end method + +#TODO: should use a closure type instead of a long. +.method public static doHelloWorld1(JLjava/lang/Object;)I + # J = closure, L = obj, I = return type + .registers 6 # 1 wide parameters, 1 narrow parameter, 3 locals + + # Prints "<before> $parameter1(Object) <after>:" without the line terminator. + + const-string v0, "(BoxInvoke) " + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + # System.out.print("<before>"); + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + # System.out.print(obj); + invoke-virtual {v1, p2}, Ljava/io/PrintStream;->print(Ljava/lang/Object;)V + + # System.out.print("<after>: "); + const-string v0, "(1-args, no closure) returned: " + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->print(Ljava/lang/String;)V + + const v2, 12345678 + return v2 +.end method + +# Force a GC. Used to ensure our weak reference table of boxed lambdas is getting swept. +.method private static forceGC()V + .registers 1 + invoke-static {}, Ljava/lang/Runtime;->getRuntime()Ljava/lang/Runtime; + move-result-object v0 + invoke-virtual {v0}, Ljava/lang/Runtime;->gc()V + + return-void +.end method diff --git a/test/955-lambda-smali/smali/BoxUnbox.smali b/test/955-lambda-smali/smali/BoxUnbox.smali index 915de2d55d..157adb359d 100644 --- a/test/955-lambda-smali/smali/BoxUnbox.smali +++ b/test/955-lambda-smali/smali/BoxUnbox.smali @@ -51,6 +51,7 @@ .registers 3 create-lambda v0, LBoxUnbox;->doHelloWorld(J)V + const-string v2, "Ljava/lang/Runnable;" box-lambda v2, v0 # v2 = box(v0) unbox-lambda v0, v2, J # v0 = unbox(v2) invoke-lambda v0, {} @@ -63,7 +64,9 @@ .registers 6 # 0 parameters, 6 locals create-lambda v0, LBoxUnbox;->doHelloWorld(J)V + const-string v2, "Ljava/lang/Runnable;" box-lambda v2, v0 # v2 = box(v0) + const-string v3, "Ljava/lang/Runnable;" box-lambda v3, v0 # v3 = box(v0) # The objects should be not-null, and they should have the same reference @@ -116,6 +119,7 @@ const v0, 0 # v0 = null const v1, 0 # v1 = null :start + const-string v2, "Ljava/lang/Runnable;" box-lambda v2, v0 # attempting to box a null lambda will throw NPE :end return-void diff --git a/test/955-lambda-smali/smali/CaptureVariables.smali b/test/955-lambda-smali/smali/CaptureVariables.smali index f18b7ff741..531c2593f7 100644 --- a/test/955-lambda-smali/smali/CaptureVariables.smali +++ b/test/955-lambda-smali/smali/CaptureVariables.smali @@ -243,6 +243,8 @@ # TODO: create-lambda should not write to both v0 and v1 invoke-lambda v0, {} + return-void + .end method #TODO: should use a closure type instead of a long diff --git a/test/955-lambda-smali/smali/Main.smali b/test/955-lambda-smali/smali/Main.smali index 9892d6124e..e8ab84c87c 100644 --- a/test/955-lambda-smali/smali/Main.smali +++ b/test/955-lambda-smali/smali/Main.smali @@ -25,6 +25,7 @@ invoke-static {}, LBoxUnbox;->run()V invoke-static {}, LMoveResult;->run()V invoke-static {}, LCaptureVariables;->run()V + invoke-static {}, LBoxInvoke;->run()V # TODO: add tests when verification fails diff --git a/test/LambdaInterfaces/LambdaInterfaces.java b/test/LambdaInterfaces/LambdaInterfaces.java new file mode 100644 index 0000000000..261163d268 --- /dev/null +++ b/test/LambdaInterfaces/LambdaInterfaces.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011 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 LambdaInterfaces { + interface I { + public int i(); + } + interface J { + public String foo = "foo"; + public void j1(); + } + interface K extends J { + } + interface L { + public int sum(int a, int b); + } + interface C { + public String concat(String a, String b); + } +} |