Merge "Extend bootstrap benchmark with memory and size metrics."
diff --git a/LIBRARY-LICENSE b/LIBRARY-LICENSE
index 12674e3..bc69c40 100644
--- a/LIBRARY-LICENSE
+++ b/LIBRARY-LICENSE
@@ -48,3 +48,21 @@
   license: ASM license
   licenseUrl: http://asm.ow2.org/license.html
   url: http://asm.ow2.org/index.html
+- artifact: org.jetbrains.kotlin:kotlin-stdlib:+
+  name: org.jetbrains.kotlin:kotlin-stdlib
+  copyrightHolder: JetBrains s.r.o.
+  license: The Apache License, Version 2.0
+  licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
+  url: https://kotlinlang.org/
+- artifact: org.jetbrains.kotlinx:kotlinx-metadata-jvm:+
+  name: org.jetbrains.kotlinx:kotlinx-metadata-jvm
+  copyrightHolder: JetBrains s.r.o.
+  license: The Apache License, Version 2.0
+  licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
+  url: https://kotlinlang.org/
+- artifact: org.jetbrains:annotations:+
+  name: IntelliJ IDEA Annotations
+  copyrightHolder: JetBrains s.r.o.
+  license: The Apache Software License, Version 2.0
+  licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
+  url: http://www.jetbrains.org
diff --git a/build.gradle b/build.gradle
index 92d7471..7f41caa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -37,6 +37,7 @@
     gsonVersion = '2.7'
     junitVersion = '4.12'
     kotlinVersion = '1.2.30'
+    kotlinExtMetadataJVMVersion = '0.0.2'
     protobufVersion = '3.0.0'
     smaliVersion = '2.2b4'
 }
@@ -74,6 +75,7 @@
 
 repositories {
     maven { url 'https://maven.google.com' }
+    maven { url 'https://kotlin.bintray.com/kotlinx' }
     mavenCentral()
 }
 
@@ -223,6 +225,7 @@
         exclude group: 'org.codehaus.mojo'
     })
     compile group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
+    compile "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinExtMetadataJVMVersion"
     compile group: 'org.ow2.asm', name: 'asm', version: asmVersion
     compile group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
     compile group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
diff --git a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
index af83158..a6389da 100644
--- a/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveClassFileProvider.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.io.ByteStreams;
 import java.io.Closeable;
 import java.io.IOException;
