Stateful lambda classes: move initialization to method on lambda class.

Change-Id: Iedf546d45ad4a9f095db1f6a5e53c89ed62b135c
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 3dd9322..7d2191c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -63,6 +63,7 @@
   final LambdaDescriptor descriptor;
   final DexMethod constructor;
   final DexMethod classConstructor;
+  final DexMethod createInstanceMethod;
   final DexField instanceField;
   final Target target;
   final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
@@ -70,8 +71,11 @@
   private final Supplier<DexProgramClass> lazyDexClass =
       Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
 
-  LambdaClass(LambdaRewriter rewriter, DexType accessedFrom,
-      DexType lambdaClassType, LambdaDescriptor descriptor) {
+  LambdaClass(
+      LambdaRewriter rewriter,
+      DexType accessedFrom,
+      DexType lambdaClassType,
+      LambdaDescriptor descriptor) {
     assert rewriter != null;
     assert lambdaClassType != null;
     assert descriptor != null;
@@ -93,6 +97,13 @@
         : factory.createMethod(lambdaClassType, constructorProto, rewriter.classConstructorName);
     this.instanceField = !stateless ? null
         : factory.createField(lambdaClassType, lambdaClassType, rewriter.instanceFieldName);
+    this.createInstanceMethod =
+        stateless
+            ? null
+            : factory.createMethod(
+                lambdaClassType,
+                factory.createProto(lambdaClassType, descriptor.captures.values),
+                rewriter.createInstanceMethodName);
 
     // We have to register this new class as a subtype of object.
     rewriter.converter.appView.appInfo().registerNewType(type, factory.objectType);
@@ -124,10 +135,15 @@
     return rewriter.factory.createType(lambdaClassDescriptor.toString());
   }
 
-  final DexProgramClass getLambdaClass() {
+  final DexProgramClass getOrCreateLambdaClass() {
     return lazyDexClass.get();
   }
 
+  DexMethod getCreateInstanceMethod() {
+    assert createInstanceMethod != null;
+    return createInstanceMethod;
+  }
+
   private DexProgramClass synthesizeLambdaClass() {
     DexMethod mainMethod =
         rewriter.factory.createMethod(type, descriptor.erasedProto, descriptor.name);
@@ -179,7 +195,7 @@
       if (synthesizedFrom.add(clazz)) {
         // The lambda class may already have been synthesized, and we therefore need to update the
         // synthesized lambda class as well.
-        getLambdaClass().addSynthesizedFrom(clazz);
+        getOrCreateLambdaClass().addSynthesizedFrom(clazz);
       }
     }
   }
@@ -226,7 +242,10 @@
   // Synthesize direct methods.
   private DexEncodedMethod[] synthesizeDirectMethods() {
     boolean stateless = isStateless();
-    DexEncodedMethod[] methods = new DexEncodedMethod[stateless ? 2 : 1];
+    boolean enableStatefulLambdaCreateInstanceMethod =
+        rewriter.converter.appView.options().testing.enableStatefulLambdaCreateInstanceMethod;
+    DexEncodedMethod[] methods =
+        new DexEncodedMethod[(stateless || enableStatefulLambdaCreateInstanceMethod) ? 2 : 1];
 
     // Constructor.
     methods[0] =
@@ -252,6 +271,16 @@
               ParameterAnnotationsList.empty(),
               new SynthesizedCode(
                   callerPosition -> new LambdaClassConstructorSourceCode(this, callerPosition)));
+    } else if (enableStatefulLambdaCreateInstanceMethod) {
+      methods[1] =
+          new DexEncodedMethod(
+              createInstanceMethod,
+              MethodAccessFlags.fromSharedAccessFlags(
+                  Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC, false),
+              DexAnnotationSet.empty(),
+              ParameterAnnotationsList.empty(),
+              new SynthesizedCode(
+                  callerPosition -> new LambdaCreateInstanceSourceCode(this, callerPosition)));
     }
     return methods;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaCreateInstanceSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaCreateInstanceSourceCode.java
