lambda: Add support for invoke-interface for boxed innate lambdas
Lambda closures created with the 'create-lambda' instruction
(termed "innate lambdas") can be turned into an object with 'box-lambda'.
This CL enables support for those kinds of lambdas to work with
'invoke-interface' by generating a proxy class for the lambda.
Note: MIPS32/64 support not included.
Bug: 24618608
Bug: 25107649
Change-Id: Ic8f1bb66ebeaed4097e758a50becf1cff6ccaefb
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index abf9ac4..8c2dc3e 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 @@
// 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 @@
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 @@
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 {