Merge commit 'b9cfe1d0e4c97d5d6e9ffcadd57c368cede4fd13' into dev-release
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
new file mode 100644
index 0000000..a7ac9e3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.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;
+
+import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
+
+import com.android.tools.r8.DexIndexedConsumer.DirectoryConsumer;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LazyLoadedDexApplication;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.FeatureClassMapping;
+import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+@Keep
+public final class DexSplitterHelper {
+
+  public static void run(
+      D8Command command, FeatureClassMapping featureClassMapping, String output, String proguardMap)
+      throws CompilationFailedException {
+    ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
+    try {
+      ExceptionUtils.withCompilationHandler(
+          command.getReporter(),
+          () -> run(command, featureClassMapping, output, proguardMap, executor));
+    } finally {
+      executor.shutdown();
+    }
+  }
+
+  public static void run(
+      D8Command command,
+      FeatureClassMapping featureClassMapping,
+      String output,
+      String proguardMap,
+      ExecutorService executor)
+      throws IOException {
+    InternalOptions options = command.getInternalOptions();
+    options.desugarState = DesugarState.OFF;
+    options.enableMainDexListCheck = false;
+    options.ignoreMainDexMissingClasses = true;
+    options.minimalMainDex = false;
+    assert !options.isMinifying();
+    options.inlinerOptions().enableInlining = false;
+    options.outline.enabled = false;
+
+    try {
+      Timing timing = new Timing("DexSplitter");
+      ApplicationReader applicationReader =
+          new ApplicationReader(command.getInputApp(), options, timing);
+      DexApplication app = applicationReader.read(executor);
+      MainDexInfo mainDexInfo = applicationReader.readMainDexClasses(app);
+
+      List<Marker> markers = app.dexItemFactory.extractMarkers();
+
+      ClassNameMapper mapper = null;
+      if (proguardMap != null) {
+        mapper = ClassNameMapper.mapperFromFile(Paths.get(proguardMap));
+      }
+      Map<String, LazyLoadedDexApplication.Builder> applications =
+          getDistribution(app, featureClassMapping, mapper);
+      for (Entry<String, LazyLoadedDexApplication.Builder> entry : applications.entrySet()) {
+        String feature = entry.getKey();
+        timing.begin("Feature " + feature);
+        DexApplication featureApp = entry.getValue().build();
+        assert !options.hasMethodsFilter();
+
+        // If this is the base, we add the main dex list.
+        AppInfo appInfo =
+            feature.equals(featureClassMapping.getBaseName())
+                ? AppInfo.createInitialAppInfo(featureApp, mainDexInfo)
+                : AppInfo.createInitialAppInfo(featureApp);
+        AppView<AppInfo> appView = AppView.createForD8(appInfo);
+
+        // Run d8 optimize to ensure jumbo strings are handled.
+        D8.optimize(appView, options, timing, executor);
+
+        // We create a specific consumer for each split.
+        Path outputDir = Paths.get(output).resolve(entry.getKey());
+        if (!Files.exists(outputDir)) {
+          Files.createDirectory(outputDir);
+        }
+        DexIndexedConsumer consumer = new DirectoryConsumer(outputDir);
+
+        try {
+          new ApplicationWriter(
+                  appView,
+                  markers,
+                  NamingLens.getIdentityLens(),
+                  consumer)
+              .write(executor);
+          options.printWarnings();
+        } finally {
+          consumer.finished(options.reporter);
+        }
+        timing.end();
+      }
+    } catch (ExecutionException e) {
+      throw unwrapExecutionException(e);
+    } catch (FeatureMappingException e) {
+      options.reporter.error(e.getMessage());
+    } finally {
+      options.signalFinishedToConsumers();
+    }
+  }
+
+  private static Map<String, LazyLoadedDexApplication.Builder> getDistribution(
+      DexApplication app, FeatureClassMapping featureClassMapping, ClassNameMapper mapper)
+      throws FeatureMappingException {
+    Map<String, LazyLoadedDexApplication.Builder> applications = new HashMap<>();
+    for (DexProgramClass clazz : app.classes()) {
+      String clazzName =
+          mapper != null ? mapper.deobfuscateClassName(clazz.toString()) : clazz.toString();
+      String feature = featureClassMapping.featureForClass(clazzName);
+      LazyLoadedDexApplication.Builder featureApplication = applications.get(feature);
+      if (featureApplication == null) {
+        featureApplication = DexApplication.builder(app.options, app.timing);
+        applications.put(feature, featureApplication);
+      }
+      featureApplication.addProgramClass(clazz);
+    }
+    return applications;
+  }
+
+  public static void runD8ForTesting(D8Command command, boolean dontCreateMarkerInD8)
+      throws CompilationFailedException {
+    InternalOptions options = command.getInternalOptions();
+    options.testing.dontCreateMarkerInD8 = dontCreateMarkerInD8;
+    D8.runForTesting(command.getInputApp(), options);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index 2e44e14..2d836ae 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.bisect.Bisect;
 import com.android.tools.r8.cf.CfVerifierTool;
 import com.android.tools.r8.compatproguard.CompatProguard;
+import com.android.tools.r8.dexsplitter.DexSplitter;
 import com.android.tools.r8.relocator.RelocatorCommandLine;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import java.util.Arrays;
@@ -40,6 +41,9 @@
       case "dexsegments":
         DexSegments.main(shift(args));
         break;
+      case "dexsplitter":
+        DexSplitter.main(shift(args));
+        break;
       case "disasm":
         Disassemble.main(shift(args));
         break;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
index af0b133..bf9dc6b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArithmeticBinop.java
@@ -225,7 +225,6 @@
       DexItemFactory dexItemFactory) {
     // ..., value1, value2 →
     // ..., result
-    FrameType frameType = FrameType.fromNumericType(type, dexItemFactory);
-    return state.pop(appView, frameType, frameType).push(frameType);
+    return state.popInitialized(appView, type).popInitialized(appView, type).push(appView, type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index cdd43c2..e696048 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -100,7 +99,10 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., arrayref →
+    // ..., length
+    return frame
+        .popInitialized(appView, dexItemFactory.objectArrayType)
+        .push(dexItemFactory.intType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 5069f14..8cc8e63 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -145,7 +144,11 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., arrayref, index →
+    // ..., value
+    return frame
+        .popInitialized(appView, dexItemFactory.intType)
+        .popInitialized(appView, dexItemFactory.objectArrayType)
+        .push(appView, type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index c51336f..942f633 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -136,7 +135,11 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., arrayref, index, value →
+    // ...
+    return frame
+        .popInitialized(appView, type)
+        .popInitialized(appView, dexItemFactory.intType)
+        .popInitialized(appView, dexItemFactory.objectArrayType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 55dd90f..323a253 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -149,7 +148,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., objectref →
+    // ..., objectref
+    return frame.popInitialized(appView, dexItemFactory.objectType).push(type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
index 4ca69a0..4a42ca3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCmp.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -149,7 +148,11 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., value1, value2 →
+    // ..., result
+    return frame
+        .popInitialized(appView, type)
+        .popInitialized(appView, type)
+        .push(dexItemFactory.intType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index 10240e5..ff36775 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -156,7 +155,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ... →
+    // ..., value
+    return frame.push(dexItemFactory.classType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index 666da46..54c7820 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -242,7 +242,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ... →
+    // ..., value
+    return frame.push(dexItemFactory.classType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
index 2f79cea..50564e3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodHandle.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -120,7 +119,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ... →
+    // ..., value
+    return frame.push(dexItemFactory.methodHandleType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
index 0cc8d78..b262ff9 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstMethodType.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -118,7 +117,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ... →
+    // ..., value
+    return frame.push(dexItemFactory.methodTypeType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
index efcd063..e849e05 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNull.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -90,7 +89,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ... →
+    // ..., value
+    return frame.push(DexItemFactory.nullValueType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index cee2f4a..34f67c7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -238,7 +237,8 @@
       DexItemFactory dexItemFactory) {
     // ... →
     // ..., value
-    frameBuilder.push(type.toPrimitiveType().toDexType(dexItemFactory));
+    assert type.isPrimitive();
+    frameBuilder.push(type.toDexType(dexItemFactory));
   }
 
   @Override
@@ -247,7 +247,9 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ... →
+    // ..., value
+    assert type.isPrimitive();
+    return frame.push(appView, type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index 5ab3742..8bccf77 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -140,7 +139,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ... →
+    // ..., value
+    return frame.push(dexItemFactory.stringType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index e7b8c99..4ab2860 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -156,7 +156,7 @@
     }
 
     public static FrameType fromNumericType(NumericType numericType, DexItemFactory factory) {
-      return FrameType.initialized(numericType.dexTypeFor(factory));
+      return FrameType.initialized(numericType.toDexType(factory));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
index f6d94b7..dacbd9e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIfCmp.java
@@ -156,10 +156,7 @@
       DexItemFactory dexItemFactory) {
     // ..., value1, value2 →
     // ...
-    DexType type =
-        this.type.isObject()
-            ? dexItemFactory.objectType
-            : this.type.toPrimitiveType().toDexType(dexItemFactory);
+    DexType type = this.type.toDexType(dexItemFactory);
     frameBuilder.popAndDiscardInitialized(type, type);
     frameBuilder.checkTarget(target);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
index 4df1ecd..a168090 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInitClass.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -133,7 +132,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., →
+    // ..., value
+    return frame.push(dexItemFactory.intType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
index df84afd..c461c07 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.code.CfOrDexInstanceFieldRead;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -84,7 +83,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., objectref →
+    // ..., value
+    return frame.popInitialized(appView, getField().getHolderType()).push(getField().getType());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
index da48878..53f9904 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceOf.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -143,7 +142,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., objectref →
+    // ..., result
+    return frame.popInitialized(appView, dexItemFactory.objectType).push(dexItemFactory.intType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index 421902e..121c288 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -187,7 +186,13 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., [arg1, [arg2 ...]] →
+    // ...
+    frame = frame.popInitialized(appView, callSite.getMethodProto().getParameters().getBacking());
+    DexType returnType = callSite.getMethodProto().getReturnType();
+    if (returnType.isVoidType()) {
+      return frame;
+    }
+    return frame.push(returnType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
index a5ee7eb..3cf6f34 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfLogicalBinop.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -206,7 +205,22 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., value1, value2 →
+    // ..., result
+    NumericType value1Type = type;
+    NumericType value2Type;
+    switch (opcode) {
+      case And:
+      case Or:
+      case Xor:
+        value2Type = value1Type;
+        break;
+      default:
+        value2Type = NumericType.INT;
+    }
+    return frame
+        .popInitialized(appView, value2Type)
+        .popInitialized(appView, value1Type)
+        .push(appView, value1Type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
index 85ddc69..f860f58 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMonitor.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -112,7 +111,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., objectref →
+    // ...
+    return frame.popInitialized(appView, dexItemFactory.objectType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
index 796a93f..a69ff20 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfMultiANewArray.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -149,7 +148,11 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., count1, [count2, ...] →
+    // ..., arrayref
+    for (int i = 0; i < dimensions; i++) {
+      frame = frame.popInitialized(appView, dexItemFactory.intType);
+    }
+    return frame.push(type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
index 9c6a7f8..c25b0ff 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNeg.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -138,7 +137,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., value →
+    // ..., result
+    return frame.popInitialized(appView, type).push(appView, type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index ba81a58..261db2e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -173,7 +172,6 @@
       DexItemFactory dexItemFactory) {
     // ..., count →
     // ..., arrayref
-    assert type.isArrayType();
     frameBuilder.popAndDiscardInitialized(dexItemFactory.intType).push(type);
   }
 
@@ -183,7 +181,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., count →
+    // ..., arrayref
+    return frame.popInitialized(appView, dexItemFactory.intType).push(type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
index 8683ddd..73d04d1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewUnboxedEnum.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -135,7 +134,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ... →
+    // ..., objectref
+    return frame.push(type);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNop.java b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
index e8b3a2e..7bc083c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNop.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNop.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -92,7 +91,6 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    return frame;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
index 0341487..ee83adb 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNumberConversion.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -210,7 +209,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., value →
+    // ..., result
+    return frame.popInitialized(appView, from).push(appView, to);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 155b17d..42ebf38 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -125,7 +124,6 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    return frame;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
index 4a9fd91..ba04a1c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfRecordFieldValues.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.cf.code;
 
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -128,7 +127,9 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    for (DexField ignored : fields) {
+      frame = frame.popInitialized(appView, dexItemFactory.objectType);
+    }
+    return frame.push(dexItemFactory.objectArrayType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
index 5f5e5a9..c929d68 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -524,8 +524,9 @@
     switch (opcode) {
       case Pop:
         {
-          // TODO(b/214496607): Implement this.
-          throw new Unimplemented();
+          // ..., value →
+          // ...
+          return frame.pop(appView, FrameType.oneWord());
         }
       case Pop2:
         {
@@ -536,7 +537,9 @@
         // ..., value →
         // ..., value, value
         return frame.pop(
-            appView, FrameType.oneWord(), frameType -> frame.push(frameType).push(frameType));
+            appView,
+            FrameType.oneWord(),
+            (newFrame, frameType) -> newFrame.push(frameType).push(frameType));
       case DupX1:
         {
           // TODO(b/214496607): Implement this.
@@ -564,8 +567,16 @@
         }
       case Swap:
         {
-          // TODO(b/214496607): Implement this.
-          throw new Unimplemented();
+          // ..., value2, value1 →
+          // ..., value1, value2
+          return frame.pop(
+              appView,
+              FrameType.oneWord(),
+              (newFrame1, frameType1) ->
+                  newFrame1.pop(
+                      appView,
+                      FrameType.oneWord(),
+                      (newFrame2, frameType2) -> newFrame2.push(frameType1).push(frameType2)));
         }
       default:
         throw new Unreachable("Invalid opcode for CfStackInstruction");
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
index 26114d8..3c64e6a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.cf.code;
 
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -83,7 +82,8 @@
       ProgramMethod context,
       AppView<?> appView,
       DexItemFactory dexItemFactory) {
-    // TODO(b/214496607): Implement this.
-    throw new Unimplemented();
+    // ..., value →
+    // ...
+    return frame.popInitialized(appView, getField().getType());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 8671b68..580d5ef 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -343,6 +343,10 @@
   @Override
   public final int acceptCompareTo(Instruction other, CompareToVisitor visitor) {
     int opcodeDiff = visitor.visitInt(getCompareToId(), other.getCompareToId());
+    if (opcodeDiff != 0) {
+      return opcodeDiff;
+    }
+    opcodeDiff = visitor.visitInt(getOffset(), other.getOffset());
     return opcodeDiff != 0 ? opcodeDiff : internalAcceptCompareTo(other, visitor);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
new file mode 100644
index 0000000..4ee416c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -0,0 +1,377 @@
+// 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.dexsplitter;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexSplitterHelper;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AbortException;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.ExceptionUtils;
+import com.android.tools.r8.utils.FeatureClassMapping;
+import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.android.tools.r8.utils.OptionsParsing;
+import com.android.tools.r8.utils.OptionsParsing.ParseContext;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+@Keep
+public final class DexSplitter {
+
+  private static final String DEFAULT_OUTPUT_DIR = "output";
+  private static final String DEFAULT_BASE_NAME = "base";
+
+  private static final boolean PRINT_ARGS = false;
+
+  public static class FeatureJar {
+    private String jar;
+    private String outputName;
+
+    public FeatureJar(String jar, String outputName) {
+      this.jar = jar;
+      this.outputName = outputName;
+    }
+
+    public FeatureJar(String jar) {
+      this(jar, featureNameFromJar(jar));
+    }
+
+    public String getJar() {
+      return jar;
+    }
+
+    public String getOutputName() {
+      return outputName;
+    }
+
+    private static String featureNameFromJar(String jar) {
+      Path jarPath = Paths.get(jar);
+      String featureName = jarPath.getFileName().toString();
+      if (featureName.endsWith(".jar") || featureName.endsWith(".zip")) {
+        featureName = featureName.substring(0, featureName.length() - 4);
+      }
+      return featureName;
+    }
+  }
+
+  private static class ZipFileOrigin extends PathOrigin {
+
+    public ZipFileOrigin(Path path) {
+      super(path);
+    }
+
+    @Override
+    public String part() {
+      return "splitting of file '" + super.part() + "'";
+    }
+  }
+
+  @Keep
+  public static final class Options {
+    private final DiagnosticsHandler diagnosticsHandler;
+    private List<String> inputArchives = new ArrayList<>();
+    private List<FeatureJar> featureJars = new ArrayList<>();
+    private List<String> baseJars = new ArrayList<>();
+    private String baseOutputName = DEFAULT_BASE_NAME;
+    private String output = DEFAULT_OUTPUT_DIR;
+    private String featureSplitMapping;
+    private String proguardMap;
+    private String mainDexList;
+    private boolean splitNonClassResources = false;
+
+    public Options() {
+      this(new DiagnosticsHandler() {});
+    }
+
+    public Options(DiagnosticsHandler diagnosticsHandler) {
+      this.diagnosticsHandler = diagnosticsHandler;
+    }
+
+    public DiagnosticsHandler getDiagnosticsHandler() {
+      return diagnosticsHandler;
+    }
+
+    public String getMainDexList() {
+      return mainDexList;
+    }
+
+    public void setMainDexList(String mainDexList) {
+      this.mainDexList = mainDexList;
+    }
+
+    public String getOutput() {
+      return output;
+    }
+
+    public void setOutput(String output) {
+      this.output = output;
+    }
+
+    public String getFeatureSplitMapping() {
+      return featureSplitMapping;
+    }
+
+    public void setFeatureSplitMapping(String featureSplitMapping) {
+      this.featureSplitMapping = featureSplitMapping;
+    }
+
+    public String getProguardMap() {
+      return proguardMap;
+    }
+
+    public void setProguardMap(String proguardMap) {
+      this.proguardMap = proguardMap;
+    }
+
+    public String getBaseOutputName() {
+      return baseOutputName;
+    }
+
+    public void setBaseOutputName(String baseOutputName) {
+      this.baseOutputName = baseOutputName;
+    }
+
+    public void addInputArchive(String inputArchive) {
+      inputArchives.add(inputArchive);
+    }
+
+    public void addBaseJar(String baseJar) {
+      baseJars.add(baseJar);
+    }
+
+    private void addFeatureJar(FeatureJar featureJar) {
+      featureJars.add(featureJar);
+    }
+
+    public void addFeatureJar(String jar) {
+      featureJars.add(new FeatureJar(jar));
+    }
+
+    public void addFeatureJar(String jar, String outputName) {
+      featureJars.add(new FeatureJar(jar, outputName));
+    }
+
+    public void setSplitNonClassResources(boolean value) {
+      splitNonClassResources = value;
+    }
+
+    public ImmutableList<String> getInputArchives() {
+      return ImmutableList.copyOf(inputArchives);
+    }
+
+    ImmutableList<FeatureJar> getFeatureJars() {
+      return ImmutableList.copyOf(featureJars);
+    }
+
+    ImmutableList<String> getBaseJars() {
+      return ImmutableList.copyOf(baseJars);
+    }
+
+    // Shorthand error messages.
+    public Diagnostic error(String msg) {
+      StringDiagnostic error = new StringDiagnostic(msg);
+      diagnosticsHandler.error(error);
+      return error;
+    }
+  }
+
+  /**
+   * Parse a feature jar argument and return the corresponding FeatureJar representation.
+   * Default to use the name of the jar file if the argument contains no ':', if the argument
+   * contains ':', then use the value after the ':' as the name.
+   * @param argument
+   */
+  private static FeatureJar parseFeatureJarArgument(String argument) {
+    if (argument.contains(":")) {
+      String[] parts = argument.split(":");
+      if (parts.length > 2) {
+        throw new RuntimeException("--feature-jar argument contains more than one :");
+      }
+      return new FeatureJar(parts[0], parts[1]);
+    }
+    return new FeatureJar(argument);
+  }
+
+  private static Options parseArguments(String[] args) {
+    Options options = new Options();
+    ParseContext context = new ParseContext(args);
+    while (context.head() != null) {
+      List<String> inputs = OptionsParsing.tryParseMulti(context, "--input");
+      if (inputs != null) {
+        inputs.forEach(options::addInputArchive);
+        continue;
+      }
+      List<String> featureJars = OptionsParsing.tryParseMulti(context, "--feature-jar");
+      if (featureJars != null) {
+        featureJars.forEach((feature) -> options.addFeatureJar(parseFeatureJarArgument(feature)));
+        continue;
+      }
+      List<String> baseJars = OptionsParsing.tryParseMulti(context, "--base-jar");
+      if (baseJars != null) {
+        baseJars.forEach(options::addBaseJar);
+        continue;
+      }
+      String output = OptionsParsing.tryParseSingle(context, "--output", "-o");
+      if (output != null) {
+        options.setOutput(output);
+        continue;
+      }
+
+      String mainDexList= OptionsParsing.tryParseSingle(context, "--main-dex-list", null);
+      if (mainDexList!= null) {
+        options.setMainDexList(mainDexList);
+        continue;
+      }
+
+      String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null);
+      if (proguardMap != null) {
+        options.setProguardMap(proguardMap);
+        continue;
+      }
+      String baseOutputName = OptionsParsing.tryParseSingle(context, "--base-output-name", null);
+      if (baseOutputName != null) {
+        options.setBaseOutputName(baseOutputName);
+        continue;
+      }
+      String featureSplit = OptionsParsing.tryParseSingle(context, "--feature-splits", null);
+      if (featureSplit != null) {
+        options.setFeatureSplitMapping(featureSplit);
+        continue;
+      }
+      Boolean b = OptionsParsing.tryParseBoolean(context, "--split-non-class-resources");
+      if (b != null) {
+        options.setSplitNonClassResources(b);
+        continue;
+      }
+      throw new RuntimeException(String.format("Unknown options: '%s'.", context.head()));
+    }
+    return options;
+  }
+
+  private static FeatureClassMapping createFeatureClassMapping(Options options)
+      throws FeatureMappingException {
+    if (options.getFeatureSplitMapping() != null) {
+      return FeatureClassMapping.fromSpecification(
+          Paths.get(options.getFeatureSplitMapping()), options.getDiagnosticsHandler());
+    }
+    assert !options.getFeatureJars().isEmpty();
+    return FeatureClassMapping.Internal.fromJarFiles(options.getFeatureJars(),
+        options.getBaseJars(), options.getBaseOutputName(), options.getDiagnosticsHandler());
+  }
+
+  private static void run(String[] args)
+      throws CompilationFailedException, FeatureMappingException {
+    Options options = parseArguments(args);
+    run(options);
+  }
+
+  public static void run(Options options)
+      throws FeatureMappingException, CompilationFailedException {
+    Diagnostic error = null;
+    if (options.getInputArchives().isEmpty()) {
+      error = options.error("Need at least one --input");
+    }
+    if (options.getFeatureSplitMapping() == null && options.getFeatureJars().isEmpty()) {
+      error = options.error("You must supply a feature split mapping or feature jars");
+    }
+    if (options.getFeatureSplitMapping() != null && !options.getFeatureJars().isEmpty()) {
+      error = options.error("You can't supply both a feature split mapping and feature jars");
+    }
+    if (error != null) {
+      throw new AbortException(error);
+    }
+
+    D8Command.Builder builder = D8Command.builder(options.diagnosticsHandler);
+
+
+    for (String s : options.inputArchives) {
+      builder.addProgramFiles(Paths.get(s));
+    }
+    // We set the actual consumer on the ApplicationWriter when we have calculated the distribution
+    // since we don't yet know the distribution.
+    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    if (options.getMainDexList() != null) {
+      builder.addMainDexListFiles(Paths.get(options.getMainDexList()));
+    }
+
+    FeatureClassMapping featureClassMapping = createFeatureClassMapping(options);
+
+    DexSplitterHelper.run(
+        builder.build(), featureClassMapping, options.getOutput(), options.getProguardMap());
+
+    if (options.splitNonClassResources) {
+      splitNonClassResources(options, featureClassMapping);
+    }
+  }
+
+  private static void splitNonClassResources(Options options,
+      FeatureClassMapping featureClassMapping) {
+    for (String s : options.inputArchives) {
+      try (ZipFile zipFile = new ZipFile(s, StandardCharsets.UTF_8)) {
+        Enumeration<? extends ZipEntry> entries = zipFile.entries();
+        while (entries.hasMoreElements()) {
+          ZipEntry entry = entries.nextElement();
+          String name = entry.getName();
+          if (!ZipUtils.isDexFile(name) && !ZipUtils.isClassFile(name)) {
+            String feature = featureClassMapping.featureForNonClass(name);
+            Path outputDir = Paths.get(options.getOutput()).resolve(feature);
+            try (InputStream stream = zipFile.getInputStream(entry)) {
+              Path outputFile = outputDir.resolve(name);
+              Path parent = outputFile.getParent();
+              if (parent != null) {
+                Files.createDirectories(parent);
+              }
+              Files.copy(stream, outputFile);
+            }
+          }
+        }
+      } catch (IOException e) {
+        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s)));
+        options.getDiagnosticsHandler().error(error);
+        throw new AbortException(error);
+      }
+    }
+  }
+
+  public static void main(String[] args) {
+    if (PRINT_ARGS) {
+      printArgs(args);
+    }
+    ExceptionUtils.withMainProgramHandler(
+        () -> {
+          try {
+            run(args);
+          } catch (FeatureMappingException e) {
+            // TODO(ricow): Report feature mapping errors via the reporter.
+            throw new RuntimeException("Splitting failed: " + e.getMessage());
+          }
+        });
+  }
+
+  private static void printArgs(String[] args) {
+    System.err.printf("r8.DexSplitter");
+    for (String s : args) {
+      System.err.printf(" %s", s);
+    }
+    System.err.println("");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index 9787686..2e6cc06 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -228,6 +228,7 @@
           new IRCode(
               appView.options(),
               method,
+              callerPosition,
               ListUtils.newLinkedList(block),
               valueNumberGenerator,
               blockNumberGenerator,
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
index 9856e0b..0b2c884 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.horizontalclassmerging.policies;
 
 import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
@@ -26,6 +27,7 @@
 import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
@@ -321,10 +323,6 @@
       worklist.clear();
     }
 
-    boolean markClassInitializerAsSeen(DexProgramClass clazz) {
-      return seenClassInitializers.add(clazz);
-    }
-
     boolean enqueueMethod(ProgramMethod method) {
       if (seenMethods.add(method)) {
         worklist.addLast(method);
@@ -434,44 +432,38 @@
         return appView.appInfo().isSubtype(getContext().getHolder(), clazz);
       }
 
-      private void triggerClassInitializer(DexType type) {
-        DexProgramClass clazz = type.asProgramClass(appView);
-        if (clazz != null) {
-          triggerClassInitializer(clazz);
-        }
-      }
-
-      private void triggerClassInitializer(DexProgramClass clazz) {
-        if (!markClassInitializerAsSeen(clazz)) {
-          return;
-        }
-
-        if (groupMembers.contains(clazz)) {
-          if (hasSingleTracingRoot(clazz)) {
-            // We found an execution path from the class initializer of the given class back to its
-            // own class initializer. Therefore this class is not eligible for merging.
-            fail();
-          } else {
-            // Record that this class initializer is reachable from the tracing roots.
-            recordClassInitializerReachableFromTracingRoots(clazz);
+      private void triggerClassInitializer(DexProgramClass root) {
+        WorkList<DexProgramClass> worklist = WorkList.newWorkList(seenClassInitializers);
+        worklist.addIfNotSeen(root);
+        while (worklist.hasNext()) {
+          DexProgramClass clazz = worklist.next();
+          if (groupMembers.contains(clazz)) {
+            if (hasSingleTracingRoot(clazz)) {
+              // We found an execution path from the class initializer of the given class back to
+              // its own class initializer. Therefore this class is not eligible for merging.
+              fail();
+            } else {
+              // Record that this class initializer is reachable from the tracing roots.
+              recordClassInitializerReachableFromTracingRoots(clazz);
+            }
           }
-        }
 
-        ProgramMethod classInitializer = clazz.getProgramClassInitializer();
-        if (classInitializer != null) {
-          if (!enqueueMethod(classInitializer)) {
+          ProgramMethod classInitializer = clazz.getProgramClassInitializer();
+          if (classInitializer != null && !enqueueMethod(classInitializer)) {
             // This class initializer is already seen in the current context, thus all of the parent
             // class initializers are also seen in the current context.
             return;
           }
-        }
 
-        triggerClassInitializer(clazz.getSuperType());
+          DexProgramClass superClass =
+              asProgramClassOrNull(appView.definitionFor(clazz.getSuperType()));
+          if (superClass != null) {
+            worklist.addIfNotSeen(superClass);
+          }
 
-        MergeGroup other = allGroups.get(clazz);
-        if (other != null && other != group) {
-          for (DexProgramClass member : other) {
-            triggerClassInitializer(member);
+          MergeGroup other = allGroups.get(clazz);
+          if (other != null && other != group) {
+            worklist.addIfNotSeen(other);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index bf8f7ad..4656ad8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -117,6 +117,7 @@
   private final ProgramMethod method;
   private final MutableMethodConversionOptions conversionOptions;
 
+  public final Position entryPosition;
   public LinkedList<BasicBlock> blocks;
   public final NumberGenerator valueNumberGenerator;
   public final NumberGenerator basicBlockNumberGenerator;
@@ -133,6 +134,7 @@
   public IRCode(
       InternalOptions options,
       ProgramMethod method,
+      Position entryPosition,
       LinkedList<BasicBlock> blocks,
       NumberGenerator valueNumberGenerator,
       NumberGenerator basicBlockNumberGenerator,
@@ -142,9 +144,11 @@
     assert metadata != null;
     assert options != null;
     assert blocks.size() == basicBlockNumberGenerator.peek();
+    assert entryPosition != null;
     this.options = options;
     this.conversionOptions = conversionOptions;
     this.method = method;
+    this.entryPosition = entryPosition;
     this.blocks = blocks;
     this.valueNumberGenerator = valueNumberGenerator;
     this.basicBlockNumberGenerator = basicBlockNumberGenerator;
@@ -169,6 +173,10 @@
     return blocks.getFirst();
   }
 
+  public Position getEntryPosition() {
+    return entryPosition;
+  }
+
   public MethodConversionOptions getConversionOptions() {
     return conversionOptions;
   }
@@ -516,7 +524,9 @@
   }
 
   public void removeBlocks(Collection<BasicBlock> blocksToRemove) {
-    blocks.removeAll(blocksToRemove);
+    if (!blocksToRemove.isEmpty()) {
+      blocks.removeAll(blocksToRemove);
+    }
   }
 
   /**
@@ -1497,34 +1507,4 @@
       }
     }
   }
-
-  public Position findFirstNonNonePosition() {
-    return findFirstNonNonePosition(Position.none());
-  }
-
-  public Position findFirstNonNonePosition(Position orElse) {
-    BasicBlock current = entryBlock();
-    Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
-    do {
-      boolean changed = visitedBlocks.add(current);
-      assert changed;
-
-      for (Instruction instruction : current.getInstructions()) {
-        if (instruction.isArgument() || instruction.isGoto()) {
-          continue;
-        }
-        if (instruction.getPosition().isSome()) {
-          return instruction.getPosition();
-        }
-      }
-
-      // The very first non-argument instruction can be chained via goto.
-      if (current.exit().isGoto()) {
-        current = current.exit().asGoto().getTarget();
-      } else {
-        break;
-      }
-    } while (!visitedBlocks.contains(current));
-    return orElse;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumericType.java b/src/main/java/com/android/tools/r8/ir/code/NumericType.java
index 99456dc..85b80b5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumericType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumericType.java
@@ -16,7 +16,7 @@
   FLOAT,
   DOUBLE;
 
-  public DexType dexTypeFor(DexItemFactory factory) {
+  public DexType toDexType(DexItemFactory factory) {
     switch (this) {
       case BYTE:
         return factory.byteType;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
index 0ae9459..4677bd4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -38,6 +39,10 @@
     return this == OBJECT;
   }
 
+  public boolean isPrimitive() {
+    return !isObject();
+  }
+
   public boolean isSingle() {
     return this == INT || this == FLOAT;
   }
@@ -138,6 +143,23 @@
     throw new Unreachable("Unexpected conversion of imprecise type: " + type);
   }
 
+  public DexType toDexType(DexItemFactory dexItemFactory) {
+    switch (this) {
+      case OBJECT:
+        return dexItemFactory.objectType;
+      case INT:
+        return dexItemFactory.intType;
+      case FLOAT:
+        return dexItemFactory.floatType;
+      case LONG:
+        return dexItemFactory.longType;
+      case DOUBLE:
+        return dexItemFactory.doubleType;
+      default:
+        throw new Unreachable();
+    }
+  }
+
   public PrimitiveTypeElement toPrimitiveType() {
     switch (this) {
       case INT:
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 1a39011..5270193 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -412,6 +412,11 @@
     if (needsGeneratedMethodSynchronization) {
       buildMethodEnterSynchronization(builder);
     }
+    Position entryPosition = getCanonicalDebugPositionAtOffset(0);
+    if (!state.getPosition().equals(entryPosition)) {
+      state.setPosition(entryPosition);
+      builder.addDebugPosition(state.getPosition());
+    }
     recordStateForTarget(0, state.getSnapshot());
     inPrelude = false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 2bd4cfa..de631d3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -54,7 +54,6 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.IntSwitch;
-import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.Position;
@@ -72,7 +71,7 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -351,6 +350,50 @@
         && currentBlock.getPredecessors().get(0) == previousBlock;
   }
 
+  private static void removeTrivialFallthroughBlocks(IRCode code) {
+    for (int blockIndex = 1; blockIndex < code.blocks.size() - 1; blockIndex++) {
+      // We skip the last block as it has no fallthrough. We also skip checking the entry block as
+      // it has no predecessors and must define the initial position. Any subsequent block must be
+      // statically reachable and thus have predecessors.
+      BasicBlock currentBlock = code.blocks.get(blockIndex);
+      assert !currentBlock.getPredecessors().isEmpty();
+      if (currentBlock.size() != 2) {
+        continue;
+      }
+      DebugPosition debugPosition = currentBlock.entry().asDebugPosition();
+      com.android.tools.r8.ir.code.Goto exit = currentBlock.exit().asGoto();
+      if (debugPosition == null || exit == null || debugPosition.getPosition().isNone()) {
+        continue;
+      }
+      BasicBlock nextBlock = code.blocks.get(blockIndex + 1);
+      if (exit.getTarget() != nextBlock) {
+        continue;
+      }
+      // The block is a trivial position block that falls through to the following.
+      // If the position is equal to each predecessor block then the line is already active on
+      // each entry to the fallthrough and it can be safely removed.
+      boolean allMatch = true;
+      Position position = debugPosition.getPosition();
+      for (BasicBlock pred : currentBlock.getPredecessors()) {
+        // Do to the target == next check this cannot be a trivial loop.
+        assert pred != currentBlock;
+        Position predExit = pred.exit().getPosition();
+        if (!position.equals(predExit)) {
+          allMatch = false;
+          break;
+        }
+      }
+      if (allMatch) {
+        currentBlock.removeInstruction(debugPosition);
+        CodeRewriter.unlinkTrivialGotoBlock(currentBlock, nextBlock);
+        code.removeBlocks(Collections.singleton(currentBlock));
+        // Having removed the block at blockIndex, the previous block may now be a trivial
+        // fallthrough. Rewind to that point and retry. This avoids iterating to a fixed point.
+        blockIndex = Math.max(0, blockIndex - 2);
+      }
+    }
+  }
+
   // Eliminates unneeded debug positions.
   //
   // After this pass all remaining debug positions mark places where we must ensure a materializing
@@ -359,6 +402,12 @@
     if (!code.metadata().mayHaveDebugPosition()) {
       return;
     }
+
+    // We must start by removing any blocks that are already trivial fallthrough blocks with no
+    // position change. With these removed it is then sound to make the fallthrough judgement when
+    // determining if a goto will materialize or not.
+    removeTrivialFallthroughBlocks(code);
+
     // Current position known to have a materializing instruction associated with it.
     Position currentMaterializedPosition = Position.none();
 
@@ -373,7 +422,6 @@
     // Compute the set of all positions that can be removed.
     // (Delaying removal to avoid ConcurrentModificationException).
     List<DebugPosition> toRemove = new ArrayList<>();
-    Set<BasicBlock> trivialBlocks = new HashSet<>();
 
     for (int blockIndex = 0; blockIndex < code.blocks.size(); blockIndex++) {
       BasicBlock currentBlock = code.blocks.get(blockIndex);
@@ -425,12 +473,11 @@
               && currentMaterializedPosition == instruction.getPosition()) {
             // Here we don't need to check locals state as the line is already active.
             toRemove.add(instruction.asDebugPosition());
-            if (currentBlock.getInstructions().size() == 2) {
-              JumpInstruction exit = currentBlock.exit();
-              if (exit.isGoto() && exit.getPosition() == currentMaterializedPosition) {
-                trivialBlocks.add(currentBlock);
-              }
-            }
+            assert currentBlock.size() != 2
+                    || currentBlock.exit().getPosition() != currentMaterializedPosition
+                    || !currentBlock.exit().isGoto()
+                    || currentBlock.exit().asGoto().getTarget() != nextBlock
+                : "Unexpected trivial fallthrough block. This should be removed already.";
           } else if (unresolvedPosition != null
               && unresolvedPosition.getPosition() == instruction.getPosition()
               && locals.equals(localsAtUnresolvedPosition)) {
@@ -474,29 +521,6 @@
       }
       assert i == toRemove.size();
     }
-
-    // Remove all trivial goto blocks that have a position known to be emitted in their predecessor.
-    if (!trivialBlocks.isEmpty()) {
-      List<BasicBlock> blocksToRemove = new ArrayList<>();
-      ListIterator<BasicBlock> iterator = code.listIterator();
-      // Skip the entry block.
-      assert code.blocks.size() > 1;
-      iterator.next();
-      BasicBlock nextBlock = iterator.next();
-      do {
-        BasicBlock block = nextBlock;
-        nextBlock = iterator.hasNext() ? iterator.next() : null;
-        if (block.isTrivialGoto()
-            && trivialBlocks.contains(block)
-            && block.exit().asGoto().getTarget() != block
-            && !CodeRewriter.isFallthroughBlock(block)) {
-          BasicBlock target = block.exit().asGoto().getTarget();
-          blocksToRemove.add(block);
-          CodeRewriter.unlinkTrivialGotoBlock(block, target);
-        }
-      } while (iterator.hasNext());
-      code.removeBlocks(blocksToRemove);
-    }
   }
 
   // Rewrite ifs with offsets that are too large for the if encoding. The rewriting transforms:
@@ -522,8 +546,10 @@
         BasicBlock trueTarget = theIf.getTrueTarget();
         BasicBlock newBlock =
             BasicBlock.createGotoBlock(
-                ir.blocks.size(), theIf.getPosition(), ir.metadata(), trueTarget);
+                ir.getNextBlockNumber(), theIf.getPosition(), ir.metadata(), trueTarget);
         theIf.setTrueTarget(newBlock);
+        newBlock.getMutablePredecessors().add(block);
+        trueTarget.replacePredecessor(block, newBlock);
         theIf.invert();
         it.add(newBlock);
       }
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 b46ebad..85ac399 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
@@ -703,6 +703,7 @@
         new IRCode(
             appView.options(),
             method,
+            source.getCanonicalDebugPositionAtOffset(0),
             blocks,
             valueNumberGenerator,
             basicBlockNumberGenerator,
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 fa7a539..0c255da 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
@@ -1109,7 +1109,7 @@
 
     if (options.canHaveInvokeInterfaceToObjectMethodBug()) {
       timing.begin("JDK-8272564 fix rewrite");
-      CodeRewriter.rewriteJdk8272564Fix(code, appView);
+      CodeRewriter.rewriteJdk8272564Fix(code, context, appView);
       timing.end();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java
index b505cf0..e8d830e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SyntheticStraightLineSourceCode.java
@@ -63,7 +63,7 @@
 
   @Override
   public Position getCanonicalDebugPositionAtOffset(int offset) {
-    return null;
+    return Position.syntheticNone();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
index a0f8961..c1e11d4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
@@ -6,12 +6,10 @@
 
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.generateTrackDesugaredAPIWarnings;
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.isAPIConversionSyntheticType;
-import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature;
 
 import com.android.tools.r8.contexts.CompilationContext.MainThreadContext;
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -21,10 +19,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APICallbackWrapperCfCodeProvider;
-import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -71,11 +66,10 @@
               trackedCallBackAPIs.add(virtualProgramMethod.getReference());
             }
             ProgramMethod callback =
-                generateCallbackMethod(
-                    virtualProgramMethod.getDefinition(),
-                    virtualProgramMethod.getHolder(),
-                    eventConsumer,
-                    mainThreadContext);
+                wrapperSynthesizor
+                    .getConversionCfProvider()
+                    .generateCallbackConversion(
+                        virtualProgramMethod, eventConsumer, mainThreadContext);
             callbacks.add(callback.getDefinition());
           }
         }
@@ -192,33 +186,6 @@
         || specification.isEmulatedInterfaceRewrittenType(dexClass.type));
   }
 
-  private ProgramMethod generateCallbackMethod(
-      DexEncodedMethod originalMethod,
-      DexProgramClass clazz,
-      DesugaredLibraryAPICallbackSynthesizorEventConsumer eventConsumer,
-      MainThreadContext context) {
-    DexMethod methodToInstall =
-        methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView);
-    CfCode cfCode =
-        new APICallbackWrapperCfCodeProvider(
-                appView,
-                originalMethod.getReference(),
-                wrapperSynthesizor,
-                clazz.isInterface(),
-                eventConsumer,
-                () -> context.createUniqueContext(clazz))
-            .generateCfCode();
-    DexEncodedMethod newMethod = wrapperSynthesizor.newSynthesizedMethod(methodToInstall, cfCode);
-    newMethod.setCode(cfCode, DexEncodedMethod.NO_PARAMETER_INFO);
-    if (originalMethod.isLibraryMethodOverride().isTrue()) {
-      newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
-    }
-    ProgramMethod callback = new ProgramMethod(clazz, newMethod);
-    assert eventConsumer != null;
-    eventConsumer.acceptAPIConversionCallback(callback);
-    return callback;
-  }
-
   private void generateTrackingWarnings() {
     generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ", appView);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
index 55abcef..b4ece95 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
@@ -4,20 +4,10 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion;
 
-import com.android.tools.r8.cf.code.CfArrayLoad;
-import com.android.tools.r8.cf.code.CfArrayStore;
-import com.android.tools.r8.cf.code.CfCheckCast;
-import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfLoad;
-import com.android.tools.r8.cf.code.CfNewArray;
-import com.android.tools.r8.cf.code.CfReturn;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -25,22 +15,16 @@
 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.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.MemberType;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConversionCfCodeProvider;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
@@ -239,94 +223,6 @@
     return vivifiedType;
   }
 
-  private static DexType invalidType(
-      DexMethod invokedMethod,
-      DexMethod returnConversion,
-      DexMethod[] parameterConversions,
-      AppView<?> appView) {
-    DexMethod convertedMethod =
-        methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView);
-    if (invokedMethod.getReturnType() != convertedMethod.getReturnType()
-        && returnConversion == null) {
-      return invokedMethod.getReturnType();
-    }
-    for (int i = 0; i < invokedMethod.getArity(); i++) {
-      if (invokedMethod.getParameter(i) != convertedMethod.getParameter(i)
-          && parameterConversions[i] == null) {
-        return invokedMethod.getParameter(i);
-      }
-    }
-    return null;
-  }
-
-  public static DexMethod getConvertedAPI(
-      DexMethod invokedMethod,
-      DexMethod returnConversion,
-      DexMethod[] parameterConversions,
-      AppView<?> appView) {
-    DexType newReturnType =
-        returnConversion != null ? returnConversion.getParameter(0) : invokedMethod.getReturnType();
-    DexType[] newParameterTypes = new DexType[parameterConversions.length];
-    for (int i = 0; i < parameterConversions.length; i++) {
-      newParameterTypes[i] =
-          parameterConversions[i] != null
-              ? parameterConversions[i].getReturnType()
-              : invokedMethod.getParameter(i);
-    }
-    DexMethod convertedAPI =
-        appView
-            .dexItemFactory()
-            .createMethod(
-                invokedMethod.holder,
-                appView.dexItemFactory().createProto(newReturnType, newParameterTypes),
-                invokedMethod.name);
-    assert convertedAPI
-            == methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView)
-        || invalidType(invokedMethod, returnConversion, parameterConversions, appView) != null;
-    return convertedAPI;
-  }
-
-  private DexMethod computeReturnConversion(
-      DexMethod invokedMethod,
-      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
-      ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
-    DexType returnType = invokedMethod.proto.returnType;
-    if (wrapperSynthesizor.shouldConvert(returnType, invokedMethod, context)) {
-      DexType newReturnType = DesugaredLibraryAPIConverter.vivifiedTypeFor(returnType, appView);
-      return wrapperSynthesizor.ensureConversionMethod(
-          returnType,
-          newReturnType,
-          returnType,
-          eventConsumer,
-          methodProcessingContext::createUniqueContext);
-    }
-    return null;
-  }
-
-  private DexMethod[] computeParameterConversions(
-      DexMethod invokedMethod,
-      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
-      ProgramMethod context,
-      MethodProcessingContext methodProcessingContext) {
-    DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
-    DexType[] parameters = invokedMethod.proto.parameters.values;
-    for (int i = 0; i < parameters.length; i++) {
-      DexType argType = parameters[i];
-      if (wrapperSynthesizor.shouldConvert(argType, invokedMethod, context)) {
-        DexType argVivifiedType = vivifiedTypeFor(argType, appView);
-        parameterConversions[i] =
-            wrapperSynthesizor.ensureConversionMethod(
-                argType,
-                argType,
-                argVivifiedType,
-                eventConsumer,
-                methodProcessingContext::createUniqueContext);
-      }
-    }
-    return parameterConversions;
-  }
-
   private Collection<CfInstruction> rewriteLibraryInvoke(
       CfInvoke invoke,
       MethodProcessingContext methodProcessingContext,
@@ -339,12 +235,18 @@
     }
     if (shouldOutlineAPIConversion(invoke, context)) {
       DexMethod outlinedAPIConversion =
-          createOutlinedAPIConversion(invoke, methodProcessingContext, eventConsumer, context);
+          wrapperSynthesizor
+              .getConversionCfProvider()
+              .generateOutlinedAPIConversion(
+                  invoke, eventConsumer, context, methodProcessingContext)
+              .getReference();
       return Collections.singletonList(
           new CfInvoke(Opcodes.INVOKESTATIC, outlinedAPIConversion, false));
     }
-    return rewriteLibraryInvokeToInlineAPIConversion(
-        invoke, methodProcessingContext, localStackAllocator, eventConsumer, context);
+    return wrapperSynthesizor
+        .getConversionCfProvider()
+        .generateInlinedAPIConversion(
+            invoke, methodProcessingContext, localStackAllocator, eventConsumer, context);
   }
 
   // If the option is set, we try to outline API conversions as much as possible to reduce the
@@ -363,195 +265,6 @@
     return methodForDesugaring.getAccessFlags().isPublic();
   }
 
-  private Collection<CfInstruction> rewriteLibraryInvokeToInlineAPIConversion(
-      CfInvoke invoke,
-      MethodProcessingContext methodProcessingContext,
-      LocalStackAllocator localStackAllocator,
-      CfInstructionDesugaringEventConsumer eventConsumer,
-      ProgramMethod context) {
 
-    DexMethod invokedMethod = invoke.getMethod();
-    DexMethod returnConversion =
-        computeReturnConversion(invokedMethod, eventConsumer, context, methodProcessingContext);
-    DexMethod[] parameterConversions =
-        computeParameterConversions(invokedMethod, eventConsumer, context, methodProcessingContext);
 
-    // If only the last 2 parameters require conversion, we do everything inlined.
-    // If other parameters require conversion, we outline the parameter conversion but keep the API
-    // call inlined.
-    // The returned value is always converted inlined.
-    boolean requireOutlinedParameterConversion = false;
-    for (int i = 0; i < parameterConversions.length - 2; i++) {
-      requireOutlinedParameterConversion |= parameterConversions[i] != null;
-    }
-
-    ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
-    if (requireOutlinedParameterConversion) {
-      addOutlineParameterConversionInstructions(
-          parameterConversions,
-          cfInstructions,
-          methodProcessingContext,
-          invokedMethod,
-          localStackAllocator,
-          eventConsumer);
-    } else {
-      addInlineParameterConversionInstructions(parameterConversions, cfInstructions);
-    }
-
-    DexMethod convertedMethod =
-        getConvertedAPI(invokedMethod, returnConversion, parameterConversions, appView);
-    cfInstructions.add(new CfInvoke(invoke.getOpcode(), convertedMethod, invoke.isInterface()));
-
-    if (returnConversion != null) {
-      cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
-    }
-
-    return cfInstructions;
-  }
-
-  // The parameters are converted and returned in an array of converted parameters. The parameter
-  // array then needs to be unwrapped at the call site.
-  private void addOutlineParameterConversionInstructions(
-      DexMethod[] parameterConversions,
-      ArrayList<CfInstruction> cfInstructions,
-      MethodProcessingContext methodProcessingContext,
-      DexMethod invokedMethod,
-      LocalStackAllocator localStackAllocator,
-      CfInstructionDesugaringEventConsumer eventConsumer) {
-    localStackAllocator.allocateLocalStack(4);
-    DexProto newProto =
-        appView
-            .dexItemFactory()
-            .createProto(
-                appView.dexItemFactory().objectArrayType, invokedMethod.getParameters().values);
-    ProgramMethod parameterConversion =
-        appView
-            .getSyntheticItems()
-            .createMethod(
-                kinds -> kinds.API_CONVERSION_PARAMETERS,
-                methodProcessingContext.createUniqueContext(),
-                appView,
-                builder ->
-                    builder
-                        .setProto(newProto)
-                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                        // Will be traced by the enqueuer.
-                        .disableAndroidApiLevelCheck()
-                        .setCode(
-                            methodSignature ->
-                                computeParameterConversionCfCode(
-                                    methodSignature.holder, invokedMethod, parameterConversions)));
-    eventConsumer.acceptAPIConversion(parameterConversion);
-    cfInstructions.add(
-        new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion.getReference(), false));
-    for (int i = 0; i < parameterConversions.length; i++) {
-      cfInstructions.add(new CfStackInstruction(Opcode.Dup));
-      cfInstructions.add(new CfConstNumber(i, ValueType.INT));
-      DexType parameterType =
-          parameterConversions[i] != null
-              ? parameterConversions[i].getReturnType()
-              : invokedMethod.getParameter(i);
-      cfInstructions.add(new CfArrayLoad(MemberType.OBJECT));
-      if (parameterType.isPrimitiveType()) {
-        cfInstructions.add(new CfCheckCast(factory.getBoxedForPrimitiveType(parameterType)));
-        DexMethod method = appView.dexItemFactory().getUnboxPrimitiveMethod(parameterType);
-        cfInstructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
-      } else {
-        cfInstructions.add(new CfCheckCast(parameterType));
-      }
-      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
-    }
-    cfInstructions.add(new CfStackInstruction(Opcode.Pop));
-  }
-
-  private CfCode computeParameterConversionCfCode(
-      DexType holder, DexMethod invokedMethod, DexMethod[] parameterConversions) {
-    ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
-    cfInstructions.add(new CfConstNumber(parameterConversions.length, ValueType.INT));
-    cfInstructions.add(new CfNewArray(factory.objectArrayType));
-    int stackIndex = 0;
-    for (int i = 0; i < invokedMethod.getArity(); i++) {
-      cfInstructions.add(new CfStackInstruction(Opcode.Dup));
-      cfInstructions.add(new CfConstNumber(i, ValueType.INT));
-      DexType param = invokedMethod.getParameter(i);
-      cfInstructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-      if (parameterConversions[i] != null) {
-        cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
-      }
-      if (param.isPrimitiveType()) {
-        DexMethod method = appView.dexItemFactory().getBoxPrimitiveMethod(param);
-        cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
-      }
-      cfInstructions.add(new CfArrayStore(MemberType.OBJECT));
-      if (param == appView.dexItemFactory().longType
-          || param == appView.dexItemFactory().doubleType) {
-        stackIndex++;
-      }
-      stackIndex++;
-    }
-    cfInstructions.add(new CfReturn(ValueType.OBJECT));
-    return new CfCode(
-        holder,
-        invokedMethod.getParameters().size() + 4,
-        invokedMethod.getParameters().size(),
-        cfInstructions);
-  }
-
-  private void addInlineParameterConversionInstructions(
-      DexMethod[] parameterConversions, ArrayList<CfInstruction> cfInstructions) {
-    if (parameterConversions.length > 0
-        && parameterConversions[parameterConversions.length - 1] != null) {
-      cfInstructions.add(
-          new CfInvoke(
-              Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 1], false));
-    }
-    if (parameterConversions.length > 1
-        && parameterConversions[parameterConversions.length - 2] != null) {
-      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
-      cfInstructions.add(
-          new CfInvoke(
-              Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 2], false));
-      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
-    }
-  }
-
-  private DexMethod createOutlinedAPIConversion(
-      CfInvoke invoke,
-      MethodProcessingContext methodProcessingContext,
-      CfInstructionDesugaringEventConsumer eventConsumer,
-      ProgramMethod context) {
-    DexMethod invokedMethod = invoke.getMethod();
-    DexProto newProto =
-        invoke.isInvokeStatic()
-            ? invokedMethod.proto
-            : factory.prependTypeToProto(invokedMethod.getHolderType(), invokedMethod.getProto());
-    DexMethod returnConversion =
-        computeReturnConversion(invokedMethod, eventConsumer, context, methodProcessingContext);
-    DexMethod[] parameterConversions =
-        computeParameterConversions(invokedMethod, eventConsumer, context, methodProcessingContext);
-    ProgramMethod outline =
-        appView
-            .getSyntheticItems()
-            .createMethod(
-                kinds -> kinds.API_CONVERSION,
-                methodProcessingContext.createUniqueContext(),
-                appView,
-                builder ->
-                    builder
-                        .setProto(newProto)
-                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                        // Will be traced by the enqueuer.
-                        .disableAndroidApiLevelCheck()
-                        .setCode(
-                            methodSignature ->
-                                new APIConversionCfCodeProvider(
-                                        appView,
-                                        methodSignature.holder,
-                                        invoke,
-                                        returnConversion,
-                                        parameterConversions)
-                                    .generateCfCode()));
-    eventConsumer.acceptAPIConversion(outline);
-    return outline.getReference();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
new file mode 100644
index 0000000..c458830
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryConversionCfProvider.java
@@ -0,0 +1,551 @@
+// Copyright (c) 2022, 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.desugar.desugaredlibrary.apiconversion;
+
+import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature;
+
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.contexts.CompilationContext.MainThreadContext;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
+import com.android.tools.r8.ir.synthetic.apiconverter.APIConversionCfCodeProvider;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import org.objectweb.asm.Opcodes;
+
+public class DesugaredLibraryConversionCfProvider {
+
+  private final AppView<?> appView;
+  private final DexItemFactory factory;
+  private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizer;
+
+  public DesugaredLibraryConversionCfProvider(
+      AppView<?> appView, DesugaredLibraryWrapperSynthesizer wrapperSynthesizer) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+    this.wrapperSynthesizer = wrapperSynthesizer;
+  }
+
+  public DexEncodedMethod generateWrapperConversionWithoutCode(
+      DexMethod method, DexField wrapperField) {
+    DexMethod methodToInstall =
+        methodWithVivifiedTypeInSignature(method, wrapperField.getHolderType(), appView);
+    return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, null);
+  }
+
+  public DexEncodedMethod generateWrapperConversion(
+      DexMethod method,
+      DexField wrapperField,
+      DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
+      Supplier<UniqueContext> contextSupplier) {
+    DexClass holderClass = appView.definitionFor(method.getHolderType());
+    assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
+    boolean isInterface = holderClass == null || holderClass.isInterface();
+    ProgramMethod context = resolveContext(method, isInterface);
+    DexMethod returnConversion =
+        computeReturnConversion(method, true, eventConsumer, context, contextSupplier);
+    DexMethod[] parameterConversions =
+        computeParameterConversions(method, false, eventConsumer, context, contextSupplier);
+    DexMethod methodToInstall =
+        convertedMethod(
+            method, false, returnConversion, parameterConversions, wrapperField.getHolderType());
+    CfCode cfCode =
+        new APIConversionCfCodeProvider(
+                appView,
+                wrapperField.getHolderType(),
+                method,
+                isInterface,
+                returnConversion,
+                parameterConversions,
+                wrapperField)
+            .generateCfCode();
+    return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode);
+  }
+
+  public DexEncodedMethod generateVivifiedWrapperConversionWithoutCode(
+      DexMethod method, DexField wrapperField) {
+    DexMethod methodToInstall =
+        factory.createMethod(wrapperField.getHolderType(), method.proto, method.name);
+    return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, null);
+  }
+
+  public DexEncodedMethod generateVivifiedWrapperConversion(
+      DexMethod method,
+      DexField wrapperField,
+      DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
+      Supplier<UniqueContext> contextSupplier) {
+    DexMethod methodToInstall =
+        factory.createMethod(wrapperField.getHolderType(), method.proto, method.name);
+    DexClass holderClass = appView.definitionFor(method.getHolderType());
+    boolean isInterface;
+    if (holderClass == null) {
+      assert appView
+          .options()
+          .machineDesugaredLibrarySpecification
+          .isEmulatedInterfaceRewrittenType(method.getHolderType());
+      isInterface = true;
+    } else {
+      isInterface = holderClass.isInterface();
+    }
+    ProgramMethod context = resolveContext(method, isInterface);
+    DexMethod returnConversion =
+        computeReturnConversion(method, false, eventConsumer, context, contextSupplier);
+    DexMethod[] parameterConversions =
+        computeParameterConversions(method, true, eventConsumer, context, contextSupplier);
+    DexMethod forwardMethod =
+        convertedMethod(
+            method, true, returnConversion, parameterConversions, wrapperField.getType());
+    CfCode cfCode =
+        new APIConversionCfCodeProvider(
+                appView,
+                wrapperField.getHolderType(),
+                forwardMethod,
+                isInterface,
+                returnConversion,
+                parameterConversions,
+                wrapperField)
+            .generateCfCode();
+    return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode);
+  }
+
+  public ProgramMethod generateCallbackConversion(
+      ProgramMethod method,
+      DesugaredLibraryAPICallbackSynthesizorEventConsumer eventConsumer,
+      MainThreadContext context) {
+    DexProgramClass clazz = method.getHolder();
+    DexMethod returnConversion =
+        computeReturnConversion(
+            method.getReference(),
+            true,
+            eventConsumer,
+            method,
+            () -> context.createUniqueContext(clazz));
+    DexMethod[] parameterConversions =
+        computeParameterConversions(
+            method.getReference(),
+            false,
+            eventConsumer,
+            method,
+            () -> context.createUniqueContext(clazz));
+    DexMethod methodToInstall =
+        convertedMethod(method.getReference(), false, returnConversion, parameterConversions);
+    CfCode cfCode =
+        new APIConversionCfCodeProvider(
+                appView,
+                method.getHolderType(),
+                method.getReference(),
+                clazz.isInterface(),
+                returnConversion,
+                parameterConversions)
+            .generateCfCode();
+    DexEncodedMethod newMethod = wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode);
+    newMethod.setCode(cfCode, DexEncodedMethod.NO_PARAMETER_INFO);
+    if (method.getDefinition().isLibraryMethodOverride().isTrue()) {
+      newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+    }
+    ProgramMethod callback = new ProgramMethod(clazz, newMethod);
+    assert eventConsumer != null;
+    eventConsumer.acceptAPIConversionCallback(callback);
+    return callback;
+  }
+
+  public ProgramMethod generateOutlinedAPIConversion(
+      CfInvoke invoke,
+      DesugaredLibraryAPIConverterEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    DexMethod method = invoke.getMethod();
+    DexProto newProto =
+        invoke.isInvokeStatic()
+            ? method.proto
+            : factory.prependTypeToProto(method.getHolderType(), method.getProto());
+    DexMethod returnConversion =
+        computeReturnConversion(
+            method, false, eventConsumer, context, methodProcessingContext::createUniqueContext);
+    DexMethod[] parameterConversions =
+        computeParameterConversions(
+            method, true, eventConsumer, context, methodProcessingContext::createUniqueContext);
+    ProgramMethod outline =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.API_CONVERSION,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .setProto(newProto)
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        // Will be traced by the enqueuer.
+                        .disableAndroidApiLevelCheck()
+                        .setCode(
+                            methodSignature ->
+                                new APIConversionCfCodeProvider(
+                                        appView,
+                                        methodSignature.holder,
+                                        convertedMethod(
+                                            method, true, returnConversion, parameterConversions),
+                                        invoke.isInterface(),
+                                        returnConversion,
+                                        parameterConversions,
+                                        invoke.getOpcode())
+                                    .generateCfCode()));
+    eventConsumer.acceptAPIConversion(outline);
+    return outline;
+  }
+
+  public Collection<CfInstruction> generateInlinedAPIConversion(
+      CfInvoke invoke,
+      MethodProcessingContext methodProcessingContext,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context) {
+
+    DexMethod invokedMethod = invoke.getMethod();
+    DexMethod returnConversion =
+        computeReturnConversion(
+            invokedMethod,
+            false,
+            eventConsumer,
+            context,
+            methodProcessingContext::createUniqueContext);
+    DexMethod[] parameterConversions =
+        computeParameterConversions(
+            invokedMethod,
+            true,
+            eventConsumer,
+            context,
+            methodProcessingContext::createUniqueContext);
+
+    // If only the last 2 parameters require conversion, we do everything inlined.
+    // If other parameters require conversion, we outline the parameter conversion but keep the API
+    // call inlined.
+    // The returned value is always converted inlined.
+    boolean requireOutlinedParameterConversion = false;
+    for (int i = 0; i < parameterConversions.length - 2; i++) {
+      requireOutlinedParameterConversion |= parameterConversions[i] != null;
+    }
+
+    ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
+    if (requireOutlinedParameterConversion) {
+      addOutlineParameterConversionInstructions(
+          parameterConversions,
+          cfInstructions,
+          methodProcessingContext,
+          invokedMethod,
+          localStackAllocator,
+          eventConsumer);
+    } else {
+      addInlineParameterConversionInstructions(parameterConversions, cfInstructions);
+    }
+
+    DexMethod convertedMethod =
+        convertedMethod(invokedMethod, true, returnConversion, parameterConversions);
+    cfInstructions.add(new CfInvoke(invoke.getOpcode(), convertedMethod, invoke.isInterface()));
+
+    if (returnConversion != null) {
+      cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
+    }
+
+    return cfInstructions;
+  }
+
+  // The parameters are converted and returned in an array of converted parameters. The parameter
+  // array then needs to be unwrapped at the call site.
+  private void addOutlineParameterConversionInstructions(
+      DexMethod[] parameterConversions,
+      ArrayList<CfInstruction> cfInstructions,
+      MethodProcessingContext methodProcessingContext,
+      DexMethod invokedMethod,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
+    localStackAllocator.allocateLocalStack(4);
+    DexProto newProto =
+        appView
+            .dexItemFactory()
+            .createProto(
+                appView.dexItemFactory().objectArrayType, invokedMethod.getParameters().values);
+    ProgramMethod parameterConversion =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                kinds -> kinds.API_CONVERSION_PARAMETERS,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    builder
+                        .setProto(newProto)
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        // Will be traced by the enqueuer.
+                        .disableAndroidApiLevelCheck()
+                        .setCode(
+                            methodSignature ->
+                                computeParameterConversionCfCode(
+                                    methodSignature.holder, invokedMethod, parameterConversions)));
+    eventConsumer.acceptAPIConversion(parameterConversion);
+    cfInstructions.add(
+        new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion.getReference(), false));
+    for (int i = 0; i < parameterConversions.length; i++) {
+      cfInstructions.add(new CfStackInstruction(Opcode.Dup));
+      cfInstructions.add(new CfConstNumber(i, ValueType.INT));
+      DexType parameterType =
+          parameterConversions[i] != null
+              ? parameterConversions[i].getReturnType()
+              : invokedMethod.getParameter(i);
+      cfInstructions.add(new CfArrayLoad(MemberType.OBJECT));
+      if (parameterType.isPrimitiveType()) {
+        cfInstructions.add(new CfCheckCast(factory.getBoxedForPrimitiveType(parameterType)));
+        DexMethod method = appView.dexItemFactory().getUnboxPrimitiveMethod(parameterType);
+        cfInstructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
+      } else {
+        cfInstructions.add(new CfCheckCast(parameterType));
+      }
+      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
+    }
+    cfInstructions.add(new CfStackInstruction(Opcode.Pop));
+  }
+
+  private CfCode computeParameterConversionCfCode(
+      DexType holder, DexMethod invokedMethod, DexMethod[] parameterConversions) {
+    ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
+    cfInstructions.add(new CfConstNumber(parameterConversions.length, ValueType.INT));
+    cfInstructions.add(new CfNewArray(factory.objectArrayType));
+    int stackIndex = 0;
+    for (int i = 0; i < invokedMethod.getArity(); i++) {
+      cfInstructions.add(new CfStackInstruction(Opcode.Dup));
+      cfInstructions.add(new CfConstNumber(i, ValueType.INT));
+      DexType param = invokedMethod.getParameter(i);
+      cfInstructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
+      if (parameterConversions[i] != null) {
+        cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
+      }
+      if (param.isPrimitiveType()) {
+        DexMethod method = appView.dexItemFactory().getBoxPrimitiveMethod(param);
+        cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
+      }
+      cfInstructions.add(new CfArrayStore(MemberType.OBJECT));
+      if (param == appView.dexItemFactory().longType
+          || param == appView.dexItemFactory().doubleType) {
+        stackIndex++;
+      }
+      stackIndex++;
+    }
+    cfInstructions.add(new CfReturn(ValueType.OBJECT));
+    return new CfCode(
+        holder,
+        invokedMethod.getParameters().size() + 4,
+        invokedMethod.getParameters().size(),
+        cfInstructions);
+  }
+
+  private void addInlineParameterConversionInstructions(
+      DexMethod[] parameterConversions, ArrayList<CfInstruction> cfInstructions) {
+    if (parameterConversions.length > 0
+        && parameterConversions[parameterConversions.length - 1] != null) {
+      cfInstructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 1], false));
+    }
+    if (parameterConversions.length > 1
+        && parameterConversions[parameterConversions.length - 2] != null) {
+      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
+      cfInstructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 2], false));
+      cfInstructions.add(new CfStackInstruction(Opcode.Swap));
+    }
+  }
+
+  private ProgramMethod resolveContext(DexMethod method, boolean isInterface) {
+    // The context is provided to improve the error message with origin and position. If the method
+    // is missing from the input, the context is null but the code runs correctly.
+    return appView
+        .appInfoForDesugaring()
+        .resolveMethodLegacy(method, isInterface)
+        .getResolvedProgramMethod();
+  }
+
+  private DexMethod computeReturnConversion(
+      DexMethod invokedMethod,
+      boolean destIsVivified,
+      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
+      ProgramMethod context,
+      Supplier<UniqueContext> contextSupplier) {
+    return internalComputeReturnConversion(
+        invokedMethod,
+        returnType ->
+            wrapperSynthesizer.ensureConversionMethod(
+                returnType, destIsVivified, eventConsumer, contextSupplier),
+        context);
+  }
+
+  private DexMethod computeReturnConversion(
+      DexMethod invokedMethod,
+      boolean destIsVivified,
+      DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
+      ProgramMethod context,
+      Supplier<UniqueContext> contextSupplier) {
+    return internalComputeReturnConversion(
+        invokedMethod,
+        returnType ->
+            wrapperSynthesizer.getExistingProgramConversionMethod(
+                returnType, destIsVivified, eventConsumer, contextSupplier),
+        context);
+  }
+
+  private DexMethod internalComputeReturnConversion(
+      DexMethod invokedMethod, Function<DexType, DexMethod> methodSupplier, ProgramMethod context) {
+    DexType returnType = invokedMethod.proto.returnType;
+    if (wrapperSynthesizer.shouldConvert(returnType, invokedMethod, context)) {
+      return methodSupplier.apply(returnType);
+    }
+    return null;
+  }
+
+  private DexMethod[] computeParameterConversions(
+      DexMethod invokedMethod,
+      boolean destIsVivified,
+      DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
+      ProgramMethod context,
+      Supplier<UniqueContext> contextSupplier) {
+    return internalComputeParameterConversions(
+        invokedMethod,
+        wrapperSynthesizer,
+        argType ->
+            wrapperSynthesizer.ensureConversionMethod(
+                argType, destIsVivified, eventConsumer, contextSupplier),
+        context);
+  }
+
+  private DexMethod[] computeParameterConversions(
+      DexMethod invokedMethod,
+      boolean destIsVivified,
+      DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
+      ProgramMethod context,
+      Supplier<UniqueContext> contextSupplier) {
+    return internalComputeParameterConversions(
+        invokedMethod,
+        wrapperSynthesizer,
+        argType ->
+            wrapperSynthesizer.getExistingProgramConversionMethod(
+                argType, destIsVivified, eventConsumer, contextSupplier),
+        context);
+  }
+
+  private DexMethod[] internalComputeParameterConversions(
+      DexMethod invokedMethod,
+      DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
+      Function<DexType, DexMethod> methodSupplier,
+      ProgramMethod context) {
+    DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
+    DexType[] parameters = invokedMethod.proto.parameters.values;
+    for (int i = 0; i < parameters.length; i++) {
+      DexType argType = parameters[i];
+      if (wrapperSynthesizor.shouldConvert(argType, invokedMethod, context)) {
+        parameterConversions[i] = methodSupplier.apply(argType);
+      }
+    }
+    return parameterConversions;
+  }
+
+  private DexMethod convertedMethod(
+      DexMethod method,
+      boolean parameterDestIsVivified,
+      DexMethod returnConversion,
+      DexMethod[] parameterConversions) {
+    return convertedMethod(
+        method,
+        parameterDestIsVivified,
+        returnConversion,
+        parameterConversions,
+        method.getHolderType());
+  }
+
+  private DexMethod convertedMethod(
+      DexMethod method,
+      boolean parameterDestIsVivified,
+      DexMethod returnConversion,
+      DexMethod[] parameterConversions,
+      DexType newHolder) {
+    DexType newReturnType =
+        returnConversion != null
+            ? parameterDestIsVivified
+                ? returnConversion.getParameter(0)
+                : returnConversion.getReturnType()
+            : method.getReturnType();
+    DexType[] newParameterTypes = new DexType[parameterConversions.length];
+    for (int i = 0; i < parameterConversions.length; i++) {
+      newParameterTypes[i] =
+          parameterConversions[i] != null
+              ? parameterDestIsVivified
+                  ? parameterConversions[i].getReturnType()
+                  : parameterConversions[i].getParameter(0)
+              : method.getParameter(i);
+    }
+    DexMethod convertedAPI =
+        appView
+            .dexItemFactory()
+            .createMethod(
+                newHolder,
+                appView.dexItemFactory().createProto(newReturnType, newParameterTypes),
+                method.name);
+    assert convertedAPI == methodWithVivifiedTypeInSignature(method, newHolder, appView)
+        || invalidType(method, returnConversion, parameterConversions, appView) != null;
+    return convertedAPI;
+  }
+
+  private static DexType invalidType(
+      DexMethod invokedMethod,
+      DexMethod returnConversion,
+      DexMethod[] parameterConversions,
+      AppView<?> appView) {
+    DexMethod convertedMethod =
+        methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView);
+    if (invokedMethod.getReturnType() != convertedMethod.getReturnType()
+        && returnConversion == null) {
+      return invokedMethod.getReturnType();
+    }
+    for (int i = 0; i < invokedMethod.getArity(); i++) {
+      if (invokedMethod.getParameter(i) != convertedMethod.getParameter(i)
+          && parameterConversions[i] == null) {
+        return invokedMethod.getParameter(i);
+      }
+    }
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java
index d69a158..a72ee2f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.EnumConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.EnumConversionCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticClasspathClassBuilder;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 47f48d4..8507816 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -31,11 +31,9 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.CustomConversionDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterVivifiedWrapperCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.ArrayConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.NullableConversionCfCodeProvider.ArrayConversionCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.apiconverter.WrapperConstructorCfCodeProvider;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.position.Position;
@@ -49,7 +47,7 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Function;
+import java.util.function.BiFunction;
 import java.util.function.Supplier;
 
 // I am responsible for the generation of wrappers used to call library APIs when desugaring
@@ -100,11 +98,17 @@
   private final AppView<?> appView;
   private final DexItemFactory factory;
   private final DesugaredLibraryEnumConversionSynthesizer enumConverter;
+  private final DesugaredLibraryConversionCfProvider conversionCfProvider;
 
   public DesugaredLibraryWrapperSynthesizer(AppView<?> appView) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.enumConverter = new DesugaredLibraryEnumConversionSynthesizer(appView);
+    this.conversionCfProvider = new DesugaredLibraryConversionCfProvider(appView, this);
+  }
+
+  public DesugaredLibraryConversionCfProvider getConversionCfProvider() {
+    return conversionCfProvider;
   }
 
   public boolean isSyntheticWrapper(DexType type) {
@@ -132,10 +136,11 @@
 
   public DexMethod ensureConversionMethod(
       DexType type,
-      DexType srcType,
-      DexType destType,
+      boolean destIsVivified,
       DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
       Supplier<UniqueContext> contextSupplier) {
+    DexType srcType = destIsVivified ? type : vivifiedTypeFor(type);
+    DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
     if (type.isArrayType()) {
       return ensureArrayConversionMethod(type, srcType, destType, eventConsumer, contextSupplier);
     }
@@ -166,11 +171,7 @@
       Supplier<UniqueContext> contextSupplier) {
     DexMethod conversion =
         ensureConversionMethod(
-            type.toDimensionMinusOneType(factory),
-            srcType.toDimensionMinusOneType(factory),
-            destType.toDimensionMinusOneType(factory),
-            eventConsumer,
-            contextSupplier);
+            type.toDimensionMinusOneType(factory), srcType == type, eventConsumer, contextSupplier);
     return ensureArrayConversionMethod(
         srcType, destType, eventConsumer, contextSupplier, conversion);
   }
@@ -183,11 +184,7 @@
       Supplier<UniqueContext> contextSupplier) {
     DexMethod conversion =
         getExistingProgramConversionMethod(
-            type.toDimensionMinusOneType(factory),
-            srcType.toDimensionMinusOneType(factory),
-            destType.toDimensionMinusOneType(factory),
-            eventConsumer,
-            contextSupplier);
+            type.toDimensionMinusOneType(factory), srcType == type, eventConsumer, contextSupplier);
     return ensureArrayConversionMethod(
         srcType, destType, eventConsumer, contextSupplier, conversion);
   }
@@ -224,10 +221,11 @@
 
   public DexMethod getExistingProgramConversionMethod(
       DexType type,
-      DexType srcType,
-      DexType destType,
+      boolean destIsVivified,
       DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
       Supplier<UniqueContext> contextSupplier) {
+    DexType srcType = destIsVivified ? type : vivifiedTypeFor(type);
+    DexType destType = destIsVivified ? vivifiedTypeFor(type) : type;
     if (type.isArrayType()) {
       return ensureArrayConversionMethodFromExistingBaseConversion(
           type, srcType, destType, eventConsumer, contextSupplier);
@@ -356,11 +354,8 @@
             type,
             classpathOrLibraryContext,
             eventConsumer,
-            wrapperField ->
-                synthesizeVirtualMethodsForTypeWrapper(
-                    methods,
-                    wrapperField,
-                    DesugaredLibraryWrapperSynthesizer::codeForClasspathMethod));
+            methods,
+            conversionCfProvider::generateWrapperConversionWithoutCode);
     DexClass vivifiedWrapper =
         ensureClasspathWrapper(
             kinds -> kinds.VIVIFIED_WRAPPER,
@@ -368,11 +363,8 @@
             vivifiedType,
             classpathOrLibraryContext,
             eventConsumer,
-            wrapperField ->
-                synthesizeVirtualMethodsForVivifiedTypeWrapper(
-                    methods,
-                    wrapperField,
-                    DesugaredLibraryWrapperSynthesizer::codeForClasspathMethod));
+            methods,
+            conversionCfProvider::generateVivifiedWrapperConversionWithoutCode);
     return new WrapperConversions(
         getConversion(wrapper, vivifiedType, type),
         getConversion(vivifiedWrapper, type, vivifiedType));
@@ -438,7 +430,8 @@
       DexType wrappedType,
       ClasspathOrLibraryClass classpathOrLibraryContext,
       DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
-      Function<DexEncodedField, Collection<DexEncodedMethod>> virtualMethodProvider) {
+      Iterable<DexMethod> methods,
+      BiFunction<DexMethod, DexField, DexEncodedMethod> methodGenerator) {
     assert eventConsumer != null;
     return appView
         .getSyntheticItems()
@@ -454,7 +447,8 @@
                   methodBuilder ->
                       buildConversionMethod(
                           methodBuilder, factory.createProto(wrappingType, wrappedType), null));
-              builder.setVirtualMethods(virtualMethodProvider.apply(wrapperField));
+              builder.setVirtualMethods(
+                  synthesizeVirtualMethodsForWrapper(methods, wrapperField, methodGenerator));
             },
             eventConsumer::acceptWrapperClasspathClass);
   }
@@ -487,7 +481,7 @@
   private CfCode computeProgramConversionMethodCode(
       DexField wrapperField, DexField reverseWrapperField, DexClass context) {
     assert context.isProgramClass();
-    return new APIConverterWrapperConversionCfCodeProvider(
+    return new NullableConversionCfCodeProvider.WrapperConversionCfCodeProvider(
             appView, reverseWrapperField, wrapperField)
         .generateCfCode();
   }
@@ -537,80 +531,24 @@
         .disableAndroidApiLevelCheck()
         .setCode(
             codeSynthesizor ->
-                new APIConverterConstructorCfCodeProvider(
+                new WrapperConstructorCfCodeProvider(
                         appView, wrappedValueField.getReference(), superType)
                     .generateCfCode());
   }
 
-  private static CfCode codeForClasspathMethod(DexMethod ignore) {
-    return null;
-  }
-
-  private Collection<DexEncodedMethod> synthesizeVirtualMethodsForVivifiedTypeWrapper(
-      Iterable<DexMethod> allImplementedMethods,
-      DexEncodedField wrapperField,
-      Function<DexMethod, CfCode> cfCodeProvider) {
-    List<DexEncodedMethod> generatedMethods = new ArrayList<>();
-    for (DexMethod method : allImplementedMethods) {
-      DexMethod methodToInstall =
-          factory.createMethod(wrapperField.getHolderType(), method.proto, method.name);
-      CfCode cfCode = cfCodeProvider.apply(method);
-      DexEncodedMethod newDexEncodedMethod = newSynthesizedMethod(methodToInstall, cfCode);
-      generatedMethods.add(newDexEncodedMethod);
-    }
-    return generatedMethods;
-  }
-
-  private CfCode synthesizeCfCodeForVivifiedTypeWrapper(
-      DexMethod method,
-      DexField wrapperField,
-      DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
-      Supplier<UniqueContext> contextSupplier) {
-    DexClass holderClass = appView.definitionFor(method.getHolderType());
-    boolean isInterface;
-    if (holderClass == null) {
-      assert appView
-          .options()
-          .machineDesugaredLibrarySpecification
-          .isEmulatedInterfaceRewrittenType(method.getHolderType());
-      isInterface = true;
-    } else {
-      isInterface = holderClass.isInterface();
-    }
-    return new APIConverterVivifiedWrapperCfCodeProvider(
-            appView, method, wrapperField, this, isInterface, eventConsumer, contextSupplier)
-        .generateCfCode();
-  }
-
-  private Collection<DexEncodedMethod> synthesizeVirtualMethodsForTypeWrapper(
+  private Collection<DexEncodedMethod> synthesizeVirtualMethodsForWrapper(
       Iterable<DexMethod> dexMethods,
       DexEncodedField wrapperField,
-      Function<DexMethod, CfCode> cfCodeProvider) {
+      BiFunction<DexMethod, DexField, DexEncodedMethod> methodGenerator) {
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     for (DexMethod method : dexMethods) {
-      DexMethod methodToInstall =
-          DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-              method, wrapperField.getHolderType(), appView);
-      CfCode cfCode = cfCodeProvider.apply(method);
-      DexEncodedMethod newDexEncodedMethod = newSynthesizedMethod(methodToInstall, cfCode);
+      DexEncodedMethod newDexEncodedMethod =
+          methodGenerator.apply(method, wrapperField.getReference());
       generatedMethods.add(newDexEncodedMethod);
     }
     return generatedMethods;
   }
 
-  private CfCode synthesizeCfCodeForTypeWrapper(
-      DexMethod method,
-      DexField wrapperField,
-      DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
-      Supplier<UniqueContext> contextSupplier) {
-    DexClass holderClass = appView.definitionFor(method.getHolderType());
-    assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
-    boolean isInterface = holderClass == null || holderClass.isInterface();
-    return new APIConverterWrapperCfCodeProvider(
-            appView, method, wrapperField, this, isInterface, eventConsumer, contextSupplier)
-        .generateCfCode();
-  }
-
   DexEncodedMethod newSynthesizedMethod(DexMethod methodToInstall, Code code) {
     MethodAccessFlags newFlags =
         MethodAccessFlags.fromSharedAccessFlags(
@@ -722,27 +660,27 @@
     DexProgramClass wrapper = getExistingProgramWrapper(context, kinds -> kinds.WRAPPER);
     DexEncodedField wrapperField = getWrapperUniqueEncodedField(wrapper);
     wrapper.addVirtualMethods(
-        synthesizeVirtualMethodsForTypeWrapper(
+        synthesizeVirtualMethodsForWrapper(
             methods,
             wrapperField,
-            m ->
-                synthesizeCfCodeForTypeWrapper(
-                    m,
-                    wrapperField.getReference(),
+            (method, field) ->
+                conversionCfProvider.generateWrapperConversion(
+                    method,
+                    field,
                     eventConsumer,
                     () -> processingContext.createUniqueContext(wrapper))));
     DexProgramClass vivifiedWrapper =
         getExistingProgramWrapper(context, kinds -> kinds.VIVIFIED_WRAPPER);
     DexEncodedField vivifiedWrapperField = getWrapperUniqueEncodedField(vivifiedWrapper);
     vivifiedWrapper.addVirtualMethods(
-        synthesizeVirtualMethodsForVivifiedTypeWrapper(
+        synthesizeVirtualMethodsForWrapper(
             methods,
             vivifiedWrapperField,
-            m ->
-                synthesizeCfCodeForVivifiedTypeWrapper(
-                    m,
-                    vivifiedWrapperField.getReference(),
+            (method, field) ->
+                conversionCfProvider.generateVivifiedWrapperConversion(
+                    method,
+                    field,
                     eventConsumer,
-                    () -> processingContext.createUniqueContext(vivifiedWrapper))));
+                    () -> processingContext.createUniqueContext(wrapper))));
   }
 }
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 3d9d2b8..7f5e42e 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
@@ -1833,6 +1833,9 @@
   }
 
   public void shortenLiveRanges(IRCode code) {
+    if (options.testing.disableShortenLiveRanges) {
+      return;
+    }
     // Currently, we are only shortening the live range of ConstNumbers in the entry block
     // and ConstStrings with one user.
     // TODO(ager): Generalize this to shorten live ranges for more instructions? Currently
@@ -3710,7 +3713,7 @@
   // The javac fix for JDK-8272564 has to be rewritten back to invoke-virtual on Object if the
   // method with an Object signature is not defined on the interface. See
   // https://bugs.openjdk.java.net/browse/JDK-8272564
-  public static void rewriteJdk8272564Fix(IRCode code, AppView<?> appView) {
+  public static void rewriteJdk8272564Fix(IRCode code, ProgramMethod context, AppView<?> appView) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     InstructionListIterator it = code.instructionListIterator();
     while (it.hasNext()) {
@@ -3718,10 +3721,15 @@
       if (instruction.isInvokeInterface()) {
         InvokeInterface invoke = instruction.asInvokeInterface();
         DexMethod method = invoke.getInvokedMethod();
-        DexMethod objectMember = dexItemFactory.objectMembers.matchingPublicObjectMember(method);
-        if (objectMember != null && appView.definitionFor(method) == null) {
-          it.replaceCurrentInstruction(
-              new InvokeVirtual(objectMember, invoke.outValue(), invoke.arguments()));
+        DexClass clazz = appView.definitionFor(method.getHolderType(), context);
+        if (clazz == null || clazz.isInterface()) {
+          DexMethod objectMember = dexItemFactory.objectMembers.matchingPublicObjectMember(method);
+          // javac before JDK-8272564 would still use invoke interface if the method is defined
+          // directly on the interface reference, so mimic that by not rewriting.
+          if (objectMember != null && appView.definitionFor(method) == null) {
+            it.replaceCurrentInstruction(
+                new InvokeVirtual(objectMember, invoke.outValue(), invoke.arguments()));
+          }
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 8839c13..bf34207 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.logging.Log;
@@ -190,7 +189,6 @@
     // Double-check the entry block does not have catch handlers.
     // Otherwise, we need to split it before moving canonicalized const-string, which may throw.
     assert !code.entryBlock().hasCatchHandlers();
-    final Position firstNonNonePosition = code.findFirstNonNonePosition(Position.syntheticNone());
     FastSortedEntrySet<Instruction, List<Value>> entries =
         valuesDefinedByConstant.object2ObjectEntrySet();
     // Sort the most frequently used constant first and exclude constant use only one time, such
@@ -256,7 +254,6 @@
         default:
           throw new Unreachable();
       }
-      newConst.setPosition(firstNonNonePosition);
       insertCanonicalizedConstant(code, newConst);
       for (Value outValue : entry.getValue()) {
         outValue.replaceUsers(newConst.outValue());
@@ -280,7 +277,9 @@
     // that can throw exceptions (since the value could be used on the exceptional edge).
     InstructionListIterator it = entryBlock.listIterator(code);
     while (it.hasNext()) {
-      if (!it.next().isArgument()) {
+      Instruction next = it.next();
+      if (!next.isArgument()) {
+        canonicalizedConstant.setPosition(code.getEntryPosition());
         it.previous();
         break;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index fa902a6..2cab3fb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -1083,7 +1083,7 @@
             if (instruction.isInvokeMethod()) {
               argumentTypes.add(argumentTypeFromValue(value, instruction.asInvokeMethod(), i));
             } else {
-              argumentTypes.add(instruction.asBinop().getNumericType().dexTypeFor(dexItemFactory));
+              argumentTypes.add(instruction.asBinop().getNumericType().toDexType(dexItemFactory));
             }
             argumentsMap.add(argumentTypes.size() - 1);
           }
@@ -1098,7 +1098,7 @@
         } else {
           updateReturnValueState(
               instruction.outValue(),
-              instruction.asBinop().getNumericType().dexTypeFor(dexItemFactory));
+              instruction.asBinop().getNumericType().toDexType(dexItemFactory));
         }
       }
     }
@@ -1738,7 +1738,7 @@
 
     @Override
     public Position getCanonicalDebugPositionAtOffset(int offset) {
-      throw new Unreachable();
+      return Position.syntheticNone();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
index dd73d7e..6e187ae 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
@@ -83,6 +83,7 @@
     return new IRCode(
         appView.options(),
         checkNotZeroMethod,
+        code.getEntryPosition(),
         code.getBlocks(),
         code.valueNumberGenerator,
         code.basicBlockNumberGenerator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 7e0b884..3ad14fb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -504,7 +504,7 @@
         if (temp == null) {
           return null;
         }
-        DexType conversionType = conversion.to.dexTypeFor(factory);
+        DexType conversionType = conversion.to.toDexType(factory);
         if (conversionType == factory.booleanType) {
           return temp.intValue() != 0 ? 1 : 0;
         } else if (conversionType == factory.byteType) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
deleted file mode 100644
index afadf9f..0000000
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ /dev/null
@@ -1,638 +0,0 @@
-// Copyright (c) 2019, 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.synthetic;
-
-import com.android.tools.r8.cf.code.CfArithmeticBinop;
-import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode;
-import com.android.tools.r8.cf.code.CfArrayLength;
-import com.android.tools.r8.cf.code.CfArrayLoad;
-import com.android.tools.r8.cf.code.CfArrayStore;
-import com.android.tools.r8.cf.code.CfCheckCast;
-import com.android.tools.r8.cf.code.CfConstNull;
-import com.android.tools.r8.cf.code.CfConstNumber;
-import com.android.tools.r8.cf.code.CfConstString;
-import com.android.tools.r8.cf.code.CfFrame;
-import com.android.tools.r8.cf.code.CfFrame.FrameType;
-import com.android.tools.r8.cf.code.CfGoto;
-import com.android.tools.r8.cf.code.CfIf;
-import com.android.tools.r8.cf.code.CfIfCmp;
-import com.android.tools.r8.cf.code.CfInstanceFieldRead;
-import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
-import com.android.tools.r8.cf.code.CfInstanceOf;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfLabel;
-import com.android.tools.r8.cf.code.CfLoad;
-import com.android.tools.r8.cf.code.CfNew;
-import com.android.tools.r8.cf.code.CfNewArray;
-import com.android.tools.r8.cf.code.CfReturn;
-import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfStackInstruction;
-import com.android.tools.r8.cf.code.CfStaticFieldRead;
-import com.android.tools.r8.cf.code.CfStore;
-import com.android.tools.r8.cf.code.CfThrow;
-import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-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.DexMethod;
-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.ir.code.If;
-import com.android.tools.r8.ir.code.MemberType;
-import com.android.tools.r8.ir.code.NumericType;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizer;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.collections.ImmutableDeque;
-import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.function.Supplier;
-import org.objectweb.asm.Opcodes;
-
-public abstract class DesugaredLibraryAPIConversionCfCodeProvider extends SyntheticCfCodeProvider {
-
-  DesugaredLibraryAPIConversionCfCodeProvider(AppView<?> appView, DexType holder) {
-    super(appView, holder);
-  }
-
-  DexType vivifiedTypeFor(DexType type) {
-    return DesugaredLibraryAPIConverter.vivifiedTypeFor(type, appView);
-  }
-
-  public static class APIConverterVivifiedWrapperCfCodeProvider
-      extends DesugaredLibraryAPIConversionCfCodeProvider {
-
-    private final DexField wrapperField;
-    private final DexMethod forwardMethod;
-    private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizer;
-    private final boolean itfCall;
-    private final DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer;
-    private final Supplier<UniqueContext> contextSupplier;
-
-    public APIConverterVivifiedWrapperCfCodeProvider(
-        AppView<?> appView,
-        DexMethod forwardMethod,
-        DexField wrapperField,
-        DesugaredLibraryWrapperSynthesizer wrapperSynthesizer,
-        boolean itfCall,
-        DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
-        Supplier<UniqueContext> contextSupplier) {
-      super(appView, wrapperField.holder);
-      this.forwardMethod = forwardMethod;
-      this.wrapperField = wrapperField;
-      this.wrapperSynthesizer = wrapperSynthesizer;
-      this.itfCall = itfCall;
-      this.eventConsumer = eventConsumer;
-      this.contextSupplier = contextSupplier;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-      // Wrapped value is a vivified type. Method uses type as external. Forward method should
-      // use vivifiedTypes.
-
-      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(new CfInstanceFieldRead(wrapperField));
-      int index = 1;
-      int stackIndex = 1;
-      DexType[] newParameters = forwardMethod.proto.parameters.values.clone();
-      for (DexType param : forwardMethod.proto.parameters.values) {
-        instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (wrapperSynthesizer.shouldConvert(param, forwardMethod)) {
-          instructions.add(
-              new CfInvoke(
-                  Opcodes.INVOKESTATIC,
-                  conversionMethod(param, param, vivifiedTypeFor(param)),
-                  false));
-          newParameters[index - 1] = vivifiedTypeFor(param);
-        }
-        if (param == factory.longType || param == factory.doubleType) {
-          stackIndex++;
-        }
-        stackIndex++;
-        index++;
-      }
-
-      DexType returnType = forwardMethod.proto.returnType;
-      DexType forwardMethodReturnType =
-          appView.typeRewriter.hasRewrittenType(returnType, appView)
-              ? vivifiedTypeFor(returnType)
-              : returnType;
-
-      DexProto newProto = factory.createProto(forwardMethodReturnType, newParameters);
-      DexMethod newForwardMethod =
-          factory.createMethod(wrapperField.type, newProto, forwardMethod.name);
-
-      if (itfCall) {
-        instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, newForwardMethod, true));
-      } else {
-        instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newForwardMethod, false));
-      }
-
-      if (wrapperSynthesizer.shouldConvert(returnType, forwardMethod)) {
-        instructions.add(
-            new CfInvoke(
-                Opcodes.INVOKESTATIC,
-                conversionMethod(returnType, vivifiedTypeFor(returnType), returnType),
-                false));
-      }
-      if (returnType == factory.voidType) {
-        instructions.add(new CfReturnVoid());
-      } else {
-        instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
-      }
-      return standardCfCodeFromInstructions(instructions);
-    }
-
-    private DexMethod conversionMethod(DexType type, DexType srcType, DexType destType) {
-      return wrapperSynthesizer.getExistingProgramConversionMethod(
-          type, srcType, destType, eventConsumer, contextSupplier);
-    }
-  }
-
-  public abstract static class AbstractAPIConverterWrapperCfCodeProvider
-      extends DesugaredLibraryAPIConversionCfCodeProvider {
-
-    DexMethod forwardMethod;
-    DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
-    boolean itfCall;
-
-    public AbstractAPIConverterWrapperCfCodeProvider(
-        AppView<?> appView,
-        DexType holder,
-        DexMethod forwardMethod,
-        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
-        boolean itfCall) {
-      super(appView, holder);
-      this.forwardMethod = forwardMethod;
-      this.wrapperSynthesizor = wrapperSynthesizor;
-      this.itfCall = itfCall;
-    }
-
-    abstract void generatePushReceiver(List<CfInstruction> instructions);
-
-    abstract DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType);
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-      // Wrapped value is a type. Method uses vivifiedTypes as external. Forward method should
-      // use types.
-
-      generatePushReceiver(instructions);
-      int stackIndex = 1;
-      for (DexType param : forwardMethod.proto.parameters.values) {
-        instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (wrapperSynthesizor.shouldConvert(param, forwardMethod)) {
-          instructions.add(
-              new CfInvoke(
-                  Opcodes.INVOKESTATIC,
-                  ensureConversionMethod(param, vivifiedTypeFor(param), param),
-                  false));
-        }
-        if (param == factory.longType || param == factory.doubleType) {
-          stackIndex++;
-        }
-        stackIndex++;
-      }
-
-      if (itfCall) {
-        instructions.add(new CfInvoke(Opcodes.INVOKEINTERFACE, forwardMethod, true));
-      } else {
-        instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, forwardMethod, false));
-      }
-
-      DexType returnType = forwardMethod.proto.returnType;
-      if (wrapperSynthesizor.shouldConvert(returnType, forwardMethod)) {
-        instructions.add(
-            new CfInvoke(
-                Opcodes.INVOKESTATIC,
-                ensureConversionMethod(returnType, returnType, vivifiedTypeFor(returnType)),
-                false));
-        returnType = vivifiedTypeFor(returnType);
-      }
-      if (returnType == factory.voidType) {
-        instructions.add(new CfReturnVoid());
-      } else {
-        instructions.add(new CfReturn(ValueType.fromDexType(returnType)));
-      }
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-
-  public static class APICallbackWrapperCfCodeProvider
-      extends AbstractAPIConverterWrapperCfCodeProvider {
-
-    private final DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer;
-    private final Supplier<UniqueContext> contextSupplier;
-
-    public APICallbackWrapperCfCodeProvider(
-        AppView<?> appView,
-        DexMethod forwardMethod,
-        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
-        boolean itfCall,
-        DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
-        Supplier<UniqueContext> contextSupplier) {
-      super(appView, forwardMethod.holder, forwardMethod, wrapperSynthesizor, itfCall);
-      this.eventConsumer = eventConsumer;
-      this.contextSupplier = contextSupplier;
-    }
-
-    @Override
-    void generatePushReceiver(List<CfInstruction> instructions) {
-      instructions.add(new CfLoad(ValueType.fromDexType(forwardMethod.holder), 0));
-    }
-
-    @Override
-    DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType) {
-      return wrapperSynthesizor.ensureConversionMethod(
-          type, srcType, destType, eventConsumer, contextSupplier);
-    }
-  }
-
-  public static class APIConverterWrapperCfCodeProvider
-      extends AbstractAPIConverterWrapperCfCodeProvider {
-
-    private final DexField wrapperField;
-    private final DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer;
-    private final Supplier<UniqueContext> contextSupplier;
-
-    public APIConverterWrapperCfCodeProvider(
-        AppView<?> appView,
-        DexMethod forwardMethod,
-        DexField wrapperField,
-        DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
-        boolean itfCall,
-        DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
-        Supplier<UniqueContext> contextSupplier) {
-      super(appView, wrapperField.holder, forwardMethod, wrapperSynthesizor, itfCall);
-      this.wrapperField = wrapperField;
-      this.eventConsumer = eventConsumer;
-      this.contextSupplier = contextSupplier;
-    }
-
-    @Override
-    void generatePushReceiver(List<CfInstruction> instructions) {
-      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(new CfInstanceFieldRead(wrapperField));
-    }
-
-    @Override
-    DexMethod ensureConversionMethod(DexType type, DexType srcType, DexType destType) {
-      return wrapperSynthesizor.getExistingProgramConversionMethod(
-          type, srcType, destType, eventConsumer, contextSupplier);
-    }
-  }
-
-  public static class APIConverterWrapperConversionCfCodeProvider extends SyntheticCfCodeProvider {
-
-    DexField reverseWrapperField;
-    DexField wrapperField;
-
-    public APIConverterWrapperConversionCfCodeProvider(
-        AppView<?> appView, DexField reverseWrapperField, DexField wrapperField) {
-      super(appView, wrapperField.holder);
-      this.reverseWrapperField = reverseWrapperField;
-      this.wrapperField = wrapperField;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-
-      DexType argType = wrapperField.type;
-      ImmutableInt2ReferenceSortedMap<FrameType> locals =
-          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
-              .put(0, FrameType.initialized(argType))
-              .build();
-
-      // if (arg == null) { return null };
-      CfLabel nullDest = new CfLabel();
-      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-      instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest));
-      instructions.add(new CfConstNull());
-      instructions.add(new CfReturn(ValueType.OBJECT));
-      instructions.add(nullDest);
-      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-
-      // if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField};
-      assert reverseWrapperField != null;
-      CfLabel unwrapDest = new CfLabel();
-      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-      instructions.add(new CfInstanceOf(reverseWrapperField.holder));
-      instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest));
-      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-      instructions.add(new CfCheckCast(reverseWrapperField.holder));
-      instructions.add(new CfInstanceFieldRead(reverseWrapperField));
-      instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
-      instructions.add(unwrapDest);
-      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-
-      // return new Wrapper(wrappedValue);
-      instructions.add(new CfNew(wrapperField.holder));
-      instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP));
-      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKESPECIAL,
-              factory.createMethod(
-                  wrapperField.holder,
-                  factory.createProto(factory.voidType, argType),
-                  factory.constructorMethodName),
-              false));
-      instructions.add(new CfReturn(ValueType.fromDexType(wrapperField.holder)));
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-
-  public static class APIConversionCfCodeProvider extends SyntheticCfCodeProvider {
-
-    private final CfInvoke initialInvoke;
-    private final DexMethod returnConversion;
-    private final DexMethod[] parameterConversions;
-
-    public APIConversionCfCodeProvider(
-        AppView<?> appView,
-        DexType holder,
-        CfInvoke initialInvoke,
-        DexMethod returnConversion,
-        DexMethod[] parameterConversions) {
-      super(appView, holder);
-      this.initialInvoke = initialInvoke;
-      this.returnConversion = returnConversion;
-      this.parameterConversions = parameterConversions;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexMethod invokedMethod = initialInvoke.getMethod();
-      DexMethod convertedMethod =
-          DesugaredLibraryAPIConverter.getConvertedAPI(
-              invokedMethod, returnConversion, parameterConversions, appView);
-
-      List<CfInstruction> instructions = new ArrayList<>();
-
-      boolean isStatic = initialInvoke.getOpcode() == Opcodes.INVOKESTATIC;
-      if (!isStatic) {
-        instructions.add(new CfLoad(ValueType.fromDexType(invokedMethod.holder), 0));
-      }
-      int receiverShift = BooleanUtils.intValue(!isStatic);
-      int stackIndex = 0;
-      for (int i = 0; i < invokedMethod.getArity(); i++) {
-        DexType param = invokedMethod.getParameter(i);
-        instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex + receiverShift));
-        if (parameterConversions[i] != null) {
-          instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
-        }
-        if (param == appView.dexItemFactory().longType
-            || param == appView.dexItemFactory().doubleType) {
-          stackIndex++;
-        }
-        stackIndex++;
-      }
-
-      // Actual call to converted value.
-      instructions.add(
-          new CfInvoke(initialInvoke.getOpcode(), convertedMethod, initialInvoke.isInterface()));
-
-      // Return conversion.
-      if (returnConversion != null) {
-        instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
-      }
-
-      if (invokedMethod.getReturnType().isVoidType()) {
-        instructions.add(new CfReturnVoid());
-      } else {
-        instructions.add(new CfReturn(ValueType.fromDexType(invokedMethod.getReturnType())));
-      }
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-
-  public static class ArrayConversionCfCodeProvider extends SyntheticCfCodeProvider {
-
-    private final DexType typeArray;
-    private final DexType convertedTypeArray;
-    private final DexMethod conversion;
-
-    public ArrayConversionCfCodeProvider(
-        AppView<?> appView,
-        DexType holder,
-        DexType typeArray,
-        DexType convertedTypeArray,
-        DexMethod conversion) {
-      super(appView, holder);
-      this.typeArray = typeArray;
-      this.convertedTypeArray = convertedTypeArray;
-      this.conversion = conversion;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-
-      // if (arg == null) { return null; }
-      instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0));
-      instructions.add(new CfConstNull());
-      CfLabel nonNull = new CfLabel();
-      instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, nonNull));
-      instructions.add(new CfConstNull());
-      instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray)));
-      instructions.add(nonNull);
-      instructions.add(
-          new CfFrame(
-              ImmutableInt2ReferenceSortedMap.<FrameType>builder()
-                  .put(0, FrameType.initialized(typeArray))
-                  .build(),
-              ImmutableDeque.of()));
-
-      ImmutableInt2ReferenceSortedMap<FrameType> locals =
-          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
-              .put(0, FrameType.initialized(typeArray))
-              .put(1, FrameType.initialized(factory.intType))
-              .put(2, FrameType.initialized(convertedTypeArray))
-              .put(3, FrameType.initialized(factory.intType))
-              .build();
-
-      // int t1 = arg.length;
-      instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0));
-      instructions.add(new CfArrayLength());
-      instructions.add(new CfStore(ValueType.INT, 1));
-      // ConvertedType[] t2 = new ConvertedType[t1];
-      instructions.add(new CfLoad(ValueType.INT, 1));
-      instructions.add(new CfNewArray(convertedTypeArray));
-      instructions.add(new CfStore(ValueType.fromDexType(convertedTypeArray), 2));
-      // int t3 = 0;
-      instructions.add(new CfConstNumber(0, ValueType.INT));
-      instructions.add(new CfStore(ValueType.INT, 3));
-      // while (t3 < t1) {
-      CfLabel returnLabel = new CfLabel();
-      CfLabel loopLabel = new CfLabel();
-      instructions.add(loopLabel);
-      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-      instructions.add(new CfLoad(ValueType.INT, 3));
-      instructions.add(new CfLoad(ValueType.INT, 1));
-      instructions.add(new CfIfCmp(If.Type.GE, ValueType.INT, returnLabel));
-      // t2[t3] = convert(arg[t3]);
-      instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2));
-      instructions.add(new CfLoad(ValueType.INT, 3));
-      instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0));
-      instructions.add(new CfLoad(ValueType.INT, 3));
-      instructions.add(new CfArrayLoad(MemberType.OBJECT));
-      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, conversion, false));
-      instructions.add(new CfArrayStore(MemberType.OBJECT));
-      // t3 = t3 + 1; }
-      instructions.add(new CfLoad(ValueType.INT, 3));
-      instructions.add(new CfConstNumber(1, ValueType.INT));
-      instructions.add(new CfArithmeticBinop(Opcode.Add, NumericType.INT));
-      instructions.add(new CfStore(ValueType.INT, 3));
-      instructions.add(new CfGoto(loopLabel));
-      // return t2;
-      instructions.add(returnLabel);
-      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-      instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2));
-      instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray)));
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-
-  public static class EnumConversionCfCodeProvider extends SyntheticCfCodeProvider {
-
-    private final Iterable<DexEncodedField> enumFields;
-    private final DexType enumType;
-    private final DexType convertedType;
-
-    public EnumConversionCfCodeProvider(
-        AppView<?> appView,
-        DexType holder,
-        Iterable<DexEncodedField> enumFields,
-        DexType enumType,
-        DexType convertedType) {
-      super(appView, holder);
-      this.enumFields = enumFields;
-      this.enumType = enumType;
-      this.convertedType = convertedType;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-
-      ImmutableInt2ReferenceSortedMap<FrameType> locals =
-          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
-              .put(0, FrameType.initialized(enumType))
-              .build();
-
-      // if (arg == null) { return null; }
-      instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0));
-      instructions.add(new CfConstNull());
-      CfLabel nonNull = new CfLabel();
-      instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, nonNull));
-      instructions.add(new CfConstNull());
-      instructions.add(new CfReturn(ValueType.fromDexType(convertedType)));
-      instructions.add(nonNull);
-      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-
-      // if (arg == enumType.enumField1) { return convertedType.enumField1; }
-      Iterator<DexEncodedField> iterator = enumFields.iterator();
-      while (iterator.hasNext()) {
-        DexEncodedField enumField = iterator.next();
-        CfLabel notEqual = new CfLabel();
-        if (iterator.hasNext()) {
-          instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0));
-          instructions.add(
-              new CfStaticFieldRead(factory.createField(enumType, enumType, enumField.getName())));
-          instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, notEqual));
-        }
-        instructions.add(
-            new CfStaticFieldRead(
-                factory.createField(convertedType, convertedType, enumField.getName())));
-        instructions.add(new CfReturn(ValueType.fromDexType(convertedType)));
-        if (iterator.hasNext()) {
-          instructions.add(notEqual);
-          instructions.add(new CfFrame(locals, ImmutableDeque.of()));
-        }
-      }
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-
-  public static class APIConverterConstructorCfCodeProvider extends SyntheticCfCodeProvider {
-
-    private final DexField wrapperField;
-    private final DexType superType;
-
-    public APIConverterConstructorCfCodeProvider(
-        AppView<?> appView, DexField wrapperField, DexType superType) {
-      super(appView, wrapperField.holder);
-      this.wrapperField = wrapperField;
-      this.superType = superType;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKESPECIAL,
-              factory.createMethod(
-                  superType, factory.createProto(factory.voidType), factory.constructorMethodName),
-              false));
-      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
-      instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.type), 1));
-      instructions.add(new CfInstanceFieldWrite(wrapperField));
-      instructions.add(new CfReturnVoid());
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-
-  public static class APIConverterThrowRuntimeExceptionCfCodeProvider
-      extends SyntheticCfCodeProvider {
-
-    DexString message;
-
-    public APIConverterThrowRuntimeExceptionCfCodeProvider(
-        AppView<?> appView, DexString message, DexType holder) {
-      super(appView, holder);
-      this.message = message;
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      DexItemFactory factory = appView.dexItemFactory();
-      List<CfInstruction> instructions = new ArrayList<>();
-      instructions.add(new CfNew(factory.runtimeExceptionType));
-      instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP));
-      instructions.add(new CfConstString(message));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKESPECIAL,
-              factory.createMethod(
-                  factory.runtimeExceptionType,
-                  factory.createProto(factory.voidType, factory.stringType),
-                  factory.constructorMethodName),
-              false));
-      instructions.add(new CfThrow());
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index c81383c..8e0d7d4 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -207,7 +207,7 @@
 
   @Override
   public Position getCanonicalDebugPositionAtOffset(int offset) {
-    throw new Unreachable();
+    return Position.syntheticNone();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/APIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/APIConversionCfCodeProvider.java
new file mode 100644
index 0000000..5932c0b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/APIConversionCfCodeProvider.java
@@ -0,0 +1,151 @@
+// Copyright (c) 2022, 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.synthetic.apiconverter;
+
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+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.code.ValueType;
+import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class APIConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+  private final DexMethod forwardMethod;
+  private final boolean itfCall;
+  private final DexMethod returnConversion;
+  private final DexMethod[] parameterConversions;
+  // By default, the method is forwarded to the receiver (unless static), if this is set, the
+  // method is forwarded on this field on the receiver.
+  private final int forwardCallOpcode;
+  private final DexField forwardFieldOrNull;
+
+  public APIConversionCfCodeProvider(
+      AppView<?> appView,
+      DexType holder,
+      DexMethod forwardMethod,
+      boolean itfCall,
+      DexMethod returnConversion,
+      DexMethod[] parameterConversions) {
+    super(appView, holder);
+    this.forwardMethod = forwardMethod;
+    this.itfCall = itfCall;
+    this.returnConversion = returnConversion;
+    this.parameterConversions = parameterConversions;
+    this.forwardCallOpcode = defaultForwardCallOpcode(itfCall);
+    this.forwardFieldOrNull = null;
+  }
+
+  public APIConversionCfCodeProvider(
+      AppView<?> appView,
+      DexType holder,
+      DexMethod forwardMethod,
+      boolean itfCall,
+      DexMethod returnConversion,
+      DexMethod[] parameterConversions,
+      int forwardCallOpcode) {
+    super(appView, holder);
+    this.forwardMethod = forwardMethod;
+    this.itfCall = itfCall;
+    this.returnConversion = returnConversion;
+    this.parameterConversions = parameterConversions;
+    this.forwardCallOpcode = forwardCallOpcode;
+    this.forwardFieldOrNull = null;
+  }
+
+  public APIConversionCfCodeProvider(
+      AppView<?> appView,
+      DexType holder,
+      DexMethod forwardMethod,
+      boolean itfCall,
+      DexMethod returnConversion,
+      DexMethod[] parameterConversions,
+      DexField forwardFieldOrNull) {
+    super(appView, holder);
+    this.forwardMethod = forwardMethod;
+    this.itfCall = itfCall;
+    this.returnConversion = returnConversion;
+    this.parameterConversions = parameterConversions;
+    this.forwardCallOpcode = defaultForwardCallOpcode(itfCall);
+    this.forwardFieldOrNull = forwardFieldOrNull;
+  }
+
+  private int defaultForwardCallOpcode(boolean itfCall) {
+    return itfCall ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL;
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    List<CfInstruction> instructions = new ArrayList<>();
+    boolean isStatic = forwardCallOpcode == Opcodes.INVOKESTATIC;
+    generatePushReceiver(instructions, isStatic);
+    generateParameterConvertAndLoads(instructions, isStatic);
+    generateForwardingCall(instructions);
+    generateReturnConversion(instructions);
+    generateReturn(instructions);
+    return standardCfCodeFromInstructions(instructions);
+  }
+
+  private void generateReturn(List<CfInstruction> instructions) {
+    if (forwardMethod.getReturnType().isVoidType()) {
+      instructions.add(new CfReturnVoid());
+    } else {
+      ValueType valueType = valueTypeFromForwardMethod(forwardMethod.getReturnType());
+      instructions.add(new CfReturn(valueType));
+    }
+  }
+
+  private void generateReturnConversion(List<CfInstruction> instructions) {
+    if (returnConversion != null) {
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
+    }
+  }
+
+  private void generateForwardingCall(List<CfInstruction> instructions) {
+    instructions.add(new CfInvoke(forwardCallOpcode, forwardMethod, itfCall));
+  }
+
+  private void generateParameterConvertAndLoads(
+      List<CfInstruction> instructions, boolean isStatic) {
+    int stackIndex = BooleanUtils.intValue(!isStatic);
+    for (int i = 0; i < forwardMethod.getArity(); i++) {
+      ValueType valueType = valueTypeFromForwardMethod(forwardMethod.getParameter(i));
+      instructions.add(new CfLoad(valueType, stackIndex));
+      if (parameterConversions[i] != null) {
+        instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
+      }
+      if (valueType.isWide()) {
+        stackIndex++;
+      }
+      stackIndex++;
+    }
+  }
+
+  private void generatePushReceiver(List<CfInstruction> instructions, boolean isStatic) {
+    if (!isStatic) {
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      if (forwardFieldOrNull != null) {
+        instructions.add(new CfInstanceFieldRead(forwardFieldOrNull));
+      }
+    }
+  }
+
+  private ValueType valueTypeFromForwardMethod(DexType type) {
+    // We note that the type may not be exact, we can have a vivified type instead of the expected
+    // type, however, the valueType is correct (primitive types are never converted).
+    return ValueType.fromDexType(type);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
new file mode 100644
index 0000000..98ec596
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
@@ -0,0 +1,260 @@
+// Copyright (c) 2022, 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.synthetic.apiconverter;
+
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
+import com.android.tools.r8.cf.code.CfArithmeticBinop.Opcode;
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+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.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
+import com.android.tools.r8.utils.collections.ImmutableDeque;
+import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public abstract class NullableConversionCfCodeProvider extends SyntheticCfCodeProvider {
+
+  protected NullableConversionCfCodeProvider(AppView<?> appView, DexType holder) {
+    super(appView, holder);
+  }
+
+  void generateNullCheck(List<CfInstruction> instructions) {
+    CfLabel nullDest = new CfLabel();
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest));
+    instructions.add(new CfConstNull());
+    instructions.add(new CfReturn(ValueType.OBJECT));
+    instructions.add(nullDest);
+  }
+
+  public static class ArrayConversionCfCodeProvider extends NullableConversionCfCodeProvider {
+
+    private final DexType typeArray;
+    private final DexType convertedTypeArray;
+    private final DexMethod conversion;
+
+    public ArrayConversionCfCodeProvider(
+        AppView<?> appView,
+        DexType holder,
+        DexType typeArray,
+        DexType convertedTypeArray,
+        DexMethod conversion) {
+      super(appView, holder);
+      this.typeArray = typeArray;
+      this.convertedTypeArray = convertedTypeArray;
+      this.conversion = conversion;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      // if (arg == null) { return null; }
+      generateNullCheck(instructions);
+      instructions.add(
+          new CfFrame(
+              ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+                  .put(0, FrameType.initialized(typeArray))
+                  .build(),
+              ImmutableDeque.of()));
+
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(typeArray))
+              .put(1, FrameType.initialized(factory.intType))
+              .put(2, FrameType.initialized(convertedTypeArray))
+              .put(3, FrameType.initialized(factory.intType))
+              .build();
+
+      // int t1 = arg.length;
+      instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0));
+      instructions.add(new CfArrayLength());
+      instructions.add(new CfStore(ValueType.INT, 1));
+      // ConvertedType[] t2 = new ConvertedType[t1];
+      instructions.add(new CfLoad(ValueType.INT, 1));
+      instructions.add(new CfNewArray(convertedTypeArray));
+      instructions.add(new CfStore(ValueType.fromDexType(convertedTypeArray), 2));
+      // int t3 = 0;
+      instructions.add(new CfConstNumber(0, ValueType.INT));
+      instructions.add(new CfStore(ValueType.INT, 3));
+      // while (t3 < t1) {
+      CfLabel returnLabel = new CfLabel();
+      CfLabel loopLabel = new CfLabel();
+      instructions.add(loopLabel);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfLoad(ValueType.INT, 1));
+      instructions.add(new CfIfCmp(If.Type.GE, ValueType.INT, returnLabel));
+      // t2[t3] = convert(arg[t3]);
+      instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2));
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfLoad(ValueType.fromDexType(typeArray), 0));
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfArrayLoad(MemberType.OBJECT));
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, conversion, false));
+      instructions.add(new CfArrayStore(MemberType.OBJECT));
+      // t3 = t3 + 1; }
+      instructions.add(new CfLoad(ValueType.INT, 3));
+      instructions.add(new CfConstNumber(1, ValueType.INT));
+      instructions.add(new CfArithmeticBinop(Opcode.Add, NumericType.INT));
+      instructions.add(new CfStore(ValueType.INT, 3));
+      instructions.add(new CfGoto(loopLabel));
+      // return t2;
+      instructions.add(returnLabel);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+      instructions.add(new CfLoad(ValueType.fromDexType(convertedTypeArray), 2));
+      instructions.add(new CfReturn(ValueType.fromDexType(convertedTypeArray)));
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+
+  public static class EnumConversionCfCodeProvider extends NullableConversionCfCodeProvider {
+
+    private final Iterable<DexEncodedField> enumFields;
+    private final DexType enumType;
+    private final DexType convertedType;
+
+    public EnumConversionCfCodeProvider(
+        AppView<?> appView,
+        DexType holder,
+        Iterable<DexEncodedField> enumFields,
+        DexType enumType,
+        DexType convertedType) {
+      super(appView, holder);
+      this.enumFields = enumFields;
+      this.enumType = enumType;
+      this.convertedType = convertedType;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(enumType))
+              .build();
+
+      // if (arg == null) { return null; }
+      generateNullCheck(instructions);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+
+      // if (arg == enumType.enumField1) { return convertedType.enumField1; }
+      Iterator<DexEncodedField> iterator = enumFields.iterator();
+      while (iterator.hasNext()) {
+        DexEncodedField enumField = iterator.next();
+        CfLabel notEqual = new CfLabel();
+        if (iterator.hasNext()) {
+          instructions.add(new CfLoad(ValueType.fromDexType(enumType), 0));
+          instructions.add(
+              new CfStaticFieldRead(factory.createField(enumType, enumType, enumField.getName())));
+          instructions.add(new CfIfCmp(If.Type.NE, ValueType.OBJECT, notEqual));
+        }
+        instructions.add(
+            new CfStaticFieldRead(
+                factory.createField(convertedType, convertedType, enumField.getName())));
+        instructions.add(new CfReturn(ValueType.fromDexType(convertedType)));
+        if (iterator.hasNext()) {
+          instructions.add(notEqual);
+          instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+        }
+      }
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+
+  public static class WrapperConversionCfCodeProvider extends NullableConversionCfCodeProvider {
+
+    DexField reverseWrapperField;
+    DexField wrapperField;
+
+    public WrapperConversionCfCodeProvider(
+        AppView<?> appView, DexField reverseWrapperField, DexField wrapperField) {
+      super(appView, wrapperField.holder);
+      this.reverseWrapperField = reverseWrapperField;
+      this.wrapperField = wrapperField;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      DexType argType = wrapperField.type;
+      ImmutableInt2ReferenceSortedMap<FrameType> locals =
+          ImmutableInt2ReferenceSortedMap.<FrameType>builder()
+              .put(0, FrameType.initialized(argType))
+              .build();
+
+      // if (arg == null) { return null };
+      generateNullCheck(instructions);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+
+      // if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField};
+      assert reverseWrapperField != null;
+      CfLabel unwrapDest = new CfLabel();
+      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
+      instructions.add(new CfInstanceOf(reverseWrapperField.holder));
+      instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest));
+      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
+      instructions.add(new CfCheckCast(reverseWrapperField.holder));
+      instructions.add(new CfInstanceFieldRead(reverseWrapperField));
+      instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
+      instructions.add(unwrapDest);
+      instructions.add(new CfFrame(locals, ImmutableDeque.of()));
+
+      // return new Wrapper(wrappedValue);
+      instructions.add(new CfNew(wrapperField.holder));
+      instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP));
+      instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESPECIAL,
+              factory.createMethod(
+                  wrapperField.holder,
+                  factory.createProto(factory.voidType, argType),
+                  factory.constructorMethodName),
+              false));
+      instructions.add(new CfReturn(ValueType.fromDexType(wrapperField.holder)));
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/WrapperConstructorCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/WrapperConstructorCfCodeProvider.java
new file mode 100644
index 0000000..94ba378
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/WrapperConstructorCfCodeProvider.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2022, 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.synthetic.apiconverter;
+
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+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.ValueType;
+import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class WrapperConstructorCfCodeProvider extends SyntheticCfCodeProvider {
+
+  private final DexField wrapperField;
+  private final DexType superType;
+
+  public WrapperConstructorCfCodeProvider(
+      AppView<?> appView, DexField wrapperField, DexType superType) {
+    super(appView, wrapperField.holder);
+    this.wrapperField = wrapperField;
+    this.superType = superType;
+  }
+
+  @Override
+  public CfCode generateCfCode() {
+    DexItemFactory factory = appView.dexItemFactory();
+    List<CfInstruction> instructions = new ArrayList<>();
+    instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
+    instructions.add(
+        new CfInvoke(
+            Opcodes.INVOKESPECIAL,
+            factory.createMethod(
+                superType, factory.createProto(factory.voidType), factory.constructorMethodName),
+            false));
+    instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.holder), 0));
+    instructions.add(new CfLoad(ValueType.fromDexType(wrapperField.type), 1));
+    instructions.add(new CfInstanceFieldWrite(wrapperField));
+    instructions.add(new CfReturnVoid());
+    return standardCfCodeFromInstructions(instructions);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
index 7572341..dad22fa 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 public class BottomCfFrameState extends CfFrameState {
@@ -43,7 +44,9 @@
 
   @Override
   public CfFrameState pop(
-      AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn) {
+      AppView<?> appView,
+      FrameType expectedType,
+      BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
     return error();
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
index ec899a2..01bda85 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
@@ -11,6 +11,10 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 public abstract class CfFrameState extends AbstractState<CfFrameState> {
@@ -38,7 +42,9 @@
   public abstract CfFrameState pop(AppView<?> appView, FrameType expectedType);
 
   public abstract CfFrameState pop(
-      AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn);
+      AppView<?> appView,
+      FrameType expectedType,
+      BiFunction<CfFrameState, FrameType, CfFrameState> fn);
 
   public abstract CfFrameState pop(AppView<?> appView, FrameType... expectedTypes);
 
@@ -49,10 +55,34 @@
 
   public abstract CfFrameState popInitialized(AppView<?> appView, DexType... expectedTypes);
 
+  public final CfFrameState popInitialized(AppView<?> appView, MemberType memberType) {
+    return pop(appView, FrameType.fromMemberType(memberType, appView.dexItemFactory()));
+  }
+
+  public final CfFrameState popInitialized(AppView<?> appView, NumericType expectedType) {
+    return popInitialized(appView, expectedType.toDexType(appView.dexItemFactory()));
+  }
+
+  // TODO(b/214496607): Pushing a value should return an error if the stack grows larger than the
+  //  max stack height.
   public abstract CfFrameState push(DexType type);
 
+  // TODO(b/214496607): Pushing a value should return an error if the stack grows larger than the
+  //  max stack height.
   public abstract CfFrameState push(FrameType frameType);
 
+  public final CfFrameState push(AppView<?> appView, MemberType memberType) {
+    return push(FrameType.fromMemberType(memberType, appView.dexItemFactory()));
+  }
+
+  public final CfFrameState push(AppView<?> appView, NumericType numericType) {
+    return push(numericType.toDexType(appView.dexItemFactory()));
+  }
+
+  public final CfFrameState push(AppView<?> appView, ValueType valueType) {
+    return push(valueType.toDexType(appView.dexItemFactory()));
+  }
+
   @Override
   public final CfFrameState join(CfFrameState state) {
     // TODO(b/214496607): Implement join.
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
index 63654b3..4abdc0f 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
@@ -18,6 +18,7 @@
 import java.util.ArrayDeque;
 import java.util.Deque;
 import java.util.Objects;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 public class ConcreteCfFrameState extends CfFrameState {
@@ -76,16 +77,18 @@
 
   @Override
   public CfFrameState pop(AppView<?> appView, FrameType expectedType) {
-    return pop(appView, expectedType, ignore -> this);
+    return pop(appView, expectedType, (newFrame, ignore) -> newFrame);
   }
 
   @Override
   public CfFrameState pop(
-      AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn) {
+      AppView<?> appView,
+      FrameType expectedType,
+      BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
     return pop(
         frameType ->
             CfAssignability.isAssignable(frameType, expectedType, appView)
-                ? fn.apply(frameType)
+                ? fn.apply(this, frameType)
                 : error());
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
index 7e71f93..cdbeba2 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 /** An analysis state representing that the code does not type check. */
@@ -44,7 +45,9 @@
 
   @Override
   public CfFrameState pop(
-      AppView<?> appView, FrameType expectedType, Function<FrameType, CfFrameState> fn) {
+      AppView<?> appView,
+      FrameType expectedType,
+      BiFunction<CfFrameState, FrameType, CfFrameState> fn) {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 4b9385d..f6959c6 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -260,9 +260,17 @@
 
   private DexAnnotationElement rewriteAnnotationElement(
       DexType annotationType, DexAnnotationElement original) {
-    DexClass definition = appView.definitionFor(annotationType);
+    // The dalvik.annotation.AnnotationDefault is typically not on bootclasspath. However, if it
+    // is present, the definition does not define the 'value' getter but that is the spec:
+    // https://source.android.com/devices/tech/dalvik/dex-format#dalvik-annotation-default
+    // If the annotation matches the structural requirement keep it.
+    if (appView.dexItemFactory().annotationDefault.equals(annotationType)
+        && appView.dexItemFactory().valueString.equals(original.name)) {
+      return original;
+    }
     // We cannot strip annotations where we cannot look up the definition, because this will break
     // apps that rely on the annotation to exist. See b/134766810 for more information.
+    DexClass definition = appView.definitionFor(annotationType);
     if (definition == null) {
       return original;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 47baa0b..a2c0d26 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -4669,6 +4669,8 @@
             defaultInitializer.getReference(),
             defaultInitializer,
             graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
+        applyMinimumKeepInfoWhenLiveOrTargeted(
+            defaultInitializer, KeepMethodInfo.newEmptyJoiner().disallowOptimization());
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
new file mode 100644
index 0000000..00412d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -0,0 +1,313 @@
+// 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.utils;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar;
+import com.android.tools.r8.origin.PathOrigin;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Provides a mappings of classes to modules. The structure of the input file is as follows:
+ * packageOrClass:module
+ *
+ * <p>Lines with a # prefix are ignored.
+ *
+ * <p>We will do most specific matching, i.e.,
+ * <pre>
+ *   com.google.foobar.*:feature2
+ *   com.google.*:base
+ * </pre>
+ * will put everything in the com.google namespace into base, except classes in com.google.foobar
+ * that will go to feature2. Class based mappings takes precedence over packages (since they are
+ * more specific):
+ * <pre>
+ *   com.google.A:feature2
+ *   com.google.*:base
+ *  </pre>
+ * Puts A into feature2, and all other classes from com.google into base.
+ *
+ * <p>Note that this format does not allow specifying inter-module dependencies, this is simply a
+ * placement tool.
+ */
+@Keep
+public final class FeatureClassMapping {
+
+  Map<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
+  Map<String, String> parseNonClassRules = new HashMap<>();
+  boolean usesOnlyExactMappings = true;
+
+  Set<FeaturePredicate> mappings = new HashSet<>();
+
+  Path mappingFile;
+  String baseName = DEFAULT_BASE_NAME;
+
+  static final String DEFAULT_BASE_NAME = "base";
+
+  static final String COMMENT = "#";
+  static final String SEPARATOR = ":";
+
+  public String getBaseName() {
+    return baseName;
+  }
+
+  private static class SpecificationOrigin extends PathOrigin {
+
+    public SpecificationOrigin(Path path) {
+      super(path);
+    }
+
+    @Override
+    public String part() {
+      return "specification file '" + super.part() + "'";
+    }
+  }
+
+  private static class JarFileOrigin extends PathOrigin {
+
+    public JarFileOrigin(Path path) {
+      super(path);
+    }
+
+    @Override
+    public String part() {
+      return "jar file '" + super.part() + "'";
+    }
+  }
+
+  public static FeatureClassMapping fromSpecification(Path file) throws FeatureMappingException {
+    return fromSpecification(file, new DiagnosticsHandler() {});
+  }
+
+  public static FeatureClassMapping fromSpecification(Path file, DiagnosticsHandler reporter)
+      throws FeatureMappingException {
+    FeatureClassMapping mapping = new FeatureClassMapping();
+    List<String> lines = null;
+    try {
+      lines = FileUtils.readAllLines(file);
+    } catch (IOException e) {
+      ExceptionDiagnostic error = new ExceptionDiagnostic(e, new SpecificationOrigin(file));
+      reporter.error(error);
+      throw new AbortException(error);
+    }
+    for (int i = 0; i < lines.size(); i++) {
+      String line = lines.get(i);
+      mapping.parseAndAdd(line, i);
+    }
+    return mapping;
+  }
+
+  public static class Internal {
+    private static List<String> getClassFileDescriptors(String jar, DiagnosticsHandler reporter) {
+      Path jarPath = Paths.get(jar);
+      try {
+        return new ArchiveClassFileProvider(jarPath).getClassDescriptors()
+            .stream()
+            .map(DescriptorUtils::descriptorToJavaType)
+            .collect(Collectors.toList());
+      } catch (IOException e) {
+        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(jarPath));
+        reporter.error(error);
+        throw new AbortException(error);
+      }
+    }
+
+    private static List<String> getNonClassFiles(String jar, DiagnosticsHandler reporter) {
+      try (ZipFile zipfile = new ZipFile(jar, StandardCharsets.UTF_8)) {
+          return zipfile.stream()
+              .filter(entry -> !ZipUtils.isClassFile(entry.getName()))
+              .map(ZipEntry::getName)
+              .collect(Collectors.toList());
+        } catch (IOException e) {
+        ExceptionDiagnostic error = new ExceptionDiagnostic(e, new JarFileOrigin(Paths.get(jar)));
+        reporter.error(error);
+        throw new AbortException(error);
+        }
+    }
+
+    public static FeatureClassMapping fromJarFiles(
+        List<FeatureJar> featureJars, List<String> baseJars, String baseName,
+        DiagnosticsHandler reporter)
+        throws FeatureMappingException {
+      FeatureClassMapping mapping = new FeatureClassMapping();
+      if (baseName != null) {
+        mapping.baseName = baseName;
+      }
+      for (FeatureJar featureJar : featureJars) {
+        for (String javaType : getClassFileDescriptors(featureJar.getJar(), reporter)) {
+          mapping.addMapping(javaType, featureJar.getOutputName());
+        }
+        for (String nonClass : getNonClassFiles(featureJar.getJar(), reporter)) {
+          mapping.addNonClassMapping(nonClass, featureJar.getOutputName());
+        }
+      }
+      for (String baseJar : baseJars) {
+        for (String javaType : getClassFileDescriptors(baseJar, reporter)) {
+          mapping.addBaseMapping(javaType);
+        }
+        for (String nonClass : getNonClassFiles(baseJar, reporter)) {
+          mapping.addBaseNonClassMapping(nonClass);
+        }
+      }
+      assert mapping.usesOnlyExactMappings;
+      return mapping;
+    }
+
+  }
+
+  private FeatureClassMapping() {}
+
+  public void addBaseMapping(String clazz) throws FeatureMappingException {
+    addMapping(clazz, baseName);
+  }
+
+  public void addBaseNonClassMapping(String name) {
+    addNonClassMapping(name, baseName);
+  }
+
+  public void addMapping(String clazz, String feature) throws FeatureMappingException {
+    addRule(clazz, feature, 0);
+  }
+
+  public void addNonClassMapping(String name, String feature) {
+    // If a non-class file is present in multiple features put the resource in the base.
+    parseNonClassRules.put(name, parseNonClassRules.containsKey(name) ? baseName : feature);
+  }
+
+  FeatureClassMapping(List<String> lines) throws FeatureMappingException {
+    for (int i = 0; i < lines.size(); i++) {
+      String line = lines.get(i);
+      parseAndAdd(line, i);
+    }
+  }
+
+  public String featureForClass(String clazz) {
+    if (usesOnlyExactMappings) {
+      return parsedRules.getOrDefault(clazz, baseName);
+    } else {
+      FeaturePredicate bestMatch = null;
+      for (FeaturePredicate mapping : mappings) {
+        if (mapping.match(clazz)) {
+          if (bestMatch == null || bestMatch.predicate.length() < mapping.predicate.length()) {
+            bestMatch = mapping;
+          }
+        }
+      }
+      if (bestMatch == null) {
+        return baseName;
+      }
+      return bestMatch.feature;
+    }
+  }
+
+  public String featureForNonClass(String nonClass) {
+    return parseNonClassRules.getOrDefault(nonClass, baseName);
+  }
+
+  private void parseAndAdd(String line, int lineNumber) throws FeatureMappingException {
+    if (line.startsWith(COMMENT)) {
+      return; // Ignore comments
+    }
+    if (line.isEmpty()) {
+      return; // Ignore blank lines
+    }
+
+    if (!line.contains(SEPARATOR)) {
+      error("Mapping lines must contain a " + SEPARATOR, lineNumber);
+    }
+    String[] values = line.split(SEPARATOR);
+    if (values.length != 2) {
+      error("Mapping lines can only contain one " + SEPARATOR, lineNumber);
+    }
+
+    String predicate = values[0];
+    String feature = values[1];
+    addRule(predicate, feature, lineNumber);
+  }
+
+  private void addRule(String predicate, String feature, int lineNumber)
+      throws FeatureMappingException {
+    if (parsedRules.containsKey(predicate)) {
+      if (!parsedRules.get(predicate).equals(feature)) {
+        error("Redefinition of predicate " + predicate + "not allowed", lineNumber);
+      }
+      return; // Already have this rule.
+    }
+    parsedRules.put(predicate, feature);
+    FeaturePredicate featurePredicate = new FeaturePredicate(predicate, feature);
+    mappings.add(featurePredicate);
+    usesOnlyExactMappings &= featurePredicate.isExactmapping();
+  }
+
+  private void error(String error, int line) throws FeatureMappingException {
+    throw new FeatureMappingException(
+        "Invalid mappings specification: " + error + "\n in file " + mappingFile + ":" + line);
+  }
+
+  @Keep
+  public static class FeatureMappingException extends Exception {
+    FeatureMappingException(String message) {
+      super(message);
+    }
+  }
+
+  /** A feature predicate can either be a wildcard or class predicate. */
+  private static class FeaturePredicate {
+    private static Pattern identifier = Pattern.compile("[A-Za-z_\\-][A-Za-z0-9_$\\-]*");
+    final String predicate;
+    final String feature;
+    final boolean isCatchAll;
+    // False implies class predicate.
+    final boolean isWildcard;
+
+    FeaturePredicate(String predicate, String feature) throws FeatureMappingException {
+      isWildcard = predicate.endsWith(".*");
+      isCatchAll =  predicate.equals("*");
+      if (isCatchAll) {
+        this.predicate = "";
+      } else if (isWildcard) {
+        String packageName = predicate.substring(0, predicate.length() - 2);
+        if (!DescriptorUtils.isValidJavaType(packageName)) {
+          throw new FeatureMappingException(packageName + " is not a valid identifier");
+        }
+        // Prefix of a fully-qualified class name, including a terminating dot.
+        this.predicate = predicate.substring(0, predicate.length() - 1);
+      } else {
+        if (!DescriptorUtils.isValidJavaType(predicate)) {
+          throw new FeatureMappingException(predicate + " is not a valid identifier");
+        }
+        this.predicate = predicate;
+      }
+      this.feature = feature;
+    }
+
+    boolean match(String className) {
+      if (isCatchAll) {
+        return true;
+      } else if (isWildcard) {
+        return className.startsWith(predicate);
+      } else {
+        return className.equals(predicate);
+      }
+    }
+
+    boolean isExactmapping() {
+      return !isWildcard && !isCatchAll;
+    }
+  }
+}
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 a2c6aae..2d401df 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1878,6 +1878,8 @@
     public boolean readInputStackMaps = true;
     public boolean disableStackMapVerification = false;
 
+    public boolean disableShortenLiveRanges = false;
+
     // Force each call of application read to dump its inputs to a file, which is subsequently
     // deleted. Useful to check that our dump functionality does not cause compilation failure.
     public boolean dumpAll = false;
diff --git a/src/main/keep.txt b/src/main/keep.txt
index deb8e6d..e376924 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -12,6 +12,7 @@
 -keep public class com.android.tools.r8.D8 { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.R8 { public static void main(java.lang.String[]); }
 -keep public class com.android.tools.r8.ExtractMarker { public static void main(java.lang.String[]); }
+-keep public class com.android.tools.r8.dexsplitter.DexSplitter { public static void main(java.lang.String[]); }
 
 -keep public class com.android.tools.r8.Version { public static final java.lang.String LABEL; }
 -keep public class com.android.tools.r8.Version { public static java.lang.String getVersionString(); }
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index c4d8683..52d84fc 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -249,4 +249,9 @@
     getState().setDiagnosticsLevelModifier(modifier);
     return self();
   }
+
+  public T allowStdoutMessages() {
+    // Default ignored.
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index b315027..ead3195 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -461,6 +461,7 @@
     return super.addLibraryProvider(provider);
   }
 
+  @Override
   public T allowStdoutMessages() {
     allowStdoutMessages = true;
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index 48e94f0..cf33980 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -93,6 +93,10 @@
     return withCfRuntime(CfRuntime.getDefaultCfRuntime().getVm());
   }
 
+  public TestParametersBuilder withDefaultRuntimes() {
+    return withDefaultDexRuntime().withDefaultCfRuntime();
+  }
+
   /** Add all available CF runtimes. */
   public TestParametersBuilder withCfRuntimes() {
     return withCfRuntimeFilter(vm -> true);
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 5445d03..a5c5302 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -332,6 +332,16 @@
     return self();
   }
 
+  public T addKeepEnumsRule() {
+    addKeepRules(
+        StringUtils.lines(
+            "-keepclassmembers enum * {",
+            "    public static **[] values();",
+            "    public static ** valueOf(java.lang.String);",
+            "}"));
+    return self();
+  }
+
   public T addPrintSeeds() {
     return addKeepRules("-printseeds");
   }
diff --git a/src/test/java/com/android/tools/r8/annotations/DefaultAnnotationTest.java b/src/test/java/com/android/tools/r8/annotations/DefaultAnnotationTest.java
new file mode 100644
index 0000000..a35b813
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/DefaultAnnotationTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2022, 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.annotations;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DefaultAnnotationTest extends TestBase {
+
+  static final String ANNO_NAME = typeName(MyAnnotation.class);
+  static final String EXPECTED = StringUtils.lines("@" + ANNO_NAME + "(hello=Hello World!)");
+  static final String EXPECTED_QUOTES =
+      StringUtils.lines("@" + ANNO_NAME + "(hello=\"Hello World!\")");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public DefaultAnnotationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNoDefinition() throws Exception {
+    runTest(b -> {});
+  }
+
+  @Test
+  public void testOnLibrary() throws Exception {
+    runTest(b -> b.addLibraryClassFileData(getDalvikAnnotationDefault()));
+  }
+
+  @Test
+  public void testOnProgram() throws Exception {
+    runTest(b -> b.addProgramClassFileData(getDalvikAnnotationDefault()));
+  }
+
+  private void runTest(ThrowableConsumer<R8FullTestBuilder> modification) throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(MyAnnotation.class, MyAnnotatedClass.class, TestClass.class)
+        .addDefaultRuntimeLibrary(parameters)
+        .apply(modification)
+        .addKeepAllAttributes()
+        .addKeepRules("-keep class * { *; }")
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(getExpected());
+  }
+
+  private String getExpected() {
+    if (parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK9)) {
+      return EXPECTED_QUOTES;
+    }
+    return EXPECTED;
+  }
+
+  private static byte[] getDalvikAnnotationDefault() throws Exception {
+    return transformer(WillBeDalvikAnnotationAnnotationDefault.class)
+        .setClassDescriptor("Ldalvik/annotation/AnnotationDefault;")
+        .transform();
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.ANNOTATION_TYPE)
+  @interface WillBeDalvikAnnotationAnnotationDefault {}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface MyAnnotation {
+    String hello() default "Hello World!";
+  }
+
+  @MyAnnotation
+  static class MyAnnotatedClass {}
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      for (Annotation annotation : MyAnnotatedClass.class.getAnnotations()) {
+        System.out.println(annotation.toString());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
index ccec2d8..ac4e8d8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import org.junit.Test;
@@ -24,6 +24,7 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("b: main", "true")
@@ -31,18 +32,20 @@
             codeInspector -> {
               ClassSubject aClassSubject = codeInspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
-
               assertThat(aClassSubject.init(), isPresent());
 
-              assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(codeInspector.clazz(B.class), isAbsent());
+              ClassSubject bClassSubject = codeInspector.clazz(B.class);
+              assertThat(bClassSubject, isPresent());
+              assertThat(bClassSubject.init(), isPresent());
             });
   }
 
   @NeverClassInline
+  @NoHorizontalClassMerging
   public static class A {}
 
   @NeverClassInline
+  @NoHorizontalClassMerging
   public static class B {
     public B(String v) {
       System.out.println("b: " + v);
@@ -50,7 +53,7 @@
   }
 
   public static class Main {
-    public static void main(String[] args) throws Exception {
+    public static void main(String[] args) {
       new B("main");
       System.out.println(A.class.toString().length() > 0);
     }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/CanonicalConstantInPreambleTest.java b/src/test/java/com/android/tools/r8/debuginfo/CanonicalConstantInPreambleTest.java
new file mode 100644
index 0000000..ab97b85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/CanonicalConstantInPreambleTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2022, 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.debuginfo;
+
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class CanonicalConstantInPreambleTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public CanonicalConstantInPreambleTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(getTransformedTestClass())
+        .apply(
+            b -> {
+              // Disable shorten live ranges to ensure the canonicalized constant remains in
+              // preamble.
+              if (b instanceof D8TestBuilder) {
+                ((D8TestBuilder) b)
+                    .addOptionsModification(o -> o.testing.disableShortenLiveRanges = true);
+              }
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private byte[] getTransformedTestClass() throws Exception {
+    return transformer(TestClass.class)
+        .stripFrames("foo")
+        .setVersion(CfVersion.V1_5)
+        .removeLineNumberTable(MethodPredicate.onName("foo"))
+        .transformMethodInsnInMethod(
+            "foo",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (!name.equals("nanoTime")) {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+                return;
+              }
+              // Jump over the preamble code.
+              Label postPreamble = new Label();
+              visitor.visitJumpInsn(Opcodes.GOTO, postPreamble);
+              visitor.visitLabel(postPreamble);
+              visitor.visitInsn(Opcodes.LCONST_0);
+              // Start an actual line after the preamble instructions.
+              Label firstLine = new Label();
+              visitor.visitLabel(firstLine);
+              visitor.visitLineNumber(42, firstLine);
+              // Create a trivial block that just has the active line and falls through.
+              // This is the block that we expect to have been removed, but due to the constant
+              // being moved up and having the wrong line it will fail.
+              Label trivialFallthrough = new Label();
+              visitor.visitJumpInsn(Opcodes.GOTO, trivialFallthrough);
+              visitor.visitLabel(trivialFallthrough);
+            })
+        .transform();
+  }
+
+  static class TestClass {
+
+    public static void foo(String arg) {
+      System.nanoTime(); // Will be replaced by transformed to preamble code.
+      // The constant '1' will be canonicalized and inserted in the preamble.
+      if (arg.equals("0") || arg.equals("1")) {
+        System.out.print("Hello, ");
+      }
+      if (!arg.equals("1")) {
+        System.out.println("world");
+      }
+    }
+
+    public static void main(String[] args) {
+      foo(String.valueOf(args.length));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NopLineRegression230337727Test.java b/src/test/java/com/android/tools/r8/debuginfo/NopLineRegression230337727Test.java
new file mode 100644
index 0000000..ccdac2d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/NopLineRegression230337727Test.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2022, 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.debuginfo;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class NopLineRegression230337727Test extends TestBase implements Opcodes {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  private final TestParameters parameters;
+
+  public NopLineRegression230337727Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClassFileData(getTestClassTransformed())
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(
+            inspector -> {
+              MethodSubject foo = inspector.clazz(TestClass.class).uniqueMethodWithName("foo");
+              assertTrue(
+                  foo.getLineNumberTable().getLines().toString(),
+                  foo.getLineNumberTable().getLines().contains(11));
+            });
+  }
+
+  static class TestClass {
+
+    public static boolean boo() {
+      return true;
+    }
+
+    public static void foo(List<Integer> args) {
+      System.nanoTime();
+      // Code added by transformer for the kotlin code:
+      //  if (boo()) {
+      //    l.forEach {}
+      //  }
+      //  if (boo()) {}
+    }
+
+    public static void main(String[] args) {
+      foo(Arrays.asList(1, 2, 3));
+    }
+  }
+
+  private static byte[] getTestClassTransformed() throws Exception {
+    // Code generated by kotlin but with frames, locals and checkNotNullParameter removed.
+    String holder = binaryName(TestClass.class);
+    return transformer(TestClass.class)
+        .setVersion(CfVersion.V1_5) // Avoid need of stack frames
+        .setMaxs(MethodPredicate.onName("foo"), 2, 7)
+        .transformMethodInsnInMethod(
+            "foo",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              Label label1 = new Label();
+              visitor.visitLabel(label1);
+              visitor.visitLineNumber(4, label1);
+              visitor.visitMethodInsn(INVOKESTATIC, holder, "boo", "()Z", false);
+              Label label2 = new Label();
+              visitor.visitJumpInsn(IFEQ, label2);
+              Label label3 = new Label();
+              visitor.visitLabel(label3);
+              visitor.visitLineNumber(5, label3);
+              visitor.visitVarInsn(ALOAD, 0);
+              visitor.visitTypeInsn(CHECKCAST, "java/lang/Iterable");
+              visitor.visitVarInsn(ASTORE, 1);
+              visitor.visitInsn(ICONST_0);
+              visitor.visitVarInsn(ISTORE, 2);
+              Label label5 = new Label();
+              visitor.visitLabel(label5);
+              visitor.visitLineNumber(10, label5);
+              visitor.visitVarInsn(ALOAD, 1);
+              visitor.visitMethodInsn(
+                  INVOKEINTERFACE,
+                  "java/lang/Iterable",
+                  "iterator",
+                  "()Ljava/util/Iterator;",
+                  true);
+              visitor.visitVarInsn(ASTORE, 3);
+              Label label6 = new Label();
+              visitor.visitLabel(label6);
+              visitor.visitVarInsn(ALOAD, 3);
+              visitor.visitMethodInsn(
+                  INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true);
+              Label label7 = new Label();
+              visitor.visitJumpInsn(IFEQ, label7);
+              visitor.visitVarInsn(ALOAD, 3);
+              visitor.visitMethodInsn(
+                  INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true);
+              visitor.visitVarInsn(ASTORE, 4);
+              visitor.visitVarInsn(ALOAD, 4);
+              visitor.visitTypeInsn(CHECKCAST, "java/lang/Number");
+              visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I", false);
+              visitor.visitVarInsn(ISTORE, 5);
+              visitor.visitInsn(ICONST_0);
+              visitor.visitVarInsn(ISTORE, 6);
+              Label label10 = new Label();
+              visitor.visitLabel(label10);
+              visitor.visitLineNumber(5, label10);
+              visitor.visitInsn(NOP);
+              visitor.visitJumpInsn(GOTO, label6);
+              visitor.visitLabel(label7);
+              visitor.visitLineNumber(11, label7);
+              visitor.visitInsn(NOP);
+              visitor.visitLabel(label2);
+              visitor.visitLineNumber(7, label2);
+              visitor.visitMethodInsn(INVOKESTATIC, holder, "boo", "()Z", false);
+              Label label12 = new Label();
+              visitor.visitJumpInsn(IFEQ, label12);
+              visitor.visitLabel(label12);
+              visitor.visitLineNumber(8, label12);
+              visitor.visitInsn(RETURN);
+            })
+        .transform();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
new file mode 100644
index 0000000..3c1716a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
@@ -0,0 +1,164 @@
+// Copyright (c) 2022, 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.LibraryDesugaringTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Function;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// Test of an API which is to be treated as a platform API. This is simulating an API which can be
+// implemented by an OEM in an on-device APK containing the DEX code. The program will probe for the
+// OEM class and if not found it will use a built-in program class implementing the interface.
+//
+// This is tested with and without library desugaring.
+
+// For context see b/229793269.
+@RunWith(Parameterized.class)
+public class PseudoPlatformApiTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters()
+            .withDexRuntimes()
+            .withApiLevelsStartingAtIncluding(AndroidApiLevel.P)
+            .build());
+  }
+
+  private Path androidJarAdditions() throws Exception {
+    return ZipBuilder.builder(temp.newFile("additions_android.jar").toPath())
+        .addFilesRelative(
+            ToolHelper.getClassPathForTests(),
+            ToolHelper.getClassFileForTestClass(PseudoPlatformInterface.class))
+        .build();
+  }
+
+  private Path androidJarAdditionsDex() throws Exception {
+    // Build the OEM DEX files without library desugaring enabled and using min API 28.
+    return testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addProgramClasses(PseudoPlatformInterface.class)
+        .setMinApi(AndroidApiLevel.B)
+        .compile()
+        .writeToZip();
+  }
+
+  private Path oemDex() throws Exception {
+    // Build the OEM DEX files without library desugaring enabled and using min API 28.
+    return testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        // This is equivalent to adding the PseudoPlatformInterface to android.jar.
+        .addLibraryClasses(PseudoPlatformInterface.class)
+        .addProgramClasses(OemClass.class)
+        .setMinApi(AndroidApiLevel.P)
+        .compile()
+        .writeToZip();
+  }
+
+  @Test
+  public void testD8WithoutLibraryDesugaringOemClassNotPresent() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addLibraryFiles(androidJarAdditions())
+        .addProgramClasses(ProgramClass.class)
+        .setMinApi(AndroidApiLevel.H_MR2)
+        .addRunClasspathFiles(androidJarAdditionsDex())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("DEFAULT-X", "Y-DEFAULT");
+  }
+
+  @Test
+  public void testD8WithoutLibraryDesugaringOemClassPresent() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addLibraryFiles(androidJarAdditions())
+        .addProgramClasses(ProgramClass.class)
+        .setMinApi(AndroidApiLevel.H_MR2)
+        .addRunClasspathFiles(androidJarAdditionsDex())
+        .addRunClasspathFiles(oemDex())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("OEM-X", "Y-OEM");
+  }
+
+  @Test
+  public void testD8WithLibraryDesugaringOemClassNotPresent() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addLibraryFiles(androidJarAdditions())
+        .addProgramClasses(ProgramClass.class)
+        .setMinApi(AndroidApiLevel.H_MR2)
+        // Enable library desugaring with an effective min API of 1.
+        .enableLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
+        .addRunClasspathFiles(androidJarAdditionsDex())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("DEFAULT-X", "Y-DEFAULT");
+  }
+
+  @Test
+  public void testD8WithLibraryDesugaringOemClassPresent() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+        .addLibraryFiles(androidJarAdditions())
+        .addProgramClasses(ProgramClass.class)
+        .setMinApi(AndroidApiLevel.H_MR2)
+        // Enable library desugaring with an effective min API of 1.
+        .enableLibraryDesugaring(LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
+        .addRunClasspathFiles(androidJarAdditionsDex())
+        .addRunClasspathFiles(oemDex())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutputLines("OEM-X", "Y-OEM");
+  }
+
+  interface PseudoPlatformInterface {
+    Function<String, String> api(Function<String, String> fn);
+  }
+
+  static class OemClass implements PseudoPlatformInterface {
+    public Function<String, String> api(Function<String, String> fn) {
+      System.out.println(fn.apply("OEM-"));
+      return (v) -> v + "-OEM";
+    }
+  }
+
+  static class ProgramClass implements PseudoPlatformInterface {
+    public Function<String, String> api(Function<String, String> fn) {
+      System.out.println(fn.apply("DEFAULT-"));
+      return (v) -> v + "-DEFAULT";
+    }
+
+    public static void main(String[] args) {
+      PseudoPlatformInterface pseudoPlatformInterface = null;
+      try {
+        Class<?> oemClass =
+            Class.forName(
+                "com.android.tools.r8.desugar.desugaredlibrary.PseudoPlatformApiTest$OemClass");
+        pseudoPlatformInterface =
+            (PseudoPlatformInterface) oemClass.getDeclaredConstructor().newInstance();
+      } catch (ClassNotFoundException
+          | NoSuchMethodException
+          | InstantiationException
+          | InvocationTargetException
+          | IllegalAccessException e) {
+        // Could not load OEM class use built-in program class.
+        pseudoPlatformInterface = new ProgramClass();
+      }
+      Function<String, String> fn = (v) -> v + "X";
+      System.out.println(pseudoPlatformInterface.api(fn).apply("Y"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
index 7b78157..effd169 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
@@ -16,15 +16,20 @@
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
 import java.nio.channels.SeekableByteChannel;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.PosixFileAttributeView;
 import java.nio.file.attribute.PosixFileAttributes;
 import java.nio.file.attribute.PosixFilePermission;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,6 +45,19 @@
           "String written: Hello World",
           "bytes read: 11",
           "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "null",
+          "true",
+          "unsupported");
+  private static final String EXPECTED_RESULT_24_26 =
+      StringUtils.lines(
+          "bytes written: 11",
+          "String written: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
+          "unsupported",
+          "unsupported",
           "null",
           "true",
           "unsupported");
@@ -49,6 +67,8 @@
           "String written: Hello World",
           "bytes read: 11",
           "String read: Hello World",
+          "bytes read: 11",
+          "String read: Hello World",
           "true",
           "true",
           "true");
@@ -69,8 +89,11 @@
   }
 
   private String getExpectedResult() {
-    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)
-        ? EXPECTED_RESULT_26
+    if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)) {
+      return EXPECTED_RESULT_26;
+    }
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
+        ? EXPECTED_RESULT_24_26
         : EXPECTED_RESULT;
   }
 
@@ -124,8 +147,12 @@
 
     public static void main(String[] args) throws Throwable {
       Path path = Files.createTempFile("example", ".txt");
-      readWrite(path);
+      readWriteThroughFilesAPI(path);
+      readThroughFileChannelAPI(path);
+      attributeAccess(path);
+    }
 
+    private static void attributeAccess(Path path) throws IOException {
       PosixFileAttributeView view = Files.getFileAttributeView(path, PosixFileAttributeView.class);
       if (view != null) {
         System.out.println(
@@ -154,7 +181,7 @@
       }
     }
 
-    private static void readWrite(Path path) throws IOException {
+    private static void readWriteThroughFilesAPI(Path path) throws IOException {
       try (SeekableByteChannel channel =
           Files.newByteChannel(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
         String toWrite = "Hello World";
@@ -173,5 +200,26 @@
         System.out.println("String read: " + new String(byteBuffer2.array()));
       }
     }
+
+    private static void readThroughFileChannelAPI(Path path) throws IOException {
+      try {
+        Set<OpenOption> openOptions = new HashSet<>();
+        openOptions.add(LinkOption.NOFOLLOW_LINKS);
+        try (FileChannel channel = FileChannel.open(path, openOptions)) {
+          String toWrite = "Hello World";
+
+          // Read the String toWrite from the channel.
+          channel.position(0);
+          ByteBuffer byteBuffer2 = ByteBuffer.allocate(toWrite.length());
+          int read = channel.read(byteBuffer2);
+          System.out.println("bytes read: " + read);
+          System.out.println("String read: " + new String(byteBuffer2.array()));
+        }
+      } catch (NoClassDefFoundError err) {
+        // TODO(b/222647019): FileChannel#open is not supported in between 24 and 26.
+        System.out.println("unsupported");
+        System.out.println("unsupported");
+      }
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564InvalidCode.java b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564InvalidCode.java
new file mode 100644
index 0000000..ddd3386
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564InvalidCode.java
@@ -0,0 +1,107 @@
+// Copyright (c) 2022, 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.desugar.jdk8272564;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class Jdk8272564InvalidCode extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimes()
+        .withDexRuntimes()
+        .withAllApiLevelsAlsoForCf()
+        .build();
+  }
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  private boolean isDefaultCfParameters() {
+    return parameters.isCfRuntime() && parameters.getApiLevel().equals(AndroidApiLevel.B);
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    assumeTrue(isDefaultCfParameters());
+    testForJvm()
+        .addProgramClasses(I.class)
+        .addProgramClassFileData(getTransformedClass())
+        .run(parameters.getRuntime(), A.class)
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || isDefaultCfParameters());
+    testForDesugaring(parameters)
+        .addProgramClasses(I.class)
+        .addProgramClassFileData(getTransformedClass())
+        .run(parameters.getRuntime(), A.class)
+        .applyIf(
+            parameters.isDexRuntime()
+                && parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V4_4_4),
+            r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || isDefaultCfParameters());
+    // The R8 lens code rewriter rewrites to the code prior to fixing JDK-8272564.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class)
+        .addProgramClassFileData(getTransformedClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(A.class)
+        .addOptionsModification(options -> options.testing.allowInvokeErrors = true)
+        .run(parameters.getRuntime(), A.class)
+        .applyIf(
+            parameters.isDexRuntime()
+                && parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V4_4_4),
+            r -> r.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+            r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
+  }
+
+  private byte[] getTransformedClass() throws Exception {
+    return transformer(A.class)
+        .transformMethodInsnInMethod(
+            "main",
+            ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+              continuation.visitMethodInsn(
+                  name.equals("hashCode") ? Opcodes.INVOKEINTERFACE : opcode,
+                  // javac generates java.lang.object as holder, change it to A.
+                  name.equals("hashCode") ? DescriptorUtils.getClassBinaryName(A.class) : owner,
+                  name,
+                  descriptor,
+                  name.equals("hashCode") || isInterface);
+            }))
+        .transform();
+  }
+
+  interface I {}
+
+  static class A implements I {
+
+    public static void main(String[] args) {
+      new A().hashCode();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
index 71e7df5..45a3be4 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterFieldTypeStrengtheningTest.java
@@ -47,6 +47,28 @@
   }
 
   @Test
+  public void testPropagationFromFeature() throws Exception {
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
+        r8TestCompileResult -> {
+          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
+          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
+        };
+    ProcessResult processResult =
+        testDexSplitter(
+            parameters,
+            ImmutableSet.of(BaseSuperClass.class),
+            ImmutableSet.of(FeatureClass.class),
+            FeatureClass.class,
+            EXPECTED,
+            ensureGetFromFeatureGone,
+            TestShrinkerBuilder::noMinification);
+    // We expect art to fail on this with the dex splitter, see b/122902374
+    assertNotEquals(processResult.exitCode, 0);
+    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
+  }
+
+  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
index 53af2b6..96220f8 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterInlineRegression.java
@@ -48,6 +48,31 @@
   }
 
   @Test
+  public void testInliningFromFeature() throws Exception {
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
+        r8TestCompileResult -> {
+          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
+          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
+        };
+    Consumer<R8FullTestBuilder> configurator =
+        r8FullTestBuilder ->
+            r8FullTestBuilder.enableNoVerticalClassMergingAnnotations().noMinification();
+    ProcessResult processResult =
+        testDexSplitter(
+            parameters,
+            ImmutableSet.of(BaseSuperClass.class),
+            ImmutableSet.of(FeatureClass.class),
+            FeatureClass.class,
+            EXPECTED,
+            ensureGetFromFeatureGone,
+            configurator);
+    // We expect art to fail on this with the dex splitter, see b/122902374
+    assertNotEquals(processResult.exitCode, 0);
+    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
+  }
+
+  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ThrowableConsumer<R8FullTestBuilder> configurator =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
index a2e1b1d..38d19da 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMemberValuePropagationRegression.java
@@ -46,6 +46,28 @@
   }
 
   @Test
+  public void testPropagationFromFeature() throws Exception {
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureGetFromFeatureGone =
+        r8TestCompileResult -> {
+          // Ensure that getFromFeature from FeatureClass is inlined into the run method.
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(FeatureClass.class);
+          assertThat(clazz.uniqueMethodWithName("getFromFeature"), not(isPresent()));
+        };
+    ProcessResult processResult =
+        testDexSplitter(
+            parameters,
+            ImmutableSet.of(BaseSuperClass.class),
+            ImmutableSet.of(FeatureClass.class, FeatureEnum.class),
+            FeatureClass.class,
+            EXPECTED,
+            ensureGetFromFeatureGone,
+            builder -> builder.enableInliningAnnotations().noMinification());
+    // We expect art to fail on this with the dex splitter, see b/122902374
+    assertNotEquals(processResult.exitCode, 0);
+    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
+  }
+
+  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ProcessResult processResult =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index a7d137f..71ddab8 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -48,6 +48,43 @@
   }
 
   @Test
+  public void testInliningFromFeature() throws Exception {
+    // Static merging is based on sorting order, we assert that we merged to the feature.
+    ThrowingConsumer<R8TestCompileResult, Exception> ensureMergingToFeature =
+        r8TestCompileResult -> {
+          ClassSubject clazz = r8TestCompileResult.inspector().clazz(AFeatureWithStatic.class);
+          assertEquals(2, clazz.allMethods().size());
+          assertThat(clazz.uniqueMethodWithName("getBase42"), isPresent());
+          assertThat(clazz.uniqueMethodWithName("getFoobar"), isPresent());
+        };
+    Consumer<R8FullTestBuilder> configurator =
+        r8FullTestBuilder ->
+            r8FullTestBuilder
+                .addOptionsModification(
+                    options ->
+                        options.testing.horizontalClassMergingTarget =
+                            (appView, candidates, target) -> candidates.iterator().next())
+                .addHorizontallyMergedClassesInspector(
+                    inspector ->
+                        inspector.assertMergedInto(BaseWithStatic.class, AFeatureWithStatic.class))
+                .enableNoVerticalClassMergingAnnotations()
+                .enableInliningAnnotations()
+                .noMinification();
+    ProcessResult processResult =
+        testDexSplitter(
+            parameters,
+            ImmutableSet.of(BaseClass.class, BaseWithStatic.class),
+            ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class),
+            FeatureClass.class,
+            EXPECTED,
+            ensureMergingToFeature,
+            configurator);
+    // We expect art to fail on this with the dex splitter.
+    assertNotEquals(processResult.exitCode, 0);
+    assertTrue(processResult.stderr.contains("NoClassDefFoundError"));
+  }
+
+  @Test
   public void testOnR8Splitter() throws IOException, CompilationFailedException {
     assumeTrue(parameters.isDexRuntime());
     ThrowableConsumer<R8FullTestBuilder> configurator =
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
new file mode 100644
index 0000000..e90e0ab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -0,0 +1,484 @@
+// Copyright (c) 2017, 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.dexsplitter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexSplitterHelper;
+import com.android.tools.r8.ExtractMarker;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.dex.Marker;
+import com.android.tools.r8.dexsplitter.DexSplitter.Options;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+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 DexSplitterTests {
+
+  private static final String CLASS_DIR =
+      ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR + "classes/dexsplitsample";
+  private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
+  private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
+  private static final String CLASS3_CLASS = CLASS_DIR + "/Class3.class";
+  private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class";
+  private static final String CLASS3_SYNTHETIC_CLASS = CLASS_DIR + "/Class3$1.class";
+  private static final String CLASS4_CLASS = CLASS_DIR + "/Class4.class";
+  private static final String CLASS4_LAMBDA_INTERFACE = CLASS_DIR + "/Class4$LambdaInterface.class";
+  private static final String TEXT_FILE =
+      ToolHelper.EXAMPLES_ANDROID_N_DIR + "dexsplitsample/TextFile.txt";
+
+
+  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  private Path createInput(boolean dontCreateMarkerInD8)
+      throws IOException, CompilationFailedException {
+    // Initial normal compile to create dex files.
+    Path inputZip = temp.newFolder().toPath().resolve("input.zip");
+    D8Command command =
+        D8Command.builder()
+            .setOutput(inputZip, OutputMode.DexIndexed)
+            .addProgramFiles(Paths.get(CLASS1_CLASS))
+            .addProgramFiles(Paths.get(CLASS2_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_SYNTHETIC_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
+            .build();
+
+    DexSplitterHelper.runD8ForTesting(command, dontCreateMarkerInD8);
+
+    return inputZip;
+  }
+
+  private void testMarker(boolean addMarkerToInput)
+      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
+    Path inputZip = createInput(!addMarkerToInput);
+
+    Path output = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(output);
+    Path splitSpec = createSplitSpec();
+
+    DexSplitter.main(
+        new String[] {
+          "--input", inputZip.toString(),
+          "--output", output.toString(),
+          "--feature-splits", splitSpec.toString()
+        });
+
+    Path base = output.resolve("base").resolve("classes.dex");
+    Path feature = output.resolve("feature1").resolve("classes.dex");
+
+    for (Path path : new Path[] {inputZip, base, feature}) {
+      Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(path);
+      assertEquals(addMarkerToInput ? 1 : 0, markers.size());
+    }
+  }
+
+  @Test
+  public void testMarkerPreserved()
+      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
+    testMarker(true);
+  }
+
+  @Test
+  public void testMarkerNotAdded()
+      throws CompilationFailedException, IOException, ResourceException, ExecutionException {
+    testMarker(false);
+  }
+
+  /**
+   * To test the file splitting we have 3 classes that we distribute like this: Class1 -> base
+   * Class2 -> feature1 Class3 -> feature1
+   *
+   * <p>Class1 and Class2 works independently of each other, but Class3 extends Class1, and
+   * therefore can't run without the base being loaded.
+   */
+  @Test
+  public void splitFilesNoObfuscation()
+      throws CompilationFailedException, IOException, FeatureMappingException {
+    noObfuscation(false);
+    noObfuscation(true);
+  }
+
+  private void noObfuscation(boolean useOptions)
+      throws IOException, CompilationFailedException, FeatureMappingException {
+    Path inputZip = createInput(false);
+    Path output = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(output);
+    Path splitSpec = createSplitSpec();
+
+    if (useOptions) {
+      Options options = new Options();
+      options.addInputArchive(inputZip.toString());
+      options.setFeatureSplitMapping(splitSpec.toString());
+      options.setOutput(output.toString());
+      DexSplitter.run(options);
+    } else {
+      DexSplitter.main(
+          new String[] {
+              "--input", inputZip.toString(),
+              "--output", output.toString(),
+              "--feature-splits", splitSpec.toString()
+          });
+    }
+
+    Path base = output.resolve("base").resolve("classes.dex");
+    Path feature = output.resolve("feature1").resolve("classes.dex");
+    validateUnobfuscatedOutput(base, feature);
+  }
+
+  private void validateUnobfuscatedOutput(Path base, Path feature) throws IOException {
+    // Both classes should still work if we give all dex files to the system.
+    for (String className : new String[] {"Class1", "Class2", "Class3"}) {
+      ArtCommandBuilder builder = new ArtCommandBuilder();
+      builder.appendClasspath(base.toString());
+      builder.appendClasspath(feature.toString());
+      builder.setMainClass("dexsplitsample." + className);
+      String out = ToolHelper.runArt(builder);
+      assertEquals(out, className + "\n");
+    }
+    // Individual classes should also work from the individual files.
+    String className = "Class1";
+    ArtCommandBuilder builder = new ArtCommandBuilder();
+    builder.appendClasspath(base.toString());
+    builder.setMainClass("dexsplitsample." + className);
+    String out = ToolHelper.runArt(builder);
+    assertEquals(out, className + "\n");
+
+    className = "Class2";
+    builder = new ArtCommandBuilder();
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass("dexsplitsample." + className);
+    out = ToolHelper.runArt(builder);
+    assertEquals(out, className + "\n");
+
+    className = "Class3";
+    builder = new ArtCommandBuilder();
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass("dexsplitsample." + className);
+    try {
+      ToolHelper.runArt(builder);
+      assertFalse(true);
+    } catch (AssertionError assertionError) {
+      // We expect this to throw since base is not in the path and Class3 depends on Class1
+    }
+
+    className = "Class4";
+    builder = new ArtCommandBuilder();
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass("dexsplitsample." + className);
+    try {
+      ToolHelper.runArt(builder);
+      assertFalse(true);
+    } catch (AssertionError assertionError) {
+      // We expect this to throw since base is not in the path and Class4 includes a lambda that
+      // would have been pushed to base.
+    }
+  }
+
+  private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
+    Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
+    try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
+      out.write(
+          "dexsplitsample.Class1:base\n"
+              + "dexsplitsample.Class2:feature1\n"
+              + "dexsplitsample.Class3:feature1\n"
+              + "dexsplitsample.Class4:feature1");
+    }
+    return splitSpec;
+  }
+
+  private List<String> getProguardConf() {
+    return ImmutableList.of(
+        "-keep class dexsplitsample.Class3 {",
+        "  public static void main(java.lang.String[]);",
+        "}");
+  }
+
+  @Test
+  public void splitFilesFromJar()
+      throws IOException, CompilationFailedException, FeatureMappingException {
+    for (boolean useOptions : new boolean[]{false, true}) {
+      for (boolean explicitBase: new boolean[]{false, true}) {
+        for (boolean renameBase: new boolean[]{false, true}) {
+          splitFromJars(useOptions, explicitBase, renameBase);
+        }
+      }
+    }
+  }
+
+  private void splitFromJars(boolean useOptions, boolean explicitBase, boolean renameBase)
+      throws IOException, CompilationFailedException, FeatureMappingException {
+    Path inputZip = createInput(false);
+    Path output = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(output);
+    Path baseJar = temp.getRoot().toPath().resolve("base.jar");
+    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
+    ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
+    String name = "dexsplitsample/Class1.class";
+    baseStream.putNextEntry(new ZipEntry(name));
+    baseStream.write(Files.readAllBytes(Paths.get(CLASS1_CLASS)));
+    baseStream.closeEntry();
+    baseStream.close();
+
+    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
+    name = "dexsplitsample/Class2.class";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS2_CLASS)));
+    featureStream.closeEntry();
+    name = "dexsplitsample/Class3.class";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_CLASS)));
+    featureStream.closeEntry();
+    name = "dexsplitsample/Class3$InnerClass.class";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_INNER_CLASS)));
+    featureStream.closeEntry();
+    name = "dexsplitsample/Class3$1.class";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_SYNTHETIC_CLASS)));
+    featureStream.closeEntry();
+    name = "dexsplitsample/Class4";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS4_CLASS)));
+    featureStream.closeEntry();
+    name = "dexsplitsample/Class4$LambdaInterface";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS4_LAMBDA_INTERFACE)));
+    featureStream.closeEntry();
+    featureStream.close();
+    // Make sure that we can pass in a name for the output.
+    String specificOutputName = "renamed";
+    if (useOptions) {
+      Options options = new Options();
+      options.addInputArchive(inputZip.toString());
+      options.setOutput(output.toString());
+      if (explicitBase) {
+        options.addBaseJar(baseJar.toString());
+      } else if (renameBase){
+        // Ensure that we can rename base (if people called a feature base)
+        options.setBaseOutputName("base_renamed");
+      }
+      options.addFeatureJar(featureJar.toString(), specificOutputName);
+      DexSplitter.run(options);
+    } else {
+      List<String> args = Lists.newArrayList(
+          "--input",
+          inputZip.toString(),
+          "--output",
+          output.toString(),
+          "--feature-jar",
+          featureJar.toString().concat(":").concat(specificOutputName));
+      if (explicitBase) {
+        args.add("--base-jar");
+        args.add(baseJar.toString());
+      } else if (renameBase) {
+        args.add("--base-output-name");
+        args.add("base_renamed");
+      }
+
+      DexSplitter.main(args.toArray(StringUtils.EMPTY_ARRAY));
+    }
+    String baseOutputName = explicitBase || !renameBase ? "base" : "base_renamed";
+    Path base = output.resolve(baseOutputName).resolve("classes.dex");
+    Path feature = output.resolve(specificOutputName).resolve("classes.dex");;
+    validateUnobfuscatedOutput(base, feature);
+  }
+
+  @Test
+  public void splitFilesObfuscation()
+      throws CompilationFailedException, IOException, ExecutionException {
+    // Initial normal compile to create dex files.
+    Path inputDex = temp.newFolder().toPath().resolve("input.zip");
+    Path proguardMap = temp.getRoot().toPath().resolve("proguard.map");
+
+    R8.run(
+        R8Command.builder()
+            .setOutput(inputDex, OutputMode.DexIndexed)
+            .addProgramFiles(Paths.get(CLASS1_CLASS))
+            .addProgramFiles(Paths.get(CLASS2_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_SYNTHETIC_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_CLASS))
+            .addProgramFiles(Paths.get(CLASS4_LAMBDA_INTERFACE))
+            .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+            .setProguardMapOutputPath(proguardMap)
+            .addProguardConfiguration(getProguardConf(), Origin.unknown())
+            .build());
+
+    Path outputDex = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(outputDex);
+    Path splitSpec = createSplitSpec();
+
+    DexSplitter.main(
+        new String[] {
+          "--input", inputDex.toString(),
+          "--output", outputDex.toString(),
+          "--feature-splits", splitSpec.toString(),
+          "--proguard-map", proguardMap.toString()
+        });
+
+    Path base = outputDex.resolve("base").resolve("classes.dex");
+    Path feature = outputDex.resolve("feature1").resolve("classes.dex");
+    String class3 = "dexsplitsample.Class3";
+    // We should still be able to run the Class3 which we kept, it has a call to the obfuscated
+    // class1 which is in base.
+    ArtCommandBuilder builder = new ArtCommandBuilder();
+    builder.appendClasspath(base.toString());
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass(class3);
+    String out = ToolHelper.runArt(builder);
+    assertEquals(out, "Class3\n");
+
+    // Class1 should not be in the feature, it should still be in base.
+    builder = new ArtCommandBuilder();
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass(class3);
+    try {
+      ToolHelper.runArt(builder);
+      assertFalse(true);
+    } catch (AssertionError assertionError) {
+      // We expect this to throw since base is not in the path and Class3 depends on Class1.
+    }
+
+    // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
+    // shaken away.
+    CodeInspector inspector = new CodeInspector(base, proguardMap);
+    ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
+    assertTrue(subject.isPresent());
+    assertTrue(subject.isRenamed());
+  }
+
+  @Test
+  public void splitNonClassFiles()
+      throws CompilationFailedException, IOException, FeatureMappingException {
+    Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
+    ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
+    String name = "dexsplitsample/TextFile.txt";
+    inputZipStream.putNextEntry(new ZipEntry(name));
+    byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
+    inputZipStream.write(fileBytes);
+    inputZipStream.closeEntry();
+    name = "dexsplitsample/TextFile2.txt";
+    inputZipStream.putNextEntry(new ZipEntry(name));
+    inputZipStream.write(fileBytes);
+    inputZipStream.write(fileBytes);
+    inputZipStream.closeEntry();
+    inputZipStream.close();
+    Path output = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(output);
+    Path baseJar = temp.getRoot().toPath().resolve("base.jar");
+    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
+    ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
+    name = "dexsplitsample/TextFile.txt";
+    baseStream.putNextEntry(new ZipEntry(name));
+    baseStream.write(fileBytes);
+    baseStream.closeEntry();
+    baseStream.close();
+    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
+    name = "dexsplitsample/TextFile2.txt";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(fileBytes);
+    featureStream.write(fileBytes);
+    featureStream.closeEntry();
+    featureStream.close();
+    Options options = new Options();
+    options.addInputArchive(inputZip.toString());
+    options.setOutput(output.toString());
+    options.addFeatureJar(baseJar.toString());
+    options.addFeatureJar(featureJar.toString());
+    options.setSplitNonClassResources(true);
+    DexSplitter.run(options);
+    Path baseDir = output.resolve("base");
+    Path featureDir = output.resolve("feature1");
+    byte[] contents = fileBytes;
+    byte[] contents2 = new byte[contents.length * 2];
+    System.arraycopy(contents, 0, contents2, 0, contents.length);
+    System.arraycopy(contents, 0, contents2, contents.length, contents.length);
+    Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
+    Path featureTextFile = featureDir.resolve("dexsplitsample/TextFile2.txt");
+    assert Files.exists(baseTextFile);
+    assert Files.exists(featureTextFile);
+    assert Arrays.equals(Files.readAllBytes(baseTextFile), contents);
+    assert Arrays.equals(Files.readAllBytes(featureTextFile), contents2);
+  }
+
+  @Test
+  public void splitDuplicateNonClassFiles()
+      throws IOException, CompilationFailedException, FeatureMappingException {
+    Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
+    ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
+    String name = "dexsplitsample/TextFile.txt";
+    inputZipStream.putNextEntry(new ZipEntry(name));
+    byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
+    inputZipStream.write(fileBytes);
+    inputZipStream.closeEntry();
+    inputZipStream.close();
+    Path output = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(output);
+    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
+    Path feature2Jar = temp.getRoot().toPath().resolve("feature2.jar");
+    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
+    name = "dexsplitsample/TextFile.txt";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(fileBytes);
+    featureStream.closeEntry();
+    featureStream.close();
+    ZipOutputStream feature2Stream = new ZipOutputStream(Files.newOutputStream(feature2Jar));
+    name = "dexsplitsample/TextFile.txt";
+    feature2Stream.putNextEntry(new ZipEntry(name));
+    feature2Stream.write(fileBytes);
+    feature2Stream.closeEntry();
+    feature2Stream.close();
+    Options options = new Options();
+    options.addInputArchive(inputZip.toString());
+    options.setOutput(output.toString());
+    options.addFeatureJar(feature2Jar.toString());
+    options.addFeatureJar(featureJar.toString());
+    options.setSplitNonClassResources(true);
+    DexSplitter.run(options);
+    Path baseDir = output.resolve("base");
+    Path feature1Dir = output.resolve("feature1");
+    Path feature2Dir = output.resolve("feature2");
+    Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
+    Path feature1TextFile = feature1Dir.resolve("dexsplitsample/TextFile2.txt");
+    Path feature2TextFile = feature2Dir.resolve("dexsplitsample/TextFile2.txt");
+    assert !Files.exists(feature1TextFile);
+    assert !Files.exists(feature2TextFile);
+    assert Files.exists(baseTextFile);
+    assert Arrays.equals(Files.readAllBytes(baseTextFile), fileBytes);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index bd7929d..05d6d3d 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -17,9 +17,12 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.dexsplitter.DexSplitter.Options;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import dalvik.system.PathClassLoader;
 import java.io.IOException;
@@ -28,7 +31,9 @@
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
@@ -150,6 +155,96 @@
         toRun, baseOutput, r8TestCompileResult.getFeature(0), parameters.getRuntime());
   }
 
+  // Compile the passed in classes plus RunInterface and SplitRunner using R8, then split
+  // based on the base/feature sets. toRun must implement the BaseRunInterface
+  <E extends Throwable> ProcessResult testDexSplitter(
+      TestParameters parameters,
+      Set<Class<?>> baseClasses,
+      Set<Class<?>> featureClasses,
+      Class<?> toRun,
+      String expectedOutput,
+      ThrowingConsumer<R8TestCompileResult, E> compileResultConsumer,
+      Consumer<R8FullTestBuilder> r8TestConfigurator)
+      throws Exception, E {
+    List<Class<?>> baseClassesWithRunner =
+        ImmutableList.<Class<?>>builder()
+            .add(RunInterface.class, SplitRunner.class)
+            .addAll(baseClasses)
+            .build();
+
+    Path baseJar = jarTestClasses(baseClassesWithRunner);
+    Path featureJar = jarTestClasses(featureClasses);
+
+    Path featureOnly =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(featureClasses)
+            .addClasspathClasses(baseClasses)
+            .addClasspathClasses(RunInterface.class)
+            .addKeepAllClassesRule()
+            .addInliningAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+    if (parameters.isDexRuntime()) {
+      // With D8 this should just work. We compile all of the base classes, then run with the
+      // feature loaded at runtime. Since there is no inlining/class merging we don't
+      // have any issues.
+      testForD8()
+          .addProgramClasses(SplitRunner.class, RunInterface.class)
+          .addProgramClasses(baseClasses)
+          .setMinApi(parameters.getApiLevel())
+          .compile()
+          .run(
+              parameters.getRuntime(),
+              SplitRunner.class,
+              toRun.getName(),
+              featureOnly.toAbsolutePath().toString())
+          .assertSuccessWithOutput(expectedOutput);
+    }
+
+    R8FullTestBuilder builder = testForR8(parameters.getBackend());
+    if (parameters.isCfRuntime()) {
+      // Compiling to jar we need to support the same way of loading code at runtime as
+      // android supports.
+      builder
+          .addProgramClasses(PathClassLoader.class)
+          .addKeepClassAndMembersRules(PathClassLoader.class);
+    }
+
+    R8FullTestBuilder r8FullTestBuilder =
+        builder
+            .setMinApi(parameters.getApiLevel())
+            .addProgramClasses(SplitRunner.class, RunInterface.class)
+            .addProgramClasses(baseClasses)
+            .addProgramClasses(featureClasses)
+            .addKeepMainRule(SplitRunner.class)
+            .addKeepClassRules(toRun);
+    r8TestConfigurator.accept(r8FullTestBuilder);
+    R8TestCompileResult r8TestCompileResult = r8FullTestBuilder.compile();
+    compileResultConsumer.accept(r8TestCompileResult);
+    Path fullFiles = r8TestCompileResult.writeToZip();
+
+    // Ensure that we can run the program as a unit (i.e., without splitting)
+    r8TestCompileResult
+        .run(parameters.getRuntime(), SplitRunner.class, toRun.getName())
+        .assertSuccessWithOutput(expectedOutput);
+
+    Path splitterOutput = temp.newFolder().toPath();
+    Path splitterBaseDexFile = splitterOutput.resolve("base").resolve("classes.dex");
+    Path splitterFeatureDexFile = splitterOutput.resolve("feature").resolve("classes.dex");
+
+    Options options = new Options();
+    options.setOutput(splitterOutput.toString());
+    options.addBaseJar(baseJar.toString());
+    options.addFeatureJar(featureJar.toString(), "feature");
+
+    options.addInputArchive(fullFiles.toString());
+    DexSplitter.run(options);
+
+    return runFeatureOnArt(
+        toRun, splitterBaseDexFile, splitterFeatureDexFile, parameters.getRuntime());
+  }
+
   ProcessResult runFeatureOnArt(
       Class toRun, Path splitterBaseDexFile, Path splitterFeatureDexFile, TestRuntime runtime)
       throws IOException {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 1427b24..9a0d0ce 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -142,6 +142,7 @@
         new IRCode(
             options,
             null,
+            Position.none(),
             blocks,
             new NumberGenerator(),
             basicBlockNumberGenerator,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 7bbc000..ea86918 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -100,6 +100,7 @@
         new IRCode(
             options,
             null,
+            Position.none(),
             blocks,
             new NumberGenerator(),
             basicBlockNumberGenerator,
@@ -188,6 +189,7 @@
         new IRCode(
             options,
             null,
+            Position.none(),
             blocks,
             new NumberGenerator(),
             basicBlockNumberGenerator,
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClass.kt b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClass.kt
new file mode 100644
index 0000000..0d3a7da
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClass.kt
@@ -0,0 +1,18 @@
+// Copyright (c) 2022, 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.reflection
+
+import kotlin.reflect.full.primaryConstructor
+import kotlin.time.Duration
+
+@JvmInline
+value class Value(private val rawValue: Int)
+
+data class Data(val value: Value)
+
+fun main() {
+  // See b/230369515 for context.
+  println(Data::class.primaryConstructor?.call(Value(0))?.value)
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java
new file mode 100644
index 0000000..3b9e678
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/ReflectiveConstructionWithInlineClassTest.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2022, 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.reflection;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.kotlin.metadata.KotlinMetadataTestBase;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// See b/230369515 for context.
+@RunWith(Parameterized.class)
+public class ReflectiveConstructionWithInlineClassTest extends KotlinTestBase {
+
+  private final TestParameters parameters;
+  private static final String EXPECTED_OUTPUT = "Value(rawValue=0)";
+  private static final String PKG =
+      ReflectiveConstructionWithInlineClassTest.class.getPackage().getName();
+  private static final String KOTLIN_FILE = "ReflectiveConstructionWithInlineClass";
+  private static final String MAIN_CLASS = PKG + "." + KOTLIN_FILE + "Kt";
+
+  private static final KotlinCompileMemoizer compiledJars =
+      getCompileMemoizer(
+          Paths.get(
+              ToolHelper.TESTS_DIR,
+              "java",
+              DescriptorUtils.getBinaryNameFromJavaType(PKG),
+              KOTLIN_FILE + FileUtils.KT_EXTENSION));
+
+  @Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        getKotlinTestParameters()
+            // Internal classes are supported from Kotlin 1.5.
+            .withCompilersStartingFromIncluding(KotlinCompilerVersion.KOTLINC_1_5_0)
+            .withOldCompilersStartingFrom(KotlinCompilerVersion.KOTLINC_1_5_0)
+            .withAllTargetVersions()
+            .build());
+  }
+
+  public ReflectiveConstructionWithInlineClassTest(
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCf() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion))
+        .addProgramFiles(kotlinc.getKotlinStdlibJar())
+        .addProgramFiles(kotlinc.getKotlinReflectJar())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion))
+        .addProgramFiles(kotlinc.getKotlinStdlibJar())
+        .addProgramFiles(kotlinc.getKotlinReflectJar())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(
+            options -> {
+              options.testing.enableD8ResourcesPassThrough = true;
+              options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+            })
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+
+  private R8FullTestBuilder configureR8(R8FullTestBuilder builder) {
+    return builder
+        .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion))
+        .addProgramFiles(kotlinc.getKotlinStdlibJar())
+        .addProgramFiles(kotlinc.getKotlinReflectJar())
+        .addProgramFiles(kotlinc.getKotlinAnnotationJar())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_CLASS)
+        .addKeepClassAndMembersRules(PKG + ".Data")
+        .addKeepEnumsRule()
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .allowDiagnosticMessages()
+        .allowUnusedDontWarnKotlinReflectJvmInternal();
+  }
+
+  @Test
+  public void testR8KeepDataClass() throws Exception {
+    configureR8(testForR8(parameters.getBackend()))
+        .compile()
+        .assertNoErrorMessages()
+        .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertFailureWithErrorThatThrows(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void testR8KeepDataClassAndInlineClass() throws Exception {
+    configureR8(testForR8(parameters.getBackend()))
+        .addKeepRules("-keep class " + PKG + ".Value { *; }")
+        .compile()
+        .assertNoErrorMessages()
+        .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 75b7dda..4d546d1 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -25,6 +25,8 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.dexsplitter.DexSplitter;
+import com.android.tools.r8.dexsplitter.DexSplitter.Options;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
@@ -253,6 +255,49 @@
   }
 
   @Test
+  public void everyThirdClassInMainWithDexSplitter() throws Throwable {
+    List<String> featureMappings = new ArrayList<>();
+    List<String> inFeatureMapping = new ArrayList<>();
+
+    ImmutableList.Builder<String> mainDexBuilder = ImmutableList.builder();
+    for (int i = 0; i < MANY_CLASSES.size(); i++) {
+      String clazz = MANY_CLASSES.get(i);
+      // Write the first 2 classes into the split.
+      if (i < 10) {
+        featureMappings.add(clazz + ":feature1");
+        inFeatureMapping.add(clazz);
+      }
+      if (i % 3 == 0) {
+        mainDexBuilder.add(clazz);
+      }
+    }
+    Path featureSplitMapping = temp.getRoot().toPath().resolve("splitmapping");
+    Path mainDexFile = temp.getRoot().toPath().resolve("maindex");
+    FileUtils.writeTextFile(featureSplitMapping, featureMappings);
+    List<String> mainDexList = mainDexBuilder.build();
+    FileUtils.writeTextFile(mainDexFile, ListUtils.map(mainDexList, MainDexListTests::typeToEntry));
+    Path output = temp.getRoot().toPath().resolve("split_output");
+    Files.createDirectories(output);
+    TestDiagnosticsHandler diagnosticsHandler = new TestDiagnosticsHandler();
+    Options options = new Options(diagnosticsHandler);
+    options.addInputArchive(getManyClassesMultiDexAppPath().toString());
+    options.setFeatureSplitMapping(featureSplitMapping.toString());
+    options.setOutput(output.toString());
+    options.setMainDexList(mainDexFile.toString());
+    DexSplitter.run(options);
+    assertEquals(0, diagnosticsHandler.numberOfErrorsAndWarnings());
+    Path baseDir = output.resolve("base");
+    CodeInspector inspector =
+        new CodeInspector(
+            AndroidApp.builder().addProgramFiles(baseDir.resolve("classes.dex")).build());
+    for (String clazz : mainDexList) {
+      if (!inspector.clazz(clazz).isPresent() && !inFeatureMapping.contains(clazz)) {
+        failedToFindClassInExpectedFile(baseDir, clazz);
+      }
+    }
+  }
+
+  @Test
   public void singleClassInMainDex() throws Throwable {
     ImmutableList<String> mainDex = ImmutableList.of(MANY_CLASSES.get(0));
     verifyMainDexContains(mainDex, getManyClassesSingleDexAppPath(), true);
@@ -969,7 +1014,7 @@
 
     @Override
     public Position getCanonicalDebugPositionAtOffset(int offset) {
-      throw new Unreachable();
+      return Position.none();
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/shaking/SoftPinDefaultInitializerWithConstClassInCompatModeTest.java b/src/test/java/com/android/tools/r8/shaking/SoftPinDefaultInitializerWithConstClassInCompatModeTest.java
new file mode 100644
index 0000000..d553900
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/SoftPinDefaultInitializerWithConstClassInCompatModeTest.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2022, 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.shaking;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SoftPinDefaultInitializerWithConstClassInCompatModeTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8Compat(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  static class Main {
+    public static void main(String[] args) throws Exception {
+      Class<?> clazz = System.currentTimeMillis() > 0 ? A.class : Object.class;
+      System.out.println(clazz.newInstance().toString());
+    }
+  }
+
+  static class A {
+
+    @Override
+    public String toString() {
+      return "A";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 889f7a7..9b4d076 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1204,23 +1204,22 @@
   }
 
   public ClassFileTransformer setMaxStackHeight(MethodPredicate predicate, int newMaxStack) {
-    return addMethodTransformer(
-        new MethodTransformer() {
-          @Override
-          public void visitMaxs(int maxStack, int maxLocals) {
-            super.visitMaxs(
-                MethodPredicate.testContext(predicate, getContext()) ? newMaxStack : maxStack,
-                maxLocals);
-          }
-        });
+    return setMaxs(predicate, newMaxStack, null);
   }
 
-  public ClassFileTransformer computeMaxs() {
+  public ClassFileTransformer setMaxs(
+      MethodPredicate predicate, Integer newMaxStack, Integer newMaxLocals) {
     return addMethodTransformer(
         new MethodTransformer() {
           @Override
           public void visitMaxs(int maxStack, int maxLocals) {
-            super.visitMaxs(maxStack, maxLocals);
+            if (MethodPredicate.testContext(predicate, getContext())) {
+              super.visitMaxs(
+                  newMaxStack != null ? newMaxStack : maxStack,
+                  newMaxLocals != null ? newMaxLocals : maxLocals);
+            } else {
+              super.visitMaxs(maxStack, maxLocals);
+            }
           }
         });
   }
diff --git a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
new file mode 100644
index 0000000..f41be5c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2016, 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.utils;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+
+import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+
+public class FeatureClassMappingTest {
+
+  @Test
+  public void testSimpleParse() throws Exception {
+
+    List<String> lines =
+        ImmutableList.of(
+            "# Comment, don't care about contents: even more ::::",
+            "com.google.base:base",
+            "", // Empty lines allowed
+            "com.google.feature1:feature1",
+            "com.google.feature1:feature1", // Multiple definitions of the same predicate allowed.
+            "com.google$:feature1",
+            "_com.google:feature21",
+            "com.google.*:feature32");
+    FeatureClassMapping mapping = new FeatureClassMapping(lines);
+  }
+
+  private void ensureThrowsMappingException(List<String> lines) {
+    try {
+      new FeatureClassMapping(lines);
+      assertFalse(true);
+    } catch (FeatureMappingException e) {
+      // Expected
+    }
+  }
+
+  private void ensureThrowsMappingException(String string) {
+    ensureThrowsMappingException(ImmutableList.of(string));
+  }
+
+  @Test
+  public void testLookup() throws Exception {
+    List<String> lines =
+        ImmutableList.of(
+            "com.google.Base:base",
+            "",
+            "com.google.Feature1:feature1",
+            "com.google.Feature1:feature1", // Multiple definitions of the same predicate allowed.
+            "com.google.different.*:feature1",
+            "_com.Google:feature21",
+            "com.google.bas42.*:feature42");
+    FeatureClassMapping mapping = new FeatureClassMapping(lines);
+    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
+    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "feature1");
+    assertEquals(mapping.featureForClass("com.google.different.Foobar"), "feature1");
+    assertEquals(mapping.featureForClass("com.google.Base"), "base");
+    assertEquals(mapping.featureForClass("com.google.bas42.foo.bar.bar.Foo"), "feature42");
+    assertEquals(mapping.featureForClass("com.google.bas42.f$o$o$.bar43.bar.Foo"), "feature42");
+    assertEquals(mapping.featureForClass("_com.Google"), "feature21");
+  }
+
+  @Test
+  public void testCatchAllWildcards() throws Exception {
+    testBaseWildcard(true);
+    testBaseWildcard(false);
+    testNonBaseCatchAll();
+  }
+
+  private void testNonBaseCatchAll() throws FeatureMappingException {
+    List<String> lines =
+        ImmutableList.of(
+            "com.google.Feature1:feature1",
+            "*:nonbase",
+            "com.strange.*:feature2");
+    FeatureClassMapping mapping = new FeatureClassMapping(lines);
+    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
+    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "nonbase");
+    assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
+    assertEquals(mapping.featureForClass("Feature1"), "nonbase");
+    assertEquals(mapping.featureForClass("a.b.z.A"), "nonbase");
+  }
+
+  private void testBaseWildcard(boolean explicitBase) throws FeatureMappingException {
+    List<String> lines =
+        ImmutableList.of(
+            "com.google.Feature1:feature1",
+            explicitBase ? "*:base" : "",
+            "com.strange.*:feature2");
+    FeatureClassMapping mapping = new FeatureClassMapping(lines);
+    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
+    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "base");
+    assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
+    assertEquals(mapping.featureForClass("com.stranger.Clazz"), "base");
+    assertEquals(mapping.featureForClass("Feature1"), "base");
+    assertEquals(mapping.featureForClass("a.b.z.A"), "base");
+  }
+
+  @Test
+  public void testWrongLines() throws Exception {
+    // No colon.
+    ensureThrowsMappingException("foo");
+    ensureThrowsMappingException("com.google.base");
+    // Two colons.
+    ensureThrowsMappingException(ImmutableList.of("a:b:c"));
+
+    // Empty identifier.
+    ensureThrowsMappingException("com..google:feature1");
+
+    // Ambiguous redefinition
+    ensureThrowsMappingException(
+        ImmutableList.of("com.google.foo:feature1", "com.google.foo:feature2"));
+    ensureThrowsMappingException(
+        ImmutableList.of("com.google.foo.*:feature1", "com.google.foo.*:feature2"));
+  }
+
+  @Test
+  public void testUsesOnlyExactMappings() throws Exception {
+    List<String> lines =
+        ImmutableList.of(
+            "com.pkg1.Clazz:feature1",
+            "com.pkg2.Clazz:feature2");
+    FeatureClassMapping mapping = new FeatureClassMapping(lines);
+
+    assertEquals(mapping.featureForClass("com.pkg1.Clazz"), "feature1");
+    assertEquals(mapping.featureForClass("com.pkg2.Clazz"), "feature2");
+    assertEquals(mapping.featureForClass("com.pkg1.Other"), mapping.baseName);
+  }
+}
diff --git a/tools/desugar_jdk_libs_repository.py b/tools/desugar_jdk_libs_repository.py
new file mode 100755
index 0000000..4cace6a
--- /dev/null
+++ b/tools/desugar_jdk_libs_repository.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022, 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.
+
+import argparse
+import os
+from os.path import join
+import shutil
+import subprocess
+import sys
+
+import utils
+import create_maven_release
+
+def parse_options():
+  result = argparse.ArgumentParser(
+    description='Local desugared library repository for JDK 11 legacy configuration')
+  result.add_argument('--repo-root', '--repo_root',
+                      default='/tmp/repo',
+                      metavar=('<path>'),
+                      help='Location for Maven repository.')
+  args = result.parse_args()
+  return args
+
+def jar_or_pom_file(unzip_dir, artifact, version, extension):
+  return join(
+      unzip_dir,
+      'com',
+      'android',
+      'tools',
+      artifact,
+      version,
+      artifact + '-' + version + '.' + extension)
+
+def jar_file(unzip_dir, artifact, version):
+  return jar_or_pom_file(unzip_dir, artifact, version, 'jar')
+
+def pom_file(unzip_dir, artifact, version):
+  return jar_or_pom_file(unzip_dir, artifact, version, 'pom')
+
+def main():
+  args = parse_options()
+  shutil.rmtree(args.repo_root, ignore_errors=True)
+  utils.makedirs_if_needed(args.repo_root)
+  with utils.TempDir() as tmp_dir:
+    version = utils.desugar_configuration_version(utils.DESUGAR_CONFIGURATION_JDK11_LEGACY)
+
+    # Checkout desugar_jdk_libs from GitHub
+    checkout_dir = join(tmp_dir, 'desugar_jdk_libs')
+    utils.RunCmd(['git', 'clone', 'https://github.com/google/desugar_jdk_libs.git', checkout_dir])
+    with utils.ChangedWorkingDirectory(checkout_dir):
+      with open('VERSION_JDK11.txt') as version_file:
+        version_file_lines = version_file.readlines()
+        for line in version_file_lines:
+          if not line.startswith('#'):
+            desugar_jdk_libs_version = line.strip()
+            if (version != desugar_jdk_libs_version):
+              raise Exception(
+                "Version mismatch. Configuration has version '"
+                + version
+                + "', and desugar_jdk_libs has version '"
+                + desugar_jdk_libs_version
+                + "'")
+
+    # Build desugared library configuration.
+    print("Building desugared library configuration " + version)
+    maven_zip = join(tmp_dir, 'desugar_configuration.zip')
+    create_maven_release.generate_desugar_configuration_maven_zip(
+      maven_zip,
+      utils.DESUGAR_CONFIGURATION_JDK11_LEGACY,
+      utils.DESUGAR_IMPLEMENTATION_JDK11)
+    unzip_dir = join(tmp_dir, 'desugar_jdk_libs_configuration_unzipped')
+    cmd = ['unzip', '-q', maven_zip, '-d', unzip_dir]
+    utils.RunCmd(cmd)
+    cmd = [
+      'mvn',
+      'deploy:deploy-file',
+      '-Durl=file:' + args.repo_root,
+      '-DrepositoryId=someName',
+      '-Dfile=' + jar_file(unzip_dir, 'desugar_jdk_libs_configuration', version),
+      '-DpomFile=' + pom_file(unzip_dir, 'desugar_jdk_libs_configuration', version)]
+    utils.RunCmd(cmd)
+
+    # Build desugared library.
+    print("Building desugared library " + version)
+    with utils.ChangedWorkingDirectory(checkout_dir):
+      utils.RunCmd([
+          'bazel',
+          '--bazelrc=/dev/null',
+          'build',
+          '--spawn_strategy=local',
+          '--verbose_failures',
+          ':maven_release_jdk11'])
+    unzip_dir = join(tmp_dir, 'desugar_jdk_libs_unzipped')
+    cmd = [
+        'unzip',
+        '-q',
+        join(checkout_dir, 'bazel-bin', 'desugar_jdk_libs_jdk11.zip'),
+        '-d',
+        unzip_dir]
+    utils.RunCmd(cmd)
+    cmd = [
+      'mvn',
+      'deploy:deploy-file',
+      '-Durl=file:' + args.repo_root,
+      '-DrepositoryId=someName',
+      '-Dfile=' + jar_file(unzip_dir, 'desugar_jdk_libs', version),
+      '-DpomFile=' + pom_file(unzip_dir, 'desugar_jdk_libs', version)]
+    utils.RunCmd(cmd)
+
+    print()
+    print("Artifacts:")
+    print("  com.android.tools:desugar_jdk_libs_configuration:" + version)
+    print("  com.android.tools:desugar_jdk_libs:" + version)
+    print()
+    print("deployed to Maven repository at " + args.repo_root + ".")
+    print()
+    print("Add")
+    print()
+    print("  maven {")
+    print("    url uri('file://" + args.repo_root + "')")
+    print("  }")
+    print()
+    print("to dependencyResolutionManagement.repositories in settings.gradle.")
+    print()
+    print("Remember to run gradle with --refresh-dependencies "
+      + "(./gradlew --refresh-dependencies ...) "
+      + "to ensure the cache is not used when the same version is published.")
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/dexsplitter.py b/tools/dexsplitter.py
new file mode 100755
index 0000000..a97d089
--- /dev/null
+++ b/tools/dexsplitter.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('dexsplitter', sys.argv[1:]))
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 45dd694..a726e1e 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -287,9 +287,6 @@
     print('Tests failed, you can print the logs by running(googlers only):')
     print('  tools/internal_test.py --print_logs %s' % git_hash)
     return 1
-  else:
-    print(' Test validation of archiving logs, see b/177799191')
-    print('  tools/internal_test.py --print_logs %s' % git_hash)
 
 def run_continuously():
   # If this script changes, we will restart ourselves
@@ -377,7 +374,7 @@
         stderr_fd.close()
       if stdout_fd:
         stdout_fd.close()
-      if exitcode != 0 or True:
+      if exitcode != 0:
         handle_output(archive, stderr, stdout, popen.returncode,
                       timed_out, ' '.join(cmd))
     return exitcode
diff --git a/tools/test.py b/tools/test.py
index 55dbac0..27c07de 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -225,8 +225,7 @@
       relative_file = absolute_file[len(upload_dir)+1:]
       if (should_upload(relative_file, absolute_file)):
         utils.upload_file_to_cloud_storage(absolute_file,
-                                           destination_dir + relative_file,
-                                           public_read=False)
+                                           destination_dir + relative_file)
   url = 'https://storage.googleapis.com/%s/%s/test/index.html' % (BUCKET, file_name)
   print('Test results available at: %s' % url)
 
diff --git a/tools/utils.py b/tools/utils.py
index 6bab90b..27a8feb 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -105,7 +105,7 @@
 
 def archive_file(name, gs_dir, src_file):
   gs_file = '%s/%s' % (gs_dir, name)
-  upload_file_to_cloud_storage(src_file, gs_file, public_read=False)
+  upload_file_to_cloud_storage(src_file, gs_file)
 
 def archive_value(name, gs_dir, value):
   with TempDir() as temp:
@@ -338,7 +338,7 @@
 def get_gsutil():
   return 'gsutil.py' if os.name != 'nt' else 'gsutil.py.bat'
 
-def upload_dir_to_cloud_storage(directory, destination, is_html=False, public_read=True):
+def upload_dir_to_cloud_storage(directory, destination, is_html=False):
   # Upload and make the content encoding right for viewing directly
   cmd = [get_gsutil(), '-m', 'cp']
   if is_html:
@@ -347,7 +347,7 @@
   PrintCmd(cmd)
   subprocess.check_call(cmd)
 
-def upload_file_to_cloud_storage(source, destination, public_read=True):
+def upload_file_to_cloud_storage(source, destination):
   cmd = [get_gsutil(), 'cp']
   cmd += [source, destination]
   PrintCmd(cmd)