new file mode 100644
index 0000000..5e9fcba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaCreateInstanceSourceCode.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+
+// Source code for the static method in a stateful lambda class which creates and initializes
+// a new instance.
+final class LambdaCreateInstanceSourceCode extends SynthesizedLambdaSourceCode {
+
+  LambdaCreateInstanceSourceCode(LambdaClass lambda, Position callerPosition) {
+    super(lambda, lambda.createInstanceMethod, callerPosition, null);
+  }
+
+  @Override
+  protected void prepareInstructions() {
+    // Create and initialize an instance.
+    int instance = nextRegister(ValueType.OBJECT);
+    add(builder -> builder.addNewInstance(instance, lambda.type));
+    List<ValueType> types = new ArrayList<>(getParamCount() + 1);
+    List<Integer> registers = new ArrayList<>(getParamCount() + 1);
+    types.add(ValueType.OBJECT);
+    registers.add(instance);
+    for (int i = 0; i < getParamCount(); ++i) {
+      types.add(ValueType.fromDexType(proto.parameters.values[i]));
+      registers.add(getParamRegister(i));
+    }
+    add(
+        builder ->
+            builder.addInvoke(
+                Invoke.Type.DIRECT,
+                lambda.constructor,
+                lambda.constructor.proto,
+                types,
+                registers,
+                false /* isInterface */));
+    add(builder -> builder.addReturn(instance));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 79621b8..c08508d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
@@ -53,6 +54,7 @@
   public static final String LAMBDA_GROUP_CLASS_NAME_PREFIX = "-$$LambdaGroup$";
   static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
   static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
+  static final String LAMBDA_CREATE_INSTANCE_METHOD_NAME = "$$createInstance";
 
   private final AppView<? extends AppInfo> appView;
   final IRConverter converter;
@@ -63,6 +65,7 @@
   final DexString constructorName;
   final DexString classConstructorName;
   final DexString instanceFieldName;
+  final DexString createInstanceMethodName;
 
   final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
 
@@ -93,6 +96,7 @@
     this.objectInitMethod = factory.createMethod(factory.objectType, initProto, constructorName);
     this.classConstructorName = factory.createString(Constants.CLASS_INITIALIZER_NAME);
     this.instanceFieldName = factory.createString(LAMBDA_INSTANCE_FIELD_NAME);
+    this.createInstanceMethodName = factory.createString(LAMBDA_CREATE_INSTANCE_METHOD_NAME);
   }
 
   /**
@@ -176,7 +180,7 @@
    */
   public DexProgramClass getLambdaClass(DexType type) {
     LambdaClass lambdaClass = getKnown(knownLambdaClasses, type);
-    return lambdaClass == null ? null : lambdaClass.getLambdaClass();
+    return lambdaClass == null ? null : lambdaClass.getOrCreateLambdaClass();
   }
 
   /** Generates lambda classes and adds them to the builder. */
@@ -184,13 +188,14 @@
       throws ExecutionException {
     AppInfo appInfo = appView.appInfo();
     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
-      DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
+      DexProgramClass synthesizedClass = lambdaClass.getOrCreateLambdaClass();
       appInfo.addSynthesizedClass(synthesizedClass);
       builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
     }
     converter.optimizeSynthesizedClasses(
         knownLambdaClasses.values().stream()
-            .map(LambdaClass::getLambdaClass).collect(ImmutableSet.toImmutableSet()),
+            .map(LambdaClass::getOrCreateLambdaClass)
+            .collect(ImmutableSet.toImmutableSet()),
         executorService);
   }
 
@@ -227,8 +232,11 @@
     // We check the map twice to to minimize time spent in synchronized block.
     LambdaClass lambdaClass = getKnown(knownLambdaClasses, lambdaClassType);
     if (lambdaClass == null) {
-      lambdaClass = putIfAbsent(knownLambdaClasses, lambdaClassType,
-          new LambdaClass(this, accessedFrom, lambdaClassType, descriptor));
+      lambdaClass =
+          putIfAbsent(
+              knownLambdaClasses,
+              lambdaClassType,
+              new LambdaClass(this, accessedFrom, lambdaClassType, descriptor));
     }
     lambdaClass.addSynthesizedFrom(appView.definitionFor(accessedFrom).asProgramClass());
     if (isInMainDexList(accessedFrom)) {
@@ -286,42 +294,57 @@
       return;
     }
 
