Merge "CfBuilder: Fix missing label and stack frame for entry block"
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 2678c1f..394805d 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -347,6 +347,7 @@
internal.enableValuePropagation = false;
internal.enableDesugaring = getEnableDesugaring();
+ internal.enableLambdaMerging = false;
return internal;
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 906c8d8..59c2639 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -14,12 +14,14 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.ClassAndMemberPublicizer;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.EnumOrdinalMapCollector;
import com.android.tools.r8.ir.optimize.SwitchMapCollector;
import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.Minifier;
import com.android.tools.r8.naming.NamingLens;
@@ -52,6 +54,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.LineNumberOptimizer;
+import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -270,6 +273,10 @@
}
}
+ // Compute kotlin info before setting the roots and before
+ // kotlin metadata annotation is removed.
+ computeKotlinInfoForProgramClasses(application, appInfo);
+
final ProguardConfiguration.Builder compatibility =
ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
@@ -480,6 +487,15 @@
}
}
+ private void computeKotlinInfoForProgramClasses(
+ DexApplication application, AppInfoWithSubtyping appInfo) {
+ Kotlin kotlin = appInfo.dexItemFactory.kotlin;
+ Reporter reporter = options.reporter;
+ for (DexProgramClass programClass : application.classes()) {
+ programClass.setKotlinInfo(kotlin.getKotlinInfo(programClass, reporter));
+ }
+ }
+
static void unwrapExecutionException(ExecutionException executionException)
throws CompilationException {
Throwable cause = executionException.getCause();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9a61a9f..4615fa5 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -643,6 +643,10 @@
internal.enableMinification = getEnableMinification();
assert internal.enableTreeShaking;
internal.enableTreeShaking = getEnableTreeShaking();
+ // In current implementation we only enable lambda merger if the tree
+ // shaking is enabled. This is caused by the fact that we rely on tree
+ // shaking for removing the lambda classes which should be revised later.
+ internal.enableLambdaMerging = getEnableTreeShaking();
assert !internal.ignoreMissingClasses;
internal.ignoreMissingClasses = proguardConfiguration.isIgnoreWarnings()
// TODO(70706667): We probably only want this in Proguard compatibility mode.
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index 604b188..c2cfe42 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -18,8 +18,6 @@
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
-import it.unimi.dsi.fastutil.ints.IntArrayList;
-import it.unimi.dsi.fastutil.ints.IntList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@@ -59,7 +57,7 @@
private final PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
// The set of registers that are free for allocation.
- private final NavigableSet<Integer> freeRegisters = new TreeSet<>();
+ private NavigableSet<Integer> freeRegisters = new TreeSet<>();
private int nextUnusedRegisterNumber = 0;
@@ -158,7 +156,6 @@
}
}
}
- IntList reactivedBeforeEnd = new IntArrayList(inactive.size());
{
// Check for inactive intervals that expired or become active.
Iterator<LiveIntervals> it = inactive.iterator();
@@ -171,19 +168,35 @@
assert inactiveIntervals.getRegister() != NO_REGISTER;
active.add(inactiveIntervals);
takeRegistersForIntervals(inactiveIntervals);
- } else if (inactiveIntervals.overlaps(unhandledInterval)) {
- reactivedBeforeEnd.add(inactiveIntervals.getRegister());
- takeRegistersForIntervals(inactiveIntervals);
}
}
}
- // Perform the actual allocation.
- assignRegisterToUnhandledInterval(
- unhandledInterval, getNextFreeRegister(unhandledInterval.getType().isWide()));
-
- // Add back the potentially free registers from the inactive set.
- freeRegisters.addAll(reactivedBeforeEnd);
+ // Find a free register that is not used by an inactive interval that overlaps with
+ // unhandledInterval.
+ boolean wide = unhandledInterval.getType().isWide();
+ int register;
+ NavigableSet<Integer> previousFreeRegisters = new TreeSet<Integer>(freeRegisters);
+ while (true) {
+ register = getNextFreeRegister(wide);
+ boolean overlapsInactiveInterval = false;
+ for (LiveIntervals inactiveIntervals : inactive) {
+ if (unhandledInterval.hasConflictingRegisters(inactiveIntervals)
+ && unhandledInterval.overlaps(inactiveIntervals)) {
+ overlapsInactiveInterval = true;
+ break;
+ }
+ }
+ if (!overlapsInactiveInterval) {
+ break;
+ }
+ // Remove so that next invocation of getNextFreeRegister does not consider this.
+ freeRegisters.remove(register);
+ // For wide types, register + 1 and 2 might be good even though register + 0 and 1 weren't,
+ // so don't remove register+1 from freeRegisters.
+ }
+ freeRegisters = previousFreeRegisters;
+ assignRegisterToUnhandledInterval(unhandledInterval, register);
}
}
diff --git a/src/main/java/com/android/tools/r8/code/Format22c.java b/src/main/java/com/android/tools/r8/code/Format22c.java
index 8254f91..6f8d6c6 100644
--- a/src/main/java/com/android/tools/r8/code/Format22c.java
+++ b/src/main/java/com/android/tools/r8/code/Format22c.java
@@ -11,7 +11,7 @@
import java.nio.ShortBuffer;
import java.util.function.BiPredicate;
-abstract class Format22c extends Base2Format {
+public abstract class Format22c extends Base2Format {
public final byte A;
public final byte B;
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 28a7fbb..6a63a8e 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -199,7 +199,7 @@
for (ProgramResource input : dexSources) {
DexReader dexReader = new DexReader(input);
computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, dexReader);
- dexParsers.add(new DexParser(dexReader, classKind, itemFactory));
+ dexParsers.add(new DexParser(dexReader, classKind, itemFactory, options.reporter));
}
options.minApiLevel = computedMinApiLevel;
for (DexParser dexParser : dexParsers) {
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 9aa7eed..0ccd724 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -8,6 +8,7 @@
import static com.android.tools.r8.utils.EncodedValueUtils.parseSigned;
import static com.android.tools.r8.utils.EncodedValueUtils.parseUnsigned;
+import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InstructionFactory;
@@ -58,6 +59,7 @@
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
import com.android.tools.r8.utils.Pair;
import com.google.common.io.ByteStreams;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
@@ -84,6 +86,7 @@
private final DexSection[] dexSections;
private int[] stringIDs;
private final ClassKind classKind;
+ private final DiagnosticsHandler reporter;
public static DexSection[] parseMapFrom(Path file) throws IOException {
return parseMapFrom(Files.newInputStream(file), new PathOrigin(file));
@@ -94,7 +97,8 @@
}
private static DexSection[] parseMapFrom(DexReader dexReader) {
- DexParser dexParser = new DexParser(dexReader, ClassKind.PROGRAM, new DexItemFactory());
+ DexParser dexParser = new DexParser(dexReader,
+ ClassKind.PROGRAM, new DexItemFactory(), new DefaultDiagnosticsHandler());
return dexParser.dexSections;
}
@@ -119,7 +123,8 @@
// Factory to canonicalize certain dexitems.
private final DexItemFactory dexItemFactory;
- public DexParser(DexReader dexReader, ClassKind classKind, DexItemFactory dexItemFactory) {
+ public DexParser(DexReader dexReader,
+ ClassKind classKind, DexItemFactory dexItemFactory, DiagnosticsHandler reporter) {
assert dexReader.getOrigin() != null;
this.origin = dexReader.getOrigin();
this.dexReader = dexReader;
@@ -128,6 +133,7 @@
dexSections = parseMap();
parseStringIDs();
this.classKind = classKind;
+ this.reporter = reporter;
}
private void ensureCodesInited() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 2e0b6ff..06e72a3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -202,6 +202,15 @@
compressSignature(signature, factory));
}
+ public static String getSignature(DexAnnotation signatureAnnotation) {
+ DexValueArray elements = (DexValueArray) signatureAnnotation.annotation.elements[0].value;
+ StringBuilder signature = new StringBuilder();
+ for (DexValue element : elements.getValues()) {
+ signature.append(((DexValueString) element).value.toString());
+ }
+ return signature.toString();
+ }
+
public static DexAnnotation createThrowsAnnotation(DexValue[] exceptions,
DexItemFactory factory) {
return createSystemValueAnnotation(factory.annotationThrows, factory,
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 2295c16..42acb7c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.google.common.base.MoreObjects;
@@ -262,6 +263,10 @@
return false;
}
+ public DexClasspathClass asClasspathClass() {
+ return null;
+ }
+
public boolean isLibraryClass() {
return false;
}
@@ -347,4 +352,11 @@
public void clearInnerClasses() {
innerClasses.clear();
}
+
+ /** Returns kotlin class info if the class is synthesized by kotlin compiler. */
+ public abstract KotlinInfo getKotlinInfo();
+
+ public final boolean hasKotlinInfo() {
+ return getKotlinInfo() != null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index 76d08d5..6148b00 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
import java.util.List;
import java.util.function.Supplier;
@@ -68,6 +69,16 @@
}
@Override
+ public DexClasspathClass asClasspathClass() {
+ return this;
+ }
+
+ @Override
+ public KotlinInfo getKotlinInfo() {
+ throw new Unreachable("Kotlin into n classpath class is not supported yet.");
+ }
+
+ @Override
public DexClasspathClass get() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 0bd48bd..b7f1802 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -168,24 +168,29 @@
public boolean isInliningCandidate(DexEncodedMethod container, Reason inliningReason,
AppInfoWithSubtyping appInfo) {
+ return isInliningCandidate(container.method.getHolder(), inliningReason, appInfo);
+ }
+
+ public boolean isInliningCandidate(DexType containerType, Reason inliningReason,
+ AppInfoWithSubtyping appInfo) {
if (isClassInitializer()) {
// This will probably never happen but never inline a class initializer.
return false;
}
if (inliningReason == Reason.FORCE) {
// Make sure we would be able to inline this normally.
- assert isInliningCandidate(container, Reason.SIMPLE, appInfo);
+ assert isInliningCandidate(containerType, Reason.SIMPLE, appInfo);
return true;
}
switch (compilationState) {
case PROCESSED_INLINING_CANDIDATE_ANY:
return true;
case PROCESSED_INLINING_CANDIDATE_SUBCLASS:
- return container.method.getHolder().isSubtypeOf(method.getHolder(), appInfo);
+ return containerType.isSubtypeOf(method.getHolder(), appInfo);
case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
- return container.method.getHolder().isSamePackage(method.getHolder());
+ return containerType.isSamePackage(method.getHolder());
case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
- return container.method.getHolder() == method.getHolder();
+ return containerType == method.getHolder();
default:
return false;
}
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 b1a5781..3439ea6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.naming.NamingLens;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
@@ -72,6 +73,10 @@
nullValueType, nullValueType,
unknownTypeName, unknownTypeName));
+ public DexItemFactory() {
+ this.kotlin = new Kotlin(this);
+ }
+
public static boolean isInternalSentinel(DexItem item) {
return internalSentinels.containsKey(item);
}
@@ -194,7 +199,7 @@
public final LongMethods longMethods = new LongMethods();
public final ThrowableMethods throwableMethods = new ThrowableMethods();
public final ClassMethods classMethods = new ClassMethods();
- public final Kotlin kotlin = new Kotlin();
+ public final Kotlin kotlin;
// Dex system annotations.
// See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation
@@ -335,24 +340,6 @@
}
}
- public class Kotlin {
- private Kotlin() {
- }
-
- public final Intrinsics intrinsics = new Intrinsics();
-
- // kotlin.jvm.internal.Intrinsics class
- public class Intrinsics {
- private Intrinsics() {
- }
-
- public final DexType type = createType(createString("Lkotlin/jvm/internal/Intrinsics;"));
- public final DexMethod throwParameterIsNullException =
- createMethod(type, createProto(voidType, stringType), "throwParameterIsNullException");
- public final DexMethod throwNpe = createMethod(type, createProto(voidType), "throwNpe");
- }
- }
-
private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
assert item != null;
assert !DexItemFactory.isInternalSentinel(item);
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index 946cd90..4700553 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
import java.util.List;
import java.util.function.Supplier;
@@ -83,6 +84,11 @@
}
@Override
+ public KotlinInfo getKotlinInfo() {
+ return null;
+ }
+
+ @Override
public DexLibraryClass get() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 37f774d..ffff10f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
import java.util.ArrayList;
import java.util.Arrays;
@@ -27,6 +28,7 @@
private DexEncodedArray staticValues = SENTINEL_NOT_YET_COMPUTED;
private final Collection<DexProgramClass> synthesizedFrom;
private int classFileVersion = -1;
+ private KotlinInfo kotlinInfo = null;
public DexProgramClass(
DexType type,
@@ -199,6 +201,16 @@
return this;
}
+ @Override
+ public KotlinInfo getKotlinInfo() {
+ return kotlinInfo;
+ }
+
+ public void setKotlinInfo(KotlinInfo kotlinInfo) {
+ assert this.kotlinInfo == null || kotlinInfo == null;
+ this.kotlinInfo = kotlinInfo;
+ }
+
public boolean hasMethodsOrFields() {
int numberOfFields = staticFields().length + instanceFields().length;
int numberOfMethods = directMethods().length + virtualMethods().length;
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index fdc07a7..cc1f8e7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -76,7 +76,7 @@
}
}
- synchronized void addDirectSubtype(DexType type) {
+ public synchronized void addDirectSubtype(DexType type) {
assert hierarchyLevel != UNKNOWN_LEVEL;
ensureDirectSubTypeSet();
directSubtypes.add(type);
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index d743f6c..ce741a3 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -12,7 +12,7 @@
* declared in the context of a constructor or method, in which case the method field will be
* non-null. Otherwise, i.e., if the class is declared in an initializer, method is null.
*/
-public class EnclosingMethodAttribute {
+public final class EnclosingMethodAttribute {
// Enclosing class of the inner class.
// Null if the inner class is declared inside of a method or constructor.
@@ -48,4 +48,17 @@
public DexType getEnclosingClass() {
return enclosingClass;
}
+
+ @Override
+ public int hashCode() {
+ assert (enclosingClass == null) != (enclosingMethod == null);
+ return System.identityHashCode(enclosingClass) + System.identityHashCode(enclosingMethod);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof EnclosingMethodAttribute &&
+ enclosingClass == ((EnclosingMethodAttribute) obj).enclosingClass &&
+ enclosingMethod == ((EnclosingMethodAttribute) obj).enclosingMethod;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 26aa423..945da10 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -252,10 +252,8 @@
instanceFields.toArray(new DexEncodedField[instanceFields.size()]),
directMethods.toArray(new DexEncodedMethod[directMethods.size()]),
virtualMethods.toArray(new DexEncodedMethod[virtualMethods.size()]));
- if (classKind == ClassKind.PROGRAM) {
- context.owner = clazz.asProgramClass();
- }
if (clazz.isProgramClass()) {
+ context.owner = clazz.asProgramClass();
clazz.asProgramClass().setClassFileVersion(version);
}
classConsumer.accept(clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 6364c82..76a1a43 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -1846,7 +1846,7 @@
targetInfo.block.incrementUnfilledPredecessorCount();
}
- void ensureNormalSuccessorBlock(int sourceOffset, int targetOffset) {
+ public void ensureNormalSuccessorBlock(int sourceOffset, int targetOffset) {
ensureSuccessorBlock(sourceOffset, targetOffset, true);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 9a3a218..42b06ef 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -39,6 +39,7 @@
import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
+import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.logging.Log;
@@ -78,6 +79,7 @@
private final StringConcatRewriter stringConcatRewriter;
private final LambdaRewriter lambdaRewriter;
private final InterfaceMethodRewriter interfaceMethodRewriter;
+ private final LambdaMerger lambdaMerger;
private final InternalOptions options;
private final CfgPrinter printer;
private final GraphLense graphLense;
@@ -114,6 +116,8 @@
this.interfaceMethodRewriter =
(options.enableDesugaring && enableInterfaceMethodDesugaring())
? new InterfaceMethodRewriter(this, options) : null;
+ this.lambdaMerger = options.enableLambdaMerging
+ ? new LambdaMerger(appInfo.dexItemFactory, options.reporter) : null;
if (enableWholeProgramOptimizations) {
assert appInfo.hasLiveness();
this.nonNullTracker = new NonNullTracker();
@@ -360,6 +364,7 @@
ExecutorService executorService)
throws ExecutionException, ApiLevelException {
removeLambdaDeserializationMethods();
+ collectLambdaMergingCandidates(application);
// The process is in two phases.
// 1) Subject all DexEncodedMethods to optimization (except outlining).
@@ -397,6 +402,7 @@
desugarInterfaceMethods(builder, IncludeAllResources);
handleSynthesizedClassMapping(builder);
+ finalizeLambdaMerging(application, directFeedback, builder, executorService);
if (outliner != null) {
timing.begin("IR conversion phase 2");
@@ -433,6 +439,21 @@
return builder.build();
}
+ private void collectLambdaMergingCandidates(DexApplication application) {
+ if (lambdaMerger != null) {
+ lambdaMerger.collectGroupCandidates(application, appInfo.withLiveness(), options);
+ }
+ }
+
+ private void finalizeLambdaMerging(DexApplication application,
+ OptimizationFeedback directFeedback, Builder<?> builder, ExecutorService executorService)
+ throws ExecutionException, ApiLevelException {
+ if (lambdaMerger != null) {
+ lambdaMerger.applyLambdaClassMapping(
+ application, this, directFeedback, builder, executorService);
+ }
+ }
+
private void clearDexMethodCompilationState() {
appInfo.classes().forEach(this::clearDexMethodCompilationState);
}
@@ -488,8 +509,13 @@
}
public void optimizeSynthesizedClass(DexProgramClass clazz) throws ApiLevelException {
- // Process the generated class, but don't apply any outlining.
- clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
+ try {
+ codeRewriter.enterCachedClass(clazz);
+ // Process the generated class, but don't apply any outlining.
+ clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
+ } finally {
+ codeRewriter.leaveCachedClass(clazz);
+ }
}
public void optimizeSynthesizedMethod(DexEncodedMethod method) throws ApiLevelException {
@@ -644,6 +670,10 @@
interfaceMethodRewriter.rewriteMethodReferences(method, code);
assert code.isConsistentSSA();
}
+ if (lambdaMerger != null) {
+ lambdaMerger.processMethodCode(method, code);
+ assert code.isConsistentSSA();
+ }
if (options.outline.enabled) {
outlineHandler.accept(code, method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java
index bd38e1a..cf647a9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/SynthesizedLambdaSourceCode.java
@@ -8,10 +8,10 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.ir.synthetic.SingleBlockSourceCode;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
// Represents source code of synthesized lambda class methods.
-abstract class SynthesizedLambdaSourceCode extends SingleBlockSourceCode {
+abstract class SynthesizedLambdaSourceCode extends SyntheticSourceCode {
final DexMethod currentMethod;
final LambdaClass lambda;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 3dc38c6..a6b29e8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -14,6 +14,7 @@
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.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue.DexValueBoolean;
@@ -117,6 +118,12 @@
private final Set<DexMethod> libraryMethodsReturningReceiver;
private final InternalOptions options;
+ // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve
+ // the current class being optimized. Since all methods of this class are optimized
+ // together and are not concurrent to other optimizations, we just store current
+ // synthetic class.
+ private DexProgramClass cachedClass = null;
+
public CodeRewriter(
AppInfo appInfo, Set<DexMethod> libraryMethodsReturningReceiver, InternalOptions options) {
this.appInfo = appInfo;
@@ -1026,13 +1033,8 @@
if (code.computeNormalExitBlocks().isEmpty()) {
return;
}
- DexClass clazz = appInfo.definitionFor(method.method.getHolder());
- if (clazz == null) {
- // TODO(67672280): Synthesized lambda classes are also optimized. However, they are not
- // added to the AppInfo.
- assert method.accessFlags.isSynthetic();
- return;
- }
+ DexClass clazz = definitionFor(method.method.getHolder());
+ assert clazz != null;
DominatorTree dominatorTree = new DominatorTree(code);
Set<StaticPut> puts = Sets.newIdentityHashSet();
Map<DexField, StaticPut> dominatingPuts = Maps.newIdentityHashMap();
@@ -1167,6 +1169,23 @@
}
}
+ private DexClass definitionFor(DexType type) {
+ if (cachedClass != null && cachedClass.type == type) {
+ return cachedClass;
+ }
+ return appInfo.definitionFor(type);
+ }
+
+ public void enterCachedClass(DexProgramClass clazz) {
+ assert cachedClass == null;
+ cachedClass = clazz;
+ }
+
+ public void leaveCachedClass(DexProgramClass clazz) {
+ assert cachedClass == clazz;
+ cachedClass = null;
+ }
+
public void removeCasts(IRCode code, TypeEnvironment typeEnvironment) {
InstructionIterator it = code.instructionIterator();
boolean needToRemoveTrivialPhis = false;
@@ -2390,7 +2409,7 @@
if (type == dexItemFactory.throwableType) {
return true;
}
- DexClass dexClass = appInfo.definitionFor(type);
+ DexClass dexClass = definitionFor(type);
if (dexClass == null) {
throw new CompilationError("Class or interface " + type.toSourceString() +
" required for desugaring of try-with-resources is not found.");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CaptureSignature.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CaptureSignature.java
new file mode 100644
index 0000000..ede37b1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CaptureSignature.java
@@ -0,0 +1,159 @@
+// 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;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntLists;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Comparator;
+import java.util.function.IntFunction;
+
+// While mapping fields representing lambda captures we rearrange fields to make sure
+// lambdas having different order of the fields still can be merged. We also store
+// all captures of reference types in fields of java.lang.Objects class.
+//
+// This allows us to use same lambda groups class for these two different lambdas:
+//
+// Lambda Group Class Lambda$1 Lambda$2
+// Object $c0 int foo -> $c1 char foo -> $c2
+// int $c1 String[] bar -> $c0 int bar -> $c1
+// char $c2 char baz -> $c2 List baz -> $c0
+//
+// Capture signature is represented by a string with sorted shorties of field
+// types, such as "CIL" for both Lambda$1 and Lambda$2 in the above example.
+//
+public final class CaptureSignature {
+ private static final IntList EMPTY_LIST = IntLists.EMPTY_LIST;
+ private static final IntList SINGLE_LIST = IntLists.singleton(0);
+
+ private CaptureSignature() {
+ }
+
+ // Returns an array representing mapping of fields of the normalized capture
+ // into fields of original capture such that:
+ //
+ // mapping = getReverseCaptureMapping(...)
+ // <original-capture-index> = mapping[<normalized-capture-index>]
+ public static IntList getReverseCaptureMapping(DexType[] types) {
+ if (types.length == 0) {
+ return EMPTY_LIST;
+ }
+
+ if (types.length == 1) {
+ return SINGLE_LIST;
+ }
+
+ IntList result = new IntArrayList(types.length);
+ for (int i = 0; i < types.length; i++) {
+ result.add(i);
+ }
+ // Sort the indices by shorties (sorting is stable).
+ result.sort(Comparator.comparingInt(i -> types[i].toShorty()));
+ assert verifyMapping(result);
+ return result;
+ }
+
+ // Given a capture signature and an index returns the type of the field.
+ public static DexType fieldType(DexItemFactory factory, String capture, int index) {
+ switch (capture.charAt(index)) {
+ case 'L':
+ return factory.objectType;
+ case 'Z':
+ return factory.booleanType;
+ case 'B':
+ return factory.byteType;
+ case 'S':
+ return factory.shortType;
+ case 'C':
+ return factory.charType;
+ case 'I':
+ return factory.intType;
+ case 'F':
+ return factory.floatType;
+ case 'J':
+ return factory.longType;
+ case 'D':
+ return factory.doubleType;
+ default:
+ throw new Unreachable("Invalid capture character: " + capture.charAt(index));
+ }
+ }
+
+ private static String getCaptureSignature(int size, IntFunction<DexType> type) {
+ if (size == 0) {
+ return "";
+ }
+ if (size == 1) {
+ return Character.toString(type.apply(0).toShorty());
+ }
+
+ char[] chars = new char[size];
+ for (int i = 0; i < size; i++) {
+ chars[i] = type.apply(i).toShorty();
+ }
+ Arrays.sort(chars);
+ return new String(chars);
+ }
+
+ // Compute capture signature based on lambda class capture fields.
+ public static String getCaptureSignature(DexEncodedField[] fields) {
+ return getCaptureSignature(fields.length, i -> fields[i].field.type);
+ }
+
+ // Compute capture signature based on type list.
+ public static String getCaptureSignature(DexTypeList types) {
+ return getCaptureSignature(types.values.length, i -> types.values[i]);
+ }
+
+ // Having a list of fields of lambda captured values, maps one of them into
+ // an index of the appropriate field in normalized capture signature.
+ public static int mapFieldIntoCaptureIndex(
+ String capture, DexEncodedField[] lambdaFields, DexField fieldToMap) {
+ char fieldKind = fieldToMap.type.toShorty();
+ int numberOfSameCaptureKind = 0;
+ int result = -1;
+
+ for (DexEncodedField encodedField : lambdaFields) {
+ if (encodedField.field == fieldToMap) {
+ result = numberOfSameCaptureKind;
+ break;
+ }
+ if (encodedField.field.type.toShorty() == fieldKind) {
+ numberOfSameCaptureKind++;
+ }
+ }
+
+ assert result >= 0 : "Field was not found in the lambda class.";
+
+ for (int index = 0; index < capture.length(); index++) {
+ if (capture.charAt(index) == fieldKind) {
+ if (result == 0) {
+ return index;
+ }
+ result--;
+ }
+ }
+
+ throw new Unreachable("Were not able to map lambda field into capture index");
+ }
+
+ private static boolean verifyMapping(IntList mapping) {
+ BitSet bits = new BitSet();
+ for (Integer i : mapping) {
+ assert i >= 0 && i < mapping.size();
+ assert !bits.get(i);
+ bits.set(i);
+ }
+ return true;
+ }
+}
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
new file mode 100644
index 0000000..daa7811
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -0,0 +1,350 @@
+// 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;
+
+import com.android.tools.r8.errors.Unreachable;
+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.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.ConstClass;
+import com.android.tools.r8.ir.code.ConstMethodHandle;
+import com.android.tools.r8.ir.code.ConstMethodType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.kotlin.Kotlin;
+import java.util.ListIterator;
+import java.util.function.Function;
+
+// Performs processing of the method code (all methods) needed by lambda rewriter.
+//
+// Functionality can be modified by strategy (specific to lambda group) and lambda
+// type visitor.
+//
+// In general it is used to
+// (a) track the code for illegal lambda type usages inside the code, and
+// (b) patching valid lambda type references to point to lambda group classes instead.
+//
+// This class is also used inside particular strategies as a context of the instruction
+// being checked or patched, it provides access to code, block and instruction iterators.
+public abstract class CodeProcessor {
+ // Strategy (specific to lambda group) for detecting valid references to the
+ // lambda classes (of this group) and patching them with group class references.
+ public interface Strategy {
+ LambdaGroup group();
+
+ boolean isValidStaticFieldWrite(CodeProcessor context, DexField field);
+
+ boolean isValidStaticFieldRead(CodeProcessor context, DexField field);
+
+ boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field);
+
+ boolean isValidInstanceFieldRead(CodeProcessor context, DexField field);
+
+ boolean isValidInvoke(CodeProcessor context, InvokeMethod invoke);
+
+ boolean isValidNewInstance(CodeProcessor context, NewInstance invoke);
+
+ void patch(CodeProcessor context, NewInstance newInstance);
+
+ void patch(CodeProcessor context, InvokeMethod invoke);
+
+ void patch(CodeProcessor context, InstancePut instancePut);
+
+ void patch(CodeProcessor context, InstanceGet instanceGet);
+
+ void patch(CodeProcessor context, StaticPut staticPut);
+
+ void patch(CodeProcessor context, StaticGet staticGet);
+ }
+
+ // No-op strategy.
+ public static final Strategy NoOp = new Strategy() {
+ @Override
+ public LambdaGroup group() {
+ return null;
+ }
+
+ @Override
+ public boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field) {
+ return false;
+ }
+
+ @Override
+ public boolean isValidInstanceFieldRead(CodeProcessor context, DexField field) {
+ return false;
+ }
+
+ @Override
+ public boolean isValidStaticFieldWrite(CodeProcessor context, DexField field) {
+ return false;
+ }
+
+ @Override
+ public boolean isValidStaticFieldRead(CodeProcessor context, DexField field) {
+ return false;
+ }
+
+ @Override
+ public boolean isValidInvoke(CodeProcessor context, InvokeMethod invoke) {
+ return false;
+ }
+
+ @Override
+ public boolean isValidNewInstance(CodeProcessor context, NewInstance invoke) {
+ return false;
+ }
+
+ @Override
+ public void patch(CodeProcessor context, NewInstance newInstance) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void patch(CodeProcessor context, InvokeMethod invoke) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void patch(CodeProcessor context, InstancePut instancePut) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void patch(CodeProcessor context, InstanceGet instanceGet) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void patch(CodeProcessor context, StaticPut staticPut) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void patch(CodeProcessor context, StaticGet staticGet) {
+ throw new Unreachable();
+ }
+ };
+
+ public final DexItemFactory factory;
+ public final Kotlin kotlin;
+
+ // Defines a factory providing a strategy for a lambda type, returns
+ // NoOp strategy if the type is not a lambda.
+ private final Function<DexType, Strategy> strategyProvider;
+
+ // Visitor for lambda type references seen in unexpected places. Either
+ // invalidates the lambda or asserts depending on the processing phase.
+ private final LambdaTypeVisitor lambdaChecker;
+
+ // Specify the context of the current instruction: method/code/blocks/instructions.
+ public final DexEncodedMethod method;
+ public final IRCode code;
+ public final ListIterator<BasicBlock> blocks;
+ private InstructionListIterator instructions;
+
+ CodeProcessor(DexItemFactory factory,
+ Function<DexType, Strategy> strategyProvider,
+ LambdaTypeVisitor lambdaChecker,
+ DexEncodedMethod method, IRCode code) {
+
+ this.strategyProvider = strategyProvider;
+ this.factory = factory;
+ this.kotlin = factory.kotlin;
+ this.lambdaChecker = lambdaChecker;
+ this.method = method;
+ this.code = code;
+ this.blocks = code.listIterator();
+ }
+
+ public final InstructionListIterator instructions() {
+ assert instructions != null;
+ return instructions;
+ }
+
+ final void processCode() {
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ instructions = block.listIterator();
+ while (instructions.hasNext()) {
+ onInstruction(instructions.next());
+ }
+ }
+ }
+
+ private void onInstruction(Instruction instruction) {
+ if (instruction.isInvoke()) {
+ handle(instruction.asInvoke());
+ } else if (instruction.isNewInstance()) {
+ handle(instruction.asNewInstance());
+ } else if (instruction.isCheckCast()) {
+ handle(instruction.asCheckCast());
+ } else if (instruction.isNewArrayEmpty()) {
+ handle(instruction.asNewArrayEmpty());
+ } else if (instruction.isConstClass()) {
+ handle(instruction.asConstClass());
+ } else if (instruction.isConstMethodType()) {
+ handle(instruction.asConstMethodType());
+ } else if (instruction.isConstMethodHandle()) {
+ handle(instruction.asConstMethodHandle());
+ } else if (instruction.isInstanceGet()) {
+ handle(instruction.asInstanceGet());
+ } else if (instruction.isInstancePut()) {
+ handle(instruction.asInstancePut());
+ } else if (instruction.isStaticGet()) {
+ handle(instruction.asStaticGet());
+ } else if (instruction.isStaticPut()) {
+ handle(instruction.asStaticPut());
+ }
+ }
+
+ private void handle(Invoke invoke) {
+ if (invoke.isInvokeNewArray()) {
+ lambdaChecker.accept(invoke.asInvokeNewArray().getReturnType());
+ return;
+ }
+ if (invoke.isInvokeMultiNewArray()) {
+ lambdaChecker.accept(invoke.asInvokeMultiNewArray().getReturnType());
+ return;
+ }
+ if (invoke.isInvokeCustom()) {
+ lambdaChecker.accept(invoke.asInvokeCustom().getCallSite());
+ return;
+ }
+
+ InvokeMethod invokeMethod = invoke.asInvokeMethod();
+ Strategy strategy = strategyProvider.apply(invokeMethod.getInvokedMethod().holder);
+ if (strategy.isValidInvoke(this, invokeMethod)) {
+ // Invalidate signature, there still should not be lambda references.
+ lambdaChecker.accept(invokeMethod.getInvokedMethod().proto);
+ // Only rewrite references to lambda classes if we are outside the class.
+ if (invokeMethod.getInvokedMethod().holder != this.method.method.holder) {
+ process(strategy, invokeMethod);
+ }
+ return;
+ }
+
+ // For the rest invalidate any references.
+ if (invoke.isInvokePolymorphic()) {
+ lambdaChecker.accept(invoke.asInvokePolymorphic().getProto());
+ }
+ lambdaChecker.accept(invokeMethod.getInvokedMethod(), null);
+ }
+
+ private void handle(NewInstance newInstance) {
+ Strategy strategy = strategyProvider.apply(newInstance.clazz);
+ if (strategy.isValidNewInstance(this, newInstance)) {
+ // Only rewrite references to lambda classes if we are outside the class.
+ if (newInstance.clazz != this.method.method.holder) {
+ process(strategy, newInstance);
+ }
+ }
+ }
+
+ private void handle(CheckCast checkCast) {
+ lambdaChecker.accept(checkCast.getType());
+ }
+
+ private void handle(NewArrayEmpty newArrayEmpty) {
+ lambdaChecker.accept(newArrayEmpty.type);
+ }
+
+ private void handle(ConstClass constClass) {
+ lambdaChecker.accept(constClass.getValue());
+ }
+
+ private void handle(ConstMethodType constMethodType) {
+ lambdaChecker.accept(constMethodType.getValue());
+ }
+
+ private void handle(ConstMethodHandle constMethodHandle) {
+ lambdaChecker.accept(constMethodHandle.getValue());
+ }
+
+ private void handle(InstanceGet instanceGet) {
+ DexField field = instanceGet.getField();
+ Strategy strategy = strategyProvider.apply(field.clazz);
+ if (strategy.isValidInstanceFieldRead(this, field)) {
+ if (field.clazz != this.method.method.holder) {
+ // Only rewrite references to lambda classes if we are outside the class.
+ process(strategy, instanceGet);
+ }
+ } else {
+ lambdaChecker.accept(field.type);
+ }
+
+ // We avoid fields with type being lambda class, it is possible for
+ // a lambda to capture another lambda, but we don't support it for now.
+ lambdaChecker.accept(field.type);
+ }
+
+ private void handle(InstancePut instancePut) {
+ DexField field = instancePut.getField();
+ Strategy strategy = strategyProvider.apply(field.clazz);
+ if (strategy.isValidInstanceFieldWrite(this, field)) {
+ if (field.clazz != this.method.method.holder) {
+ // Only rewrite references to lambda classes if we are outside the class.
+ process(strategy, instancePut);
+ }
+ } else {
+ lambdaChecker.accept(field.type);
+ }
+
+ // We avoid fields with type being lambda class, it is possible for
+ // a lambda to capture another lambda, but we don't support it for now.
+ lambdaChecker.accept(field.type);
+ }
+
+ private void handle(StaticGet staticGet) {
+ DexField field = staticGet.getField();
+ Strategy strategy = strategyProvider.apply(field.clazz);
+ if (strategy.isValidStaticFieldRead(this, field)) {
+ if (field.clazz != this.method.method.holder) {
+ // Only rewrite references to lambda classes if we are outside the class.
+ process(strategy, staticGet);
+ }
+ } else {
+ lambdaChecker.accept(field.type);
+ lambdaChecker.accept(field.clazz);
+ }
+ }
+
+ private void handle(StaticPut staticPut) {
+ DexField field = staticPut.getField();
+ Strategy strategy = strategyProvider.apply(field.clazz);
+ if (strategy.isValidStaticFieldWrite(this, field)) {
+ if (field.clazz != this.method.method.holder) {
+ // Only rewrite references to lambda classes if we are outside the class.
+ process(strategy, staticPut);
+ }
+ } else {
+ lambdaChecker.accept(field.type);
+ lambdaChecker.accept(field.clazz);
+ }
+ }
+
+ abstract void process(Strategy strategy, InvokeMethod invokeMethod);
+
+ abstract void process(Strategy strategy, NewInstance newInstance);
+
+ abstract void process(Strategy strategy, InstancePut instancePut);
+
+ abstract void process(Strategy strategy, InstanceGet instanceGet);
+
+ abstract void process(Strategy strategy, StaticPut staticPut);
+
+ abstract void process(Strategy strategy, StaticGet staticGet);
+}
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
new file mode 100644
index 0000000..fea83eb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -0,0 +1,176 @@
+// 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;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+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.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.Lists;
+import com.google.common.io.BaseEncoding;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+// 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.
+public abstract class LambdaGroup {
+ public final LambdaGroupId id;
+
+ // Lambda group class name. Is intended to be stable and uniques.
+ // In current implementation is generated in following way:
+ // <optional-package>.-$$LambdaGroup$<HASH>
+ // with HASH generated based on names of the lambda class names
+ // of lambdas included in the group.
+ private DexType classType;
+
+ // 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 {
+ public int id;
+ public final DexProgramClass clazz;
+
+ LambdaInfo(int id, DexProgramClass clazz) {
+ this.id = id;
+ this.clazz = clazz;
+ }
+ }
+
+ public LambdaGroup(LambdaGroupId id) {
+ this.id = id;
+ }
+
+ public final DexType getGroupClassType() {
+ assert classType != null;
+ return classType;
+ }
+
+ public final List<LambdaInfo> lambdas() {
+ return Lists.newArrayList(lambdas.values());
+ }
+
+ public 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()) {
+ if (appInfo.isInMainDexList(type)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public final boolean containsLambda(DexType lambda) {
+ return lambdas.containsKey(lambda);
+ }
+
+ public final int lambdaId(DexType lambda) {
+ assert lambdas.containsKey(lambda);
+ return lambdas.get(lambda).id;
+ }
+
+ protected final DexEncodedField[] lambdaCaptureFields(DexType lambda) {
+ assert lambdas.containsKey(lambda);
+ return lambdas.get(lambda).clazz.instanceFields();
+ }
+
+ // Contains less than 2 elements?
+ final boolean isTrivial() {
+ return lambdas.size() < 2;
+ }
+
+ final void add(DexProgramClass lambda) {
+ assert !lambdas.containsKey(lambda.type);
+ lambdas.put(lambda.type, new LambdaInfo(lambdas.size(), lambda));
+ }
+
+ final void remove(DexType lambda) {
+ assert lambdas.containsKey(lambda);
+ lambdas.remove(lambda);
+ }
+
+ final void compact() {
+ int lastUsed = -1;
+ int lastSeen = -1;
+ for (Entry<DexType, LambdaInfo> entry : lambdas.entrySet()) {
+ Integer index = entry.getValue().id;
+ assert lastUsed <= lastSeen && lastSeen < index;
+ lastUsed++;
+ lastSeen = index;
+ if (lastUsed < index) {
+ entry.getValue().id = lastUsed;
+ }
+ }
+ }
+
+ public abstract Strategy getCodeStrategy();
+
+ public abstract ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
+ Kotlin kotlin, AppInfoWithSubtyping appInfo);
+
+ // Package for a lambda group class to be created in.
+ protected abstract String getTypePackage();
+
+ public final DexProgramClass synthesizeClass(DexItemFactory factory) {
+ assert classType == null;
+ 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);
+ return getBuilder(factory).synthesizeClass();
+ }
+
+ protected abstract LambdaGroupClassBuilder getBuilder(DexItemFactory factory);
+
+ private String createHash(List<LambdaInfo> lambdas) {
+ try {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(bytes);
+
+ // We will generate SHA-1 hash of the list of lambda classes represented in the group.
+ for (LambdaInfo lambda : lambdas) {
+ DexString descriptor = lambda.clazz.type.descriptor;
+ out.writeInt(descriptor.size); // To avoid same-prefix problem
+ out.write(descriptor.content);
+ }
+ out.close();
+
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ digest.update(bytes.toByteArray());
+ return BaseEncoding.base64Url().omitPadding().encode(digest.digest());
+ } catch (NoSuchAlgorithmException | IOException ex) {
+ throw new Unreachable("Cannot get SHA-1 message digest");
+ }
+ }
+
+ public static class LambdaStructureError extends Exception {
+ public LambdaStructureError(String cause) {
+ super(cause);
+ }
+ }
+}
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
new file mode 100644
index 0000000..5c591ab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
@@ -0,0 +1,73 @@
+// 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;
+
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+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;
+
+// Encapsulates lambda group class building logic and separates
+// it from the rest of lambda group functionality.
+public abstract class LambdaGroupClassBuilder<T extends LambdaGroup> {
+ 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() {
+ return new DexProgramClass(
+ group.getGroupClassType(),
+ null,
+ new SynthesizedOrigin(origin, getClass()),
+ buildAccessFlags(),
+ getSuperClassType(),
+ buildInterfaces(),
+ factory.createString(origin),
+ buildEnclosingMethodAttribute(),
+ buildInnerClasses(),
+ buildAnnotations(),
+ buildStaticFields(),
+ buildInstanceFields(),
+ buildDirectMethods(),
+ buildVirtualMethods());
+ }
+
+ protected abstract DexType getSuperClassType();
+
+ protected abstract ClassAccessFlags buildAccessFlags();
+
+ protected abstract EnclosingMethodAttribute buildEnclosingMethodAttribute();
+
+ protected abstract List<InnerClassAttribute> buildInnerClasses();
+
+ protected abstract DexAnnotationSet buildAnnotations();
+
+ protected abstract DexEncodedMethod[] buildVirtualMethods();
+
+ protected abstract DexEncodedMethod[] buildDirectMethods();
+
+ protected abstract DexEncodedField[] buildInstanceFields();
+
+ protected abstract DexEncodedField[] buildStaticFields();
+
+ protected abstract DexTypeList buildInterfaces();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupId.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupId.java
new file mode 100644
index 0000000..2c31541
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupId.java
@@ -0,0 +1,20 @@
+// 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;
+
+// Represents a lambda group identifier uniquely identifying the groups
+// of potentially mergeable lambdas.
+//
+// Implements hashCode/equals in a way that guarantees that if two lambda
+// classes has equal ids they belong to the same lambda group.
+public interface LambdaGroupId {
+ LambdaGroup createGroup();
+
+ @Override
+ int hashCode();
+
+ @Override
+ boolean equals(Object obj);
+}
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
new file mode 100644
index 0000000..11976b6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -0,0 +1,414 @@
+// 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;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
+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.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.conversion.IRConverter;
+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.kotlin.Kotlin;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+// Merging lambda classes into single lambda group classes. There are three flavors
+// of lambdas we are dealing with:
+// (a) lambda classes synthesized in desugaring, handles java lambdas
+// (b) k-style lambda classes synthesized by kotlin compiler
+// (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.
+//
+// In general we merge lambdas in 5 phases:
+// 1. collect all lambdas and compute group candidates. we do it synchronously
+// and ensure that the order of lambda groups and lambdas inside each group
+// is stable.
+// 2. analyze usages of lambdas and exclude lambdas with unexpected usage
+// NOTE: currently we consider *all* usages outside the code invalid
+// so we only need to patch method code when replacing the lambda class.
+// 3. exclude (invalidate) all lambda classes with usages we don't understand
+// or support, compact the remaining lambda groups, remove trivial groups
+// with less that 2 lambdas.
+// 4. replace lambda valid/supported class constructions with references to
+// lambda group classes.
+// 5. synthesize group lambda classes.
+//
+public final class LambdaMerger {
+ // Maps lambda into a group, only contains lambdas we decided to merge.
+ // NOTE: needs synchronization.
+ private final Map<DexType, LambdaGroup> lambdas = new IdentityHashMap<>();
+ // We use linked map to ensure stable ordering of the groups
+ // when they are processed sequentially.
+ // NOTE: needs synchronization.
+ private final Map<LambdaGroupId, LambdaGroup> groups = new LinkedHashMap<>();
+
+ // Since invalidating lambdas may happen concurrently we don't remove invalidated lambdas
+ // from groups (and `lambdas`) right away since the ordering may be important. Instead we
+ // collect invalidated lambdas and remove them from groups after analysis is done.
+ private final Set<DexType> invalidatedLambdas = Sets.newConcurrentHashSet();
+
+ // Methods which need to be patched to reference lambda group classes instead of the
+ // original lambda classes. The number of methods is expected to be small since there
+ // is a 1:1 relation between lambda and method it is defined in (unless such a method
+ // was inlined by either kotlinc or r8).
+ //
+ // Note that we don't track precisely lambda -> method mapping, so it may happen that
+ // we mark a method for further processing, and then invalidate the only lambda referenced
+ // from it. In this case we will reprocess method that does not need patching, but it
+ // should not be happening very frequently and we ignore possible overhead.
+ private final Set<DexEncodedMethod> methodsToReprocess = Sets.newIdentityHashSet();
+
+ private final DexItemFactory factory;
+ private final Kotlin kotlin;
+ private final DiagnosticsHandler reporter;
+
+ private BiFunction<DexEncodedMethod, IRCode, CodeProcessor> strategyFactory = null;
+
+ // Lambda visitor invalidating lambdas it sees.
+ private final LambdaTypeVisitor lambdaInvalidator;
+ // Lambda visitor throwing Unreachable on each lambdas it sees.
+ private final LambdaTypeVisitor lambdaChecker;
+
+ public LambdaMerger(DexItemFactory factory, DiagnosticsHandler reporter) {
+ this.factory = factory;
+ this.kotlin = factory.kotlin;
+ this.reporter = reporter;
+
+ this.lambdaInvalidator = new LambdaTypeVisitor(factory, this::isMergeableLambda,
+ this::invalidateLambda);
+ this.lambdaChecker = new LambdaTypeVisitor(factory, this::isMergeableLambda,
+ type -> {
+ throw new Unreachable("Unexpected lambda " + type.toSourceString());
+ });
+ }
+
+ private void invalidateLambda(DexType lambda) {
+ invalidatedLambdas.add(lambda);
+ }
+
+ private synchronized boolean isMergeableLambda(DexType lambda) {
+ return lambdas.containsKey(lambda);
+ }
+
+ private synchronized LambdaGroup getLambdaGroup(DexType lambda) {
+ return lambdas.get(lambda);
+ }
+
+ private synchronized void queueForProcessing(DexEncodedMethod method) {
+ methodsToReprocess.add(method);
+ }
+
+ // Collect all group candidates and assign unique lambda ids inside each group.
+ // We do this before methods are being processed to guarantee stable order of
+ // lambdas inside each group.
+ public final void collectGroupCandidates(
+ DexApplication app, AppInfoWithLiveness infoWithLiveness, InternalOptions options) {
+ assert infoWithLiveness != null;
+ // Collect lambda groups.
+ app.classes().stream()
+ .filter(cls -> !infoWithLiveness.isPinned(cls.type))
+ .filter(cls -> cls.hasKotlinInfo() &&
+ cls.getKotlinInfo().isSyntheticClass() &&
+ cls.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda())
+ .sorted((a, b) -> a.type.slowCompareTo(b.type)) // Ensure stable ordering.
+ .forEachOrdered(lambda -> {
+ try {
+ LambdaGroupId id = KStyleLambdaGroupIdFactory.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()));
+ }
+ });
+
+ // Remove trivial groups.
+ removeTrivialLambdaGroups();
+
+ assert strategyFactory == null;
+ strategyFactory = AnalysisStrategy::new;
+ }
+
+ // Is called by IRConverter::rewriteCode, performs different actions
+ // depending on phase:
+ // - in ANALYZE phase just analyzes invalid usages of lambda classes
+ // inside the method code, invalidated such lambda classes,
+ // collects methods that need to be patched.
+ // - in APPLY phase patches the code to use lambda group classes, also
+ // asserts that there are no more invalid lambda class references.
+ public final void processMethodCode(DexEncodedMethod method, IRCode code) {
+ if (strategyFactory != null) {
+ strategyFactory.apply(method, code).processCode();
+ }
+ }
+
+ public final void applyLambdaClassMapping(DexApplication app,
+ IRConverter converter, OptimizationFeedback feedback, Builder<?> builder,
+ ExecutorService executorService) throws ExecutionException, ApiLevelException {
+ if (lambdas.isEmpty()) {
+ return;
+ }
+
+ // Analyse references from program classes. We assume that this optimization
+ // is only used for full program analysis and there are no classpath classes.
+ analyzeReferencesInProgramClasses(app, executorService);
+
+ // Analyse more complex aspects of lambda classes including method code.
+ assert converter.appInfo.hasSubtyping();
+ analyzeLambdaClassesStructure(converter.appInfo.withSubtyping(), executorService);
+
+ // Remove invalidated lambdas, compact groups to ensure
+ // sequential lambda ids, create group lambda classes.
+ Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups();
+
+ // Switch to APPLY strategy.
+ this.strategyFactory = ApplyStrategy::new;
+
+ // Add synthesized lambda group classes to the builder.
+ for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
+ converter.optimizeSynthesizedClass(entry.getValue());
+ builder.addSynthesizedClass(entry.getValue(),
+ entry.getKey().shouldAddToMainDex(converter.appInfo));
+ }
+
+ // Rewrite lambda class references into lambda group class
+ // references inside methods from the processing queue.
+ rewriteLambdaReferences(converter, feedback);
+ this.strategyFactory = null;
+ }
+
+ private void analyzeReferencesInProgramClasses(
+ DexApplication app, ExecutorService service) throws ExecutionException {
+ List<Future<?>> futures = new ArrayList<>();
+ for (DexProgramClass clazz : app.classes()) {
+ futures.add(service.submit(() -> analyzeClass(clazz)));
+ }
+ ThreadUtils.awaitFutures(futures);
+ }
+
+ private void analyzeLambdaClassesStructure(
+ AppInfoWithSubtyping appInfo, ExecutorService service) throws ExecutionException {
+ List<Future<?>> futures = new ArrayList<>();
+ 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);
+ }
+ }));
+ }
+ }
+ ThreadUtils.awaitFutures(futures);
+ }
+
+ private synchronized Map<LambdaGroup, DexProgramClass> finalizeLambdaGroups() {
+ for (DexType lambda : invalidatedLambdas) {
+ LambdaGroup group = lambdas.get(lambda);
+ assert group != null;
+ lambdas.remove(lambda);
+ group.remove(lambda);
+ }
+ invalidatedLambdas.clear();
+
+ // Remove new trivial lambdas.
+ removeTrivialLambdaGroups();
+
+ // Compact lambda groups, synthesize lambda group classes.
+ Map<LambdaGroup, DexProgramClass> result = new LinkedHashMap<>();
+ for (LambdaGroup group : groups.values()) {
+ assert !group.isTrivial() : "No trivial group is expected here.";
+ group.compact();
+ result.put(group, group.synthesizeClass(factory));
+ }
+ return result;
+ }
+
+ private void removeTrivialLambdaGroups() {
+ Iterator<Entry<LambdaGroupId, LambdaGroup>> iterator = groups.entrySet().iterator();
+ while (iterator.hasNext()) {
+ 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);
+ }
+ }
+ }
+ }
+
+ private void rewriteLambdaReferences(
+ IRConverter converter, OptimizationFeedback feedback) throws ApiLevelException {
+ List<DexEncodedMethod> methods =
+ methodsToReprocess
+ .stream()
+ .sorted(DexEncodedMethod::slowCompare)
+ .collect(Collectors.toList());
+ for (DexEncodedMethod method : methods) {
+ converter.processMethod(method, feedback,
+ x -> false, CallSiteInformation.empty(), Outliner::noProcessing);
+ assert method.isProcessed();
+ }
+ }
+
+ private void analyzeClass(DexProgramClass clazz) {
+ lambdaInvalidator.accept(clazz.superType);
+ lambdaInvalidator.accept(clazz.interfaces);
+ lambdaInvalidator.accept(clazz.annotations);
+
+ for (DexEncodedField field : clazz.staticFields()) {
+ lambdaInvalidator.accept(field.annotations);
+ if (field.field.type != clazz.type) {
+ // Ignore static fields of the same type.
+ lambdaInvalidator.accept(field.field, clazz.type);
+ }
+ }
+ for (DexEncodedField field : clazz.instanceFields()) {
+ lambdaInvalidator.accept(field.annotations);
+ lambdaInvalidator.accept(field.field, clazz.type);
+ }
+
+ for (DexEncodedMethod method : clazz.directMethods()) {
+ lambdaInvalidator.accept(method.annotations);
+ lambdaInvalidator.accept(method.parameterAnnotations);
+ lambdaInvalidator.accept(method.method, clazz.type);
+ }
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
+ lambdaInvalidator.accept(method.annotations);
+ lambdaInvalidator.accept(method.parameterAnnotations);
+ lambdaInvalidator.accept(method.method, clazz.type);
+ }
+ }
+
+ private Strategy strategyProvider(DexType type) {
+ LambdaGroup group = this.getLambdaGroup(type);
+ return group != null ? group.getCodeStrategy() : CodeProcessor.NoOp;
+ }
+
+ private final class AnalysisStrategy extends CodeProcessor {
+ private AnalysisStrategy(DexEncodedMethod method, IRCode code) {
+ super(LambdaMerger.this.factory,
+ LambdaMerger.this::strategyProvider, lambdaInvalidator, method, code);
+ }
+
+ @Override
+ void process(Strategy strategy, InvokeMethod invokeMethod) {
+ queueForProcessing(method);
+ }
+
+ @Override
+ void process(Strategy strategy, NewInstance newInstance) {
+ queueForProcessing(method);
+ }
+
+ @Override
+ void process(Strategy strategy, InstancePut instancePut) {
+ queueForProcessing(method);
+ }
+
+ @Override
+ void process(Strategy strategy, InstanceGet instanceGet) {
+ queueForProcessing(method);
+ }
+
+ @Override
+ void process(Strategy strategy, StaticPut staticPut) {
+ queueForProcessing(method);
+ }
+
+ @Override
+ void process(Strategy strategy, StaticGet staticGet) {
+ queueForProcessing(method);
+ }
+ }
+
+ private final class ApplyStrategy extends CodeProcessor {
+ private ApplyStrategy(DexEncodedMethod method, IRCode code) {
+ super(LambdaMerger.this.factory,
+ LambdaMerger.this::strategyProvider, lambdaChecker, method, code);
+ }
+
+ @Override
+ void process(Strategy strategy, InvokeMethod invokeMethod) {
+ strategy.patch(this, invokeMethod);
+ }
+
+ @Override
+ void process(Strategy strategy, NewInstance newInstance) {
+ strategy.patch(this, newInstance);
+ }
+
+ @Override
+ void process(Strategy strategy, InstancePut instancePut) {
+ strategy.patch(this, instancePut);
+ }
+
+ @Override
+ void process(Strategy strategy, InstanceGet instanceGet) {
+ strategy.patch(this, instanceGet);
+ }
+
+ @Override
+ void process(Strategy strategy, StaticPut staticPut) {
+ strategy.patch(this, staticPut);
+ }
+
+ @Override
+ void process(Strategy strategy, StaticGet staticGet) {
+ strategy.patch(this, staticGet);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
new file mode 100644
index 0000000..4f56306
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
@@ -0,0 +1,155 @@
+// 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;
+
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexAnnotationSetRefList;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+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.DexMethodHandle;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueField;
+import com.android.tools.r8.graph.DexValue.DexValueMethod;
+import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+// Encapsulates the logic of visiting all lambda classes.
+final class LambdaTypeVisitor {
+ private final DexItemFactory factory;
+ private final Predicate<DexType> isLambdaType;
+ private final Consumer<DexType> onLambdaType;
+
+ LambdaTypeVisitor(DexItemFactory factory,
+ Predicate<DexType> isLambdaType, Consumer<DexType> onLambdaType) {
+ this.factory = factory;
+ this.isLambdaType = isLambdaType;
+ this.onLambdaType = onLambdaType;
+ }
+
+ void accept(DexCallSite callSite) {
+ accept(callSite.methodProto);
+ accept(callSite.bootstrapMethod);
+ for (DexValue value : callSite.bootstrapArgs) {
+ accept(value);
+ }
+ }
+
+ private void accept(DexValue value) {
+ if (value instanceof DexValueType) {
+ accept(((DexValueType) value).value);
+ return;
+ }
+ if (value instanceof DexValueArray) {
+ for (DexValue subValue : ((DexValueArray) value).getValues()) {
+ accept(subValue);
+ }
+ return;
+ }
+ if (value instanceof DexValueMethod) {
+ accept(((DexValueMethod) value).value, null);
+ return;
+ }
+ if (value instanceof DexValueMethodHandle) {
+ accept(((DexValueMethodHandle) value).value);
+ return;
+ }
+ if (value instanceof DexValueMethodType) {
+ accept(((DexValueMethodType) value).value);
+ return;
+ }
+ if (value instanceof DexValueField) {
+ accept(((DexValueField) value).value, null);
+ }
+ }
+
+ void accept(DexMethodHandle handle) {
+ if (handle.isFieldHandle()) {
+ accept(handle.asField(), null);
+ } else {
+ assert handle.isMethodHandle();
+ accept(handle.asMethod(), null);
+ }
+ }
+
+ void accept(DexField field, DexType holderToIgnore) {
+ accept(field.type);
+ if (holderToIgnore != field.clazz) {
+ accept(field.clazz);
+ }
+ }
+
+ void accept(DexMethod method, DexType holderToIgnore) {
+ if (holderToIgnore != method.holder) {
+ accept(method.holder);
+ }
+ accept(method.proto);
+ }
+
+ void accept(DexProto proto) {
+ accept(proto.returnType);
+ accept(proto.parameters);
+ }
+
+ void accept(DexTypeList types) {
+ for (DexType type : types.values) {
+ accept(type);
+ }
+ }
+
+ void accept(DexAnnotationSet annotationSet) {
+ for (DexAnnotation annotation : annotationSet.annotations) {
+ accept(annotation);
+ }
+ }
+
+ void accept(DexAnnotationSetRefList annotationSetRefList) {
+ for (DexAnnotationSet annotationSet : annotationSetRefList.values) {
+ accept(annotationSet);
+ }
+ }
+
+ private void accept(DexAnnotation annotation) {
+ accept(annotation.annotation);
+ }
+
+ private void accept(DexEncodedAnnotation annotation) {
+ accept(annotation.type);
+ for (DexAnnotationElement element : annotation.elements) {
+ accept(element);
+ }
+ }
+
+ private void accept(DexAnnotationElement element) {
+ accept(element.value);
+ }
+
+ void accept(DexType type) {
+ if (type == null) {
+ return;
+ }
+ if (type.isPrimitiveType() || type.isVoidType() || type.isPrimitiveArrayType()) {
+ return;
+ }
+ if (type.isArrayType()) {
+ accept(type.toArrayElementType(factory));
+ return;
+ }
+ if (isLambdaType.test(type)) {
+ onLambdaType.accept(type);
+ }
+ }
+}
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
new file mode 100644
index 0000000..1cc0dcd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/ClassInitializerSourceCode.java
@@ -0,0 +1,53 @@
+// 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
new file mode 100644
index 0000000..00571be
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/InstanceInitializerSourceCode.java
@@ -0,0 +1,55 @@
+// 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/KStyleConstants.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleConstants.java
new file mode 100644
index 0000000..619fa67
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleConstants.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.kstyle;
+
+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 {
+ // Default lambda class flags.
+ ClassAccessFlags LAMBDA_CLASS_FLAGS =
+ ClassAccessFlags.fromDexAccessFlags(Constants.ACC_FINAL);
+ // Access-relaxed lambda class flags.
+ ClassAccessFlags PUBLIC_LAMBDA_CLASS_FLAGS =
+ ClassAccessFlags.fromDexAccessFlags(Constants.ACC_PUBLIC + Constants.ACC_FINAL);
+
+ // Default lambda class initializer flags.
+ MethodAccessFlags CLASS_INITIALIZER_FLAGS =
+ MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_STATIC, true);
+ // Default lambda class constructor flags.
+ MethodAccessFlags CONSTRUCTOR_FLAGS =
+ MethodAccessFlags.fromSharedAccessFlags(0, true);
+ // Access-relaxed lambda class constructor flags.
+ MethodAccessFlags CONSTRUCTOR_FLAGS_RELAXED =
+ MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, true);
+
+ // Default main lambda method flags.
+ MethodAccessFlags MAIN_METHOD_FLAGS =
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC + Constants.ACC_FINAL, false);
+ // Default bridge lambda method flags.
+ MethodAccessFlags BRIDGE_METHOD_FLAGS =
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC + Constants.ACC_SYNTHETIC + Constants.ACC_BRIDGE, false);
+ // Bridge lambda method flags after inliner.
+ MethodAccessFlags BRIDGE_METHOD_FLAGS_FIXED =
+ MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false);
+
+ // Default singleton instance folding field flags.
+ FieldAccessFlags SINGLETON_FIELD_FLAGS =
+ FieldAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC + Constants.ACC_STATIC + Constants.ACC_FINAL);
+ // Default instance (lambda capture) field flags.
+ FieldAccessFlags CAPTURE_FIELD_FLAGS =
+ FieldAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_FINAL + Constants.ACC_SYNTHETIC);
+ // access-relaxed instance (lambda capture) field flags.
+ FieldAccessFlags CAPTURE_FIELD_FLAGS_RELAXED =
+ FieldAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC + Constants.ACC_FINAL + Constants.ACC_SYNTHETIC);
+}
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/kstyle/KStyleLambdaClassValidator.java
new file mode 100644
index 0000000..ffd0f7d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaClassValidator.java
@@ -0,0 +1,242 @@
+// 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.code.Const16;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.Format22c;
+import com.android.tools.r8.code.Iput;
+import com.android.tools.r8.code.IputBoolean;
+import com.android.tools.r8.code.IputByte;
+import com.android.tools.r8.code.IputChar;
+import com.android.tools.r8.code.IputObject;
+import com.android.tools.r8.code.IputShort;
+import com.android.tools.r8.code.IputWide;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.code.SputObject;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.Code;
+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.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.ThrowingConsumer;
+
+// Encapsulates the logic of deep-checking of the lambda class assumptions.
+//
+// For k-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
+// nothing expect instantiating the instance and storing it in
+// static instance field.
+//
+// (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;
+ private final AppInfoWithSubtyping appInfo;
+
+ KStyleLambdaClassValidator(Kotlin kotlin, KStyleLambdaGroup group, AppInfoWithSubtyping appInfo) {
+ this.kotlin = kotlin;
+ this.group = group;
+ this.appInfo = appInfo;
+ }
+
+ @Override
+ public void accept(DexClass lambda) throws LambdaStructureError {
+ if (!CaptureSignature.getCaptureSignature(lambda.instanceFields()).equals(group.id().capture)) {
+ throw new LambdaStructureError("capture signature was modified");
+ }
+
+ DexEncodedMethod classInitializer = null;
+ DexEncodedMethod instanceInitializer = null;
+ for (DexEncodedMethod method : lambda.directMethods()) {
+ // We only check bodies of class and instance initializers since we don't expect to
+ // see any static or private methods and all virtual methods will be translated into
+ // 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 (classInitializer != null || code == null || !code.isDexCode() ||
+ !validateStatelessLambdaClassInitializer(lambda, code.asDexCode())) {
+ throw new LambdaStructureError("static initializer code verification failed");
+ }
+ 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");
+ }
+ instanceInitializer = method;
+ }
+ }
+
+ if (group.isStateless() && (classInitializer == null)) {
+ throw new LambdaStructureError("missing static initializer on stateless lambda");
+ }
+
+ // This check is actually not required for lambda class merging, we only have to do
+ // this because group class method composition piggybacks on inlining which has
+ // assertions checking when we can inline force-inlined methods. So we double-check
+ // these assertion never triggers.
+ //
+ // NOTE: the real type name for lambda group class is not generated yet, but we
+ // can safely use a fake one here.
+ DexType fakeLambdaGroupType = kotlin.factory.createType(
+ "L" + group.getTypePackage() + "-$$LambdaGroup$XXXX;");
+ for (DexEncodedMethod method : lambda.virtualMethods()) {
+ if (!method.isInliningCandidate(fakeLambdaGroupType, Reason.SIMPLE, appInfo)) {
+ throw new LambdaStructureError("method " + method.method.toSourceString() +
+ " is not inline-able into lambda group class");
+ }
+ }
+ }
+
+ private boolean validateInstanceInitializer(DexClass lambda, Code code) {
+ 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;
+ }
+
+ // Capture field assignments: go through captured fields in assumed order
+ // and ensure they are assigned values from appropriate parameters.
+ int wideFieldsSeen = 0;
+ for (DexEncodedField field : captures) {
+ switch (field.field.type.toShorty()) {
+ case 'Z':
+ if (!(instructions[index] instanceof IputBoolean) ||
+ (instructions[index].getField() != field.field) ||
+ (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+ return false;
+ }
+ break;
+
+ case 'B':
+ if (!(instructions[index] instanceof IputByte) ||
+ (instructions[index].getField() != field.field) ||
+ (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+ return false;
+ }
+ break;
+
+ case 'S':
+ if (!(instructions[index] instanceof IputShort) ||
+ (instructions[index].getField() != field.field) ||
+ (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+ return false;
+ }
+ break;
+
+ case 'C':
+ if (!(instructions[index] instanceof IputChar) ||
+ (instructions[index].getField() != field.field) ||
+ (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+ return false;
+ }
+ break;
+
+ case 'I':
+ case 'F':
+ if (!(instructions[index] instanceof Iput) ||
+ (instructions[index].getField() != field.field) ||
+ (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+ return false;
+ }
+ break;
+
+ case 'J':
+ case 'D':
+ if (!(instructions[index] instanceof IputWide) ||
+ (instructions[index].getField() != field.field) ||
+ (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+ return false;
+ }
+ wideFieldsSeen++;
+ break;
+
+ case 'L':
+ if (!(instructions[index] instanceof IputObject) ||
+ (instructions[index].getField() != field.field) ||
+ (((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
+ return false;
+ }
+ break;
+
+ default:
+ throw new Unreachable();
+ }
+ 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;
+ }
+
+ private boolean validateStatelessLambdaClassInitializer(DexClass lambda, Code code) {
+ assert group.isStateless();
+ com.android.tools.r8.code.Instruction[] instructions = code.asDexCode().instructions;
+ if (instructions.length != 4) {
+ return false;
+ }
+ if (!(instructions[0] instanceof com.android.tools.r8.code.NewInstance) ||
+ ((com.android.tools.r8.code.NewInstance) instructions[0]).getType() != lambda.type) {
+ return false;
+ }
+ if (!(instructions[1] instanceof com.android.tools.r8.code.InvokeDirect) ||
+ !isLambdaInitializerMethod(lambda, instructions[1].getMethod())) {
+ return false;
+ }
+ if (!(instructions[2] instanceof SputObject) ||
+ !isLambdaSingletonField(lambda, instructions[2].getField())) {
+ return false;
+ }
+ if (!(instructions[3] instanceof ReturnVoid)) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isLambdaSingletonField(DexClass lambda, DexField field) {
+ return field.type == lambda.type && field.clazz == lambda.type &&
+ field.name == kotlin.functional.kotlinStyleLambdaInstanceName;
+ }
+
+ private boolean isLambdaInitializerMethod(DexClass holder, DexMethod method) {
+ return method.holder == holder.type && method.name == kotlin.factory.constructorMethodName &&
+ method.proto.parameters.isEmpty() && method.proto.returnType == kotlin.factory.voidType;
+ }
+}
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
new file mode 100644
index 0000000..54f1d3a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroup.java
@@ -0,0 +1,163 @@
+// 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/ir/optimize/lambda/kstyle/KStyleLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupClassBuilder.java
new file mode 100644
index 0000000..59de393
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupClassBuilder.java
@@ -0,0 +1,234 @@
+// 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.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexAnnotationSetRefList;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DexValue.DexValueNull;
+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.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+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 {
+
+ private final KStyleLambdaGroupId id;
+
+ KStyleLambdaGroupClassBuilder(DexItemFactory factory, KStyleLambdaGroup group, String origin) {
+ super(group, factory, origin);
+ this.id = group.id();
+ }
+
+ @Override
+ protected DexType getSuperClassType() {
+ return factory.kotlin.functional.lambdaType;
+ }
+
+ // Always generate public final classes.
+ @Override
+ protected ClassAccessFlags buildAccessFlags() {
+ return PUBLIC_LAMBDA_CLASS_FLAGS;
+ }
+
+ // Take the attribute from the group, if exists.
+ @Override
+ protected EnclosingMethodAttribute buildEnclosingMethodAttribute() {
+ return id.enclosing;
+ }
+
+ // Take the attribute from the group, if exists.
+ @Override
+ protected List<InnerClassAttribute> buildInnerClasses() {
+ return id.innerClassAccess == KStyleLambdaGroupId.MISSING_INNER_CLASS_ATTRIBUTE
+ ? Collections.emptyList()
+ : Lists.newArrayList(new InnerClassAttribute(
+ id.innerClassAccess, group.getGroupClassType(), null, null));
+ }
+
+ @Override
+ protected DexAnnotationSet buildAnnotations() {
+ // Kotlin-style lambdas supported by the merged may only contain optional signature and
+ // kotlin metadata annotations. We remove the latter, but keep the signature if present.
+ String signature = id.signature;
+ return signature == null ? DexAnnotationSet.empty()
+ : new DexAnnotationSet(new DexAnnotation[]{
+ DexAnnotation.createSignatureAnnotation(signature, factory)});
+ }
+
+ @Override
+ protected DexEncodedMethod[] buildVirtualMethods() {
+ // All virtual method are dispatched on $id$ field.
+ //
+ // For each of the virtual method name/signatures seen in the group
+ // we generate a correspondent method in lambda group class with same
+ // name/signatures dispatching the call to appropriate code taken
+ // from the lambda class.
+
+ Map<DexString, Map<DexProto, List<DexEncodedMethod>>> methods = collectVirtualMethods();
+ List<DexEncodedMethod> result = new ArrayList<>();
+
+ for (Entry<DexString, Map<DexProto, List<DexEncodedMethod>>> upper : methods.entrySet()) {
+ DexString methodName = upper.getKey();
+ for (Entry<DexProto, List<DexEncodedMethod>> inner : upper.getValue().entrySet()) {
+ // Methods for unique name/signature pair.
+ DexProto methodProto = inner.getKey();
+ List<DexEncodedMethod> implMethods = inner.getValue();
+
+ boolean isMainMethod =
+ id.mainMethodName == methodName && id.mainMethodProto == methodProto;
+
+ // For bridge methods we still use same PUBLIC FINAL as for the main method,
+ // since inlining removes BRIDGE & SYNTHETIC attributes from the bridge methods
+ // anyways and our new method is a product of inlining.
+ MethodAccessFlags accessFlags = MAIN_METHOD_FLAGS.copy();
+
+ // Mark all the impl methods for force inlining
+ // LambdaGroupVirtualMethodSourceCode relies on.
+ for (DexEncodedMethod implMethod : implMethods) {
+ if (implMethod != null) {
+ implMethod.markForceInline();
+ }
+ }
+
+ result.add(new DexEncodedMethod(
+ factory.createMethod(group.getGroupClassType(), methodProto, methodName),
+ accessFlags,
+ isMainMethod ? id.mainMethodAnnotations : DexAnnotationSet.empty(),
+ isMainMethod ? id.mainMethodParamAnnotations : DexAnnotationSetRefList.empty(),
+ new SynthesizedCode(
+ new VirtualMethodSourceCode(factory, group.getGroupClassType(),
+ methodProto, group.getLambdaIdField(factory), implMethods))));
+ }
+ }
+
+ return result.toArray(new DexEncodedMethod[result.size()]);
+ }
+
+ // Build a map of virtual methods with unique name/proto pointing to a list of methods
+ // from lambda classes implementing appropriate logic. The indices in the list correspond
+ // to lambda ids. Note that some of the slots in the lists may be empty, indicating the
+ // 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()) {
+ 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);
+ }
+ }
+ 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();
+ DexType groupClassType = group.getGroupClassType();
+
+ DexEncodedMethod[] result = new DexEncodedMethod[statelessLambda ? 2 : 1];
+ // Instance initializer mapping parameters into capture fields.
+ DexProto initializerProto = group.createConstructorProto(factory);
+ result[0] = new DexEncodedMethod(
+ factory.createMethod(groupClassType, initializerProto, factory.constructorMethodName),
+ 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())));
+
+ // Static class initializer for stateless lambdas.
+ if (statelessLambda) {
+ result[1] = new DexEncodedMethod(
+ factory.createMethod(groupClassType,
+ factory.createProto(factory.voidType),
+ factory.classConstructorMethodName),
+ CLASS_INITIALIZER_FLAGS,
+ DexAnnotationSet.empty(),
+ DexAnnotationSetRefList.empty(),
+ new SynthesizedCode(
+ new ClassInitializerSourceCode(
+ factory, groupClassType, lambdas.size(),
+ id -> group.getSingletonInstanceField(factory, id))));
+ }
+
+ return result;
+ }
+
+ @Override
+ protected DexEncodedField[] buildInstanceFields() {
+ // Lambda id field plus other fields defined by the capture signature.
+ String capture = id.capture;
+ int size = capture.length();
+ DexEncodedField[] result = new DexEncodedField[1 + size];
+
+ result[0] = new DexEncodedField(group.getLambdaIdField(factory),
+ CAPTURE_FIELD_FLAGS_RELAXED, DexAnnotationSet.empty(), null);
+
+ for (int id = 0; id < size; id++) {
+ result[id + 1] = new DexEncodedField(group.getCaptureField(factory, id),
+ CAPTURE_FIELD_FLAGS_RELAXED, DexAnnotationSet.empty(), null);
+ }
+
+ return result;
+ }
+
+ @Override
+ protected DexEncodedField[] buildStaticFields() {
+ 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;
+ }
+
+ @Override
+ protected DexTypeList buildInterfaces() {
+ return new DexTypeList(new DexType[]{id.iface});
+ }
+}
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/kstyle/KStyleLambdaGroupCodeStrategy.java
new file mode 100644
index 0000000..dd066b6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupCodeStrategy.java
@@ -0,0 +1,256 @@
+// 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.errors.Unreachable;
+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.CheckCast;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
+import com.android.tools.r8.ir.optimize.lambda.CodeProcessor;
+import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
+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;
+
+ KStyleLambdaGroupCodeStrategy(KStyleLambdaGroup group) {
+ this.group = group;
+ }
+
+ @Override
+ public LambdaGroup group() {
+ return group;
+ }
+
+ @Override
+ public boolean isValidStaticFieldWrite(CodeProcessor context, DexField field) {
+ DexType lambda = field.clazz;
+ assert group.containsLambda(lambda);
+ // Only support writes to singleton static field named 'INSTANCE' from lambda
+ // static class initializer.
+ return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName &&
+ lambda == field.type &&
+ context.factory.isClassConstructor(context.method.method) &&
+ context.method.method.holder == lambda;
+ }
+
+ @Override
+ public boolean isValidStaticFieldRead(CodeProcessor context, DexField field) {
+ DexType lambda = field.clazz;
+ assert group.containsLambda(lambda);
+ // Support all reads of singleton static field named 'INSTANCE'.
+ return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName &&
+ lambda == field.type;
+ }
+
+ @Override
+ public boolean isValidInstanceFieldWrite(CodeProcessor context, DexField field) {
+ DexType lambda = field.clazz;
+ DexMethod method = context.method.method;
+ assert group.containsLambda(lambda);
+ // Support writes to capture instance fields inside lambda constructor only.
+ return method.holder == lambda && context.factory.isConstructor(method);
+ }
+
+ @Override
+ public boolean isValidInstanceFieldRead(CodeProcessor context, DexField field) {
+ assert group.containsLambda(field.clazz);
+ // Support all reads from capture instance fields.
+ return true;
+ }
+
+ @Override
+ public boolean isValidNewInstance(CodeProcessor context, NewInstance invoke) {
+ // Only valid for stateful lambdas.
+ return !group.isStateless();
+ }
+
+ @Override
+ public boolean isValidInvoke(CodeProcessor context, InvokeMethod invoke) {
+ return isValidInitializerCall(context, invoke) || isValidVirtualCall(invoke);
+ }
+
+ private boolean isValidInitializerCall(CodeProcessor context, InvokeMethod invoke) {
+ 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,
+ // otherwise allow such a call only from the same class static initializer.
+ return (group.isStateless() == (context.method.method.holder == lambda)) &&
+ invoke.isInvokeDirect() &&
+ context.factory.isConstructor(method) &&
+ CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
+ }
+
+ private boolean isValidVirtualCall(InvokeMethod invoke) {
+ assert group.containsLambda(invoke.getInvokedMethod().holder);
+ // Allow all virtual calls.
+ return invoke.isInvokeVirtual();
+ }
+
+ @Override
+ public void patch(CodeProcessor context, NewInstance newInstance) {
+ NewInstance patchedNewInstance = new NewInstance(
+ group.getGroupClassType(), context.code.createValue(ValueType.OBJECT));
+ context.instructions().replaceCurrentInstruction(patchedNewInstance);
+ }
+
+ @Override
+ public void patch(CodeProcessor context, InvokeMethod invoke) {
+ assert group.containsLambda(invoke.getInvokedMethod().holder);
+ if (isValidInitializerCall(context, invoke)) {
+ patchInitializer(context, invoke.asInvokeDirect());
+ } else {
+ // Regular calls to virtual methods only need target method be replaced.
+ assert isValidVirtualCall(invoke);
+ DexMethod method = invoke.getInvokedMethod();
+ context.instructions().replaceCurrentInstruction(
+ new InvokeVirtual(mapVirtualMethod(context.factory, method),
+ createValueForType(context, method.proto.returnType), invoke.arguments()));
+ }
+ }
+
+ @Override
+ public void patch(CodeProcessor context, InstancePut instancePut) {
+ // Instance put should only appear in lambda class instance constructor,
+ // we should never get here since we never rewrite them.
+ throw new Unreachable();
+ }
+
+ @Override
+ public void patch(CodeProcessor context, InstanceGet instanceGet) {
+ DexField field = instanceGet.getField();
+ DexType fieldType = field.type;
+
+ // We need to insert remapped values and in case the capture field
+ // of type Object optionally cast to expected field.
+ InstanceGet newInstanceGet = new InstanceGet(instanceGet.getType(),
+ createValueForType(context, fieldType), instanceGet.object(),
+ mapCaptureField(context.factory, field.clazz, field));
+ context.instructions().replaceCurrentInstruction(newInstanceGet);
+
+ if (fieldType.isPrimitiveType() || fieldType == context.factory.objectType) {
+ return;
+ }
+
+ // Since all captured values of non-primitive types are stored in fields of type
+ // java.lang.Object, we need to cast them to appropriate type to satisfy the verifier.
+ Value newValue = context.code.createValue(ValueType.OBJECT, newInstanceGet.getLocalInfo());
+ newInstanceGet.outValue().replaceUsers(newValue);
+ CheckCast cast = new CheckCast(newValue, newInstanceGet.outValue(), fieldType);
+ cast.setPosition(newInstanceGet.getPosition());
+ context.instructions().add(cast);
+ // If the current block has catch handlers split the check cast into its own block.
+ // Since new cast is never supposed to fail, we leave catch handlers empty.
+ if (cast.getBlock().hasCatchHandlers()) {
+ context.instructions().previous();
+ context.instructions().split(context.code, 1, context.blocks);
+ }
+ }
+
+ @Override
+ public void patch(CodeProcessor context, StaticPut staticPut) {
+ // Static put should only appear in lambda class static initializer,
+ // we should never get here since we never rewrite them.
+ throw new Unreachable();
+ }
+
+ @Override
+ public void patch(CodeProcessor context, StaticGet staticGet) {
+ context.instructions().replaceCurrentInstruction(
+ new StaticGet(staticGet.getType(), context.code.createValue(ValueType.OBJECT),
+ mapSingletonInstanceField(context.factory, staticGet.getField())));
+ }
+
+ private void patchInitializer(CodeProcessor context, InvokeDirect invoke) {
+ // Patching includes:
+ // - change of methods
+ // - adding lambda id as the first argument
+ // - reshuffling other arguments (representing captured values)
+ // according to capture signature of the group.
+
+ DexMethod method = invoke.getInvokedMethod();
+ DexType lambda = method.holder;
+
+ // Create constant with lambda id.
+ Value lambdaIdValue = context.code.createValue(ValueType.INT);
+ ConstNumber lambdaId = new ConstNumber(lambdaIdValue, group.lambdaId(lambda));
+ lambdaId.setPosition(invoke.getPosition());
+ context.instructions().previous();
+ context.instructions().add(lambdaId);
+
+ // Create a new InvokeDirect instruction.
+ Instruction next = context.instructions().next();
+ assert next == invoke;
+
+ DexMethod newTarget = mapInitializerMethod(context.factory, method);
+ List<Value> newArguments = mapInitializerArgs(lambdaIdValue, invoke.arguments(), method.proto);
+ context.instructions().replaceCurrentInstruction(
+ new InvokeDirect(newTarget, context.code.createValue(ValueType.OBJECT), newArguments)
+ );
+ }
+
+ private Value createValueForType(CodeProcessor context, DexType returnType) {
+ return returnType == context.factory.voidType ? null :
+ context.code.createValue(ValueType.fromDexType(returnType));
+ }
+
+ private List<Value> mapInitializerArgs(
+ Value lambdaIdValue, List<Value> oldArguments, DexProto proto) {
+ assert oldArguments.size() == proto.parameters.size() + 1;
+ List<Value> newArguments = new ArrayList<>();
+ newArguments.add(oldArguments.get(0)); // receiver
+ newArguments.add(lambdaIdValue); // lambda-id
+ List<Integer> reverseMapping =
+ CaptureSignature.getReverseCaptureMapping(proto.parameters.values);
+ for (int index : reverseMapping) {
+ // <original-capture-index> = mapping[<normalized-capture-index>]
+ newArguments.add(oldArguments.get(index + 1 /* after receiver */));
+ }
+ return newArguments;
+ }
+
+ // Map lambda class initializer into lambda group class initializer.
+ private DexMethod mapInitializerMethod(DexItemFactory factory, DexMethod method) {
+ assert factory.isConstructor(method);
+ assert CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
+ return factory.createMethod(group.getGroupClassType(),
+ group.createConstructorProto(factory), method.name);
+ }
+
+ // Map lambda class virtual method into lambda group class method.
+ private DexMethod mapVirtualMethod(DexItemFactory factory, DexMethod method) {
+ return factory.createMethod(group.getGroupClassType(), method.proto, method.name);
+ }
+
+ // Map lambda class capture field into lambda group class capture field.
+ private DexField mapCaptureField(DexItemFactory factory, DexType lambda, DexField field) {
+ return group.getCaptureField(factory, group.mapFieldIntoCaptureIndex(lambda, field));
+ }
+
+ // Map lambda class initializer into lambda group class initializer.
+ private DexField mapSingletonInstanceField(DexItemFactory factory, DexField field) {
+ return group.getSingletonInstanceField(factory, group.lambdaId(field.clazz));
+ }
+}
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/kstyle/KStyleLambdaGroupId.java
new file mode 100644
index 0000000..6b6f73b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupId.java
@@ -0,0 +1,138 @@
+// 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.DexAnnotationSet;
+import com.android.tools.r8.graph.DexAnnotationSetRefList;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+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.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;
+
+ private final int hash;
+
+ // Capture signature.
+ final String capture;
+ // Kotlin functional interface.
+ final DexType iface;
+ // Package (in case access relaxation is enabled root package is used).
+ final String pkg;
+ // Generic signature of the lambda class.
+ final String signature;
+
+ // Characteristics of the main lambda method. Kotlin generates one main method with
+ // the signature matching the lambda signature, and a bridge method (if needed)
+ // forwarding the call via interface to the main method. Main method may have
+ // generic signature and parameter annotations defining nullability.
+ //
+ // TODO: address cases when main method is removed after inlining.
+ // (the main method if created is supposed to be inlined, since it is always
+ // only called from the bridge, and removed. In this case the method with
+ // signature and annotations is removed allowing more lambda to be merged.)
+ final DexString mainMethodName;
+ final DexProto mainMethodProto;
+ final DexAnnotationSet mainMethodAnnotations;
+ final DexAnnotationSetRefList mainMethodParamAnnotations;
+
+ final EnclosingMethodAttribute enclosing;
+
+ // Note that lambda classes are always created as _anonymous_ inner classes. We only
+ // need to store the fact that the class had this attribute and if it had store the
+ // access from InnerClassAttribute.
+ final int innerClassAccess;
+
+ KStyleLambdaGroupId(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);
+ this.capture = capture;
+ this.iface = iface;
+ this.pkg = pkg;
+ this.signature = signature;
+ this.mainMethodName = mainMethod.method.name;
+ this.mainMethodProto = mainMethod.method.proto;
+ this.mainMethodAnnotations = mainMethod.annotations;
+ this.mainMethodParamAnnotations = mainMethod.parameterAnnotations;
+ this.innerClassAccess = inner != null ? inner.getAccess() : MISSING_INNER_CLASS_ATTRIBUTE;
+ this.enclosing = enclosing;
+ this.hash = computeHashCode();
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ private int computeHashCode() {
+ int hash = capture.hashCode() * 7;
+ hash += iface.hashCode() * 17;
+ hash += pkg.hashCode() * 37;
+ hash += signature != null ? signature.hashCode() * 47 : 0;
+ hash += mainMethodName.hashCode() * 71;
+ hash += mainMethodProto.hashCode() * 89;
+ hash += mainMethodAnnotations != null ? mainMethodAnnotations.hashCode() * 101 : 0;
+ hash += mainMethodParamAnnotations != null ? mainMethodParamAnnotations.hashCode() * 113 : 0;
+ hash += innerClassAccess * 131;
+ hash += enclosing != null ? enclosing.hashCode() * 211 : 0;
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof KStyleLambdaGroupId)) {
+ return false;
+ }
+ KStyleLambdaGroupId other = (KStyleLambdaGroupId) obj;
+ return capture.equals(other.capture) &&
+ iface == other.iface &&
+ pkg.equals(other.pkg) &&
+ mainMethodName == other.mainMethodName &&
+ mainMethodProto == other.mainMethodProto &&
+ (mainMethodAnnotations == null ? other.mainMethodAnnotations == null
+ : mainMethodAnnotations.equals(other.mainMethodAnnotations)) &&
+ (mainMethodParamAnnotations == null ? other.mainMethodParamAnnotations == null
+ : mainMethodParamAnnotations.equals(other.mainMethodParamAnnotations)) &&
+ (signature == null ? other.signature == null : signature.equals(other.signature)) &&
+ innerClassAccess == other.innerClassAccess &&
+ (enclosing == null ? other.enclosing == null : enclosing.equals(other.enclosing));
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("Kotlin-style lambda group")
+ .append("\n capture: ").append(capture)
+ .append("\n interface: ").append(iface.descriptor)
+ .append("\n package: ").append(pkg)
+ .append("\n signature: ").append(signature)
+ .append("\n main method name: ").append(mainMethodName.toString())
+ .append("\n main method: ").append(mainMethodProto.toSourceString())
+ .append("\n main annotations: ").append(mainMethodAnnotations)
+ .append("\n main param annotations: ").append(mainMethodParamAnnotations)
+ .append("\n inner: ")
+ .append(innerClassAccess == MISSING_INNER_CLASS_ATTRIBUTE ? "none" : innerClassAccess);
+ if (enclosing != null) {
+ if (enclosing.getEnclosingClass() != null) {
+ builder.append("\n enclosingClass: ")
+ .append(enclosing.getEnclosingClass().descriptor);
+ } else {
+ builder.append("\n enclosingMethod: ")
+ .append(enclosing.getEnclosingMethod().toSourceString());
+ }
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public LambdaGroup createGroup() {
+ return new KStyleLambdaGroup(this);
+ }
+}
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/kstyle/KStyleLambdaGroupIdFactory.java
new file mode 100644
index 0000000..7a516cd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupIdFactory.java
@@ -0,0 +1,240 @@
+// 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.errors.Unreachable;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.DexAnnotation;
+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.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
+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;
+import java.util.List;
+
+public final class KStyleLambdaGroupIdFactory implements KStyleConstants {
+ private KStyleLambdaGroupIdFactory() {
+ }
+
+ // Creates a lambda group id for kotlin style lambda. Should never return null, if the lambda
+ // does not pass pre-requirements (mostly by not meeting high-level structure expectations)
+ // should throw LambdaStructureError leaving the caller to decide if/how it needs to be reported.
+ //
+ // At this point we only perform high-level checks before qualifying the lambda as a candidate
+ // for merging and assigning lambda group id. We can NOT perform checks on method bodies since
+ // 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);
+
+ 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());
+ }
+
+ private static DexEncodedMethod validateVirtualMethods(DexClass lambda)
+ throws LambdaStructureError {
+ DexEncodedMethod mainMethod = null;
+
+ for (DexEncodedMethod method : lambda.virtualMethods()) {
+ if (method.accessFlags.equals(MAIN_METHOD_FLAGS)) {
+ if (mainMethod != null) {
+ throw new LambdaStructureError("more than one main method found");
+ }
+ mainMethod = method;
+ } else {
+ checkAccessFlags("unexpected virtual method access flags",
+ method.accessFlags, BRIDGE_METHOD_FLAGS, BRIDGE_METHOD_FLAGS_FIXED);
+ checkDirectMethodAnnotations(method);
+ }
+ }
+
+ if (mainMethod == null) {
+ throw new LambdaStructureError("no main method found");
+ }
+ return mainMethod;
+ }
+
+ private static 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()) {
+ throw new LambdaStructureError("is not anonymous");
+ }
+ return innerClass;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static String validateAnnotations(Kotlin kotlin, DexClass lambda)
+ throws LambdaStructureError {
+ String signature = null;
+ if (!lambda.annotations.isEmpty()) {
+ for (DexAnnotation annotation : lambda.annotations.annotations) {
+ if (DexAnnotation.isSignatureAnnotation(annotation, kotlin.factory)) {
+ signature = DexAnnotation.getSignature(annotation);
+ continue;
+ }
+
+ if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
+ // Ignore kotlin metadata on lambda classes. Metadata on synthetic
+ // classes exists but is not used in the current Kotlin version (1.2.21)
+ // and newly generated lambda _group_ class is not exactly a kotlin class.
+ continue;
+ }
+
+ throw new LambdaStructureError(
+ "unexpected annotation: " + annotation.annotation.type.toSourceString());
+ }
+ }
+ return signature;
+ }
+
+ private static void validateStaticFields(Kotlin kotlin, DexClass lambda)
+ throws LambdaStructureError {
+ DexEncodedField[] staticFields = lambda.staticFields();
+ if (staticFields.length == 1) {
+ DexEncodedField field = staticFields[0];
+ if (field.field.name != kotlin.functional.kotlinStyleLambdaInstanceName ||
+ field.field.type != lambda.type || !field.accessFlags.isPublic() ||
+ !field.accessFlags.isFinal() || !field.accessFlags.isStatic()) {
+ throw new LambdaStructureError("unexpected static field " + field.toSourceString());
+ }
+ // No state if the lambda is a singleton.
+ if (lambda.instanceFields().length > 0) {
+ throw new LambdaStructureError("has instance fields along with INSTANCE");
+ }
+ checkAccessFlags("static field access flags", field.accessFlags, SINGLETON_FIELD_FLAGS);
+ checkFieldAnnotations(field);
+
+ } 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)
+ throws LambdaStructureError {
+ DexEncodedField[] instanceFields = lambda.instanceFields();
+ for (DexEncodedField field : instanceFields) {
+ checkAccessFlags("capture field access flags", field.accessFlags,
+ accessRelaxed ? CAPTURE_FIELD_FLAGS_RELAXED : CAPTURE_FIELD_FLAGS);
+ checkFieldAnnotations(field);
+ }
+ return CaptureSignature.getCaptureSignature(instanceFields);
+ }
+
+ private static void validateDirectMethods(DexClass lambda) throws LambdaStructureError {
+ DexEncodedMethod[] directMethods = lambda.directMethods();
+ for (DexEncodedMethod method : directMethods) {
+ if (method.isClassInitializer()) {
+ // We expect to see class initializer only if there is a singleton field.
+ if (lambda.staticFields().length != 1) {
+ throw new LambdaStructureError("has static initializer, but no singleton field");
+ }
+ checkAccessFlags("unexpected static initializer access flags",
+ method.accessFlags, CLASS_INITIALIZER_FLAGS);
+ checkDirectMethodAnnotations(method);
+
+ } else if (method.isStaticMethod()) {
+ throw new LambdaStructureError(
+ "unexpected static method: " + method.method.toSourceString());
+
+ } else if (method.isInstanceInitializer()) {
+ // Lambda class is expected to have one constructor
+ // with parameters matching capture signature.
+ DexType[] parameters = method.method.proto.parameters.values;
+ DexEncodedField[] instanceFields = lambda.instanceFields();
+ if (parameters.length != instanceFields.length) {
+ throw new LambdaStructureError("constructor parameters don't match captured values.");
+ }
+ for (int i = 0; i < parameters.length; i++) {
+ if (parameters[i] != instanceFields[i].field.type) {
+ throw new LambdaStructureError("constructor parameters don't match captured values.");
+ }
+ }
+ checkAccessFlags("unexpected constructor access flags",
+ method.accessFlags, CONSTRUCTOR_FLAGS, CONSTRUCTOR_FLAGS_RELAXED);
+ checkDirectMethodAnnotations(method);
+
+ } else {
+ throw new Unreachable();
+ }
+ }
+ }
+
+ private static void checkDirectMethodAnnotations(DexEncodedMethod method)
+ throws LambdaStructureError {
+ if (!method.annotations.isEmpty()) {
+ throw new LambdaStructureError("unexpected method annotations [" +
+ method.annotations.toSmaliString() + "] on " + method.method.toSourceString());
+ }
+ if (!method.parameterAnnotations.isEmpty()) {
+ throw new LambdaStructureError("unexpected method parameters annotations [" +
+ method.annotations.toSmaliString() + "] on " + method.method.toSourceString());
+ }
+ }
+
+ private static void checkFieldAnnotations(DexEncodedField field) throws LambdaStructureError {
+ if (!field.annotations.isEmpty()) {
+ throw new LambdaStructureError("unexpected field annotations [" +
+ field.annotations.toSmaliString() + "] on " + field.field.toSourceString());
+ }
+ }
+
+ @SafeVarargs
+ private static <T extends AccessFlags> void checkAccessFlags(
+ String message, T actual, T... expected) throws LambdaStructureError {
+ for (T flag : expected) {
+ if (flag.equals(actual)) {
+ return;
+ }
+ }
+ throw new LambdaStructureError(message);
+ }
+}
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/kstyle/VirtualMethodSourceCode.java
new file mode 100644
index 0000000..8921299
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/VirtualMethodSourceCode.java
@@ -0,0 +1,102 @@
+// 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.DexEncodedMethod;
+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.code.Invoke.Type;
+import com.android.tools.r8.ir.code.Value;
+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 java.util.ArrayList;
+import java.util.List;
+
+final class VirtualMethodSourceCode extends SyntheticSourceCode {
+ private final DexItemFactory factory;
+ private final DexField idField;
+ private final List<DexEncodedMethod> implMethods;
+
+ VirtualMethodSourceCode(DexItemFactory factory, DexType groupClass,
+ DexProto proto, DexField idField, List<DexEncodedMethod> implMethods) {
+ super(groupClass, proto);
+ this.factory = factory;
+ this.idField = idField;
+ this.implMethods = implMethods;
+ }
+
+ @Override
+ protected void prepareInstructions() {
+ int implMethodCount = implMethods.size();
+ int paramCount = getParamCount();
+ List<Value> arguments = new ArrayList<>(paramCount + 1);
+
+ // We generate a single switch on lambda $id value read from appropriate
+ // field, and for each lambda id generate a call to appropriate method of
+ // the lambda class. Since this methods are marked as 'force inline',
+ // they are inlined by the inliner.
+
+ // Return value register if needed.
+ DexType returnType = proto.returnType;
+ boolean returnsValue = returnType != factory.voidType;
+ ValueType retValueType = returnsValue ? ValueType.fromDexType(returnType) : null;
+ int retRegister = returnsValue ? nextRegister(retValueType) : -1;
+
+ // Lambda id register to switch on.
+ int idRegister = nextRegister(ValueType.INT);
+ add(builder -> builder.addInstanceGet(idRegister, getReceiverRegister(), idField));
+
+ // Switch on id.
+ // Note that 'keys' and 'offsets' are just captured here and filled
+ // in with values when appropriate basic blocks are created.
+ int[] keys = new int[implMethodCount];
+ int[] offsets = new int[implMethodCount];
+ int[] fallthrough = new int[1]; // Array as a container for late initialization.
+ int switchIndex = lastInstructionIndex();
+ add(builder -> builder.addSwitch(idRegister, keys, fallthrough[0], offsets),
+ builder -> endsSwitch(builder, switchIndex, fallthrough[0], offsets));
+
+ // Fallthrough treated as unreachable.
+ fallthrough[0] = nextInstructionIndex();
+ int nullRegister = nextRegister(ValueType.OBJECT);
+ add(builder -> builder.addNullConst(nullRegister));
+ add(builder -> builder.addThrow(nullRegister), endsBlock);
+
+ // Blocks for each lambda id.
+ for (int i = 0; i < implMethodCount; i++) {
+ keys[i] = i;
+ DexEncodedMethod impl = implMethods.get(i);
+ if (impl == null) {
+ // Virtual method is missing in lambda class.
+ offsets[i] = fallthrough[0];
+ continue;
+ }
+ offsets[i] = nextInstructionIndex();
+
+ // Emit fake call on `this` receiver.
+ add(builder -> {
+ if (arguments.isEmpty()) {
+ // Late initialization of argument list.
+ arguments.add(getReceiverValue());
+ for (int index = 0; index < paramCount; index++) {
+ arguments.add(getParamValue(index));
+ }
+ }
+ builder.addInvoke(Type.VIRTUAL, impl.method, impl.method.proto, arguments);
+ });
+
+ // Handle return value if needed.
+ if (returnsValue) {
+ add(builder -> builder.addMoveResult(retRegister));
+ add(builder -> builder.addReturn(retValueType, retRegister), endsBlock);
+ } else {
+ add(IRBuilder::addReturn, endsBlock);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 2bbdaf9..f353a67 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -303,6 +303,13 @@
return false;
}
+ public boolean hasConflictingRegisters(LiveIntervals other) {
+ if (other.usesRegister(register) || (getType().isWide() && other.usesRegister(register + 1))) {
+ return true;
+ }
+ return false;
+ }
+
public void clearRegisterAssignment() {
register = NO_REGISTER;
hint = null;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
index 4c59686..9259485 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
@@ -16,7 +16,7 @@
import java.util.List;
// Source code representing simple forwarding method.
-public final class ForwardMethodSourceCode extends SingleBlockSourceCode {
+public final class ForwardMethodSourceCode extends SyntheticSourceCode {
private final DexType targetReceiver;
private final DexMethod target;
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index 26ef333..62238c3 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -56,7 +56,7 @@
public void registerCaughtTypes(Consumer<DexType> dexTypeConsumer) {
// Support for synthesized code with catch handler is not implemented.
// Let's check that we're in a well known where no catch handler is possible.
- assert sourceCode.instructionCount() == 1 || sourceCode instanceof SingleBlockSourceCode;
+ assert sourceCode.instructionCount() == 1 || sourceCode instanceof SyntheticSourceCode;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
similarity index 82%
rename from src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
rename to src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index ed4a207..5d03dfd 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -21,8 +21,11 @@
import com.android.tools.r8.utils.ThrowingConsumer;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Predicate;
-public abstract class SingleBlockSourceCode implements SourceCode {
+public abstract class SyntheticSourceCode implements SourceCode {
+ protected final static Predicate<IRBuilder> doesNotEndBlock = x -> false;
+ protected final static Predicate<IRBuilder> endsBlock = x -> true;
protected final DexType receiver;
protected final DexProto proto;
@@ -41,8 +44,9 @@
// Instruction constructors
private List<ThrowingConsumer<IRBuilder, ApiLevelException>> constructors = new ArrayList<>();
+ private List<Predicate<IRBuilder>> traceEvents = new ArrayList<>();
- protected SingleBlockSourceCode(DexType receiver, DexProto proto) {
+ protected SyntheticSourceCode(DexType receiver, DexProto proto) {
assert proto != null;
this.receiver = receiver;
this.proto = proto;
@@ -60,7 +64,13 @@
}
protected final void add(ThrowingConsumer<IRBuilder, ApiLevelException> constructor) {
+ add(constructor, doesNotEndBlock);
+ }
+
+ protected final void add(
+ ThrowingConsumer<IRBuilder, ApiLevelException> constructor, Predicate<IRBuilder> traceEvent) {
constructors.add(constructor);
+ traceEvents.add(traceEvent);
}
protected final int nextRegister(ValueType type) {
@@ -104,6 +114,14 @@
return constructors.size();
}
+ protected final int lastInstructionIndex() {
+ return constructors.size() - 1;
+ }
+
+ protected final int nextInstructionIndex() {
+ return constructors.size();
+ }
+
@Override
public final int instructionIndex(int instructionOffset) {
return instructionOffset;
@@ -121,7 +139,8 @@
@Override
public final int traceInstruction(int instructionIndex, IRBuilder builder) {
- return (instructionIndex == constructors.size() - 1) ? instructionIndex : -1;
+ return (traceEvents.get(instructionIndex).test(builder) ||
+ (instructionIndex == constructors.size() - 1)) ? instructionIndex : -1;
}
@Override
@@ -139,6 +158,7 @@
@Override
public final void clear() {
constructors = null;
+ traceEvents = null;
paramRegisters = null;
paramValues = null;
receiverValue = null;
@@ -219,4 +239,15 @@
public final boolean verifyRegister(int register) {
return true;
}
+
+ // To be used as a tracing event for switch instruction.,
+ protected boolean endsSwitch(
+ IRBuilder builder, int switchIndex, int fallthrough, int[] offsets) {
+ // ensure successors of switch instruction
+ for (int offset : offsets) {
+ builder.ensureNormalSuccessorBlock(switchIndex, offset);
+ }
+ builder.ensureNormalSuccessorBlock(switchIndex, fallthrough);
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
new file mode 100644
index 0000000..9b0415b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -0,0 +1,156 @@
+// 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 com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+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.DexValueInt;
+import com.android.tools.r8.kotlin.KotlinSyntheticClass.Flavour;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+/** Class provides basic information about symbols related to Kotlin support. */
+public final class Kotlin {
+ public final DexItemFactory factory;
+
+ public final Functional functional;
+ public final Intrinsics intrinsics;
+ public final Metadata metadata;
+
+ public Kotlin(DexItemFactory factory) {
+ this.factory = factory;
+
+ this.functional = new Functional();
+ this.intrinsics = new Intrinsics();
+ this.metadata = new Metadata();
+ }
+
+ public final class Functional {
+ private final Set<DexType> functions = Sets.newIdentityHashSet();
+
+ private Functional() {
+ // NOTE: Kotlin stdlib defines interface Function0 till Function22 explicitly, see:
+ // https://github.com/JetBrains/kotlin/blob/master/libraries/
+ // stdlib/jvm/runtime/kotlin/jvm/functions/Functions.kt
+ //
+ // For functions with arity bigger that 22 it is supposed to use FunctionN as described
+ // in https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md,
+ // but in current implementation (v1.2.21) defining such a lambda results in:
+ // > "Error: A JNI error has occurred, please check your installation and try again"
+ //
+ // This implementation just ignores lambdas with arity > 22.
+ for (int i = 0; i <= 22; i++) {
+ functions.add(factory.createType("Lkotlin/jvm/functions/Function" + i + ";"));
+ }
+ }
+
+ public final DexString kotlinStyleLambdaInstanceName = factory.createString("INSTANCE");
+
+ public final DexType lambdaType = factory.createType("Lkotlin/jvm/internal/Lambda;");
+
+ public final DexMethod lambdaInitializerMethod = factory.createMethod(lambdaType,
+ factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
+
+ public boolean isFunctionInterface(DexType type) {
+ return functions.contains(type);
+ }
+ }
+
+ public final class Metadata {
+ public final DexType kotlinMetadataType = factory.createType("Lkotlin/Metadata;");
+ public final DexString elementNameK = factory.createString("k");
+ public final DexString elementNameD1 = factory.createString("d1");
+ public final DexString elementNameD2 = factory.createString("d2");
+ }
+
+ // kotlin.jvm.internal.Intrinsics class
+ public final class Intrinsics {
+ public final DexType type = factory.createType("Lkotlin/jvm/internal/Intrinsics;");
+ public final DexMethod throwParameterIsNullException = factory.createMethod(type,
+ factory.createProto(factory.voidType, factory.stringType), "throwParameterIsNullException");
+ public final DexMethod throwNpe = factory.createMethod(
+ type, factory.createProto(factory.voidType), "throwNpe");
+ }
+
+ // Calculates kotlin info for a class.
+ public KotlinInfo getKotlinInfo(DexClass clazz, DiagnosticsHandler reporter) {
+ if (clazz.annotations.isEmpty()) {
+ return null;
+ }
+ DexAnnotation meta = clazz.annotations.getFirstMatching(metadata.kotlinMetadataType);
+ if (meta != null) {
+ try {
+ return createKotlinInfo(clazz, meta);
+ } catch (MetadataError e) {
+ reporter.warning(
+ new StringDiagnostic("Class " + clazz.type.toSourceString() +
+ " has malformed kotlin.Metadata: " + e.getMessage()));
+ }
+ }
+ return null;
+ }
+
+ private KotlinInfo createKotlinInfo(DexClass clazz, DexAnnotation meta) {
+ DexAnnotationElement kindElement = getAnnotationElement(meta, metadata.elementNameK);
+ if (kindElement == null) {
+ throw new MetadataError("element 'k' is missing");
+ }
+
+ DexValue value = kindElement.value;
+ if (!(value instanceof DexValueInt)) {
+ throw new MetadataError("invalid 'k' value: " + value.toSourceString());
+ }
+
+ DexValueInt intValue = (DexValueInt) value;
+ switch (intValue.value) {
+ case 1:
+ return new KotlinClass();
+ case 2:
+ return new KotlinFile();
+ case 3:
+ return createSyntheticClass(clazz);
+ case 4:
+ return new KotlinClassFacade();
+ case 5:
+ return new KotlinClassPart();
+ default:
+ throw new MetadataError("unsupported 'k' value: " + value.toSourceString());
+ }
+ }
+
+ private KotlinSyntheticClass createSyntheticClass(DexClass clazz) {
+ KotlinSyntheticClass.Flavour flavour =
+ isKotlinStyleLambda(clazz) ? Flavour.KotlinStyleLambda : Flavour.Unclassified;
+ return new KotlinSyntheticClass(flavour);
+ }
+
+ private boolean isKotlinStyleLambda(DexClass clazz) {
+ // TODO: replace with direct hints from kotlin metadata when available.
+ return clazz.superType == this.functional.lambdaType;
+ }
+
+ private DexAnnotationElement getAnnotationElement(DexAnnotation annotation, DexString name) {
+ for (DexAnnotationElement element : annotation.annotation.elements) {
+ if (element.name == name) {
+ return element;
+ }
+ }
+ return null;
+ }
+
+ private static class MetadataError extends RuntimeException {
+ MetadataError(String cause) {
+ super(cause);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
new file mode 100644
index 0000000..fc81994
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -0,0 +1,25 @@
+// 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;
+
+public class KotlinClass extends KotlinInfo {
+ @Override
+ public Kind getKind() {
+ return Kind.Class;
+ }
+
+ @Override
+ public boolean isClass() {
+ return true;
+ }
+
+ @Override
+ public KotlinClass asClass() {
+ return this;
+ }
+
+ KotlinClass() {
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
new file mode 100644
index 0000000..e829a27
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -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 com.android.tools.r8.kotlin;
+
+public final class KotlinClassFacade extends KotlinInfo {
+ @Override
+ public Kind getKind() {
+ return Kind.Facade;
+ }
+
+ @Override
+ public boolean isClassFacade() {
+ return true;
+ }
+
+ @Override
+ public KotlinClassFacade asClassFacade() {
+ return this;
+ }
+
+ KotlinClassFacade() {
+ super();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
new file mode 100644
index 0000000..d6da817
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -0,0 +1,25 @@
+// 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;
+
+public final class KotlinClassPart extends KotlinInfo {
+ @Override
+ public Kind getKind() {
+ return Kind.Part;
+ }
+
+ @Override
+ public boolean isClassPart() {
+ return true;
+ }
+
+ @Override
+ public KotlinClassPart asClassPart() {
+ return this;
+ }
+
+ KotlinClassPart() {
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
new file mode 100644
index 0000000..38a77ad
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -0,0 +1,25 @@
+// 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;
+
+public final class KotlinFile extends KotlinInfo {
+ @Override
+ public Kind getKind() {
+ return Kind.File;
+ }
+
+ @Override
+ public boolean isFile() {
+ return true;
+ }
+
+ @Override
+ public KotlinFile asFile() {
+ return this;
+ }
+
+ KotlinFile() {
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
new file mode 100644
index 0000000..4043bc6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -0,0 +1,57 @@
+// 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;
+
+// Provides access to kotlin information.
+public abstract class KotlinInfo {
+ KotlinInfo() {
+ }
+
+ public enum Kind {
+ Class, File, Synthetic, Part, Facade
+ }
+
+ public abstract Kind getKind();
+
+ public boolean isClass() {
+ return false;
+ }
+
+ public KotlinClass asClass() {
+ return null;
+ }
+
+ public boolean isFile() {
+ return false;
+ }
+
+ public KotlinFile asFile() {
+ return null;
+ }
+
+ public boolean isSyntheticClass() {
+ return false;
+ }
+
+ public KotlinSyntheticClass asSyntheticClass() {
+ return null;
+ }
+
+ public boolean isClassPart() {
+ return false;
+ }
+
+ public KotlinClassPart asClassPart() {
+ return null;
+ }
+
+ public boolean isClassFacade() {
+ return false;
+ }
+
+ public KotlinClassFacade asClassFacade() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
new file mode 100644
index 0000000..b0e37b4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -0,0 +1,37 @@
+// 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;
+
+public final class KotlinSyntheticClass extends KotlinInfo {
+ public enum Flavour {
+ KotlinStyleLambda,
+ Unclassified
+ }
+
+ public final Flavour flavour;
+
+ KotlinSyntheticClass(Flavour flavour) {
+ this.flavour = flavour;
+ }
+
+ public boolean isKotlinStyleLambda() {
+ return flavour == Flavour.KotlinStyleLambda;
+ }
+
+ @Override
+ public final Kind getKind() {
+ return Kind.Synthetic;
+ }
+
+ @Override
+ public final boolean isSyntheticClass() {
+ return true;
+ }
+
+ @Override
+ public KotlinSyntheticClass asSyntheticClass() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index ff50c3e..dd00254 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -15,9 +15,6 @@
import com.android.tools.r8.graph.DexProto;
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.DexValueString;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.naming.signature.GenericSignatureAction;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
@@ -162,7 +159,7 @@
for (int i = 0; i < annotations.length; i++) {
DexAnnotation annotation = annotations[i];
if (DexAnnotation.isSignatureAnnotation(annotation, appInfo.dexItemFactory)) {
- parser.accept(getSignatureFromAnnotation(annotation));
+ parser.accept(DexAnnotation.getSignature(annotation));
annotations[i] = DexAnnotation.createSignatureAnnotation(
genericSignatureRewriter.getRenamedSignature(),
appInfo.dexItemFactory);
@@ -170,15 +167,6 @@
}
}
- private static String getSignatureFromAnnotation(DexAnnotation signatureAnnotation) {
- DexValueArray elements = (DexValueArray) signatureAnnotation.annotation.elements[0].value;
- StringBuilder signature = new StringBuilder();
- for (DexValue element : elements.getValues()) {
- signature.append(((DexValueString) element).value.toString());
- }
- return signature.toString();
- }
-
/**
* Registers the given type as used.
* <p>
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 3831116..3414d43 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -160,6 +160,8 @@
public boolean intermediate = false;
public List<String> logArgumentsFilter = ImmutableList.of();
+ // Flag to turn on/off lambda class merging in R8.
+ public boolean enableLambdaMerging = false;
// Flag to turn on/off desugaring in D8/R8.
public boolean enableDesugaring = true;
// Defines interface method rewriter behavior.
diff --git a/src/test/java/com/android/tools/r8/kotlin/KStyleLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KStyleLambdaMergingTest.java
new file mode 100644
index 0000000..48358f0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KStyleLambdaMergingTest.java
@@ -0,0 +1,314 @@
+// 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/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 82e6cd0..fbb14f1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.kotlin.KotlinClass.Visibility;
+import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
@@ -25,14 +25,14 @@
private static final String JAVA_LANG_STRING = "java.lang.String";
- private static final KotlinCompanionClass ACCESSOR_COMPANION_PROPERTY_CLASS =
- new KotlinCompanionClass("accessors.Accessor")
+ private static final TestKotlinCompanionClass ACCESSOR_COMPANION_PROPERTY_CLASS =
+ new TestKotlinCompanionClass("accessors.Accessor")
.addProperty("property", JAVA_LANG_STRING, Visibility.PRIVATE);
private static final String PROPERTIES_PACKAGE_NAME = "properties";
- private static final KotlinCompanionClass COMPANION_PROPERTY_CLASS =
- new KotlinCompanionClass("properties.CompanionProperties")
+ private static final TestKotlinCompanionClass COMPANION_PROPERTY_CLASS =
+ new TestKotlinCompanionClass("properties.CompanionProperties")
.addProperty("privateProp", JAVA_LANG_STRING, Visibility.PRIVATE)
.addProperty("protectedProp", JAVA_LANG_STRING, Visibility.PROTECTED)
.addProperty("internalProp", JAVA_LANG_STRING, Visibility.INTERNAL)
@@ -170,7 +170,7 @@
"accessor_accessCompanionPrivate");
runTest("accessors", mainClass, (app) -> {
DexInspector dexInspector = new DexInspector(app);
- KotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
+ TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
ClassSubject outerClass = checkClassExists(dexInspector,
testedClass.getOuterClassName());
ClassSubject companionClass = checkClassExists(dexInspector,
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index daf05f5..29c5582 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -5,7 +5,7 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.kotlin.KotlinClass.Visibility;
+import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -15,7 +15,8 @@
public class R8KotlinDataClassTest extends AbstractR8KotlinTestBase {
- private static final KotlinDataClass TEST_DATA_CLASS = new KotlinDataClass("dataclass.Person")
+ private static final TestKotlinDataClass TEST_DATA_CLASS =
+ new TestKotlinDataClass("dataclass.Person")
.addProperty("name", "java.lang.String", Visibility.PUBLIC)
.addProperty("age", "int", Visibility.PUBLIC);
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
index b059382..8fef44c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -17,8 +17,8 @@
@RunWith(Parameterized.class)
public class R8KotlinIntrinsicsTest extends AbstractR8KotlinTestBase {
- private static final KotlinDataClass KOTLIN_INTRINSICS_CLASS =
- new KotlinDataClass("kotlin.jvm.internal.Intrinsics");
+ private static final TestKotlinDataClass KOTLIN_INTRINSICS_CLASS =
+ new TestKotlinDataClass("kotlin.jvm.internal.Intrinsics");
@Test
public void testParameterNullCheckIsInlined() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index 499cc2f..cb3820e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -6,7 +6,7 @@
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.kotlin.KotlinClass.Visibility;
+import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.DexInspector;
@@ -20,28 +20,28 @@
private static final String JAVA_LANG_STRING = "java.lang.String";
- private static final KotlinClass MUTABLE_PROPERTY_CLASS =
- new KotlinClass("properties.MutableProperty")
+ private static final TestKotlinClass MUTABLE_PROPERTY_CLASS =
+ new TestKotlinClass("properties.MutableProperty")
.addProperty("privateProp", JAVA_LANG_STRING, Visibility.PRIVATE)
.addProperty("protectedProp", JAVA_LANG_STRING, Visibility.PROTECTED)
.addProperty("internalProp", JAVA_LANG_STRING, Visibility.INTERNAL)
.addProperty("publicProp", JAVA_LANG_STRING, Visibility.PUBLIC)
.addProperty("primitiveProp", "int", Visibility.PUBLIC);
- private static final KotlinClass USER_DEFINED_PROPERTY_CLASS =
- new KotlinClass("properties.UserDefinedProperty")
+ private static final TestKotlinClass USER_DEFINED_PROPERTY_CLASS =
+ new TestKotlinClass("properties.UserDefinedProperty")
.addProperty("durationInMilliSeconds", "int", Visibility.PUBLIC)
.addProperty("durationInSeconds", "int", Visibility.PUBLIC);
- private static final KotlinClass LATE_INIT_PROPERTY_CLASS =
- new KotlinClass("properties.LateInitProperty")
+ private static final TestKotlinClass LATE_INIT_PROPERTY_CLASS =
+ new TestKotlinClass("properties.LateInitProperty")
.addProperty("privateLateInitProp", JAVA_LANG_STRING, Visibility.PRIVATE)
.addProperty("protectedLateInitProp", JAVA_LANG_STRING, Visibility.PROTECTED)
.addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
.addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
- private static final KotlinCompanionClass COMPANION_PROPERTY_CLASS =
- new KotlinCompanionClass("properties.CompanionProperties")
+ private static final TestKotlinCompanionClass COMPANION_PROPERTY_CLASS =
+ new TestKotlinCompanionClass("properties.CompanionProperties")
.addProperty("privateProp", JAVA_LANG_STRING, Visibility.PRIVATE)
.addProperty("protectedProp", JAVA_LANG_STRING, Visibility.PROTECTED)
.addProperty("internalProp", JAVA_LANG_STRING, Visibility.INTERNAL)
@@ -51,8 +51,8 @@
.addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
.addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
- private static final KotlinCompanionClass COMPANION_LATE_INIT_PROPERTY_CLASS =
- new KotlinCompanionClass("properties.CompanionLateInitProperties")
+ private static final TestKotlinCompanionClass COMPANION_LATE_INIT_PROPERTY_CLASS =
+ new TestKotlinCompanionClass("properties.CompanionLateInitProperties")
.addProperty("privateLateInitProp", JAVA_LANG_STRING, Visibility.PRIVATE)
.addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
.addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
@@ -461,7 +461,7 @@
@Test
public void testCompanionProperty_privateLateInitPropertyIsAlwaysInlined() throws Exception {
- final KotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
+ final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePrivateLateInitProp");
runTest(PACKAGE_NAME, mainClass, (app) -> {
@@ -492,7 +492,7 @@
@Test
public void testCompanionProperty_internalLateInitPropertyCannotBeInlined() throws Exception {
- final KotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
+ final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_useInternalLateInitProp");
runTest(PACKAGE_NAME, mainClass, (app) -> {
@@ -516,7 +516,7 @@
@Test
public void testCompanionProperty_publicLateInitPropertyCannotBeInlined() throws Exception {
- final KotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
+ final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
"companionLateInitProperties_usePublicLateInitProp");
runTest(PACKAGE_NAME, mainClass, (app) -> {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java
similarity index 95%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
rename to src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java
index 56a727e..d364bb8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/TestKotlinClass.java
@@ -14,7 +14,7 @@
*
* <p>See https://kotlinlang.org/docs/reference/classes.html</p>
*/
-class KotlinClass {
+class TestKotlinClass {
/**
* This is the suffix appended by Kotlin compiler to getter and setter method names of
@@ -61,10 +61,11 @@
return index;
}
}
+
protected final String className;
protected final Map<String, KotlinProperty> properties = Maps.newHashMap();
- public KotlinClass(String className) {
+ public TestKotlinClass(String className) {
this.className = className;
}
@@ -72,7 +73,7 @@
return className;
}
- public KotlinClass addProperty(String name, String type, Visibility visibility) {
+ public TestKotlinClass addProperty(String name, String type, Visibility visibility) {
assert !properties.containsKey(name);
properties.put(name, new KotlinProperty(name, type, visibility, properties.size()));
return this;
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinCompanionClass.java b/src/test/java/com/android/tools/r8/kotlin/TestKotlinCompanionClass.java
similarity index 68%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinCompanionClass.java
rename to src/test/java/com/android/tools/r8/kotlin/TestKotlinCompanionClass.java
index bf36dde..5c12baa 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinCompanionClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/TestKotlinCompanionClass.java
@@ -9,18 +9,18 @@
*
* <p>See https://kotlinlang.org/docs/reference/object-declarations.html#companion-objects</p>
*/
-public class KotlinCompanionClass extends KotlinClass {
+public class TestKotlinCompanionClass extends TestKotlinClass {
private final String outerClassName;
- public KotlinCompanionClass(String outerClassName) {
+ public TestKotlinCompanionClass(String outerClassName) {
super(outerClassName + "$Companion");
this.outerClassName = outerClassName;
}
@Override
- public KotlinCompanionClass addProperty(String name, String type, Visibility visibility) {
- return (KotlinCompanionClass) super.addProperty(name, type, visibility);
+ public TestKotlinCompanionClass addProperty(String name, String type, Visibility visibility) {
+ return (TestKotlinCompanionClass) super.addProperty(name, type, visibility);
}
public String getOuterClassName() {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java b/src/test/java/com/android/tools/r8/kotlin/TestKotlinDataClass.java
similarity index 88%
rename from src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java
rename to src/test/java/com/android/tools/r8/kotlin/TestKotlinDataClass.java
index bb24adc..62cda72 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDataClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/TestKotlinDataClass.java
@@ -16,15 +16,15 @@
*
* <p>See https://kotlinlang.org/docs/reference/data-classes.html</p>
*/
-class KotlinDataClass extends KotlinClass {
+class TestKotlinDataClass extends TestKotlinClass {
- KotlinDataClass(String className) {
+ TestKotlinDataClass(String className) {
super(className);
}
@Override
- public KotlinDataClass addProperty(String name, String type, Visibility visibility) {
- return (KotlinDataClass) super.addProperty(name, type, visibility);
+ public TestKotlinDataClass addProperty(String name, String type, Visibility visibility) {
+ return (TestKotlinDataClass) super.addProperty(name, type, visibility);
}
public MemberNaming.MethodSignature getComponentNFunctionForProperty(String name) {
diff --git a/src/test/kotlinR8TestResources/lambdas_kstyle_captures/main.kt b/src/test/kotlinR8TestResources/lambdas_kstyle_captures/main.kt
new file mode 100644
index 0000000..3ee0bf8
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_kstyle_captures/main.kt
@@ -0,0 +1,65 @@
+// 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.kstyle.captures
+
+fun consume(l: () -> String) = l()
+
+fun main(args: Array<String>) {
+ test()
+}
+
+private fun test() {
+ test1(1, 2, 3, "A", "B", "C", D("x"), D("y"), D("z"), 7, 8, 9)
+ test2(true, 10, '*', 20, 30, 40, 50.0f, 60.0, D("D"), "S", null, 70)
+}
+
+data class D(val d: String)
+
+private fun test1(
+ i1: Int, i2: Int, i3: Int,
+ s1: String, s2: String, s3: String,
+ d1: D, d2: D, d3: D,
+ o1: Int?, o2: Int?, o3: Int?
+) {
+ println(consume { "a: $i1 $i2 $i3" })
+ println(consume { "b: $i2 $i3 $i1" })
+ println(consume { "c: $i3 $i1 $i2" })
+
+ println(consume { "d: $i1 $s1 $d1" })
+ println(consume { "e: $i2 $d2 $s2" })
+ println(consume { "f: $i3 $d3 $d1" })
+ println(consume { "g: $o1 $d3 $i3" })
+ println(consume { "h: $o2 $o3 $i1" })
+
+ println(consume { "i: $s1 $s2 $s3" })
+ println(consume { "j: $d1 $d2 $d3" })
+ println(consume { "k: $o1 $o2 $o3" })
+ println(consume { "l: $s1 $d2 $o3" })
+ println(consume { "n: $o1 $s2 $d3" })
+ println(consume { "o: $d1 $o2 $s3" })
+
+ println(consume { "p: $i1 $i2 $s3" })
+}
+
+private fun test2(
+ z: Boolean, b: Byte, c: Char, s: Short,
+ i: Int, l: Long, f: Float, d: Double,
+ o1: D, o2: String, o3: Any?, o4: Byte?
+) {
+ println(consume { "a: $z $b $c $s $i $l $f $d $o1 $o2 $o3 $o4" })
+ println(consume { "a: $z $b $o1 $o2 $c $s $i $l $f $d $o3 $o4" })
+ println(consume { "a: $z $c $s $l $f $d $o2 $o3 $o4 $b $i $o1" })
+ println(consume { "a: $o1 $o2 $o3 $o4 $z $b $c $s $i $l $f $d" })
+
+ println(consume { "a: $z $b $c $s $i $l $f $d $o1 $o2 \$o3 \$o4" })
+ println(consume { "a: $z $b $c $s $i $l $f $d $o1 \$o2 \$o3 $o4" })
+ println(consume { "a: $z $b $c $s $i $l $f $d \$o1 \$o2 $o3 $o4" })
+ println(consume { "a: $z $b $c $s $i $l $f $d \$o1 $o2 $o3 \$o4" })
+
+ println(consume { "x: $z $b $c $s $i $l $f $d $o1 $o2 \$o3 $o4" })
+ println(consume { "y: $z $b $c $s $i $l \$f $d $o1 $o2 $o3 $o4" })
+ println(consume { "z: $z $b $c \$s $i $l $f $d $o1 $o2 $o3 $o4" })
+}
+
diff --git a/src/test/kotlinR8TestResources/lambdas_kstyle_generics/main.kt b/src/test/kotlinR8TestResources/lambdas_kstyle_generics/main.kt
new file mode 100644
index 0000000..7fb22eb
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_kstyle_generics/main.kt
@@ -0,0 +1,60 @@
+// 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.kstyle.generics
+
+private var COUNT = 11
+
+private fun next() = "${COUNT++}"
+
+data class Alpha(val id: String = next())
+
+data class Beta(val id: String = next())
+
+data class Gamma<out T>(val payload: T?, val id: String = next())
+
+fun <T> consume(t: T, l: (t: T) -> String) = l(t)
+
+fun main(args: Array<String>) {
+ println(consume(Any(), { "${Alpha()}" }))
+ println(consume(Any(), { "${Beta()}" }))
+ println(consume(Any(), { "${Gamma("{any}")}" }))
+ println(consume(Alpha(), { "$it" }))
+
+ testFirst(11)
+ testSecond(22)
+ testThird()
+}
+
+private fun testFirst(sh: Short) {
+ val prefix = "First"
+ println(consume(Beta(), { "$prefix-1-$it" }))
+ println(consume(Beta(), { "$prefix-2-$it" }))
+ println(consume(Beta(), { "$prefix-3-$it" }))
+ println(consume(Gamma(next()), { "$prefix-A-$it-$sh" }))
+ println(consume(Gamma(next()), { "$prefix-B-$it-$sh" }))
+ println(consume(Gamma(next()), { "$prefix-C-$it-$sh" }))
+ println(consume(Gamma(COUNT++), { "$prefix-D-$it-$sh" }))
+ println(consume(Gamma(COUNT++), { "$prefix-E-$it-$sh" }))
+ println(consume(Gamma(COUNT++), { "$prefix-F-$it-$sh" }))
+}
+
+private fun testSecond(sh: Short) {
+ val prefix = "Second"
+ println(consume(Beta(), { "$prefix-1-$it" }))
+ println(consume(Beta(), { "$prefix-2-$it" }))
+ println(consume(Beta(), { "$prefix-3-$it" }))
+ println(consume(Gamma(next()), { "$prefix-A-$it-$sh" }))
+ println(consume(Gamma(next()), { "$prefix-B-$it-$sh" }))
+ println(consume(Gamma(next()), { "$prefix-C-$it-$sh" }))
+ println(consume(Gamma(COUNT++), { "$prefix-D-$it-$sh" }))
+ println(consume(Gamma(COUNT++), { "$prefix-E-$it-$sh" }))
+ println(consume(Gamma(COUNT++), { "$prefix-F-$it-$sh" }))
+}
+
+private fun testThird() {
+ println(consume(4321, { "$it ${next()} ${next()} ${next()}" }))
+ println(consume(1234, { "$it ${Alpha()} ${Beta()} ${Gamma(next())}" }))
+}
+
diff --git a/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/inner.kt b/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/inner.kt
new file mode 100644
index 0000000..00587a9
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_kstyle_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.kstyle.trivial.inner
+
+import lambdas.kstyle.trivial.consumeEmpty
+import lambdas.kstyle.trivial.consumeOne
+import lambdas.kstyle.trivial.consumeTwo
+
+fun testInner() {
+ testInnerStateless()
+}
+
+private fun testInnerStateless() {
+ println(consumeEmpty { "first empty" })
+ println(consumeEmpty { "second empty" })
+
+ println(consumeOne { _ -> "first single" })
+ println(consumeOne { _ -> "second single" })
+ println(consumeOne { _ -> "third single" })
+ println(consumeOne { x -> x })
+
+ println(consumeTwo { x, y -> x + "-" + y })
+}
+
diff --git a/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/main.kt b/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/main.kt
new file mode 100644
index 0000000..34ba194
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_kstyle_trivial/main.kt
@@ -0,0 +1,89 @@
+// 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.kstyle.trivial
+
+import lambdas.kstyle.trivial.inner.testInner
+
+private var COUNT = 11
+
+private fun next() = "${COUNT++}"
+
+fun consumeEmpty(l: () -> String) = l()
+fun consumeOne(l: (x: String) -> String): String = l(next())
+fun consumeTwo(l: (x: String, y: String) -> String): String = l(next(), next())
+fun consumeThree(l: (x: String, y: String, z: String) -> String) = l(next(), next(), next())
+fun consumeTwentyTwo(l: (v0: String, v1: String, v2: String, v3: String, v4: String,
+ v5: String, v6: String, v7: String, v8: String, v9: String,
+ v10: String, v11: String, v12: String, v13: String, v14: String,
+ v15: String, v16: String, v17: String, v18: String, v19: String,
+ v20: String, v21: String) -> String) = l(
+ next(), next(), next(), next(), next(), next(), next(), next(), next(), next(), next(),
+ next(), next(), next(), next(), next(), next(), next(), next(), next(), next(), next())
+
+fun main(args: Array<String>) {
+ test()
+ testInner()
+ testPrimitive()
+ testUnit()
+}
+
+private fun test() {
+ testStateless()
+}
+
+private fun testStateless() {
+ println(consumeEmpty { "first empty" })
+ println(consumeEmpty { "second empty" })
+
+ println(consumeOne { _ -> "first single" })
+ println(consumeOne { _ -> "second single" })
+ println(consumeOne { _ -> "third single" })
+ println(consumeOne { x ->
+ try {
+ throw RuntimeException("exception#$x")
+ } catch (e: RuntimeException) {
+ "caught: ${e.message}"
+ } catch (e: Exception) {
+ "NEVER"
+ }
+ })
+ println(consumeOne { x -> x })
+
+ println(consumeTwo { x, y -> x + "-" + y })
+
+ println(consumeThree { x, y, z -> x + y + z })
+ println(consumeThree { _, _, _ -> "one-two-three" })
+
+ println(consumeTwentyTwo { _, _, _, _, _, _, _, _, _, _, _,
+ _, _, _, _, _, _, _, _, _, _, _ ->
+ "one-two-...-twentythree"
+ })
+ println(consumeTwentyTwo { v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11,
+ v12, v13, v14, v15, v16, v17, v18, v19, v20, v21 ->
+ v0 + v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10 + v11 +
+ v12 + v13 + v14 + v15 + v16 + v17 + v18 + v19 + v20 + v21
+ })
+}
+
+private fun consumePrimitive(i: Int, l: (Int, Short) -> Int) = l(i, 5)
+
+private fun testPrimitive() {
+ println(consumePrimitive(1, { x, y -> x }))
+ println(consumePrimitive(2, { x, y -> y.toInt() }))
+ println(consumePrimitive(3, { x, y -> x + y }))
+ println(consumePrimitive(4, { x, y -> x * y }))
+ val l: (Int, Short) -> Int = { x, y -> x / y }
+ println(l(100, 20))
+}
+
+private fun consumeUnit(i: Int, l: (Int, Short) -> Unit) = l(i, 10)
+
+private fun testUnit() {
+ println(consumeUnit(11, { x, y -> println() }))
+ println(consumeUnit(12, { x, y -> println(y) }))
+ println(consumeUnit(13, { x, y -> println(x) }))
+ println(consumeUnit(14, { x, y -> println("$x -- $y") }))
+}
+