Merge commit '0ff3415ebe3965d6ced3759dcf263f12fa2d0ec7' into dev-release

Change-Id: I5b6705a917c14971787e4b51dab0e227fe34b9f3
diff --git a/.gitignore b/.gitignore
index b62cc8f..8bd1412 100644
--- a/.gitignore
+++ b/.gitignore
@@ -113,8 +113,8 @@
 third_party/framework
 third_party/framework.tar.gz
 third_party/gmscore/*
-third_party/google/google-java-format/1.14.0
-third_party/google/google-java-format/1.14.0.tar.gz
+third_party/google/google-java-format/1.24.0
+third_party/google/google-java-format/1.24.0.tar.gz
 third_party/google/yapf/20231013
 third_party/google/yapf/20231013.tar.gz
 third_party/google-java-format
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 2312362..986d67c 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -19,16 +19,16 @@
     'third_party',
     'google',
     'google-java-format',
-    '1.14.0',
-    'google-java-format-1.14.0',
+    '1.24.0',
+    'google-java-format-1.24.0',
     'scripts',
     'google-java-format-diff.py')
 
 FMT_CMD_JDK17 = path.join('tools','google-java-format-diff.py')
 FMT_SHA1 = path.join(
-    'third_party', 'google', 'google-java-format', '1.14.0.tar.gz.sha1')
+    'third_party', 'google', 'google-java-format', '1.24.0.tar.gz.sha1')
 FMT_TGZ = path.join(
-    'third_party', 'google', 'google-java-format', '1.14.0.tar.gz')
+    'third_party', 'google', 'google-java-format', '1.24.0.tar.gz')
 
 def CheckDoNotMerge(input_api, output_api):
   for l in input_api.change.FullDescriptionText().splitlines():
@@ -73,7 +73,7 @@
     FMT_CMD,
     FMT_CMD_JDK17,
     '--google-java-format-jar',
-    'third_party/google/google-java-format/1.14.0/google-java-format-1.14.0-all-deps.jar'
+    'third_party/google/google-java-format/1.24.0/google-java-format-1.24.0-all-deps.jar'
 )))
   return results
 
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index 16fa3e8..ecffa3a 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -623,10 +623,10 @@
     "google-java-format",
     Paths.get("third_party", "google-java-format").toFile(),
     Paths.get("third_party", "google-java-format.tar.gz.sha1").toFile())
-  val googleJavaFormat_1_14 = ThirdPartyDependency(
-    "google-java-format-1.14",
-    Paths.get("third_party", "google", "google-java-format", "1.14.0").toFile(),
-    Paths.get("third_party", "google", "google-java-format", "1.14.0.tar.gz.sha1").toFile())
+  val googleJavaFormat_1_24 = ThirdPartyDependency(
+    "google-java-format-1.24",
+    Paths.get("third_party", "google", "google-java-format", "1.24.0").toFile(),
+    Paths.get("third_party", "google", "google-java-format", "1.24.0.tar.gz.sha1").toFile())
   val googleYapf_20231013 = ThirdPartyDependency(
     "google-yapf-20231013",
     Paths.get("third_party", "google", "yapf", "20231013").toFile(),
@@ -817,7 +817,8 @@
     "lib-v32",
     "lib-v33",
     "lib-v34",
-    "lib-v35"
+    "lib-v35",
+    "lib-v36"
   ).map(::getThirdPartyAndroidJar)
 }
 
@@ -893,6 +894,7 @@
     "kotlin-compiler-1.8.0",
     "kotlin-compiler-1.9.21",
     "kotlin-compiler-2.0.20",
+    "kotlin-compiler-2.1.0-Beta1",
     "kotlin-compiler-dev")
     .map { ThirdPartyDependency(
       it,
diff --git a/d8_r8/test/build.gradle.kts b/d8_r8/test/build.gradle.kts
index 80bfd4e..c9a5d63 100644
--- a/d8_r8/test/build.gradle.kts
+++ b/d8_r8/test/build.gradle.kts
@@ -229,23 +229,6 @@
             "r8lib.jar")
   }
 
-  val resourceshrinkercli by registering(Exec::class) {
-    dependsOn(r8WithRelocatedDepsTask)
-    val r8 = r8WithRelocatedDepsTask.getSingleOutputFile()
-    val keepTxt = getRoot().resolveAll("src", "main", "resourceshrinker_cli.txt")
-    val cliKeep = getRoot().resolveAll("src", "main", "keep_r8resourceshrinker.txt")
-    inputs.files(keepTxt, cliKeep)
-    val output = file(Paths.get("build", "libs", "resourceshrinkercli.jar"))
-    outputs.file(output)
-    commandLine = createR8LibCommandLine(
-      r8,
-      r8,
-      output,
-      listOf(keepTxt, cliKeep),
-      false,
-      false)
-  }
-
   fun Task.generateTestKeepRulesForR8Lib(
           r8LibJarProvider: TaskProvider<Exec>, artifactName: String) {
     dependsOn(r8LibJarProvider)
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 489f9e0..f2197c4 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -1526,6 +1526,11 @@
         .applyIf(
             featureSplitConfiguration != null,
             b -> b.setIsolatedSplits(featureSplitConfiguration.isIsolatedSplitsEnabled()))
+        .applyIf(
+            resourceShrinkerConfiguration != null,
+            b ->
+                b.setOptimizedResourceShrinking(
+                    resourceShrinkerConfiguration.isOptimizedShrinking()))
         .build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 0081d41..64dee4d 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -25,7 +26,6 @@
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
 
 public class LoadStoreHelper {
@@ -34,8 +34,12 @@
   private final IRCode code;
   private final TypeVerificationHelper typesHelper;
 
-  private Map<Value, ConstInstruction> clonableConstants = null;
-  private ListIterator<BasicBlock> blockIterator = null;
+  private Map<Value, ConstInstruction> clonableConstants;
+
+  // The active block and instruction iterator used by the pass. These are stored in fields to avoid
+  // needing to pass them back and forth through Instruction#insertLoadAndStores.
+  private BasicBlockIterator blockIterator;
+  private InstructionListIterator instructionIterator;
 
   public LoadStoreHelper(AppView<?> appView, IRCode code, TypeVerificationHelper typesHelper) {
     this.appView = appView;
@@ -99,9 +103,11 @@
     clonableConstants = new IdentityHashMap<>();
     blockIterator = code.listIterator();
     while (blockIterator.hasNext()) {
-      InstructionListIterator it = blockIterator.next().listIterator();
-      while (it.hasNext()) {
-        it.next().insertLoadAndStores(it, this);
+      BasicBlock block = blockIterator.next();
+      instructionIterator = block.listIterator();
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        instruction.insertLoadAndStores(this);
       }
       clonableConstants.clear();
     }
@@ -128,10 +134,10 @@
               moves.add(new PhiMove(phi, value));
             }
           }
-          InstructionListIterator it = pred.listIterator(pred.getInstructions().size());
-          Instruction exit = it.previous();
+          instructionIterator = pred.listIterator(pred.getInstructions().size());
+          Instruction exit = instructionIterator.previous();
           assert pred.exit() == exit;
-          movePhis(moves, it, exit.getPosition());
+          movePhis(moves, exit.getPosition());
         }
         allocator.addToLiveAtEntrySet(block, block.getPhis());
       }
@@ -147,9 +153,9 @@
     return StackValue.create(typesHelper.createInitializedType(type), height, appView);
   }
 
-  public void loadInValues(Instruction instruction, InstructionListIterator it) {
+  public void loadInValues(Instruction instruction) {
     int topOfStack = 0;
-    it.previous();
+    instructionIterator.previous();
     for (int i = 0; i < instruction.inValues().size(); i++) {
       Value value = instruction.inValues().get(i);
       StackValue stackValue = createStackValue(value, topOfStack++);
@@ -158,26 +164,25 @@
       if (constInstruction != null) {
         ConstInstruction clonedConstInstruction =
             ConstInstruction.copyOf(stackValue, constInstruction);
-        add(clonedConstInstruction, instruction, it);
+        add(clonedConstInstruction, instruction);
       } else {
-        add(load(stackValue, value), instruction, it);
+        add(load(stackValue, value), instruction);
       }
       instruction.replaceValue(i, stackValue);
     }
-    it.next();
+    instructionIterator.next();
   }
 
-  public void storeOrPopOutValue(
-      DexType type, Instruction instruction, InstructionListIterator it) {
+  public void storeOrPopOutValue(DexType type, Instruction instruction) {
     if (instruction.hasOutValue()) {
       assert instruction.outValue().isUsed();
-      storeOutValue(instruction, it);
+      storeOutValue(instruction);
     } else {
-      popOutType(type, instruction, it);
+      popOutType(type, instruction);
     }
   }
 
-  public void storeOutValue(Instruction instruction, InstructionListIterator it) {
+  public void storeOutValue(Instruction instruction) {
     assert instruction.hasOutValue();
     assert !(instruction.outValue() instanceof StackValue);
     if (instruction.isConstInstruction()) {
@@ -187,7 +192,7 @@
             || constInstruction.outValue().numberOfUsers() == 1;
         clonableConstants.put(instruction.outValue(), constInstruction);
         instruction.outValue().clearUsers();
-        it.removeOrReplaceByDebugLocalRead();
+        instructionIterator.removeOrReplaceByDebugLocalRead();
         return;
       }
       assert instruction.outValue().isUsed()
@@ -195,7 +200,7 @@
           : "Expected instruction to be removed: " + instruction;
     }
     if (!instruction.outValue().isUsed()) {
-      popOutValue(instruction.outValue(), instruction, it);
+      popOutValue(instruction.outValue(), instruction);
       return;
     }
     StackValue newOutValue = createStackValue(instruction.outValue(), 0);
@@ -208,41 +213,40 @@
     // instruction is throwing, the action should be moved to a new block - otherwise, the store
     // should be inserted and the remaining instructions should be moved along with the handlers to
     // the new block.
-    boolean hasCatchHandlers = instruction.getBlock().hasCatchHandlers();
+    boolean hasCatchHandlers = storeBlock.hasCatchHandlers();
     if (hasCatchHandlers && instruction.instructionTypeCanThrow()) {
-      storeBlock = it.split(this.code, this.blockIterator);
-      it = storeBlock.listIterator();
+      storeBlock = instructionIterator.split(this.code, this.blockIterator);
+      instructionIterator = storeBlock.listIterator();
     }
-    add(store, instruction.getPosition(), it);
+    add(store, instruction.getPosition());
     if (hasCatchHandlers && !instruction.instructionTypeCanThrow()) {
-      splitAfterStoredOutValue(it);
+      splitAfterStoredOutValue();
     }
   }
 
   // DebugLocalWrite encodes a store and it needs to consistently split out the catch range after
   // its store.
-  public void splitAfterStoredOutValue(InstructionListIterator it) {
-    it.split(this.code, this.blockIterator);
-    this.blockIterator.previous();
+  public void splitAfterStoredOutValue() {
+    instructionIterator.split(this.code, this.blockIterator);
+    blockIterator.previous();
   }
 
-  public void popOutType(DexType type, Instruction instruction, InstructionListIterator it) {
-    popOutValue(createStackValue(type, 0), instruction, it);
+  public void popOutType(DexType type, Instruction instruction) {
+    popOutValue(createStackValue(type, 0), instruction);
   }
 
-  private void popOutValue(Value value, Instruction instruction, InstructionListIterator it) {
-    popOutValue(createStackValue(value, 0), instruction, it);
+  private void popOutValue(Value value, Instruction instruction) {
+    popOutValue(createStackValue(value, 0), instruction);
   }
 
-  private void popOutValue(
-      StackValue newOutValue, Instruction instruction, InstructionListIterator it) {
+  private void popOutValue(StackValue newOutValue, Instruction instruction) {
     BasicBlock insertBlock = instruction.getBlock();
     if (insertBlock.hasCatchHandlers() && instruction.instructionTypeCanThrow()) {
-      insertBlock = it.split(this.code, this.blockIterator);
-      it = insertBlock.listIterator();
+      insertBlock = instructionIterator.split(this.code, this.blockIterator);
+      instructionIterator = insertBlock.listIterator();
     }
     instruction.swapOutValue(newOutValue);
-    add(new Pop(newOutValue), instruction.getPosition(), it);
+    add(new Pop(newOutValue), instruction.getPosition());
   }
 
   private static class PhiMove {
@@ -255,13 +259,13 @@
     }
   }
 
-  private void movePhis(List<PhiMove> moves, InstructionListIterator it, Position position) {
+  private void movePhis(List<PhiMove> moves, Position position) {
     // TODO(zerny): Accounting for non-interfering phis would lower the max stack size.
     int topOfStack = 0;
     List<StackValue> temps = new ArrayList<>(moves.size());
     for (PhiMove move : moves) {
       StackValue tmp = createStackValue(move.phi, topOfStack++);
-      add(load(tmp, move.operand), position, it);
+      add(load(tmp, move.operand), position);
       temps.add(tmp);
       move.operand.removePhiUser(move.phi);
     }
@@ -269,7 +273,7 @@
       PhiMove move = moves.get(i);
       StackValue tmp = temps.get(i);
       FixedLocalValue out = new FixedLocalValue(move.phi);
-      add(new Store(out, tmp), position, it);
+      add(new Store(out, tmp), position);
       move.phi.replaceUsers(out);
     }
   }
@@ -294,14 +298,12 @@
     return new Load(stackValue, value);
   }
 
-  private static void add(
-      Instruction newInstruction, Instruction existingInstruction, InstructionListIterator it) {
-    add(newInstruction, existingInstruction.getPosition(), it);
+  private void add(Instruction newInstruction, Instruction existingInstruction) {
+    add(newInstruction, existingInstruction.getPosition());
   }
 
-  private static void add(
-      Instruction newInstruction, Position position, InstructionListIterator it) {
+  private void add(Instruction newInstruction, Position position) {
     newInstruction.setPosition(position);
-    it.add(newInstruction);
+    instructionIterator.add(newInstruction);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 985fbc9..9ee017e 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -220,6 +220,10 @@
     return false;
   }
 
+  public CfStaticFieldRead asStaticFieldGet() {
+    return null;
+  }
+
   public boolean isFieldPut() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
index f7ae0ff..12467da 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
@@ -48,6 +48,11 @@
   }
 
   @Override
+  public CfStaticFieldRead asStaticFieldGet() {
+    return this;
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerStaticFieldReadInstruction(this);
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index f2beb28..052a6de 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -47,6 +47,7 @@
   private static final String INTERMEDIATE_KEY = "intermediate";
   private static final String INCLUDE_DATA_RESOURCES_KEY = "include-data-resources";
   private static final String ISOLATED_SPLITS_KEY = "isolated-splits";
+  private static final String OPTIMIZED_RESOURCE_SHRINKING = "optimized-resource-shrinking";
   private static final String TREE_SHAKING_KEY = "tree-shaking";
   private static final String MINIFICATION_KEY = "minification";
   private static final String FORCE_PROGUARD_COMPATIBILITY_KEY = "force-proguard-compatibility";
@@ -66,6 +67,7 @@
   private final Optional<Boolean> intermediate;
   private final Optional<Boolean> includeDataResources;
   private final Optional<Boolean> isolatedSplits;
+  private final Optional<Boolean> optimizedResourceShrinking;
   private final Optional<Boolean> treeShaking;
   private final Optional<Boolean> minification;
   private final Optional<Boolean> forceProguardCompatibility;
@@ -113,7 +115,8 @@
       Map<String, String> systemProperties,
       boolean dumpInputToFile,
       String traceReferencesConsumer,
-      AndroidResourceProvider androidResourceProvider) {
+      AndroidResourceProvider androidResourceProvider,
+      Optional<Boolean> optimizedResourceShrinking) {
     this.backend = backend;
     this.tool = tool;
     this.compilationMode = compilationMode;
@@ -125,6 +128,7 @@
     this.intermediate = intermediate;
     this.includeDataResources = includeDataResources;
     this.isolatedSplits = isolatedSplits;
+    this.optimizedResourceShrinking = optimizedResourceShrinking;
     this.treeShaking = treeShaking;
     this.minification = minification;
     this.forceProguardCompatibility = forceProguardCompatibility;
@@ -173,9 +177,12 @@
       addOptionalDumpEntry(buildProperties, INTERMEDIATE_KEY, intermediate);
       addOptionalDumpEntry(buildProperties, INCLUDE_DATA_RESOURCES_KEY, includeDataResources);
       addOptionalDumpEntry(buildProperties, ISOLATED_SPLITS_KEY, isolatedSplits);
+      addOptionalDumpEntry(buildProperties, OPTIMIZED_RESOURCE_SHRINKING, isolatedSplits);
       addOptionalDumpEntry(buildProperties, TREE_SHAKING_KEY, treeShaking);
       addOptionalDumpEntry(
           buildProperties, FORCE_PROGUARD_COMPATIBILITY_KEY, forceProguardCompatibility);
+      addOptionalDumpEntry(
+          buildProperties, OPTIMIZED_RESOURCE_SHRINKING, optimizedResourceShrinking);
     } else {
       addDumpEntry(buildProperties, TRACE_REFERENCES_CONSUMER, traceReferencesConsumer);
     }
@@ -251,6 +258,9 @@
       case ISOLATED_SPLITS_KEY:
         builder.setIsolatedSplits(Boolean.parseBoolean(value));
         return;
+      case OPTIMIZED_RESOURCE_SHRINKING:
+        builder.setOptimizedResourceShrinking(Boolean.parseBoolean(value));
+        return;
       case TREE_SHAKING_KEY:
         builder.setTreeShaking(Boolean.parseBoolean(value));
         return;
@@ -391,6 +401,7 @@
     private Collection<ArtProfileProvider> artProfileProviders;
     private Collection<StartupProfileProvider> startupProfileProviders;
     private AndroidResourceProvider androidResourceProvider;
+    private Optional<Boolean> optimizedResourceShrinking = Optional.empty();
 
     private boolean enableMissingLibraryApiModeling = false;
     private boolean isAndroidPlatformBuild = false;
@@ -472,6 +483,10 @@
       return this;
     }
 
+    public void setOptimizedResourceShrinking(boolean optimizedResourceShrinking) {
+      this.optimizedResourceShrinking = Optional.of(optimizedResourceShrinking);
+    }
+
     public Builder setForceProguardCompatibility(boolean forceProguardCompatibility) {
       this.forceProguardCompatibility = Optional.of(forceProguardCompatibility);
       return this;
@@ -581,7 +596,8 @@
           systemProperties,
           dumpInputToFile,
           traceReferencesConsumer,
-          androidResourceProvider);
+          androidResourceProvider,
+          optimizedResourceShrinking);
     }
 
     public AndroidResourceProvider getAndroidResourceProvider() {
diff --git a/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java
index f3b5171..93ac3df 100644
--- a/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/errors/IgnoredBackportMethodDiagnostic.java
@@ -3,22 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.errors;
 
-import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 
 @KeepForApi
 public class IgnoredBackportMethodDiagnostic implements DesugarDiagnostic {
 
-  private final DexMethod backport;
+  private final DexMember<?, ?> backport;
   private final Origin origin;
   private final Position position;
   private final int minApiLevel;
 
   public IgnoredBackportMethodDiagnostic(
-      DexMethod backport, Origin origin, Position position, int minApiLevel) {
+      DexMember<?, ?> backport, Origin origin, Position position, int minApiLevel) {
     this.backport = backport;
     this.origin = origin;
     this.position = position;
@@ -26,7 +27,11 @@
   }
 
   public MethodReference getIgnoredBackportMethod() {
-    return backport.asMethodReference();
+    return backport.isDexMethod() ? backport.asDexMethod().asMethodReference() : null;
+  }
+
+  public FieldReference getIgnoredBackportField() {
+    return backport.isDexField() ? backport.asDexField().asFieldReference() : null;
   }
 
   public int getConfiguredMinApiLevel() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
index 1dc176f..1f1eff4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
@@ -61,7 +61,7 @@
     return this;
   }
 
-  public final boolean isFinalOrEffectivelyFinal(AppView<?> appView) {
+  public boolean isFinalOrEffectivelyFinal(AppView<?> appView) {
     return getAccessFlags().isFinal()
         || (appView.hasLiveness() && isEffectivelyFinal(appView.withLiveness()));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index a79ae60..cea12d6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -773,6 +773,7 @@
           androidSystemOsConstantsMembers,
           androidViewViewMembers,
           // java.**
+          enumMembers,
           javaIoFileMembers,
           javaMathBigIntegerMembers,
           javaNioByteOrderMembers,
@@ -1215,6 +1216,8 @@
     public final DexField RELEASE = createField(androidOsBuildVersionType, stringType, "RELEASE");
     public final DexField SDK = createField(androidOsBuildVersionType, stringType, "SDK");
     public final DexField SDK_INT = createField(androidOsBuildVersionType, intType, "SDK_INT");
+    public final DexField SDK_INT_FULL =
+        createField(androidOsBuildVersionType, intType, "SDK_INT_FULL");
     public final DexField SECURITY_PATCH =
         createField(androidOsBuildVersionType, stringType, "SECURITY_PATCH");
 
@@ -1339,6 +1342,8 @@
         createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
     public final DexMethod toString =
         createMethod(boxedBooleanType, createProto(stringType), "toString");
+    public final DexMethod staticHashCode =
+        createMethod(boxedBooleanType, createProto(intType, booleanType), "hashCode");
 
     private BooleanMembers() {}
 
@@ -1403,6 +1408,8 @@
         createMethod(boxedFloatType, createProto(stringType), "toString");
     public final DexMethod valueOf =
         createMethod(boxedFloatType, createProto(boxedFloatType, floatType), "valueOf");
+    public final DexMethod staticHashCode =
+        createMethod(boxedFloatType, createProto(intType, floatType), "hashCode");
 
     private FloatMembers() {}
 
@@ -1606,6 +1613,8 @@
         createMethod(boxedLongType, createProto(stringType), "toString");
     public final DexMethod valueOf =
         createMethod(boxedLongType, createProto(boxedLongType, longType), "valueOf");
+    public final DexMethod staticHashCode =
+        createMethod(boxedLongType, createProto(intType, longType), "hashCode");
 
     private LongMembers() {}
 
@@ -1632,6 +1641,8 @@
         createMethod(boxedDoubleType, createProto(stringType), "toString");
     public final DexMethod valueOf =
         createMethod(boxedDoubleType, createProto(boxedDoubleType, doubleType), "valueOf");
+    public final DexMethod staticHashCode =
+        createMethod(boxedDoubleType, createProto(intType, doubleType), "hashCode");
 
     private DoubleMembers() {}
 
@@ -2114,7 +2125,7 @@
     private JavaIoPrintStreamMembers() {}
   }
 
-  public class EnumMembers {
+  public class EnumMembers extends LibraryMembers {
 
     public final DexField nameField = createField(enumType, stringType, "name");
     public final DexField ordinalField = createField(enumType, intType, "ordinal");
@@ -2169,6 +2180,12 @@
       fn.accept(ordinalField);
     }
 
+    @Override
+    public void forEachFinalField(Consumer<DexField> fn) {
+      fn.accept(nameField);
+      fn.accept(ordinalField);
+    }
+
     @SuppressWarnings("ReferenceEquality")
     public boolean isNameOrOrdinalField(DexField field) {
       return field == nameField || field == ordinalField;
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryField.java b/src/main/java/com/android/tools/r8/graph/LibraryField.java
index 49ed565..8221f8d 100644
--- a/src/main/java/com/android/tools/r8/graph/LibraryField.java
+++ b/src/main/java/com/android/tools/r8/graph/LibraryField.java
@@ -32,4 +32,9 @@
   public boolean isLibraryMember() {
     return true;
   }
+
+  @Override
+  public boolean isFinalOrEffectivelyFinal(AppView<?> appView) {
+    return appView.libraryMethodOptimizer().isFinalLibraryField(getDefinition());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
index 020db76..3a544aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
@@ -77,7 +77,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
index cd4d6a4..8bfdfcf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
@@ -73,7 +73,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Nothing to do.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index a5cf0e4..3f99c84 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -74,7 +74,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index bebe521..d759235 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -133,7 +133,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Arguments are defined by locals so nothing to load or store.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 1f6ea43..865d83b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -176,9 +176,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index ae761d1..ba579de 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -111,9 +111,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 9419bef..c96f6f6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -238,8 +238,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index 279d1b9..e29df9f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -214,7 +214,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable(ERROR_MESSAGE);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 9bc7905..7afef24 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1175,7 +1175,7 @@
   public boolean consistentCatchHandlers() {
     // Check that catch handlers are always the first successors of a block.
     if (hasCatchHandlers()) {
-      assert exit().isGoto() || exit().isThrow();
+      assert exit().isGoto() || exit().isReturn() || exit().isThrow();
       CatchHandlers<Integer> catchHandlers = getCatchHandlersWithSuccessorIndexes();
       // Check that guards are unique.
       assert catchHandlers.getGuards().size()
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 4bd1624..4bccf15 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -1010,6 +1011,34 @@
 
   private InstructionListIterator ensureSingleReturnInstruction(
       AppView<?> appView, IRCode code, List<BasicBlock> normalExits) {
+    // First ensure that there will be not critical edges after inlining. This is needed since
+    // return blocks are allowed to have catch handlers.
+    if (Iterables.any(normalExits, BasicBlock::hasCatchHandlers)) {
+      normalExits =
+          ListUtils.map(
+              normalExits,
+              exitBlock -> {
+                if (!exitBlock.hasCatchHandlers()) {
+                  return exitBlock;
+                }
+                Return exit = exitBlock.exit().asReturn();
+
+                // Create new exit block.
+                BasicBlock newExitBlock = new BasicBlock(code.metadata());
+                newExitBlock.setNumber(code.getNextBlockNumber());
+                Return newReturn =
+                    exit.isReturnVoid() ? new Return() : new Return(exit.returnValue());
+                newReturn.setPosition(exit.getPosition());
+                newExitBlock.add(newReturn, code.metadata());
+
+                // Fixup old exit.
+                exit.replace(new Goto());
+                exitBlock.link(newExitBlock);
+                newExitBlock.close(null);
+                code.blocks.add(newExitBlock);
+                return newExitBlock;
+              });
+    }
     if (normalExits.size() == 1) {
       InstructionListIterator it = normalExits.get(0).listIterator();
       it.nextUntil(Instruction::isReturn);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index 12cbc36..207024e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -137,9 +137,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 9845028..1b1d55c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -249,9 +249,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index fe1c8b3..5273534 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -188,8 +188,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 54cca22..514e31f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -136,8 +136,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index cd799fe..5f7f2c5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -128,8 +128,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 95df669..124b3f7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -166,8 +166,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 0b2b5e1..590fa00 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -152,8 +152,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 3e8e999..8b3b71f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -90,7 +90,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Non-materializing so no stack values are needed.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index 7304858..ea4ef1f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -64,12 +64,12 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
     // A local-write does not have an outgoing stack value, but in writes directly to the local.
     assert !instructionTypeCanThrow();
     if (getBlock().hasCatchHandlers()) {
-      helper.splitAfterStoredOutValue(it);
+      helper.splitAfterStoredOutValue();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 5852d5e..a8291d6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -123,7 +123,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index 090040a..174392a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -74,7 +74,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Nothing to do for positions which are not actual instructions.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 6b3072f..d1bea5b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -144,8 +144,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 41dea29..071839b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -103,7 +103,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Intentionally empty. Dup is a stack operation.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index eebb405..c05a8af 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -117,7 +117,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Intentionally empty. Dup2 is a stack operation.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index 3c5a357..6bb4e8e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -84,7 +84,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Nothing to do.
   }
 
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 631034e..25c51c9 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
@@ -1156,7 +1156,7 @@
   }
 
   public Iterator<Argument> argumentIterator() {
-    return new Iterator<Argument>() {
+    return new Iterator<>() {
 
       private final InstructionIterator instructionIterator = entryBlock().iterator();
       private Argument next = instructionIterator.next().asArgument();
@@ -1178,6 +1178,10 @@
     };
   }
 
+  public Iterable<Argument> arguments() {
+    return () -> argumentIterator();
+  }
+
   public List<Value> collectArguments() {
     return collectArguments(false);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index fac1512..e19b240 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -243,8 +243,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Inc.java b/src/main/java/com/android/tools/r8/ir/code/Inc.java
index 3b51d7d..37af806 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Inc.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Inc.java
@@ -71,7 +71,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Inc is inserted after load/store insertion, after register allocation.
     throw new Unreachable();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index c9039c1..91df093 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -164,8 +164,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 18401ac..90bf26a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -210,9 +210,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 19b2929..0ea0546 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -107,9 +107,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index d54a445..4e3f1ce 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -250,8 +250,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index f9072c0..9da772e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1567,7 +1567,7 @@
   public abstract ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, ProgramMethod context);
 
-  public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
+  public abstract void insertLoadAndStores(LoadStoreHelper helper);
 
   public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
     assert outValue == null || !outValue.getType().isReferenceType();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
index de5ce5a..8c2287b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
@@ -285,8 +285,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 6aa12e5..b284aa5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -241,13 +241,13 @@
     if (arguments().isEmpty()) {
       return false;
     }
-    Value current = getFirstArgument();
-    if (!current.isArgument()) {
+    Argument current = getFirstArgument().getDefinitionOrNull(Instruction::isArgument);
+    if (current == null) {
       return false;
     }
     for (int i = 1; i < arguments().size(); i++) {
-      Value next = getArgument(i);
-      if (current.getNextConsecutive() != next) {
+      Argument next = getArgument(i).getDefinitionOrNull(Instruction::isArgument);
+      if (current.getNext() != next) {
         return false;
       }
       current = next;
@@ -317,5 +317,5 @@
    * Subclasses must implement load and store handling and make sure to deal with a null out-value
    */
   @Override
-  public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
+  public abstract void insertLoadAndStores(LoadStoreHelper helper);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index a3f258c..7e03629 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -187,14 +187,14 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Essentially the same as InvokeMethod but with call site's method proto
     // instead of a static called method.
-    helper.loadInValues(this, it);
+    helper.loadInValues(this);
     if (getCallSite().methodProto.returnType.isVoidType()) {
       return;
     }
