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