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/build/Android.gtest.mk b/build/Android.gtest.mk
index dcde5ab..717403f 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -29,6 +29,7 @@
   GetMethodSignature \
   Instrumentation \
   Interfaces \
+  LambdaInterfaces \
   Lookup \
   Main \
   MultiDex \
@@ -77,6 +78,7 @@
 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 @@
 
 # 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 @@
 
 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_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 0b0f094..4f4792a 100644
--- a/runtime/Android.mk
+++ b/runtime/Android.mk
@@ -106,6 +106,7 @@
   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 d6ba304..771c8b7 100644
--- a/runtime/arch/arch_test.cc
+++ b/runtime/arch/arch_test.cc
@@ -46,9 +46,15 @@
   }
 };
 
+}  // 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 631b784..588268d 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -1045,6 +1045,26 @@
     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 9ccabad..177873d 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -1582,6 +1582,28 @@
     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 0691f2a..af79f5e 100644
--- a/runtime/arch/mips/quick_entrypoints_mips.S
+++ b/runtime/arch/mips/quick_entrypoints_mips.S
@@ -1377,6 +1377,10 @@
     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 66c8aad..5e70a95 100644
--- a/runtime/arch/mips64/quick_entrypoints_mips64.S
+++ b/runtime/arch/mips64/quick_entrypoints_mips64.S
@@ -1431,6 +1431,10 @@
     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 463c9cf..4fb6119 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -1391,6 +1391,149 @@
     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 17d277e..0a54aa3 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 @@
     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 @@
     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 4166e22d..ab42d0e 100644
--- a/runtime/art_field-inl.h
+++ b/runtime/art_field-inl.h
@@ -255,7 +255,7 @@
 
 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::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 @@
 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 3737e0d..3ac563a 100644
--- a/runtime/art_field.cc
+++ b/runtime/art_field.cc
@@ -69,8 +69,8 @@
   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 a943a34..4ebe6fb 100644
--- a/runtime/art_field.h
+++ b/runtime/art_field.h
@@ -191,7 +191,9 @@
   }
 
  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 cf548ad..b6e811f 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -292,7 +292,7 @@
 }
 
 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::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::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 @@
 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 5a2d6c3..98f5aee 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -171,8 +171,16 @@
     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 @@
                                             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 @@
 
   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 b548dfb..785a9be 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 @@
             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 969f5b9..e2ade07 100644
--- a/runtime/base/allocator.h
+++ b/runtime/base/allocator.h
@@ -53,6 +53,7 @@
   kAllocatorTagClassTable,
   kAllocatorTagInternTable,
   kAllocatorTagLambdaBoxTable,
+  kAllocatorTagLambdaProxyClassBoxTable,
   kAllocatorTagMaps,
   kAllocatorTagLOS,
   kAllocatorTagSafeMap,
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index 70bd398..6ca56f5 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -65,6 +65,7 @@
 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 @@
     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 @@
     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 d4c9057..e2d7062 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -60,6 +60,7 @@
   kUnexpectedSignalLock,
   kThreadSuspendCountLock,
   kAbortLock,
+  kLambdaClassTableLock,
   kLambdaTableLock,
   kJdwpSocketLock,
   kRegionSpaceRegionLock,
@@ -692,6 +693,10 @@
   // 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 2dd2a83..8a0d8d4 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 @@
   // 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 @@
   }
   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 @@
 // 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 @@
   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 @@
                                                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()->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());
+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()->GetThrowsForAnyProxy(),
+             soa.Decode<mirror::ObjectArray<mirror::ObjectArray<mirror::Class>>*>(throws));
+  }
+  return klass.Get();
+}
+
+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 @@
 
 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 @@
   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 @@
   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 @@
     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 @@
   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 @@
            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 @@
     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 @@
     "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 29aac31..f073cd8 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -40,6 +40,11 @@
   class ImageSpace;
 }  // namespace space
 }  // namespace gc
+
+namespace lambda {
+  class BoxClassTable;
+}  // namespace lambda
+
 namespace mirror {
   class ClassLoader;
   class DexCache;
@@ -82,6 +87,7 @@
     kJavaLangReflectField,
     kJavaLangReflectMethod,
     kJavaLangReflectProxy,
+    kJavaLangLambdaProxy,
     kJavaLangStringArrayClass,
     kJavaLangReflectConstructorArrayClass,
     kJavaLangReflectFieldArrayClass,
@@ -424,12 +430,46 @@
                                   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 @@
     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 @@
 
   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 @@
 
   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 @@
   // 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 2c086c5..4a9db1d 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 @@
   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 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 @@
   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 f705a50..e84313c 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -55,7 +55,6 @@
   // 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 87e29ae..2a92226 100644
--- a/runtime/entrypoints/entrypoint_utils.cc
+++ b/runtime/entrypoints/entrypoint_utils.cc
@@ -313,7 +313,7 @@
           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 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 {
diff --git a/runtime/entrypoints/runtime_asm_entrypoints.h b/runtime/entrypoints/runtime_asm_entrypoints.h
index 2842c5a..1ef7585 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 @@
   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 da9a79e..07f0628 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -672,8 +672,8 @@
     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 9f6699f..2de8e7e 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -888,12 +888,56 @@
     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 bf95a0e..11a8c2e 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -102,6 +102,8 @@
   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 6f9f8bb..0690cd1 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 @@
   }
 }
 
