Merge "Java-style lambda class merging."
diff --git a/build.gradle b/build.gradle
index 2cf3fe1..e0c50ee 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1190,12 +1190,30 @@
def name = dir.getName()
def taskName = "jar_kotlinR8TestResources_${name}_${kotlinTargetVersion}"
def outputFile = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}.jar"
- task "${taskName}"(type: kotlin.Kotlinc) {
- source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.kt')
+ def javaOutput = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}/java"
+ def javaOutputJarName = "${name}.java.jar"
+ def javaOutputJarDir = "build/test/kotlinR8TestResources/${kotlinTargetVersion}"
+ task "${taskName}Kotlin"(type: kotlin.Kotlinc) {
+ source = fileTree(dir: file("${examplesDir}/${name}"),
+ include: ['**/*.kt', '**/*.java'])
destination = file(outputFile)
targetVersion = kotlinTargetVersion
}
- dependsOn taskName
+ task "${taskName}Java"(type: JavaCompile) {
+ source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.java')
+ destinationDir = file(javaOutput)
+ classpath = sourceSets.main.compileClasspath
+ sourceCompatibility = JavaVersion.VERSION_1_6
+ targetCompatibility = JavaVersion.VERSION_1_6
+ options.compilerArgs += ["-g", "-Xlint:-options"]
+ }
+ task "${taskName}JavaJar"(type: Jar, dependsOn: "${taskName}Java") {
+ archiveName = javaOutputJarName
+ destinationDir = file(javaOutputJarDir)
+ from javaOutput
+ include "**/*.class"
+ }
+ dependsOn "${taskName}Kotlin", "${taskName}JavaJar"
}
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 5afe490..ff3e30d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -257,10 +257,13 @@
public class ObjectMethods {
public final DexMethod getClass;
+ public final DexMethod constructor;
private ObjectMethods() {
getClass = createMethod(objectDescriptor, getClassMethodName, classDescriptor,
DexString.EMPTY_ARRAY);
+ constructor = createMethod(objectDescriptor,
+ constructorMethodName, voidType.descriptor, DexString.EMPTY_ARRAY);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 3ad0168..96b2122 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -984,7 +984,14 @@
int source = builder.getInfo(jump).getOffset();
Info targetInfo = builder.getTargetInfo(jump.getTarget());
int relativeOffset = targetInfo.getOffset() - source;
- if (size == relativeOffset) {
+ // Emit a return if the target is a return and the size of the return is the computed
+ // size of this instruction.
+ Return ret = targetInfo.getIR().asReturn();
+ if (ret != null && size == targetInfo.getSize() && ret.getPosition().isNone()) {
+ Instruction dex = ret.createDexInstruction(builder);
+ dex.setOffset(getOffset()); // for better printing of the dex code.
+ instructions.add(dex);
+ } else if (size == relativeOffset) {
// We should never generate a goto targeting the next instruction. However, if we do
// we replace it with nops. This works around a dalvik bug where the dalvik tracing
// jit crashes on 'goto next instruction' on Android 4.1.1.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
index daa7811..eb7826e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -72,7 +72,7 @@
}
// No-op strategy.
- public static final Strategy NoOp = new Strategy() {
+ static final Strategy NoOp = new Strategy() {
@Override
public LambdaGroup group() {
return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
index fea83eb..f3fecde 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -27,10 +27,14 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
// Represents a group of lambda classes which potentially can be represented
// by the same lambda _group_ class. Each lambda class inside the group is
// assigned an integer id.
+//
+// NOTE: access to lambdas in lambda group is NOT thread-safe.
public abstract class LambdaGroup {
public final LambdaGroupId id;
@@ -43,9 +47,6 @@
// Maps lambda classes belonging to the group into the index inside the
// group. Note usage of linked hash map to keep insertion ordering stable.
- //
- // WARNING: access to this map is NOT synchronized and must be performed in
- // thread-safe context.
private final Map<DexType, LambdaInfo> lambdas = new LinkedHashMap<>();
public static class LambdaInfo {
@@ -67,11 +68,28 @@
return classType;
}
- public final List<LambdaInfo> lambdas() {
- return Lists.newArrayList(lambdas.values());
+ public final int size() {
+ return lambdas.size();
}
- public final boolean shouldAddToMainDex(AppInfo appInfo) {
+ public final void forEachLambda(Consumer<LambdaInfo> action) {
+ assert verifyLambdaIds(false);
+ for (LambdaInfo info : lambdas.values()) {
+ action.accept(info);
+ }
+ }
+
+ public final boolean anyLambda(Predicate<LambdaInfo> predicate) {
+ assert verifyLambdaIds(false);
+ for (LambdaInfo info : lambdas.values()) {
+ if (predicate.test(info)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ final boolean shouldAddToMainDex(AppInfo appInfo) {
// We add the group class to main index if any of the
// lambda classes it replaces is added to main index.
for (DexType type : lambdas.keySet()) {
@@ -96,6 +114,13 @@
return lambdas.get(lambda).clazz.instanceFields();
}
+ protected final DexEncodedField lambdaSingletonField(DexType lambda) {
+ assert lambdas.containsKey(lambda);
+ DexEncodedField[] fields = lambdas.get(lambda).clazz.staticFields();
+ assert fields.length < 2;
+ return fields.length == 0 ? null : fields[0];
+ }
+
// Contains less than 2 elements?
final boolean isTrivial() {
return lambdas.size() < 2;
@@ -112,6 +137,7 @@
}
final void compact() {
+ assert verifyLambdaIds(false);
int lastUsed = -1;
int lastSeen = -1;
for (Entry<DexType, LambdaInfo> entry : lambdas.entrySet()) {
@@ -123,6 +149,7 @@
entry.getValue().id = lastUsed;
}
}
+ assert verifyLambdaIds(true);
}
public abstract Strategy getCodeStrategy();
@@ -133,15 +160,14 @@
// Package for a lambda group class to be created in.
protected abstract String getTypePackage();
- public final DexProgramClass synthesizeClass(DexItemFactory factory) {
+ protected abstract String getGroupSuffix();
+
+ final DexProgramClass synthesizeClass(DexItemFactory factory) {
assert classType == null;
+ assert verifyLambdaIds(true);
List<LambdaInfo> lambdas = Lists.newArrayList(this.lambdas.values());
classType = factory.createType(
- "L" + getTypePackage() + "-$$LambdaGroup$" + createHash(lambdas) + ";");
- // We need to register new subtype manually the newly introduced type
- // does not have 'hierarchyLevel' set, but it is actually needed during
- // synthetic class methods processing.
- factory.kotlin.functional.lambdaType.addDirectSubtype(classType);
+ "L" + getTypePackage() + "-$$LambdaGroup$" + getGroupSuffix() + createHash(lambdas) + ";");
return getBuilder(factory).synthesizeClass();
}
@@ -168,9 +194,25 @@
}
}
+ private boolean verifyLambdaIds(boolean strict) {
+ int previous = -1;
+ for (LambdaInfo info : lambdas.values()) {
+ assert strict ? (previous + 1) == info.id : previous < info.id;
+ previous = info.id;
+ }
+ return true;
+ }
+
public static class LambdaStructureError extends Exception {
+ final boolean reportable;
+
public LambdaStructureError(String cause) {
+ this(cause, true);
+ }
+
+ public LambdaStructureError(String cause, boolean reportable) {
super(cause);
+ this.reportable = reportable;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
index 5c591ab..5127632 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaInfo;
import com.android.tools.r8.origin.SynthesizedOrigin;
import java.util.List;
@@ -24,22 +23,23 @@
protected final T group;
protected final DexItemFactory factory;
protected final String origin;
- protected final List<LambdaInfo> lambdas;
protected LambdaGroupClassBuilder(T group, DexItemFactory factory, String origin) {
this.group = group;
this.factory = factory;
this.origin = origin;
- this.lambdas = group.lambdas();
}
public final DexProgramClass synthesizeClass() {
+ DexType groupClassType = group.getGroupClassType();
+ DexType superClassType = getSuperClassType();
+
return new DexProgramClass(
- group.getGroupClassType(),
+ groupClassType,
null,
new SynthesizedOrigin(origin, getClass()),
buildAccessFlags(),
- getSuperClassType(),
+ superClassType,
buildInterfaces(),
factory.createString(origin),
buildEnclosingMethodAttribute(),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 11976b6..03daf44 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -28,9 +28,8 @@
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaInfo;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
-import com.android.tools.r8.ir.optimize.lambda.kstyle.KStyleLambdaGroupIdFactory;
+import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -59,7 +58,7 @@
// (c) j-style lambda classes synthesized by kotlin compiler
//
// Lambda merging is potentially applicable to all three of them, but
-// current implementation only deals with k-style lambdas.
+// current implementation deals with both k- and j-style lambdas.
//
// In general we merge lambdas in 5 phases:
// 1. collect all lambdas and compute group candidates. we do it synchronously
@@ -151,18 +150,20 @@
.filter(cls -> !infoWithLiveness.isPinned(cls.type))
.filter(cls -> cls.hasKotlinInfo() &&
cls.getKotlinInfo().isSyntheticClass() &&
- cls.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda())
+ cls.getKotlinInfo().asSyntheticClass().isLambda())
.sorted((a, b) -> a.type.slowCompareTo(b.type)) // Ensure stable ordering.
.forEachOrdered(lambda -> {
try {
- LambdaGroupId id = KStyleLambdaGroupIdFactory.create(kotlin, lambda, options);
+ LambdaGroupId id = KotlinLambdaGroupIdFactory.create(kotlin, lambda, options);
LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup);
group.add(lambda);
lambdas.put(lambda.type, group);
} catch (LambdaStructureError error) {
- reporter.warning(
- new StringDiagnostic("Invalid Kotlin-style lambda [" +
- lambda.type.toSourceString() + "]: " + error.getMessage()));
+ if (error.reportable) {
+ reporter.warning(
+ new StringDiagnostic("Unrecognized Kotlin lambda [" +
+ lambda.type.toSourceString() + "]: " + error.getMessage()));
+ }
}
});
@@ -199,11 +200,12 @@
// Analyse more complex aspects of lambda classes including method code.
assert converter.appInfo.hasSubtyping();
- analyzeLambdaClassesStructure(converter.appInfo.withSubtyping(), executorService);
+ AppInfoWithSubtyping appInfo = converter.appInfo.withSubtyping();
+ analyzeLambdaClassesStructure(appInfo, executorService);
// Remove invalidated lambdas, compact groups to ensure
// sequential lambda ids, create group lambda classes.
- Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups();
+ Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups(appInfo);
// Switch to APPLY strategy.
this.strategyFactory = ApplyStrategy::new;
@@ -236,24 +238,25 @@
for (LambdaGroup group : groups.values()) {
ThrowingConsumer<DexClass, LambdaStructureError> validator =
group.lambdaClassValidator(kotlin, appInfo);
- for (LambdaInfo lambda : group.lambdas()) {
- futures.add(service.submit(() -> {
- try {
- validator.accept(lambda.clazz);
- } catch (LambdaStructureError error) {
- reporter.info(
- new StringDiagnostic("Unexpected Kotlin-style lambda structure [" +
- lambda.clazz.type.toSourceString() + "]: " + error.getMessage())
- );
- invalidateLambda(lambda.clazz.type);
- }
- }));
- }
+ group.forEachLambda(info ->
+ futures.add(service.submit(() -> {
+ try {
+ validator.accept(info.clazz);
+ } catch (LambdaStructureError error) {
+ if (error.reportable) {
+ reporter.info(
+ new StringDiagnostic("Unexpected Kotlin lambda structure [" +
+ info.clazz.type.toSourceString() + "]: " + error.getMessage())
+ );
+ }
+ invalidateLambda(info.clazz.type);
+ }
+ })));
}
ThreadUtils.awaitFutures(futures);
}
- private synchronized Map<LambdaGroup, DexProgramClass> finalizeLambdaGroups() {
+ private Map<LambdaGroup, DexProgramClass> finalizeLambdaGroups(AppInfoWithSubtyping appInfo) {
for (DexType lambda : invalidatedLambdas) {
LambdaGroup group = lambdas.get(lambda);
assert group != null;
@@ -270,7 +273,11 @@
for (LambdaGroup group : groups.values()) {
assert !group.isTrivial() : "No trivial group is expected here.";
group.compact();
- result.put(group, group.synthesizeClass(factory));
+ DexProgramClass lambdaGroupClass = group.synthesizeClass(factory);
+ result.put(group, lambdaGroupClass);
+
+ // We have to register this new class as a subtype of object.
+ appInfo.registerNewType(lambdaGroupClass.type, lambdaGroupClass.superType);
}
return result;
}
@@ -281,11 +288,8 @@
Entry<LambdaGroupId, LambdaGroup> group = iterator.next();
if (group.getValue().isTrivial()) {
iterator.remove();
- List<LambdaInfo> lambdas = group.getValue().lambdas();
- assert lambdas.size() < 2;
- for (LambdaInfo lambda : lambdas) {
- this.lambdas.remove(lambda.clazz.type);
- }
+ assert group.getValue().size() < 2;
+ group.getValue().forEachLambda(info -> this.lambdas.remove(info.clazz.type));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
new file mode 100644
index 0000000..a5b6793
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2018, 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import com.google.common.collect.Lists;
+import java.util.List;
+
+final class ClassInitializerSourceCode extends SyntheticSourceCode {
+ private final DexItemFactory factory;
+ private final KotlinLambdaGroup group;
+
+ ClassInitializerSourceCode(DexItemFactory factory, KotlinLambdaGroup group) {
+ super(null, factory.createProto(factory.voidType));
+ this.factory = factory;
+ this.group = group;
+ }
+
+ @Override
+ protected void prepareInstructions() {
+ DexType groupClassType = group.getGroupClassType();
+ DexMethod lambdaConstructorMethod = factory.createMethod(groupClassType,
+ factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
+
+ int instance = nextRegister(ValueType.OBJECT);
+ int lambdaId = nextRegister(ValueType.INT);
+ List<ValueType> argTypes = Lists.newArrayList(ValueType.OBJECT, ValueType.INT);
+ List<Integer> argRegisters = Lists.newArrayList(instance, lambdaId);
+
+ group.forEachLambda(info -> {
+ DexType lambda = info.clazz.type;
+ if (group.isSingletonLambda(lambda)) {
+ int id = group.lambdaId(lambda);
+ add(builder -> builder.addNewInstance(instance, groupClassType));
+ add(builder -> builder.addConst(ValueType.INT, lambdaId, id));
+ add(builder -> builder.addInvoke(Type.DIRECT,
+ lambdaConstructorMethod, lambdaConstructorMethod.proto, argTypes, argRegisters));
+ add(builder -> builder.addStaticPut(
+ instance, group.getSingletonInstanceField(factory, id)));
+ }
+ });
+
+ assert this.nextInstructionIndex() > 0 : "no single field initialized";
+ add(IRBuilder::addReturn);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
new file mode 100644
index 0000000..b4a1865
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
@@ -0,0 +1,212 @@
+// Copyright (c) 2018, 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.Lists;
+import java.util.function.IntFunction;
+
+// Represents a j-style lambda group created to combine several lambda classes
+// generated by kotlin compiler for kotlin lambda expressions passed to java receivers,
+// like:
+//
+// --- Java --------------------------------------------------------------
+// public static void acceptString(Supplier<String> s) {
+// // ...
+// }
+// -----------------------------------------------------------------------
+// acceptString({ "A" })
+// -----------------------------------------------------------------------
+//
+// Regular stateless j-style lambda class structure looks like below:
+// NOTE: stateless j-style lambdas do not always have INSTANCE field.
+//
+// -----------------------------------------------------------------------------------------------
+// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
+// Ljava/util/function/Supplier<Ljava/lang/String;>;
+// final class lambdas/LambdasKt$foo$4 implements java/util/function/Supplier {
+//
+// public synthetic bridge get()Ljava/lang/Object;
+//
+// public final get()Ljava/lang/String;
+// @Lorg/jetbrains/annotations/NotNull;() // invisible
+//
+// <init>()V
+//
+// public final static Llambdas/LambdasKt$foo$4; INSTANCE
+//
+// static <clinit>()V
+//
+// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
+// final static INNERCLASS lambdas/LambdasKt$foo$4 null null
+// }
+// -----------------------------------------------------------------------------------------------
+//
+// Regular stateful j-style lambda class structure looks like below:
+//
+// -----------------------------------------------------------------------------------------------
+// signature <T:Ljava/lang/Object;>
+// Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/String;>;
+// declaration: lambdas/LambdasKt$foo$5<T> implements java.util.function.Supplier<java.lang.String>
+// final class lambdas/LambdasKt$foo$5 implements java/util/function/Supplier {
+//
+// public synthetic bridge get()Ljava/lang/Object;
+//
+// public final get()Ljava/lang/String;
+// @Lorg/jetbrains/annotations/NotNull;() // invisible
+//
+// <init>(Ljava/lang/String;I)V
+//
+// final synthetic Ljava/lang/String; $m
+// final synthetic I $v
+//
+// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
+// final static INNERCLASS lambdas/LambdasKt$foo$5 null null
+// }
+// -----------------------------------------------------------------------------------------------
+//
+// Key j-style lambda class details:
+// - extends java.lang.Object
+// - implements *any* functional interface (Kotlin does not seem to support scenarios when
+// lambda can implement multiple interfaces).
+// - lambda class is created as an anonymous inner class
+// - lambda class carries generic signature and kotlin metadata attribute
+// - class instance fields represent captured values and have an instance constructor
+// with matching parameters initializing them (see the second class above)
+// - stateless lambda *may* be implemented as a singleton with a static field storing the
+// only instance and initialized in static class constructor (see the first class above)
+// - main lambda method usually matches an exact lambda signature and may have
+// generic signature attribute and nullability parameter annotations
+// - optional bridge method created to satisfy interface implementation and
+// forwarding call to lambda main method
+//
+final class JStyleLambdaGroup extends KotlinLambdaGroup {
+ private JStyleLambdaGroup(GroupId id) {
+ super(id);
+ }
+
+ @Override
+ protected LambdaGroupClassBuilder getBuilder(DexItemFactory factory) {
+ return new ClassBuilder(factory, "java-style lambda group");
+ }
+
+ @Override
+ public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
+ Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+ return new ClassValidator(kotlin, appInfo);
+ }
+
+ @Override
+ protected String getGroupSuffix() {
+ return "js$";
+ }
+
+ // Specialized group id.
+ final static class GroupId extends KotlinLambdaGroupId {
+ GroupId(String capture, DexType iface,
+ String pkg, String signature, DexEncodedMethod mainMethod,
+ InnerClassAttribute inner, EnclosingMethodAttribute enclosing) {
+ super(capture, iface, pkg, signature, mainMethod, inner, enclosing);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof GroupId && computeEquals((KotlinLambdaGroupId) obj);
+ }
+
+ @Override
+ String getLambdaKindDescriptor() {
+ return "Kotlin j-style lambda group";
+ }
+
+ @Override
+ public LambdaGroup createGroup() {
+ return new JStyleLambdaGroup(this);
+ }
+ }
+
+ // Specialized class validator.
+ private class ClassValidator extends KotlinLambdaClassValidator {
+ ClassValidator(Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+ super(kotlin, JStyleLambdaGroup.this, appInfo);
+ }
+
+ @Override
+ int getInstanceInitializerSize(DexEncodedField[] captures) {
+ return captures.length + 2;
+ }
+
+ @Override
+ int validateInstanceInitializerEpilogue(
+ com.android.tools.r8.code.Instruction[] instructions, int index)
+ throws LambdaStructureError {
+ if (!(instructions[index] instanceof com.android.tools.r8.code.InvokeDirect) ||
+ instructions[index].getMethod() != kotlin.factory.objectMethods.constructor) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
+ }
+ index++;
+ if (!(instructions[index] instanceof ReturnVoid)) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
+ }
+ return index + 1;
+ }
+ }
+
+ // Specialized class builder.
+ private final class ClassBuilder extends KotlinLambdaGroupClassBuilder<JStyleLambdaGroup> {
+ ClassBuilder(DexItemFactory factory, String origin) {
+ super(JStyleLambdaGroup.this, factory, origin);
+ }
+
+ @Override
+ protected DexType getSuperClassType() {
+ return factory.objectType;
+ }
+
+ @Override
+ SyntheticSourceCode createInstanceInitializerSourceCode(
+ DexType groupClassType, DexProto initializerProto) {
+ return new InstanceInitializerSourceCode(
+ factory, groupClassType, group.getLambdaIdField(factory),
+ id -> group.getCaptureField(factory, id), initializerProto);
+ }
+ }
+
+ // Specialized instance initializer code.
+ private static final class InstanceInitializerSourceCode
+ extends KotlinInstanceInitializerSourceCode {
+ private final DexMethod objectInitializer;
+
+ InstanceInitializerSourceCode(DexItemFactory factory, DexType lambdaGroupType,
+ DexField idField, IntFunction<DexField> fieldGenerator, DexProto proto) {
+ super(lambdaGroupType, idField, fieldGenerator, proto);
+ this.objectInitializer = factory.objectMethods.constructor;
+ }
+
+ @Override
+ void prepareSuperConstructorCall(int receiverRegister) {
+ add(builder -> builder.addInvoke(Type.DIRECT, objectInitializer, objectInitializer.proto,
+ Lists.newArrayList(ValueType.OBJECT), Lists.newArrayList(receiverRegister)));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
new file mode 100644
index 0000000..f906137
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2018, 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.InternalOptions;
+
+final class JStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
+ static final JStyleLambdaGroupIdFactory INSTANCE = new JStyleLambdaGroupIdFactory();
+
+ @Override
+ LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
+ throws LambdaStructureError {
+ boolean accessRelaxed = options.proguardConfiguration.isAccessModificationAllowed();
+
+ assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
+ assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
+
+ checkAccessFlags("class access flags", lambda.accessFlags,
+ PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
+
+ // Class and interface.
+ validateSuperclass(kotlin, lambda);
+ DexType iface = validateInterfaces(kotlin, lambda);
+
+ validateStaticFields(kotlin, lambda);
+ String captureSignature = validateInstanceFields(lambda, accessRelaxed);
+ validateDirectMethods(lambda);
+ DexEncodedMethod mainMethod = validateVirtualMethods(lambda);
+ String genericSignature = validateAnnotations(kotlin, lambda);
+ InnerClassAttribute innerClass = validateInnerClasses(lambda);
+
+ return new JStyleLambdaGroup.GroupId(captureSignature, iface,
+ accessRelaxed ? "" : lambda.type.getPackageDescriptor(),
+ genericSignature, mainMethod, innerClass, lambda.getEnclosingMethod());
+ }
+
+ @Override
+ void validateSuperclass(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
+ if (lambda.superType != kotlin.factory.objectType) {
+ throw new LambdaStructureError("implements " + lambda.superType.toSourceString() +
+ " instead of java.lang.Object");
+ }
+ }
+
+ @Override
+ DexType validateInterfaces(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
+ if (lambda.interfaces.size() == 0) {
+ throw new LambdaStructureError("does not implement any interfaces");
+ }
+ if (lambda.interfaces.size() > 1) {
+ throw new LambdaStructureError(
+ "implements more than one interface: " + lambda.interfaces.size());
+ }
+
+ // We don't validate that the interface is actually a functional interface,
+ // since it may be desugared, or optimized in any other way which should not
+ // affect lambda class merging.
+ return lambda.interfaces.values[0];
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
new file mode 100644
index 0000000..46c14e9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -0,0 +1,228 @@
+// Copyright (c) 2018, 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.code.Const16;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.Lists;
+import java.util.function.IntFunction;
+
+// Represents a k-style lambda group created to combine several lambda classes
+// generated by kotlin compiler for regular kotlin lambda expressions, like:
+//
+// -----------------------------------------------------------------------
+// fun foo(m: String, v: Int): () -> String {
+// val lambda: (String, Int) -> String = { s, i -> s.substring(i) }
+// return { "$m: $v" }
+// }
+// -----------------------------------------------------------------------
+//
+// Regular stateless k-style lambda class structure looks like below:
+// NOTE: stateless j-style lambdas do not always have INSTANCE field.
+//
+// -----------------------------------------------------------------------------------------------
+// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function2<
+// Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;>;
+// final class lambdas/LambdasKt$foo$lambda1$1
+// extends kotlin/jvm/internal/Lambda
+// implements kotlin/jvm/functions/Function2 {
+//
+// public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+//
+// public final invoke(Ljava/lang/String;I)Ljava/lang/String;
+// @Lorg/jetbrains/annotations/NotNull;() // invisible
+// @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
+//
+// <init>()V
+//
+// public final static Llambdas/LambdasKt$foo$lambda1$1; INSTANCE
+//
+// static <clinit>()V
+//
+// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
+// final static INNERCLASS lambdas/LambdasKt$foo$lambda1$1 null null
+// }
+// -----------------------------------------------------------------------------------------------
+//
+// Regular stateful k-style lambda class structure looks like below:
+//
+// -----------------------------------------------------------------------------------------------
+// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Ljava/lang/String;>;
+// final class lambdas/LambdasKt$foo$1
+// extends kotlin/jvm/internal/Lambda
+// implements kotlin/jvm/functions/Function0 {
+//
+// public synthetic bridge invoke()Ljava/lang/Object;
+//
+// public final invoke()Ljava/lang/String;
+// @Lorg/jetbrains/annotations/NotNull;() // invisible
+//
+// <init>(Ljava/lang/String;I)V
+//
+// final synthetic Ljava/lang/String; $m
+// final synthetic I $v
+//
+// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
+// final static INNERCLASS lambdas/LambdasKt$foo$1 null null
+// }
+// -----------------------------------------------------------------------------------------------
+//
+// Key k-style lambda class details:
+// - extends kotlin.jvm.internal.Lambda
+// - implements one of kotlin.jvm.functions.Function0..Function22, or FunctionN
+// see: https://github.com/JetBrains/kotlin/blob/master/libraries/
+// stdlib/jvm/runtime/kotlin/jvm/functions/Functions.kt
+// and: https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md
+// - lambda class is created as an anonymous inner class
+// - lambda class carries generic signature and kotlin metadata attribute
+// - class instance fields represent captured values and have an instance constructor
+// with matching parameters initializing them (see the second class above)
+// - stateless lambda *may* be implemented as a singleton with a static field storing the
+// only instance and initialized in static class constructor (see the first class above)
+// - main lambda method usually matches an exact lambda signature and may have
+// generic signature attribute and nullability parameter annotations
+// - optional bridge method created to satisfy interface implementation and
+// forwarding call to lambda main method
+//
+final class KStyleLambdaGroup extends KotlinLambdaGroup {
+ private KStyleLambdaGroup(GroupId id) {
+ super(id);
+ }
+
+ @Override
+ protected LambdaGroupClassBuilder getBuilder(DexItemFactory factory) {
+ return new ClassBuilder(factory, "kotlin-style lambda group");
+ }
+
+ @Override
+ public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
+ Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+ return new ClassValidator(kotlin, appInfo);
+ }
+
+ @Override
+ protected String getGroupSuffix() {
+ return "ks$";
+ }
+
+ // Specialized group id.
+ final static class GroupId extends KotlinLambdaGroupId {
+ GroupId(String capture, DexType iface,
+ String pkg, String signature, DexEncodedMethod mainMethod,
+ InnerClassAttribute inner, EnclosingMethodAttribute enclosing) {
+ super(capture, iface, pkg, signature, mainMethod, inner, enclosing);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof GroupId && computeEquals((KotlinLambdaGroupId) obj);
+ }
+
+ @Override
+ String getLambdaKindDescriptor() {
+ return "Kotlin k-style lambda group";
+ }
+
+ @Override
+ public LambdaGroup createGroup() {
+ return new KStyleLambdaGroup(this);
+ }
+ }
+
+ // Specialized class validator.
+ private final class ClassValidator extends KotlinLambdaClassValidator {
+ ClassValidator(Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+ super(kotlin, KStyleLambdaGroup.this, appInfo);
+ }
+
+ @Override
+ int getInstanceInitializerSize(DexEncodedField[] captures) {
+ return captures.length + 3;
+ }
+
+ @Override
+ int validateInstanceInitializerEpilogue(
+ com.android.tools.r8.code.Instruction[] instructions, int index)
+ throws LambdaStructureError {
+ if (!(instructions[index] instanceof Const4) &&
+ !(instructions[index] instanceof Const16)) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
+ }
+ index++;
+ if (!(instructions[index] instanceof com.android.tools.r8.code.InvokeDirect) ||
+ instructions[index].getMethod() != kotlin.functional.lambdaInitializerMethod) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
+ }
+ index++;
+ if (!(instructions[index] instanceof ReturnVoid)) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
+ }
+ return index + 1;
+ }
+ }
+
+ // Specialized class builder.
+ private final class ClassBuilder extends KotlinLambdaGroupClassBuilder<KStyleLambdaGroup> {
+ ClassBuilder(DexItemFactory factory, String origin) {
+ super(KStyleLambdaGroup.this, factory, origin);
+ }
+
+ @Override
+ protected DexType getSuperClassType() {
+ return factory.kotlin.functional.lambdaType;
+ }
+
+ @Override
+ SyntheticSourceCode createInstanceInitializerSourceCode(
+ DexType groupClassType, DexProto initializerProto) {
+ return new InstanceInitializerSourceCode(factory, groupClassType,
+ group.getLambdaIdField(factory), id -> group.getCaptureField(factory, id),
+ initializerProto, id.mainMethodProto.parameters.size());
+ }
+ }
+
+ // Specialized instance initializer code.
+ private final static class InstanceInitializerSourceCode
+ extends KotlinInstanceInitializerSourceCode {
+ private final int arity;
+ private final DexMethod lambdaInitializer;
+
+ InstanceInitializerSourceCode(DexItemFactory factory, DexType lambdaGroupType,
+ DexField idField, IntFunction<DexField> fieldGenerator, DexProto proto, int arity) {
+ super(lambdaGroupType, idField, fieldGenerator, proto);
+ this.arity = arity;
+ this.lambdaInitializer = factory.createMethod(factory.kotlin.functional.lambdaType,
+ factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
+ }
+
+ @Override
+ void prepareSuperConstructorCall(int receiverRegister) {
+ int arityRegister = nextRegister(ValueType.INT);
+ add(builder -> builder.addConst(ValueType.INT, arityRegister, arity));
+ add(builder -> builder.addInvoke(Type.DIRECT, lambdaInitializer, lambdaInitializer.proto,
+ Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
+ Lists.newArrayList(receiverRegister, arityRegister)));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
new file mode 100644
index 0000000..36f9ecd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2018, 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.InternalOptions;
+
+final class KStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
+ static final KotlinLambdaGroupIdFactory INSTANCE = new KStyleLambdaGroupIdFactory();
+
+ @Override
+ LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
+ throws LambdaStructureError {
+ boolean accessRelaxed = options.proguardConfiguration.isAccessModificationAllowed();
+
+ assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
+ assert lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda();
+
+ checkAccessFlags("class access flags", lambda.accessFlags,
+ PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
+
+ // Class and interface.
+ validateSuperclass(kotlin, lambda);
+ DexType iface = validateInterfaces(kotlin, lambda);
+
+ validateStaticFields(kotlin, lambda);
+ String captureSignature = validateInstanceFields(lambda, accessRelaxed);
+ validateDirectMethods(lambda);
+ DexEncodedMethod mainMethod = validateVirtualMethods(lambda);
+ String genericSignature = validateAnnotations(kotlin, lambda);
+ InnerClassAttribute innerClass = validateInnerClasses(lambda);
+
+ return new KStyleLambdaGroup.GroupId(captureSignature, iface,
+ accessRelaxed ? "" : lambda.type.getPackageDescriptor(),
+ genericSignature, mainMethod, innerClass, lambda.getEnclosingMethod());
+ }
+
+ @Override
+ void validateSuperclass(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
+ if (lambda.superType != kotlin.functional.lambdaType) {
+ throw new LambdaStructureError("implements " + lambda.superType.toSourceString() +
+ " instead of kotlin.jvm.internal.Lambda");
+ }
+ }
+
+ @Override
+ DexType validateInterfaces(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
+ if (lambda.interfaces.size() == 0) {
+ throw new LambdaStructureError("does not implement any interfaces");
+ }
+ if (lambda.interfaces.size() > 1) {
+ throw new LambdaStructureError(
+ "implements more than one interface: " + lambda.interfaces.size());
+ }
+ DexType iface = lambda.interfaces.values[0];
+ if (!kotlin.functional.isFunctionInterface(iface)) {
+ throw new LambdaStructureError("implements " + iface.toSourceString() +
+ " instead of kotlin functional interface.");
+ }
+ return iface;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinInstanceInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinInstanceInitializerSourceCode.java
new file mode 100644
index 0000000..396cdca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinInstanceInitializerSourceCode.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2018, 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import java.util.function.IntFunction;
+
+abstract class KotlinInstanceInitializerSourceCode extends SyntheticSourceCode {
+ private final DexField idField;
+ private final IntFunction<DexField> fieldGenerator;
+
+ KotlinInstanceInitializerSourceCode(DexType lambdaGroupType,
+ DexField idField, IntFunction<DexField> fieldGenerator, DexProto proto) {
+ super(lambdaGroupType, proto);
+ this.idField = idField;
+ this.fieldGenerator = fieldGenerator;
+ }
+
+ @Override
+ protected void prepareInstructions() {
+ int receiverRegister = getReceiverRegister();
+
+ // Initialize lambda id field.
+ add(builder -> builder.addInstancePut(getParamRegister(0), receiverRegister, idField));
+
+ // Initialize capture values.
+ DexType[] values = proto.parameters.values;
+ for (int i = 1; i < values.length; i++) {
+ int index = i;
+ add(builder -> builder.addInstancePut(
+ getParamRegister(index), receiverRegister, fieldGenerator.apply(index - 1)));
+ }
+
+ // Call superclass constructor.
+ prepareSuperConstructorCall(receiverRegister);
+
+ add(IRBuilder::addReturn);
+ }
+
+ abstract void prepareSuperConstructorCall(int receiverRegister);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaClassValidator.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
similarity index 67%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaClassValidator.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
index ffd0f7d..3410703 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaClassValidator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
@@ -2,11 +2,10 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
-import com.android.tools.r8.code.Const16;
-import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.Format22c;
+import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.Iput;
import com.android.tools.r8.code.IputBoolean;
import com.android.tools.r8.code.IputByte;
@@ -33,7 +32,7 @@
// Encapsulates the logic of deep-checking of the lambda class assumptions.
//
-// For k-style lambdas we only check the code of class and instance
+// For k- and j-style lambdas we only check the code of class and instance
// initializers to ensure that their code performs no unexpected actions:
//
// (a) Class initializer is only present for stateless lambdas and does
@@ -42,21 +41,33 @@
//
// (b) Instance initializers stores all captured values in proper capture
// fields and calls the super constructor passing arity to it.
-final class KStyleLambdaClassValidator implements ThrowingConsumer<DexClass, LambdaStructureError> {
- private final Kotlin kotlin;
- private final KStyleLambdaGroup group;
+abstract class KotlinLambdaClassValidator
+ implements ThrowingConsumer<DexClass, LambdaStructureError> {
+
+ static final String LAMBDA_INIT_CODE_VERIFICATION_FAILED =
+ "instance initializer code verification failed";
+ private static final String LAMBDA_CLINIT_CODE_VERIFICATION_FAILED =
+ "static initializer code verification failed";
+
+ final Kotlin kotlin;
+ private final KotlinLambdaGroup group;
private final AppInfoWithSubtyping appInfo;
- KStyleLambdaClassValidator(Kotlin kotlin, KStyleLambdaGroup group, AppInfoWithSubtyping appInfo) {
+ KotlinLambdaClassValidator(Kotlin kotlin, KotlinLambdaGroup group, AppInfoWithSubtyping appInfo) {
this.kotlin = kotlin;
this.group = group;
this.appInfo = appInfo;
}
+ // Report a structure error.
+ final LambdaStructureError structureError(String s) {
+ return new LambdaStructureError(s, false);
+ }
+
@Override
public void accept(DexClass lambda) throws LambdaStructureError {
if (!CaptureSignature.getCaptureSignature(lambda.instanceFields()).equals(group.id().capture)) {
- throw new LambdaStructureError("capture signature was modified");
+ throw structureError("capture signature was modified");
}
DexEncodedMethod classInitializer = null;
@@ -67,27 +78,27 @@
// same methods dispatching on lambda id to proper code.
if (method.isClassInitializer()) {
Code code = method.getCode();
- if (!group.isStateless()) {
- throw new LambdaStructureError("static initializer on stateful lambda");
+ if (!(group.isStateless() && group.isSingletonLambda(lambda.type))) {
+ throw structureError("static initializer on non-singleton lambda");
}
- if (classInitializer != null || code == null || !code.isDexCode() ||
- !validateStatelessLambdaClassInitializer(lambda, code.asDexCode())) {
- throw new LambdaStructureError("static initializer code verification failed");
+ if (classInitializer != null || code == null || !code.isDexCode()) {
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
+ validateStatelessLambdaClassInitializer(lambda, code.asDexCode());
classInitializer = method;
} else if (method.isInstanceInitializer()) {
Code code = method.getCode();
- if (instanceInitializer != null || code == null || !code.isDexCode() ||
- !validateInstanceInitializer(lambda, code.asDexCode())) {
- throw new LambdaStructureError("instance initializer code verification failed");
+ if (instanceInitializer != null || code == null || !code.isDexCode()) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
+ validateInstanceInitializer(lambda, code.asDexCode());
instanceInitializer = method;
}
}
- if (group.isStateless() && (classInitializer == null)) {
- throw new LambdaStructureError("missing static initializer on stateless lambda");
+ if (group.isStateless() && group.isSingletonLambda(lambda.type) && (classInitializer == null)) {
+ throw structureError("missing static initializer on singleton lambda");
}
// This check is actually not required for lambda class merging, we only have to do
@@ -101,23 +112,38 @@
"L" + group.getTypePackage() + "-$$LambdaGroup$XXXX;");
for (DexEncodedMethod method : lambda.virtualMethods()) {
if (!method.isInliningCandidate(fakeLambdaGroupType, Reason.SIMPLE, appInfo)) {
- throw new LambdaStructureError("method " + method.method.toSourceString() +
+ throw structureError("method " + method.method.toSourceString() +
" is not inline-able into lambda group class");
}
}
}
- private boolean validateInstanceInitializer(DexClass lambda, Code code) {
+ abstract int getInstanceInitializerSize(DexEncodedField[] captures);
+
+ abstract int validateInstanceInitializerEpilogue(
+ com.android.tools.r8.code.Instruction[] instructions, int index) throws LambdaStructureError;
+
+ private void validateInstanceInitializer(DexClass lambda, Code code)
+ throws LambdaStructureError {
DexEncodedField[] captures = lambda.instanceFields();
com.android.tools.r8.code.Instruction[] instructions = code.asDexCode().instructions;
int index = 0;
- if (instructions.length != (captures.length + 3)) {
- return false;
+ if (instructions.length != getInstanceInitializerSize(captures)) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
// Capture field assignments: go through captured fields in assumed order
// and ensure they are assigned values from appropriate parameters.
+ index = validateInstanceInitializerParameterMapping(captures, instructions, index);
+
+ // Check the constructor epilogue: a call to superclass constructor.
+ index = validateInstanceInitializerEpilogue(instructions, index);
+ assert index == instructions.length;
+ }
+
+ private int validateInstanceInitializerParameterMapping(DexEncodedField[] captures,
+ Instruction[] instructions, int index) throws LambdaStructureError {
int wideFieldsSeen = 0;
for (DexEncodedField field : captures) {
switch (field.field.type.toShorty()) {
@@ -125,7 +151,7 @@
if (!(instructions[index] instanceof IputBoolean) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -133,7 +159,7 @@
if (!(instructions[index] instanceof IputByte) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -141,7 +167,7 @@
if (!(instructions[index] instanceof IputShort) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -149,7 +175,7 @@
if (!(instructions[index] instanceof IputChar) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -158,7 +184,7 @@
if (!(instructions[index] instanceof Iput) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -167,7 +193,7 @@
if (!(instructions[index] instanceof IputWide) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
wideFieldsSeen++;
break;
@@ -176,7 +202,7 @@
if (!(instructions[index] instanceof IputObject) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -185,49 +211,31 @@
}
index++;
}
-
- // Epilogue.
- if (!(instructions[index] instanceof Const4) &&
- !(instructions[index] instanceof Const16)) {
- return false;
- }
- index++;
- if (!(instructions[index] instanceof com.android.tools.r8.code.InvokeDirect) ||
- instructions[index].getMethod() != kotlin.functional.lambdaInitializerMethod) {
- return false;
- }
- index++;
- if (!(instructions[index] instanceof ReturnVoid)) {
- return false;
- }
- index++;
-
- assert index == instructions.length;
- return true;
+ return index;
}
- private boolean validateStatelessLambdaClassInitializer(DexClass lambda, Code code) {
- assert group.isStateless();
+ private void validateStatelessLambdaClassInitializer(DexClass lambda, Code code)
+ throws LambdaStructureError {
+ assert group.isStateless() && group.isSingletonLambda(lambda.type);
com.android.tools.r8.code.Instruction[] instructions = code.asDexCode().instructions;
if (instructions.length != 4) {
- return false;
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
if (!(instructions[0] instanceof com.android.tools.r8.code.NewInstance) ||
((com.android.tools.r8.code.NewInstance) instructions[0]).getType() != lambda.type) {
- return false;
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
if (!(instructions[1] instanceof com.android.tools.r8.code.InvokeDirect) ||
!isLambdaInitializerMethod(lambda, instructions[1].getMethod())) {
- return false;
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
if (!(instructions[2] instanceof SputObject) ||
!isLambdaSingletonField(lambda, instructions[2].getField())) {
- return false;
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
if (!(instructions[3] instanceof ReturnVoid)) {
- return false;
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
- return true;
}
private boolean isLambdaSingletonField(DexClass lambda, DexField field) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleConstants.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaConstants.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleConstants.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaConstants.java
index 619fa67..9ac5ca7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleConstants.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaConstants.java
@@ -2,14 +2,14 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
-interface KStyleConstants {
+interface KotlinLambdaConstants {
// Default lambda class flags.
ClassAccessFlags LAMBDA_CLASS_FLAGS =
ClassAccessFlags.fromDexAccessFlags(Constants.ACC_FINAL);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroup.java
new file mode 100644
index 0000000..fc3f2bd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroup.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2018, 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
+import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
+
+// Represents a lambda group created to combine several lambda classes generated
+// by kotlin compiler for either regular kotlin lambda expressions (k-style lambdas)
+// or lambda expressions created to implement java SAM interface.
+abstract class KotlinLambdaGroup extends LambdaGroup {
+ private final Strategy strategy = new KotlinLambdaGroupCodeStrategy(this);
+
+ KotlinLambdaGroup(LambdaGroupId id) {
+ super(id);
+ }
+
+ final KotlinLambdaGroupId id() {
+ return (KotlinLambdaGroupId) id;
+ }
+
+ final boolean isStateless() {
+ return id().capture.isEmpty();
+ }
+
+ final boolean hasAnySingletons() {
+ assert isStateless();
+ return anyLambda(info -> this.isSingletonLambda(info.clazz.type));
+ }
+
+ final boolean isSingletonLambda(DexType lambda) {
+ assert isStateless();
+ return lambdaSingletonField(lambda) != null;
+ }
+
+ // Field referencing singleton instance for a lambda with specified id.
+ final DexField getSingletonInstanceField(DexItemFactory factory, int id) {
+ return factory.createField(this.getGroupClassType(),
+ this.getGroupClassType(), factory.createString("INSTANCE$" + id));
+ }
+
+ @Override
+ protected String getTypePackage() {
+ String pkg = id().pkg;
+ return pkg.isEmpty() ? "" : (pkg + "/");
+ }
+
+ final DexProto createConstructorProto(DexItemFactory factory) {
+ String capture = id().capture;
+ DexType[] newParameters = new DexType[capture.length() + 1];
+ newParameters[0] = factory.intType; // Lambda id.
+ for (int i = 0; i < capture.length(); i++) {
+ newParameters[i + 1] = CaptureSignature.fieldType(factory, capture, i);
+ }
+ return factory.createProto(factory.voidType, newParameters);
+ }
+
+ final DexField getLambdaIdField(DexItemFactory factory) {
+ return factory.createField(this.getGroupClassType(), factory.intType, "$id$");
+ }
+
+ final int mapFieldIntoCaptureIndex(DexType lambda, DexField field) {
+ return CaptureSignature.mapFieldIntoCaptureIndex(
+ id().capture, lambdaCaptureFields(lambda), field);
+ }
+
+ final DexField getCaptureField(DexItemFactory factory, int index) {
+ assert index >= 0 && index < id().capture.length();
+ return factory.createField(this.getGroupClassType(),
+ CaptureSignature.fieldType(factory, id().capture, index), "$capture$" + index);
+ }
+
+ @Override
+ public Strategy getCodeStrategy() {
+ return strategy;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
similarity index 76%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupClassBuilder.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index 59de393..d441a00 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -2,7 +2,7 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotation;
@@ -19,9 +19,9 @@
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaInfo;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
@@ -30,21 +30,19 @@
import java.util.Map;
import java.util.Map.Entry;
-// Builds components of k-style lambda group class.
-final class KStyleLambdaGroupClassBuilder
- extends LambdaGroupClassBuilder<KStyleLambdaGroup> implements KStyleConstants {
+// Builds components of kotlin lambda group class.
+abstract class KotlinLambdaGroupClassBuilder<T extends KotlinLambdaGroup>
+ extends LambdaGroupClassBuilder<T> implements KotlinLambdaConstants {
- private final KStyleLambdaGroupId id;
+ final KotlinLambdaGroupId id;
- KStyleLambdaGroupClassBuilder(DexItemFactory factory, KStyleLambdaGroup group, String origin) {
+ KotlinLambdaGroupClassBuilder(T group, DexItemFactory factory, String origin) {
super(group, factory, origin);
this.id = group.id();
}
- @Override
- protected DexType getSuperClassType() {
- return factory.kotlin.functional.lambdaType;
- }
+ abstract SyntheticSourceCode createInstanceInitializerSourceCode(
+ DexType groupClassType, DexProto initializerProto);
// Always generate public final classes.
@Override
@@ -61,8 +59,7 @@
// Take the attribute from the group, if exists.
@Override
protected List<InnerClassAttribute> buildInnerClasses() {
- return id.innerClassAccess == KStyleLambdaGroupId.MISSING_INNER_CLASS_ATTRIBUTE
- ? Collections.emptyList()
+ return !id.hasInnerClassAttribute() ? Collections.emptyList()
: Lists.newArrayList(new InnerClassAttribute(
id.innerClassAccess, group.getGroupClassType(), null, null));
}
@@ -118,7 +115,7 @@
isMainMethod ? id.mainMethodAnnotations : DexAnnotationSet.empty(),
isMainMethod ? id.mainMethodParamAnnotations : DexAnnotationSetRefList.empty(),
new SynthesizedCode(
- new VirtualMethodSourceCode(factory, group.getGroupClassType(),
+ new KotlinLambdaVirtualMethodSourceCode(factory, group.getGroupClassType(),
methodProto, group.getLambdaIdField(factory), implMethods))));
}
}
@@ -132,37 +129,30 @@
// fact that corresponding lambda does not have a virtual method with this signature.
private Map<DexString, Map<DexProto, List<DexEncodedMethod>>> collectVirtualMethods() {
Map<DexString, Map<DexProto, List<DexEncodedMethod>>> methods = new LinkedHashMap<>();
- assert lambdaIdsOrdered();
- for (LambdaInfo lambda : lambdas) {
- for (DexEncodedMethod method : lambda.clazz.virtualMethods()) {
+ int size = group.size();
+ group.forEachLambda(info -> {
+ for (DexEncodedMethod method : info.clazz.virtualMethods()) {
List<DexEncodedMethod> list = methods
.computeIfAbsent(method.method.name,
k -> new LinkedHashMap<>())
.computeIfAbsent(method.method.proto,
- k -> Lists.newArrayList(Collections.nCopies(lambdas.size(), null)));
- assert list.get(lambda.id) == null;
- list.set(lambda.id, method);
+ k -> Lists.newArrayList(Collections.nCopies(size, null)));
+ assert list.get(info.id) == null;
+ list.set(info.id, method);
}
- }
+ });
return methods;
}
- private boolean lambdaIdsOrdered() {
- for (int i = 0; i < lambdas.size(); i++) {
- assert lambdas.get(i).id == i;
- }
- return true;
- }
-
@Override
protected DexEncodedMethod[] buildDirectMethods() {
// We only build an instance initializer and optional class
// initializer for stateless lambdas.
- boolean statelessLambda = group.isStateless();
+ boolean needsSingletonInstances = group.isStateless() && group.hasAnySingletons();
DexType groupClassType = group.getGroupClassType();
- DexEncodedMethod[] result = new DexEncodedMethod[statelessLambda ? 2 : 1];
+ DexEncodedMethod[] result = new DexEncodedMethod[needsSingletonInstances ? 2 : 1];
// Instance initializer mapping parameters into capture fields.
DexProto initializerProto = group.createConstructorProto(factory);
result[0] = new DexEncodedMethod(
@@ -170,13 +160,10 @@
CONSTRUCTOR_FLAGS_RELAXED, // always create access-relaxed constructor.
DexAnnotationSet.empty(),
DexAnnotationSetRefList.empty(),
- new SynthesizedCode(
- new InstanceInitializerSourceCode(factory, groupClassType,
- group.getLambdaIdField(factory), id -> group.getCaptureField(factory, id),
- initializerProto, id.mainMethodProto.parameters.size())));
+ new SynthesizedCode(createInstanceInitializerSourceCode(groupClassType, initializerProto)));
// Static class initializer for stateless lambdas.
- if (statelessLambda) {
+ if (needsSingletonInstances) {
result[1] = new DexEncodedMethod(
factory.createMethod(groupClassType,
factory.createProto(factory.voidType),
@@ -184,10 +171,7 @@
CLASS_INITIALIZER_FLAGS,
DexAnnotationSet.empty(),
DexAnnotationSetRefList.empty(),
- new SynthesizedCode(
- new ClassInitializerSourceCode(
- factory, groupClassType, lambdas.size(),
- id -> group.getSingletonInstanceField(factory, id))));
+ new SynthesizedCode(new ClassInitializerSourceCode(factory, group)));
}
return result;
@@ -216,15 +200,16 @@
if (!group.isStateless()) {
return DexEncodedField.EMPTY_ARRAY;
}
-
- // One field for each stateless lambda in the group.
- int size = lambdas.size();
- DexEncodedField[] result = new DexEncodedField[size];
- for (int id = 0; id < size; id++) {
- result[id] = new DexEncodedField(group.getSingletonInstanceField(factory, id),
- SINGLETON_FIELD_FLAGS, DexAnnotationSet.empty(), DexValueNull.NULL);
- }
- return result;
+ // One field for each singleton lambda in the group.
+ List<DexEncodedField> result = new ArrayList<>(group.size());
+ group.forEachLambda(info -> {
+ if (group.isSingletonLambda(info.clazz.type)) {
+ result.add(new DexEncodedField(group.getSingletonInstanceField(factory, info.id),
+ SINGLETON_FIELD_FLAGS, DexAnnotationSet.empty(), DexValueNull.NULL));
+ }
+ });
+ assert result.isEmpty() == !group.hasAnySingletons();
+ return result.toArray(new DexEncodedField[result.size()]);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupCodeStrategy.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index dd066b6..d42ecf6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -2,7 +2,7 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexField;
@@ -30,11 +30,11 @@
import java.util.ArrayList;
import java.util.List;
-// Defines the code processing strategy for k-style lambdas.
-final class KStyleLambdaGroupCodeStrategy implements Strategy {
- private final KStyleLambdaGroup group;
+// Defines the code processing strategy for kotlin lambdas.
+final class KotlinLambdaGroupCodeStrategy implements Strategy {
+ private final KotlinLambdaGroup group;
- KStyleLambdaGroupCodeStrategy(KStyleLambdaGroup group) {
+ KotlinLambdaGroupCodeStrategy(KotlinLambdaGroup group) {
this.group = group;
}
@@ -83,7 +83,7 @@
@Override
public boolean isValidNewInstance(CodeProcessor context, NewInstance invoke) {
// Only valid for stateful lambdas.
- return !group.isStateless();
+ return !(group.isStateless() && group.isSingletonLambda(invoke.clazz));
}
@Override
@@ -95,9 +95,10 @@
DexMethod method = invoke.getInvokedMethod();
DexType lambda = method.holder;
assert group.containsLambda(lambda);
- // Allow calls to a constructor from other classes if the lambda is stateful,
+ // Allow calls to a constructor from other classes if the lambda is singleton,
// otherwise allow such a call only from the same class static initializer.
- return (group.isStateless() == (context.method.method.holder == lambda)) &&
+ boolean isSingletonLambda = group.isStateless() && group.isSingletonLambda(lambda);
+ return (isSingletonLambda == (context.method.method.holder == lambda)) &&
invoke.isInvokeDirect() &&
context.factory.isConstructor(method) &&
CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupId.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
similarity index 89%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupId.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
index 6b6f73b..0540bef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupId.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
@@ -2,7 +2,7 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexAnnotationSetRefList;
@@ -15,8 +15,8 @@
import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
-final class KStyleLambdaGroupId implements LambdaGroupId {
- public static final int MISSING_INNER_CLASS_ATTRIBUTE = -1;
+abstract class KotlinLambdaGroupId implements LambdaGroupId {
+ private static final int MISSING_INNER_CLASS_ATTRIBUTE = -1;
private final int hash;
@@ -50,7 +50,7 @@
// access from InnerClassAttribute.
final int innerClassAccess;
- KStyleLambdaGroupId(String capture, DexType iface, String pkg, String signature,
+ KotlinLambdaGroupId(String capture, DexType iface, String pkg, String signature,
DexEncodedMethod mainMethod, InnerClassAttribute inner, EnclosingMethodAttribute enclosing) {
assert capture != null && iface != null && pkg != null && mainMethod != null;
assert inner == null || (inner.isAnonymous() && inner.getOuter() == null);
@@ -67,8 +67,12 @@
this.hash = computeHashCode();
}
+ final boolean hasInnerClassAttribute() {
+ return innerClassAccess != MISSING_INNER_CLASS_ATTRIBUTE;
+ }
+
@Override
- public int hashCode() {
+ public final int hashCode() {
return hash;
}
@@ -87,11 +91,9 @@
}
@Override
- public boolean equals(Object obj) {
- if (!(obj instanceof KStyleLambdaGroupId)) {
- return false;
- }
- KStyleLambdaGroupId other = (KStyleLambdaGroupId) obj;
+ public abstract boolean equals(Object obj);
+
+ boolean computeEquals(KotlinLambdaGroupId other) {
return capture.equals(other.capture) &&
iface == other.iface &&
pkg.equals(other.pkg) &&
@@ -108,7 +110,7 @@
@Override
public String toString() {
- StringBuilder builder = new StringBuilder("Kotlin-style lambda group")
+ StringBuilder builder = new StringBuilder(getLambdaKindDescriptor())
.append("\n capture: ").append(capture)
.append("\n interface: ").append(iface.descriptor)
.append("\n package: ").append(pkg)
@@ -131,8 +133,8 @@
return builder.toString();
}
+ abstract String getLambdaKindDescriptor();
+
@Override
- public LambdaGroup createGroup() {
- return new KStyleLambdaGroup(this);
- }
+ public abstract LambdaGroup createGroup();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
similarity index 71%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupIdFactory.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index 7a516cd..f5698aa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -2,7 +2,7 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessFlags;
@@ -19,8 +19,8 @@
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
-public final class KStyleLambdaGroupIdFactory implements KStyleConstants {
- private KStyleLambdaGroupIdFactory() {
+public abstract class KotlinLambdaGroupIdFactory implements KotlinLambdaConstants {
+ KotlinLambdaGroupIdFactory() {
}
// Creates a lambda group id for kotlin style lambda. Should never return null, if the lambda
@@ -32,26 +32,24 @@
// they may not be converted yet, we'll do that in KStyleLambdaClassValidator.
public static LambdaGroupId create(Kotlin kotlin, DexClass lambda, InternalOptions options)
throws LambdaStructureError {
- boolean accessRelaxed = options.proguardConfiguration.isAccessModificationAllowed();
- checkAccessFlags("class access flags", lambda.accessFlags,
- PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
+ assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
+ if (lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda()) {
+ return KStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(kotlin, lambda, options);
+ }
- validateStaticFields(kotlin, lambda);
- String captureSignature = validateInstanceFields(lambda, accessRelaxed);
- validateDirectMethods(lambda);
- DexEncodedMethod mainMethod = validateVirtualMethods(lambda);
- DexType iface = validateInterfaces(kotlin, lambda);
- String genericSignature = validateAnnotations(kotlin, lambda);
- InnerClassAttribute innerClass = validateInnerClasses(lambda);
-
- return new KStyleLambdaGroupId(captureSignature, iface,
- accessRelaxed ? "" : lambda.type.getPackageDescriptor(),
- genericSignature, mainMethod, innerClass, lambda.getEnclosingMethod());
+ assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
+ return JStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(kotlin, lambda, options);
}
- private static DexEncodedMethod validateVirtualMethods(DexClass lambda)
- throws LambdaStructureError {
+ abstract LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
+ throws LambdaStructureError;
+
+ abstract void validateSuperclass(Kotlin kotlin, DexClass lambda) throws LambdaStructureError;
+
+ abstract DexType validateInterfaces(Kotlin kotlin, DexClass lambda) throws LambdaStructureError;
+
+ DexEncodedMethod validateVirtualMethods(DexClass lambda) throws LambdaStructureError {
DexEncodedMethod mainMethod = null;
for (DexEncodedMethod method : lambda.virtualMethods()) {
@@ -68,31 +66,28 @@
}
if (mainMethod == null) {
- throw new LambdaStructureError("no main method found");
+ // Missing main method may be a result of tree shaking.
+ throw new LambdaStructureError("no main method found", false);
}
return mainMethod;
}
- private static InnerClassAttribute validateInnerClasses(DexClass lambda)
- throws LambdaStructureError {
+ InnerClassAttribute validateInnerClasses(DexClass lambda) throws LambdaStructureError {
List<InnerClassAttribute> innerClasses = lambda.getInnerClasses();
- InnerClassAttribute innerClass = null;
if (innerClasses != null) {
for (InnerClassAttribute inner : innerClasses) {
if (inner.getInner() == lambda.type) {
- innerClass = inner;
- if (!innerClass.isAnonymous()) {
+ if (!inner.isAnonymous()) {
throw new LambdaStructureError("is not anonymous");
}
- return innerClass;
+ return inner;
}
}
}
return null;
}
- private static String validateAnnotations(Kotlin kotlin, DexClass lambda)
- throws LambdaStructureError {
+ String validateAnnotations(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
String signature = null;
if (!lambda.annotations.isEmpty()) {
for (DexAnnotation annotation : lambda.annotations.annotations) {
@@ -115,8 +110,7 @@
return signature;
}
- private static void validateStaticFields(Kotlin kotlin, DexClass lambda)
- throws LambdaStructureError {
+ void validateStaticFields(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
DexEncodedField[] staticFields = lambda.staticFields();
if (staticFields.length == 1) {
DexEncodedField field = staticFields[0];
@@ -135,30 +129,10 @@
} else if (staticFields.length > 1) {
throw new LambdaStructureError(
"only one static field max expected, found " + staticFields.length);
-
- } else if (lambda.instanceFields().length == 0) {
- throw new LambdaStructureError("stateless lambda without INSTANCE field");
}
}
- private static DexType validateInterfaces(Kotlin kotlin, DexClass lambda)
- throws LambdaStructureError {
- if (lambda.interfaces.size() == 0) {
- throw new LambdaStructureError("does not implement any interfaces");
- }
- if (lambda.interfaces.size() > 1) {
- throw new LambdaStructureError(
- "implements more than one interface: " + lambda.interfaces.size());
- }
- DexType iface = lambda.interfaces.values[0];
- if (!kotlin.functional.isFunctionInterface(iface)) {
- throw new LambdaStructureError("implements " + iface.toSourceString() +
- " instead of kotlin functional interface.");
- }
- return iface;
- }
-
- private static String validateInstanceFields(DexClass lambda, boolean accessRelaxed)
+ String validateInstanceFields(DexClass lambda, boolean accessRelaxed)
throws LambdaStructureError {
DexEncodedField[] instanceFields = lambda.instanceFields();
for (DexEncodedField field : instanceFields) {
@@ -169,7 +143,7 @@
return CaptureSignature.getCaptureSignature(instanceFields);
}
- private static void validateDirectMethods(DexClass lambda) throws LambdaStructureError {
+ void validateDirectMethods(DexClass lambda) throws LambdaStructureError {
DexEncodedMethod[] directMethods = lambda.directMethods();
for (DexEncodedMethod method : directMethods) {
if (method.isClassInitializer()) {
@@ -194,8 +168,12 @@
throw new LambdaStructureError("constructor parameters don't match captured values.");
}
for (int i = 0; i < parameters.length; i++) {
+ // Kotlin compiler sometimes reshuffles the parameters so that their order
+ // in the constructor don't match order of capture fields. We could add
+ // support for it, but it happens quite rarely so don't bother for now.
if (parameters[i] != instanceFields[i].field.type) {
- throw new LambdaStructureError("constructor parameters don't match captured values.");
+ throw new LambdaStructureError(
+ "constructor parameters don't match captured values.", false);
}
}
checkAccessFlags("unexpected constructor access flags",
@@ -208,8 +186,7 @@
}
}
- private static void checkDirectMethodAnnotations(DexEncodedMethod method)
- throws LambdaStructureError {
+ void checkDirectMethodAnnotations(DexEncodedMethod method) throws LambdaStructureError {
if (!method.annotations.isEmpty()) {
throw new LambdaStructureError("unexpected method annotations [" +
method.annotations.toSmaliString() + "] on " + method.method.toSourceString());
@@ -228,7 +205,7 @@
}
@SafeVarargs
- private static <T extends AccessFlags> void checkAccessFlags(
+ static <T extends AccessFlags> void checkAccessFlags(
String message, T actual, T... expected) throws LambdaStructureError {
for (T flag : expected) {
if (flag.equals(actual)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/VirtualMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/VirtualMethodSourceCode.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
index 8921299..1bcc2c7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/VirtualMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
@@ -2,7 +2,7 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -17,12 +17,12 @@
import java.util.ArrayList;
import java.util.List;
-final class VirtualMethodSourceCode extends SyntheticSourceCode {
+final class KotlinLambdaVirtualMethodSourceCode extends SyntheticSourceCode {
private final DexItemFactory factory;
private final DexField idField;
private final List<DexEncodedMethod> implMethods;
- VirtualMethodSourceCode(DexItemFactory factory, DexType groupClass,
+ KotlinLambdaVirtualMethodSourceCode(DexItemFactory factory, DexType groupClass,
DexProto proto, DexField idField, List<DexEncodedMethod> implMethods) {
super(groupClass, proto);
this.factory = factory;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/ClassInitializerSourceCode.java
deleted file mode 100644
index 1cc0dcd..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/ClassInitializerSourceCode.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) 2018, 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.optimize.lambda.kstyle;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-import com.google.common.collect.Lists;
-import java.util.List;
-import java.util.function.IntFunction;
-
-final class ClassInitializerSourceCode extends SyntheticSourceCode {
- private final DexType lambdaGroupType;
- private final DexMethod lambdaConstructorMethod;
- private final int count;
- private final IntFunction<DexField> fieldGenerator;
-
- ClassInitializerSourceCode(DexItemFactory factory,
- DexType lambdaGroupType, int count, IntFunction<DexField> fieldGenerator) {
- super(null, factory.createProto(factory.voidType));
- this.lambdaGroupType = lambdaGroupType;
- this.count = count;
- this.fieldGenerator = fieldGenerator;
- this.lambdaConstructorMethod = factory.createMethod(lambdaGroupType,
- factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
- }
-
- @Override
- protected void prepareInstructions() {
- int instance = nextRegister(ValueType.OBJECT);
- int lambdaId = nextRegister(ValueType.INT);
- List<ValueType> argValues = Lists.newArrayList(ValueType.OBJECT, ValueType.INT);
- List<Integer> argRegisters = Lists.newArrayList(instance, lambdaId);
-
- for (int id = 0; id < count; id++) {
- int finalId = id;
- add(builder -> builder.addNewInstance(instance, lambdaGroupType));
- add(builder -> builder.addConst(ValueType.INT, lambdaId, finalId));
- add(builder -> builder.addInvoke(Type.DIRECT,
- lambdaConstructorMethod, lambdaConstructorMethod.proto, argValues, argRegisters));
- add(builder -> builder.addStaticPut(instance, fieldGenerator.apply(finalId)));
- }
-
- add(IRBuilder::addReturn);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/InstanceInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/InstanceInitializerSourceCode.java
deleted file mode 100644
index 00571be..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/InstanceInitializerSourceCode.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright (c) 2018, 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.optimize.lambda.kstyle;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-import com.google.common.collect.Lists;
-import java.util.function.IntFunction;
-
-final class InstanceInitializerSourceCode extends SyntheticSourceCode {
- private final DexField idField;
- private final IntFunction<DexField> fieldGenerator;
- private final int arity;
- private final DexMethod lambdaInitializer;
-
- InstanceInitializerSourceCode(DexItemFactory factory, DexType lambdaGroupType,
- DexField idField, IntFunction<DexField> fieldGenerator, DexProto proto, int arity) {
- super(lambdaGroupType, proto);
- this.idField = idField;
- this.fieldGenerator = fieldGenerator;
- this.arity = arity;
- this.lambdaInitializer = factory.createMethod(factory.kotlin.functional.lambdaType,
- factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
- }
-
- @Override
- protected void prepareInstructions() {
- int receiverRegister = getReceiverRegister();
-
- add(builder -> builder.addInstancePut(getParamRegister(0), receiverRegister, idField));
-
- DexType[] values = proto.parameters.values;
- for (int i = 1; i < values.length; i++) {
- int index = i;
- add(builder -> builder.addInstancePut(
- getParamRegister(index), receiverRegister, fieldGenerator.apply(index - 1)));
- }
-
- int arityRegister = nextRegister(ValueType.INT);
- add(builder -> builder.addConst(ValueType.INT, arityRegister, arity));
- add(builder -> builder.addInvoke(Type.DIRECT, lambdaInitializer, lambdaInitializer.proto,
- Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
- Lists.newArrayList(receiverRegister, arityRegister)));
- add(IRBuilder::addReturn);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroup.java
deleted file mode 100644
index 54f1d3a..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroup.java
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright (c) 2018, 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.optimize.lambda.kstyle;
-
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
-import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.utils.ThrowingConsumer;
-
-// Represents a k-style lambda group created to combine several lambda classes
-// generated by kotlin compiler for regular kotlin lambda expressions, like:
-//
-// -----------------------------------------------------------------------
-// fun foo(m: String, v: Int): () -> String {
-// val lambda: (String, Int) -> String = { s, i -> s.substring(i) }
-// return { "$m: $v" }
-// }
-// -----------------------------------------------------------------------
-//
-// Regular stateless k-style lambda class structure looks like below:
-//
-// -----------------------------------------------------------------------------------------------
-// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function2<
-// Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;>;
-// final class lambdas/LambdasKt$foo$lambda1$1
-// extends kotlin/jvm/internal/Lambda
-// implements kotlin/jvm/functions/Function2 {
-//
-// public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-//
-// public final invoke(Ljava/lang/String;I)Ljava/lang/String;
-// @Lorg/jetbrains/annotations/NotNull;() // invisible
-// @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
-//
-// <init>()V
-//
-// public final static Llambdas/LambdasKt$foo$lambda1$1; INSTANCE
-//
-// static <clinit>()V
-//
-// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
-// final static INNERCLASS lambdas/LambdasKt$foo$lambda1$1 null null
-// }
-// -----------------------------------------------------------------------------------------------
-//
-// Regular stateful k-style lambda class structure looks like below:
-//
-// -----------------------------------------------------------------------------------------------
-// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Ljava/lang/String;>;
-// final class lambdas/LambdasKt$foo$1
-// extends kotlin/jvm/internal/Lambda
-// implements kotlin/jvm/functions/Function0 {
-//
-// public synthetic bridge invoke()Ljava/lang/Object;
-//
-// public final invoke()Ljava/lang/String;
-// @Lorg/jetbrains/annotations/NotNull;() // invisible
-//
-// <init>(Ljava/lang/String;I)V
-//
-// final synthetic Ljava/lang/String; $m
-// final synthetic I $v
-//
-// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
-// final static INNERCLASS lambdas/LambdasKt$foo$1 null null
-// }
-// -----------------------------------------------------------------------------------------------
-//
-// Key k-style lambda class details:
-// - extends kotlin.jvm.internal.Lambda
-// - implements one of kotlin.jvm.functions.Function0..Function22, or FunctionN
-// see: https://github.com/JetBrains/kotlin/blob/master/libraries/
-// stdlib/jvm/runtime/kotlin/jvm/functions/Functions.kt
-// and: https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md
-// - lambda class is created as an anonymous inner class
-// - lambda class carries generic signature and kotlin metadata attribute
-// - class instance fields represent captured values and have an instance constructor
-// with matching parameters initializing them (see the second class above)
-// - stateless lambda is implemented as a singleton with a static field storing the only
-// instance and initialized in static class constructor (see the first class above)
-// - main lambda method usually matches an exact lambda signature and may have
-// generic signature attribute and nullability parameter annotations
-// - optional bridge method created to satisfy interface implementation and
-// forwarding call to lambda main method
-//
-final class KStyleLambdaGroup extends LambdaGroup {
- private final Strategy strategy = new KStyleLambdaGroupCodeStrategy(this);
-
- KStyleLambdaGroup(KStyleLambdaGroupId id) {
- super(id);
- }
-
- final KStyleLambdaGroupId id() {
- return (KStyleLambdaGroupId) id;
- }
-
- final boolean isStateless() {
- return id().capture.isEmpty();
- }
-
- // Field referencing singleton instance for a lambda with specified id.
- final DexField getSingletonInstanceField(DexItemFactory factory, int id) {
- return factory.createField(this.getGroupClassType(),
- this.getGroupClassType(), factory.createString("INSTANCE$" + id));
- }
-
- @Override
- protected String getTypePackage() {
- String pkg = id().pkg;
- return pkg.isEmpty() ? "" : (pkg + "/");
- }
-
- final DexProto createConstructorProto(DexItemFactory factory) {
- String capture = id().capture;
- DexType[] newParameters = new DexType[capture.length() + 1];
- newParameters[0] = factory.intType; // Lambda id.
- for (int i = 0; i < capture.length(); i++) {
- newParameters[i + 1] = CaptureSignature.fieldType(factory, capture, i);
- }
- return factory.createProto(factory.voidType, newParameters);
- }
-
- final DexField getLambdaIdField(DexItemFactory factory) {
- return factory.createField(this.getGroupClassType(), factory.intType, "$id$");
- }
-
- final int mapFieldIntoCaptureIndex(DexType lambda, DexField field) {
- return CaptureSignature.mapFieldIntoCaptureIndex(
- id().capture, lambdaCaptureFields(lambda), field);
- }
-
- final DexField getCaptureField(DexItemFactory factory, int index) {
- assert index >= 0 && index < id().capture.length();
- return factory.createField(this.getGroupClassType(),
- CaptureSignature.fieldType(factory, id().capture, index), "$capture$" + index);
- }
-
- @Override
- protected LambdaGroupClassBuilder getBuilder(DexItemFactory factory) {
- return new KStyleLambdaGroupClassBuilder(factory, this, "kotlin-style lambda group");
- }
-
- @Override
- public Strategy getCodeStrategy() {
- return strategy;
- }
-
- @Override
- public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
- Kotlin kotlin, AppInfoWithSubtyping appInfo) {
- return new KStyleLambdaClassValidator(kotlin, this, appInfo);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 9b0415b..cf2b2bc 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -13,7 +13,9 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.kotlin.KotlinSyntheticClass.Flavour;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
@@ -118,7 +120,7 @@
case 2:
return new KotlinFile();
case 3:
- return createSyntheticClass(clazz);
+ return createSyntheticClass(clazz, meta);
case 4:
return new KotlinClassFacade();
case 5:
@@ -128,10 +130,14 @@
}
}
- private KotlinSyntheticClass createSyntheticClass(DexClass clazz) {
- KotlinSyntheticClass.Flavour flavour =
- isKotlinStyleLambda(clazz) ? Flavour.KotlinStyleLambda : Flavour.Unclassified;
- return new KotlinSyntheticClass(flavour);
+ private KotlinSyntheticClass createSyntheticClass(DexClass clazz, DexAnnotation meta) {
+ if (isKotlinStyleLambda(clazz)) {
+ return new KotlinSyntheticClass(Flavour.KotlinStyleLambda);
+ }
+ if (isJavaStyleLambda(clazz, meta)) {
+ return new KotlinSyntheticClass(Flavour.JavaStyleLambda);
+ }
+ return new KotlinSyntheticClass(Flavour.Unclassified);
}
private boolean isKotlinStyleLambda(DexClass clazz) {
@@ -139,6 +145,13 @@
return clazz.superType == this.functional.lambdaType;
}
+ private boolean isJavaStyleLambda(DexClass clazz, DexAnnotation meta) {
+ assert !isKotlinStyleLambda(clazz);
+ return clazz.superType == this.factory.objectType &&
+ clazz.interfaces.size() == 1 &&
+ isAnnotationElementNotEmpty(meta, metadata.elementNameD1);
+ }
+
private DexAnnotationElement getAnnotationElement(DexAnnotation annotation, DexString name) {
for (DexAnnotationElement element : annotation.annotation.elements) {
if (element.name == name) {
@@ -148,6 +161,20 @@
return null;
}
+ private boolean isAnnotationElementNotEmpty(DexAnnotation annotation, DexString name) {
+ for (DexAnnotationElement element : annotation.annotation.elements) {
+ if (element.name == name && element.value instanceof DexValueArray) {
+ DexValue[] values = ((DexValueArray) element.value).getValues();
+ if (values.length == 1 && values[0] instanceof DexValueString) {
+ return true;
+ }
+ // Must be broken metadata.
+ assert false;
+ }
+ }
+ return false;
+ }
+
private static class MetadataError extends RuntimeException {
MetadataError(String cause) {
super(cause);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index b0e37b4..68721bb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -7,19 +7,28 @@
public final class KotlinSyntheticClass extends KotlinInfo {
public enum Flavour {
KotlinStyleLambda,
+ JavaStyleLambda,
Unclassified
}
- public final Flavour flavour;
+ private final Flavour flavour;
KotlinSyntheticClass(Flavour flavour) {
this.flavour = flavour;
}
+ public boolean isLambda() {
+ return flavour == Flavour.KotlinStyleLambda || flavour == Flavour.JavaStyleLambda;
+ }
+
public boolean isKotlinStyleLambda() {
return flavour == Flavour.KotlinStyleLambda;
}
+ public boolean isJavaStyleLambda() {
+ return flavour == Flavour.JavaStyleLambda;
+ }
+
@Override
public final Kind getKind() {
return Kind.Synthetic;
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 1419b95..3e46ff8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -185,8 +185,6 @@
AndroidAppInspector inspector) throws Exception {
Assume.assumeTrue(ToolHelper.artSupported());
- Path jarFile = getJarFile(folder);
-
String proguardRules = buildProguardRules(mainClass);
if (extraProguardRules != null) {
proguardRules += extraProguardRules;
@@ -194,7 +192,8 @@
// Build classpath for compilation (and java execution)
List<Path> classpath = new ArrayList<>(extraClasspath.size() + 1);
- classpath.add(jarFile);
+ classpath.add(getKotlinJarFile(folder));
+ classpath.add(getJavaJarFile(folder));
classpath.addAll(extraClasspath);
// Build with R8
@@ -222,11 +221,16 @@
inspector.inspectApp(app);
}
- private Path getJarFile(String folder) {
+ private Path getKotlinJarFile(String folder) {
return Paths.get(ToolHelper.TESTS_BUILD_DIR, "kotlinR8TestResources",
targetVersion.getFolderName(), folder + FileUtils.JAR_EXTENSION);
}
+ private Path getJavaJarFile(String folder) {
+ return Paths.get(ToolHelper.TESTS_BUILD_DIR, "kotlinR8TestResources",
+ targetVersion.getFolderName(), folder + ".java" + FileUtils.JAR_EXTENSION);
+ }
+
@FunctionalInterface
interface AndroidAppInspector {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KStyleLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KStyleLambdaMergingTest.java
deleted file mode 100644
index 48358f0..0000000
--- a/src/test/java/com/android/tools/r8/kotlin/KStyleLambdaMergingTest.java
+++ /dev/null
@@ -1,314 +0,0 @@
-// Copyright (c) 2018, 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.kotlin;
-
-import static junit.framework.TestCase.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.google.common.collect.Lists;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import org.junit.Test;
-
-public class KStyleLambdaMergingTest extends AbstractR8KotlinTestBase {
- private static final String KOTLIN_FUNCTION_IFACE = "Lkotlin/jvm/functions/Function";
- private static final String KEEP_INNER_AND_ENCLOSING =
- "-keepattributes InnerClasses,EnclosingMethod\n";
- private static final String KEEP_SIGNATURE_INNER_ENCLOSING =
- "-keepattributes Signature,InnerClasses,EnclosingMethod\n";
-
- abstract static class LambdaOrGroup {
- abstract boolean match(DexClass clazz);
- }
-
- static class Group extends LambdaOrGroup {
- final String pkg;
- final String capture;
- final int arity;
-
- private Group(String pkg, String capture, int arity) {
- this.pkg = pkg;
- this.capture = fixCapture(capture);
- this.arity = arity;
- }
-
- private String fixCapture(String capture) {
- capture += "I";
- char[] chars = capture.toCharArray();
- Arrays.sort(chars);
- return new String(chars);
- }
-
- @Override
- public String toString() {
- return "group class " +
- (pkg.length() == 0 ? "" : pkg + "/") +
- "-$$LambdaGroup$XXXX (arity: " + arity + ", capture: " + capture + ")";
- }
-
- @Override
- boolean match(DexClass clazz) {
- return clazz.type.getPackageDescriptor().equals(pkg) &&
- getLambdaGroupCapture(clazz).equals(capture) &&
- getLambdaArity(clazz) == arity;
- }
- }
-
- static class Lambda extends LambdaOrGroup {
- final String pkg;
- final String name;
- final int arity;
-
- private Lambda(String pkg, String name, int arity) {
- this.pkg = pkg;
- this.name = name;
- this.arity = arity;
- }
-
- @Override
- public String toString() {
- return "lambda class " +
- (pkg.length() == 0 ? "" : pkg + "/") +
- name + " (arity: " + arity + ")";
- }
-
- @Override
- boolean match(DexClass clazz) {
- return clazz.type.getPackageDescriptor().equals(pkg) &&
- clazz.type.getName().equals(name) &&
- getLambdaArity(clazz) == arity;
- }
- }
-
- static class Verifier {
- final DexInspector dexInspector;
- final List<DexClass> lambdas = new ArrayList<>();
- final List<DexClass> groups = new ArrayList<>();
-
- Verifier(AndroidApp app) throws IOException, ExecutionException {
- this.dexInspector = new DexInspector(app);
- dexInspector.forAllClasses(clazz -> {
- DexClass dexClass = clazz.getDexClass();
- if (extendsLambdaBase(dexClass)) {
- if (isLambdaGroupClass(dexClass)) {
- groups.add(dexClass);
- } else {
- lambdas.add(dexClass);
- }
- }
- });
- }
-
- void assertLambdaGroups(Group... groups) {
- assertLambdasOrGroups("Lambda group", this.groups, groups);
- }
-
- void assertLambdas(Lambda... lambdas) {
- assertLambdasOrGroups("Lambda", this.lambdas, lambdas);
- }
-
- @SafeVarargs
- private static <T extends LambdaOrGroup>
- void assertLambdasOrGroups(String what, List<DexClass> objects, T... checks) {
- ArrayList<DexClass> list = Lists.newArrayList(objects);
- for (int i = 0; i < checks.length; i++) {
- T check = checks[i];
- for (DexClass clazz : list) {
- if (check.match(clazz)) {
- list.remove(clazz);
- checks[i] = null;
- break;
- }
- }
- }
-
- int notFound = 0;
- for (T check : checks) {
- if (check != null) {
- System.err.println(what + " not found: " + check);
- notFound++;
- }
- }
-
- for (DexClass dexClass : list) {
- System.err.println(what + " unexpected: " +
- dexClass.type.descriptor.toString() +
- ", arity: " + getLambdaArity(dexClass) +
- ", capture: " + getLambdaGroupCapture(dexClass));
- notFound++;
- }
-
- assertTrue(what + "s match failed", 0 == notFound && 0 == list.size());
- }
- }
-
- private static int getLambdaArity(DexClass clazz) {
- for (DexType iface : clazz.interfaces.values) {
- String descr = iface.descriptor.toString();
- if (descr.startsWith(KOTLIN_FUNCTION_IFACE)) {
- return Integer.parseInt(
- descr.substring(KOTLIN_FUNCTION_IFACE.length(), descr.length() - 1));
- }
- }
- fail("Type " + clazz.type.descriptor.toString() +
- " does not implement functional interface.");
- throw new AssertionError();
- }
-
- private static boolean extendsLambdaBase(DexClass clazz) {
- return clazz.superType.descriptor.toString().equals("Lkotlin/jvm/internal/Lambda;");
- }
-
- private static boolean isLambdaGroupClass(DexClass clazz) {
- return clazz.type.getName().startsWith("-$$LambdaGroup$");
- }
-
- private static String getLambdaGroupCapture(DexClass clazz) {
- return CaptureSignature.getCaptureSignature(clazz.instanceFields());
- }
-
- @Test
- public void testTrivial() throws Exception {
- final String mainClassName = "lambdas.kstyle.trivial.MainKt";
- runTest("lambdas_kstyle_trivial", mainClassName, null, (app) -> {
- Verifier verifier = new Verifier(app);
- String pkg = "lambdas/kstyle/trivial";
-
- verifier.assertLambdaGroups(
- allowAccessModification ?
- new Group[]{
- new Group("", "", 0),
- new Group("", "", 1),
- new Group("", "", 2), // -\
- new Group("", "", 2), // - 3 groups different by main method
- new Group("", "", 2), // -/
- new Group("", "", 3),
- new Group("", "", 22)} :
- new Group[]{
- new Group(pkg, "", 0),
- new Group(pkg, "", 1),
- new Group(pkg, "", 2), // - 2 groups different by main method
- new Group(pkg, "", 2), // -/
- new Group(pkg, "", 3),
- new Group(pkg, "", 22),
- new Group(pkg + "/inner", "", 0),
- new Group(pkg + "/inner", "", 1)}
- );
-
- verifier.assertLambdas(
- allowAccessModification ?
- new Lambda[]{
- new Lambda(pkg, "MainKt$testStateless$6", 1) /* Banned for limited inlining */} :
- new Lambda[]{
- new Lambda(pkg, "MainKt$testStateless$6", 1), /* Banned for limited inlining */
- new Lambda(pkg, "MainKt$testStateless$8", 2),
- new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)}
-
- );
- });
- }
-
- @Test
- public void testCaptures() throws Exception {
- final String mainClassName = "lambdas.kstyle.captures.MainKt";
- runTest("lambdas_kstyle_captures", mainClassName, null, (app) -> {
- Verifier verifier = new Verifier(app);
- String pkg = "lambdas/kstyle/captures";
- String grpPkg = allowAccessModification ? "" : pkg;
-
- verifier.assertLambdaGroups(
- new Group(grpPkg, "LLL", 0),
- new Group(grpPkg, "ILL", 0),
- new Group(grpPkg, "III", 0),
- new Group(grpPkg, "BCDFIJLLLLSZ", 0),
- new Group(grpPkg, "BCDFIJLLSZ", 0)
- );
-
- verifier.assertLambdas(
- new Lambda(pkg, "MainKt$test1$15", 0),
- new Lambda(pkg, "MainKt$test2$10", 0),
- new Lambda(pkg, "MainKt$test2$11", 0),
- new Lambda(pkg, "MainKt$test2$9", 0)
- );
- });
- }
-
- @Test
- public void testGenericsNoSignature() throws Exception {
- final String mainClassName = "lambdas.kstyle.generics.MainKt";
- runTest("lambdas_kstyle_generics", mainClassName, null, (app) -> {
- Verifier verifier = new Verifier(app);
- String pkg = "lambdas/kstyle/generics";
- String grpPkg = allowAccessModification ? "" : pkg;
-
- verifier.assertLambdaGroups(
- new Group(grpPkg, "", 1), // Group for Any
- new Group(grpPkg, "L", 1), // Group for Beta
- new Group(grpPkg, "LS", 1), // Group for Gamma
- new Group(grpPkg, "", 1) // Group for int
- );
-
- verifier.assertLambdas(
- new Lambda(pkg, "MainKt$main$4", 1)
- );
- });
- }
-
- @Test
- public void testInnerClassesAndEnclosingMethods() throws Exception {
- final String mainClassName = "lambdas.kstyle.generics.MainKt";
- runTest("lambdas_kstyle_generics", mainClassName, KEEP_INNER_AND_ENCLOSING, (app) -> {
- Verifier verifier = new Verifier(app);
- String pkg = "lambdas/kstyle/generics";
- String grpPkg = allowAccessModification ? "" : pkg;
-
- verifier.assertLambdaGroups(
- new Group(grpPkg, "", 1), // Group for Any
- new Group(grpPkg, "L", 1), // Group for Beta // First
- new Group(grpPkg, "L", 1), // Group for Beta // Second
- new Group(grpPkg, "LS", 1), // Group for Gamma // First
- new Group(grpPkg, "LS", 1), // Group for Gamma // Second
- new Group(grpPkg, "", 1) // Group for int
- );
-
- verifier.assertLambdas(
- new Lambda(pkg, "MainKt$main$4", 1)
- );
- });
- }
-
- @Test
- public void testGenericsSignatureInnerEnclosing() throws Exception {
- final String mainClassName = "lambdas.kstyle.generics.MainKt";
- runTest("lambdas_kstyle_generics", mainClassName, KEEP_SIGNATURE_INNER_ENCLOSING, (app) -> {
- Verifier verifier = new Verifier(app);
- String pkg = "lambdas/kstyle/generics";
- String grpPkg = allowAccessModification ? "" : pkg;
-
- verifier.assertLambdaGroups(
- new Group(grpPkg, "", 1), // Group for Any
- new Group(grpPkg, "L", 1), // Group for Beta in First
- new Group(grpPkg, "L", 1), // Group for Beta in Second
- new Group(grpPkg, "LS", 1), // Group for Gamma<String> in First
- new Group(grpPkg, "LS", 1), // Group for Gamma<Integer> in First
- new Group(grpPkg, "LS", 1), // Group for Gamma<String> in Second
- new Group(grpPkg, "LS", 1), // Group for Gamma<Integer> in Second
- new Group(grpPkg, "", 1) // Group for int
- );
-
- verifier.assertLambdas(
- new Lambda(pkg, "MainKt$main$4", 1)
- );
- });
- }
-}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
new file mode 100644
index 0000000..65dc98f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -0,0 +1,451 @@
+// Copyright (c) 2018, 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.kotlin;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class KotlinLambdaMergingTest extends AbstractR8KotlinTestBase {
+ private static final String KOTLIN_FUNCTION_IFACE = "Lkotlin/jvm/functions/Function";
+ private static final String KOTLIN_FUNCTION_IFACE_STR = "kotlin.jvm.functions.Function";
+ private static final String KEEP_INNER_AND_ENCLOSING =
+ "-keepattributes InnerClasses,EnclosingMethod\n";
+ private static final String KEEP_SIGNATURE_INNER_ENCLOSING =
+ "-keepattributes Signature,InnerClasses,EnclosingMethod\n";
+
+ abstract static class LambdaOrGroup {
+ abstract boolean match(DexClass clazz);
+ }
+
+ private static class Group extends LambdaOrGroup {
+ final String pkg;
+ final String capture;
+ final int arity;
+ final String sam;
+ final int singletons;
+
+ private Group(String pkg, String capture, int arity, String sam, int singletons) {
+ this.pkg = pkg;
+ this.capture = fixCapture(capture);
+ this.arity = arity;
+ this.sam = sam;
+ this.singletons = singletons;
+ }
+
+ private String fixCapture(String capture) {
+ capture += "I";
+ char[] chars = capture.toCharArray();
+ Arrays.sort(chars);
+ return new String(chars);
+ }
+
+ @Override
+ public String toString() {
+ return "group class " +
+ (pkg.length() == 0 ? "" : pkg + "/") +
+ "-$$LambdaGroup$XXXX (arity: " + arity +
+ ", capture: " + capture + ", iface: " + sam + ", sing: " + singletons + ")";
+ }
+
+ @Override
+ boolean match(DexClass clazz) {
+ return clazz.type.getPackageDescriptor().equals(pkg) &&
+ getLambdaOrGroupCapture(clazz).equals(capture) &&
+ getLambdaSam(clazz).equals(sam) &&
+ getLambdaSingletons(clazz) == singletons &&
+ getLambdaOrGroupArity(clazz) == arity;
+ }
+ }
+
+ private Group kstyleImpl(String pkg, String capture, int arity, int singletons) {
+ assertEquals(capture.isEmpty(), singletons != 0);
+ return new Group(pkg, capture, arity, KOTLIN_FUNCTION_IFACE_STR + arity, singletons);
+ }
+
+ private Group kstyle(String pkg, int arity, int singletons) {
+ assertTrue(singletons != 0);
+ return kstyleImpl(pkg, "", arity, singletons);
+ }
+
+ private Group kstyle(String pkg, String capture, int arity) {
+ assertFalse(capture.isEmpty());
+ return kstyleImpl(pkg, capture, arity, 0);
+ }
+
+ private Group jstyleImpl(String pkg, String capture, int arity, String sam, int singletons) {
+ assertTrue(capture.isEmpty() || singletons == 0);
+ return new Group(pkg, capture, arity, sam, singletons);
+ }
+
+ private Group jstyle(String pkg, String capture, int arity, String sam) {
+ return jstyleImpl(pkg, capture, arity, sam, 0);
+ }
+
+ private Group jstyle(String pkg, int arity, String sam, int singletons) {
+ return jstyleImpl(pkg, "", arity, sam, singletons);
+ }
+
+ static class Lambda extends LambdaOrGroup {
+ final String pkg;
+ final String name;
+ final int arity;
+
+ private Lambda(String pkg, String name, int arity) {
+ this.pkg = pkg;
+ this.name = name;
+ this.arity = arity;
+ }
+
+ @Override
+ public String toString() {
+ return "lambda class " +
+ (pkg.length() == 0 ? "" : pkg + "/") +
+ name + " (arity: " + arity + ")";
+ }
+
+ @Override
+ boolean match(DexClass clazz) {
+ return clazz.type.getPackageDescriptor().equals(pkg) &&
+ clazz.type.getName().equals(name) &&
+ getLambdaOrGroupArity(clazz) == arity;
+ }
+ }
+
+ static class Verifier {
+ final DexInspector dexInspector;
+ final List<DexClass> lambdas = new ArrayList<>();
+ final List<DexClass> groups = new ArrayList<>();
+
+ Verifier(AndroidApp app) throws IOException, ExecutionException {
+ this.dexInspector = new DexInspector(app);
+ dexInspector.forAllClasses(clazz -> {
+ DexClass dexClass = clazz.getDexClass();
+ if (isLambdaOrGroup(dexClass)) {
+ if (isLambdaGroupClass(dexClass)) {
+ groups.add(dexClass);
+ } else {
+ lambdas.add(dexClass);
+ }
+ }
+ });
+ }
+
+ void assertLambdaGroups(Group... groups) {
+ assertLambdasOrGroups("Lambda group", this.groups, groups);
+ }
+
+ void assertLambdas(Lambda... lambdas) {
+ assertLambdasOrGroups("Lambda", this.lambdas, lambdas);
+ }
+
+ @SafeVarargs
+ private static <T extends LambdaOrGroup>
+ void assertLambdasOrGroups(String what, List<DexClass> objects, T... checks) {
+ ArrayList<DexClass> list = Lists.newArrayList(objects);
+ for (int i = 0; i < checks.length; i++) {
+ T check = checks[i];
+ for (DexClass clazz : list) {
+ if (check.match(clazz)) {
+ // Validate static initializer.
+ if (check instanceof Group) {
+ assertEquals(clazz.directMethods().length, ((Group) check).singletons == 0 ? 1 : 2);
+ }
+
+ list.remove(clazz);
+ checks[i] = null;
+ break;
+ }
+ }
+ }
+
+ int notFound = 0;
+ for (T check : checks) {
+ if (check != null) {
+ System.err.println(what + " not found: " + check);
+ notFound++;
+ }
+ }
+
+ for (DexClass dexClass : list) {
+ System.err.println(what + " unexpected: " +
+ dexClass.type.descriptor.toString() +
+ ", arity: " + getLambdaOrGroupArity(dexClass) +
+ ", capture: " + getLambdaOrGroupCapture(dexClass) +
+ ", sam: " + getLambdaSam(dexClass) +
+ ", sing: " + getLambdaSingletons(dexClass));
+ notFound++;
+ }
+
+ assertTrue(what + "s match failed", 0 == notFound && 0 == list.size());
+ }
+ }
+
+ private static int getLambdaOrGroupArity(DexClass clazz) {
+ if (isKStyleLambdaOrGroup(clazz)) {
+ for (DexType iface : clazz.interfaces.values) {
+ String descr = iface.descriptor.toString();
+ if (descr.startsWith(KOTLIN_FUNCTION_IFACE)) {
+ return Integer.parseInt(
+ descr.substring(KOTLIN_FUNCTION_IFACE.length(), descr.length() - 1));
+ }
+ }
+
+ } else {
+ assertTrue(isJStyleLambdaOrGroup(clazz));
+ // Taking the number of any virtual method parameters seems to be good enough.
+ assertTrue(clazz.virtualMethods().length > 0);
+ return clazz.virtualMethods()[0].method.proto.parameters.size();
+ }
+ fail("Failed to get arity for " + clazz.type.descriptor.toString());
+ throw new AssertionError();
+ }
+
+ private static String getLambdaSam(DexClass clazz) {
+ assertEquals(1, clazz.interfaces.size());
+ return clazz.interfaces.values[0].toSourceString();
+ }
+
+ private static int getLambdaSingletons(DexClass clazz) {
+ assertEquals(1, clazz.interfaces.size());
+ return clazz.staticFields().length;
+ }
+
+ private static boolean isLambdaOrGroup(DexClass clazz) {
+ return !clazz.type.getPackageDescriptor().startsWith("kotlin") &&
+ (isKStyleLambdaOrGroup(clazz) || isJStyleLambdaOrGroup(clazz));
+ }
+
+ private static boolean isKStyleLambdaOrGroup(DexClass clazz) {
+ return clazz.superType.descriptor.toString().equals("Lkotlin/jvm/internal/Lambda;");
+ }
+
+ private static boolean isJStyleLambdaOrGroup(DexClass clazz) {
+ return clazz.superType.descriptor.toString().equals("Ljava/lang/Object;") &&
+ clazz.interfaces.size() == 1;
+ }
+
+ private static boolean isLambdaGroupClass(DexClass clazz) {
+ return clazz.type.getName().startsWith("-$$LambdaGroup$");
+ }
+
+ private static String getLambdaOrGroupCapture(DexClass clazz) {
+ return CaptureSignature.getCaptureSignature(clazz.instanceFields());
+ }
+
+ @Test
+ public void testTrivialKs() throws Exception {
+ final String mainClassName = "lambdas.kstyle.trivial.MainKt";
+ runTest("lambdas_kstyle_trivial", mainClassName, null, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/kstyle/trivial";
+
+ verifier.assertLambdaGroups(
+ allowAccessModification ?
+ new Group[]{
+ kstyle("", 0, 4),
+ kstyle("", 1, 8),
+ kstyle("", 2, 2), // -\
+ kstyle("", 2, 5), // - 3 groups different by main method
+ kstyle("", 2, 4), // -/
+ kstyle("", 3, 2),
+ kstyle("", 22, 2)} :
+ new Group[]{
+ kstyle(pkg, 0, 2),
+ kstyle(pkg, 1, 4),
+ kstyle(pkg, 2, 5), // - 2 groups different by main method
+ kstyle(pkg, 2, 4), // -/
+ kstyle(pkg, 3, 2),
+ kstyle(pkg, 22, 2),
+ kstyle(pkg + "/inner", 0, 2),
+ kstyle(pkg + "/inner", 1, 4)}
+ );
+
+ verifier.assertLambdas(
+ allowAccessModification ?
+ new Lambda[]{
+ new Lambda(pkg, "MainKt$testStateless$6", 1) /* Banned for limited inlining */} :
+ new Lambda[]{
+ new Lambda(pkg, "MainKt$testStateless$6", 1), /* Banned for limited inlining */
+ new Lambda(pkg, "MainKt$testStateless$8", 2),
+ new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)}
+
+ );
+ });
+ }
+
+ @Test
+ public void testCapturesKs() throws Exception {
+ final String mainClassName = "lambdas.kstyle.captures.MainKt";
+ runTest("lambdas_kstyle_captures", mainClassName, null, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/kstyle/captures";
+ String grpPkg = allowAccessModification ? "" : pkg;
+
+ verifier.assertLambdaGroups(
+ kstyle(grpPkg, "LLL", 0),
+ kstyle(grpPkg, "ILL", 0),
+ kstyle(grpPkg, "III", 0),
+ kstyle(grpPkg, "BCDFIJLLLLSZ", 0),
+ kstyle(grpPkg, "BCDFIJLLSZ", 0)
+ );
+
+ verifier.assertLambdas(
+ new Lambda(pkg, "MainKt$test1$15", 0),
+ new Lambda(pkg, "MainKt$test2$10", 0),
+ new Lambda(pkg, "MainKt$test2$11", 0),
+ new Lambda(pkg, "MainKt$test2$9", 0)
+ );
+ });
+ }
+
+ @Test
+ public void testGenericsNoSignatureKs() throws Exception {
+ final String mainClassName = "lambdas.kstyle.generics.MainKt";
+ runTest("lambdas_kstyle_generics", mainClassName, null, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/kstyle/generics";
+ String grpPkg = allowAccessModification ? "" : pkg;
+
+ verifier.assertLambdaGroups(
+ kstyle(grpPkg, 1, 3), // Group for Any
+ kstyle(grpPkg, "L", 1), // Group for Beta
+ kstyle(grpPkg, "LS", 1), // Group for Gamma
+ kstyle(grpPkg, 1, 2) // Group for int
+ );
+
+ verifier.assertLambdas(
+ new Lambda(pkg, "MainKt$main$4", 1)
+ );
+ });
+ }
+
+ @Test
+ public void testInnerClassesAndEnclosingMethodsKs() throws Exception {
+ final String mainClassName = "lambdas.kstyle.generics.MainKt";
+ runTest("lambdas_kstyle_generics", mainClassName, KEEP_INNER_AND_ENCLOSING, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/kstyle/generics";
+ String grpPkg = allowAccessModification ? "" : pkg;
+
+ verifier.assertLambdaGroups(
+ kstyle(grpPkg, 1, 3), // Group for Any
+ kstyle(grpPkg, "L", 1), // Group for Beta // First
+ kstyle(grpPkg, "L", 1), // Group for Beta // Second
+ kstyle(grpPkg, "LS", 1), // Group for Gamma // First
+ kstyle(grpPkg, "LS", 1), // Group for Gamma // Second
+ kstyle(grpPkg, 1, 2) // Group for int
+ );
+
+ verifier.assertLambdas(
+ new Lambda(pkg, "MainKt$main$4", 1)
+ );
+ });
+ }
+
+ @Test
+ public void testGenericsSignatureInnerEnclosingKs() throws Exception {
+ final String mainClassName = "lambdas.kstyle.generics.MainKt";
+ runTest("lambdas_kstyle_generics", mainClassName, KEEP_SIGNATURE_INNER_ENCLOSING, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/kstyle/generics";
+ String grpPkg = allowAccessModification ? "" : pkg;
+
+ verifier.assertLambdaGroups(
+ kstyle(grpPkg, 1, 3), // Group for Any
+ kstyle(grpPkg, "L", 1), // Group for Beta in First
+ kstyle(grpPkg, "L", 1), // Group for Beta in Second
+ kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in First
+ kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in First
+ kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in Second
+ kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in Second
+ kstyle(grpPkg, 1, 2) // Group for int
+ );
+
+ verifier.assertLambdas(
+ new Lambda(pkg, "MainKt$main$4", 1)
+ );
+ });
+ }
+
+ @Test
+ public void testTrivialJs() throws Exception {
+ final String mainClassName = "lambdas.jstyle.trivial.MainKt";
+ runTest("lambdas_jstyle_trivial", mainClassName, null, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/jstyle/trivial";
+ String grp = allowAccessModification ? "" : pkg;
+
+ String supplier = "Lambdas$Supplier";
+ String intSupplier = "Lambdas$IntSupplier";
+ String consumer = "Lambdas$Consumer";
+ String intConsumer = "Lambdas$IntConsumer";
+ String multiFunction = "Lambdas$MultiFunction";
+
+ verifier.assertLambdaGroups(
+ jstyle(grp, 0, intSupplier, 2),
+ jstyle(grp, "L", 0, supplier),
+ jstyle(grp, "LL", 0, supplier),
+ jstyle(grp, "LLL", 0, supplier),
+ jstyle(grp, 1, intConsumer, allowAccessModification ? 3 : 2),
+ jstyle(grp, "I", 1, consumer),
+ jstyle(grp, "II", 1, consumer),
+ jstyle(grp, "III", 1, consumer),
+ jstyle(grp, "IIII", 1, consumer),
+ jstyle(grp, 3, multiFunction, 2),
+ jstyle(grp, 3, multiFunction, 2),
+ jstyle(grp, 3, multiFunction, 4),
+ jstyle(grp, 3, multiFunction, 6)
+ );
+
+ verifier.assertLambdas(
+ allowAccessModification ?
+ new Lambda[]{
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$4", 1),
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$5", 1)
+ } :
+ new Lambda[]{
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$1", 1),
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$2", 1),
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$3", 1),
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$4", 1),
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$5", 1)
+ }
+
+ );
+ });
+ }
+
+ @Test
+ public void testSingleton() throws Exception {
+ final String mainClassName = "lambdas.singleton.MainKt";
+ runTest("lambdas_singleton", mainClassName, null, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/singleton";
+ String grp = allowAccessModification ? "" : pkg;
+
+ verifier.assertLambdaGroups(
+ kstyle(grp, 1, 1 /* 1 out of 5 lambdas in the group */),
+ jstyle(grp, 2, "java.util.Comparator", 0 /* 0 out of 2 lambdas in the group */)
+ );
+
+ verifier.assertLambdas(/* None */);
+ });
+ }
+}
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/Lambdas.java b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/Lambdas.java
new file mode 100644
index 0000000..69e9a78
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/Lambdas.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2018, 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.
+
+public class Lambdas {
+ public interface IntConsumer {
+ void put(int value);
+ }
+
+ public interface Consumer<T> {
+ void put(T t);
+ }
+
+ public interface Supplier<T> {
+ T get();
+ }
+
+ public interface IntSupplier {
+ int get();
+ }
+
+ public interface MultiFunction<R, P1, P2, P3> {
+ R get(P1 a, P2 b, P3 c);
+ }
+
+ public synchronized static void acceptStringSupplier(Supplier<String> s) {
+ System.out.println(s.get());
+ }
+
+ public synchronized static <T> void acceptGenericSupplier(Supplier<T> s) {
+ System.out.println(s.get());
+ }
+
+ public synchronized static void acceptIntSupplier(IntSupplier s) {
+ System.out.println(s.get());
+ }
+
+ public synchronized static void acceptStringConsumer(Consumer<String> s, String arg) {
+ s.put(arg);
+ }
+
+ public synchronized static <T> void acceptGenericConsumer(Consumer<T> s, T arg) {
+ s.put(arg);
+ }
+
+ public synchronized static void acceptIntConsumer(IntConsumer s, int arg) {
+ s.put(arg);
+ }
+
+ public synchronized static <R, P1, P2, P3>
+ void acceptMultiFunction(MultiFunction<R, P1, P2, P3> s, P1 a, P2 b, P3 c) {
+ System.out.println(s.get(a, b, c));
+ }
+}
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner.kt
new file mode 100644
index 0000000..5da8884
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner.kt
@@ -0,0 +1,26 @@
+// Copyright (c) 2018, 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 lambdas.jstyle.trivial.inner
+
+import Lambdas
+import lambdas.jstyle.trivial.next
+import lambdas.jstyle.trivial.nextInt
+
+fun testInner() {
+ testInner1(nextInt(), nextInt(), nextInt(), nextInt())
+}
+
+private data class InnerLocal<out T>(val id: T)
+
+private fun testInner1(c0: Int, c1: Int, c2: Int, c3: Int) {
+ Lambdas.acceptIntConsumer({ println("{${next()}:$it}") }, 100)
+ Lambdas.acceptStringConsumer({ println("${next()}:{$it}:{$c0}") }, next())
+ Lambdas.acceptGenericConsumer({ println("${next()}:{$it}:{$c0}:{$c1}") }, next())
+ Lambdas.acceptGenericConsumer(
+ { println("${next()}:{$it}:{$c0}:{$c1}:{$c2}") }, InnerLocal(next()))
+ Lambdas.acceptGenericConsumer(
+ { println("${next()}:{$it}:{$c0}:{$c1}:{$c2}:{$c3") }, InnerLocal(InnerLocal(next())))
+}
+
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/main.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/main.kt
new file mode 100644
index 0000000..39673af
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/main.kt
@@ -0,0 +1,88 @@
+// Copyright (c) 2018, 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 lambdas.jstyle.trivial
+
+import Lambdas
+import lambdas.jstyle.trivial.inner.testInner
+
+private var COUNT = 0
+
+fun nextInt() = COUNT++
+fun next() = "${nextInt()}".padStart(3, '0')
+
+fun main(args: Array<String>) {
+ test()
+ testInner()
+}
+
+private fun test() {
+ test1(nextInt(), nextInt(), nextInt(), nextInt())
+ test2(next(), next(), next())
+ test3a(next(), next(), next())
+ test3a(next(), Local(next()), Local(Local(next())))
+ test3b(next(), next(), next())
+ test3b(next(), Local(next()), Local(next()))
+}
+
+private data class Local<out T>(val id: T)
+
+private fun test1(c0: Int, c1: Int, c2: Int, c3: Int) {
+ Lambdas.acceptIntConsumer({ println("{${next()}:$it}") }, nextInt())
+ Lambdas.acceptIntConsumer({ println("{${next()}:$it}") }, nextInt())
+
+ Lambdas.acceptStringConsumer({ println("${next()}:{$it}:{$c0}") }, next())
+ Lambdas.acceptStringConsumer({ println("${next()}:{$it}:{$c0}") }, next())
+
+ Lambdas.acceptGenericConsumer({ println("${next()}:{$it}:{$c0}:{$c1}") }, next())
+ Lambdas.acceptGenericConsumer({ println("${next()}:{$it}:{$c0}:{$c1}") }, next())
+
+ Lambdas.acceptGenericConsumer({ println("${next()}:{$it}:{$c0}:{$c1}:{$c2}") }, Local(next()))
+ Lambdas.acceptGenericConsumer({ println("${next()}:{$it}:{$c0}:{$c1}:{$c2}") }, Local(next()))
+
+ Lambdas.acceptGenericConsumer(
+ { println("${next()}:{$it}:{$c0}:{$c1}:{$c2}:{$c3}") }, Local(Local(next())))
+ Lambdas.acceptGenericConsumer(
+ { println("${next()}:{$it}:{$c0}:{$c1}:{$c2}:{$c3}") }, Local(Local(next())))
+}
+
+private fun test2(c0: String, c1: String, c2: String) {
+ println(Lambdas.acceptIntSupplier { nextInt() })
+ println(Lambdas.acceptIntSupplier { nextInt() })
+
+ println(Lambdas.acceptStringSupplier { "${next()}:$c0" })
+ println(Lambdas.acceptStringSupplier { "${next()}:$c0" })
+
+ println(Lambdas.acceptGenericSupplier { "${next()}:$c0" })
+ println(Lambdas.acceptGenericSupplier { "${next()}:$c0" })
+
+ println(Lambdas.acceptGenericSupplier { "${Local(next())}:$c0:$c1" })
+ println(Lambdas.acceptGenericSupplier { "${Local(next())}:$c0:$c1" })
+
+ println(Lambdas.acceptGenericSupplier { "${Local(Local(next()))}:$c0:$c1:$c2" })
+ println(Lambdas.acceptGenericSupplier { "${Local(Local(next()))}:$c0:$c1:$c2" })
+}
+
+private fun <P1, P2, P3> test3a(a: P1, b: P2, c: P3) {
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, a, b, c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, c, a, b)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, b, c, a)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, Local(a), b, c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, Local(b), a, c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, a, Local(b), c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, a, Local(c), b)
+ Lambdas.acceptMultiFunction(
+ { x, y, z -> "$x:$y:$z" }, Local(Local(a)), Local(Local(b)), Local(Local(c)))
+ Lambdas.acceptMultiFunction(
+ { x, y, z -> "$x:$y:$z" }, Local(Local(c)), Local(Local(a)), Local(Local(b)))
+}
+
+private fun <P> test3b(a: P, b: P, c: P) {
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, a, b, c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, c, a, b)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, b, c, a)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, Local(a), b, c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, Local(b), a, c)
+}
+
diff --git a/src/test/kotlinR8TestResources/lambdas_singleton/Lambdas.java b/src/test/kotlinR8TestResources/lambdas_singleton/Lambdas.java
new file mode 100644
index 0000000..4140293
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_singleton/Lambdas.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, 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.
+
+public class Lambdas {
+ public interface Function<R, P1, P2> {
+ R get(P1 a, P2 b);
+ }
+
+ public synchronized static <R, P1, P2> void accept(Function<R, P1, P2> s, P1 a, P2 b) {
+ System.out.println(s.get(a, b));
+ }
+}
diff --git a/src/test/kotlinR8TestResources/lambdas_singleton/main.kt b/src/test/kotlinR8TestResources/lambdas_singleton/main.kt
new file mode 100644
index 0000000..8e34a0c
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_singleton/main.kt
@@ -0,0 +1,46 @@
+// Copyright (c) 2018, 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 lambdas.singleton
+
+private var COUNT = 0
+
+fun nextInt() = COUNT++
+fun next() = "${nextInt()}".padStart(3, '0')
+
+fun main(args: Array<String>) {
+ test()
+}
+
+private fun test() {
+ test2(listOf(next(), next(), next(), next(), next(), next(), next(), next(), next(), next()))
+}
+
+private fun Collection<String>.flatten() =
+ this.joinToString(prefix = "(*", postfix = "*)", separator = "*")
+
+private fun Array<String>.flatten() =
+ this.joinToString(prefix = "(*", postfix = "*)", separator = "*")
+
+private fun test2(args: Collection<String>) {
+ println(args.sortedByDescending { it.length }.flatten())
+ println(args.sortedByDescending { -it.length }.flatten())
+ process(::println)
+ process(::println)
+ val lambda: (Array<String>) -> Unit = {}
+}
+
+private inline fun process(crossinline f: (String) -> Unit) {
+ feed2 { f(it.flatten()) }
+ feed3 { f(it.flatten()) }
+}
+
+private fun feed3(f: (Array<String>) -> Unit) {
+ f(arrayOf(next(), next(), next()))
+}
+
+private fun feed2(f: (Array<String>) -> Unit) {
+ f(arrayOf(next(), next()))
+}
+