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))