+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 ea13eb7..a858bf9 100644
--- a/runtime/lambda/art_lambda_method.h
+++ b/runtime/lambda/art_lambda_method.h
@@ -90,6 +90,17 @@
     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 0000000..2fc34a7
--- /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 0000000..1e49886
--- /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 0000000..17e1026
--- /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 9918bb7..0032d08 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 @@
       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 @@
   }
 }
 
-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 @@
     //   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 @@
     // 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);
 
-  if (UNLIKELY(closure_as_array_object == nullptr)) {
+  // 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);
+
+  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 @@
     // 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 @@
 
   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 @@
 
     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 @@
 }
 
 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 adb7332..9dca6ab 100644
--- a/runtime/lambda/box_table.h
+++ b/runtime/lambda/box_table.h
@@ -30,6 +30,9 @@
 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 @@
   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 @@
                                     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 179e4ee..f935e04 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 @@
   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 31ff194..38ec063 100644
--- a/runtime/lambda/closure.h
+++ b/runtime/lambda/closure.h
@@ -33,12 +33,52 @@
 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 @@
   // 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 @@
   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 739e965..7b36042 100644
--- a/runtime/lambda/closure_builder.cc
+++ b/runtime/lambda/closure_builder.cc
@@ -75,7 +75,7 @@
   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 @@
     << "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 46ddaa9..54bb4d4 100644
--- a/runtime/lambda/shorty_field_type.h
+++ b/runtime/lambda/shorty_field_type.h
@@ -285,6 +285,39 @@
     }
   }
 
+  // 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 32bade9..430e39e 100644
--- a/runtime/lambda/shorty_field_type_test.cc
+++ b/runtime/lambda/shorty_field_type_test.cc
@@ -218,6 +218,56 @@
   }
 }  // 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 0000000..63d6ccc
--- /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 9e416dc..a8685b8 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -695,7 +695,11 @@
 }
 
 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 @@
     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 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 @@
     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 05a9039..b201293 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -538,6 +538,7 @@
 
 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 @@
     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 @@
       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 @@
 
 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 @@
   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 0ab5b97..fcfb4b9 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -352,8 +352,16 @@
   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 @@
     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 @@
     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 @@
 
   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 @@
                 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 @@
   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 e22ddd7..3139117 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 @@
   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 c2a65d6..9d4fe96 100644
--- a/runtime/mirror/class_loader.h
+++ b/runtime/mirror/class_loader.h
@@ -24,6 +24,12 @@
 struct ClassLoaderOffsets;
 class ClassTable;
 
+namespace lambda {
+
+class BoxClassTable;
+
+}  // namespace lambda
+
 namespace mirror {
 
 class Class;
@@ -60,6 +66,16 @@
                       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 @@
   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 8a0daec..49c443e 100644
--- a/runtime/mirror/field-inl.h
+++ b/runtime/mirror/field-inl.h
@@ -57,14 +57,15 @@
   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 ff6847c..b02e5b5 100644
--- a/runtime/mirror/field.cc
+++ b/runtime/mirror/field.cc
@@ -56,7 +56,7 @@
 
 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 0000000..cff3a12
--- /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 9946eab..36aa57f 100644
--- a/runtime/modifiers.h
+++ b/runtime/modifiers.h
@@ -54,6 +54,8 @@
 // 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 5e42392..6cebd4d 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -103,7 +103,7 @@
 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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 @@
   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 aac800a..9166ecc 100644
--- a/runtime/native/java_lang_reflect_Field.cc
+++ b/runtime/native/java_lang_reflect_Field.cc
@@ -419,7 +419,7 @@
   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 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_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 @@
   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 caacba6..7894c9b 100644
--- a/runtime/native/java_lang_reflect_Method.cc
+++ b/runtime/native/java_lang_reflect_Method.cc
@@ -32,7 +32,7 @@
 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 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 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 @@
       ++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_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 @@
                                                  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 4a6ab40..647cec0 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 57472ad..57aafcc 100644
--- a/runtime/proxy_test.cc
+++ b/runtime/proxy_test.cc
@@ -121,7 +121,7 @@
       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 @@
   }
 
   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 @@
   }
 
   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 9098d38..2ff9fd2 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -172,12 +172,23 @@
     } 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 @@
     // 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 16381e4..8afe4bc 100644
--- a/test/955-lambda-smali/expected.txt
+++ b/test/955-lambda-smali/expected.txt
@@ -26,3 +26,5 @@
 (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 0000000..8b53333
--- /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 915de2d..157adb3 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 f18b7ff..531c259 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 9892d61..e8ab84c 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 0000000..261163d
--- /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);
+    }
+}