-    // For stateful lambdas we always create a new instance since we need to pass
-    // captured values to the constructor.
-    //
-    // We replace InvokeCustom instruction with a new NewInstance instruction
-    // instantiating lambda followed by InvokeDirect instruction calling a
-    // constructor on it.
-    //
-    //    original:
-    //      Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
-    //
-    //    result:
-    //      NewInstance   rResult <-  LambdaClass
-    //      Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
-    NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
-    instructions.replaceCurrentInstruction(newInstance);
+    if (!converter.appView.options().testing.enableStatefulLambdaCreateInstanceMethod) {
+      // For stateful lambdas we always create a new instance since we need to pass
+      // captured values to the constructor.
+      //
+      // We replace InvokeCustom instruction with a new NewInstance instruction
+      // instantiating lambda followed by InvokeDirect instruction calling a
+      // constructor on it.
+      //
+      //    original:
+      //      Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
+      //
+      //    result:
+      //      NewInstance   rResult <-  LambdaClass
+      //      Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
+      NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
+      instructions.replaceCurrentInstruction(newInstance);
 
-    List<Value> arguments = new ArrayList<>();
-    arguments.add(lambdaInstanceValue);
-    arguments.addAll(invoke.arguments()); // Optional captures.
-    InvokeDirect constructorCall = new InvokeDirect(
-        lambdaClass.constructor, null /* no return value */, arguments);
-    instructions.add(constructorCall);
-    constructorCall.setPosition(newInstance.getPosition());
+      List<Value> arguments = new ArrayList<>();
+      arguments.add(lambdaInstanceValue);
+      arguments.addAll(invoke.arguments()); // Optional captures.
+      InvokeDirect constructorCall =
+          new InvokeDirect(lambdaClass.constructor, null /* no return value */, arguments);
+      instructions.add(constructorCall);
+      constructorCall.setPosition(newInstance.getPosition());
 
-    // If we don't have catch handlers we are done.
-    if (!constructorCall.getBlock().hasCatchHandlers()) {
-      return;
+      // If we don't have catch handlers we are done.
+      if (!constructorCall.getBlock().hasCatchHandlers()) {
+        return;
+      }
+
+      // Move the iterator back to position it between the two instructions, split
+      // the block between the two instructions, and copy the catch handlers.
+      instructions.previous();
+      assert instructions.peekNext().isInvokeDirect();
+      BasicBlock currentBlock = newInstance.getBlock();
+      BasicBlock nextBlock = instructions.split(code, blocks);
+      assert !instructions.hasNext();
+      nextBlock.copyCatchHandlers(code, blocks, currentBlock, appView.options());
+    } else {
+      // For stateful lambdas we call the createInstance method.
+      //
+      //    original:
+      //      Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
+      //
+      //    result:
+      //      Invoke-Static rResult <- { rArg0, rArg1, ... }; method void
+      // LambdaClass.createInstance(...)
+      InvokeStatic invokeStatic =
+          new InvokeStatic(
+              lambdaClass.getCreateInstanceMethod(), lambdaInstanceValue, invoke.arguments());
+      instructions.replaceCurrentInstruction(invokeStatic);
     }
-
-    // Move the iterator back to position it between the two instructions, split
-    // the block between the two instructions, and copy the catch handlers.
-    instructions.previous();
-    assert instructions.peekNext().isInvokeDirect();
-    BasicBlock currentBlock = newInstance.getBlock();
-    BasicBlock nextBlock = instructions.split(code, blocks);
-    assert !instructions.hasNext();
-    nextBlock.copyCatchHandlers(code, blocks, currentBlock, appView.options());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 3c663fa..7cf6054 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -604,6 +604,9 @@
     public boolean disallowLoadStoreOptimization = false;
     public Consumer<IRCode> irModifier = null;
 
+    // TODO(b/129458850) When fixed, remove this and change all usages to "true".
+    public boolean enableStatefulLambdaCreateInstanceMethod = false;
+
     public MinifierTestingOptions minifier = new MinifierTestingOptions();
 
     public static class MinifierTestingOptions {
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index daab81d..a6522b1 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -369,6 +369,15 @@
   }
 
   @Test
+  public void lambdaDesugaringCreateMethod() throws Throwable {
+    test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
+        .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
+        .withKeepAll()
+        .withOptionConsumer(o -> o.testing.enableStatefulLambdaCreateInstanceMethod = true)
+        .run();
+  }
+
+  @Test
   public void lambdaDesugaringNPlus() throws Throwable {
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))