-    helper.storeOrPopOutValue(getCallSite().methodProto.returnType, this, it);
+    helper.storeOrPopOutValue(getCallSite().methodProto.returnType, this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index c1a5688..e15b67c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -265,12 +265,12 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
     if (getReturnType().isVoidType()) {
       return;
     }
-    helper.storeOrPopOutValue(getReturnType(), this, it);
+    helper.storeOrPopOutValue(getReturnType(), this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index b4256d2..bb1400e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -101,9 +101,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
-    helper.storeOrPopOutValue(type, this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
+    helper.storeOrPopOutValue(type, this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index ac3865d..d9d2c52 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -91,7 +91,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Nothing to do. This is only hit because loads and stores are insert for phis.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 430f8a4..0982912 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -120,8 +120,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index a1c5475..24a777f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -121,7 +121,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable(ERROR_MESSAGE);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 4d73577..9d12798 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -101,8 +101,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 1bd40d4..0eb198e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -155,9 +155,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
index 3b44907..bdaeb21 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
@@ -139,7 +139,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw cfUnsupported();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index cd21fbc..33a0b7d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -114,7 +114,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable(ERROR_MESSAGE);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index f946940..a3f05e0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -125,8 +125,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
index cafceff..bc3d425 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
@@ -130,8 +130,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index 483aa87..2a98b81 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -96,7 +96,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // JVM has no Not instruction, they should be replaced by "Load -1, Xor" before building CF.
     throw new Unreachable();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 00554e8..92c7745 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -121,7 +121,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable("This IR must not be inserted before load and store insertion.");
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java b/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
index 7ab1c92..0876c64 100644
--- a/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/code/RecordFieldValues.java
@@ -114,9 +114,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java
index d18a410..053384e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ResourceConstNumber.java
@@ -56,7 +56,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable("We never write cf code with resource numbers");
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 4e89ab8..b263cb4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -58,6 +58,10 @@
     return !isReturnVoid();
   }
 
+  public Value getReturnValueOrNull() {
+    return hasReturnValue() ? returnValue() : null;
+  }
+
   public Value returnValue() {
     assert !isReturnVoid();
     return inValues.get(0);
@@ -129,13 +133,18 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     if (!isReturnVoid()) {
-      helper.loadInValues(this, it);
+      helper.loadInValues(this);
     }
   }
 
   @Override
+  public boolean isAllowedAfterThrowingInstruction() {
+    return true;
+  }
+
+  @Override
   public void buildCf(CfBuilder builder) {
     builder.add(
         isReturnVoid() ? new CfReturnVoid() : new CfReturn(ValueType.fromType(getReturnType())),
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index b7a26d9..aed2b5e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -213,8 +213,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index b201864..dc29a40 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -230,8 +230,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 0ace979..552466d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -92,7 +92,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable("This IR must not be inserted before load and store insertion.");
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java b/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
index 506cb80..9968963 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StringSwitch.java
@@ -130,7 +130,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 3e826c8..097dda3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -98,7 +98,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Intentionally empty. Swap is a stack operation.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index ab5db40..06bfeaa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -83,8 +83,8 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
index 3561840..b01312c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
@@ -82,7 +82,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     // Non-materializing so no stack values are needed.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Unop.java b/src/main/java/com/android/tools/r8/ir/code/Unop.java
index 9c65c4e..daf2e77 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Unop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Unop.java
@@ -52,9 +52,9 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
-    helper.loadInValues(this, it);
-    helper.storeOutValue(this, it);
+  public void insertLoadAndStores(LoadStoreHelper helper) {
+    helper.loadInValues(this);
+    helper.storeOutValue(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java b/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
index e9ded3f..bd527d6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/UnusedArgument.java
@@ -88,7 +88,7 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+  public void insertLoadAndStores(LoadStoreHelper helper) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 2b027ab..d782e2b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -12,7 +12,6 @@
 import static com.android.tools.r8.ir.code.Opcodes.DEX_ITEM_BASED_CONST_STRING;
 import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
 
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -33,7 +32,6 @@
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.ObjectUtils;
@@ -180,8 +178,6 @@
   private LinkedList<Phi> phiUsers = new LinkedList<>();
 
   private Set<Phi> uniquePhiUsers = null;
-  private Value nextConsecutive = null;
-  private Value previousConsecutive = null;
   private LiveIntervals liveIntervals;
   private int needsRegister = -1;
   private boolean isThis = false;
@@ -213,6 +209,14 @@
     return definition;
   }
 
+  @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
+  public <T extends Instruction> T getDefinitionOrNull(Predicate<Instruction> predicate) {
+    if (definition != null && predicate.test(definition)) {
+      return (T) definition;
+    }
+    return null;
+  }
+
   public boolean hasAliasedValue() {
     return getAliasedValue() != this;
   }
@@ -306,47 +310,6 @@
     end.addDebugValue(this);
   }
 
-  public void linkTo(Value other) {
-    assert nextConsecutive == null || nextConsecutive == other;
-    assert other.previousConsecutive == null || other.previousConsecutive == this;
-    other.previousConsecutive = this;
-    nextConsecutive = other;
-  }
-
-  public void replaceLink(Value newArgument) {
-    assert isLinked();
-    if (previousConsecutive != null) {
-      previousConsecutive.nextConsecutive = newArgument;
-      newArgument.previousConsecutive = previousConsecutive;
-      previousConsecutive = null;
-    }
-    if (nextConsecutive != null) {
-      nextConsecutive.previousConsecutive = newArgument;
-      newArgument.nextConsecutive = nextConsecutive;
-      nextConsecutive = null;
-    }
-  }
-
-  public boolean isLinked() {
-    return nextConsecutive != null || previousConsecutive != null;
-  }
-
-  public Value getStartOfConsecutive() {
-    Value current = this;
-    while (current.getPreviousConsecutive() != null) {
-      current = current.getPreviousConsecutive();
-    }
-    return current;
-  }
-
-  public Value getNextConsecutive() {
-    return nextConsecutive;
-  }
-
-  public Value getPreviousConsecutive() {
-    return previousConsecutive;
-  }
-
   public boolean onlyUsedInBlock(BasicBlock block) {
     if (hasPhiUsers() || hasDebugUsers()) {
       return false;
@@ -374,6 +337,10 @@
     return uniqueUsers().size() == 1;
   }
 
+  public boolean hasSingleUniqueUserAndNoOtherUsers() {
+    return hasSingleUniqueUser() && !hasPhiUsers() && !hasDebugUsers();
+  }
+
   public Instruction singleUniqueUser() {
     assert hasSingleUniqueUser();
     return firstUser();
@@ -779,15 +746,6 @@
     return false;
   }
 
-  public boolean hasRegisterConstraint() {
-    for (Instruction instruction : uniqueUsers()) {
-      if (instruction.maxInValueRegister() != Constants.U16BIT_MAX) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   public boolean isValueOnStack() {
     return false;
   }
@@ -893,11 +851,6 @@
         && getConstInstruction().asConstNumber().getRawValue() == rawValue;
   }
 
-  public boolean isConstBoolean(boolean value) {
-    return isConstNumber()
-        && definition.asConstNumber().getRawValue() == BooleanUtils.longValue(value);
-  }
-
   public boolean isConstZero() {
     return isConstNumber() && definition.asConstNumber().isZero();
   }
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 5422c8a..a17b3f1 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
@@ -516,7 +516,7 @@
       timing.begin("Lens rewrite");
       lensCodeRewriter.rewrite(code, context, methodProcessor);
       timing.end();
-      previous = printMethod(code, "IR after disable assertions (SSA)", previous);
+      previous = printMethod(code, "IR after lens code rewriting (SSA)", previous);
     }
 
     boolean isDebugMode = options.debug || context.isReachabilitySensitive();
@@ -592,17 +592,17 @@
     }
 
     assertionsRewriter.run(method, code, deadCodeRemover, timing);
+    previous = printMethod(code, "IR after assertions rewriter (SSA)", previous);
+
     CheckNotNullConverter.runIfNecessary(appView, code);
-    previous = printMethod(code, "IR after disable assertions (SSA)", previous);
+    previous = printMethod(code, "IR after check not null converter (SSA)", previous);
 
     timing.begin("Run proto shrinking tasks");
     appView.withGeneratedExtensionRegistryShrinker(shrinker -> shrinker.rewriteCode(method, code));
-
     previous = printMethod(code, "IR after generated extension registry shrinking (SSA)", previous);
 
     appView.withGeneratedMessageLiteShrinker(shrinker -> shrinker.run(code));
     timing.end();
-
     previous = printMethod(code, "IR after generated message lite shrinking (SSA)", previous);
 
     if (memberValuePropagation != null) {
@@ -637,18 +637,18 @@
 
     if (assumeInserter != null) {
       assumeInserter.insertAssumeInstructions(code, timing);
+      previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
     }
 
-    previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
 
     if (inliner != null && !isDebugMode) {
       timing.begin("Inlining");
       inliner.performInlining(code.context(), code, feedback, methodProcessor, timing);
       timing.end();
       assert code.verifyTypes(appView);
+      previous = printMethod(code, "IR after inlining (SSA)", previous);
     }
 
-    previous = printMethod(code, "IR after inlining (SSA)", previous);
 
     if (appView.appInfo().hasLiveness()) {
       // Reflection optimization 1. getClass() / forName() -> const-class
@@ -665,9 +665,9 @@
           .libraryMethodOptimizer()
           .optimize(code, feedback, methodProcessor, methodProcessingContext);
       timing.end();
-      previous = printMethod(code, "IR after class library method optimizer (SSA)", previous);
       code.removeRedundantBlocks();
       assert code.isConsistentSSA(appView);
+      previous = printMethod(code, "IR after class library method optimizer (SSA)", previous);
     }
 
     assert code.verifyTypes(appView);
@@ -704,17 +704,16 @@
     // dead code which is removed right before register allocation in performRegisterAllocation.
     deadCodeRemover.run(code, timing);
     assert code.isConsistentSSA(appView);
+    previous = printMethod(code, "IR after dead code removal (SSA)", previous);
 
     if (options.testing.invertConditionals) {
       invertConditionalsForTesting(code);
+      previous = printMethod(code, "IR after inverting conditionals for testing (SSA)", previous);
     }
 
-    previous = printMethod(code, "IR after dead code removal (SSA)", previous);
 
     assert code.verifyTypes(appView);
 
-    previous = printMethod(code, "IR before class inlining (SSA)", previous);
-
     if (classInliner != null) {
       timing.begin("Inline classes");
       // Class inliner should work before lambda merger, so if it inlines the
@@ -725,25 +724,21 @@
       code.removeRedundantBlocks();
       assert code.isConsistentSSA(appView);
       assert code.verifyTypes(appView);
+      previous = printMethod(code, "IR after class inlining (SSA)", previous);
     }
 
-    previous = printMethod(code, "IR after class inlining (SSA)", previous);
-
     assert code.verifyTypes(appView);
 
-    previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
-
     // TODO(b/140766440): an ideal solution would be putting CodeOptimization for this into
     //  the list for primary processing only.
     outliner.collectOutlineSites(code, timing);
-
     assert code.verifyTypes(appView);
-
     previous = printMethod(code, "IR after outline handler (SSA)", previous);
 
     if (!code.getConversionOptions().isGeneratingLir()) {
       new FilledNewArrayRewriter(appView)
           .run(code, methodProcessor, methodProcessingContext, timing);
+      previous = printMethod(code, "IR after filled-new-array rewriter (SSA)", previous);
     }
 
     // Remove string switches prior to canonicalization to ensure that the constants that are
@@ -761,7 +756,7 @@
       previous = printMethod(code, "IR after constant canonicalization (SSA)", previous);
       new DexConstantOptimizer(appView, constantCanonicalizer)
           .run(code, methodProcessor, methodProcessingContext, timing);
-      previous = printMethod(code, "IR after dex constant optimization (SSA)", previous);
+      previous = printMethod(code, "IR after DEX constant optimization (SSA)", previous);
     }
 
     if (removeVerificationErrorForUnknownReturnedValues != null) {
@@ -771,12 +766,9 @@
     timing.begin("Canonicalize idempotent calls");
     idempotentFunctionCallCanonicalizer.canonicalize(code);
     timing.end();
-
     previous =
         printMethod(code, "IR after idempotent function call canonicalization (SSA)", previous);
 
-    previous = printMethod(code, "IR after argument type logging (SSA)", previous);
-
     assert code.verifyTypes(appView);
 
     deadCodeRemover.run(code, timing);
@@ -810,18 +802,17 @@
       code.removeRedundantBlocks();
       timing.end();
       assert code.isConsistentSSA(appView);
+      previous = printMethod(code, "IR after removing assume instructions (SSA)", previous);
 
       // TODO(b/214496607): Remove when dynamic types are safe w.r.t. interface assignment rules.
       new MoveResultRewriter(appView).run(code, methodProcessor, methodProcessingContext, timing);
+      previous = printMethod(code, "IR after move result rewriter (SSA)", previous);
     }
 
     // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL.
     assert code.verifyNoNullabilityBottomTypes();
     assert code.verifyTypes(appView);
 
-    previous =
-        printMethod(code, "IR after computation of optimization info summary (SSA)", previous);
-
     previous = printMethod(code, "Optimized IR (SSA)", previous);
     timing.begin("Finalize IR");
     finalizeIR(code, feedback, bytecodeMetadataProviderBuilder.build(), timing, previous);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
index c971967..c7c4533 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRToDexFinalizer.java
@@ -71,7 +71,8 @@
     // does not allow dead code (to make sure that we do not waste registers for unneeded values).
     assert deadCodeRemover.verifyNoDeadCode(code);
     timing.begin("Allocate registers");
-    LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code);
+    LinearScanRegisterAllocator registerAllocator =
+        new LinearScanRegisterAllocator(appView, code, timing);
     registerAllocator.allocateRegisters();
     timing.end();
     TrivialGotosCollapser trivialGotosCollapser = new TrivialGotosCollapser(appView);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
index a6bbd64..24ab29b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPassCollection.java
@@ -53,11 +53,14 @@
     passes.add(new BranchSimplifier(appView));
     passes.add(new SplitBranch(appView));
     passes.add(new RedundantConstNumberRemover(appView));
-    if (!appView.options().debug) {
+    if (appView.options().isRelease()) {
       passes.add(new RedundantFieldLoadAndStoreElimination(appView));
     }
     passes.add(new BinopRewriter(appView));
     passes.add(new ServiceLoaderRewriter(appView));
+    if (appView.options().isRelease()) {
+      passes.add(new SplitReturnRewriter(appView));
+    }
     return new CodeRewriterPassCollection(passes);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitReturnRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitReturnRewriter.java
new file mode 100644
index 0000000..ee27ea1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/SplitReturnRewriter.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2024, 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.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Set;
+
+public class SplitReturnRewriter extends CodeRewriterPass<AppInfo> {
+
+  public SplitReturnRewriter(AppView<?> appView) {
+    super(appView);
+  }
+
+  @Override
+  protected String getRewriterId() {
+    return "SplitReturnRewriter";
+  }
+
+  @Override
+  protected boolean shouldRewriteCode(IRCode code, MethodProcessor methodProcessor) {
+    // Disable in tests that need dead switches to be left behind.
+    assert options.isRelease();
+    return appView.options().getTestingOptions().enableDeadSwitchCaseElimination;
+  }
+
+  @Override
+  protected CodeRewriterResult rewriteCode(IRCode code) {
+    boolean changed = false;
+    boolean hasUnreachableBlocks = false;
+    Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
+    Deque<BasicBlock> worklist = new ArrayDeque<>(code.computeNormalExitBlocks());
+    while (!worklist.isEmpty()) {
+      BasicBlock block = worklist.removeFirst();
+      Return returnInstruction = block.entry().asReturn();
+      if (returnInstruction == null) {
+        continue;
+      }
+      Value returnValue = returnInstruction.getReturnValueOrNull();
+      IntList predecessorsToRemove = new IntArrayList();
+      for (int predecessorIndex = 0;
+          predecessorIndex < block.getPredecessors().size();
+          predecessorIndex++) {
+        BasicBlock predecessor = block.getPredecessor(predecessorIndex);
+        if (!predecessor.exit().isGoto()) {
+          continue;
+        }
+        if (predecessor.exit().asGoto().getTarget() != block) {
+          assert predecessor.hasCatchSuccessor(block);
+          continue;
+        }
+        if (block.hasCatchHandlers()) {
+          for (BasicBlock catchHandlerBlock : block.getSuccessors()) {
+            catchHandlerBlock.getMutablePredecessors().clear();
+          }
+          block.getMutableSuccessors().clear();
+          block.clearCatchHandlers();
+          hasUnreachableBlocks = true;
+        } else {
+          assert block.getSuccessors().isEmpty();
+        }
+        Value newReturnValue;
+        if (returnValue != null && returnValue.isPhi() && returnValue.getBlock() == block) {
+          newReturnValue = returnValue.asPhi().getOperand(predecessorIndex);
+        } else {
+          newReturnValue = returnValue;
+        }
+        Return newReturnInstruction =
+            Return.builder().setReturnValue(newReturnValue).setPosition(returnInstruction).build();
+        predecessor.exit().replace(newReturnInstruction);
+        predecessor.removeAllNormalSuccessors();
+        predecessorsToRemove.add(predecessorIndex);
+        worklist.add(predecessor);
+      }
+      if (!predecessorsToRemove.isEmpty()) {
+        if (predecessorsToRemove.size() == block.getPredecessors().size()) {
+          blocksToRemove.add(block);
+          for (Phi phi : block.getPhis()) {
+            for (Value operand : phi.getOperands()) {
+              operand.removePhiUser(phi);
+            }
+          }
+          if (returnValue != null) {
+            returnValue.removeUser(returnInstruction);
+          }
+        } else {
+          block.removePredecessorsByIndex(predecessorsToRemove);
+          block.removePhisByIndex(predecessorsToRemove);
+        }
+        changed = true;
+      }
+    }
+    code.removeBlocks(blocksToRemove);
+    if (hasUnreachableBlocks) {
+      AffectedValues affectedValues = code.removeUnreachableBlocks();
+      affectedValues.narrowingWithAssumeRemoval(appView, code);
+    }
+    if (changed) {
+      code.removeRedundantBlocks();
+    }
+    return CodeRewriterResult.hasChanged(changed);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java
index 10960e0..44cba60 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringSwitchConverter.java
@@ -775,6 +775,7 @@
 
         if (idValue == null
             || !idValue.getType().isInt()
+            || !idValue.hasSingleUniqueUserAndNoOtherUsers()
             || (toBeExtended != null && idValue != toBeExtended.idValue)) {
           // Not an extension of `toBeExtended`.
           return setFallthroughBlock(toBeExtended, fallthroughBlock);
@@ -808,7 +809,9 @@
       private IdToTargetMapping extendWithSwitch(
           IdToTargetMapping toBeExtended, IntSwitch theSwitch, BasicBlock fallthroughBlock) {
         Value switchValue = theSwitch.value();
-        if (!switchValue.isPhi() || (toBeExtended != null && switchValue != toBeExtended.idValue)) {
+        if (!switchValue.isPhi()
+            || !switchValue.hasSingleUniqueUserAndNoOtherUsers()
+            || (toBeExtended != null && switchValue != toBeExtended.idValue)) {
           // Not an extension of `toBeExtended`.
           return setFallthroughBlock(toBeExtended, fallthroughBlock);
         }
@@ -830,6 +833,7 @@
     private final Int2ReferenceMap<BasicBlock> mapping = new Int2ReferenceOpenHashMap<>();
 
     private IdToTargetMapping(Phi idValue) {
+      assert idValue.hasSingleUniqueUserAndNoOtherUsers();
       this.idValue = idValue;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index a9ec6d2..f9d4c18 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.Constants;
@@ -32,7 +33,9 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
@@ -100,22 +103,40 @@
 
   @Override
   public DesugarDescription compute(CfInstruction instruction, ProgramMethod context) {
-    if (!instruction.isInvoke()) {
+    // Only invokes and static field gets are backported.
+    if (!instruction.isInvoke() && !instruction.isStaticFieldGet()) {
       return DesugarDescription.nothing();
     }
-
-    CfInvoke invoke = instruction.asInvoke();
-    MethodProvider methodProvider = getMethodProviderOrNull(invoke.getMethod(), context);
-    if (methodProvider == null
-        || appView
-            .getSyntheticItems()
-            .isSyntheticOfKind(context.getContextType(), kinds -> kinds.BACKPORT_WITH_FORWARDING)) {
-      return DesugarDescription.nothing();
+    if (instruction.isInvoke()) {
+      CfInvoke invoke = instruction.asInvoke();
+      MethodProvider<DexMethod> methodProvider = getProviderOrNull(invoke.getMethod(), context);
+      if (methodProvider == null
+          || appView
+              .getSyntheticItems()
+              .isSyntheticOfKind(
+                  context.getContextType(), kinds -> kinds.BACKPORT_WITH_FORWARDING)) {
+        return DesugarDescription.nothing();
+      }
+      return desugarInstruction(invoke, methodProvider);
+    } else {
+      assert instruction.isStaticFieldGet();
+      CfStaticFieldRead staticGet = instruction.asStaticFieldGet();
+      MethodProvider<DexField> methodProvider = getProviderOrNull(staticGet.getField());
+      if (methodProvider == null
+          || appView
+              .getSyntheticItems()
+              .isSyntheticOfKind(context.getContextType(), kinds -> kinds.BACKPORT_WITH_FORWARDING)
+          || appView
+              .getSyntheticItems()
+              .isSyntheticOfKind(context.getContextType(), kinds -> kinds.API_MODEL_OUTLINE)) {
+        return DesugarDescription.nothing();
+      }
+      return desugarInstruction(staticGet, methodProvider);
     }
-    return desugarInstruction(invoke, methodProvider);
   }
 
-  private DesugarDescription desugarInstruction(CfInvoke invoke, MethodProvider methodProvider) {
+  private DesugarDescription desugarInstruction(
+      CfInvoke invoke, MethodProvider<DexMethod> methodProvider) {
     return DesugarDescription.builder()
         .setDesugarRewrite(
             (position,
@@ -127,7 +148,7 @@
                 methodProcessingContext,
                 desugaringCollection,
                 dexItemFactory) ->
-                methodProvider.rewriteInvoke(
+                methodProvider.rewriteInstruction(
                     position,
                     invoke,
                     appView,
@@ -137,35 +158,63 @@
         .build();
   }
 
-  public static List<DexMethod> generateListOfBackportedMethods(
-      AndroidApp androidApp, InternalOptions options, ExecutorService executor) throws IOException {
+  private DesugarDescription desugarInstruction(
+      CfStaticFieldRead staticGet, MethodProvider<DexField> methodProvider) {
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (position,
+                freshLocalProvider,
+                localStackAllocator,
+                desugaringInfo,
+                eventConsumer,
+                context,
+                methodProcessingContext,
+                desugaringCollection,
+                dexItemFactory) ->
+                methodProvider.rewriteInstruction(
+                    position,
+                    staticGet,
+                    appView,
+                    eventConsumer,
+                    methodProcessingContext,
+                    localStackAllocator))
+        .build();
+  }
+
+  public static void generateListOfBackportedMethodsAndFields(
+      AndroidApp androidApp,
+      InternalOptions options,
+      ExecutorService executor,
+      Consumer<DexMethod> methods,
+      Consumer<DexField> fields)
+      throws IOException {
     DexApplication app = null;
     if (androidApp != null) {
       app = new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
     }
-    return generateListOfBackportedMethods(app, options);
+    generateListOfBackportedMethodsAndFields(app, options, methods, fields);
   }
 
-  public static List<DexMethod> generateListOfBackportedMethods(
-      DexApplication app, InternalOptions options) throws IOException {
-    List<DexMethod> methods = new ArrayList<>();
+  public static void generateListOfBackportedMethodsAndFields(
+      DexApplication app,
+      InternalOptions options,
+      Consumer<DexMethod> methods,
+      Consumer<DexField> fields)
+      throws IOException {
     options.loadMachineDesugaredLibrarySpecification(Timing.empty(), app);
     TypeRewriter typeRewriter = options.getTypeRewriter();
-    AppInfo appInfo = null;
-    if (app != null) {
-      appInfo = AppInfo.createInitialAppInfo(app, GlobalSyntheticsStrategy.forNonSynthesizing());
-
-    }
+    AppInfo appInfo =
+        AppInfo.createInitialAppInfo(app, GlobalSyntheticsStrategy.forNonSynthesizing());
     AppView<?> appView = AppView.createForD8(appInfo, typeRewriter, Timing.empty());
     BackportedMethodRewriter.RewritableMethods rewritableMethods =
         new BackportedMethodRewriter.RewritableMethods(appView);
-    rewritableMethods.visit(methods::add);
+    rewritableMethods.visit(methods);
     if (appInfo != null) {
       DesugaredLibraryRetargeter desugaredLibraryRetargeter =
           new DesugaredLibraryRetargeter(appView);
-      desugaredLibraryRetargeter.visit(methods::add);
+      desugaredLibraryRetargeter.visit(methods);
     }
-    return methods;
+    rewritableMethods.visitFields(fields);
   }
 
   public static void registerAssumedLibraryTypes(InternalOptions options) {
@@ -173,10 +222,11 @@
     BackportedMethods.registerSynthesizedCodeReferences(options.itemFactory);
   }
 
-  private MethodProvider getMethodProviderOrNull(DexMethod method, ProgramMethod context) {
+  private MethodProvider<DexMethod> getProviderOrNull(DexMethod member, ProgramMethod context) {
+    DexMethod method = member.asDexMethod();
     DexMethod original = appView.graphLens().getOriginalMethodSignature(method);
     assert original != null;
-    MethodProvider provider = rewritableMethods.getProvider(original);
+    MethodProvider<DexMethod> provider = rewritableMethods.getProvider(original);
     // Old versions of desugared library have in the jar file pre-desugared code. This is used
     // to undesugar pre-desugared code, then the code is re-desugared with D8/R8. This is
     // maintained for legacy only, recent desugared library should not be shipped with
@@ -196,14 +246,14 @@
       // unit, assume that the compilation is the defining instance and no backport is needed.
       DexClass clazz =
           appView
-              .contextIndependentDefinitionForWithResolutionResult(provider.method.holder)
+              .contextIndependentDefinitionForWithResolutionResult(provider.member.getHolderType())
               .toSingleClassWithProgramOverLibrary();
       if (clazz == null || !clazz.isProgramDefinition()) {
         appView
             .reporter()
             .warning(
                 new IgnoredBackportMethodDiagnostic(
-                    provider.method,
+                    provider.member,
                     context.getOrigin(),
                     MethodPosition.create(context),
                     appView.options().getMinApiLevel().getLevel()));
@@ -213,14 +263,24 @@
     return provider;
   }
 
+  private MethodProvider<DexField> getProviderOrNull(DexField field) {
+    MethodProvider<DexField> provider = rewritableMethods.getProvider(field);
+    return provider;
+  }
+
   private static final class RewritableMethods {
 
     private final Map<DexType, AndroidApiLevel> typeMinApi;
 
     private final AppView<?> appView;
 
-    // Map backported method to a provider for creating the actual target method (with code).
-    private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
+    // Map backported field or method to a provider for creating the actual target method
+    // (with code).
+    private final Map<DexMethod, MethodProvider<DexMethod>> rewritableMethods =
+        new IdentityHashMap<>();
+    private final Map<DexField, MethodProvider<DexField>> rewritableFields =
+        new IdentityHashMap<>();
+    MethodProvider<DexField> singleBackportedField = null; // As there is only one now skip map.
 
     RewritableMethods(AppView<?> appView) {
       InternalOptions options = appView.options();
@@ -298,6 +358,9 @@
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.V)) {
         initializeAndroidVMethodProviders(factory);
       }
+      if (options.getMinApiLevel().isLessThan(AndroidApiLevel.BAKLAVA)) {
+        initializeAndroidBaklavaMethodProviders(factory);
+      }
     }
 
     private Map<DexType, AndroidApiLevel> initializeTypeMinApi(DexItemFactory factory) {
@@ -384,11 +447,15 @@
     }
 
     boolean isEmpty() {
-      return rewritable.isEmpty();
+      return rewritableMethods.isEmpty() && rewritableFields.isEmpty();
     }
 
     public void visit(Consumer<DexMethod> consumer) {
-      rewritable.keySet().forEach(consumer);
+      rewritableMethods.keySet().forEach(consumer);
+    }
+
+    public void visitFields(Consumer<DexField> consumer) {
+      rewritableFields.keySet().forEach(consumer);
     }
 
     private void initializeAndroidKObjectsMethodProviders(DexItemFactory factory) {
@@ -1694,6 +1761,20 @@
       }
     }
 
+    private void initializeAndroidBaklavaMethodProviders(DexItemFactory factory) {
+      // android.os.Build$VERSION
+      DexType type = factory.androidOsBuildVersionType;
+
+      // int android.os.Build$VERSION.SDK_INT_FULL
+      DexString name = factory.createString("SDK_INT_FULL");
+      DexField field = factory.createField(type, factory.intType, name);
+      addProviderForField(
+          new StaticFieldGetMethodWithForwardingGenerator(
+              field,
+              // Template code calls the method again.
+              BackportedMethods::AndroidOsBuildVersionMethods_getSdkIntFull));
+    }
+
     private void initializeAndroidUMethodProviders(DexItemFactory factory) {
       DexType type;
       DexString name;
@@ -1815,34 +1896,50 @@
       addProvider(new ThreadLocalWithInitialWithSupplierGenerator(method));
     }
 
-    private void addProvider(MethodProvider generator) {
-      MethodProvider replaced = rewritable.put(generator.method, generator);
+    private void addProvider(MethodProvider<DexMethod> generator) {
+      MethodProvider<DexMethod> replaced = rewritableMethods.put(generator.member, generator);
       assert replaced == null;
     }
 
-    MethodProvider getProvider(DexMethod method) {
-      return rewritable.get(method);
+    MethodProvider<DexMethod> getProvider(DexMethod method) {
+      return rewritableMethods.get(method);
+    }
+
+    private void addProviderForField(MethodProvider<DexField> generator) {
+      MethodProvider<DexField> replaced = rewritableFields.put(generator.member, generator);
+      assert replaced == null;
+      assert singleBackportedField == null;
+      singleBackportedField = generator;
+    }
+
+    MethodProvider<DexField> getProvider(DexField field) {
+      if (field.isIdenticalTo(singleBackportedField.member)) {
+        return singleBackportedField;
+      }
+      assert !rewritableFields.containsKey(field);
+      return null;
     }
   }
 
-  public abstract static class MethodProvider {
+  public abstract static class MethodProvider<
+      T extends DexMember<? extends DexItem, ? extends DexMember<?, ?>>> {
 
-    final DexMethod method;
+    final T member;
 
-    public MethodProvider(DexMethod method) {
-      this.method = method;
+    public MethodProvider(T member) {
+      this.member = member;
     }
 
-    public abstract Collection<CfInstruction> rewriteInvoke(
+    public abstract Collection<CfInstruction> rewriteInstruction(
         Position position,
-        CfInvoke invoke,
+        CfInstruction instruction,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
         MethodProcessingContext methodProcessingContext,
         LocalStackAllocator localStackAllocator);
   }
 
-  private static final class InvokeRewriter extends MethodProvider {
+  private static final class InvokeRewriter extends MethodProvider<DexMethod> {
 
     private final MethodInvokeRewriter rewriter;
 
@@ -1852,15 +1949,15 @@
     }
 
     @Override
-    public Collection<CfInstruction> rewriteInvoke(
+    public Collection<CfInstruction> rewriteInstruction(
         Position position,
-        CfInvoke invoke,
+        CfInstruction instruction,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
         MethodProcessingContext methodProcessingContext,
         LocalStackAllocator localStackAllocator) {
       Collection<CfInstruction> instructions =
-          rewriter.rewrite(invoke, appView.dexItemFactory(), localStackAllocator);
+          rewriter.rewrite(instruction.asInvoke(), appView.dexItemFactory(), localStackAllocator);
       if (position == null) {
         return instructions;
       }
@@ -1868,7 +1965,7 @@
       CfLabel start = new CfLabel();
       CfLabel end = new CfLabel();
       Position inlinePosition =
-          SourcePosition.builder().setCallerPosition(position).setMethod(method).setLine(0).build();
+          SourcePosition.builder().setCallerPosition(position).setMethod(member).setLine(0).build();
       instructionsWithPositions.add(start);
       instructionsWithPositions.add(new CfPosition(start, inlinePosition));
       instructionsWithPositions.addAll(instructions);
@@ -1878,7 +1975,7 @@
     }
   }
 
-  private static class MethodGenerator extends MethodProvider {
+  private static class MethodGenerator extends MethodProvider<DexMethod> {
 
     private final TemplateMethodFactory factory;
 
@@ -1897,9 +1994,9 @@
     }
 
     @Override
-    public Collection<CfInstruction> rewriteInvoke(
+    public Collection<CfInstruction> rewriteInstruction(
         Position position,
-        CfInvoke invoke,
+        CfInstruction instruction,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
         MethodProcessingContext methodProcessingContext,
@@ -1927,14 +2024,14 @@
                             Code code = generateTemplateMethod(appView.dexItemFactory(), methodSig);
                             if (appView.options().hasMappingFileSupport()) {
                               return code.getCodeAsInlining(
-                                  methodSig, true, method, false, appView.dexItemFactory());
+                                  methodSig, true, member, false, appView.dexItemFactory());
                             }
                             return code;
                           }));
     }
 
     public DexProto getProto(DexItemFactory itemFactory) {
-      return method.proto;
+      return member.getProto();
     }
 
     public Code generateTemplateMethod(DexItemFactory dexItemFactory, DexMethod method) {
@@ -1957,7 +2054,7 @@
 
     @Override
     public DexProto getProto(DexItemFactory factory) {
-      return method.getProto().prependParameter(receiverType, factory);
+      return member.getProto().prependParameter(receiverType, factory);
     }
   }
 
@@ -2008,9 +2105,9 @@
     }
 
     @Override
-    public Collection<CfInstruction> rewriteInvoke(
+    public Collection<CfInstruction> rewriteInstruction(
         Position position,
-        CfInvoke invoke,
+        CfInstruction instruction,
         AppView<?> appView,
         BackportedMethodDesugaringEventConsumer eventConsumer,
         MethodProcessingContext methodProcessingContext,
@@ -2172,4 +2269,65 @@
     public abstract Collection<CfInstruction> rewrite(
         CfInvoke invoke, DexItemFactory factory, LocalStackAllocator localStackAllocator);
   }
+
+  // Generator for backports of static field gets which will again read the field they backport in
+  // the backport code. So using BACKPORT_WITH_FORWARDING as such backports cannot not go through
+  // backporting again as that would cause an infinite backporting loop.
+  private static class StaticFieldGetMethodWithForwardingGenerator
+      extends MethodProvider<DexField> {
+
+    private final TemplateMethodFactory factory;
+
+    StaticFieldGetMethodWithForwardingGenerator(DexField field, TemplateMethodFactory factory) {
+      super(field);
+      this.factory = factory;
+    }
+
+    @Override
+    public Collection<CfInstruction> rewriteInstruction(
+        Position position,
+        CfInstruction instruction,
+        AppView<?> appView,
+        BackportedMethodDesugaringEventConsumer eventConsumer,
+        MethodProcessingContext methodProcessingContext,
+        LocalStackAllocator localStackAllocator) {
+      ProgramMethod method = getSyntheticMethod(appView, methodProcessingContext);
+      eventConsumer.acceptBackportedMethod(method, methodProcessingContext.getMethodContext());
+      return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
+    }
+
+    protected SyntheticKind getSyntheticKind(SyntheticNaming naming) {
+      return naming.BACKPORT_WITH_FORWARDING;
+    }
+
+    private ProgramMethod getSyntheticMethod(
+        AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+      return appView
+          .getSyntheticItems()
+          .createMethod(
+              this::getSyntheticKind,
+              methodProcessingContext.createUniqueContext(),
+              appView,
+              builder ->
+                  builder
+                      // As this is forwarding to the field read set the API level accordingly to
+                      // ensure API outlining when needed.
+                      .setApiLevelForCode(
+                          appView.apiLevelCompute().computeApiLevelForLibraryReference(member))
+                      .setProto(getProto(appView.dexItemFactory()))
+                      .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                      .setCode(
+                          methodSig ->
+                              generateTemplateMethod(appView.dexItemFactory(), methodSig)));
+    }
+
+    public DexProto getProto(DexItemFactory itemFactory) {
+      // Proto for the method replacing the field read.
+      return itemFactory.createProto(member.getType());
+    }
+
+    public Code generateTemplateMethod(DexItemFactory dexItemFactory, DexMethod method) {
+      return factory.create(dexItemFactory, method);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 20f1dda..ace4c1e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -221,7 +221,7 @@
 
     @Override
     public void acceptRecordEqualsHelperMethod(ProgramMethod method, ProgramMethod context) {
-      // Intentionally empty. Added to the program using ProgramAdditions.
+      methodProcessor.scheduleMethodForProcessing(method, outermostEventConsumer);
     }
 
     @Override
@@ -232,7 +232,7 @@
 
     @Override
     public void acceptRecordHashCodeHelperMethod(ProgramMethod method, ProgramMethod context) {
-      methodProcessor.scheduleDesugaredMethodForProcessing(method);
+      methodProcessor.scheduleMethodForProcessing(method, outermostEventConsumer);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index e3cbc07..4d0f768 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -153,10 +153,13 @@
         if (target == null) {
             target = appInfo.lookupDirectTarget(method, context, appView, appInfo);
         }
-        assert target == null
-            || (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
-            || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
-            || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
+          assert target == null
+              // TODO(b/366932318): We should disallow staticizing of methods called from lambdas
+              //  or update the implHandle accordingly.
+              || (implHandle.type.isInvokeInstance()
+                  && (isInstanceMethod(target) || target.getAccessFlags().isStatic()))
+              || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
+              || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
         return target;
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 29259c9..259234e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -95,7 +95,13 @@
 
   private ComputedApiLevel getComputedApiLevelInstructionOnHolderWithMinApi(
       CfInstruction instruction, ProgramMethod context) {
-    if (context.getDefinition().isD8R8Synthesized()) {
+    // Some backports will forward to the method/field they backport. For such synthetics run
+    // outlining. Other synthetics should not need it. And explicitly not API outlines, as that
+    // would cause infinite outlining.
+    if (context.getDefinition().isD8R8Synthesized()
+        && !appView
+            .getSyntheticItems()
+            .isSyntheticOfKind(context.getHolderType(), k -> k.BACKPORT_WITH_FORWARDING)) {
       return appView.computedMinApiLevel();
     }
     DexReference reference = getReferenceFromInstruction(instruction);
@@ -194,10 +200,11 @@
       ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
       ProgramMethod context) {
     assert instruction.isInvoke()
-        || instruction.isFieldInstruction()
-        || instruction.isCheckCast()
-        || instruction.isInstanceOf()
-        || instruction.isConstClass();
+            || instruction.isFieldInstruction()
+            || instruction.isCheckCast()
+            || instruction.isInstanceOf()
+            || instruction.isConstClass()
+        : instruction;
     ProgramMethod outlinedMethod =
         ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
     eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 6c73cde..005543b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -56,6 +56,7 @@
 public final class BackportedMethods {
 
   public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("Landroid/os/Build$VERSION;");
     factory.createSynthesizedType("Ljava/lang/ArithmeticException;");
     factory.createSynthesizedType("Ljava/lang/AssertionError;");
     factory.createSynthesizedType("Ljava/lang/Double;");
@@ -122,6 +123,45 @@
     factory.createSynthesizedType("[Ljava/util/Map$Entry;");
   }
 
+  public static CfCode AndroidOsBuildVersionMethods_getSdkIntFull(
+      DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        0,
+        ImmutableList.of(
+            label0,
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Landroid/os/Build$VERSION;"),
+                    factory.intType,
+                    factory.createString("SDK_INT"))),
+            new CfConstNumber(36, ValueType.INT),
+            new CfIfCmp(IfType.GE, ValueType.INT, label2),
+            label1,
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Landroid/os/Build$VERSION;"),
+                    factory.intType,
+                    factory.createString("SDK_INT"))),
+            new CfConstNumber(100000, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT),
+            new CfReturn(ValueType.INT),
+            label2,
+            new CfFrame(),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Landroid/os/Build$VERSION;"),
+                    factory.intType,
+                    factory.createString("SDK_INT_FULL"))),
+            new CfReturn(ValueType.INT)),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode AssertionErrorMethods_createAssertionError(
       DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
index 8d1ede7..b257bc2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateDesugaredLibraryLintFiles.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.StringResource;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.lint.SupportedClasses.MethodAnnotation;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -107,6 +108,14 @@
       desugaredApisSignatures.add(
           classBinaryName + '#' + extraMethod.name + extraMethod.proto.toDescriptorString());
     }
+    if (FORMAT_WITH_FIELD) {
+      for (DexField extraField : supportedClasses.getExtraFields()) {
+        String classBinaryName =
+            DescriptorUtils.getClassBinaryNameFromDescriptor(
+                extraField.getHolderType().descriptor.toString());
+        desugaredApisSignatures.add(classBinaryName + '#' + extraField.name);
+      }
+    }
 
     // Write a plain text file with the desugared APIs.
     desugaredApisSignatures.sort(Comparator.naturalOrder());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
index db8847b..3e568da 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClasses.java
@@ -32,10 +32,15 @@
 public class SupportedClasses {
   private final Map<DexType, SupportedClass> supportedClasses;
   private final List<DexMethod> extraMethods;
+  private final List<DexField> extraFields;
 
-  SupportedClasses(Map<DexType, SupportedClass> supportedClasses, List<DexMethod> extraMethods) {
+  SupportedClasses(
+      Map<DexType, SupportedClass> supportedClasses,
+      List<DexMethod> extraMethods,
+      List<DexField> extraFields) {
     this.supportedClasses = supportedClasses;
     this.extraMethods = extraMethods;
+    this.extraFields = extraFields;
   }
 
   public void forEachClass(Consumer<SupportedClass> consumer) {
@@ -46,6 +51,10 @@
     return extraMethods;
   }
 
+  public List<DexField> getExtraFields() {
+    return extraFields;
+  }
+
   public static class SupportedClass {
 
     private final DexClass clazz;
@@ -199,6 +208,7 @@
 
     Map<DexType, SupportedClass.Builder> supportedClassBuilders = new IdentityHashMap<>();
     private List<DexMethod> extraMethods = ImmutableList.of();
+    private List<DexField> extraFields = ImmutableList.of();
 
     ClassAnnotation getClassAnnotation(DexType type) {
       SupportedClass.Builder builder = supportedClassBuilders.get(type);
@@ -282,6 +292,10 @@
       this.extraMethods = extraMethods;
     }
 
+    public void setExtraFields(List<DexField> extraFields) {
+      this.extraFields = extraFields;
+    }
+
     public boolean hasOnlyExtraMethods() {
       return supportedClassBuilders.isEmpty();
     }
@@ -292,7 +306,7 @@
           (type, classBuilder) -> {
             map.put(type, classBuilder.build());
           });
-      return new SupportedClasses(ImmutableSortedMap.copyOf(map), extraMethods);
+      return new SupportedClasses(ImmutableSortedMap.copyOf(map), extraMethods, extraFields);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
index a3ce881..d458dc0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/SupportedClassesGenerator.java
@@ -43,7 +43,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.io.IOException;
@@ -53,6 +52,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 
 public class SupportedClassesGenerator {
 
@@ -181,7 +181,8 @@
       AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
 
       // This should depend only on machine specification and min api.
-      List<DexMethod> backports = generateListOfBackportedMethods();
+      List<DexMethod> backports = new ArrayList<>();
+      generateListOfBackportedMethodsAndFields(backports::add, f -> {});
 
       int finalApi = api;
       builder.forEachClassAndMethod(
@@ -306,7 +307,9 @@
     DirectMappedDexApplication implementationApplication =
         new ApplicationReader(implementation, options, Timing.empty()).read().toDirect();
 
-    List<DexMethod> backports = generateListOfBackportedMethods();
+    List<DexMethod> backports = new ArrayList<>();
+    List<DexField> backportFields = new ArrayList<>();
+    generateListOfBackportedMethodsAndFields(backports::add, backportFields::add);
 
     for (DexProgramClass clazz : implementationApplication.classes()) {
       // All emulated interfaces static and default methods are supported.
@@ -389,14 +392,24 @@
       }
       extraMethods.sort(Comparator.naturalOrder());
       builder.setExtraMethods(extraMethods);
+
+      List<DexField> extraFields = new ArrayList<>();
+      for (DexField backport : backportFields) {
+        if (implementationApplication.definitionFor(backport.getHolderType()) == null) {
+          extraFields.add(backport);
+        }
+      }
+      extraFields.sort(Comparator.naturalOrder());
+      builder.setExtraFields(extraFields);
     }
   }
 
-  private List<DexMethod> generateListOfBackportedMethods() throws IOException {
-    if (androidPlatformBuild) {
-      return ImmutableList.of();
+  private void generateListOfBackportedMethodsAndFields(
+      Consumer<DexMethod> methods, Consumer<DexField> fields) throws IOException {
+    if (!androidPlatformBuild) {
+      BackportedMethodRewriter.generateListOfBackportedMethodsAndFields(
+          appForMax, options, methods, fields);
     }
-    return BackportedMethodRewriter.generateListOfBackportedMethods(appForMax, options);
   }
 
   private void registerMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
index 3743f0c..2084f67 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordCfMethods.java
@@ -42,45 +42,9 @@
 public final class RecordCfMethods {
 
   public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
-    factory.createSynthesizedType("Ljava/util/Arrays;");
     factory.createSynthesizedType("[Ljava/lang/Object;");
     factory.createSynthesizedType("[Ljava/lang/String;");
   }
-
-  public static CfCode RecordMethods_hashCode(DexItemFactory factory, DexMethod method) {
-    CfLabel label0 = new CfLabel();
-    CfLabel label1 = new CfLabel();
-    return new CfCode(
-        method.holder,
-        2,
-        2,
-        ImmutableList.of(
-            label0,
-            new CfConstNumber(31, ValueType.INT),
-            new CfLoad(ValueType.OBJECT, 1),
-            new CfInvoke(
-                184,
-                factory.createMethod(
-                    factory.createType("Ljava/util/Arrays;"),
-                    factory.createProto(factory.intType, factory.createType("[Ljava/lang/Object;")),
-                    factory.createString("hashCode")),
-                false),
-            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT),
-            new CfLoad(ValueType.OBJECT, 0),
-            new CfInvoke(
-                182,
-                factory.createMethod(
-                    factory.objectType,
-                    factory.createProto(factory.intType),
-                    factory.createString("hashCode")),
-                false),
-            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
-            new CfReturn(ValueType.INT),
-            label1),
-        ImmutableList.of(),
-        ImmutableList.of());
-  }
-
   public static CfCode RecordMethods_toString(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java
index fb6b45b..773189b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordInstructionDesugaring.java
@@ -4,24 +4,29 @@
 
 package com.android.tools.r8.ir.desugar.records;
 
-import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Dup;
-import static com.android.tools.r8.cf.code.CfStackInstruction.Opcode.Swap;
 import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.isInvokeDynamicOnRecord;
 import static com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.parseInvokeDynamicOnRecord;
 
 import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
+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.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 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.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -31,40 +36,56 @@
 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.ValueType;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.records.RecordRewriterHelper.RecordInvokeDynamic;
-import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordEqualsCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordEqCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordGetFieldsAsObjectsCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordHashCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.function.BiFunction;
 import org.objectweb.asm.Opcodes;
 
 public class RecordInstructionDesugaring implements CfInstructionDesugaring {
 
+  private static final int MAX_FIELDS_FOR_OUTLINE = 32;
+
   final AppView<?> appView;
   final DexItemFactory factory;
+  private final List<DexType> orderedSharedTypes;
   private final DexProto recordToStringHelperProto;
-  private final DexProto recordHashCodeHelperProto;
 
   public static final String GET_FIELDS_AS_OBJECTS_METHOD_NAME = "$record$getFieldsAsObjects";
+  public static final String HASH_CODE_METHOD_NAME = "$record$hashCode";
   public static final String EQUALS_RECORD_METHOD_NAME = "$record$equals";
 
   RecordInstructionDesugaring(AppView<?> appView) {
     this.appView = appView;
     factory = appView.dexItemFactory();
+    orderedSharedTypes =
+        ImmutableList.of(
+            factory.booleanType,
+            factory.intType,
+            factory.longType,
+            factory.floatType,
+            factory.doubleType,
+            factory.objectType);
     recordToStringHelperProto =
         factory.createProto(
             factory.stringType, factory.objectArrayType, factory.classType, factory.stringType);
-    recordHashCodeHelperProto =
-        factory.createProto(factory.intType, factory.classType, factory.objectArrayType);
   }
 
   public static RecordInstructionDesugaring create(AppView<?> appView) {
@@ -82,7 +103,8 @@
   public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
     RecordCfMethods.registerSynthesizedCodeReferences(factory);
     RecordGetFieldsAsObjectsCfCodeProvider.registerSynthesizedCodeReferences(factory);
-    RecordEqualsCfCodeProvider.registerSynthesizedCodeReferences(factory);
+    RecordEqCfCodeProvider.registerSynthesizedCodeReferences(factory);
+    RecordHashCfCodeProvider.registerSynthesizedCodeReferences(factory);
   }
 
   @Override
@@ -107,11 +129,16 @@
       RecordInstructionDesugaringEventConsumer eventConsumer) {
     RecordInvokeDynamic recordInvokeDynamic =
         parseInvokeDynamicOnRecord(invokeDynamic.getCallSite(), appView);
-    if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName
-        || recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
+    if (recordInvokeDynamic.getMethodName() == factory.toStringMethodName) {
       ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions, context, eventConsumer);
       return;
     }
+    if (recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
+      if (!shouldOutlineMethods(recordInvokeDynamic.getRecordClass())) {
+        ensureHashCodeMethod(recordInvokeDynamic, programAdditions, context, eventConsumer);
+      }
+      return;
+    }
     if (recordInvokeDynamic.getMethodName() == factory.equalsMethodName) {
       ensureEqualsRecord(recordInvokeDynamic, programAdditions, context, eventConsumer);
       return;
@@ -157,6 +184,7 @@
                 dexItemFactory) ->
                 desugarInvokeDynamicOnRecord(
                     instruction.asInvokeDynamic(),
+                    freshLocalProvider,
                     localStackAllocator,
                     eventConsumer,
                     context,
@@ -184,6 +212,7 @@
   @SuppressWarnings("ReferenceEquality")
   private List<CfInstruction> desugarInvokeDynamicOnRecord(
       CfInvokeDynamic invokeDynamic,
+      FreshLocalProvider freshLocalProvider,
       LocalStackAllocator localStackAllocator,
       CfInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
@@ -201,6 +230,7 @@
     if (recordInvokeDynamic.getMethodName() == factory.hashCodeMethodName) {
       return desugarInvokeRecordHashCode(
           recordInvokeDynamic,
+          freshLocalProvider,
           localStackAllocator,
           eventConsumer,
           context,
@@ -212,12 +242,6 @@
     throw new Unreachable("Invoke dynamic needs record desugaring but could not be desugared.");
   }
 
-  private ProgramMethod synthesizeEqualsRecordMethod(
-      DexProgramClass clazz, DexMethod getFieldsAsObjects, DexMethod method) {
-    return synthesizeMethod(
-        clazz, new RecordEqualsCfCodeProvider(appView, clazz.type, getFieldsAsObjects), method);
-  }
-
   private ProgramMethod synthesizeGetFieldsAsObjectsMethod(
       DexProgramClass clazz, DexField[] fields, DexMethod method) {
     return synthesizeMethod(
@@ -243,19 +267,43 @@
     return result;
   }
 
+  private void ensureHashCodeMethod(
+      RecordInvokeDynamic recordInvokeDynamic,
+      ProgramAdditions programAdditions,
+      ProgramMethod context,
+      RecordInstructionDesugaringEventConsumer eventConsumer) {
+    DexProgramClass clazz = recordInvokeDynamic.getRecordClass();
+    DexMethod method = hashCodeMethod(clazz.type);
+    assert clazz.lookupProgramMethod(method) == null;
+    Pair<List<DexField>, List<DexType>> pair = sortedInstanceFields(clazz.instanceFields());
+    ProgramMethod hashCodeHelperMethod =
+        programAdditions.ensureMethod(
+            method,
+            () ->
+                synthesizeMethod(
+                    clazz,
+                    new RecordHashCfCodeProvider(appView, clazz.type, pair.getFirst(), false),
+                    method));
+    eventConsumer.acceptRecordHashCodeHelperMethod(hashCodeHelperMethod, context);
+  }
+
   private void ensureEqualsRecord(
       RecordInvokeDynamic recordInvokeDynamic,
       ProgramAdditions programAdditions,
       ProgramMethod context,
       RecordInstructionDesugaringEventConsumer eventConsumer) {
-    DexMethod getFieldsAsObjects =
-        ensureGetFieldsAsObjects(recordInvokeDynamic, programAdditions, context, eventConsumer);
     DexProgramClass clazz = recordInvokeDynamic.getRecordClass();
     DexMethod method = equalsRecordMethod(clazz.type);
     assert clazz.lookupProgramMethod(method) == null;
+    Pair<List<DexField>, List<DexType>> pair = sortedInstanceFields(clazz.instanceFields());
     ProgramMethod equalsHelperMethod =
         programAdditions.ensureMethod(
-            method, () -> synthesizeEqualsRecordMethod(clazz, getFieldsAsObjects, method));
+            method,
+            () ->
+                synthesizeMethod(
+                    clazz,
+                    new RecordEqCfCodeProvider(appView, clazz.type, pair.getFirst()),
+                    method));
     eventConsumer.acceptRecordEqualsHelperMethod(equalsHelperMethod, context);
   }
 
@@ -277,6 +325,11 @@
     return method;
   }
 
+  private DexMethod hashCodeMethod(DexType holder) {
+    return factory.createMethod(
+        holder, factory.createProto(factory.intType), HASH_CODE_METHOD_NAME);
+  }
+
   private DexMethod getFieldsAsObjectsMethod(DexType holder) {
     return factory.createMethod(
         holder, factory.createProto(factory.objectArrayType), GET_FIELDS_AS_OBJECTS_METHOD_NAME);
@@ -307,34 +360,110 @@
                     .disableAndroidApiLevelCheck());
   }
 
+  private boolean shouldOutlineMethods(DexClass recordClass) {
+    return recordClass.instanceFields().size() < MAX_FIELDS_FOR_OUTLINE;
+  }
+
+  public static int fixedHashCodeForEmptyRecord() {
+    return 0;
+  }
+
+  // Answers an ordered map of the fields with the type for the hashCode proto.
+  private Pair<List<DexField>, List<DexType>> sortedInstanceFields(
+      List<DexEncodedField> instanceFields) {
+    Map<DexType, List<DexField>> temp = new IdentityHashMap<>();
+    for (DexEncodedField instanceField : instanceFields) {
+      DexType protoType =
+          instanceField.getType().isBooleanType()
+              ? instanceField.getType()
+              : ValueType.fromDexType(instanceField.getType()).toDexType(factory);
+      temp.computeIfAbsent(protoType, ignored -> new ArrayList<>())
+          .add(instanceField.getReference());
+    }
+    Pair<List<DexField>, List<DexType>> pair = new Pair<>(new ArrayList<>(), new ArrayList<>());
+    for (DexType orderedSharedType : orderedSharedTypes) {
+      List<DexField> dexFields = temp.get(orderedSharedType);
+      if (dexFields != null) {
+        for (DexField dexField : dexFields) {
+          pair.getFirst().add(dexField);
+          pair.getSecond().add(orderedSharedType);
+        }
+      }
+    }
+    assert pair.getFirst().size() == instanceFields.size();
+    assert pair.getSecond().size() == instanceFields.size();
+    return pair;
+  }
+
   private List<CfInstruction> desugarInvokeRecordHashCode(
       RecordInvokeDynamic recordInvokeDynamic,
+      FreshLocalProvider freshLocalProvider,
       LocalStackAllocator localStackAllocator,
       RecordInstructionDesugaringEventConsumer eventConsumer,
       ProgramMethod context,
       MethodProcessingContext methodProcessingContext) {
-    localStackAllocator.allocateLocalStack(1);
-    DexMethod getFieldsAsObjects = getFieldsAsObjectsMethod(recordInvokeDynamic.getRecordType());
-    assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(getFieldsAsObjects) != null;
     ArrayList<CfInstruction> instructions = new ArrayList<>();
-    instructions.add(new CfStackInstruction(Dup));
-    instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false));
-    instructions.add(new CfStackInstruction(Swap));
-    instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
-    ProgramMethod programMethod =
-        synthesizeRecordHelper(
-            recordHashCodeHelperProto,
-            RecordCfMethods::RecordMethods_hashCode,
-            methodProcessingContext);
-    eventConsumer.acceptRecordHashCodeHelperMethod(programMethod, context);
-    instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, programMethod.getReference(), false));
+    DexProgramClass recordClass = recordInvokeDynamic.getRecordClass();
+    DexMethod hashCodeMethod = hashCodeMethod(recordInvokeDynamic.getRecordType());
+    if (recordClass.instanceFields().isEmpty()) {
+      localStackAllocator.allocateLocalStack(1);
+      instructions.add(new CfConstNumber(fixedHashCodeForEmptyRecord(), ValueType.INT));
+      return instructions;
+    }
+    if (shouldOutlineMethods(recordClass)) {
+      Pair<List<DexField>, List<DexType>> sortedFields =
+          sortedInstanceFields(recordClass.instanceFields());
+      int freshLocal = freshLocalProvider.getFreshLocal(ValueType.OBJECT.requiredRegisters());
+      instructions.add(new CfStackInstruction(Opcode.Dup));
+      instructions.add(new CfStore(ValueType.OBJECT, freshLocal));
+      DexField field = sortedFields.getFirst().get(0);
+      int extraStack = field.getType().getRequiredRegisters();
+      instructions.add(new CfInstanceFieldRead(field));
+      for (int i = 1; i < sortedFields.getFirst().size(); i++) {
+        instructions.add(new CfLoad(ValueType.OBJECT, freshLocal));
+        field = sortedFields.getFirst().get(i);
+        instructions.add(new CfInstanceFieldRead(field));
+        extraStack += field.getType().getRequiredRegisters();
+      }
+      localStackAllocator.allocateLocalStack(extraStack);
+      ProgramMethod method =
+          appView
+              .getSyntheticItems()
+              .createMethod(
+                  kinds -> kinds.RECORD_HELPER,
+                  methodProcessingContext.createUniqueContext(),
+                  appView,
+                  builder ->
+                      builder
+                          .setProto(
+                              factory.createProto(
+                                  factory.intType, new ArrayList<>(sortedFields.getSecond())))
+                          .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                          .setCode(
+                              methodSig ->
+                                  new RecordHashCfCodeProvider(
+                                          appView,
+                                          recordClass.getType(),
+                                          sortedFields.getFirst(),
+                                          true)
+                                      .generateCfCode())
+                          .disableAndroidApiLevelCheck());
+      eventConsumer.acceptRecordHashCodeHelperMethod(method, context);
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
+    } else {
+      assert recordClass.lookupProgramMethod(hashCodeMethod) != null;
+      instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, hashCodeMethod, false));
+    }
     return instructions;
   }
 
   private List<CfInstruction> desugarInvokeRecordEquals(RecordInvokeDynamic recordInvokeDynamic) {
-    DexMethod equalsRecord = equalsRecordMethod(recordInvokeDynamic.getRecordType());
-    assert recordInvokeDynamic.getRecordClass().lookupProgramMethod(equalsRecord) != null;
-    return Collections.singletonList(new CfInvoke(Opcodes.INVOKESPECIAL, equalsRecord, false));
+    ArrayList<CfInstruction> instructions = new ArrayList<>();
+    DexProgramClass recordClass = recordInvokeDynamic.getRecordClass();
+    DexMethod equalsMethod = equalsRecordMethod(recordInvokeDynamic.getRecordType());
+    assert recordClass.lookupProgramMethod(equalsMethod) != null;
+    instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, equalsMethod, false));
+    return instructions;
   }
 
   private List<CfInstruction> desugarInvokeRecordToString(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 10988dc..471dbf6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -7,11 +7,13 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -38,6 +40,11 @@
         returnedTypes.add(returnValue.getDynamicType(appView));
       }
     }
-    return DynamicType.join(appView, returnedTypes);
+    DynamicType dynamicReturnType = DynamicType.join(appView, returnedTypes);
+    AssumeInfoCollection assumeInfoCollection = appView.getAssumeInfoCollection();
+    if (assumeInfoCollection.get(method).getAssumeType().getNullability().isDefinitelyNotNull()) {
+      return dynamicReturnType.withNullability(Nullability.definitelyNotNull());
+    }
+    return dynamicReturnType;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 17eb9a8..b58048e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -746,17 +746,20 @@
           monitorExitBlock.close(null);
         }
 
-        for (BasicBlock block : code.blocks) {
+        BasicBlockIterator blockIterator = code.listIterator();
+        while (blockIterator.hasNext()) {
+          BasicBlock block = blockIterator.next();
           if (block.exit().isReturn()) {
-            // Since return instructions are not allowed after a throwing instruction in a block
-            // with catch handlers, the call to prepareBlocksForCatchHandlers() has already taken
-            // care of ensuring that all return blocks have no throwing instructions.
-            assert !block.canThrow();
-
             InstructionListIterator instructionIterator =
                 block.listIterator(block.getInstructions().size() - 1);
-            instructionIterator.setInsertionPosition(Position.syntheticNone());
-            instructionIterator.add(new Monitor(MonitorType.EXIT, lockValue));
+            if (block.canThrow()) {
+              BasicBlock splitBlock =
+                  instructionIterator.splitCopyCatchHandlers(code, blockIterator, options);
+              instructionIterator = splitBlock.listIterator();
+            }
+            Monitor monitorExit = new Monitor(MonitorType.EXIT, lockValue);
+            monitorExit.setPosition(Position.syntheticNone());
+            instructionIterator.add(monitorExit);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 39b5c5c..7768041 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -157,12 +157,11 @@
     }
 
     @Override
-    @SuppressWarnings("EqualsGetClass")
     public boolean equals(Object other) {
       if (this == other) {
         return true;
       }
-      if (other == null || getClass() != other.getClass()) {
+      if (!(other instanceof ArraySlotWithConstantIndex)) {
         return false;
       }
       ArraySlotWithConstantIndex arraySlot = (ArraySlotWithConstantIndex) other;
@@ -190,12 +189,11 @@
     }
 
     @Override
-    @SuppressWarnings("EqualsGetClass")
     public boolean equals(Object other) {
       if (this == other) {
         return true;
       }
-      if (other == null || getClass() != other.getClass()) {
+      if (!(other instanceof ArraySlotWithValueIndex)) {
         return false;
       }
       ArraySlotWithValueIndex arraySlot = (ArraySlotWithValueIndex) other;
@@ -220,13 +218,15 @@
     }
 
     @Override
-    @SuppressWarnings("ReferenceEquality")
     public boolean equals(Object other) {
+      if (this == other) {
+        return true;
+      }
       if (!(other instanceof FieldAndObject)) {
         return false;
       }
       FieldAndObject o = (FieldAndObject) other;
-      return o.object == object && o.field == field;
+      return o.object == object && o.field.isIdenticalTo(field);
     }
   }
 
@@ -345,14 +345,13 @@
       return appView.libraryMethodOptimizer().isFinalLibraryField(field.getDefinition());
     }
 
-    @SuppressWarnings("ReferenceEquality")
     private DexClassAndField resolveField(DexField field) {
       if (appView.enableWholeProgramOptimizations()) {
         SingleFieldResolutionResult resolutionResult =
             appView.appInfo().withLiveness().resolveField(field).asSingleFieldResolutionResult();
         return resolutionResult != null ? resolutionResult.getResolutionPair() : null;
       }
-      if (field.getHolderType() == method.getHolderType()) {
+      if (field.getHolderType().isIdenticalTo(method.getHolderType())) {
         return method.getHolder().lookupProgramField(field);
       }
       return null;
@@ -614,10 +613,10 @@
       return activeState.markClassAsInitialized(type);
     }
 
-    @SuppressWarnings("ReferenceEquality")
     private void markMostRecentInitClassForRemoval(DexType initializedType) {
       InitClass mostRecentInitClass = activeState.getMostRecentInitClass();
-      if (mostRecentInitClass != null && mostRecentInitClass.getClassValue() == initializedType) {
+      if (mostRecentInitClass != null
+          && mostRecentInitClass.getClassValue().isIdenticalTo(initializedType)) {
         instructionsToRemove
             .computeIfAbsent(mostRecentInitClass.getBlock(), ignoreKey(Sets::newIdentityHashSet))
             .add(mostRecentInitClass);
@@ -1113,10 +1112,9 @@
       clearMostRecentStaticFieldWrites();
     }
 
-    @SuppressWarnings("ReferenceEquality")
     public void clearMostRecentInstanceFieldWrite(DexField field) {
       if (mostRecentInstanceFieldWrites != null) {
-        mostRecentInstanceFieldWrites.keySet().removeIf(key -> key.field == field);
+        mostRecentInstanceFieldWrites.keySet().removeIf(key -> key.field.isIdenticalTo(field));
       }
     }
 
@@ -1329,10 +1327,9 @@
       }
     }
 
-    @SuppressWarnings("ReferenceEquality")
     public void removeNonFinalInstanceFields(DexField field) {
       if (nonFinalInstanceFieldValues != null) {
-        nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field == field);
+        nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field.isIdenticalTo(field));
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java b/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java
index 6262a76..b0af2ab 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RemoveVerificationErrorForUnknownReturnedValues.java
@@ -14,8 +14,11 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.CheckCast;
 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.Return;
 import com.android.tools.r8.ir.code.Value;
@@ -191,12 +194,26 @@
     if (returnsNeedingCast.isEmpty()) {
       return;
     }
-    InstructionListIterator iterator = code.instructionListIterator();
-    while (iterator.hasNext()) {
-      Return returnInstruction = iterator.next().asReturn();
+    BasicBlockIterator blockIterator = code.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      Return returnInstruction = block.exit().asReturn();
       if (returnInstruction == null) {
         continue;
       }
+
+      BasicBlock insertionBlock;
+      if (block.hasCatchHandlers() && block.canThrow()) {
+        InstructionListIterator instructionIterator = block.listIterator();
+        instructionIterator.nextUntil(Instruction::instructionTypeCanThrow);
+        insertionBlock =
+            instructionIterator.splitCopyCatchHandlers(code, blockIterator, appView.options());
+      } else {
+        insertionBlock = block;
+      }
+      InstructionListIterator instructionIterator =
+          insertionBlock.listIterator(insertionBlock.size() - 1);
+
       DexType returnType = context.getReturnType();
       Value returnValue = returnInstruction.returnValue();
       CheckCast checkCast =
@@ -207,12 +224,10 @@
               .setCastType(returnType)
               .setPosition(returnInstruction.getPosition())
               .build();
-      iterator.replaceCurrentInstruction(checkCast);
-      iterator.add(
-          Return.builder()
-              .setPosition(returnInstruction.getPosition())
-              .setReturnValue(checkCast.outValue())
-              .build());
+      instructionIterator.add(checkCast);
+      Instruction next = instructionIterator.next();
+      assert next.isReturn();
+      next.replaceValue(0, checkCast.outValue());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index dee6c67..1d4a1a3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -202,7 +202,7 @@
               invoke,
               affectedValues,
               (s, i, j) ->
-                  i <= 0 && i <= j && j <= s.length() ? s.substring(i, j, dexItemFactory) : null);
+                  0 <= i && i <= j && j <= s.length() ? s.substring(i, j, dexItemFactory) : null);
         }
         break;
       case 't':
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
index 5cdcc6b..89469c7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderNodeMuncher.java
@@ -385,13 +385,16 @@
           boolean canRemoveIfLastAndNoLoop =
               !isLoopingOnPath(root, currentNode, munchingState)
                   && currentNode.getSuccessors().isEmpty();
+          Instruction instruction = currentNode.asAppendNode().getInstruction();
           boolean hasKnownArgumentOrCannotBeObserved =
               appendNode.hasConstantOrNonConstantArgument()
-                  || !munchingState.oracle.canObserveStringBuilderCall(
-                      currentNode.asAppendNode().getInstruction());
+                  || !munchingState.oracle.canObserveStringBuilderCall(instruction);
+          // R8 would need to check for range overflow if removing append with sub arrays.
+          boolean canRemoveNonSub = !munchingState.oracle.isAppendWithSubArray(instruction);
           if (canRemoveIfNoInspectionOrMaterializing
               && canRemoveIfLastAndNoLoop
-              && hasKnownArgumentOrCannotBeObserved) {
+              && hasKnownArgumentOrCannotBeObserved
+              && canRemoveNonSub) {
             removeNode = true;
           }
         } else if (currentNode.isInitNode()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
index 2c43b60..d9428ac 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOracle.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import java.util.List;
@@ -39,6 +38,8 @@
 
   boolean isAppend(Instruction instruction);
 
+  boolean isAppendWithSubArray(Instruction instruction);
+
   boolean canObserveStringBuilderCall(Instruction instruction);
 
   boolean isInit(Instruction instruction);
@@ -182,7 +183,11 @@
           || factory.stringBufferMethods.isAppendMethod(invokedMethod);
     }
 
-    public boolean isAppendWithSubArray(InvokeMethodWithReceiver instruction) {
+    @Override
+    public boolean isAppendWithSubArray(Instruction instruction) {
+      if (!instruction.isInvokeMethodWithReceiver()) {
+        return false;
+      }
       DexMethod invokedMethod = instruction.asInvokeMethod().getInvokedMethod();
       return factory.stringBuilderMethods.isAppendSubArrayMethod(invokedMethod)
           || factory.stringBufferMethods.isAppendSubArrayMethod(invokedMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 2e2dd39..185cce6 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.HashMultiset;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -112,6 +113,21 @@
     ALLOW_ARGUMENT_REUSE_U8BIT_RETRY,
     ALLOW_ARGUMENT_REUSE_U16BIT;
 
+    int getMaxRegisterNumber() {
+      switch (this) {
+        case ALLOW_ARGUMENT_REUSE_U4BIT:
+          return Constants.U4BIT_MAX;
+        case ALLOW_ARGUMENT_REUSE_U8BIT:
+        case ALLOW_ARGUMENT_REUSE_U8BIT_REFINEMENT:
+        case ALLOW_ARGUMENT_REUSE_U8BIT_RETRY:
+          return Constants.U8BIT_MAX;
+        case ALLOW_ARGUMENT_REUSE_U16BIT:
+          return Constants.U16BIT_MAX;
+        default:
+          throw new Unreachable();
+      }
+    }
+
     boolean hasRegisterConstraint(LiveIntervals intervals) {
       return hasRegisterConstraint(intervals.getRegisterLimit());
     }
@@ -238,6 +254,13 @@
   // because their values can be rematerialized.
   private int[] unusedRegisters = null;
 
+  private final Timing timing;
+
+  Iterable<LiveIntervals> getArgumentLiveIntervals() {
+    return Iterables.transform(
+        code.arguments(), argument -> argument.outValue().getLiveIntervals());
+  }
+
   // Whether or not the code has a move exception instruction. Used to pin the move exception
   // register.
   private boolean hasDedicatedMoveExceptionRegister() {
@@ -276,7 +299,7 @@
     return !isDedicatedMoveExceptionRegisterInFirstLocalRegister();
   }
 
-  public LinearScanRegisterAllocator(AppView<?> appView, IRCode code) {
+  public LinearScanRegisterAllocator(AppView<?> appView, IRCode code, Timing timing) {
     this.appView = appView;
     this.code = code;
     int argumentRegisters = 0;
@@ -288,13 +311,13 @@
       }
     }
     numberOfArgumentRegisters = argumentRegisters;
+    this.timing = timing;
   }
 
   private boolean retry8BitAllocationWith4BitArgumentRegisters() {
     assert mode.is8Bit();
     assert numberOf4BitArgumentRegisters == 0;
-    if (!options().getTestingOptions().enableRegisterAllocation8BitRefinement
-        || code.context().getDefinition().getNumberOfArguments() == 0) {
+    if (code.context().getDefinition().getNumberOfArguments() == 0) {
       return false;
     }
     numberOf4BitArgumentRegisters = computeNumberOf4BitArgumentRegisters();
@@ -334,16 +357,19 @@
   @Override
   public void allocateRegisters() {
     // There are no linked values prior to register allocation.
-    assert noLinkedValues();
     assert code.isConsistentSSA(appView);
     if (this.code.method().accessFlags.isBridge() && implementationIsBridge(this.code)) {
       transformBridgeMethod();
     }
+    timing.begin("Setup");
     computeNeedsRegister();
     constrainArgumentIntervals();
     insertRangeInvokeMoves();
     ImmutableList<BasicBlock> blocks = computeLivenessInformation();
+    timing.end();
+    timing.begin("Allocate");
     performAllocation();
+    timing.end();
     assert code.isConsistentGraph(appView);
     assert mode.is4Bit() || registersUsed() == 0 || unusedRegisters != null;
     // Even if the method is reachability sensitive, we do not compute debug information after
@@ -857,15 +883,18 @@
     assert numberOf4BitArgumentRegisters == 0 || mode.is8BitRefinement();
     ArgumentReuseMode result = mode;
     this.mode = mode;
-
+    timing.begin(mode.toString());
+    timing.begin("Prepare");
     if (retry) {
       clearRegisterAssignments();
       removeSpillAndPhiMoves();
     }
 
     pinArgumentRegisters();
+    timing.end();
 
     boolean succeeded = performLinearScan(mode);
+    timing.end();
     if (succeeded) {
       InsertMovesResult insertMovesResult = insertMoves();
 
@@ -917,7 +946,7 @@
         }
       }
     } else {
-      assert mode.is4Bit();
+      assert !mode.is16Bit();
     }
 
     switch (mode) {
@@ -986,10 +1015,7 @@
     }
     Reference2IntMap<LiveIntervals> originalRegisterAssignment = new Reference2IntOpenHashMap<>();
     originalRegisterAssignment.defaultReturnValue(NO_REGISTER);
-    for (Value current = firstArgumentValue;
-        current != null;
-        current = current.getNextConsecutive()) {
-      LiveIntervals intervals = current.getLiveIntervals();
+    for (LiveIntervals intervals : getArgumentLiveIntervals()) {
       int conservativeRealRegisterEnd = realRegisterNumberFromAllocated(intervals.getRegisterEnd());
       assert !mode.hasRegisterConstraint(intervals)
           || (mode.is8BitRefinement()
@@ -1076,7 +1102,7 @@
   int unadjustedRealRegisterFromAllocated(int allocated) {
     assert allocated != NO_REGISTER;
     assert allocated >= 0;
-    if (allocated < numberOfArgumentRegisters) {
+    if (isArgumentRegister(allocated)) {
       // For the |numberOfArguments| first registers map to the correct argument register.
       return maxRegisterNumber - (numberOfArgumentRegisters - allocated - 1);
     } else if (hasDedicatedMoveExceptionRegister()
@@ -1113,15 +1139,29 @@
   private boolean performLinearScan(ArgumentReuseMode mode) {
     unhandled.addAll(liveIntervals);
 
+    timing.begin("Prelude");
     processArgumentLiveIntervals();
     boolean hasInvokeRangeLiveIntervals = splitLiveIntervalsForInvokeRange();
     allocateRegistersForMoveExceptionIntervals(hasInvokeRangeLiveIntervals);
+    timing.end();
+
+    timing.begin("Argument linked");
+    for (LiveIntervals argumentLiveIntervals : getArgumentLiveIntervals()) {
+      allocateRegistersForInvokeRangeSplits(argumentLiveIntervals);
+    }
+    timing.end();
 
     // Go through each unhandled live interval and find a register for it.
+    timing.begin("Process all unhandled");
     while (!unhandled.isEmpty()) {
       assert invariantsHold(mode);
 
       LiveIntervals unhandledInterval = unhandled.poll();
+      if (unhandledInterval.isHandled()) {
+        assert unhandledInterval.hasRegister();
+        continue;
+      }
+
       setHintForDestRegOfCheckCast(unhandledInterval);
       setHintToPromote2AddrInstruction(unhandledInterval);
 
@@ -1129,38 +1169,43 @@
       // consecutive arguments now and add hints to the live intervals leading up to this
       // invoke/range. This looks forward and propagate hints backwards to avoid many moves in
       // connection with ranged invokes.
+      timing.begin("Linked");
       allocateRegistersForInvokeRangeSplits(unhandledInterval);
-      if (unhandledInterval.getRegister() != NO_REGISTER) {
+      timing.end();
+      if (unhandledInterval.hasRegister()) {
         // The value itself is in the chain that has now gotten registers allocated.
         continue;
       }
 
+      timing.begin("Advance state");
       advanceStateToLiveIntervals(unhandledInterval);
+      timing.end();
 
       // Perform the actual allocation.
-      if (!allocateSingleInterval(unhandledInterval)) {
+      timing.begin("Alloc single");
+      if (!allocateSingleInterval(unhandledInterval)
+          || maxRegisterNumber > mode.getMaxRegisterNumber()) {
+        timing.end();
+        timing.end();
         return false;
       }
-
+      timing.end();
       expiredHere.clear();
     }
+    timing.end();
     assert invariantsHold(mode);
     return true;
   }
 
   private void processArgumentLiveIntervals() {
-    for (Value argumentValue = firstArgumentValue;
-        argumentValue != null;
-        argumentValue = argumentValue.getNextConsecutive()) {
-      LiveIntervals argumentInterval = argumentValue.getLiveIntervals();
+    for (LiveIntervals argumentInterval : getArgumentLiveIntervals()) {
       assert argumentInterval.hasRegister();
-      unhandled.remove(argumentInterval);
+      argumentInterval.setHandled();
       if (!mode.hasRegisterConstraint(argumentInterval)) {
         // All the argument intervals are active in the beginning and have preallocated registers.
         active.add(argumentInterval);
       } else if (mode.is8BitRefinement()
-          && argumentInterval.getRegister() + argumentValue.requiredRegisters()
-              <= numberOf4BitArgumentRegisters) {
+          && argumentInterval.getRegisterEnd() < numberOf4BitArgumentRegisters) {
         active.add(argumentInterval);
       } else {
         // Treat the argument interval as spilled which will require a load to a different
@@ -1173,15 +1218,16 @@
             LiveIntervals split;
             if (argumentInterval.numberOfUsesWithConstraint() == 1) {
               // If there is only one register-constrained use, split before that one use.
-              split = argumentInterval.splitBefore(use.getPosition());
+              split = argumentInterval.splitBefore(use.getPosition(), mode);
             } else {
               // If there are multiple register-constrained users, split right after the definition
               // to make it more likely that arguments get in usable registers from the start.
               // TODO(christofferqa): This is not great if there are many arguments with multiple
               // constrained uses, since we fill up all the low registers immediately, making it
               // likely that we will have to kick them back out before they are actually used.
-              split = argumentInterval
-                  .splitBefore(argumentInterval.getValue().definition.getNumber() + 1);
+              split =
+                  argumentInterval.splitBefore(
+                      argumentInterval.getValue().definition.getNumber() + 1, mode);
             }
             unhandled.add(split);
           }
@@ -1209,13 +1255,13 @@
       MoveException moveException = block.entry().asMoveException();
       LiveIntervals intervals = moveException.outValue().getLiveIntervals();
       if (intervals.getValue().hasAnyUsers()) {
-        LiveIntervals split = intervals.splitAfter(intervals.getValue().getDefinition());
+        LiveIntervals split = intervals.splitAfter(intervals.getValue().getDefinition(), mode);
         unhandled.add(split);
       }
       if (intervals.getStart() < moveException.getNumber()) {
-        intervals = intervals.splitBefore(moveException);
+        intervals = intervals.splitBefore(moveException, mode);
       } else {
-        unhandled.remove(intervals);
+        intervals.setHandled();
       }
       moveExceptionIntervals.add(intervals);
       intervals.setRegister(getMoveExceptionRegister());
@@ -1237,12 +1283,12 @@
         if (overlappingIntervals.getStart() == toGapPosition(invoke.getNumber())) {
           invokeRangeIntervals = overlappingIntervals;
         } else {
-          invokeRangeIntervals = overlappingIntervals.splitBefore(invoke);
+          invokeRangeIntervals = overlappingIntervals.splitBefore(invoke, mode);
           unhandled.add(invokeRangeIntervals);
         }
-        invokeRangeIntervals.setIsInvokeRangeIntervals();
+        invokeRangeIntervals.setIsInvokeRangeIntervals(invoke);
         if (invoke.getNumber() + 1 < invokeRangeIntervals.getEnd()) {
-          LiveIntervals successorIntervals = invokeRangeIntervals.splitAfter(invoke);
+          LiveIntervals successorIntervals = invokeRangeIntervals.splitAfter(invoke, mode);
           unhandled.add(successorIntervals);
         }
         hasInvokeRangeLiveIntervals = true;
@@ -1282,7 +1328,7 @@
         }
       } else if (!activeIntervals.overlapsPosition(start)) {
         activeIterator.remove();
-        assert activeIntervals.getRegister() != NO_REGISTER;
+        assert activeIntervals.hasRegister();
         inactive.add(activeIntervals);
         freeOccupiedRegistersForIntervals(activeIntervals);
       }
@@ -1302,7 +1348,7 @@
         }
       } else if (inactiveIntervals.overlapsPosition(start)) {
         inactiveIterator.remove();
-        assert inactiveIntervals.getRegister() != NO_REGISTER;
+        assert inactiveIntervals.hasRegister();
         active.add(inactiveIntervals);
         takeFreeRegistersForIntervals(inactiveIntervals);
       }
@@ -1367,11 +1413,8 @@
   }
 
   private boolean verifyRegisterAssignmentNotConflictingWithArgument(LiveIntervals interval) {
-    assert interval.getRegister() != NO_REGISTER;
-    for (Value argumentValue = firstArgumentValue;
-        argumentValue != null;
-        argumentValue = argumentValue.getNextConsecutive()) {
-      LiveIntervals argumentIntervals = argumentValue.getLiveIntervals();
+    assert interval.hasRegister();
+    for (LiveIntervals argumentIntervals : getArgumentLiveIntervals()) {
       assert interval.getSplitParent() == argumentIntervals
           || !isPinnedArgumentRegister(argumentIntervals)
           || !interval.hasConflictingRegisters(argumentIntervals)
@@ -1431,21 +1474,19 @@
    */
   @SuppressWarnings("JdkObsolete")
   private void allocateRegistersForInvokeRangeSplits(LiveIntervals unhandledIntervals) {
-    Value value = unhandledIntervals.getValue();
-    for (Invoke invoke : value.<Invoke>uniqueUsers(this::needsInvokeRangeLiveIntervals)) {
-      LiveIntervals overlappingIntervals =
-          unhandledIntervals.getSplitParent().getSplitCovering(invoke);
-      if (overlappingIntervals.hasRegister()) {
-        assert invoke.arguments().stream()
-            .allMatch(
-                invokeArgument -> {
-                  LiveIntervals overlappingInvokeArgumentIntervals =
-                      invokeArgument.getLiveIntervals().getSplitCovering(invoke);
-                  assert overlappingInvokeArgumentIntervals.hasRegister();
-                  return true;
-                });
-        continue;
-      }
+    if (!unhandledIntervals.isSplitParent()) {
+      return;
+    }
+    timing.begin("Extract splits");
+    List<LiveIntervals> invokeRangeIntervals =
+        ListUtils.filter(
+            unhandledIntervals.getSplitChildren(),
+            split -> split.isInvokeRangeIntervals() && !split.hasRegister());
+    timing.end();
+    timing.begin("Process splits");
+    for (LiveIntervals split : invokeRangeIntervals) {
+      timing.begin("Extract list");
+      Invoke invoke = split.getIsInvokeRangeIntervals();
       List<LiveIntervals> intervalsList =
           ListUtils.map(
               invoke.arguments(),
@@ -1458,13 +1499,18 @@
                     || overlappingInvokeArgumentIntervals.getEnd() == invoke.getNumber() + 1;
                 return overlappingInvokeArgumentIntervals;
               });
+      timing.end();
+      timing.begin("Prelude");
 
       // Save the current register allocation state so we can restore it at the end.
+      timing.begin("Copy free registers");
       IntSortedSet savedFreeRegisters = new IntRBTreeSet(freeRegisters);
       int savedMaxRegisterNumber = maxRegisterNumber;
+      timing.end();
 
       // Simulate adding all the active intervals to the inactive set by blocking their register if
       // they overlap with any of the invoke/range intervals.
+      timing.begin("Overlaps active");
       for (LiveIntervals active : active) {
         // We could allow the use of all the currently active registers for the ranged invoke (by
         // adding the registers for all the active intervals to freeRegisters here). That could lead
@@ -1480,10 +1526,16 @@
           freeOccupiedRegistersForIntervals(active);
         }
       }
+      timing.end();
 
-      unhandled.removeAll(intervalsList);
+      timing.begin("Remove intervals from unhandled");
+      intervalsList.forEach(LiveIntervals::setHandled);
+      timing.end();
+      timing.end();
+      timing.begin("Allocate");
       allocateLinkedIntervals(intervalsList, invoke);
-
+      timing.end();
+      timing.begin("Postlude");
       // Restore the register allocation state.
       freeRegisters = savedFreeRegisters;
       // In case maxRegisterNumber has changed, update freeRegisters.
@@ -1492,12 +1544,15 @@
       }
       // Move all the argument intervals to the inactive set.
       inactive.addAll(intervalsList);
+      timing.end();
     }
+    timing.end();
   }
 
   private void allocateLinkedIntervals(List<LiveIntervals> intervalsList, Invoke invoke) {
     LiveIntervals start = ListUtils.first(intervalsList);
 
+    timing.begin("Prelude");
     boolean consecutiveArguments =
         IterableUtils.allWithPrevious(
             intervalsList,
@@ -1507,6 +1562,7 @@
                         == previous.getSplitParent());
     boolean consecutivePinnedArguments =
         consecutiveArguments && Iterables.all(intervalsList, this::isPinnedArgumentRegister);
+    timing.end();
 
     int nextRegister;
     if (consecutivePinnedArguments) {
@@ -1515,6 +1571,7 @@
     } else {
       // Ensure that there is a free register for the out value (or two consecutive registers if
       // wide).
+      timing.begin("Not consecutive pinned args");
       int numberOfRegisters = getNumberOfRequiredRegisters(intervalsList);
       int numberOfOutRegisters = invoke.hasOutValue() ? invoke.outValue().requiredRegisters() : 0;
       if (numberOfOutRegisters > 0
@@ -1532,32 +1589,43 @@
 
       // Exclude the registers that overlap the start of one of the live ranges we are going to
       // assign registers to now.
+      timing.begin("Overlaps inactive");
       for (LiveIntervals inactiveIntervals : inactive) {
-        if (Iterables.any(intervalsList, inactiveIntervals::overlaps)) {
+        if (inactiveIntervals.isInvokeRangeIntervals()) {
+          // This is the live intervals for another invoke-range, these can never overlap.
+          assert !Iterables.any(intervalsList, inactiveIntervals::overlaps);
+          continue;
+        }
+        // All of the invoke-range live intervals usually start at the same instruction number.
+        if (inactiveIntervals.overlapsAnyInvokeRangeIntervals(intervalsList)) {
           excludeRegistersForInterval(inactiveIntervals);
         }
       }
+      timing.end();
 
+      timing.begin("Register range is free");
       if (consecutiveArguments
           && registerRangeIsFree(start.getSplitParent().getRegister(), numberOfRegisters)) {
         // For consecutive arguments we always to use the input argument registers, if they are
         // free.
+        timing.end();
         nextRegister = start.getSplitParent().getRegister();
       } else {
+        timing.end();
         // Exclude the pinned argument registers for which there exists a split that overlaps with
         // one of the inputs to the invoke-range instruction.
-        for (Value argument = firstArgumentValue;
-            argument != null;
-            argument = argument.getNextConsecutive()) {
-          LiveIntervals argumentLiveIntervals = argument.getLiveIntervals();
+        timing.begin("Exclude pinned args");
+        for (LiveIntervals argumentLiveIntervals : getArgumentLiveIntervals()) {
           if (isPinnedArgumentRegister(argumentLiveIntervals)
               && liveIntervalsOverlappingAnyOf(argumentLiveIntervals, intervalsList)) {
             excludeRegistersForInterval(argumentLiveIntervals);
           }
         }
+        timing.end();
         // Exclude move exception register if the first interval overlaps a move exception interval.
         // It is not necessary to check the remaining consecutive intervals, since we always use
         // register 0 (after remapping) for the argument register.
+        timing.begin("Exclude move exc");
         if (hasDedicatedMoveExceptionRegister()) {
           boolean canUseMoveExceptionRegisterForLinkedIntervals =
               isDedicatedMoveExceptionRegisterInFirstLocalRegister()
@@ -1566,33 +1634,22 @@
             freeRegisters.remove(getMoveExceptionRegister());
           }
         }
+        timing.end();
+
         // Select registers.
         nextRegister = getFreeConsecutiveRegisters(numberOfRegisters);
       }
+      timing.end();
     }
 
     // Assign registers.
+    timing.begin("Assign regs");
     for (LiveIntervals current : intervalsList) {
       current.setRegister(nextRegister);
       assert verifyRegisterAssignmentNotConflictingWithArgument(current);
       nextRegister += current.requiredRegisters();
     }
-
-    // Add hints.
-    for (LiveIntervals intervals : intervalsList) {
-      LiveIntervals parentIntervals = intervals.getSplitParent();
-      parentIntervals.setHint(intervals, unhandled);
-      for (LiveIntervals siblingIntervals : parentIntervals.getSplitChildren()) {
-        if (siblingIntervals != intervals && !siblingIntervals.hasRegister()) {
-          siblingIntervals.setHint(intervals, unhandled);
-        }
-      }
-      Value value = intervals.getValue();
-      if (value.isDefinedByInstructionSatisfying(Instruction::isMove)) {
-        Move move = value.getDefinition().asMove();
-        move.src().getLiveIntervals().setHint(intervals, unhandled);
-      }
-    }
+    timing.end();
   }
 
   private int getNumberOfRequiredRegisters(List<LiveIntervals> intervalsList) {
@@ -1606,10 +1663,15 @@
   // Returns true if intervals has a split, which overlaps with any of the live intervals in the
   // given list.
   private boolean liveIntervalsOverlappingAnyOf(
-      LiveIntervals intervals, List<LiveIntervals> intervalsList) {
-    assert intervals == intervals.getSplitParent();
-    for (LiveIntervals split : intervals.getSplitChildren()) {
-      if (Iterables.any(intervalsList, split::overlaps)) {
+      LiveIntervals argumentLiveIntervals, List<LiveIntervals> intervalsList) {
+    assert argumentLiveIntervals == argumentLiveIntervals.getSplitParent();
+    for (LiveIntervals intervals : intervalsList) {
+      if (intervals.getValue() == argumentLiveIntervals.getValue()) {
+        return true;
+      }
+    }
+    for (LiveIntervals split : argumentLiveIntervals.getSplitChildren()) {
+      if (split.overlapsAnyInvokeRangeIntervals(intervalsList)) {
         return true;
       }
     }
@@ -1629,7 +1691,7 @@
   }
 
   private int getSpillRegister(LiveIntervals intervals, IntList excludedRegisters) {
-    if (intervals.isArgumentInterval()) {
+    if (isPinnedArgumentRegister(intervals)) {
       // Arguments are always in the argument registers, so for arguments just use that register
       // for the unconstrained prefix. For everything else, get a spill register.
       return intervals.getSplitParent().getRegister();
@@ -1690,7 +1752,7 @@
     //
     // Note that this is *not* guaranteed when overlapsInactiveIntervals is null, because it is
     // possible that some live ranges of the argument are still in the unhandled set.
-    if (register < numberOfArgumentRegisters) {
+    if (isArgumentRegister(register)) {
       // Find the first argument value that uses the given register.
       LiveIntervals argumentLiveIntervals = firstArgumentValue.getLiveIntervals();
       while (!argumentLiveIntervals.usesRegister(register, intervals.getType().isWide())) {
@@ -2001,9 +2063,12 @@
     assert freePositionsAreConsistentWithFreeRegisters(freePositions, registerConstraint);
 
     // Attempt to use register hints.
+    timing.begin("Try hint");
     if (useRegisterHint(unhandledInterval, registerConstraint, freePositions, needsRegisterPair)) {
+      timing.end();
       return true;
     }
+    timing.end();
 
     // Get the register (pair) that is free the longest. That is the register with the largest
     // free position.
@@ -2039,11 +2104,18 @@
       // of finding another candidate to spill via allocateBlockedRegister.
       assert unhandledInterval.hasUses();
       if (!unhandledInterval.getUses().first().hasConstraint()) {
-        int nextConstrainedPosition = unhandledInterval.firstUseWithConstraint(mode).getPosition();
-        int register = getSpillRegister(unhandledInterval, null);
-        LiveIntervals split = unhandledInterval.splitBefore(nextConstrainedPosition);
-        assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
-        unhandled.add(split);
+        if (mode.hasRegisterConstraint(unhandledInterval)) {
+          int nextConstrainedPosition =
+              unhandledInterval.firstUseWithConstraint(mode).getPosition();
+          int register = getSpillRegister(unhandledInterval, null);
+          LiveIntervals split = unhandledInterval.splitBefore(nextConstrainedPosition, mode);
+          assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
+          unhandled.add(split);
+        } else {
+          assert unhandledInterval.firstUseWithConstraint(mode) == null;
+          int register = getSpillRegister(unhandledInterval, null);
+          assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
+        }
       } else {
         allocateBlockedRegister(unhandledInterval, registerConstraint);
       }
@@ -2063,7 +2135,7 @@
         // The candidate is free for the beginning of an interval. We split the interval
         // and use the register for as long as we can.
         int registerConstraintBeforeSplit = unhandledInterval.getRegisterLimit();
-        LiveIntervals split = unhandledInterval.splitBefore(largestFreePosition);
+        LiveIntervals split = unhandledInterval.splitBefore(largestFreePosition, mode);
         assert split != unhandledInterval;
         unhandled.add(split);
 
@@ -2103,10 +2175,9 @@
         firstArgumentValue.getLiveIntervals().forEachRegister(freePositions::setBlocked);
       }
       // But not any of the other argument registers.
-      for (Value argument = firstArgumentValue;
-          argument != null;
-          argument = argument.getNextConsecutive()) {
-        assert !isPinnedArgumentRegister(argument.getLiveIntervals()) || argument.isThis();
+      for (LiveIntervals argumentIntervals : getArgumentLiveIntervals()) {
+        assert !isPinnedArgumentRegister(argumentIntervals)
+            || argumentIntervals.getValue().isThis();
       }
     } else {
       // Generally argument reuse is not allowed and we block all the argument registers so that
@@ -2119,10 +2190,8 @@
       if (mode.is8BitRefinement()) {
         assert numberOf4BitArgumentRegisters > 0;
         int remainingNumberOf4BitArgumentRegisters = numberOf4BitArgumentRegisters;
-        for (Value argumentValue = firstArgumentValue;
-            argumentValue != null;
-            argumentValue = argumentValue.getNextConsecutive()) {
-          int requiredRegisters = argumentValue.requiredRegisters();
+        for (LiveIntervals argumentLiveIntervals : getArgumentLiveIntervals()) {
+          int requiredRegisters = argumentLiveIntervals.requiredRegisters();
           remainingNumberOf4BitArgumentRegisters -= requiredRegisters;
           if (remainingNumberOf4BitArgumentRegisters < 0) {
             // Block all subsequent argument registers.
@@ -2131,7 +2200,7 @@
           // Block this argument register if there is any overlap between the two live intervals.
           // TODO(b/374266460): Allow using the argument register even when there are overlapping
           //  live intervals.
-          if (argumentValue.getLiveIntervals().anySplitOverlaps(unhandledInterval)) {
+          if (argumentLiveIntervals.anySplitOverlaps(unhandledInterval)) {
             for (int j = 0; j < requiredRegisters; j++) {
               freePositions.setBlocked(i + j);
             }
@@ -2139,7 +2208,7 @@
           i += requiredRegisters;
         }
       }
-      for (; i < numberOfArgumentRegisters && i <= registerConstraint; i++) {
+      for (; isArgumentRegister(i) && i <= registerConstraint; i++) {
         freePositions.setBlocked(i);
       }
     }
@@ -2330,10 +2399,8 @@
       return false;
     }
     if (isArgumentRegister(candidate)) {
-      for (Value argument = firstArgumentValue;
-          argument != null;
-          argument = argument.getNextConsecutive()) {
-        if (isPinnedArgument(argument)) {
+      for (LiveIntervals argumentLiveIntervals : getArgumentLiveIntervals()) {
+        if (isPinnedArgumentRegister(argumentLiveIntervals)) {
           return false;
         }
       }
@@ -2378,7 +2445,7 @@
     if (!expiredHere.isEmpty()) {
       return false;
     }
-    LiveIntervals split = blockingInterval.splitBefore(unhandledInterval.getStart());
+    LiveIntervals split = blockingInterval.splitBefore(unhandledInterval.getStart(), mode);
     freeOccupiedRegistersForIntervals(blockingInterval);
     assignFreeRegisterToUnhandledInterval(unhandledInterval, blockingInterval.getRegister());
     active.remove(blockingInterval);
@@ -2621,7 +2688,9 @@
           if (activeRegister + i <= registerConstraint) {
             int unhandledStart = unhandledInterval.getStart();
             usePositions.set(
-                activeRegister + i, intervals.firstUseAfter(unhandledStart), intervals);
+                activeRegister + i,
+                intervals.firstUseWithConstraintAfter(unhandledStart, mode),
+                intervals);
           }
         }
       }
@@ -2633,7 +2702,8 @@
       if (inactiveRegister <= registerConstraint && intervals.overlaps(unhandledInterval)) {
         for (int i = 0; i < intervals.requiredRegisters(); i++) {
           if (inactiveRegister + i <= registerConstraint) {
-            int firstUse = intervals.firstUseAfter(unhandledInterval.getStart());
+            int firstUse =
+                intervals.firstUseWithConstraintAfter(unhandledInterval.getStart(), mode);
             if (firstUse < usePositions.get(inactiveRegister + i)) {
               usePositions.set(inactiveRegister + i, firstUse, intervals);
             }
@@ -2644,7 +2714,7 @@
 
     // Disallow the reuse of argument registers by always treating them as being used
     // at instruction number 0.
-    for (int i = 0; i < numberOfArgumentRegisters; i++) {
+    for (int i = 0; isArgumentRegister(i); i++) {
       usePositions.setBlocked(i);
     }
 
@@ -2723,7 +2793,7 @@
       // All active and inactive intervals are used before current. Therefore, it is best to spill
       // current itself.
       int splitPosition = unhandledInterval.getFirstUse();
-      LiveIntervals split = unhandledInterval.splitBefore(splitPosition);
+      LiveIntervals split = unhandledInterval.splitBefore(splitPosition, mode);
       assert split != unhandledInterval;
       // Experiments show that it has a positive impact on code size to use a fresh register here.
       int registerNumber = getNewSpillRegister(unhandledInterval);
@@ -2743,7 +2813,7 @@
         assignRegisterAndSpill(unhandledInterval, candidate, needsRegisterPair);
       } else {
         // Spilling only makes a register available for the first part of current.
-        LiveIntervals splitChild = unhandledInterval.splitBefore(blockedPosition);
+        LiveIntervals splitChild = unhandledInterval.splitBefore(blockedPosition, mode);
         unhandled.add(splitChild);
         assignRegisterAndSpill(unhandledInterval, candidate, needsRegisterPair);
       }
@@ -2774,24 +2844,11 @@
 
   protected void splitOverlappingInactiveIntervals(
       LiveIntervals unhandledInterval, int candidate, boolean candidateIsWide) {
-    List<LiveIntervals> newInactive = new ArrayList<>();
     Iterator<LiveIntervals> inactiveIterator = inactive.iterator();
     while (inactiveIterator.hasNext()) {
       LiveIntervals intervals = inactiveIterator.next();
       if (intervals.usesRegister(candidate, candidateIsWide)
           && intervals.overlaps(unhandledInterval)) {
-        if (intervals.isLinked() && !intervals.isArgumentInterval()) {
-          // If the inactive register is linked but not an argument, it needs to get the
-          // same register again at the next use after the start of the unhandled interval.
-          // If there are no such uses, we can use a different register for the remainder
-          // of the inactive interval and therefore do not have to split here.
-          int nextUsePosition = intervals.firstUseAfter(unhandledInterval.getStart());
-          if (nextUsePosition != Integer.MAX_VALUE) {
-            LiveIntervals split = intervals.splitBefore(nextUsePosition);
-            split.setRegister(intervals.getRegister());
-            newInactive.add(split);
-          }
-        }
         if (intervals.getStart() > unhandledInterval.getStart()) {
           // The inactive live intervals hasn't started yet. Clear the temporary register
           // assignment and move back to unhandled for register reassignment.
@@ -2801,17 +2858,16 @@
         } else {
           // The inactive live intervals is in a live range hole. Split the interval and
           // put the ranges after the hole into the unhandled set for register reassignment.
-          LiveIntervals split = intervals.splitBefore(unhandledInterval.getStart());
+          LiveIntervals split = intervals.splitBefore(unhandledInterval.getStart(), mode);
           unhandled.add(split);
         }
       }
     }
-    inactive.addAll(newInactive);
   }
 
   private void spillOverlappingActiveIntervals(
       LiveIntervals unhandledInterval, int candidate, boolean candidateIsWide) {
-    assert unhandledInterval.getRegister() == NO_REGISTER;
+    assert !unhandledInterval.hasRegister();
     assert atLeastOneOfRegistersAreTaken(candidate, candidateIsWide);
     // Registers that we cannot choose for spilling.
     IntList excludedRegisters = new IntArrayList(candidateIsWide ? 2 : 1);
@@ -2838,12 +2894,12 @@
         // because we might otherwise end up spilling to the current registers of intervals,
         // depending on getSpillRegister.
         freeOccupiedRegistersForIntervals(intervals);
-        LiveIntervals splitChild = intervals.splitBefore(unhandledInterval.getStart());
+        LiveIntervals splitChild = intervals.splitBefore(unhandledInterval.getStart(), mode);
         assignRegister(splitChild, registerNumber);
         splitChild.setSpilled(true);
         takeFreeRegistersForIntervals(splitChild);
-        assert splitChild.getRegister() != NO_REGISTER;
-        assert intervals.getRegister() != NO_REGISTER;
+        assert splitChild.hasRegister();
+        assert intervals.hasRegister();
         newActive.add(splitChild);
         // If the constant is split before its first actual use, mark the constant as being
         // spilled. That will allows us to remove it afterwards if it is rematerializable.
@@ -2852,20 +2908,15 @@
             && intervals.getUses().size() == 1) {
           intervals.setSpilled(true);
         }
-        if (splitChild.getUses().size() > 0) {
-          if (splitChild.isLinked() && !splitChild.isArgumentInterval()) {
-            // Spilling a value with a pinned register. We need to move back at the next use.
-            LiveIntervals splitOfSplit = splitChild.splitBefore(splitChild.getFirstUse());
-            splitOfSplit.setRegister(intervals.getRegister());
-            inactive.add(splitOfSplit);
-          } else if (intervals.getValue().isConstNumber()) {
+        if (splitChild.hasUses()) {
+          if (intervals.getValue().isConstNumber()) {
             // TODO(ager): Do this for all constants. Currently we only rematerialize const
-            // number and therefore we only do it for numbers at this point.
+            //  number and therefore we only do it for numbers at this point.
             splitRangesForSpilledConstant(splitChild, registerNumber);
           } else if (intervals.isArgumentInterval()) {
             splitRangesForSpilledArgument(splitChild);
           } else {
-            splitRangesForSpilledInterval(splitChild, registerNumber);
+            splitRangesForSpilledInterval(splitChild);
           }
         }
       }
@@ -2881,44 +2932,34 @@
     // that is yet, and therefore we split before the next use to make sure we get a usable
     // register at the next use.
     if (!spilled.getUses().isEmpty()) {
-      LiveIntervals split = spilled.splitBefore(spilled.getUses().first().getPosition());
-      unhandled.add(split);
+      LiveIntervals split = spilled.splitBefore(spilled.getUses().first().getPosition(), mode);
+      if (split != spilled) {
+        unhandled.add(split);
+      } else {
+        spilled.setRegister(spilled.getSplitParent().getRegister());
+      }
     }
   }
 
-  private void splitRangesForSpilledInterval(LiveIntervals spilled, int registerNumber) {
+  private void splitRangesForSpilledInterval(LiveIntervals spilled) {
     // Spilling a non-pinned, non-rematerializable value. We use the value in the spill
     // register for as long as possible to avoid further moves.
     assert spilled.isSpilled();
     assert !spilled.getValue().isConstNumber();
-    assert !spilled.isLinked() || spilled.isArgumentInterval();
-    boolean isSpillingToArgumentRegister =
-        (spilled.isArgumentInterval() || registerNumber < numberOfArgumentRegisters);
-    if (isSpillingToArgumentRegister) {
-      if (mode.is8Bit()) {
-        registerNumber = Constants.U8BIT_MAX;
+    LiveIntervalsUse firstUseWithConstraint = spilled.firstUseWithConstraint(mode);
+    if (firstUseWithConstraint != null) {
+      int register = spilled.getRegister();
+      LiveIntervals splitOfSplit = spilled.splitBefore(firstUseWithConstraint.getPosition(), mode);
+      if (splitOfSplit != spilled) {
+        unhandled.add(splitOfSplit);
       } else {
-        registerNumber = Constants.U16BIT_MAX;
+        assert !spilled.hasRegister();
+        spilled.setRegister(register);
+        if (spilled.hasUses()) {
+          spilled.setSpilled(false);
+        }
       }
     }
-    LiveIntervalsUse firstUseWithLowerLimit = null;
-    boolean hasUsesBeforeFirstUseWithLowerLimit = false;
-    int highestRegisterNumber = registerNumber + spilled.requiredRegisters() - 1;
-    for (LiveIntervalsUse use : spilled.getUses()) {
-      if (highestRegisterNumber > use.getLimit()) {
-        firstUseWithLowerLimit = use;
-        break;
-      } else {
-        hasUsesBeforeFirstUseWithLowerLimit = true;
-      }
-    }
-    if (hasUsesBeforeFirstUseWithLowerLimit) {
-      spilled.setSpilled(false);
-    }
-    if (firstUseWithLowerLimit != null) {
-      LiveIntervals splitOfSplit = spilled.splitBefore(firstUseWithLowerLimit.getPosition());
-      unhandled.add(splitOfSplit);
-    }
   }
 
   private void splitRangesForSpilledConstant(LiveIntervals spilled, int spillRegister) {
@@ -2929,13 +2970,16 @@
     // as much as possible.
     assert spilled.isSpilled();
     assert spilled.getValue().isConstNumber();
-    assert !spilled.isLinked() || spilled.isArgumentInterval();
     // Do not split range if constant is reused by one of the eleven following instruction.
     int maxGapSize = 11 * INSTRUCTION_NUMBER_DELTA;
-    if (!spilled.getUses().isEmpty()) {
+    LiveIntervalsUse firstUseWithConstraint = spilled.firstUseWithConstraint(mode);
+    if (firstUseWithConstraint != null) {
       // Split at first use after the spill position and add to unhandled to get a register
       // assigned for rematerialization.
-      LiveIntervals split = spilled.splitBefore(spilled.getFirstUse());
+      LiveIntervals split = spilled.splitBefore(firstUseWithConstraint.getPosition(), mode);
+      if (spilled.hasUses()) {
+        spilled.setSpilled(false);
+      }
       unhandled.add(split);
       // Now repeatedly split for each use that is more than maxGapSize away from the previous use.
       boolean changed = true;
@@ -2946,14 +2990,14 @@
           if (use.getPosition() - previousUse > maxGapSize) {
             // Found a use that is more than gap size away from the previous use. Split after
             // the previous use.
-            split = split.splitBefore(previousUse + INSTRUCTION_NUMBER_DELTA);
+            split = split.splitBefore(previousUse + INSTRUCTION_NUMBER_DELTA, mode);
             // If the next use is not at the start of the new split, we split again at the next use
             // and spill the gap.
             if (toGapPosition(use.getPosition()) > split.getStart()) {
               assignRegister(split, spillRegister);
               split.setSpilled(true);
               inactive.add(split);
-              split = split.splitBefore(use.getPosition());
+              split = split.splitBefore(use.getPosition(), mode);
             }
             // |split| now starts at the next use - add it to unhandled to get a register
             // assigned for rematerialization.
@@ -2965,6 +3009,8 @@
           previousUse = use.getPosition();
         }
       }
+    } else if (spilled.hasUses()) {
+      spilled.setSpilled(false);
     }
   }
 
@@ -3137,7 +3183,7 @@
       return false;
     }
     assert intervals.hasRegister();
-    if (intervals.getRegister() >= numberOfArgumentRegisters) {
+    if (!isArgumentRegister(intervals.getRegister())) {
       return false;
     }
     // An argument register could be moved to another argument register.
@@ -3161,19 +3207,7 @@
       instructionNumber = instruction.getNumber();
     }
     if (value.getLiveIntervals() == null) {
-      Value current = value.getStartOfConsecutive();
-      LiveIntervals intervals = new LiveIntervals(current);
-      while (true) {
-        liveIntervals.add(intervals);
-        Value next = current.getNextConsecutive();
-        if (next == null) {
-          break;
-        }
-        LiveIntervals nextIntervals = new LiveIntervals(next);
-        intervals.link(nextIntervals);
-        current = next;
-        intervals = nextIntervals;
-      }
+      liveIntervals.add(new LiveIntervals(value));
     }
     LiveIntervals intervals = value.getLiveIntervals();
     if (firstInstructionInBlock <= instructionNumber &&
@@ -3493,10 +3527,13 @@
 
   private static boolean argumentsAreAlreadyLinked(Invoke invoke) {
     Iterator<Value> it = invoke.arguments().iterator();
-    Value current = it.next();
+    Argument current = it.next().getDefinitionOrNull(Instruction::isArgument);
+    if (current == null) {
+      return false;
+    }
     while (it.hasNext()) {
-      Value next = it.next();
-      if (!current.isLinked() || current.getNextConsecutive() != next) {
+      Argument next = it.next().getDefinitionOrNull(Instruction::isArgument);
+      if (current.getNext() != next) {
         return false;
       }
       current = next;
@@ -3522,7 +3559,6 @@
       Value last = firstArgumentValue = arguments.get(0);
       for (int i = 1; i < arguments.size(); ++i) {
         Value next = arguments.get(i);
-        last.linkTo(next);
         last.getLiveIntervals().link(next.getLiveIntervals());
         last = next;
       }
@@ -3568,12 +3604,9 @@
     if (firstArgumentValue != null) {
       increaseCapacity(numberOfArgumentRegisters - 1, true);
       int register = 0;
-      for (Value current = firstArgumentValue;
-          current != null;
-          current = current.getNextConsecutive()) {
-        LiveIntervals argumentLiveInterval = current.getLiveIntervals();
-        assignRegister(argumentLiveInterval, register);
-        register += current.requiredRegisters();
+      for (LiveIntervals argumentLiveIntervals : getArgumentLiveIntervals()) {
+        assignRegister(argumentLiveIntervals, register);
+        register += argumentLiveIntervals.requiredRegisters();
       }
     }
   }
@@ -3608,8 +3641,8 @@
       freeRegistersWithDesiredOrdering =
           new IntRBTreeSet(
               (Integer x, Integer y) -> {
-                boolean xIsArgument = x < numberOfArgumentRegisters;
-                boolean yIsArgument = y < numberOfArgumentRegisters;
+                boolean xIsArgument = isArgumentRegister(x);
+                boolean yIsArgument = isArgumentRegister(y);
                 // If x is an argument and y is not, then prioritize y.
                 if (xIsArgument && !yIsArgument) {
                   return 1;
@@ -3646,10 +3679,8 @@
     }
     // Either all the consecutive registers are from the argument registers, or all are from the
     // non-argument registers.
-    assert (first < numberOfArgumentRegisters
-            && first + numberOfRegisters - 1 < numberOfArgumentRegisters)
-        || (first >= numberOfArgumentRegisters
-            && first + numberOfRegisters - 1 >= numberOfArgumentRegisters);
+    assert (isArgumentRegister(first) && isArgumentRegister(first + numberOfRegisters - 1))
+        || (!isArgumentRegister(first) && !isArgumentRegister(first + numberOfRegisters - 1));
     return first;
   }
 
@@ -3749,7 +3780,7 @@
   }
 
   private boolean registersForIntervalsAreTaken(LiveIntervals intervals) {
-    assert intervals.getRegister() != NO_REGISTER;
+    assert intervals.hasRegister();
     return registersAreTaken(intervals.getRegister(), intervals.getType().isWide());
   }
 
@@ -3757,22 +3788,6 @@
     return !freeRegisters.contains(register) || (isWide && !freeRegisters.contains(register + 1));
   }
 
-  private boolean noLinkedValues() {
-    for (BasicBlock block : code.blocks) {
-      for (Phi phi : block.getPhis()) {
-        assert phi.getNextConsecutive() == null;
-      }
-      for (Instruction instruction : block.getInstructions()) {
-        for (Value value : instruction.inValues()) {
-          assert value.getNextConsecutive() == null;
-        }
-        assert instruction.outValue() == null ||
-            instruction.outValue().getNextConsecutive() == null;
-      }
-    }
-    return true;
-  }
-
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder("Live ranges:\n");
@@ -3785,10 +3800,10 @@
     builder.append("\nLive range ascii art: \n");
     for (LiveIntervals intervals : liveIntervals) {
       Value value = intervals.getValue();
-      if (intervals.getRegister() == NO_REGISTER) {
-        StringUtils.appendRightPadded(builder, value + " (no reg): ", 20);
-      } else {
+      if (intervals.hasRegister()) {
         StringUtils.appendRightPadded(builder, value + " r" + intervals.getRegister() + ": ", 20);
+      } else {
+        StringUtils.appendRightPadded(builder, value + " (no reg): ", 20);
       }
       builder.append("|");
       builder.append(intervals.toAscciArtString());
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 64d42b0..75fd057 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
 
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
@@ -36,14 +37,15 @@
   private final List<LiveIntervals> splitChildren = new ArrayList<>();
   private final IntArrayList sortedSplitChildrenEnds = new IntArrayList();
   private boolean sortedChildren = false;
-  private List<LiveRange> ranges = new ArrayList<>();
+  private ArrayList<LiveRange> ranges = new ArrayList<>();
   private final TreeSet<LiveIntervalsUse> uses = new TreeSet<>();
   private int register = NO_REGISTER;
   private int hint = NO_REGISTER;
   private boolean spilled = false;
-  private boolean isInvokeRangeIntervals = false;
+  private Invoke isInvokeRangeIntervals = null;
   private boolean usedInMonitorOperations = false;
   private boolean liveAtMoveExceptionEntry = false;
+  private boolean handled = false;
 
   // Only registers up to and including the registerLimit are allowed for this interval.
   private int registerLimit = U16BIT_MAX;
@@ -110,6 +112,15 @@
     return hint;
   }
 
+  public boolean isHandled() {
+    return handled;
+  }
+
+  // Equivalent to removing the live intervals from the unhandled set. This is O(1) instead of O(n).
+  public void setHandled() {
+    handled = true;
+  }
+
   public void setSpilled(boolean value) {
     // Check that we always spill arguments to their original register.
     assert getRegister() != NO_REGISTER;
@@ -143,10 +154,6 @@
     next.previousConsecutive = this;
   }
 
-  public boolean isLinked() {
-    return splitParent.previousConsecutive != null || splitParent.nextConsecutive != null;
-  }
-
   public boolean isArgumentInterval() {
     Instruction definition = this.splitParent.value.definition;
     return definition != null && definition.isArgument();
@@ -297,17 +304,21 @@
   }
 
   public boolean isInvokeRangeIntervals() {
+    return isInvokeRangeIntervals != null;
+  }
+
+  public Invoke getIsInvokeRangeIntervals() {
     return isInvokeRangeIntervals;
   }
 
-  public void setIsInvokeRangeIntervals() {
-    assert !isInvokeRangeIntervals;
-    isInvokeRangeIntervals = true;
+  public void setIsInvokeRangeIntervals(Invoke invoke) {
+    assert !isInvokeRangeIntervals();
+    isInvokeRangeIntervals = invoke;
   }
 
   public void unsetIsInvokeRangeIntervals() {
     assert isSplitParent();
-    isInvokeRangeIntervals = false;
+    isInvokeRangeIntervals = null;
   }
 
   public boolean isLiveAtMoveExceptionEntry() {
@@ -372,6 +383,9 @@
   }
 
   public boolean overlapsPosition(int position) {
+    if (position < getStart() || position >= getEnd()) {
+      return false;
+    }
     for (LiveRange range : ranges) {
       if (range.start > position) {
         // Ranges are sorted. When a range starts after position there is no overlap.
@@ -388,6 +402,25 @@
     return nextOverlap(other) != -1;
   }
 
+  public boolean overlapsAnyInvokeRangeIntervals(List<LiveIntervals> intervalsList) {
+    boolean checked = false;
+    for (LiveIntervals intervals : intervalsList) {
+      boolean skip;
+      if (intervals.getValue().isDefinedByInstructionSatisfying(Instruction::isMove)) {
+        skip = false;
+      } else {
+        skip = checked;
+        if (!checked) {
+          checked = true;
+        }
+      }
+      if (!skip && overlaps(intervals)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public boolean anySplitOverlaps(LiveIntervals other) {
     LiveIntervals parent = getSplitParent();
     if (parent.overlaps(other)) {
@@ -427,6 +460,18 @@
     return Integer.MAX_VALUE;
   }
 
+  public int firstUseWithConstraintAfter(int unhandledStart, ArgumentReuseMode mode) {
+    if (isInvokeRangeIntervals()) {
+      return getFirstUse();
+    }
+    for (LiveIntervalsUse use : uses) {
+      if (use.hasConstraint(mode) && use.getPosition() >= unhandledStart) {
+        return use.getPosition();
+      }
+    }
+    return Integer.MAX_VALUE;
+  }
+
   public boolean hasUses() {
     return !uses.isEmpty();
   }
@@ -451,17 +496,19 @@
     }
   }
 
-  public LiveIntervals splitBefore(Instruction instruction) {
-    return splitBefore(instruction.getNumber());
+  public LiveIntervals splitBefore(Instruction instruction, ArgumentReuseMode mode) {
+    return splitBefore(instruction.getNumber(), mode);
   }
 
-  public LiveIntervals splitAfter(Instruction instruction) {
-    return splitBefore(instruction.getNumber() + INSTRUCTION_NUMBER_DELTA);
+  public LiveIntervals splitAfter(Instruction instruction, ArgumentReuseMode mode) {
+    return splitBefore(instruction.getNumber() + INSTRUCTION_NUMBER_DELTA, mode);
   }
 
-  public LiveIntervals splitBefore(int start) {
+  public LiveIntervals splitBefore(int start, ArgumentReuseMode mode) {
     if (toInstructionPosition(start) == toInstructionPosition(getStart())) {
-      assert uses.size() == 0 || getFirstUse() != start;
+      assert uses.isEmpty()
+          || getFirstUse() != start
+          || (!uses.first().hasConstraint(mode) && !isInvokeRangeIntervals());
       register = NO_REGISTER;
       return this;
     }
@@ -472,8 +519,8 @@
     LiveIntervals splitChild = new LiveIntervals(splitParent);
     splitParent.splitChildren.add(splitChild);
     splitParent.sortedChildren = splitParent.splitChildren.size() == 1;
-    List<LiveRange> beforeSplit = new ArrayList<>();
-    List<LiveRange> afterSplit = new ArrayList<>();
+    ArrayList<LiveRange> beforeSplit = new ArrayList<>();
+    ArrayList<LiveRange> afterSplit = new ArrayList<>();
     if (start == getEnd()) {
       beforeSplit = ranges;
       afterSplit.add(new LiveRange(start, start));
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index 2575d3b..736ed62 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -7,12 +7,12 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 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.Value;
 import com.android.tools.r8.utils.MapUtils;
 import java.util.Collection;
 import java.util.Collections;
@@ -135,12 +135,9 @@
           insertAt.next();
         }
         // Generate moves for each argument.
-        for (Value argumentValue = allocator.firstArgumentValue;
-            argumentValue != null;
-            argumentValue = argumentValue.getNextConsecutive()) {
-          Instruction instruction = argumentValue.definition;
-          if (needsMovesBeforeInstruction(instruction)) {
-            scheduleMovesBeforeInstruction(tempRegister, instruction, insertAt);
+        for (Argument argument : code.arguments()) {
+          if (needsMovesBeforeInstruction(argument)) {
+            scheduleMovesBeforeInstruction(tempRegister, argument, insertAt);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java b/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java
index 5b0810d..cf78fe1 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/UnsplitArgumentsResult.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.ir.regalloc.LiveIntervals.NO_REGISTER;
 
-import com.android.tools.r8.ir.code.Value;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 
 public class UnsplitArgumentsResult {
@@ -28,10 +27,8 @@
   // Returns true if any changes were made.
   public boolean revertPartial() {
     boolean changed = false;
-    for (Value argument = allocator.firstArgumentValue;
-        argument != null;
-        argument = argument.getNextConsecutive()) {
-      for (LiveIntervals child : argument.getLiveIntervals().getSplitChildren()) {
+    for (LiveIntervals argumentLiveIntervals : allocator.getArgumentLiveIntervals()) {
+      for (LiveIntervals child : argumentLiveIntervals.getSplitChildren()) {
         changed |= revertPartial(child);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
index 6621432..aaccebe 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/RecordCfCodeProvider.java
@@ -4,13 +4,17 @@
 
 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.CfArrayStore;
 import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfFrame;
 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;
@@ -26,14 +30,177 @@
 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.Cmp.Bias;
 import com.android.tools.r8.ir.code.IfType;
 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.records.RecordInstructionDesugaring;
 import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
 
-public abstract class RecordCfCodeProvider {
+public abstract class RecordCfCodeProvider extends SyntheticCfCodeProvider {
+
+  protected RecordCfCodeProvider(AppView<?> appView, DexType holder) {
+    super(appView, holder);
+  }
+
+  /**
+   * Generates a method which hashes all the fields. If outline, the method has all fields as
+   * parameters, else it's a local public method in the record class.
+   */
+  public static class RecordHashCfCodeProvider extends RecordCfCodeProvider {
+
+    private final List<DexField> fieldsToHash;
+    private final boolean outline;
+
+    public RecordHashCfCodeProvider(
+        AppView<?> appView, DexType holder, List<DexField> fieldsToHash, boolean outline) {
+      super(appView, holder);
+      this.fieldsToHash = fieldsToHash;
+      this.outline = outline;
+    }
+
+    public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+      factory.createSynthesizedType("Ljava/lang/Objects;");
+      factory.createSynthesizedType("Ljava/lang/Double;");
+      factory.createSynthesizedType("Ljava/lang/Float;");
+      factory.createSynthesizedType("Ljava/lang/Boolean;");
+      factory.createSynthesizedType("Ljava/lang/Long;");
+    }
+
+    private void addInvokeStatic(List<CfInstruction> instructions, DexMethod method) {
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
+    }
+
+    private void pushHashCode(List<CfInstruction> instructions, DexField field, int index) {
+      DexItemFactory factory = appView.dexItemFactory();
+      ValueType valueType = ValueType.fromDexType(field.getType());
+      if (outline) {
+        instructions.add(new CfLoad(valueType, index));
+      } else {
+        instructions.add(new CfLoad(ValueType.OBJECT, 0));
+        instructions.add(new CfInstanceFieldRead(field));
+      }
+      if (valueType == ValueType.DOUBLE) {
+        addInvokeStatic(instructions, factory.doubleMembers.staticHashCode);
+      } else if (valueType == ValueType.FLOAT) {
+        addInvokeStatic(instructions, factory.floatMembers.staticHashCode);
+      } else if (field.getType().isBooleanType()) {
+        addInvokeStatic(instructions, factory.booleanMembers.staticHashCode);
+      } else if (valueType == ValueType.LONG) {
+        addInvokeStatic(instructions, factory.longMembers.staticHashCode);
+      } else if (valueType.isObject()) {
+        addInvokeStatic(instructions, factory.objectsMethods.hashCode);
+      } else {
+        assert valueType == ValueType.INT;
+      }
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      int stackUsed = 0;
+      int localsUsed = 0;
+      List<CfInstruction> instructions = new ArrayList<>();
+      if (fieldsToHash.isEmpty()) {
+        stackUsed++;
+        instructions.add(
+            new CfConstNumber(
+                RecordInstructionDesugaring.fixedHashCodeForEmptyRecord(), ValueType.INT));
+      } else {
+        pushHashCode(instructions, fieldsToHash.get(0), 0);
+        boolean seenWide = fieldsToHash.get(0).getType().isWideType();
+        int argIndex = fieldsToHash.get(0).getType().getRequiredRegisters();
+        for (int i = 1; i < fieldsToHash.size(); i++) {
+          instructions.add(new CfConstNumber(31, ValueType.INT));
+          instructions.add(new CfArithmeticBinop(Opcode.Mul, NumericType.INT));
+          pushHashCode(instructions, fieldsToHash.get(i), argIndex);
+          seenWide |= fieldsToHash.get(i).getType().isWideType();
+          instructions.add(new CfArithmeticBinop(Opcode.Add, NumericType.INT));
+          argIndex += fieldsToHash.get(i).getType().getRequiredRegisters();
+        }
+        stackUsed += seenWide ? 3 : 2;
+        localsUsed += argIndex;
+      }
+      instructions.add(new CfReturn(ValueType.INT));
+      return new CfCode(getHolder(), stackUsed, localsUsed, instructions);
+    }
+  }
+
+  public static class RecordEqCfCodeProvider extends RecordCfCodeProvider {
+
+    private final List<DexField> fieldsToCompare;
+
+    public RecordEqCfCodeProvider(
+        AppView<?> appView, DexType holder, List<DexField> fieldsToCompare) {
+      super(appView, holder);
+      this.fieldsToCompare = fieldsToCompare;
+    }
+
+    public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+      factory.createSynthesizedType("Ljava/lang/Objects;");
+    }
+
+    private void addInvokeStatic(List<CfInstruction> instructions, DexMethod method) {
+      instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
+    }
+
+    private void pushComparison(
+        List<CfInstruction> instructions, DexField field, CfLabel falseLabel) {
+      ValueType valueType = ValueType.fromDexType(field.getType());
+      instructions.add(new CfLoad(ValueType.OBJECT, 0));
+      instructions.add(new CfInstanceFieldRead(field));
+      instructions.add(new CfLoad(ValueType.OBJECT, 2));
+      instructions.add(new CfInstanceFieldRead(field));
+      if (valueType == ValueType.DOUBLE) {
+        instructions.add(new CfCmp(Bias.LT, NumericType.DOUBLE));
+        instructions.add(new CfIf(IfType.NE, ValueType.INT, falseLabel));
+      } else if (valueType == ValueType.FLOAT) {
+        instructions.add(new CfCmp(Bias.LT, NumericType.FLOAT));
+        instructions.add(new CfIf(IfType.NE, ValueType.INT, falseLabel));
+      } else if (valueType == ValueType.LONG) {
+        instructions.add(new CfCmp(Bias.NONE, NumericType.LONG));
+        instructions.add(new CfIf(IfType.NE, ValueType.INT, falseLabel));
+      } else if (valueType.isObject()) {
+        addInvokeStatic(instructions, appView.dexItemFactory().objectsMethods.equals);
+        instructions.add(new CfIf(IfType.EQ, ValueType.INT, falseLabel));
+      } else {
+        assert valueType == ValueType.INT;
+        instructions.add(new CfIfCmp(IfType.NE, ValueType.INT, falseLabel));
+      }
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      CfFrame frame = buildFrame();
+      List<CfInstruction> instructions = new ArrayList<>();
+      CfLabel falseLabel = new CfLabel();
+      instructions.add(new CfLoad(ValueType.OBJECT, 1));
+      instructions.add(new CfInstanceOf(getHolder()));
+      instructions.add(new CfIf(IfType.EQ, ValueType.INT, falseLabel));
+      instructions.add(new CfLoad(ValueType.OBJECT, 1));
+      instructions.add(new CfCheckCast(getHolder()));
+      instructions.add(new CfStore(ValueType.OBJECT, 2));
+      for (int i = 0; i < fieldsToCompare.size(); i++) {
+        pushComparison(instructions, fieldsToCompare.get(i), falseLabel);
+      }
+      instructions.add(new CfConstNumber(1, ValueType.INT));
+      instructions.add(new CfReturn(ValueType.INT));
+      instructions.add(falseLabel);
+      instructions.add(frame);
+      instructions.add(new CfConstNumber(0, ValueType.INT));
+      instructions.add(new CfReturn(ValueType.INT));
+      return standardCfCodeFromInstructions(instructions);
+    }
+
+    private CfFrame buildFrame() {
+      return CfFrame.builder()
+          .appendLocal(FrameType.initialized(getHolder()))
+          .appendLocal(FrameType.initialized(appView.dexItemFactory().objectType))
+          .build();
+    }
+  }
 
   /**
    * Generates a method which answers all field values as an array of objects. If the field value is
@@ -61,6 +228,11 @@
           });
     }
 
+    @Override
+    protected int defaultMaxStack() {
+      return fields.length + 3;
+    }
+
     private final DexField[] fields;
 
     public RecordGetFieldsAsObjectsCfCodeProvider(
@@ -135,75 +307,4 @@
       }
     }
   }
-
-  public static class RecordEqualsCfCodeProvider extends SyntheticCfCodeProvider {
-
-    private final DexMethod getFieldsAsObjects;
-
-    public RecordEqualsCfCodeProvider(
-        AppView<?> appView, DexType holder, DexMethod getFieldsAsObjects) {
-      super(appView, holder);
-      this.getFieldsAsObjects = getFieldsAsObjects;
-    }
-
-    public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
-      factory.createSynthesizedType("[Ljava/lang/Object;");
-      factory.createSynthesizedType("[Ljava/util/Arrays;");
-    }
-
-    @Override
-    public CfCode generateCfCode() {
-      // This generates something along the lines of:
-      // if (other == null) {
-      //   return false;
-      // }
-      // if (this.getClass() != other.getClass()) {
-      //     return false;
-      // }
-      // return Arrays.equals(
-      //     recordInstance.getFieldsAsObjects(),
-      //     ((RecordClass) other).getFieldsAsObjects());
-      DexItemFactory factory = appView.dexItemFactory();
-      int numberOfInstructions = 22;
-      List<CfInstruction> instructions = new ArrayList<>(numberOfInstructions);
-      CfLabel notNullLabel = new CfLabel();
-      CfLabel fieldCmp = new CfLabel();
-      ValueType recordType = ValueType.fromDexType(getHolder());
-      ValueType objectType = ValueType.fromDexType(factory.objectType);
-      instructions.add(new CfLoad(objectType, 1));
-      instructions.add(new CfIf(IfType.NE, ValueType.OBJECT, notNullLabel));
-      instructions.add(new CfConstNumber(0, ValueType.INT));
-      instructions.add(new CfReturn(ValueType.INT));
-      instructions.add(notNullLabel);
-      instructions.add(
-          CfFrame.builder()
-              .appendLocal(FrameType.initialized(getHolder()))
-              .appendLocal(FrameType.initialized(appView.dexItemFactory().objectType))
-              .build());
-      instructions.add(new CfLoad(recordType, 0));
-      instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false));
-      instructions.add(new CfLoad(objectType, 1));
-      instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.objectMembers.getClass, false));
-      instructions.add(new CfIfCmp(IfType.EQ, ValueType.OBJECT, fieldCmp));
-      instructions.add(new CfConstNumber(0, ValueType.INT));
-      instructions.add(new CfReturn(ValueType.INT));
-      instructions.add(fieldCmp);
-      instructions.add(
-          CfFrame.builder()
-              .appendLocal(FrameType.initialized(getHolder()))
-              .appendLocal(FrameType.initialized(appView.dexItemFactory().objectType))
-              .build());
-      instructions.add(new CfLoad(recordType, 0));
-      instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
-      instructions.add(new CfLoad(objectType, 1));
-      instructions.add(new CfCheckCast(getHolder(), true));
-      instructions.add(new CfInvoke(Opcodes.INVOKESPECIAL, getFieldsAsObjects, false));
-      instructions.add(
-          new CfInvoke(
-              Opcodes.INVOKESTATIC, factory.javaUtilArraysMethods.equalsObjectArray, false));
-      instructions.add(new CfReturn(ValueType.INT));
-      assert instructions.size() == numberOfInstructions;
-      return standardCfCodeFromInstructions(instructions);
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index 287de23..2bdebd2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
+import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -25,6 +26,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.IRToLirFinalizer;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.optimize.AffectedValues;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.lightir.LirCode;
@@ -318,6 +320,30 @@
     }
 
     @Override
+    public void registerCallSite(DexCallSite callSite) {
+      LambdaDescriptor descriptor =
+          LambdaDescriptor.tryInfer(callSite, appView, appViewWithLiveness.appInfo(), getContext());
+      if (descriptor == null || descriptor.interfaces.isEmpty()) {
+        return;
+      }
+      ProgramMethod resolvedMainMethod =
+          appViewWithLiveness
+              .appInfo()
+              .resolveMethodOnInterface(descriptor.interfaces.get(0), descriptor.getMainMethod())
+              .getResolvedProgramMethod();
+      if (resolvedMainMethod == null) {
+        return;
+      }
+      DexMethod rewrittenMainMethod =
+          graphLens.getNextMethodSignature(resolvedMainMethod.getReference());
+      if (rewrittenMainMethod.isNotIdenticalTo(resolvedMainMethod.getReference())) {
+        markAffected();
+      } else {
+        assert !graphLens.hasPrototypeChanges(rewrittenMainMethod);
+      }
+    }
+
+    @Override
     public void registerInstanceFieldRead(DexField field) {
       registerFieldAccess(field);
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
index 800b277..c403ea1 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPropagator.java
@@ -84,6 +84,7 @@
             converter,
             fieldStates,
             methodStates,
+            immediateSubtypingInfo,
             inFlowComparator)
         .run(executorService);
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 5291e19..1c973c4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -314,7 +314,8 @@
             method -> {
               KeepMethodInfo keepInfo = appView.getKeepInfo(method);
               if (!keepInfo.isOptimizationAllowed(options)
-                  || !keepInfo.isShrinkingAllowed(options)) {
+                  || !keepInfo.isShrinkingAllowed(options)
+                  || !keepInfo.isClosedWorldReasoningAllowed(options)) {
                 pinnedMethodSignatures.add(method.getMethodSignature());
               }
             });
@@ -781,7 +782,7 @@
 
       // We need to find a new name for this method, since the signature is already occupied.
       // TODO(b/190154391): Instead of generating a new name, we could also try permuting the order
-      // of parameters.
+      //  of parameters.
       IntBox suffix =
           newMethodSignatureSuffixes.computeIfAbsent(
               methodSignatureWithParametersRemoved, ignoreKey(IntBox::new));
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
index 28d0ead..4c9489a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
@@ -36,6 +37,8 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -57,17 +60,20 @@
   private final Set<DexProgramClass> classesWithSingleCallerInlinedInstanceInitializers;
   private final FieldStateCollection fieldStates;
   private final List<FlowGraph> flowGraphs;
+  private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
 
   public DefaultFieldValueJoiner(
       AppView<AppInfoWithLiveness> appView,
       Set<DexProgramClass> classesWithSingleCallerInlinedInstanceInitializers,
       FieldStateCollection fieldStates,
-      List<FlowGraph> flowGraphs) {
+      List<FlowGraph> flowGraphs,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
     this.appView = appView;
     this.classesWithSingleCallerInlinedInstanceInitializers =
         classesWithSingleCallerInlinedInstanceInitializers;
     this.fieldStates = fieldStates;
     this.flowGraphs = flowGraphs;
+    this.immediateSubtypingInfo = immediateSubtypingInfo;
   }
 
   public Map<FlowGraph, Deque<FlowGraphNode>> joinDefaultFieldValuesForFieldsWithReadBeforeWrite(
@@ -79,12 +85,35 @@
       return Collections.emptyMap();
     }
 
+    // Classes that are kept or have a kept subclass can be instantiated in a way that does not use
+    // any instance initializers. For all fields on such classes, we therefore include the default
+    // field value.
+    Map<DexProgramClass, Boolean> classesWithKeptSubclasses =
+        computeClassesWithKeptSubclasses(fieldsOfInterest);
+    ProgramFieldSet fieldsWithLiveDefaultValue = ProgramFieldSet.createConcurrent();
+    MapUtils.removeIf(
+        fieldsOfInterest,
+        (clazz, fields) -> {
+          Boolean isKeptOrHasKeptSubclass = classesWithKeptSubclasses.get(clazz);
+          assert isKeptOrHasKeptSubclass != null;
+          if (isKeptOrHasKeptSubclass) {
+            fields.removeIf(
+                field -> {
+                  if (field.getDefinition().isInstance()) {
+                    fieldsWithLiveDefaultValue.add(field);
+                    return true;
+                  }
+                  return false;
+                });
+          }
+          return fields.isEmpty();
+        });
+
     // If constructor inlining is disabled, then we focus on whether each instance initializer
     // definitely assigns the given field before it is read. We do the same for final and static
     // fields.
     Map<DexType, ProgramFieldSet> fieldsNotSubjectToInitializerAnalysis =
         removeFieldsNotSubjectToInitializerAnalysis(fieldsOfInterest);
-    ProgramFieldSet fieldsWithLiveDefaultValue = ProgramFieldSet.createConcurrent();
     analyzeInitializers(
         fieldsOfInterest,
         field -> {
@@ -108,7 +137,46 @@
     return updateFlowGraphs(fieldsWithLiveDefaultValue, executorService);
   }
 
-  protected Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
+  /**
+   * For each of the classes in the key set of the given {@param fieldsOfInterest}, this computes
+   * whether the class is kept or has a kept subclass.
+   */
+  private Map<DexProgramClass, Boolean> computeClassesWithKeptSubclasses(
+      Map<DexProgramClass, List<ProgramField>> fieldsOfInterest) {
+    Reference2BooleanMap<DexProgramClass> classesWithKeptSubclasses =
+        new Reference2BooleanOpenHashMap<>();
+    for (DexProgramClass clazz : fieldsOfInterest.keySet()) {
+      computeClassHasKeptSubclass(clazz, classesWithKeptSubclasses);
+    }
+    return classesWithKeptSubclasses;
+  }
+
+  private boolean computeClassHasKeptSubclass(
+      DexProgramClass clazz, Reference2BooleanMap<DexProgramClass> classesWithKeptSubclasses) {
+    if (classesWithKeptSubclasses.containsKey(clazz)) {
+      return classesWithKeptSubclasses.getBoolean(clazz);
+    }
+    if (isKeptDirectly(clazz)) {
+      classesWithKeptSubclasses.put(clazz, true);
+      return true;
+    }
+    for (DexProgramClass subclass : immediateSubtypingInfo.getSubclasses(clazz)) {
+      if (computeClassHasKeptSubclass(subclass, classesWithKeptSubclasses)) {
+        classesWithKeptSubclasses.put(clazz, true);
+        return true;
+      }
+    }
+    assert !classesWithKeptSubclasses.containsKey(clazz)
+        || !classesWithKeptSubclasses.getBoolean(clazz);
+    classesWithKeptSubclasses.put(clazz, false);
+    return false;
+  }
+
+  private boolean isKeptDirectly(DexProgramClass clazz) {
+    return appView.getKeepInfo(clazz).isPinned(appView.options());
+  }
+
+  private Map<DexProgramClass, List<ProgramField>> getFieldsOfInterest() {
     Map<DexProgramClass, List<ProgramField>> fieldsOfInterest = new IdentityHashMap<>();
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       clazz.forEachProgramField(
@@ -262,12 +330,6 @@
         (holderType, fields) -> {
           assert !fields.isEmpty();
           DexProgramClass holder = fields.iterator().next().getHolder();
-          // If the class is kept it could be instantiated directly, in which case all default field
-          // values could be live.
-          if (appView.getKeepInfo(holder).isPinned(appView.options())) {
-            fields.forEach(liveDefaultValueConsumer);
-            return true;
-          }
           if (holder.isFinal() || !appView.appInfo().isInstantiatedIndirectly(holder)) {
             // When the class is not explicitly marked final, the class could in principle have
             // injected subclasses if it is pinned. However, none of the fields are pinned, so we
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
index 752e59d..ee8e703 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/InFlowPropagator.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
@@ -50,6 +51,7 @@
   final IRConverter converter;
   protected final FieldStateCollection fieldStates;
   final MethodStateCollectionByReference methodStates;
+  final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
   final InFlowComparator inFlowComparator;
 
   public InFlowPropagator(
@@ -58,6 +60,7 @@
       IRConverter converter,
       FieldStateCollection fieldStates,
       MethodStateCollectionByReference methodStates,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
       InFlowComparator inFlowComparator) {
     this.appView = appView;
     this.classesWithSingleCallerInlinedInstanceInitializers =
@@ -65,6 +68,7 @@
     this.converter = converter;
     this.fieldStates = fieldStates;
     this.methodStates = methodStates;
+    this.immediateSubtypingInfo = immediateSubtypingInfo;
     this.inFlowComparator = inFlowComparator;
   }
 
@@ -124,7 +128,11 @@
 
   protected DefaultFieldValueJoiner createDefaultFieldValueJoiner(List<FlowGraph> flowGraphs) {
     return new DefaultFieldValueJoiner(
-        appView, classesWithSingleCallerInlinedInstanceInitializers, fieldStates, flowGraphs);
+        appView,
+        classesWithSingleCallerInlinedInstanceInitializers,
+        fieldStates,
+        flowGraphs,
+        immediateSubtypingInfo);
   }
 
   private void processFlowGraphs(List<FlowGraph> flowGraphs, ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index d512ac1..2f83503c 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -45,14 +45,15 @@
   T(33),
   U(34),
   V(35),
-  MAIN(36), // API level for main is tentative.
+  BAKLAVA(36),
+  MAIN(37), // API level for main is tentative.
   EXTENSION(Integer.MAX_VALUE); // Used for API modeling of Android extension APIs.
 
   // When updating LATEST and a new version goes public, add a new api-versions.xml to third_party
   // and update the version and generated jar in AndroidApiDatabaseBuilderGeneratorTest. Together
   // with that update third_party/android_jar/libcore_latest/core-oj.jar and run
   // GenerateCovariantReturnTypeMethodsTest.
-  public static final AndroidApiLevel LATEST = V;
+  public static final AndroidApiLevel LATEST = BAKLAVA;
 
   public static final AndroidApiLevel API_DATABASE_LEVEL = LATEST;
 
@@ -116,7 +117,7 @@
 
   public static AndroidApiLevel getAndroidApiLevel(int apiLevel) {
     assert apiLevel > 0;
-    assert V == LATEST; // This has to be updated when we add new api levels.
+    assert BAKLAVA == LATEST; // This has to be updated when we add new api levels.
     assert UNKNOWN.isGreaterThan(LATEST);
     switch (apiLevel) {
       case 1:
@@ -189,6 +190,8 @@
         return U;
       case 35:
         return V;
+      case 36:
+        return BAKLAVA;
       default:
         return MAIN;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 3e484d4..ab542a4 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -49,6 +49,7 @@
           "--release",
           "--enable-missing-library-api-modeling",
           "--android-platform-build",
+          "--optimized-resource-shrinking",
           ISOLATED_SPLITS_FLAG);
 
   private static final List<String> VALID_OPTIONS_WITH_SINGLE_OPERAND =
@@ -120,6 +121,7 @@
     int threads = -1;
     BooleanBox enableMissingLibraryApiModeling = new BooleanBox(false);
     BooleanBox androidPlatformBuild = new BooleanBox(false);
+    BooleanBox optimizedResourceShrinking = new BooleanBox(false);
     BooleanBox isolatedSplits = new BooleanBox(false);
     for (int i = 0; i < args.length; i++) {
       String option = args[i];
@@ -151,6 +153,9 @@
           case "--android-platform-build":
             androidPlatformBuild.set(true);
             break;
+          case "--optimized-resource-shrinking":
+            optimizedResourceShrinking.set(true);
+            break;
           case ISOLATED_SPLITS_FLAG:
             isolatedSplits.set(true);
             break;
@@ -302,6 +307,11 @@
               ResourceShrinkerDumpUtils.setupBaseResourceShrinking(
                   finalAndroidResourcesInput, finalAndroidResourcesOutput, commandBuilder),
           "Failed initializing resource shrinker.");
+      runIgnoreMissing(
+          () ->
+              ResourceShrinkerDumpUtils.setOptimziedResourceShrinking(
+                  optimizedResourceShrinking.get(), commandBuilder),
+          "Failed setting optimized resource shrinking flag.");
     }
     if (desugaredLibKeepRuleConsumer != null) {
       commandBuilder.setDesugaredLibraryKeepRuleConsumer(desugaredLibKeepRuleConsumer);
diff --git a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
index a4d1c8f..48ceda2 100644
--- a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
+++ b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
@@ -255,11 +255,12 @@
     protected TraversalContinuation<TB, S> internalOnJoin(DFSNodeWithStateImpl<N, S> node) {
       return joiner(
           node,
-          childStateMap.computeIfAbsent(
+          MapUtils.removeOrComputeDefault(
+              childStateMap,
               node,
               n -> {
                 assert false : "Unexpected joining of not visited node";
-                return new ArrayList<>();
+                return Collections.emptyList();
               }));
     }
 
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index f3e1670..269b85c 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -68,6 +68,7 @@
     switch (androidApiLevel) {
         // MAIN is an unknown higher api version we therefore choose the highest known version.
       case MAIN:
+      case BAKLAVA:
       case V:
       case U:
       case T:
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 20d9edc..2b5aa51 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2488,11 +2488,6 @@
     public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true;
     public boolean alwaysUsePessimisticRegisterAllocation = false;
     public boolean enableRegisterHintsForBlockedRegisters = false;
-    // TODO(b/374266460): Investigate why enabling this leads to more moves, for example, in
-    //  JetNews. Also investigate the impact on performance and how often the refinement pass is
-    //  successful (i.e., how often the assumed 4 bit argument registers actually end up being 4
-    //  bit). If the failure rate is too high maybe add a some buffer.
-    public boolean enableRegisterAllocation8BitRefinement = false;
     // TODO(b/374715251): Look into enabling this.
     public boolean enableUseLastLocalRegisterAsMoveExceptionRegister = false;
     public boolean enableKeepInfoCanonicalizer = true;
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index ac5d7f6..99fae2e 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -96,6 +96,11 @@
     return value != null ? value : defaultValue;
   }
 
+  public static <K, V> V removeOrComputeDefault(Map<K, V> map, K key, Function<K, V> defaultValue) {
+    V value = map.remove(key);
+    return value != null ? value : defaultValue.apply(key);
+  }
+
   public static String toString(Map<?, ?> map) {
     return StringUtils.join(
         ",", map.entrySet(), entry -> entry.getKey() + ":" + entry.getValue(), BraceType.TUBORG);
diff --git a/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java b/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java
index 8ab1c93..c4964c1 100644
--- a/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/compiledump/ResourceShrinkerDumpUtils.java
@@ -22,4 +22,8 @@
     builder.setAndroidResourceProvider(new ArchiveProtoAndroidResourceProvider(input));
     builder.setAndroidResourceConsumer(new ArchiveProtoAndroidResourceConsumer(output));
   }
+
+  public static void setOptimziedResourceShrinking(boolean value, R8Command.Builder builder) {
+    builder.setResourceShrinkerConfiguration(b -> b.enableOptimizedShrinkingWithR8().build());
+  }
 }
diff --git a/src/main/resourceshrinker_cli.txt b/src/main/resourceshrinker_cli.txt
deleted file mode 100644
index 6bc0f39..0000000
--- a/src/main/resourceshrinker_cli.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2023, 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.
-
-# This is dependent on the resource shrinker being relocated into r8 namespace
--keep class com.android.tools.r8.resourceshrinker.ResourceShrinkerCli {
-  public static void main(java.lang.String[]);
-}
-
diff --git a/src/test/examplesJava17/records/RecordHashCodeManyFieldsTest.java b/src/test/examplesJava17/records/RecordHashCodeManyFieldsTest.java
new file mode 100644
index 0000000..6c2a31b
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordHashCodeManyFieldsTest.java
@@ -0,0 +1,262 @@
+// Copyright (c) 2024, 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 records;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.NeverInline;
+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.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Objects;
+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 RecordHashCodeManyFieldsTest extends TestBase {
+
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("true", "true", "false", "false", "true", "true");
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private boolean isCfRuntimeWithNativeRecordSupport() {
+    return parameters.isCfRuntime()
+        && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK14)
+        && parameters.getApiLevel().equals(AndroidApiLevel.B);
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(isCfRuntimeWithNativeRecordSupport());
+    testForJvm(parameters)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), TestClass.class, "no-desugar")
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class, "desugar")
+        .applyIf(
+            isRecordsFullyDesugaredForD8(parameters)
+                || runtimeWithRecordsSupport(parameters.getRuntime()),
+            r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    assumeTrue(parameters.isDexRuntime() || isCfRuntimeWithNativeRecordSupport());
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addInnerClassesAndStrippedOuter(getClass())
+            .setMinApi(parameters)
+            .addInliningAnnotations()
+            .addKeepMainRule(TestClass.class);
+    if (parameters.isCfRuntime()) {
+      builder
+          .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+          .run(parameters.getRuntime(), TestClass.class, "no-desugar")
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+      return;
+    }
+    builder
+        .run(parameters.getRuntime(), TestClass.class, "desugar")
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8DontShrinkDontObfuscate() throws Exception {
+    parameters.assumeR8TestParameters();
+    assumeTrue(parameters.isDexRuntime() || isCfRuntimeWithNativeRecordSupport());
+    R8FullTestBuilder builder =
+        testForR8(parameters.getBackend())
+            .addInnerClassesAndStrippedOuter(getClass())
+            .setMinApi(parameters)
+            .addDontShrink()
+            .addDontObfuscate()
+            .addInliningAnnotations()
+            .addKeepMainRule(TestClass.class);
+    if (parameters.isCfRuntime()) {
+      builder
+          .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+          .run(parameters.getRuntime(), TestClass.class, "no-desugar")
+          .assertSuccessWithOutput(EXPECTED_RESULT);
+      return;
+    }
+    builder
+        .run(parameters.getRuntime(), TestClass.class, "desugar")
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  public static class TestClass {
+
+    record Wide(int i1, int i2, long l1, long l2, double d1, double d2, String s1, String s2) {}
+
+    // It needs at least 33 fields with all primitive types.
+    record Many(
+        String s1,
+        String s2,
+        String s3,
+        String s4,
+        String s5,
+        boolean b1,
+        boolean b2,
+        boolean b3,
+        boolean b4,
+        boolean b5,
+        byte by1,
+        byte by2,
+        byte by3,
+        byte by4,
+        byte by5,
+        short sh1,
+        short sh2,
+        short sh3,
+        short sh4,
+        char c1,
+        char c2,
+        char c3,
+        char c4,
+        int i1,
+        int i2,
+        int i3,
+        int i4,
+        int i5,
+        long l1,
+        long l2,
+        long l3,
+        long l4,
+        long l5,
+        float f1,
+        float f2,
+        float f3,
+        float f4,
+        float f5,
+        double d1,
+        double d2,
+        double d3,
+        double d4,
+        double d5) {}
+
+    public static void main(String[] args) {
+
+      Many m1 =
+          new Many(
+              "s1", "s2", null, "s4", "s5", true, false, true, false, true, (byte) 1, (byte) 2,
+              (byte) 3, (byte) 4, (byte) 5, (short) 6, (short) 7, (short) 8, (short) 9, 'a', 'b',
+              'c', 'd', 21, 22, 23, 24, 25, 31L, 32L, 33L, 34L, 35L, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f,
+              11.0, 12.0, 13.0, 14.0, 15.0);
+      Many m2 =
+          new Many(
+              "ss1", "ss2", null, "s4", null, true, true, true, false, true, (byte) 1, (byte) 2,
+              (byte) 3, (byte) 4, (byte) 5, (short) 6, (short) 7, (short) 8, (short) 9, 'a', 'b',
+              'f', 'd', 21, 22, 23, 24, 25, 31L, 32L, 33L, 34L, 35L, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f,
+              11.0, 12.0, 13.0, 14.0, 15.0);
+      System.out.println(equals(m1.hashCode(), m1.hashCode()));
+      System.out.println(equalsHash(m1, m1));
+      System.out.println(equals(m1.hashCode(), m2.hashCode()));
+      System.out.println(equalsHash(m1, m2));
+      // The equals below are valid only if the record is desugared, i.e., not on r8 cf to cf.
+      if (args[0].equals("desugar")) {
+        System.out.println(equals(m1.hashCode(), hash(m1)));
+        Wide w = new Wide(1, 2, 3L, 4L, 1.0, 2.0, "s1", null);
+        System.out.println(equals(w.hashCode(), hash(w)));
+      } else {
+        System.out.println("true");
+        System.out.println("true");
+      }
+    }
+
+    @NeverInline
+    public static int hash(Many m) {
+      int hash = Boolean.hashCode(m.b1);
+      hash = 31 * hash + Boolean.hashCode(m.b2);
+      hash = 31 * hash + Boolean.hashCode(m.b3);
+      hash = 31 * hash + Boolean.hashCode(m.b4);
+      hash = 31 * hash + Boolean.hashCode(m.b5);
+      hash = 31 * hash + Integer.hashCode(m.by1);
+      hash = 31 * hash + Integer.hashCode(m.by2);
+      hash = 31 * hash + Integer.hashCode(m.by3);
+      hash = 31 * hash + Integer.hashCode(m.by4);
+      hash = 31 * hash + Integer.hashCode(m.by5);
+      hash = 31 * hash + Integer.hashCode(m.sh1);
+      hash = 31 * hash + Integer.hashCode(m.sh2);
+      hash = 31 * hash + Integer.hashCode(m.sh3);
+      hash = 31 * hash + Integer.hashCode(m.sh4);
+      hash = 31 * hash + Integer.hashCode(m.c1);
+      hash = 31 * hash + Integer.hashCode(m.c2);
+      hash = 31 * hash + Integer.hashCode(m.c3);
+      hash = 31 * hash + Integer.hashCode(m.c4);
+      hash = 31 * hash + Integer.hashCode(m.i1);
+      hash = 31 * hash + Integer.hashCode(m.i2);
+      hash = 31 * hash + Integer.hashCode(m.i3);
+      hash = 31 * hash + Integer.hashCode(m.i4);
+      hash = 31 * hash + Integer.hashCode(m.i5);
+      hash = 31 * hash + Long.hashCode(m.l1);
+      hash = 31 * hash + Long.hashCode(m.l2);
+      hash = 31 * hash + Long.hashCode(m.l3);
+      hash = 31 * hash + Long.hashCode(m.l4);
+      hash = 31 * hash + Long.hashCode(m.l5);
+      hash = 31 * hash + Float.hashCode(m.f1);
+      hash = 31 * hash + Float.hashCode(m.f2);
+      hash = 31 * hash + Float.hashCode(m.f3);
+      hash = 31 * hash + Float.hashCode(m.f4);
+      hash = 31 * hash + Float.hashCode(m.f5);
+      hash = 31 * hash + Double.hashCode(m.d1);
+      hash = 31 * hash + Double.hashCode(m.d2);
+      hash = 31 * hash + Double.hashCode(m.d3);
+      hash = 31 * hash + Double.hashCode(m.d4);
+      hash = 31 * hash + Double.hashCode(m.d5);
+      hash = 31 * hash + Objects.hashCode(m.s1);
+      hash = 31 * hash + Objects.hashCode(m.s2);
+      hash = 31 * hash + Objects.hashCode(m.s3);
+      hash = 31 * hash + Objects.hashCode(m.s4);
+      hash = 31 * hash + Objects.hashCode(m.s5);
+      return hash;
+    }
+
+    @NeverInline
+    public static int hash(Wide w) {
+      int hash = w.i1;
+      hash = 31 * hash + w.i2;
+      hash = 31 * hash + Long.hashCode(w.l1);
+      hash = 31 * hash + Long.hashCode(w.l2);
+      hash = 31 * hash + Double.hashCode(w.d1);
+      hash = 31 * hash + Double.hashCode(w.d2);
+      hash = 31 * hash + Objects.hashCode(w.s1);
+      hash = 31 * hash + Objects.hashCode(w.s2);
+      return hash;
+    }
+
+    @NeverInline
+    public static boolean equals(int i1, int i2) {
+      return System.currentTimeMillis() > 0 ? i1 == i2 : false;
+    }
+
+    @NeverInline
+    public static boolean equalsHash(Record r1, Record r2) {
+      return System.currentTimeMillis() > 0 ? equals(r1.hashCode(), r2.hashCode()) : false;
+    }
+  }
+}
diff --git a/src/test/examplesJava17/string/StringBuilderWithAppendOutOfBoundsTest.java b/src/test/examplesJava17/string/StringBuilderWithAppendOutOfBoundsTest.java
new file mode 100644
index 0000000..cb3e379
--- /dev/null
+++ b/src/test/examplesJava17/string/StringBuilderWithAppendOutOfBoundsTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2024, 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 string;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+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 StringBuilderWithAppendOutOfBoundsTest extends TestBase {
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("class java.lang.IndexOutOfBoundsException");
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .addKeepMainRule(Main.class)
+        .addLibraryFiles(ToolHelper.getAndroidJar(35))
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class Main {
+
+    public static void main(String[] strArr) {
+      String text = "sss";
+      int l = 7;
+
+      StringBuilder sb = new StringBuilder();
+      try {
+        sb.append(text, 0, l);
+        System.out.println("not out of bounds");
+      } catch (Exception e) {
+        System.out.println(e.getClass());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/B379241435Test.java b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedAftertInstantiatingImplementorTest.java
similarity index 83%
rename from src/test/java/com/android/tools/r8/B379241435Test.java
rename to src/test/java/com/android/tools/r8/B379241435InterfaceInitializedAftertInstantiatingImplementorTest.java
index 3647f0a..9fb3f4f 100644
--- a/src/test/java/com/android/tools/r8/B379241435Test.java
+++ b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedAftertInstantiatingImplementorTest.java
@@ -12,7 +12,7 @@
 
 // Regression test for b/379241435.
 @RunWith(Parameterized.class)
-public class B379241435Test extends TestBase {
+public class B379241435InterfaceInitializedAftertInstantiatingImplementorTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
@@ -22,7 +22,7 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  private static final String EXPECTED_OUTPUT = StringUtils.lines("A.A()", "I.f()");
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("B.B()", "A.A()", "I.f()");
 
   @Test
   public void testJvm() throws Exception {
@@ -56,12 +56,14 @@
                     .getApiLevel()
                     .isLessThan(apiLevelWithDefaultInterfaceMethodsSupport()),
             r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
-            r -> r.assertSuccessWithOutputLines("I.f()"));
+            r -> r.assertSuccessWithOutputLines("B.B()", "I.f()"));
   }
 
   public static class TestClass {
     public static void main(String[] args) {
+      // Instantiating B does not trigger class initialization of I.
       I b = new B();
+      // Invoking the static method on I trigger class initialization of I.
       I.f();
     }
   }
@@ -80,5 +82,9 @@
     }
   }
 
-  static class B implements I {}
+  static class B implements I {
+    B() {
+      System.out.println("B.B()");
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/B379241435Test.java b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorStaticMethodTest.java
similarity index 75%
copy from src/test/java/com/android/tools/r8/B379241435Test.java
copy to src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorStaticMethodTest.java
index 3647f0a..bb43551 100644
--- a/src/test/java/com/android/tools/r8/B379241435Test.java
+++ b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorStaticMethodTest.java
@@ -10,9 +10,9 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
-// Regression test for b/379241435.
+// Regression test for a variant of b/379241435.
 @RunWith(Parameterized.class)
-public class B379241435Test extends TestBase {
+public class B379241435InterfaceInitializedFromImplementorStaticMethodTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
@@ -22,7 +22,7 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  private static final String EXPECTED_OUTPUT = StringUtils.lines("A.A()", "I.f()");
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("B.B()", "A.A()", "I.f()");
 
   @Test
   public void testJvm() throws Exception {
@@ -49,6 +49,8 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
         .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
             parameters.isDexRuntime()
@@ -56,13 +58,15 @@
                     .getApiLevel()
                     .isLessThan(apiLevelWithDefaultInterfaceMethodsSupport()),
             r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
-            r -> r.assertSuccessWithOutputLines("I.f()"));
+            r -> r.assertSuccessWithOutputLines("B.B()", "I.f()"));
   }
 
   public static class TestClass {
     public static void main(String[] args) {
-      I b = new B();
-      I.f();
+      // Instantiating B does not trigger class initialization of I.
+      B b = new B();
+      // Invoking m indirectly trigger class initialization of I as it calls a static method on I.
+      B.m();
     }
   }
 
@@ -80,5 +84,15 @@
     }
   }
 
-  static class B implements I {}
+  @NeverClassInline
+  static class B implements I {
+    B() {
+      System.out.println("B.B()");
+    }
+
+    @NeverInline
+    static void m() {
+      I.f();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/B379241435Test.java b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorVirtualMethodTest.java
similarity index 74%
copy from src/test/java/com/android/tools/r8/B379241435Test.java
copy to src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorVirtualMethodTest.java
index 3647f0a..bdb3b3b 100644
--- a/src/test/java/com/android/tools/r8/B379241435Test.java
+++ b/src/test/java/com/android/tools/r8/B379241435InterfaceInitializedFromImplementorVirtualMethodTest.java
@@ -10,9 +10,9 @@
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
-// Regression test for b/379241435.
+// Regression test for a variant of b/379241435.
 @RunWith(Parameterized.class)
-public class B379241435Test extends TestBase {
+public class B379241435InterfaceInitializedFromImplementorVirtualMethodTest extends TestBase {
 
   @Parameter(0)
   public TestParameters parameters;
@@ -22,7 +22,7 @@
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  private static final String EXPECTED_OUTPUT = StringUtils.lines("A.A()", "I.f()");
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("B.B()", "A.A()", "I.f()");
 
   @Test
   public void testJvm() throws Exception {
@@ -49,6 +49,9 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoMethodStaticizingAnnotations()
         .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
             parameters.isDexRuntime()
@@ -56,13 +59,15 @@
                     .getApiLevel()
                     .isLessThan(apiLevelWithDefaultInterfaceMethodsSupport()),
             r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
-            r -> r.assertSuccessWithOutputLines("I.f()"));
+            r -> r.assertSuccessWithOutputLines("B.B()", "I.f()"));
   }
 
   public static class TestClass {
     public static void main(String[] args) {
-      I b = new B();
-      I.f();
+      // Instantiating B does not trigger class initialization of I.
+      B b = new B();
+      // Invoking m indirectly trigger class initialization of I as it calls a static method on I.
+      b.m();
     }
   }
 
@@ -80,5 +85,16 @@
     }
   }
 
-  static class B implements I {}
+  @NeverClassInline
+  static class B implements I {
+    B() {
+      System.out.println("B.B()");
+    }
+
+    @NeverInline
+    @NoMethodStaticizing
+    void m() {
+      I.f();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
index f003fdf..37f266a 100644
--- a/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
+++ b/src/test/java/com/android/tools/r8/BackportedMethodListTest.java
@@ -118,6 +118,11 @@
     assertEquals(
         apiLevel < AndroidApiLevel.V.getLevel(),
         backports.contains("java/lang/Character#toString(I)Ljava/lang/String;"));
+
+    // BAKLAVA added static field android/os/Build$VERSION.SDK_INT_FULL.
+    assertEquals(
+        apiLevel < AndroidApiLevel.BAKLAVA.getLevel(),
+        backports.contains("android/os/Build$VERSION#SDK_INT_FULL"));
   }
 
   private void addLibraryDesugaring(BackportedMethodListCommand.Builder builder) {
diff --git a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
index 76c0cde..469b5cd 100644
--- a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
@@ -75,7 +75,7 @@
       Paths.get(ToolHelper.MAIN_SOURCE_DIR)
           .resolve(PACKAGE_NAME.replace('.', '/'))
           .resolve(CLASS_NAME + ".java");
-  private static final AndroidApiLevel GENERATED_FOR_API_LEVEL = AndroidApiLevel.V;
+  private static final AndroidApiLevel GENERATED_FOR_API_LEVEL = AndroidApiLevel.BAKLAVA;
 
   @Parameter public TestParameters parameters;
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
index 58a8ac6..9325826 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -200,19 +200,6 @@
     expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/NfcB;"));
     expectedMissingMembers.add(factory.createType("Landroid/nfc/tech/Ndef;"));
     expectedMissingMembers.add(factory.createType("Landroid/webkit/CookieSyncManager;"));
-    expectedMissingMembers.add(
-        factory.createType("Landroid/adservices/customaudience/CustomAudienceManager;"));
-    expectedMissingMembers.add(
-        factory.createType("Landroid/adservices/customaudience/PartialCustomAudience$Builder;"));
-    expectedMissingMembers.add(
-        factory.createType("Landroid/adservices/customaudience/PartialCustomAudience;"));
-    expectedMissingMembers.add(
-        factory.createType(
-            "Landroid/adservices/customaudience/ScheduleCustomAudienceUpdateRequest$Builder;"));
-    expectedMissingMembers.add(
-        factory.createType(
-            "Landroid/adservices/customaudience/ScheduleCustomAudienceUpdateRequest;"));
-    expectedMissingMembers.add(factory.createType("Landroid/app/appsearch/AppSearchResult;"));
     assertEquals(
         expectedMissingMembers.stream()
                 .map(DexType::toDescriptorString)
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 0dda84b2..5d93f88 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -120,9 +120,9 @@
                   methodReferences.forEach(field -> numberOfMethods.increment())));
         });
     // These numbers will change when updating api-versions.xml
-    assertEquals(5972, parsedApiClasses.size());
-    assertEquals(30341, numberOfFields.get());
-    assertEquals(46576, numberOfMethods.get());
+    assertEquals(6031, parsedApiClasses.size());
+    assertEquals(30501, numberOfFields.get());
+    assertEquals(46885, numberOfMethods.get());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
index 2d09844..071d6cb 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -89,7 +89,8 @@
   private Set<String> getDeletedTypesMissingRemovedAttribute() {
     Set<String> removedTypeNames = new HashSet<>();
     if (maxApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.U)) {
-      if (maxApiLevel.isLessThan(AndroidApiLevel.V)) {
+      if (maxApiLevel.isLessThan(AndroidApiLevel.V)
+          || maxApiLevel.equals(AndroidApiLevel.BAKLAVA)) {
         removedTypeNames.add("com.android.internal.util.Predicate");
       }
       removedTypeNames.add("android.adservices.AdServicesVersion");
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
index 2617cb6..2925557 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/CodeGenerationBase.java
@@ -20,9 +20,9 @@
 public abstract class CodeGenerationBase extends TestBase {
 
   private static final Path GOOGLE_FORMAT_DIR =
-      Paths.get(ToolHelper.THIRD_PARTY_DIR, "google", "google-java-format", "1.14.0");
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "google", "google-java-format", "1.24.0");
   private static final Path GOOGLE_FORMAT_JAR =
-      GOOGLE_FORMAT_DIR.resolve("google-java-format-1.14.0-all-deps.jar");
+      GOOGLE_FORMAT_DIR.resolve("google-java-format-1.24.0-all-deps.jar");
 
   protected final DexItemFactory factory = new DexItemFactory();
 
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AndroidOsBuildVersionBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AndroidOsBuildVersionBackportTest.java
new file mode 100644
index 0000000..ebf9f8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AndroidOsBuildVersionBackportTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2024, 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.backports;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AndroidOsBuildVersionBackportTest extends AbstractBackportTest {
+
+  private static final String ANDROID_OS_BUILD_VERSION_TYPE_NAME = "android.os.Build$VERSION";
+  private static final String ANDROID_OS_BUILD_VERSION_DESCRIPTOR =
+      DescriptorUtils.javaTypeToDescriptor(ANDROID_OS_BUILD_VERSION_TYPE_NAME);
+
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+        .withAllApiLevels()
+        .build();
+  }
+
+  public AndroidOsBuildVersionBackportTest(TestParameters parameters)
+      throws IOException, NoSuchFieldException {
+    super(
+        parameters,
+        ANDROID_OS_BUILD_VERSION_TYPE_NAME,
+        ImmutableList.of(getTestRunner(), getTransformedBuildVERSIONClass()));
+
+    // android.os.Build$VERSION.SDK_INT_FULL is on API 36.
+    registerFieldTarget(AndroidApiLevel.BAKLAVA, 1);
+  }
+
+  // Stub out android.os.Build$VERSION as it does not exist when building R8.
+  public static class /*android.os.Build$*/ VERSION {
+
+    public static /*final*/ int SDK_INT = -1;
+    public static /*final*/ int SDK_INT_FULL = -1;
+  }
+
+  private static byte[] getTransformedBuildVERSIONClass() throws IOException, NoSuchFieldException {
+    return transformer(VERSION.class)
+        .setClassDescriptor(ANDROID_OS_BUILD_VERSION_DESCRIPTOR)
+        .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+        .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT_FULL"), AccessFlags::setFinal)
+        .transform();
+  }
+
+  private static byte[] getTestRunner() throws IOException {
+    return transformer(TestRunner.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(VERSION.class), ANDROID_OS_BUILD_VERSION_DESCRIPTOR)
+        .transform();
+  }
+
+  public static class TestRunner extends MiniAssert {
+
+    public static void main(String[] args) throws Exception {
+      System.out.println(VERSION.SDK_INT_FULL);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/SdkIntFullBackportOutlineInBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/SdkIntFullBackportOutlineInBackportTest.java
new file mode 100644
index 0000000..260ea5d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/SdkIntFullBackportOutlineInBackportTest.java
@@ -0,0 +1,208 @@
+// Copyright (c) 2024, 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.backports;
+
+import static com.android.tools.r8.utils.AndroidApiLevel.BAKLAVA;
+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 static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.androidapi.AndroidApiLevelHashingDatabaseImpl;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class SdkIntFullBackportOutlineInBackportTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void checkSdkIntFullApiLevel() {
+    assumeTrue(parameters.isNoneRuntime());
+    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+    InternalOptions options = new InternalOptions();
+    AndroidApiLevelHashingDatabaseImpl androidApiLevelDatabase =
+        new AndroidApiLevelHashingDatabaseImpl(ImmutableList.of(), options, diagnosticsHandler);
+    assertEquals(
+        BAKLAVA.getLevel(),
+        androidApiLevelDatabase
+            .getFieldApiLevel(
+                options.dexItemFactory().androidOsBuildVersionMembers.SDK_INT_FULL.asDexField())
+            .getLevel());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+      if (apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.LATEST)) {
+        continue;
+      }
+      testForD8()
+          .addProgramClassFileData(getTransformedMainClass())
+          .addLibraryClassFileData(getTransformedBuildVersionClass())
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+          .setMinApi(apiLevel)
+          .compile()
+          .inspect(inspector -> inspectD8(inspector, apiLevel));
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+      // SDK_INT was introduced in D so don't bother testing before.
+      if (apiLevel.isLessThanOrEqualTo(AndroidApiLevel.D)
+          || apiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.LATEST)) {
+        continue;
+      }
+      System.out.println(apiLevel);
+      testForR8(Backend.DEX)
+          .addProgramClassFileData(getTransformedMainClass())
+          .addKeepMainRule(TestClass.class)
+          .addLibraryClassFileData(getTransformedBuildVersionClass())
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.BAKLAVA))
+          .setMinApi(apiLevel)
+          .addDontObfuscate()
+          .compile()
+          .inspect(inspector -> inspectR8(inspector, apiLevel));
+    }
+  }
+
+  private void inspectD8(CodeInspector inspector, AndroidApiLevel apiLevel) {
+    if (apiLevel.isGreaterThanOrEqualTo(BAKLAVA)) {
+      // From BAKLAVA the SDK_INT_FULL get stays.
+      assertEquals(
+          1,
+          countStaticGets(
+              inspector.clazz(TestClass.class).mainMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+    } else {
+      // Before BAKLAVA the SDK_INT_FULL static get is backported and the SDK_INT_FULL static get
+      // is outlined from the backport as well.
+      ClassSubject backport =
+          inspector.clazz(
+              SyntheticItemsTestUtils.syntheticBackportWithForwardingClass(TestClass.class, 1));
+      assertThat(backport, isPresent());
+      assertEquals(
+          2,
+          countStaticGets(
+              backport.uniqueMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT));
+      assertEquals(
+          0,
+          countStaticGets(
+              backport.uniqueMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+      ClassSubject apiOutline =
+          inspector.clazz(SyntheticItemsTestUtils.syntheticApiOutlineClass(TestClass.class, 0));
+      assertThat(apiOutline.uniqueMethod(), isPresent());
+      assertEquals(
+          1,
+          countStaticGets(
+              apiOutline.uniqueMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+    }
+  }
+
+  private void inspectR8(CodeInspector inspector, AndroidApiLevel apiLevel) {
+    if (apiLevel.isGreaterThanOrEqualTo(BAKLAVA)) {
+      // From BAKLAVA the SDK_INT_FULL get stays.
+      assertEquals(
+          1,
+          countStaticGets(
+              inspector.clazz(TestClass.class).mainMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+    } else {
+      // Before BAKLAVA the SDK_INT_FULL static get is backported and the SDK_INT_FULL static get
+      // is outlined from the backport as well. With just one use of the backport it is inlined
+      // into main.
+      ClassSubject backport =
+          inspector.clazz(
+              SyntheticItemsTestUtils.syntheticBackportWithForwardingClass(TestClass.class, 1));
+      assertThat(backport, isAbsent());
+      assertEquals(
+          1,
+          countStaticGets(
+              inspector.clazz(TestClass.class).mainMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT));
+      assertEquals(
+          0,
+          countStaticGets(
+              inspector.clazz(TestClass.class).mainMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+      ClassSubject apiOutline =
+          inspector.clazz(SyntheticItemsTestUtils.syntheticApiOutlineClass(TestClass.class, 0));
+      assertThat(apiOutline.uniqueMethod(), isPresent());
+      assertEquals(
+          1,
+          countStaticGets(
+              apiOutline.uniqueMethod(),
+              inspector.getFactory().androidOsBuildVersionMembers.SDK_INT_FULL));
+    }
+  }
+
+  private long countStaticGets(MethodSubject subject, DexField field) {
+    return subject
+        .streamInstructions()
+        .filter(InstructionSubject::isStaticGet)
+        .map(InstructionSubject::getField)
+        .filter(dexField -> dexField.isIdenticalTo(field))
+        .count();
+  }
+
+  private static byte[] getTransformedMainClass() throws IOException {
+    return transformer(TestClass.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(VERSION.class), "Landroid/os/Build$VERSION;")
+        .transform();
+  }
+
+  private byte[] getTransformedBuildVersionClass() throws IOException, NoSuchFieldException {
+    return transformer(VERSION.class)
+        .setClassDescriptor("Landroid/os/Build$VERSION;")
+        .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+        .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT_FULL"), AccessFlags::setFinal)
+        .transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(VERSION.SDK_INT_FULL);
+    }
+  }
+
+  public static class /*android.os.Build$*/ VERSION {
+
+    public static /*final*/ int SDK_INT = -1;
+    public static /*final*/ int SDK_INT_FULL = -1;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 26098b7..c3b9af4 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -6,12 +6,14 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+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;
@@ -24,9 +26,11 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableSet;
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
@@ -100,14 +104,19 @@
       CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(apiLevel));
       InternalOptions options = new InternalOptions();
       options.setMinApiLevel(apiLevel);
-      List<DexMethod> backportedMethods =
-          BackportedMethodRewriter.generateListOfBackportedMethods(
-              AndroidApp.builder().build(), options, ThreadUtils.getExecutorService(options));
+      List<DexMethod> backportedMethods = new ArrayList<>();
+      List<DexField> backportedFields = new ArrayList<>();
+      BackportedMethodRewriter.generateListOfBackportedMethodsAndFields(
+          AndroidApp.builder().build(),
+          options,
+          ThreadUtils.getExecutorService(options),
+          backportedMethods::add,
+          backportedFields::add);
       Set<DexMethod> alwaysPresent = expectedToAlwaysBePresentInAndroidJar(options.itemFactory);
       for (DexMethod method : backportedMethods) {
         // Two different DexItemFactories are in play, but as toSourceString is used for lookup
         // that is not an issue.
-        ClassSubject clazz = inspector.clazz(method.holder.toSourceString());
+        ClassSubject clazz = inspector.clazz(method.getHolderType().toSourceString());
         MethodSubject foundInAndroidJar =
             clazz.method(
                 method.proto.returnType.toSourceString(),
@@ -120,6 +129,15 @@
             foundInAndroidJar,
             notIf(isPresent(), !alwaysPresent.contains(method)));
       }
+      for (DexField field : backportedFields) {
+        // Two different DexItemFactories are in play, but as toSourceString is used for lookup
+        // that is not an issue.
+        ClassSubject clazz = inspector.clazz(field.getHolderType().toSourceString());
+        FieldSubject foundInAndroidJar =
+            clazz.field(field.getType().toSourceString(), field.getName().toSourceString());
+        assertThat(
+            foundInAndroidJar + " present in " + apiLevel, foundInAndroidJar, not(isPresent()));
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
index cdbee2c..ac65d6a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvokeAllResolveTest.java
@@ -129,7 +129,8 @@
                     finalApp, GlobalSyntheticsStrategy.forNonSynthesizing()))
             .appInfoForDesugaring();
     Set<DexMethod> backports = Sets.newIdentityHashSet();
-    backports.addAll(BackportedMethodRewriter.generateListOfBackportedMethods(libHolder, options));
+    BackportedMethodRewriter.generateListOfBackportedMethodsAndFields(
+        libHolder, options, backports::add, f -> {});
     Map<DexMethod, Object> failures = new IdentityHashMap<>();
     for (FoundClassSubject clazz : inspector.allClasses()) {
       if (clazz.toString().startsWith("j$.sun.nio.cs.")
diff --git a/src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestBasedAccessToNativeMethodTest.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java
rename to src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestBasedAccessToNativeMethodTest.java
index b5eff1f..ecbb82f 100644
--- a/src/test/java/com/android/tools/r8/desugar/nest/NestBasedAccessToNativeMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestBasedAccessToNativeMethodTest.java
@@ -2,14 +2,14 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.desugar.nest;
+package com.android.tools.r8.desugar.nestaccesscontrol;
 
 import static com.android.tools.r8.TestRuntime.CfVm.JDK11;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.desugar.nest.NestBasedAccessToNativeMethodTest.Main.Inner;
+import com.android.tools.r8.desugar.nestaccesscontrol.NestBasedAccessToNativeMethodTest.Main.Inner;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.util.List;
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
index f00657c..5ec2331 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMethods.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.desugar.records;
 
-import java.util.Arrays;
-
 // This class implements support methods for record desugaring. The RecordRewriter
 // rewrites relevant calls to one of the following methods.
 public class RecordMethods {
@@ -25,8 +23,4 @@
     builder.append("]");
     return builder.toString();
   }
-
-  public static int hashCode(Class<?> recordClass, Object[] recordFieldsAsObjects) {
-    return 31 * Arrays.hashCode(recordFieldsAsObjects) + recordClass.hashCode();
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
index fcd2106..a19c8e9 100644
--- a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
+++ b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
@@ -58,7 +58,7 @@
         .setMinApi(AndroidApiLevel.K)
         .compile()
         .inspect(
-            inspector -> assertEquals(backend.isDex() ? 1091 : 4, inspector.allClasses().size()));
+            inspector -> assertEquals(backend.isDex() ? 1104 : 4, inspector.allClasses().size()));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/AndroidOsBuildVersionMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/AndroidOsBuildVersionMethods.java
new file mode 100644
index 0000000..9b127ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/AndroidOsBuildVersionMethods.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2024, 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.backports;
+
+public final class AndroidOsBuildVersionMethods {
+  // Stub out android.os.Build$VERSION as it does not exist when building R8.
+  private static class AndroidOsBuildVersionStub {
+    public static int SDK_INT;
+    public static int SDK_INT_FULL;
+  }
+
+  // Android runtime value of field android.os.Build$VERSION.SDK_INT_FULL for all Android
+  // versions. Calculated from android.os.Build$VERSION.SDK_INT before Baklava (API level 36).
+  // See android.os.Build$VERSION_CODES_FULL for the constants for versions before Baklava.
+  public static int getSdkIntFull() {
+    if (AndroidOsBuildVersionStub.SDK_INT < 36) {
+      // Based on the constants in android.os.Build$VERSION_CODES_FULL.
+      return AndroidOsBuildVersionStub.SDK_INT * 100_000;
+    }
+    return AndroidOsBuildVersionStub.SDK_INT_FULL;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 9e964ea..7ec725f 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ToolHelper.TestDataSourceSet;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -35,6 +36,7 @@
       factory.createType("Lcom/android/tools/r8/ir/desugar/backports/BackportedMethods;");
   private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
       ImmutableList.of(
+          AndroidOsBuildVersionMethods.class,
           AssertionErrorMethods.class,
           AtomicReferenceArrayMethods.class,
           AtomicReferenceFieldUpdaterMethods.class,
@@ -139,6 +141,28 @@
     return instruction;
   }
 
+  private static CfInstruction rewriteToAndroidOsBuildVersion(
+      DexItemFactory itemFactory, CfInstruction instruction) {
+    // Rewrite references to UnsafeStub to sun.misc.Unsafe.
+    if (instruction.isStaticFieldGet()) {
+      CfStaticFieldRead fieldGet = instruction.asStaticFieldGet();
+      return new CfStaticFieldRead(
+          itemFactory.createField(
+              itemFactory.createType("Landroid/os/Build$VERSION;"),
+              fieldGet.getField().getType(),
+              fieldGet.getField().getName()));
+    }
+    if (instruction.isFrame()) {
+      return instruction
+          .asFrame()
+          .mapReferenceTypes(
+              type -> {
+                throw new RuntimeException("Unexpected CfFrame instruction.");
+              });
+    }
+    return instruction;
+  }
+
   @Override
   protected CfCode getCode(String holderName, String methodName, CfCode code) {
     if (methodName.endsWith("Stub")) {
@@ -157,6 +181,12 @@
               .map(instruction -> rewriteToUnsafe(factory, instruction))
               .collect(Collectors.toList()));
     }
+    if (holderName.equals("AndroidOsBuildVersionMethods") && methodName.equals("getSdkIntFull")) {
+      code.setInstructions(
+          code.getInstructions().stream()
+              .map(instruction -> rewriteToAndroidOsBuildVersion(factory, instruction))
+              .collect(Collectors.toList()));
+    }
     return code;
   }
 
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 adc7fb7..850cc4b 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
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
 import java.util.LinkedList;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,7 +50,7 @@
 
   private static class MockLinearScanRegisterAllocator extends LinearScanRegisterAllocator {
     MockLinearScanRegisterAllocator(AppView<?> appView, IRCode code) {
-      super(appView, code);
+      super(appView, code, Timing.empty());
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 12e89e5..818ddcf 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -341,10 +341,7 @@
     assertCounters(ALWAYS_INLINABLE, ALWAYS_INLINABLE, countInvokes(inspector, m));
 
     m = clazz.method("int", "notInlinableDueToSideEffect", ImmutableList.of("inlining.A"));
-    assertCounters(
-        parameters.isCfRuntime() ? ALWAYS_INLINABLE : NEVER_INLINABLE,
-        NEVER_INLINABLE,
-        countInvokes(inspector, m));
+    assertCounters(ALWAYS_INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
 
     m =
         clazz.method(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
index ade059d..89000f3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
@@ -5,11 +5,9 @@
 package com.android.tools.r8.ir.optimize.inliner;
 
 import static com.android.tools.r8.ir.optimize.inliner.testclasses.InliningIntoVisibilityBridgeTestClasses.getClassA;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.R8TestRunResult;
@@ -64,27 +62,23 @@
             .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput);
 
-    // Verify that A.method() is only there if there is an explicit -neverinline rule.
+    // Verify that A.method() is removed unless there is an explicit -neverinline rule.
     {
       ClassSubject classSubject = result.inspector().clazz(getClassA());
-      assertThat(classSubject, isPresent());
+      assertThat(classSubject, isPresentIf(neverInline || parameters.isDexRuntime()));
 
       MethodSubject methodSubject = classSubject.uniqueMethodWithOriginalName("method");
       assertEquals(neverInline, methodSubject.isPresent());
     }
 
-    // Verify that B.method() is still there, and that B.method() is neither a bridge nor a
-    // synthetic method unless there is an explicit -neverinline rule.
+    // Verify that B.method() is removed.
     {
       ClassSubject classSubject =
           result.inspector().clazz(InliningIntoVisibilityBridgeTestClassB.class);
-      assertThat(classSubject, isPresent());
+      assertThat(classSubject, isPresentIf(neverInline || parameters.isDexRuntime()));
 
-      MethodSubject methodSubject = classSubject.uniqueMethodWithOriginalName("method");
-      if (!neverInline) {
-        assertThat(methodSubject, isPresentAndRenamed());
-        assertFalse(methodSubject.isBridge());
-        assertFalse(methodSubject.isSynthetic());
+      if (neverInline) {
+        assertEquals(0, classSubject.allMethods().size());
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantLibraryFieldLoadEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantLibraryFieldLoadEliminationTest.java
new file mode 100644
index 0000000..01593db
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantLibraryFieldLoadEliminationTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.redundantfieldloadelimination;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.PrintStream;
+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 RedundantLibraryFieldLoadEliminationTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8()
+        .addInnerClasses(getClass())
+        .release()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello, world!");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+    assertThat(mainMethodSubject, isPresent());
+    assertEquals(
+        2, mainMethodSubject.streamInstructions().filter(InstructionSubject::isStaticGet).count());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      PrintStream out = System.out;
+      System.currentTimeMillis();
+      PrintStream out2 = System.out;
+      out.print("Hello");
+      out2.println(", world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/B369739224Test.java b/src/test/java/com/android/tools/r8/ir/optimize/string/B369739224Test.java
index 615ba60..bc159f5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/B369739224Test.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/B369739224Test.java
@@ -49,8 +49,7 @@
         .addKeepMainRule(TestClass.class)
         .setMinApi(parameters)
         .run(parameters.getRuntime(), TestClass.class)
-        // TODO(b/369739224): Should throw IndexOutOfBoundsException.
-        .assertSuccessWithOutputLines("46");
+        .assertFailureWithErrorThatThrows(IndexOutOfBoundsException.class);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/B135542760.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/B135542760.java
index 83d1acb..810baae 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/B135542760.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/B135542760.java
@@ -34,7 +34,6 @@
         .addOptionsModification(
             options -> {
               options.testing.enableSwitchToIfRewriting = false;
-              options.testing.enableDeadSwitchCaseElimination = true;
             })
         .enableInliningAnnotations()
         .setMinApi(parameters)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithNonLocalPhiTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithNonLocalPhiTest.java
new file mode 100644
index 0000000..dc5c1d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWithNonLocalPhiTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.switches;
+
+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 StringSwitchWithNonLocalPhiTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8()
+        .addProgramClasses(Main.class)
+        .release()
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo", "1", "Bar", "2", "Baz", "0");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo", "1", "Bar", "2", "Baz", "0");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      test("Foo");
+      test("Bar");
+      test("Baz");
+    }
+
+    static void test(String str) {
+      int hashCode = str.hashCode();
+      int id = 0;
+      switch (hashCode) {
+        case 70822: // "Foo".hashCode()
+          if (str.equals("Foo")) {
+            id = 1;
+          }
+          break;
+        case 66547: // "Bar".hashCode()
+          if (str.equals("Bar")) {
+            id = 2;
+          }
+          break;
+      }
+      switch (id) {
+        case 1:
+          System.out.println("Foo");
+          break;
+        case 2:
+          System.out.println("Bar");
+          break;
+        default:
+          System.out.println("Baz");
+          break;
+      }
+      System.out.println(id);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
index d3f2fed..319f2f0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchCaseRemovalTest.java
@@ -55,7 +55,6 @@
         .addOptionsModification(
             options -> {
               options.testing.enableSwitchToIfRewriting = false;
-              options.testing.enableDeadSwitchCaseElimination = true;
             })
         .enableInliningAnnotations()
         .enableMemberValuePropagationAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.java
index 197fa98..59e9fcf 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentIn4BitRegisterTest.java
@@ -33,8 +33,6 @@
   public void test() throws Exception {
     testForD8()
         .addInnerClasses(getClass())
-        .addOptionsModification(
-            options -> options.getTestingOptions().enableRegisterAllocation8BitRefinement = true)
         .release()
         .setMinApi(parameters)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java
index e5a34c5..ab68384 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/ArgumentInLowRegisterWithMoreThan16RegistersTest.java
@@ -33,8 +33,6 @@
   public void testD8() throws Exception {
     testForD8()
         .addInnerClasses(getClass())
-        .addOptionsModification(
-            options -> options.getTestingOptions().enableRegisterAllocation8BitRefinement = true)
         .release()
         .setMinApi(parameters)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeForConsecutiveArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeForConsecutiveArgumentsTest.java
index 642eb7e..439e6ea 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeForConsecutiveArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeForConsecutiveArgumentsTest.java
@@ -33,8 +33,6 @@
   public void testD8() throws Exception {
     testForD8()
         .addInnerClasses(getClass())
-        .addOptionsModification(
-            options -> options.getTestingOptions().enableRegisterAllocation8BitRefinement = true)
         .release()
         .setMinApi(parameters)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java
index 95ade31..a6452b5 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/InvokeRangeWithSameValueRepeatedTest.java
@@ -28,10 +28,7 @@
     testForD8()
         .addInnerClasses(getClass())
         .addOptionsModification(
-            options -> {
-              options.getTestingOptions().enableRegisterAllocation8BitRefinement = true;
-              options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true;
-            })
+            options -> options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true)
         .release()
         .setMinApi(parameters)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/MoveExceptionInHighestLocalTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/MoveExceptionInHighestLocalTest.java
index 38c5012..a909ae4 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/MoveExceptionInHighestLocalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/MoveExceptionInHighestLocalTest.java
@@ -39,10 +39,9 @@
     testForD8()
         .addInnerClasses(getClass())
         .addOptionsModification(
-            options -> {
-              options.getTestingOptions().enableRegisterAllocation8BitRefinement = true;
-              options.getTestingOptions().enableUseLastLocalRegisterAsMoveExceptionRegister = true;
-            })
+            options ->
+                options.getTestingOptions().enableUseLastLocalRegisterAsMoveExceptionRegister =
+                    true)
         .release()
         .setMinApi(parameters)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java
index d1977ab..c86017b 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantArgumentToPhiMoveIn16BitRegisterAllocationTest.java
@@ -51,9 +51,10 @@
 
   static class Main {
 
-    static void test(long a, long b, long c, long d, long e, long f, long g, long h, int def) {
-      long i = (def & 1) == 0 ? a : 42;
-      accept(i);
+    static void test(
+        long a, long b, long c, long d, long e, long f, long g, long h, long i, int def) {
+      long j = (def & 1) == 0 ? i : 42;
+      accept(j);
     }
 
     static void accept(long a) {}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java
index 1b947ab..f87a53d 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RedundantSpillingBeforeInvokeRangeTest.java
@@ -34,10 +34,7 @@
     testForD8()
         .addInnerClasses(getClass())
         .addOptionsModification(
-            options -> {
-              options.getTestingOptions().enableRegisterAllocation8BitRefinement = true;
-              options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true;
-            })
+            options -> options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true)
         .release()
         .setMinApi(parameters)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index fd91973..3394057 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.PriorityQueue;
@@ -25,7 +26,7 @@
 
   private static class MyRegisterAllocator extends LinearScanRegisterAllocator {
     public MyRegisterAllocator(AppView<?> appView, IRCode code) {
-      super(appView, code);
+      super(appView, code, Timing.empty());
     }
 
     public void addInactiveIntervals(LiveIntervals intervals) {
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java
index 15ff3af..4e2a166 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/SpillToHighUnusedArgumentRegisterTest.java
@@ -5,11 +5,12 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.dex.code.DexMoveFrom16;
 import com.android.tools.r8.dex.code.DexMoveResult;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -36,8 +37,6 @@
   public void testD8() throws Exception {
     testForD8()
         .addInnerClasses(getClass())
-        .addOptionsModification(
-            options -> options.getTestingOptions().enableRegisterAllocation8BitRefinement = true)
         .release()
         .setMinApi(parameters)
         .compile()
@@ -56,13 +55,15 @@
                       .asDexInstruction()
                       .getInstruction();
 
-              // TODO(b/375142715): The test no longer spills the `i` value. Look into if the test
-              //  can be tweeked so that `i` is spilled, and validate that it is spilled to the
-              //  unused argument register.
-              assertTrue(
+              DexMoveFrom16 spillMove =
                   testMethodSubject
                       .streamInstructions()
-                      .noneMatch(i -> i.isMoveFrom(moveResult.AA)));
+                      .filter(i -> i.isMoveFrom(moveResult.AA))
+                      .collect(MoreCollectors.onlyElement())
+                      .asDexInstruction()
+                      .getInstruction();
+              int lastArgumentRegister = code.registerSize - 1;
+              assertEquals(lastArgumentRegister, spillMove.AA);
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java
index 152a959..b8bd25f 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/ValueUsedInMultipleInvokeRangeInstructionsTest.java
@@ -28,10 +28,7 @@
     testForD8()
         .addInnerClasses(getClass())
         .addOptionsModification(
-            options -> {
-              options.getTestingOptions().enableRegisterAllocation8BitRefinement = true;
-              options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true;
-            })
+            options -> options.getTestingOptions().enableRegisterHintsForBlockedRegisters = true)
         .release()
         .setMinApi(parameters)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoInstanceOfTargetTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoInstanceOfTargetTest.java
new file mode 100644
index 0000000..9605e14
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoInstanceOfTargetTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class KeepAnnoInstanceOfTargetTest extends KeepAnnoTestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello anno");
+
+  @Parameter public KeepAnnoParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<KeepAnnoParameters> data() {
+    return createParameters(
+        getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForKeepAnno(parameters)
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .setExcludedOuterClass(getClass())
+        .run(TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            codeInspector -> {
+              // TODO(b/349276075): The rule based version should not keep less than native.
+              if (!parameters.isShrinker() || parameters.isNativeR8()) {
+                assertTrue(codeInspector.clazz(C.class).isPresent());
+              } else {
+                assertFalse(codeInspector.clazz(C.class).isPresent());
+              }
+            });
+  }
+
+  static class TestClass {
+    public static void main(String[] args) throws Exception {
+      new A().foo();
+    }
+  }
+
+  static class A {
+    @UsesReflection(@KeepTarget(instanceOfClassConstant = B.class, methodName = "inject"))
+    public void foo() throws Exception {
+      System.out.println("Hello anno");
+    }
+  }
+
+  static class B {
+    public static void inject() {
+      System.currentTimeMillis();
+    }
+  }
+
+  static class C extends B {
+    public static void inject() {
+      System.currentTimeMillis();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepAnnoWithWhyAreYouKeepingTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoWithWhyAreYouKeepingTest.java
new file mode 100644
index 0000000..52b9cd9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepAnnoWithWhyAreYouKeepingTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2024, 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.keepanno;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class KeepAnnoWithWhyAreYouKeepingTest extends KeepAnnoTestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello anno");
+
+  @Parameter public KeepAnnoParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<KeepAnnoParameters> data() {
+    return createParameters(
+        getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    KeepAnnoTestBuilder keepAnnoTestBuilder =
+        testForKeepAnno(parameters)
+            .addInnerClasses(getClass())
+            .addKeepMainRule(TestClass.class)
+            .setExcludedOuterClass(getClass())
+            .applyIfShrinker(
+                b -> {
+                  b.addKeepRules("-whyareyoukeeping class **B {}");
+                  b.allowStdoutMessages(); //
+                });
+
+    if (parameters.isNativeR8()) {
+      try {
+        keepAnnoTestBuilder.run(TestClass.class);
+      } catch (CompilationFailedException e) {
+        // TODO(b/381217105): Should not throw
+      }
+    } else {
+      keepAnnoTestBuilder
+          .run(TestClass.class)
+          .assertSuccessWithOutput(EXPECTED)
+          .inspect(
+              codeInspector -> {
+                assertTrue(codeInspector.clazz(B.class).init().isPresent());
+              });
+    }
+  }
+
+  static class TestClass {
+    public static void main(String[] args) throws Exception {
+      new A().foo();
+    }
+  }
+
+  static class A {
+    @UsesReflection(@KeepTarget(classConstant = B.class, kind = KeepItemKind.CLASS_AND_MEMBERS))
+    public void foo() throws Exception {
+      System.out.println("Hello anno");
+    }
+  }
+
+  static class B {}
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/DefaultFieldValueAnalysisWithKeptSubclassTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/DefaultFieldValueAnalysisWithKeptSubclassTest.java
index d2e241e..dd9a582 100644
--- a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/DefaultFieldValueAnalysisWithKeptSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/DefaultFieldValueAnalysisWithKeptSubclassTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize.argumentpropagation;
 
-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;
 
@@ -65,11 +64,10 @@
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
-              assertThat(aClassSubject.uniqueFieldWithOriginalName("f"), isAbsent());
+              assertThat(aClassSubject.uniqueFieldWithOriginalName("f"), isPresent());
             })
-        // TODO(b/379034741): Should succeed with expected output.
         .run(parameters.getRuntime(), Main.class, B.class.getTypeName())
-        .assertSuccessWithEmptyOutput();
+        .assertSuccessWithOutputLines("Hello, world!");
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
index 03275f0..3059040 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -150,9 +150,7 @@
     );
 
     Consumer<InternalOptions> optionsConsumer =
-        options -> {
-          options.testing.enableDeadSwitchCaseElimination = false;
-        };
+        options -> options.testing.enableDeadSwitchCaseElimination = false;
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication, optionsConsumer);
     DexEncodedMethod method = getMethod(processedApplication, signature);
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
index 9b61b25..f251a9c 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForJavaLangClassTest.java
@@ -70,19 +70,14 @@
   private void inspectMethod(
       MethodSubject methodSubject, boolean maybeNullReceiver, boolean maybeSubtype) {
     assertThat(methodSubject, isPresent());
-    assertThat(
-        methodSubject, onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("equals")));
-    assertThat(
-        methodSubject,
-        onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("hashCode")));
+    assertThat(methodSubject, onlyIf(maybeSubtype, invokesMethodWithName("equals")));
+    assertThat(methodSubject, onlyIf(maybeSubtype, invokesMethodWithName("hashCode")));
     assertThat(
         methodSubject,
         onlyIf(
             maybeNullReceiver,
             anyOf(invokesMethodWithName("getClass"), invokesMethodWithName("requireNonNull"))));
-    assertThat(
-        methodSubject,
-        onlyIf(maybeNullReceiver || maybeSubtype, invokesMethodWithName("toString")));
+    assertThat(methodSubject, onlyIf(maybeSubtype, invokesMethodWithName("toString")));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
index 693a077..181a85a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -118,7 +118,7 @@
       ClassSubject testClassSubject = inspector.clazz(TestClass.class);
       assertThat(testClassSubject, isPresent());
 
-      if (enableVerticalClassMerging && parameters.canInitNewInstanceUsingSuperclassConstructor()) {
+      if (enableVerticalClassMerging) {
         // Verify that TestClass.field has been removed.
         assertEquals(1, testClassSubject.allFields().size());
 
diff --git a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
index 9642e4e..237753d 100644
--- a/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
+++ b/src/test/java/com/android/tools/r8/smali/IfSimplificationTest.java
@@ -450,8 +450,8 @@
     );
     DexCode code = method.getCode().asDexCode();
     // TODO(sgjesse): Maybe this test is too fragile, as it leaves quite a lot of code, so the
-    // expectation might need changing with other optimizations.
+    //  expectation might need changing with other optimizations.
     // TODO(zerny): Consider optimizing the fallthrough branch of conditionals to not be return.
-    assertEquals(26, code.instructions.length);
+    assertEquals(27, code.instructions.length);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 25e8d89..edacb15 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.dex.code.DexConstWideHigh16;
 import com.android.tools.r8.dex.code.DexDivInt;
 import com.android.tools.r8.dex.code.DexDivInt2Addr;
-import com.android.tools.r8.dex.code.DexGoto;
 import com.android.tools.r8.dex.code.DexInstruction;
 import com.android.tools.r8.dex.code.DexInvokeStatic;
 import com.android.tools.r8.dex.code.DexInvokeStaticRange;
@@ -1294,7 +1293,7 @@
     assertTrue(code.instructions[1] instanceof DexInvokeStatic);
     assertTrue(code.instructions[2] instanceof DexMoveResult);
     assertTrue(code.instructions[3] instanceof DexDivInt2Addr);
-    assertTrue(code.instructions[4] instanceof DexGoto);
+    assertTrue(code.instructions[4] instanceof DexReturn);
     assertTrue(code.instructions[5] instanceof DexConst4);
     assertTrue(code.instructions[6] instanceof DexReturn);
     DexInvokeStatic invoke = (DexInvokeStatic) code.instructions[1];
diff --git a/src/test/testbase/java/com/android/tools/r8/KotlinTestParameters.java b/src/test/testbase/java/com/android/tools/r8/KotlinTestParameters.java
index ba16ee1..692c387 100644
--- a/src/test/testbase/java/com/android/tools/r8/KotlinTestParameters.java
+++ b/src/test/testbase/java/com/android/tools/r8/KotlinTestParameters.java
@@ -192,7 +192,9 @@
       int index = 0;
       List<KotlinCompilerVersion> compilerVersions;
       if (withDevCompiler) {
-        compilerVersions = ImmutableList.of(KotlinCompilerVersion.KOTLIN_DEV);
+        compilerVersions =
+            ImmutableList.of(
+                KotlinCompilerVersion.KOTLINC_2_1_0_BETA1, KotlinCompilerVersion.KOTLIN_DEV);
       } else if (withOldCompilers) {
         compilerVersions =
             Arrays.stream(KotlinCompilerVersion.values())
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
similarity index 84%
rename from src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
rename to src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 4c2c44a..18c8f65 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -36,13 +36,14 @@
 import org.junit.Assert;
 import org.junit.Test;
 
-abstract class AbstractBackportTest extends TestBase {
+public abstract class AbstractBackportTest extends TestBase {
   protected final TestParameters parameters;
   private final ClassInfo targetClass;
   private final ClassInfo testClass;
   private final Path testJar;
   private final String testClassName;
   private final Int2IntSortedMap invokeStaticCounts = new Int2IntAVLTreeMap();
+  private final Int2IntSortedMap staticGetCounts = new Int2IntAVLTreeMap();
   private final Set<String> ignoredInvokes = new HashSet<>();
 
   private static class ClassInfo {
@@ -85,22 +86,22 @@
     }
   }
 
-  AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
-      Class<?> testClass) {
+  protected AbstractBackportTest(
+      TestParameters parameters, Class<?> targetClass, Class<?> testClass) {
     this(parameters, new ClassInfo(targetClass), new ClassInfo(testClass), null, null);
   }
 
-  AbstractBackportTest(
+  protected AbstractBackportTest(
       TestParameters parameters, Class<?> targetClass, List<byte[]> testClassFileData) {
     this(parameters, new ClassInfo(targetClass), new ClassInfo(testClassFileData), null, null);
   }
 
-  AbstractBackportTest(
+  protected AbstractBackportTest(
       TestParameters parameters, String className, List<byte[]> testClassFileData) {
     this(parameters, new ClassInfo(className), new ClassInfo(testClassFileData), null, null);
   }
 
-  AbstractBackportTest(
+  protected AbstractBackportTest(
       TestParameters parameters, byte[] targetClassFileData, List<byte[]> testClassFileData) {
     this(
         parameters,
@@ -110,8 +111,8 @@
         null);
   }
 
-  AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
-      Path testJar, String testClassName) {
+  public AbstractBackportTest(
+      TestParameters parameters, Class<?> targetClass, Path testJar, String testClassName) {
     this(parameters, new ClassInfo(targetClass), null, testJar, testClassName);
   }
 
@@ -136,20 +137,30 @@
       this.testClassName = testClassName;
     }
 
-    // Assume all method calls will be rewritten on the lowest API level.
+    // Assume all method calls and static gets will be rewritten on the lowest API level.
     invokeStaticCounts.put(AndroidApiLevel.B.getLevel(), 0);
+    staticGetCounts.put(AndroidApiLevel.B.getLevel(), 0);
   }
 
-  void registerTarget(AndroidApiLevel apiLevel, int invokeStaticCount) {
+  protected void registerTarget(AndroidApiLevel apiLevel, int invokeStaticCount) {
     invokeStaticCounts.put(apiLevel.getLevel(), invokeStaticCount);
   }
 
+  void registerFieldTarget(AndroidApiLevel apiLevel, int getStaticCount) {
+    staticGetCounts.put(apiLevel.getLevel(), getStaticCount);
+  }
+
   private int getTargetInvokesCount(AndroidApiLevel apiLevel) {
     int key = invokeStaticCounts.headMap(apiLevel.getLevel() + 1).lastIntKey();
     return invokeStaticCounts.get(key);
   }
 
-  void ignoreInvokes(String methodName) {
+  private int getTargetGetCount(AndroidApiLevel apiLevel) {
+    int key = staticGetCounts.headMap(apiLevel.getLevel() + 1).lastIntKey();
+    return staticGetCounts.get(key);
+  }
+
+  protected void ignoreInvokes(String methodName) {
     ignoredInvokes.add(methodName);
   }
 
@@ -243,6 +254,27 @@
         + actualTargetInvokes
         + ": "
         + javaInvokeStatics, expectedTargetInvokes, actualTargetInvokes);
+
+    List<InstructionSubject> javaStaticGets =
+        testSubject.allMethods().stream()
+            .flatMap(MethodSubject::streamInstructions)
+            .filter(InstructionSubject::isStaticGet)
+            .filter(is -> is.getField().holder.toSourceString().equals(targetClass.getName()))
+            .collect(toList());
+
+    long expectedTargetStaticGets = getTargetGetCount(apiLevel);
+    long actualTargetStaticGets = javaStaticGets.size();
+    assertEquals(
+        "Expected "
+            + expectedTargetStaticGets
+            + " static gets on "
+            + targetClass.getName()
+            + " but found "
+            + actualTargetStaticGets
+            + ": "
+            + javaStaticGets,
+        expectedTargetStaticGets,
+        actualTargetStaticGets);
   }
 
   public String getTestClassName() {
@@ -250,7 +282,7 @@
   }
 
   /** JUnit {@link Assert} isn't available in the VM runtime. This is a mini mirror of its API. */
-  static abstract class MiniAssert {
+  public abstract static class MiniAssert {
     static void assertTrue(boolean value) {
       assertEquals(true, value);
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java b/src/test/testbase/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java
similarity index 72%
rename from src/test/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java
rename to src/test/testbase/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java
index 2e61664..76aa62c 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/backports/IgnoreInvokes.java
@@ -11,13 +11,12 @@
 import java.lang.annotation.Target;
 
 /**
- * Denote a method that contains invoke instructions on the target class which should be ignored
- * in the counts. This is useful for using other functionality of the target class to verify the
+ * Denote a method that contains invoke instructions on the target class which should be ignored in
+ * the counts. This is useful for using other functionality of the target class to verify the
  * behavior of the backport.
  *
- * Methods with this annotation will never be inlined.
+ * <p>Methods with this annotation will never be inlined.
  */
 @Target(METHOD)
 @Retention(RUNTIME)
-@interface IgnoreInvokes {
-}
+public @interface IgnoreInvokes {}
diff --git a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index de1aec9..da0c617 100644
--- a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -134,6 +134,10 @@
     return syntheticClass(classReference, naming.BACKPORT, id);
   }
 
+  public static ClassReference syntheticBackportWithForwardingClass(Class<?> clazz, int id) {
+    return syntheticClass(clazz, naming.BACKPORT_WITH_FORWARDING, id);
+  }
+
   public static ClassReference syntheticBackportWithForwardingClass(
       ClassReference classReference, int id) {
     return syntheticClass(classReference, naming.BACKPORT_WITH_FORWARDING, id);
diff --git a/third_party/android_jar/lib-v36.tar.gz.sha1 b/third_party/android_jar/lib-v36.tar.gz.sha1
new file mode 100644
index 0000000..939f711
--- /dev/null
+++ b/third_party/android_jar/lib-v36.tar.gz.sha1
@@ -0,0 +1 @@
+f6fe8070ccad90b10d1bcc28a90f5befd3d9afb4
\ No newline at end of file
diff --git a/third_party/android_jar/libcore_latest.tar.gz.sha1 b/third_party/android_jar/libcore_latest.tar.gz.sha1
index 8200e76..4bcde1b 100644
--- a/third_party/android_jar/libcore_latest.tar.gz.sha1
+++ b/third_party/android_jar/libcore_latest.tar.gz.sha1
@@ -1 +1 @@
-96a23da90f1b9412821cef769f3524ef6b05436b
\ No newline at end of file
+6ea109a75610a6f2e173a361bd2345e622b8be06
\ No newline at end of file
diff --git a/third_party/api_database/api_database.tar.gz.sha1 b/third_party/api_database/api_database.tar.gz.sha1
index 966e156c..b81dbaf 100644
--- a/third_party/api_database/api_database.tar.gz.sha1
+++ b/third_party/api_database/api_database.tar.gz.sha1
@@ -1 +1 @@
-0aab829fe216f39035a282024966bbd26bea3a26
\ No newline at end of file
+912f0e6d9fb5e1789c458801f3ed8e6c4e700767
\ No newline at end of file
diff --git a/third_party/google/google-java-format/1.14.0.tar.gz.sha1 b/third_party/google/google-java-format/1.14.0.tar.gz.sha1
deleted file mode 100644
index 4989c27..0000000
--- a/third_party/google/google-java-format/1.14.0.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-b26f4b8c825401d198d54d5e230779fe7fc3c132
\ No newline at end of file
diff --git a/third_party/google/google-java-format/1.24.0.tar.gz.sha1 b/third_party/google/google-java-format/1.24.0.tar.gz.sha1
new file mode 100644
index 0000000..a5ed90d
--- /dev/null
+++ b/third_party/google/google-java-format/1.24.0.tar.gz.sha1
@@ -0,0 +1 @@
+f15e5757395aaeb77e9940884c46b29f1abfade7
\ No newline at end of file
diff --git a/tools/add-android-sdk.py b/tools/add-android-sdk.py
index e299172..5d00eab 100755
--- a/tools/add-android-sdk.py
+++ b/tools/add-android-sdk.py
@@ -21,6 +21,10 @@
                         required=True,
                         metavar=('<name>'),
                         help='Name of the SDK, either API level or code name')
+    parser.add_argument('--api-level',
+                        '--api_level',
+                        metavar=('<level>'),
+                        help='API level to add this as in third_party')
     return parser.parse_args()
 
 
@@ -60,7 +64,15 @@
         print('Path %s does not exist' % source)
         sys.exit(1)
 
-    destination = utils.get_android_jar_dir(args.sdk_name)
+    api_level = -1
+    try:
+        api_level = int(args.api_level if args.api_level else args.sdk_name)
+    except:
+        print('API level "%s" must be an integer'
+            % (args.api_level if args.api_level else args.sdk_name))
+        sys.exit(1)
+
+    destination = utils.get_android_jar_dir(api_level)
 
     # Remove existing if present.
     shutil.rmtree(destination, ignore_errors=True)
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 43267ef..c27fcf8 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -150,6 +150,10 @@
                         help='Run as a platform build',
                         default=False,
                         action='store_true')
+    parser.add_argument('--optimized-resource-shrinking',
+                        help='Use optimized resource shrinking',
+                        default=False,
+                        action='store_true')
     parser.add_argument('--compilation-mode',
                         '--compilation_mode',
                         help='Run compilation in specified mode',
@@ -427,6 +431,10 @@
         return True
     return build_properties.get('android-platform-build') == 'true'
 
+def determine_optimized_resource_shrinking(args, build_properties):
+    if args.optimized_resource_shrinking:
+        return True
+    return build_properties.get('optimized-resource-shrinking') == 'true'
 
 def determine_enable_missing_library_api_modeling(args, build_properties):
     if args.enable_missing_library_api_modeling:
@@ -600,6 +608,8 @@
         classfile = determine_class_file(args, build_properties)
         android_platform_build = determine_android_platform_build(
             args, build_properties)
+        optimized_resource_shrinking = determine_optimized_resource_shrinking(
+            args, build_properties)
         enable_missing_library_api_modeling = determine_enable_missing_library_api_modeling(
             args, build_properties)
         mode = determine_compilation_mode(args, build_properties)
@@ -715,6 +725,8 @@
             cmd.extend(['--classfile'])
         if android_platform_build:
             cmd.extend(['--android-platform-build'])
+        if optimized_resource_shrinking:
+            cmd.extend(['--optimized-resource-shrinking'])
         if enable_missing_library_api_modeling:
             cmd.extend(['--enable-missing-library-api-modeling'])
         if args.threads:
diff --git a/tools/d8.py b/tools/d8.py
index 636b7e7..b831849 100755
--- a/tools/d8.py
+++ b/tools/d8.py
@@ -17,6 +17,11 @@
                       '--commit_hash',
                       help='Commit hash of D8 to use.',
                       default=None)
+    parser.add_option('--disable-assertions',
+                      '--disable_assertions',
+                      help='Disable assertions when running',
+                      default=False,
+                      action='store_true')
     parser.add_option('--print-runtimeraw',
                       '--print_runtimeraw',
                       metavar='BENCHMARKNAME',
@@ -40,6 +45,7 @@
     return toolhelper.run('d8',
                           d8_args,
                           build=not options.no_build,
+                          disable_assertions=options.disable_assertions,
                           jar=utils.find_r8_jar_from_options(options),
                           main='com.android.tools.r8.D8',
                           time_consumer=time_consumer)
diff --git a/tools/disasm.py b/tools/disasm.py
index e341557..e050d9c 100755
--- a/tools/disasm.py
+++ b/tools/disasm.py
@@ -7,4 +7,4 @@
 import toolhelper
 
 if __name__ == '__main__':
-    sys.exit(toolhelper.run('disasm', sys.argv[1:], debug=False))
+    sys.exit(toolhelper.run('disasm', sys.argv[1:], disable_assertions=True))
diff --git a/tools/fmt-diff.py b/tools/fmt-diff.py
index 0e4ada7..3a63fcf 100755
--- a/tools/fmt-diff.py
+++ b/tools/fmt-diff.py
@@ -13,8 +13,8 @@
 from subprocess import Popen, PIPE
 
 GOOGLE_JAVA_FORMAT_DIFF = os.path.join(utils.THIRD_PARTY, 'google',
-                                       'google-java-format', '1.14.0',
-                                       'google-java-format-1.14.0', 'scripts',
+                                       'google-java-format', '1.24.0',
+                                       'google-java-format-1.24.0', 'scripts',
                                        'google-java-format-diff.py')
 
 GOOGLE_YAPF = os.path.join(utils.THIRD_PARTY, 'google/yapf/20231013')
diff --git a/tools/minify_tool.py b/tools/minify_tool.py
index 2356ac1..c51f861 100755
--- a/tools/minify_tool.py
+++ b/tools/minify_tool.py
@@ -125,7 +125,7 @@
         start_time = time.time()
         return_code = toolhelper.run('r8',
                                      args,
-                                     debug=debug,
+                                     disable_assertions=not debug,
                                      build=build,
                                      track_memory_file=track_memory_file,
                                      extra_args=java_args)
diff --git a/tools/r8.py b/tools/r8.py
index 5a2f5e3..a6bf42e 100755
--- a/tools/r8.py
+++ b/tools/r8.py
@@ -23,12 +23,11 @@
         'Enable Java debug agent and suspend compilation (default disabled)',
         default=False,
         action='store_true')
-    parser.add_option(
-        '--ea',
-        help=
-        'Enable Java assertions when running the compiler (default disabled)',
-        default=False,
-        action='store_true')
+    parser.add_option('--disable-assertions',
+                      '--disable_assertions',
+                      help='Disable assertions when running',
+                      default=False,
+                      action='store_true')
     parser.add_option('--lib-android',
                       help='Add the android.jar for the given API level',
                       default=None,
@@ -64,7 +63,7 @@
     return toolhelper.run('r8',
                           r8_args,
                           build=not options.no_build,
-                          debug=options.ea,
+                          disable_assertions=options.disable_assertions,
                           debug_agent=options.debug_agent,
                           jar=utils.find_r8_jar_from_options(options),
                           main='com.android.tools.r8.R8',
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index ffe4298..f106022 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -523,7 +523,7 @@
     exit_code = toolhelper.run(tool,
                                args,
                                build=should_build(options),
-                               debug=not options.disable_assertions,
+                               disable_assertions=options.disable_assertions,
                                quiet=quiet,
                                jar=jar,
                                main=main)
@@ -743,7 +743,7 @@
                         tool,
                         args,
                         build=False,
-                        debug=not options.disable_assertions,
+                        disable_assertions=options.disable_assertions,
                         profile=options.profile,
                         track_memory_file=options.track_memory_to_file,
                         extra_args=extra_args,
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index cfc386c..15fa557 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -15,7 +15,7 @@
 def run(tool,
         args,
         build=None,
-        debug=True,
+        disable_assertions=False,
         profile=False,
         track_memory_file=None,
         extra_args=None,
@@ -51,7 +51,7 @@
         cmd.append(
             '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005'
         )
-    if debug:
+    if not disable_assertions:
         cmd.append('-ea')
     if profile:
         cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')