@@ -21,7 +21,6 @@
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashSet;
@@ -73,7 +72,7 @@
     while (entries.hasMoreElements()) {
       ZipEntry entry = entries.nextElement();
       String name = entry.getName();
-      if (FileUtils.isClassFile(Paths.get(name)) && include.test(name)) {
+      if (ZipUtils.isClassFile(name) && include.test(name)) {
         descriptors.add(DescriptorUtils.guessTypeDescriptor(name));
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
index 425b3c3..1d6e678 100644
--- a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -3,22 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static com.android.tools.r8.utils.FileUtils.isClassFile;
-import static com.android.tools.r8.utils.FileUtils.isDexFile;
-
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -39,16 +35,15 @@
   }
 
   public static boolean includeClassFileEntries(String entry) {
-    return isClassFile(Paths.get(entry));
+    return ZipUtils.isClassFile(entry);
   }
 
   public static boolean includeDexEntries(String entry) {
-    return isDexFile(Paths.get(entry));
+    return ZipUtils.isDexFile(entry);
   }
 
   public static boolean includeClassFileOrDexEntries(String entry) {
-    Path path = Paths.get(entry);
-    return isClassFile(path) || isDexFile(path);
+    return ZipUtils.isClassFile(entry) || ZipUtils.isDexFile(entry);
   }
 
   private final Origin origin;
@@ -97,14 +92,13 @@
         ZipEntry entry = entries.nextElement();
         try (InputStream stream = zipFile.getInputStream(entry)) {
           String name = entry.getName();
-          Path path = Paths.get(name);
-          Origin entryOrigin = new ArchiveEntryOrigin(entry.getName(), origin);
+          Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
           if (include.test(name)) {
-            if (FileUtils.isDexFile(path)) {
+            if (ZipUtils.isDexFile(name)) {
               dexResources.add(
                   ProgramResource.fromBytes(
                       entryOrigin, Kind.DEX, ByteStreams.toByteArray(stream), null));
-            } else if (isClassFile(path)) {
+            } else if (ZipUtils.isClassFile(name)) {
               String descriptor = DescriptorUtils.guessTypeDescriptor(name);
               classResources.add(
                   ProgramResource.fromBytes(
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b775fd3..f896eee 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.3.0-dev";
+  public static final String LABEL = "1.3.1-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
index 1336f95..807509a 100644
--- a/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
+++ b/src/main/java/com/android/tools/r8/benchmarks/FrameworkIncrementalDexingBenchmark.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableMap;
@@ -61,7 +60,7 @@
           archive.toString(),
           (entry, stream) -> {
             String name = entry.getName();
-            if (FileUtils.isClassFile(Paths.get(name))) {
+            if (ZipUtils.isClassFile(name)) {
               String descriptor = DescriptorUtils.guessTypeDescriptor(name);
               builder.put(descriptor, ByteStreams.toByteArray(stream));
             }
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 871f6d8..7d2907a 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -3,13 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.compatdx;
 
-import static com.android.tools.r8.utils.FileUtils.isApkFile;
-import static com.android.tools.r8.utils.FileUtils.isArchive;
-import static com.android.tools.r8.utils.FileUtils.isClassFile;
-import static com.android.tools.r8.utils.FileUtils.isDexFile;
-import static com.android.tools.r8.utils.FileUtils.isJarFile;
-import static com.android.tools.r8.utils.FileUtils.isZipFile;
-
 import com.android.tools.r8.CompatDxHelper;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
@@ -28,6 +21,7 @@
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import java.io.File;
@@ -475,7 +469,7 @@
     }
     if (singleDexFile) {
       return new SingleDexFileConsumer(
-          isDexFile(output)
+          FileUtils.isDexFile(output)
               ? new NamedDexFileConsumer(output)
               : createDexConsumer(output, inputs, keepClasses));
     }
@@ -486,13 +480,13 @@
       Path output, List<Path> inputs, boolean keepClasses)
       throws DxUsageMessage {
     if (keepClasses) {
-      if (!isArchive(output)) {
+      if (!FileUtils.isArchive(output)) {
         throw new DxCompatOptions.DxUsageMessage(
             "Output must be an archive when --keep-classes is set.");
       }
       return new DexKeepClassesConsumer(output, inputs);
     }
-    return isArchive(output)
+    return FileUtils.isArchive(output)
         ? new DexIndexedConsumer.ArchiveConsumer(output)
         : new DexIndexedConsumer.DirectoryConsumer(output);
   }
@@ -572,12 +566,12 @@
     private void writeZipWithClasses(DiagnosticsHandler handler) throws IOException {
       // For each input archive file, add all class files within.
       for (Path input : inputs) {
-        if (isArchive(input)) {
+        if (FileUtils.isArchive(input)) {
           try (ZipFile zipFile = new ZipFile(input.toFile(), StandardCharsets.UTF_8)) {
             final Enumeration<? extends ZipEntry> entries = zipFile.entries();
             while (entries.hasMoreElements()) {
               ZipEntry entry = entries.nextElement();
-              if (isClassFile(Paths.get(entry.getName()))) {
+              if (ZipUtils.isClassFile(entry.getName())) {
                 try (InputStream entryStream = zipFile.getInputStream(entry)) {
                   outputBuilder.addFile(
                       entry.getName(), ByteStreams.toByteArray(entryStream), handler);
@@ -604,11 +598,11 @@
       return;
     }
     Path path = file.toPath();
-    if (isZipFile(path) || isJarFile(path) || isClassFile(path)) {
+    if (FileUtils.isZipFile(path) || FileUtils.isJarFile(path) || FileUtils.isClassFile(path)) {
       files.add(path);
       return;
     }
-    if (isApkFile(path)) {
+    if (FileUtils.isApkFile(path)) {
       throw new Unimplemented("apk files not yet supported");
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
new file mode 100644
index 0000000..7f31098
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DefaultUseRegistry.java
@@ -0,0 +1,63 @@
+// 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.graph;
+
+public class DefaultUseRegistry extends UseRegistry {
+
+  @Override
+  public boolean registerInvokeVirtual(DexMethod method) {
+    return true;
+  }
+
+  @Override
+  public boolean registerInvokeDirect(DexMethod method) {
+    return true;
+  }
+
+  @Override
+  public boolean registerInvokeStatic(DexMethod method) {
+    return true;
+  }
+
+  @Override
+  public boolean registerInvokeInterface(DexMethod method) {
+    return true;
+  }
+
+  @Override
+  public boolean registerInvokeSuper(DexMethod method) {
+    return true;
+  }
+
+  @Override
+  public boolean registerInstanceFieldWrite(DexField field) {
+    return true;
+  }
+
+  @Override
+  public boolean registerInstanceFieldRead(DexField field) {
+    return true;
+  }
+
+  @Override
+  public boolean registerNewInstance(DexType type) {
+    return true;
+  }
+
+  @Override
+  public boolean registerStaticFieldRead(DexField field) {
+    return true;
+  }
+
+  @Override
+  public boolean registerStaticFieldWrite(DexField field) {
+    return true;
+  }
+
+  @Override
+  public boolean registerTypeReference(DexType type) {
+    return true;
+  }
+}
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 23139c5..2c188da 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.Iterators;
 import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
@@ -96,6 +97,16 @@
     }
   }
 
+  public Iterable<DexEncodedField> fields() {
+    return () ->
+        Iterators.concat(Iterators.forArray(instanceFields), Iterators.forArray(staticFields));
+  }
+
+  public Iterable<DexEncodedMethod> methods() {
+    return () ->
+        Iterators.concat(Iterators.forArray(directMethods), Iterators.forArray(virtualMethods));
+  }
+
   @Override
   void collectMixedSectionItems(MixedSectionCollection mixedItems) {
     throw new Unreachable();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index d2c7701..a9df6a3 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -195,7 +195,8 @@
       transformBridgeMethod();
     }
     computeNeedsRegister();
-    insertArgumentMoves();
+    constrainArgumentIntervals();
+    insertRangeInvokeMoves();
     ImmutableList<BasicBlock> blocks = computeLivenessInformation();
     // First attempt to allocate register allowing argument reuse. This will fail if spilling
     // is required or if we end up using more than 16 registers.
@@ -2598,11 +2599,14 @@
     }
   }
 
-  private void insertArgumentMoves() {
+  private void constrainArgumentIntervals() {
     // Record the constraint that incoming arguments are in consecutive registers.
     List<Value> arguments = code.collectArguments();
     createArgumentLiveIntervals(arguments);
     linkArgumentValuesAndIntervals(arguments);
+  }
+
+  private void insertRangeInvokeMoves() {
     for (BasicBlock block : code.blocks) {
       InstructionListIterator it = block.listIterator();
       while (it.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index cf2b2bc..0af41b1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -5,19 +5,11 @@
 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.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.kotlin.KotlinSyntheticClass.Flavour;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
@@ -58,10 +50,13 @@
 
     public final DexString kotlinStyleLambdaInstanceName = factory.createString("INSTANCE");
 
+    public final DexType functionBase = factory.createType("Lkotlin/jvm/internal/FunctionBase;");
     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 final DexMethod lambdaInitializerMethod = factory.createMethod(
+        lambdaType,
+        factory.createProto(factory.voidType, factory.intType),
+        factory.constructorMethodName);
 
     public boolean isFunctionInterface(DexType type) {
       return functions.contains(type);
@@ -70,9 +65,14 @@
 
   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");
+    public final DexString kind = factory.createString("k");
+    public final DexString metadataVersion = factory.createString("mv");
+    public final DexString bytecodeVersion = factory.createString("bv");
+    public final DexString data1 = factory.createString("d1");
+    public final DexString data2 = factory.createString("d2");
+    public final DexString extraString = factory.createString("xs");
+    public final DexString packageName = factory.createString("pn");
+    public final DexString extraInt = factory.createString("xi");
   }
 
   // kotlin.jvm.internal.Intrinsics class
@@ -86,98 +86,6 @@
 
   // 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, meta);
-      case 4:
-        return new KotlinClassFacade();
-      case 5:
-        return new KotlinClassPart();
-      default:
-        throw new MetadataError("unsupported 'k' value: " + value.toSourceString());
-    }
-  }
-
-  private KotlinSyntheticClass createSyntheticClass(DexClass clazz, DexAnnotation meta) {
-    if (isKotlinStyleLambda(clazz)) {
-      return new KotlinSyntheticClass(Flavour.KotlinStyleLambda);
-    }
-    if (isJavaStyleLambda(clazz, meta)) {
-      return new KotlinSyntheticClass(Flavour.JavaStyleLambda);
-    }
-    return new KotlinSyntheticClass(Flavour.Unclassified);
-  }
-
-  private boolean isKotlinStyleLambda(DexClass clazz) {
-    // TODO: replace with direct hints from kotlin metadata when available.
-    return clazz.superType == this.functional.lambdaType;
-  }
-
-  private boolean isJavaStyleLambda(DexClass clazz, DexAnnotation meta) {
-    assert !isKotlinStyleLambda(clazz);
-    return clazz.superType == this.factory.objectType &&
-        clazz.interfaces.size() == 1 &&
-        isAnnotationElementNotEmpty(meta, metadata.elementNameD1);
-  }
-
-  private DexAnnotationElement getAnnotationElement(DexAnnotation annotation, DexString name) {
-    for (DexAnnotationElement element : annotation.annotation.elements) {
-      if (element.name == name) {
-        return element;
-      }
-    }
-    return null;
-  }
-
-  private boolean isAnnotationElementNotEmpty(DexAnnotation annotation, DexString name) {
-    for (DexAnnotationElement element : annotation.annotation.elements) {
-      if (element.name == name && element.value instanceof DexValueArray) {
-        DexValue[] values = ((DexValueArray) element.value).getValues();
-        if (values.length == 1 && values[0] instanceof DexValueString) {
-          return true;
-        }
-        // Must be broken metadata.
-        assert false;
-      }
-    }
-    return false;
-  }
-
-  private static class MetadataError extends RuntimeException {
-    MetadataError(String cause) {
-      super(cause);
-    }
+    return KotlinClassMetadataReader.getKotlinInfo(this, clazz, reporter);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index fc81994..74184a5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,7 +4,31 @@
 
 package com.android.tools.r8.kotlin;
 
-public class KotlinClass extends KotlinInfo {
+import kotlinx.metadata.KmClassVisitor;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
+
+  static KotlinClass fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+    assert kotlinClassMetadata instanceof KotlinClassMetadata.Class;
+    KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) kotlinClassMetadata;
+    return new KotlinClass(kClass);
+  }
+
+  private KotlinClass(KotlinClassMetadata.Class metadata) {
+    super(metadata);
+  }
+
+  @Override
+  void validateMetadata(KotlinClassMetadata.Class metadata) {
+    ClassMetadataVisitor visitor = new ClassMetadataVisitor();
+    // To avoid lazy parsing/verifying metadata.
+    metadata.accept(visitor);
+  }
+
+  private static class ClassMetadataVisitor extends KmClassVisitor {
+  }
+
   @Override
   public Kind getKind() {
     return Kind.Class;
@@ -20,6 +44,4 @@
     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
index e829a27..62db3d1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,7 +4,26 @@
 
 package com.android.tools.r8.kotlin;
 
-public final class KotlinClassFacade extends KotlinInfo {
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public final class KotlinClassFacade extends KotlinInfo<KotlinClassMetadata.MultiFileClassFacade> {
+
+  static KotlinClassFacade fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+    assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassFacade;
+    KotlinClassMetadata.MultiFileClassFacade multiFileClassFacade =
+        (KotlinClassMetadata.MultiFileClassFacade) kotlinClassMetadata;
+    return new KotlinClassFacade(multiFileClassFacade);
+  }
+
+  private KotlinClassFacade(KotlinClassMetadata.MultiFileClassFacade metadata) {
+    super(metadata);
+  }
+
+  @Override
+  void validateMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
+    // No worries about lazy parsing/verifying, since no API to explore metadata details.
+  }
+
   @Override
   public Kind getKind() {
     return Kind.Facade;
@@ -20,7 +39,4 @@
     return this;
   }
 
-  KotlinClassFacade() {
-    super();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
new file mode 100644
index 0000000..32aebff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -0,0 +1,133 @@
+// 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.DexString;
+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.utils.StringDiagnostic;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import kotlinx.metadata.InconsistentKotlinMetadataException;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+final class KotlinClassMetadataReader {
+
+  static KotlinInfo getKotlinInfo(
+      Kotlin kotlin,
+      DexClass clazz,
+      DiagnosticsHandler reporter) {
+    if (clazz.annotations.isEmpty()) {
+      return null;
+    }
+    DexAnnotation meta = clazz.annotations.getFirstMatching(kotlin.metadata.kotlinMetadataType);
+    if (meta != null) {
+      try {
+        return createKotlinInfo(kotlin, clazz, meta);
+      } catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
+        reporter.warning(
+            new StringDiagnostic("Class " + clazz.type.toSourceString()
+                + " has malformed kotlin.Metadata: " + e.getMessage()));
+      } catch (Throwable e) {
+         reporter.warning(
+            new StringDiagnostic("Unexpected error while reading " + clazz.type.toSourceString()
+                + "'s kotlin.Metadata: " + e.getMessage()));
+      }
+    }
+    return null;
+  }
+
+  private static KotlinInfo createKotlinInfo(
+      Kotlin kotlin,
+      DexClass clazz,
+      DexAnnotation meta) {
+    Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>();
+    for (DexAnnotationElement element : meta.annotation.elements) {
+      elementMap.put(element.name, element);
+    }
+
+    DexAnnotationElement kind = elementMap.get(kotlin.metadata.kind);
+    if (kind == null) {
+      throw new MetadataError("element 'k' is missing.");
+    }
+    Integer k = (Integer) kind.value.getBoxedValue();
+    DexAnnotationElement metadataVersion = elementMap.get(kotlin.metadata.metadataVersion);
+    int[] mv = metadataVersion == null ? null : getUnboxedIntArray(metadataVersion.value, "mv");
+    DexAnnotationElement bytecodeVersion = elementMap.get(kotlin.metadata.bytecodeVersion);
+    int[] bv = bytecodeVersion == null ? null : getUnboxedIntArray(bytecodeVersion.value, "bv");
+    DexAnnotationElement data1 = elementMap.get(kotlin.metadata.data1);
+    String[] d1 = data1 == null ? null : getUnboxedStringArray(data1.value, "d1");
+    DexAnnotationElement data2 = elementMap.get(kotlin.metadata.data2);
+    String[] d2 = data2 == null ? null : getUnboxedStringArray(data2.value, "d2");
+    DexAnnotationElement extraString = elementMap.get(kotlin.metadata.extraString);
+    String xs = extraString == null ? null : getUnboxedString(extraString.value, "xs");
+    DexAnnotationElement packageName = elementMap.get(kotlin.metadata.packageName);
+    String pn = packageName == null ? null : getUnboxedString(packageName.value, "pn");
+    DexAnnotationElement extraInt = elementMap.get(kotlin.metadata.extraInt);
+    Integer xi = extraInt == null ? null : (Integer) extraInt.value.getBoxedValue();
+
+    KotlinClassHeader header = new KotlinClassHeader(k, mv, bv, d1, d2, xs, pn, xi);
+    KotlinClassMetadata kMetadata = KotlinClassMetadata.read(header);
+
+    if (kMetadata instanceof KotlinClassMetadata.Class) {
+      return KotlinClass.fromKotlinClassMetadata(kMetadata);
+    } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
+      return KotlinFile.fromKotlinClassMetadata(kMetadata);
+    } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
+      return KotlinClassFacade.fromKotlinClassMetadata(kMetadata);
+    } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
+      return KotlinClassPart.fromKotlinClassMetdata(kMetadata);
+    } else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
+      return KotlinSyntheticClass.fromKotlinClassMetadata(kMetadata, kotlin, clazz);
+    } else {
+      throw new MetadataError("unsupported 'k' value: " + k);
+    }
+  }
+
+  private static int[] getUnboxedIntArray(DexValue v, String elementName) {
+    if (!(v instanceof DexValueArray)) {
+      throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+    }
+    DexValueArray intArrayValue = (DexValueArray) v;
+    DexValue[] values = intArrayValue.getValues();
+    int[] result = new int [values.length];
+    for (int i = 0; i < values.length; i++) {
+      result[i] = (Integer) values[i].getBoxedValue();
+    }
+    return result;
+  }
+
+  private static String[] getUnboxedStringArray(DexValue v, String elementName) {
+    if (!(v instanceof DexValueArray)) {
+      throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+    }
+    DexValueArray stringArrayValue = (DexValueArray) v;
+    DexValue[] values = stringArrayValue.getValues();
+    String[] result = new String [values.length];
+    for (int i = 0; i < values.length; i++) {
+      result[i] = getUnboxedString(values[i], elementName + "[" + i + "]");
+    }
+    return result;
+  }
+
+  private static String getUnboxedString(DexValue v, String elementName) {
+    if (!(v instanceof DexValueString)) {
+      throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
+    }
+    return ((DexValueString) v).getValue().toString();
+  }
+
+  private static class MetadataError extends RuntimeException {
+    MetadataError(String cause) {
+      super(cause);
+    }
+  }
+
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index d6da817..da66e6c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,7 +4,31 @@
 
 package com.android.tools.r8.kotlin;
 
-public final class KotlinClassPart extends KotlinInfo {
+import kotlinx.metadata.KmPackageVisitor;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
+
+  static KotlinClassPart fromKotlinClassMetdata(KotlinClassMetadata kotlinClassMetadata) {
+    assert kotlinClassMetadata instanceof KotlinClassMetadata.MultiFileClassPart;
+    KotlinClassMetadata.MultiFileClassPart multiFileClassPart =
+        (KotlinClassMetadata.MultiFileClassPart) kotlinClassMetadata;
+    return new KotlinClassPart(multiFileClassPart);
+  }
+
+  private KotlinClassPart(KotlinClassMetadata.MultiFileClassPart metadata) {
+    super(metadata);
+  }
+
+  @Override
+  void validateMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
+    // To avoid lazy parsing/verifying metadata.
+    metadata.accept(new MultiFileClassPartMetadataVisitor());
+  }
+
+  private static class MultiFileClassPartMetadataVisitor extends KmPackageVisitor {
+  }
+
   @Override
   public Kind getKind() {
     return Kind.Part;
@@ -20,6 +44,4 @@
     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
index 38a77ad..bcb70ed 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -4,7 +4,31 @@
 
 package com.android.tools.r8.kotlin;
 
-public final class KotlinFile extends KotlinInfo {
+import kotlinx.metadata.KmPackageVisitor;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public final class KotlinFile extends KotlinInfo<KotlinClassMetadata.FileFacade> {
+
+  static KotlinFile fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+    assert kotlinClassMetadata instanceof KotlinClassMetadata.FileFacade;
+    KotlinClassMetadata.FileFacade fileFacade =
+        (KotlinClassMetadata.FileFacade) kotlinClassMetadata;
+    return new KotlinFile(fileFacade);
+  }
+
+  private KotlinFile(KotlinClassMetadata.FileFacade metadata) {
+    super(metadata);
+  }
+
+  @Override
+  void validateMetadata(KotlinClassMetadata.FileFacade metadata) {
+    // To avoid lazy parsing/verifying metadata.
+    metadata.accept(new FileFacadeMetadataVisitor());
+  }
+
+  private static class FileFacadeMetadataVisitor extends KmPackageVisitor {
+  }
+
   @Override
   public Kind getKind() {
     return Kind.File;
@@ -20,6 +44,4 @@
     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
index 4043bc6..702e0eb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,11 +4,23 @@
 
 package com.android.tools.r8.kotlin;
 
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
 // Provides access to kotlin information.
-public abstract class KotlinInfo {
+public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
+  MetadataKind metadata;
+
   KotlinInfo() {
   }
 
+  KotlinInfo(MetadataKind metadata) {
+    validateMetadata(metadata);
+    this.metadata = metadata;
+  }
+
+  // Subtypes will define how to validate the given metadata.
+  abstract void validateMetadata(MetadataKind metadata);
+
   public enum Kind {
     Class, File, Synthetic, Part, Facade
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 68721bb..4660800 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.kotlin;
 
-public final class KotlinSyntheticClass extends KotlinInfo {
+import com.android.tools.r8.graph.DexClass;
+import kotlinx.metadata.KmLambdaVisitor;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
+
+public final class KotlinSyntheticClass extends KotlinInfo<KotlinClassMetadata.SyntheticClass> {
   public enum Flavour {
     KotlinStyleLambda,
     JavaStyleLambda,
@@ -13,12 +17,40 @@
 
   private final Flavour flavour;
 
-  KotlinSyntheticClass(Flavour flavour) {
+  static KotlinSyntheticClass fromKotlinClassMetadata(
+      KotlinClassMetadata kotlinClassMetadata, Kotlin kotlin, DexClass clazz) {
+    assert kotlinClassMetadata instanceof KotlinClassMetadata.SyntheticClass;
+    KotlinClassMetadata.SyntheticClass syntheticClass =
+        (KotlinClassMetadata.SyntheticClass) kotlinClassMetadata;
+    if (isKotlinStyleLambda(syntheticClass, kotlin, clazz)) {
+      return new KotlinSyntheticClass(Flavour.KotlinStyleLambda, syntheticClass);
+    } else if (isJavaStyleLambda(syntheticClass, kotlin, clazz)) {
+      return new KotlinSyntheticClass(Flavour.JavaStyleLambda, syntheticClass);
+    } else {
+      return new KotlinSyntheticClass(Flavour.Unclassified, syntheticClass);
+    }
+  }
+
+  private KotlinSyntheticClass(Flavour flavour, KotlinClassMetadata.SyntheticClass metadata) {
     this.flavour = flavour;
+    validateMetadata(metadata);
+    this.metadata = metadata;
+  }
+
+  @Override
+  void validateMetadata(KotlinClassMetadata.SyntheticClass metadata) {
+    if (metadata.isLambda()) {
+      SyntheticClassMetadataVisitor visitor = new SyntheticClassMetadataVisitor();
+      // To avoid lazy parsing/verifying metadata.
+      metadata.accept(visitor);
+    }
+  }
+
+  private static class SyntheticClassMetadataVisitor extends KmLambdaVisitor {
   }
 
   public boolean isLambda() {
-    return flavour == Flavour.KotlinStyleLambda || flavour == Flavour.JavaStyleLambda;
+    return isKotlinStyleLambda() || isJavaStyleLambda();
   }
 
   public boolean isKotlinStyleLambda() {
@@ -43,4 +75,31 @@
   public KotlinSyntheticClass asSyntheticClass() {
     return this;
   }
+
+  /**
+   * Returns {@code true} if the given {@link DexClass} is a Kotlin-style lambda:
+   *   a class that
+   *     1) is recognized as lambda in its Kotlin metadata;
+   *     2) directly extends kotlin.jvm.internal.Lambda
+   */
+  private static boolean isKotlinStyleLambda(
+      KotlinClassMetadata.SyntheticClass metadata, Kotlin kotlin, DexClass clazz) {
+    return metadata.isLambda()
+        && clazz.superType == kotlin.functional.lambdaType;
+  }
+
+  /**
+   * Returns {@code true} if the given {@link DexClass} is a Java-style lambda:
+   *   a class that
+   *     1) is recognized as lambda in its Kotlin metadata;
+   *     2) doesn't extend any other class;
+   *     3) directly implements only one Java SAM.
+   */
+  private static boolean isJavaStyleLambda(
+      KotlinClassMetadata.SyntheticClass metadata, Kotlin kotlin, DexClass clazz) {
+    return metadata.isLambda()
+        && clazz.superType == kotlin.factory.objectType
+        && clazz.interfaces.size() == 1;
+  }
+
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java b/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
index 1eb11e9..c67af00 100644
--- a/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
+++ b/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
@@ -46,14 +46,14 @@
     return path;
   }
 
-  public boolean matchesFile(Path file) {
+  public boolean matchesFile(String name) {
     if (isUnfiltered()) {
       return true;
     }
     boolean isNegated = false;
     for (String pattern : pattern) {
       isNegated = pattern.charAt(0) == '!';
-      boolean matches = matchAgainstFileName(file.toString(), 0, pattern, isNegated ? 1 : 0);
+      boolean matches = matchAgainstFileName(name, 0, pattern, isNegated ? 1 : 0);
       if (matches) {
         return !isNegated;
       }
@@ -63,7 +63,7 @@
   }
 
   private boolean containsFileSeparator(String string) {
-    return string.indexOf(File.separatorChar) != -1;
+    return string.indexOf('/') != -1;
   }
 
   private boolean matchAgainstFileName(String fileName, int namePos, String pattern,
@@ -95,7 +95,7 @@
         }
       } else {
         for (int i = namePos; i < fileName.length(); i++) {
-          if (!includeFileSeparators && fileName.charAt(i) == File.separatorChar) {
+          if (!includeFileSeparators && fileName.charAt(i) == '/') {
             return false;
           }
           if (matchAgainstFileName(fileName, i, pattern, patternPos + 1)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index d54c95c..4b57209 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DefaultUseRegistry;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
@@ -67,7 +69,7 @@
   private final DexApplication application;
   private final AppInfoWithLiveness appInfo;
   private final GraphLense graphLense;
-  private final Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
+  private final Map<DexType, DexType> mergedClasses = new HashMap<>();
   private final Timing timing;
   private Collection<DexMethod> invokes;
 
@@ -84,19 +86,109 @@
 
   // Returns a set of types that must not be merged into other types.
   private Set<DexType> getPinnedTypes(Iterable<DexProgramClass> classes) {
-    // TODO(christofferqa): Compute types from [classes] that must be pinned in order for vertical
-    // class merging to work.
-    return new HashSet<>();
+    Set<DexType> pinnedTypes = new HashSet<>();
+    for (DexProgramClass clazz : classes) {
+      for (DexEncodedMethod method : clazz.methods()) {
+        // TODO(christofferqa): Remove the invariant that the graph lense should not modify any
+        // methods from the sets alwaysInline and noSideEffects (see use of assertNotModifiedBy-
+        // Lense).
+        if (appInfo.alwaysInline.contains(method) || appInfo.noSideEffects.containsKey(method)) {
+          DexClass other = appInfo.definitionFor(method.method.proto.returnType);
+          if (other != null && other.isProgramClass()) {
+            // If we were to merge [other] into its sub class, then we would implicitly change the
+            // signature of this method, and therefore break the invariant.
+            pinnedTypes.add(other.type);
+          }
+          for (DexType parameterType : method.method.proto.parameters.values) {
+            other = appInfo.definitionFor(parameterType);
+            if (other != null && other.isProgramClass()) {
+              // If we were to merge [other] into its sub class, then we would implicitly change the
+              // signature of this method, and therefore break the invariant.
+              pinnedTypes.add(other.type);
+            }
+          }
+        }
+
+        // Avoid merging two types if this could remove a NoSuchMethodError, as illustrated by the
+        // following example. (Alternatively, it would be possible to merge A and B and rewrite the
+        // "invoke-super A.m" instruction into "invoke-super Object.m" to preserve the error. This
+        // situation should generally not occur in practice, though.)
+        //
+        //   class A {}
+        //   class B extends A {
+        //     public void m() {}
+        //   }
+        //   class C extends A {
+        //     public void m() {
+        //       invoke-super "A.m" <- should yield NoSuchMethodError, cannot merge A and B
+        //     }
+        //   }
+        if (!method.isStaticMethod()) {
+          method.registerCodeReferences(
+              new DefaultUseRegistry() {
+                @Override
+                public boolean registerInvokeSuper(DexMethod target) {
+                  DexClass targetClass = appInfo.definitionFor(target.getHolder());
+                  if (targetClass != null
+                      && targetClass.isProgramClass()
+                      && targetClass.lookupVirtualMethod(target) == null) {
+                    pinnedTypes.add(target.getHolder());
+                  }
+                  return true;
+                }
+              });
+        }
+      }
+    }
+    return pinnedTypes;
   }
 
   private boolean isMergeCandidate(DexProgramClass clazz, Set<DexType> pinnedTypes) {
-    // We can merge program classes if they are not instantiated, have a single subtype
-    // and we do not have to keep them.
-    return !clazz.isLibraryClass()
-        && !appInfo.instantiatedTypes.contains(clazz.type)
-        && !appInfo.isPinned(clazz.type)
-        && !pinnedTypes.contains(clazz.type)
-        && clazz.type.getSingleSubtype() != null;
+    if (appInfo.instantiatedTypes.contains(clazz.type)
+        || appInfo.isPinned(clazz.type)
+        || pinnedTypes.contains(clazz.type)) {
+      return false;
+    }
+    DexType singleSubtype = clazz.type.getSingleSubtype();
+    if (singleSubtype == null) {
+      // TODO(christofferqa): Even if [clazz] has multiple subtypes, we could still merge it into
+      // its subclass if [clazz] is not live. This should only be done, though, if it does not
+      // lead to members being duplicated.
+      return false;
+    }
+    DexClass targetClass = appInfo.definitionFor(singleSubtype);
+    if (mergeMayLeadToIllegalAccesses(clazz, targetClass)) {
+      return false;
+    }
+    for (DexEncodedField field : clazz.fields()) {
+      if (appInfo.isPinned(field.field)) {
+        return false;
+      }
+    }
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (appInfo.isPinned(method.method)) {
+        return false;
+      }
+      if (method.isInstanceInitializer() && disallowInlining(method)) {
+        // Cannot guarantee that markForceInline() will work.
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private boolean mergeMayLeadToIllegalAccesses(DexClass clazz, DexClass singleSubclass) {
+    if (clazz.type.isSamePackage(singleSubclass.type)) {
+      return false;
+    }
+    // TODO(christofferqa): To merge [clazz] into a class from another package we need to ensure:
+    // (A) All accesses to [clazz] and its members from inside the current package of [clazz] will
+    //     continue to work. This is guaranteed if [clazz] is public and all members of [clazz] are
+    //     either private or public.
+    // (B) All accesses from [clazz] to classes or members from the current package of [clazz] will
+    //     continue to work. This is guaranteed if the methods of [clazz] do not access any private
+    //     or protected classes or members from the current package of [clazz].
+    return true;
   }
 
   private void addProgramMethods(Set<Wrapper<DexMethod>> set, DexMethod method,
@@ -228,6 +320,11 @@
         }
         continue;
       }
+      // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
+      // to the super class.
+      if (fieldResolutionMayChange(clazz, targetClass)) {
+        continue;
+      }
       // Guard against the case where we have two methods that may get the same signature
       // if we replace types. This is rare, so we approximate and err on the safe side here.
       if (new CollisionDetector(clazz.type, targetClass.type, getInvokes(), mergedClasses)
@@ -273,6 +370,35 @@
     return renamedMembersLense.build(graphLense);
   }
 
+  private boolean fieldResolutionMayChange(DexClass source, DexClass target) {
+    if (source.type == target.superType) {
+      // If there is a "iget Target.f" or "iput Target.f" instruction in target, and the class
+      // Target implements an interface that declares a static final field f, this should yield an
+      // IncompatibleClassChangeError.
+      // TODO(christofferqa): In the following we only check if a static field from an interface
+      // shadows an instance field from [source]. We could actually check if there is an iget/iput
+      // instruction whose resolution would be affected by the merge. The situation where a static
+      // field shadows an instance field is probably not widespread in practice, though.
+      FieldSignatureEquivalence equivalence = FieldSignatureEquivalence.get();
+      Set<Wrapper<DexField>> staticFieldsInInterfacesOfTarget = new HashSet<>();
+      for (DexType interfaceType : target.interfaces.values) {
+        DexClass clazz = appInfo.definitionFor(interfaceType);
+        for (DexEncodedField staticField : clazz.staticFields()) {
+          staticFieldsInInterfacesOfTarget.add(equivalence.wrap(staticField.field));
+        }
+      }
+      for (DexEncodedField instanceField : source.instanceFields()) {
+        if (staticFieldsInInterfacesOfTarget.contains(equivalence.wrap(instanceField.field))) {
+          // An instruction "iget Target.f" or "iput Target.f" that used to hit a static field in an
+          // interface would now hit an instance field from [source], so that an IncompatibleClass-
+          // ChangeError would no longer be thrown. Abort merge.
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
   private class ClassMerger {
 
     private static final String CONSTRUCTOR_NAME = "constructor";
@@ -298,8 +424,7 @@
       // targetClass that are not currently contained.
       // Step 1: Merge methods
       Set<Wrapper<DexMethod>> existingMethods = new HashSet<>();
-      addAll(existingMethods, target.directMethods(), MethodSignatureEquivalence.get());
-      addAll(existingMethods, target.virtualMethods(), MethodSignatureEquivalence.get());
+      addAll(existingMethods, target.methods(), MethodSignatureEquivalence.get());
 
       List<DexEncodedMethod> directMethods = new ArrayList<>();
       for (DexEncodedMethod directMethod : source.directMethods()) {
@@ -341,8 +466,7 @@
 
         // Record that invoke-super instructions in the target class should be redirected to the
         // newly created direct method.
-        deferredRenamings.mapVirtualMethodToDirectInType(
-            virtualMethod.method, resultingDirectMethod.method, target.type);
+        redirectSuperCallsInTarget(virtualMethod.method, resultingDirectMethod.method);
 
         if (shadowedBy == null) {
           // In addition to the newly added direct method, create a virtual method such that we do
@@ -380,8 +504,7 @@
       }
       // Step 2: Merge fields
       Set<Wrapper<DexField>> existingFields = new HashSet<>();
-      addAll(existingFields, target.instanceFields(), FieldSignatureEquivalence.get());
-      addAll(existingFields, target.staticFields(), FieldSignatureEquivalence.get());
+      addAll(existingFields, target.fields(), FieldSignatureEquivalence.get());
       Collection<DexEncodedField> mergedStaticFields = mergeItems(
           Iterators.forArray(source.staticFields()),
           target.staticFields(),
@@ -432,6 +555,36 @@
       return deferredRenamings;
     }
 
+    private void redirectSuperCallsInTarget(DexMethod oldTarget, DexMethod newTarget) {
+      // If we merge class B into class C, and class C contains an invocation super.m(), then it
+      // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
+      // C.m$B denotes the direct method that has been created in C for B.m). In particular, there
+      // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is
+      // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()".
+      //
+      // We handle this by adding a mapping for [target] and all of its supertypes.
+      DexClass holder = target;
+      while (holder != null && holder.isProgramClass()) {
+        DexMethod signatureInHolder =
+            application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name);
+        // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
+        if (resolutionSucceeds(signatureInHolder)) {
+          deferredRenamings.mapVirtualMethodToDirectInType(
+              signatureInHolder, newTarget, target.type);
+          holder = holder.superType != null ? appInfo.definitionFor(holder.superType) : null;
+        } else {
+          break;
+        }
+      }
+    }
+
+    private boolean resolutionSucceeds(DexMethod targetMethod) {
+      DexClass enclosingClass = appInfo.definitionFor(targetMethod.holder);
+      return enclosingClass != null
+          && (enclosingClass.lookupVirtualMethod(targetMethod) != null
+              || appInfo.lookupSuperTarget(targetMethod, enclosingClass.type) != null);
+    }
+
     private DexEncodedMethod buildBridgeMethod(
         DexEncodedMethod signature, DexMethod invocationTarget) {
       DexType holder = target.type;
@@ -468,7 +621,7 @@
     }
 
     private <T extends KeyedDexItem<S>, S extends PresortedComparable<S>> void addAll(
-        Collection<Wrapper<S>> collection, T[] items, Equivalence<S> equivalence) {
+        Collection<Wrapper<S>> collection, Iterable<T> items, Equivalence<S> equivalence) {
       for (T item : items) {
         collection.add(equivalence.wrap(item.getKey()));
       }
@@ -836,6 +989,86 @@
     }
   }
 
+  private static boolean disallowInlining(DexEncodedMethod method) {
+    // TODO(christofferqa): Determine the situations where markForceInline() may fail, and ensure
+    // that we always return true here in these cases.
+    MethodInlineDecision registry = new MethodInlineDecision();
+    method.getCode().registerCodeReferences(registry);
+    return registry.isInliningDisallowed();
+  }
+
+  private static class MethodInlineDecision extends UseRegistry {
+    private boolean disallowInlining = false;
+
+    public boolean isInliningDisallowed() {
+      return disallowInlining;
+    }
+
+    private boolean allowInlining() {
+      return true;
+    }
+
+    private boolean disallowInlining() {
+      disallowInlining = true;
+      return true;
+    }
+
+    @Override
+    public boolean registerInvokeInterface(DexMethod method) {
+      return disallowInlining();
+    }
+
+    @Override
+    public boolean registerInvokeVirtual(DexMethod method) {
+      return disallowInlining();
+    }
+
+    @Override
+    public boolean registerInvokeDirect(DexMethod method) {
+      return allowInlining();
+    }
+
+    @Override
+    public boolean registerInvokeStatic(DexMethod method) {
+      return allowInlining();
+    }
+
+    @Override
+    public boolean registerInvokeSuper(DexMethod method) {
+      return allowInlining();
+    }
+
+    @Override
+    public boolean registerInstanceFieldWrite(DexField field) {
+      return allowInlining();
+    }
+
+    @Override
+    public boolean registerInstanceFieldRead(DexField field) {
+      return allowInlining();
+    }
+
+    @Override
+    public boolean registerNewInstance(DexType type) {
+      return allowInlining();
+    }
+
+    @Override
+    public boolean registerStaticFieldRead(DexField field) {
+      return allowInlining();
+    }
+
+    @Override
+    public boolean registerStaticFieldWrite(DexField field) {
+      return allowInlining();
+    }
+
+    @Override
+    public boolean registerTypeReference(DexType type) {
+      return allowInlining();
+    }
+  }
+
   public Collection<DexType> getRemovedClasses() {
     return Collections.unmodifiableCollection(mergedClasses.keySet());
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index 0f06ca8..a611105 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -4,8 +4,6 @@
 package com.android.tools.r8.utils;
 
 import static com.android.tools.r8.utils.FileUtils.isArchive;
-import static com.android.tools.r8.utils.FileUtils.isClassFile;
-import static com.android.tools.r8.utils.FileUtils.isDexFile;
 
 import com.android.tools.r8.DataDirectoryResource;
 import com.android.tools.r8.DataEntryResource;
@@ -23,8 +21,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -56,17 +52,16 @@
         ZipEntry entry = entries.nextElement();
         try (InputStream stream = zipFile.getInputStream(entry)) {
           String name = entry.getName();
-          Path path = Paths.get(name);
           Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
-          if (archive.matchesFile(path)) {
-            if (isDexFile(path)) {
+          if (archive.matchesFile(name)) {
+            if (ZipUtils.isDexFile(name)) {
               if (!ignoreDexInArchive) {
                 ProgramResource resource =
                     OneShotByteResource.create(
                         Kind.DEX, entryOrigin, ByteStreams.toByteArray(stream), null);
                 dexResources.add(resource);
               }
-            } else if (isClassFile(path)) {
+            } else if (ZipUtils.isClassFile(name)) {
               String descriptor = DescriptorUtils.guessTypeDescriptor(name);
               ProgramResource resource =
                   OneShotByteResource.create(
@@ -111,7 +106,7 @@
       final Enumeration<? extends ZipEntry> entries = zipFile.entries();
       while (entries.hasMoreElements()) {
         ZipEntry entry = entries.nextElement();
-        Path name = Paths.get(entry.getName());
+        String name = entry.getName();
         if (archive.matchesFile(name) && !isProgramResourceName(name)) {
           if (entry.isDirectory()) {
             resourceBrowser.visit(DataDirectoryResource.fromZip(zipFile, entry));
@@ -129,7 +124,7 @@
     }
   }
 
-  private boolean isProgramResourceName(Path name) {
-    return isClassFile(name) || (isDexFile(name) && !ignoreDexInArchive);
+  private boolean isProgramResourceName(String name) {
+    return ZipUtils.isClassFile(name) || (ZipUtils.isDexFile(name) && !ignoreDexInArchive);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
index f933b42..0c6cc14 100644
--- a/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
@@ -6,12 +6,11 @@
 import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import java.io.IOException;
-import java.nio.file.Paths;
 
 // Internal filtered class-file provider.
 class FilteredArchiveClassFileProvider extends ArchiveClassFileProvider {
 
   FilteredArchiveClassFileProvider(FilteredClassPath archive) throws IOException {
-    super(archive.getPath(), entry -> archive.matchesFile(Paths.get(entry)));
+    super(archive.getPath(), entry -> archive.matchesFile(entry));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index 849377b..fd50583 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -3,6 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.DEX_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.MODULE_INFO_CLASS;
+
 import com.android.tools.r8.errors.CompilationError;
 import com.google.common.io.ByteStreams;
 import java.io.File;
@@ -92,4 +96,17 @@
     stream.write(content);
     stream.closeEntry();
   }
+
+  public static boolean isDexFile(String entry) {
+    String name = entry.toLowerCase();
+    return name.endsWith(DEX_EXTENSION);
+  }
+
+  public static boolean isClassFile(String entry) {
+    String name = entry.toLowerCase();
+    if (name.endsWith(MODULE_INFO_CLASS)) {
+      return false;
+    }
+    return name.endsWith(CLASS_EXTENSION);
+  }
 }
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
index 2ef0123..23dc7e0 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/D8ApiUsageSample.java
@@ -391,8 +391,9 @@
         ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
         ZipEntry entry;
         while (null != (entry = zip.getNextEntry())) {
-          if (isClassFile(Paths.get(entry.getName()))) {
-            Origin origin = new ArchiveEntryOrigin(entry.getName(), zipOrigin);
+          String name = entry.getName();
+          if (isClassFile(name)) {
+            Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
             classfiles.add(new ClassFileContent(origin, readBytes(zip)));
           }
         }
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
index 2c2e8d7..7bce10a 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -382,8 +382,9 @@
         ZipInputStream zip = new ZipInputStream(Files.newInputStream(file), StandardCharsets.UTF_8);
         ZipEntry entry;
         while (null != (entry = zip.getNextEntry())) {
-          if (isClassFile(Paths.get(entry.getName()))) {
-            Origin origin = new ArchiveEntryOrigin(entry.getName(), zipOrigin);
+          String name = entry.getName();
+          if (isClassFile(name)) {
+            Origin origin = new ArchiveEntryOrigin(name, zipOrigin);
             classfiles.add(new ClassFileContent(origin, readBytes(zip)));
           }
         }
diff --git a/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java b/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java
new file mode 100644
index 0000000..6ceb0a2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ArchiveClassFileProviderTest.java
@@ -0,0 +1,43 @@
+// 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;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class ArchiveClassFileProviderTest {
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  public Path createZip() throws IOException {
+    Path tempRoot = temporaryFolder.getRoot().toPath();
+    Path zipFile = tempRoot.resolve("zipfile.zip");
+    ZipOutputStream zipStream =
+        new ZipOutputStream(new FileOutputStream(zipFile.toFile()), StandardCharsets.UTF_8);
+    ZipEntry entry = new ZipEntry("non-ascii:$\u02CF");
+    zipStream.putNextEntry(entry);
+    zipStream.write(10);
+    zipStream.close();
+    return zipFile;
+  }
+
+  @Test
+  public void testSystemLocale() throws IOException, ResourceException {
+    // Set the locale used for Paths to ASCII which will make Path creation fail
+    // for non-ascii names.
+    System.setProperty("sun.jnu.encoding", "ASCII");
+    Path zipFile = createZip();
+    new ArchiveClassFileProvider(zipFile);
+    ArchiveProgramResourceProvider.fromArchive(zipFile).getProgramResources();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 258aa43..ee82fc4 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1920,11 +1920,6 @@
           // 1) t03
           // java.lang.AssertionError: expected null, but was:<[I@1b32f32>
 
-          .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01",
-              match(runtimes(Version.DEFAULT, Version.V7_0_0, Version.V6_0_1, Version.V5_1_1)))
-          // 1) t03
-          // java.lang.AssertionError: reference is not enqueued after 2 sec
-
           .put("lang.StackTraceElement.toString.StackTraceElement_toString_A01",
               match(runtimes(Version.DEFAULT)))
           // 1) t03
@@ -4959,8 +4954,9 @@
           .put("lang.ref.PhantomReference.isEnqueued.PhantomReference_isEnqueued_A01",
               match(runtimesUpTo(Version.V4_4_4)))
 
-          .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01",
-              match(runtimesUpTo(Version.V4_4_4)))
+          .put("lang.ref.WeakReference.isEnqueued.WeakReference_isEnqueued_A01", any())
+          // 1) t03
+          // java.lang.AssertionError: reference is not enqueued after 2 sec
 
           .put("lang.ref.WeakReference.enqueue.WeakReference_enqueue_A01",
               match(runtimesUpTo(Version.V4_4_4)))
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index 6069b4c..da32cce 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -31,7 +31,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-
 @RunWith(Parameterized.class)
 public class MethodHandleTestRunner extends TestBase {
   static final Class<?> CLASS = MethodHandleTest.class;
@@ -169,7 +168,9 @@
           Arrays.asList(
               "-keep public class com.android.tools.r8.cf.MethodHandleTest {",
               "  public static void main(...);",
-              "}"),
+              "}",
+              // Disallow merging MethodHandleTest$I into MethodHandleTest$Impl
+              "-keep public interface com.android.tools.r8.cf.MethodHandleTest$I"),
           Origin.unknown());
     }
     try {
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 52e2313..7016163 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -238,7 +238,6 @@
     assertEquals(2, numberOfMoveExceptionInstructions);
   }
 
-  @Ignore("b/73958515")
   @Test
   public void testNoIllegalClassAccess() throws Exception {
     String main = "classmerging.SimpleInterfaceAccessTest";
@@ -258,18 +257,36 @@
             "classmerging.pkg.SimpleInterfaceImplRetriever",
             "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
     runTest(main, programFiles, preservedClassNames);
+  }
+
+  @Ignore("b/73958515")
+  @Test
+  public void testNoIllegalClassAccessWithAccessModifications() throws Exception {
     // If access modifications are allowed then SimpleInterface should be merged into
     // SimpleInterfaceImpl.
-    preservedClassNames =
+    String main = "classmerging.SimpleInterfaceAccessTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("SimpleInterface.class"),
+          CF_DIR.resolve("SimpleInterfaceAccessTest.class"),
+          CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever.class"),
+          CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class")
+        };
+    ImmutableSet<String> preservedClassNames =
         ImmutableSet.of(
             "classmerging.SimpleInterfaceAccessTest",
             "classmerging.pkg.SimpleInterfaceImplRetriever",
             "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
+    // Allow access modifications (and prevent SimpleInterfaceImplRetriever from being removed as
+    // a result of inlining).
     runTest(
         main,
         programFiles,
         preservedClassNames,
-        getProguardConfig(EXAMPLE_KEEP, "-allowaccessmodification"));
+        getProguardConfig(
+            EXAMPLE_KEEP,
+            "-allowaccessmodification",
+            "-keep public class classmerging.pkg.SimpleInterfaceImplRetriever"));
   }
 
   @Test
@@ -328,6 +345,7 @@
     }
     for (String rule : additionalRules) {
       builder.append(rule);
+      builder.append(System.lineSeparator());
     }
     return builder.toString();
   }
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
new file mode 100644
index 0000000..2c1479e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSpecialTest.java
@@ -0,0 +1,24 @@
+// 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.graph;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.invokespecial.Main;
+import com.android.tools.r8.graph.invokespecial.TestClassDump;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class InvokeSpecialTest extends AsmTestBase {
+
+  @Ignore("b/110175213")
+  @Test
+  public void testInvokeSpecial() throws Exception {
+    ensureSameOutput(
+        Main.class.getCanonicalName(),
+        ToolHelper.getClassAsBytes(Main.class),
+        TestClassDump.dump());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
new file mode 100644
index 0000000..deb316d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.invokespecial;
+
+public class Main {
+
+  public static void main(String[] args) {
+    TestClass x = new TestClass();
+    x.m(true);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
new file mode 100644
index 0000000..c08091e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
@@ -0,0 +1,15 @@
+// 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.graph.invokespecial;
+
+public class TestClass {
+
+  public void m(boolean recurse) {
+    System.out.println(recurse);
+    if (recurse) {
+      m(false);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
new file mode 100644
index 0000000..ae336834
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
@@ -0,0 +1,67 @@
+// 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.graph.invokespecial;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running ./tools/asmifier.py build/classes/test/com/android/tools/r8/graph/-
+// invokespecial/TestClass.class, and changing the invoke-virtual TestClass.m() instruction to
+// an invoke-special instruction.
+public class TestClassDump implements Opcodes {
+
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(
+        V1_8,
+        ACC_PUBLIC + ACC_SUPER,
+        "com/android/tools/r8/graph/invokespecial/TestClass",
+        null,
+        "java/lang/Object",
+        null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "m", "(Z)V", null, null);
+      mv.visitCode();
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitVarInsn(ILOAD, 1);
+      mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false);
+      mv.visitVarInsn(ILOAD, 1);
+      Label l0 = new Label();
+      mv.visitJumpInsn(IFEQ, l0);
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitInsn(ICONST_0);
+      // Note: Changed from INVOKEVIRTUAL to INVOKESPECIAL.
+      mv.visitMethodInsn(
+          INVOKESPECIAL, "com/android/tools/r8/graph/invokespecial/TestClass", "m", "(Z)V", false);
+      mv.visitLabel(l0);
+      mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(2, 2);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
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 74e3d5e..6ebb08b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -83,13 +83,16 @@
           .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
           .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
 
-  private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false;
+  private Consumer<InternalOptions> disableClassInliningAndMerging = o -> {
+    o.enableClassInlining = false;
+    o.enableClassMerging = false;
+  };
 
   @Test
   public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -112,7 +115,7 @@
   public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -134,7 +137,7 @@
   public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_useProtectedProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -157,7 +160,7 @@
   public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -180,7 +183,7 @@
   public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -203,7 +206,7 @@
   public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
@@ -228,7 +231,7 @@
   public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
         "lateInitProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -252,7 +255,7 @@
   public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -275,7 +278,7 @@
   public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
         "lateInitProperty_useProtectedLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -296,7 +299,7 @@
   public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -315,7 +318,7 @@
   public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
@@ -334,7 +337,7 @@
   public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -351,7 +354,7 @@
   public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject classSubject = checkClassIsKept(dexInspector,
           USER_DEFINED_PROPERTY_CLASS.getClassName());
@@ -377,7 +380,7 @@
   public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassIsKept(dexInspector,
           "properties.CompanionProperties");
@@ -408,7 +411,7 @@
   public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassIsKept(dexInspector,
           "properties.CompanionProperties");
@@ -442,7 +445,7 @@
   public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassIsKept(dexInspector,
           "properties.CompanionProperties");
@@ -473,7 +476,7 @@
   public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassIsKept(dexInspector,
           "properties.CompanionProperties");
@@ -505,7 +508,7 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePrivateLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
       ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
@@ -536,7 +539,7 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_useInternalLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
       ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
@@ -560,7 +563,7 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePublicLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliner, (app) -> {
+    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
       ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
diff --git a/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java b/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java
index f27813e..7c243ae 100644
--- a/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java
@@ -34,16 +34,8 @@
 
   private void testPath(List<String> filters, List<String> positives, List<String> negatives) {
     FilteredClassPath path = makeFilteredClassPath(filters);
-    Assert.assertTrue(
-        positives.stream().map(FilteredClassPathTest::adaptFileSeparator).map(Paths::get)
-            .allMatch(path::matchesFile));
-    Assert.assertFalse(
-        negatives.stream().map(FilteredClassPathTest::adaptFileSeparator).map(Paths::get)
-            .allMatch(path::matchesFile));
-  }
-
-  private static String adaptFileSeparator(String s) {
-    return s.replace('/', File.separatorChar);
+    Assert.assertTrue(positives.stream().allMatch(path::matchesFile));
+    Assert.assertFalse(negatives.stream().allMatch(path::matchesFile));
   }
 
   private static FilteredClassPath makeFilteredClassPath(List<String> filters) {
@@ -51,9 +43,7 @@
   }
 
   private static FilteredClassPath makeFilteredClassPath(Path path, List<String> filters) {
-    return new FilteredClassPath(path,
-        filters.stream().map(FilteredClassPathTest::adaptFileSeparator)
-            .collect(ImmutableList.toImmutableList()));
+    return new FilteredClassPath(path, ImmutableList.copyOf(filters));
   }
 
   @Test