Java-style lambda class merging.

Adds support for Kotlin java-style lambda class merging implemented in
the same way and scope as similar merging of kotlin-style lambda classes.

Actually, there are just few differences between kotlin- and java-style
lambdas synthesized by Kotlin compiler and most of the implementation
is shared.

As before, the scope of the merging depends on proguard rules, on
tachiyomi with most optimistic expectetions (assuming no lambda classes
are pinned by blanket keep rules, allowaccessmodification is specified,
and none of [Signature, InnerClasses, EnclosingMethod] is kept) we get:

K-STYLE LAMBDAS:
  422 lambda classes total
  303 lambda classes in 81 groups after invalid and non-grouped lambdas removed
  222 lambda classes removed

J-STYLE LAMBDA CLASSES:
  503 lambda classes total
  310 lambda classes in 83 groups after invalid and non-grouped lambdas removed
  227 lambda classes removed

DEX size w/o optimization : 4865332 bytes
DEX size with optimization: 4774368 bytes (-91K, -1.87%)

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