[LIR] Use LIR in R8 conversion and inlining

Bug: b/225838009
Change-Id: I02f5df60c983fed94122939721d59d90917eace6
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index a76bdf5..3332852 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.PositionBuilder;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.lightir.LirCode;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -148,6 +149,14 @@
 
   public abstract int estimatedDexCodeSizeUpperBoundInBytes();
 
+  public final boolean isLirCode() {
+    return asLirCode() != null;
+  }
+
+  public LirCode<Integer> asLirCode() {
+    return null;
+  }
+
   public CfCode asCfCode() {
     throw new Unreachable(getClass().getCanonicalName() + ".asCfCode()");
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index d871616..ca741c9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -36,8 +36,14 @@
     this.fieldBitAccessAnalysis =
         options.enableFieldBitAccessAnalysis ? new FieldBitAccessAnalysis() : null;
     this.fieldAssignmentTracker = new FieldAssignmentTracker(appView);
-    this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView);
-    this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView);
+    if (options.testing.canUseLir(appView)) {
+      // When using LIR the bytecode metadata is computed later during finalization via IR.
+      this.fieldReadForInvokeReceiverAnalysis = null;
+      this.fieldReadForWriteAnalysis = null;
+    } else {
+      this.fieldReadForInvokeReceiverAnalysis = new FieldReadForInvokeReceiverAnalysis(appView);
+      this.fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView);
+    }
   }
 
   @VisibleForTesting
@@ -111,4 +117,32 @@
       }
     }
   }
+
+  public static BytecodeMetadataProvider computeBytecodeMetadata(
+      IRCode irCode, AppView<AppInfoWithLiveness> appView) {
+    // This rebuilding of metadata should only be used in the LIR pipeline where the info is
+    // discarded when translating from IR to LIR.
+    assert appView.options().testing.canUseLir(appView);
+    BytecodeMetadataProvider bytecodeMetadataProvider = BytecodeMetadataProvider.empty();
+    if (irCode.metadata().mayHaveFieldInstruction()) {
+      BytecodeMetadataProvider.Builder builder = BytecodeMetadataProvider.builder();
+      FieldReadForInvokeReceiverAnalysis fieldReadForInvokeReceiverAnalysis =
+          new FieldReadForInvokeReceiverAnalysis(appView);
+      FieldReadForWriteAnalysis fieldReadForWriteAnalysis = new FieldReadForWriteAnalysis(appView);
+      for (Instruction instruction : irCode.instructions()) {
+        if (instruction.isFieldInstruction()) {
+          FieldInstruction fieldInstruction = instruction.asFieldInstruction();
+          ProgramField field =
+              appView.appInfo().resolveField(fieldInstruction.getField()).getProgramField();
+          if (field != null) {
+            fieldReadForInvokeReceiverAnalysis.recordFieldAccess(
+                fieldInstruction, field, builder, irCode.context());
+            fieldReadForWriteAnalysis.recordFieldAccess(fieldInstruction, field, builder);
+          }
+        }
+      }
+      bytecodeMetadataProvider = builder.build();
+    }
+    return bytecodeMetadataProvider;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java
index 976020c6..6289b5d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForInvokeReceiverAnalysis.java
@@ -23,7 +23,7 @@
 
   private final AppView<AppInfoWithLiveness> appView;
 
-  FieldReadForInvokeReceiverAnalysis(AppView<AppInfoWithLiveness> appView) {
+  public FieldReadForInvokeReceiverAnalysis(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java
index 6a5c420..2155676 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldReadForWriteAnalysis.java
@@ -21,7 +21,7 @@
 
   private final AppView<AppInfoWithLiveness> appView;
 
-  FieldReadForWriteAnalysis(AppView<AppInfoWithLiveness> appView) {
+  public FieldReadForWriteAnalysis(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
   }
 
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 a7d54cab..e943123 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
@@ -616,12 +616,14 @@
 
     assertionsRewriter.run(method, code, deadCodeRemover, timing);
     CheckNotNullConverter.runIfNecessary(appView, code);
+    previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
     if (serviceLoaderRewriter != null) {
       assert appView.appInfo().hasLiveness();
       timing.begin("Rewrite service loaders");
       serviceLoaderRewriter.rewrite(code, methodProcessor, methodProcessingContext);
       timing.end();
+      previous = printMethod(code, "IR after service rewriting (SSA)", previous);
     }
 
     if (identifierNameStringMarker != null) {
@@ -629,12 +631,14 @@
       identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(code);
       timing.end();
       assert code.isConsistentSSA(appView);
+      previous = printMethod(code, "IR after identifier-name strings (SSA)", previous);
     }
 
     if (memberValuePropagation != null) {
       timing.begin("Propagate member values");
       memberValuePropagation.run(code);
       timing.end();
+      previous = printMethod(code, "IR after member-value propagation (SSA)", previous);
     }
 
     if (enumValueOptimizer != null) {
@@ -642,16 +646,16 @@
       timing.begin("Remove switch maps");
       enumValueOptimizer.removeSwitchMaps(code);
       timing.end();
+      previous = printMethod(code, "IR after enum-value optimization (SSA)", previous);
     }
 
     if (instanceInitializerOutliner != null) {
       instanceInitializerOutliner.rewriteInstanceInitializers(
           code, context, methodProcessor, methodProcessingContext);
       assert code.verifyTypes(appView);
+      previous = printMethod(code, "IR after instance initializer outlining (SSA)", previous);
     }
 
-    previous = printMethod(code, "IR after disable assertions (SSA)", previous);
-
     // Update the IR code if collected call site optimization info has something useful.
     // While aggregation of parameter information at call sites would be more precise than static
     // types, those could be still less precise at one single call site, where specific arguments
@@ -1061,7 +1065,11 @@
     if (options.testing.roundtripThroughLir) {
       code = roundtripThroughLir(code, feedback, bytecodeMetadataProvider, timing);
     }
-    if (options.isGeneratingClassFiles()) {
+    if (options.testing.canUseLir(appView)) {
+      timing.begin("IR->LIR");
+      finalizeToLir(code, feedback, bytecodeMetadataProvider, timing);
+      timing.end();
+    } else if (options.isGeneratingClassFiles()) {
       timing.begin("IR->CF");
       finalizeToCf(code, feedback, bytecodeMetadataProvider, timing);
       timing.end();
@@ -1101,7 +1109,7 @@
       IRCode code, S strategy, String name, Timing timing) {
     timing.begin("IR->LIR (" + name + ")");
     LirCode<EV> lirCode =
-        IR2LirConverter.translate(code, strategy.getEncodingStrategy(), appView.dexItemFactory());
+        IR2LirConverter.translate(code, strategy.getEncodingStrategy(), appView.options());
     timing.end();
     // Check that printing does not fail.
     String lirString = lirCode.toString();
@@ -1109,11 +1117,26 @@
     timing.begin("LIR->IR (" + name + ")");
     IRCode irCode =
         Lir2IRConverter.translate(
-            code.context(), lirCode, strategy.getDecodingStrategy(lirCode), appView);
+            code.context(), lirCode, strategy.getDecodingStrategy(lirCode, null), appView);
     timing.end();
     return irCode;
   }
 
+  private void finalizeToLir(
+      IRCode code,
+      OptimizationFeedback feedback,
+      BytecodeMetadataProvider bytecodeMetadataProvider,
+      Timing timing) {
+    assert deadCodeRemover.verifyNoDeadCode(code);
+    assert BytecodeMetadataProvider.empty() == bytecodeMetadataProvider;
+    LirCode<Integer> lirCode =
+        IR2LirConverter.translate(
+            code, LirStrategy.getDefaultStrategy().getEncodingStrategy(), appView.options());
+    ProgramMethod method = code.context();
+    method.setCode(lirCode, appView);
+    markProcessed(code, feedback);
+  }
+
   private void finalizeToCf(
       IRCode code,
       OptimizationFeedback feedback,
@@ -1251,4 +1274,32 @@
       inliner.onMethodCodePruned(method);
     }
   }
+
+  public void finalizeLirMethodToOutputFormat(ProgramMethod method) {
+    Code code = method.getDefinition().getCode();
+    if (!(code instanceof LirCode)) {
+      return;
+    }
+    Timing onThreadTiming = Timing.empty();
+    IRCode irCode = method.buildIR(appView);
+    BytecodeMetadataProvider bytecodeMetadataProvider =
+        FieldAccessAnalysis.computeBytecodeMetadata(irCode, appView.withLiveness());
+    // During processing optimization info may cause previously live code to become dead.
+    // E.g., we may now have knowledge that an invoke does not have side effects.
+    // Thus, we re-run the dead-code remover now as it is assumed complete by CF/DEX finalization.
+    deadCodeRemover.run(irCode, onThreadTiming);
+    if (options.isGeneratingClassFiles()) {
+      method.setCode(
+          new IRToCfFinalizer(appView, deadCodeRemover)
+              .finalizeCode(irCode, bytecodeMetadataProvider, onThreadTiming),
+          appView);
+    } else {
+      assert options.isGeneratingDex();
+      method.setCode(
+          new IRToDexFinalizer(appView, deadCodeRemover)
+              .finalizeCode(irCode, bytecodeMetadataProvider, onThreadTiming),
+          appView);
+      updateHighestSortingStrings(method.getDefinition());
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index f8946d4..1187f29 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.io.IOException;
@@ -100,6 +101,7 @@
       lastWaveDone(postMethodProcessorBuilder, executorService);
       eventConsumer.finished(appView);
       assert appView.graphLens() == graphLensForPrimaryOptimizationPass;
+      finalizeLirToOutputFormat(timing, executorService);
       timing.end();
     }
 
@@ -176,6 +178,7 @@
         eventConsumer.finished(appView);
         assert appView.graphLens() == graphLensForSecondaryOptimizationPass;
       }
+      finalizeLirToOutputFormat(timing, executorService);
       timing.end();
     }
 
@@ -207,9 +210,28 @@
 
     // Assure that no more optimization feedback left after post processing.
     assert feedback.noUpdatesLeft();
+    finalizeLirToOutputFormat(timing, executorService);
     return builder.build();
   }
 
+  private void finalizeLirToOutputFormat(Timing timing, ExecutorService executorService)
+      throws ExecutionException {
+    if (!options.testing.canUseLir(appView)) {
+      return;
+    }
+    String output = options.isGeneratingClassFiles() ? "CF" : "DEX";
+    timing.begin("LIR->IR->" + output);
+    ThreadUtils.processItems(
+        appView.appInfo().classes(),
+        clazz -> clazz.forEachProgramMethod(this::finalizeLirMethodToOutputFormat),
+        executorService);
+    appView
+        .getSyntheticItems()
+        .getPendingSyntheticClasses()
+        .forEach(clazz -> clazz.forEachProgramMethod(this::finalizeLirMethodToOutputFormat));
+    timing.end();
+  }
+
   private void clearDexMethodCompilationState() {
     appView.appInfo().classes().forEach(this::clearDexMethodCompilationState);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
index 1653ae2..6646ce0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
@@ -85,6 +85,7 @@
     assert done;
     irConverter.removeDeadCodeAndFinalizeIR(
         irCode, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+    irConverter.finalizeLirMethodToOutputFormat(programMethod);
   }
 
   public void rewriteRecordFieldArray(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index 12028fb..cf4a6fe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -136,7 +136,7 @@
           break;
 
         case RETURN:
-          // Wil not materialize after class inlining.
+          // Will not materialize after class inlining.
           if (appView.options().isGeneratingClassFiles()) {
             result++;
           } else {
diff --git a/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java b/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java
index ee53ceb..3bc2fe6 100644
--- a/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/IR2LirConverter.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.lightir;
 
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlockIterator;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -12,6 +11,7 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
@@ -24,24 +24,22 @@
 
 public class IR2LirConverter<EV> {
 
-  private final DexItemFactory factory;
   private final IRCode irCode;
   private final LirEncodingStrategy<Value, EV> strategy;
   private final LirBuilder<Value, EV> builder;
 
   private IR2LirConverter(
-      DexItemFactory factory, IRCode irCode, LirEncodingStrategy<Value, EV> strategy) {
-    this.factory = factory;
+      InternalOptions options, IRCode irCode, LirEncodingStrategy<Value, EV> strategy) {
     this.irCode = irCode;
     this.strategy = strategy;
     this.builder =
-        new LirBuilder<>(irCode.context().getReference(), strategy, factory)
+        new LirBuilder<>(irCode.context().getReference(), strategy, options)
             .setMetadata(irCode.metadata());
   }
 
   public static <EV> LirCode<EV> translate(
-      IRCode irCode, LirEncodingStrategy<Value, EV> strategy, DexItemFactory factory) {
-    return new IR2LirConverter<>(factory, irCode, strategy).internalTranslate();
+      IRCode irCode, LirEncodingStrategy<Value, EV> strategy, InternalOptions options) {
+    return new IR2LirConverter<>(options, irCode, strategy).internalTranslate();
   }
 
   private void recordBlock(BasicBlock block, int blockIndex) {
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index 8777089..4315a85 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -15,6 +15,11 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.proto.ArgumentInfo;
+import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
+import com.android.tools.r8.graph.proto.RemovedArgumentInfo;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -25,6 +30,7 @@
 import com.android.tools.r8.ir.code.ArrayLength;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.Cmp;
@@ -116,7 +122,36 @@
       LirCode<EV> lirCode,
       LirDecodingStrategy<Value, EV> strategy,
       AppView<?> appView) {
-    Parser<EV> parser = new Parser<>(lirCode, method.getReference(), appView, strategy);
+    return translate(
+        method,
+        lirCode,
+        strategy,
+        appView,
+        new NumberGenerator(),
+        null,
+        RewrittenPrototypeDescription.none(),
+        appView.graphLens().getOriginalMethodSignature(method.getReference()));
+  }
+
+  public static <EV> IRCode translate(
+      ProgramMethod method,
+      LirCode<EV> lirCode,
+      LirDecodingStrategy<Value, EV> strategy,
+      AppView<?> appView,
+      NumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      RewrittenPrototypeDescription protoChanges,
+      DexMethod originalMethod) {
+    Parser<EV> parser =
+        new Parser<>(
+            lirCode,
+            originalMethod,
+            method.getDefinition().isD8R8Synthesized(),
+            appView,
+            strategy,
+            valueNumberGenerator,
+            callerPosition,
+            protoChanges);
     parser.parseArguments(method);
     parser.ensureDebugInfo();
     lirCode.forEach(view -> view.accept(parser));
@@ -134,8 +169,9 @@
     private final AppView<?> appView;
     private final LirCode<EV> code;
     private final LirDecodingStrategy<Value, EV> strategy;
-    private final NumberGenerator valueNumberGenerator = new NumberGenerator();
+    private final NumberGenerator valueNumberGenerator;
     private final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
+    private final RewrittenPrototypeDescription protoChanges;
 
     private final Int2ReferenceMap<BasicBlock> blocks = new Int2ReferenceOpenHashMap<>();
 
@@ -145,18 +181,62 @@
     private Position currentPosition;
     private PositionEntry nextPositionEntry = null;
     private int nextIndexInPositionsTable = 0;
+    private final PositionEntry[] positionTable;
+
+    private final boolean buildForInlining;
 
     public Parser(
         LirCode<EV> code,
         DexMethod method,
+        boolean isD8R8Synthesized,
         AppView<?> appView,
-        LirDecodingStrategy<Value, EV> strategy) {
+        LirDecodingStrategy<Value, EV> strategy,
+        NumberGenerator valueNumberGenerator,
+        Position callerPosition,
+        RewrittenPrototypeDescription protoChanges) {
       super(code);
       this.appView = appView;
       this.code = code;
       this.strategy = strategy;
-      // Recreate the preamble position. This is active for arguments and code with no positions.
-      currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build();
+      this.valueNumberGenerator = valueNumberGenerator;
+      this.protoChanges = protoChanges;
+      assert protoChanges != null;
+      if (callerPosition == null) {
+        buildForInlining = false;
+        positionTable = code.getPositionTable();
+        // Recreate the preamble position. This is active for arguments and code with no positions.
+        currentPosition = SyntheticPosition.builder().setLine(0).setMethod(method).build();
+      } else {
+        buildForInlining = true;
+        PositionEntry[] inlineePositions = code.getPositionTable();
+        Position inlineePreamble = null;
+        if (inlineePositions.length > 0 && inlineePositions[0].fromInstructionIndex == 0) {
+          inlineePreamble = inlineePositions[0].position;
+        }
+        CanonicalPositions canonicalPositions =
+            new CanonicalPositions(
+                callerPosition,
+                inlineePositions.length,
+                method,
+                isD8R8Synthesized,
+                inlineePreamble);
+        currentPosition = canonicalPositions.getPreamblePosition();
+        positionTable = new PositionEntry[inlineePositions.length];
+        for (int i = 0; i < inlineePositions.length; i++) {
+          PositionEntry inlineeEntry = inlineePositions[i];
+          Position inlineePosition = inlineeEntry.position;
+          positionTable[i] =
+              new PositionEntry(
+                  inlineeEntry.fromInstructionIndex,
+                  canonicalPositions.getCanonical(
+                      inlineePosition
+                          .builderWithCopy()
+                          .setCallerPosition(
+                              canonicalPositions.canonicalizeCallerPosition(
+                                  inlineePosition.getCallerPosition()))
+                          .build()));
+        }
+      }
     }
 
     @Override
@@ -197,23 +277,47 @@
 
     private void advanceNextPositionEntry() {
       nextPositionEntry =
-          nextIndexInPositionsTable < code.getPositionTable().length
-              ? code.getPositionTable()[nextIndexInPositionsTable++]
+          nextIndexInPositionsTable < positionTable.length
+              ? positionTable[nextIndexInPositionsTable++]
               : null;
     }
 
     public void parseArguments(ProgramMethod method) {
+      ArgumentInfoCollection argumentsInfo = protoChanges.getArgumentInfoCollection();
       currentBlock = getBasicBlock(ENTRY_BLOCK_INDEX);
       boolean hasReceiverArgument = !method.getDefinition().isStatic();
-      assert code.getArgumentCount()
-          == method.getParameters().size() + (hasReceiverArgument ? 1 : 0);
+
+      int index = 0;
       if (hasReceiverArgument) {
+        assert argumentsInfo.getNewArgumentIndex(0) == 0;
         addThisArgument(method.getHolderType());
+        index++;
       }
-      int index = hasReceiverArgument ? 1 : 0;
-      for (DexType parameter : method.getParameters()) {
-        addArgument(parameter, index++);
+
+      int originalNumberOfArguments =
+          method.getParameters().size()
+              + argumentsInfo.numberOfRemovedArguments()
+              + method.getDefinition().getFirstNonReceiverArgumentIndex()
+              - protoChanges.numberOfExtraParameters();
+
+      int numberOfRemovedArguments = 0;
+      while (index < originalNumberOfArguments) {
+        ArgumentInfo argumentInfo = argumentsInfo.getArgumentInfo(index);
+        if (argumentInfo.isRemovedArgumentInfo()) {
+          RemovedArgumentInfo removedArgumentInfo = argumentInfo.asRemovedArgumentInfo();
+          addArgument(removedArgumentInfo.getType(), index++);
+          numberOfRemovedArguments++;
+        } else if (argumentInfo.isRewrittenTypeInfo()) {
+          RewrittenTypeInfo rewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo();
+          int newArgumentIndex = argumentsInfo.getNewArgumentIndex(index, numberOfRemovedArguments);
+          assert method.getArgumentType(newArgumentIndex) == rewrittenTypeInfo.getNewType();
+          addArgument(rewrittenTypeInfo.getOldType(), index++);
+        } else {
+          int newArgumentIndex = argumentsInfo.getNewArgumentIndex(index, numberOfRemovedArguments);
+          addArgument(method.getArgumentType(newArgumentIndex), index++);
+        }
       }
+
       // Set up position state after adding arguments.
       advanceNextPositionEntry();
     }
@@ -252,8 +356,14 @@
           }
         }
       }
-      for (int i = 0; i < peekNextInstructionIndex(); ++i) {
-        valueNumberGenerator.next();
+      if (!buildForInlining) {
+        // The decoding strategy will increment this on demand when built for inlining.
+        // Not incrementing for normal building results in nice order of instruction index and
+        // value number.
+        int lastValueIndex = getCurrentValueIndex();
+        for (int i = 0; i < lastValueIndex; ++i) {
+          valueNumberGenerator.next();
+        }
       }
       return new IRCode(
           appView.options(),
@@ -262,7 +372,7 @@
           blockList,
           valueNumberGenerator,
           basicBlockNumberGenerator,
-          code.getMetadata(),
+          code.getMetadataForIR(),
           method.getOrigin(),
           new MutableMethodConversionOptions(appView.options()));
     }
@@ -365,7 +475,7 @@
               index, typeElement, code::getDebugLocalInfo);
       Argument argument = new Argument(dest, index, type.isBooleanType());
       assert currentBlock != null;
-      assert currentPosition.isSyntheticPosition();
+      assert currentPosition.isSyntheticPosition() || buildForInlining;
       argument.setPosition(currentPosition);
       currentBlock.getInstructions().add(argument);
       argument.setBlock(currentBlock);
@@ -512,7 +622,7 @@
     public void onConstClass(DexType type, boolean ignoreCompatRules) {
       Value dest =
           getOutValueForNextInstruction(
-              type.toTypeElement(appView, Nullability.definitelyNotNull()));
+              TypeElement.classClassType(appView, Nullability.definitelyNotNull()));
       addInstruction(new ConstClass(dest, type, ignoreCompatRules));
     }
 
@@ -727,8 +837,12 @@
 
     @Override
     public void onReturn(EV value) {
-      addInstruction(new Return(getValue(value)));
-      closeCurrentBlock();
+      if (protoChanges.hasBeenChangedToReturnVoid()) {
+        onReturnVoid();
+      } else {
+        addInstruction(new Return(getValue(value)));
+        closeCurrentBlock();
+      }
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
index f6ca030..8c8e4c5 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -37,6 +37,7 @@
 import com.android.tools.r8.lightir.LirCode.PositionEntry;
 import com.android.tools.r8.lightir.LirCode.TryCatchTable;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -60,6 +61,7 @@
   private static final long DOUBLE_0 = Double.doubleToRawLongBits(0);
   private static final long DOUBLE_1 = Double.doubleToRawLongBits(1);
 
+  private final boolean useDexEstimationStrategy;
   private final DexItemFactory factory;
   private final ByteArrayWriter byteWriter = new ByteArrayWriter();
   private final LirWriter writer = new LirWriter(byteWriter);
@@ -140,8 +142,10 @@
     }
   }
 
-  public LirBuilder(DexMethod method, LirEncodingStrategy<V, EV> strategy, DexItemFactory factory) {
-    this.factory = factory;
+  public LirBuilder(
+      DexMethod method, LirEncodingStrategy<V, EV> strategy, InternalOptions options) {
+    useDexEstimationStrategy = options.isGeneratingDex();
+    factory = options.dexItemFactory();
     constants = new Reference2IntOpenHashMap<>();
     positionTable = new ArrayList<>();
     this.strategy = strategy;
@@ -174,7 +178,9 @@
 
   public LirBuilder<V, EV> setCurrentPosition(Position position) {
     assert position != null;
-    currentPosition = position;
+    if (!position.isNone()) {
+      currentPosition = position;
+    }
     return this;
   }
 
@@ -721,7 +727,8 @@
         instructionCount,
         tryCatchTable,
         debugTable,
-        strategy.getStrategyInfo());
+        strategy.getStrategyInfo(),
+        useDexEstimationStrategy);
   }
 
   private int getCmpOpcode(NumericType type, Cmp.Bias bias) {
diff --git a/src/main/java/com/android/tools/r8/lightir/LirCode.java b/src/main/java/com/android/tools/r8/lightir/LirCode.java
index f1c04ec..75a1aa3 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirCode.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirCode.java
@@ -3,20 +3,43 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.lightir;
 
+import com.android.tools.r8.dex.code.CfOrDexInstruction;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ArgumentUse;
+import com.android.tools.r8.graph.ClasspathMethod;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
 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.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.google.common.collect.ImmutableMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.Map;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
-public class LirCode<EV> implements Iterable<LirInstructionView> {
+public class LirCode<EV> extends Code implements Iterable<LirInstructionView> {
 
   public static class PositionEntry {
     final int fromInstructionIndex;
@@ -72,7 +95,9 @@
 
   private final LirStrategyInfo<EV> strategyInfo;
 
-  private final IRMetadata metadata;
+  private final boolean useDexEstimationStrategy;
+
+  private final IRMetadata irMetadata;
 
   /** Constant pool of items. */
   private final DexItem[] constants;
@@ -95,13 +120,13 @@
   private final DebugLocalInfoTable<EV> debugLocalInfoTable;
 
   public static <V, EV> LirBuilder<V, EV> builder(
-      DexMethod method, LirEncodingStrategy<V, EV> strategy, DexItemFactory factory) {
-    return new LirBuilder<>(method, strategy, factory);
+      DexMethod method, LirEncodingStrategy<V, EV> strategy, InternalOptions options) {
+    return new LirBuilder<>(method, strategy, options);
   }
 
   /** Should be constructed using {@link LirBuilder}. */
   LirCode(
-      IRMetadata metadata,
+      IRMetadata irMetadata,
       DexItem[] constants,
       PositionEntry[] positions,
       int argumentCount,
@@ -109,8 +134,9 @@
       int instructionCount,
       TryCatchTable tryCatchTable,
       DebugLocalInfoTable<EV> debugLocalInfoTable,
-      LirStrategyInfo<EV> strategyInfo) {
-    this.metadata = metadata;
+      LirStrategyInfo<EV> strategyInfo,
+      boolean useDexEstimationStrategy) {
+    this.irMetadata = irMetadata;
     this.constants = constants;
     this.positionTable = positions;
     this.argumentCount = argumentCount;
@@ -119,6 +145,24 @@
     this.tryCatchTable = tryCatchTable;
     this.debugLocalInfoTable = debugLocalInfoTable;
     this.strategyInfo = strategyInfo;
+    this.useDexEstimationStrategy = useDexEstimationStrategy;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public LirCode<Integer> asLirCode() {
+    // TODO(b/225838009): Unchecked cast will be removed once the encoding strategy is definitive.
+    return (LirCode<Integer>) this;
+  }
+
+  @Override
+  protected int computeHashCode() {
+    throw new Unreachable("LIR code should not be subject to hashing.");
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    throw new Unreachable("LIR code should not be subject to equality checks.");
   }
 
   public EV decodeValueIndex(int encodedValueIndex, int currentValueIndex) {
@@ -143,8 +187,8 @@
     return instructionCount;
   }
 
-  public IRMetadata getMetadata() {
-    return metadata;
+  public IRMetadata getMetadataForIR() {
+    return irMetadata;
   }
 
   public DexItem getConstantItem(int index) {
@@ -172,12 +216,184 @@
   }
 
   @Override
+  public BytecodeMetadata<? extends CfOrDexInstruction> getMetadata() {
+    // Bytecode metadata is recomputed when finalizing via IR.
+    throw new Unreachable();
+  }
+
+  @Override
+  public BytecodeInstructionMetadata getMetadata(CfOrDexInstruction instruction) {
+    // Bytecode metadata is recomputed when finalizing via IR.
+    throw new Unreachable();
+  }
+
+  @Override
   public LirIterator iterator() {
     return new LirIterator(new ByteArrayIterator(instructions));
   }
 
   @Override
+  public IRCode buildIR(
+      ProgramMethod method,
+      AppView<?> appView,
+      Origin origin,
+      MutableMethodConversionOptions conversionOptions) {
+    LirCode<Integer> typedLir = asLirCode();
+    return Lir2IRConverter.translate(
+        method,
+        typedLir,
+        LirStrategy.getDefaultStrategy().getDecodingStrategy(typedLir, null),
+        appView);
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      ProgramMethod context,
+      ProgramMethod method,
+      AppView<?> appView,
+      GraphLens codeLens,
+      NumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      Origin origin,
+      RewrittenPrototypeDescription protoChanges) {
+    assert valueNumberGenerator != null;
+    assert callerPosition != null;
+    assert protoChanges != null;
+    LirCode<Integer> typedLir = asLirCode();
+    IRCode irCode =
+        Lir2IRConverter.translate(
+            method,
+            typedLir,
+            LirStrategy.getDefaultStrategy().getDecodingStrategy(typedLir, valueNumberGenerator),
+            appView,
+            valueNumberGenerator,
+            callerPosition,
+            protoChanges,
+            appView.graphLens().getOriginalMethodSignature(method.getReference()));
+    // TODO(b/225838009): Should we keep track of which code objects need to be narrowed?
+    //   In particular, the encoding of phis does not maintain interfaces.
+    new TypeAnalysis(appView).narrowing(irCode);
+    return irCode;
+  }
+
+  @Override
+  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+    assert registry.getTraversalContinuation().shouldContinue();
+    LirUseRegistryCallback<EV> registryCallbacks = new LirUseRegistryCallback<>(this, registry);
+    for (LirInstructionView view : this) {
+      registryCallbacks.onInstructionView(view);
+      if (registry.getTraversalContinuation().shouldBreak()) {
+        return;
+      }
+    }
+    if (tryCatchTable != null) {
+      for (CatchHandlers<Integer> handler : tryCatchTable.tryCatchHandlers.values()) {
+        for (DexType guard : handler.getGuards()) {
+          registry.registerExceptionGuard(guard);
+          if (registry.getTraversalContinuation().shouldBreak()) {
+            return;
+          }
+        }
+      }
+    }
+  }
+
+  @Override
+  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+    throw new Unimplemented();
+  }
+
+  @Override
+  public Int2ReferenceMap<DebugLocalInfo> collectParameterInfo(
+      DexEncodedMethod encodedMethod, AppView<?> appView) {
+    throw new Unimplemented();
+  }
+
+  @Override
+  public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) {
+    throw new Unimplemented();
+  }
+
+  @Override
   public String toString() {
     return new LirPrinter<>(this).prettyPrint();
   }
+
+  @Override
+  public String toString(DexEncodedMethod method, RetracerForCodePrinting retracer) {
+    // TODO(b/225838009): Add retracing to printer.
+    return toString();
+  }
+
+  @Override
+  public int estimatedDexCodeSizeUpperBoundInBytes() {
+    throw new Unimplemented();
+  }
+
+  @Override
+  public int estimatedSizeForInlining() {
+    if (useDexEstimationStrategy) {
+      LirSizeEstimation<EV> estimation = new LirSizeEstimation<>(this);
+      for (LirInstructionView view : this) {
+        estimation.onInstructionView(view);
+      }
+      return estimation.getSizeEstimate();
+    } else {
+      // TODO(b/225838009): Currently the size estimation for CF has size one for each instruction
+      //  (even switches!) and ignores stack instructions, thus loads to arguments are not included.
+      //  The result is a much smaller estimate than for DEX. Once LIR is in place we should use the
+      //  same estimate for both.
+      return instructionCount;
+    }
+  }
+
+  @Override
+  public boolean estimatedSizeForInliningAtMost(int threshold) {
+    if (useDexEstimationStrategy) {
+      LirSizeEstimation<EV> estimation = new LirSizeEstimation<>(this);
+      for (LirInstructionView view : this) {
+        estimation.onInstructionView(view);
+        if (estimation.getSizeEstimate() > threshold) {
+          return false;
+        }
+      }
+      return true;
+    } else {
+      return estimatedSizeForInlining() <= threshold;
+    }
+  }
+
+  @Override
+  public Code getCodeAsInlining(DexMethod caller, DexEncodedMethod callee, DexItemFactory factory) {
+    throw new Unimplemented();
+  }
+
+  @Override
+  public boolean isEmptyVoidMethod() {
+    for (LirInstructionView view : this) {
+      int opcode = view.getOpcode();
+      if (opcode != LirOpcodes.RETURN && opcode != LirOpcodes.DEBUGPOS) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public boolean hasMonitorInstructions() {
+    for (LirInstructionView view : this) {
+      int opcode = view.getOpcode();
+      if (opcode == LirOpcodes.MONITORENTER || opcode == LirOpcodes.MONITOREXIT) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public void forEachPosition(Consumer<Position> positionConsumer) {
+    for (PositionEntry entry : positionTable) {
+      positionConsumer.accept(entry.position);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java b/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java
index 0b0a51d..f0eaeef 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirDecodingStrategy.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Phi;
 import java.util.function.Function;
 import java.util.function.IntFunction;
@@ -13,6 +14,16 @@
 /** Abstraction for how to decode SSA values (and basic blocks) when reading LIR. */
 public abstract class LirDecodingStrategy<V, EV> {
 
+  private final NumberGenerator valueNumberGenerator;
+
+  public LirDecodingStrategy(NumberGenerator valueNumberGenerator) {
+    this.valueNumberGenerator = valueNumberGenerator;
+  }
+
+  public final int getValueNumber(int encodedValueIndex) {
+    return valueNumberGenerator == null ? encodedValueIndex : valueNumberGenerator.next();
+  }
+
   public abstract V getValue(EV encodedValue, LirStrategyInfo<EV> strategyInfo);
 
   public abstract V getValueDefinitionForInstructionIndex(
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
index 6803d0b..fc1ccc6 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
@@ -151,7 +151,7 @@
   // int JSR = 168;
   // int RET = 169;
   int TABLESWITCH = 170;
-  int LOOKUPSWITCH = 171;
+  // int LOOKUPSWITCH = 171;
   // int IRETURN = 172;
   // int LRETURN = 173;
   // int FRETURN = 174;
@@ -447,8 +447,7 @@
         // case RET: return "RET";
       case TABLESWITCH:
         return "TABLESWITCH";
-      case LOOKUPSWITCH:
-        return "LOOKUPSWITCH";
+        // case LOOKUPSWITCH:
       case ARETURN:
         return "ARETURN";
       case RETURN:
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
index 444df4e..d979ada 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -448,13 +448,21 @@
     onFieldInstruction(field);
   }
 
-  public abstract void onInstanceGet(DexField field, EV object);
+  public void onInstanceGet(DexField field, EV object) {
+    onFieldInstruction(field);
+  }
 
-  public abstract void onInstancePut(DexField field, EV object, EV value);
+  public void onInstancePut(DexField field, EV object, EV value) {
+    onFieldInstruction(field);
+  }
 
-  public abstract void onNewArrayEmpty(DexType type, EV size);
+  public void onNewArrayEmpty(DexType type, EV size) {
+    onInstruction();
+  }
 
-  public abstract void onThrow(EV exception);
+  public void onThrow(EV exception) {
+    onInstruction();
+  }
 
   public void onReturnVoid() {
     onInstruction();
@@ -493,9 +501,17 @@
     onInstruction();
   }
 
-  public abstract void onMonitorEnter(EV value);
+  public void onMonitorInstruction(EV value) {
+    onInstruction();
+  }
 
-  public abstract void onMonitorExit(EV value);
+  public void onMonitorEnter(EV value) {
+    onMonitorInstruction(value);
+  }
+
+  public void onMonitorExit(EV value) {
+    onMonitorInstruction(value);
+  }
 
   public void onNewUnboxedEnumInstance(DexType type, int ordinal) {
     onInstruction();
diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
index 96d9cef..cfe1a08 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.IfType;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumericType;
@@ -61,7 +62,7 @@
   }
 
   private String fmtValueIndex(EV valueIndex) {
-    return valueIndex.toString();
+    return "v" + valueIndex.toString();
   }
 
   private String fmtInsnIndex(int instructionIndex) {
@@ -86,6 +87,22 @@
       advanceToNextValueIndex();
     }
     code.forEach(this::onInstructionView);
+    if (code.getTryCatchTable() != null) {
+      builder.append("try-catch-handlers:\n");
+      code.getTryCatchTable()
+          .tryCatchHandlers
+          .forEach(
+              (index, handlers) -> {
+                builder.append(index).append(":\n");
+                for (CatchHandler<Integer> handler : handlers) {
+                  builder
+                      .append(handler.getGuard())
+                      .append(" -> ")
+                      .append(handler.getTarget())
+                      .append('\n');
+                }
+              });
+    }
     return builder.toString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java b/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java
new file mode 100644
index 0000000..eaa93d0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LirSizeEstimation.java
@@ -0,0 +1,339 @@
+// 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.
+
+package com.android.tools.r8.lightir;
+
+import com.android.tools.r8.dex.code.DexAget;
+import com.android.tools.r8.dex.code.DexAput;
+import com.android.tools.r8.dex.code.DexArrayLength;
+import com.android.tools.r8.dex.code.DexBase1Format;
+import com.android.tools.r8.dex.code.DexBase2Format;
+import com.android.tools.r8.dex.code.DexBase3Format;
+import com.android.tools.r8.dex.code.DexCheckCast;
+import com.android.tools.r8.dex.code.DexConst16;
+import com.android.tools.r8.dex.code.DexConst4;
+import com.android.tools.r8.dex.code.DexConstClass;
+import com.android.tools.r8.dex.code.DexConstString;
+import com.android.tools.r8.dex.code.DexConstWide16;
+import com.android.tools.r8.dex.code.DexFillArrayData;
+import com.android.tools.r8.dex.code.DexFillArrayDataPayload;
+import com.android.tools.r8.dex.code.DexFilledNewArray;
+import com.android.tools.r8.dex.code.DexGoto;
+import com.android.tools.r8.dex.code.DexInstanceOf;
+import com.android.tools.r8.dex.code.DexInvokeCustom;
+import com.android.tools.r8.dex.code.DexMonitorEnter;
+import com.android.tools.r8.dex.code.DexMonitorExit;
+import com.android.tools.r8.dex.code.DexMove;
+import com.android.tools.r8.dex.code.DexMoveException;
+import com.android.tools.r8.dex.code.DexNewArray;
+import com.android.tools.r8.dex.code.DexNewInstance;
+import com.android.tools.r8.dex.code.DexNotInt;
+import com.android.tools.r8.dex.code.DexNotLong;
+import com.android.tools.r8.dex.code.DexPackedSwitch;
+import com.android.tools.r8.dex.code.DexPackedSwitchPayload;
+import com.android.tools.r8.dex.code.DexSget;
+import com.android.tools.r8.dex.code.DexThrow;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
+
+public class LirSizeEstimation<EV> extends LirParsedInstructionCallback<EV> implements LirOpcodes {
+
+  private int sizeEstimate = 0;
+
+  LirSizeEstimation(LirCode<EV> code) {
+    super(code);
+  }
+
+  @Override
+  public int getCurrentValueIndex() {
+    // We don't use value information.
+    return 0;
+  }
+
+  public int getSizeEstimate() {
+    return sizeEstimate;
+  }
+
+  /**
+   * Most size information can be found just using opcode.
+   *
+   * <p>We overwrite the base view callback and only in the few payload instruction cases do we make
+   * use of the parsed-instruction callbacks.
+   */
+  @Override
+  public void onInstructionView(LirInstructionView view) {
+    sizeEstimate += instructionSize(view.getOpcode(), view);
+  }
+
+  @Override
+  public void onIntSwitch(EV unusedValue, IntSwitchPayload payload) {
+    sizeEstimate +=
+        DexPackedSwitch.SIZE
+            + DexPackedSwitchPayload.SIZE
+            + (2 * payload.keys.length)
+            + (2 * payload.targets.length);
+  }
+
+  @Override
+  public void onNewArrayFilledData(int elementWidth, long size, short[] data, EV unusedSrc) {
+    sizeEstimate += DexFillArrayData.SIZE + DexFillArrayDataPayload.SIZE + 4 + data.length;
+  }
+
+  private int instructionSize(int opcode, LirInstructionView view) {
+    switch (opcode) {
+      case TABLESWITCH:
+      case NEWARRAYFILLEDDATA:
+        // The payload instructions use the "parsed callback" to compute the payloads.
+        super.onInstructionView(view);
+        // The full size is added by the callbacks so return zero here.
+        return 0;
+
+      case ACONST_NULL:
+      case ICONST_M1:
+      case ICONST_0:
+      case ICONST_1:
+      case ICONST_2:
+      case ICONST_3:
+      case ICONST_4:
+      case ICONST_5:
+        return DexConst4.SIZE;
+
+      case LCONST_0:
+      case LCONST_1:
+      case FCONST_0:
+      case FCONST_1:
+      case FCONST_2:
+      case DCONST_0:
+      case DCONST_1:
+        return DexConstWide16.SIZE;
+
+      case LDC:
+        // Most of the const loads are the same size (2).
+        return DexConstString.SIZE;
+
+      case IALOAD:
+      case LALOAD:
+      case FALOAD:
+      case DALOAD:
+      case AALOAD:
+      case BALOAD:
+      case CALOAD:
+      case SALOAD:
+        // The loads are all size 2.
+        return DexAget.SIZE;
+
+      case IASTORE:
+      case LASTORE:
+      case FASTORE:
+      case DASTORE:
+      case AASTORE:
+      case BASTORE:
+      case CASTORE:
+      case SASTORE:
+        // The loads are all size 2.
+        return DexAput.SIZE;
+
+      case IADD:
+      case LADD:
+      case FADD:
+      case DADD:
+      case ISUB:
+      case LSUB:
+      case FSUB:
+      case DSUB:
+      case IMUL:
+      case LMUL:
+      case FMUL:
+      case DMUL:
+      case IDIV:
+      case LDIV:
+      case FDIV:
+      case DDIV:
+      case IREM:
+      case LREM:
+      case FREM:
+      case DREM:
+        // The binary ops are all size 2.
+        return DexBase2Format.SIZE;
+
+      case INEG:
+      case LNEG:
+      case FNEG:
+      case DNEG:
+        // The negs are all size 1.
+        return DexBase1Format.SIZE;
+
+      case ISHL:
+      case LSHL:
+      case ISHR:
+      case LSHR:
+      case IUSHR:
+      case LUSHR:
+      case IAND:
+      case LAND:
+      case IOR:
+      case LOR:
+      case IXOR:
+      case LXOR:
+        // The binary ops are all size 2.
+        return DexBase2Format.SIZE;
+
+      case I2L:
+      case I2F:
+      case I2D:
+      case L2I:
+      case L2F:
+      case L2D:
+      case F2I:
+      case F2L:
+      case F2D:
+      case D2I:
+      case D2L:
+      case D2F:
+      case I2B:
+      case I2C:
+      case I2S:
+        // Number conversions are all size 1.
+        return DexBase1Format.SIZE;
+
+      case LCMP:
+      case FCMPL:
+      case FCMPG:
+      case DCMPL:
+      case DCMPG:
+        return DexBase2Format.SIZE;
+
+      case IFEQ:
+      case IFNE:
+      case IFLT:
+      case IFGE:
+      case IFGT:
+      case IFLE:
+      case IF_ICMPEQ:
+      case IF_ICMPNE:
+      case IF_ICMPLT:
+      case IF_ICMPGE:
+      case IF_ICMPGT:
+      case IF_ICMPLE:
+      case IF_ACMPEQ:
+      case IF_ACMPNE:
+        return DexBase2Format.SIZE;
+
+      case GOTO:
+        return DexGoto.SIZE;
+
+      case ARETURN:
+      case RETURN:
+        return DexBase1Format.SIZE;
+
+      case GETSTATIC:
+      case PUTSTATIC:
+      case GETFIELD:
+      case PUTFIELD:
+        return DexBase2Format.SIZE;
+
+      case INVOKEVIRTUAL:
+      case INVOKESPECIAL:
+      case INVOKESTATIC:
+      case INVOKEINTERFACE:
+        return DexBase3Format.SIZE;
+
+      case INVOKEDYNAMIC:
+        return DexInvokeCustom.SIZE;
+
+      case NEW:
+        return DexNewInstance.SIZE;
+      case NEWARRAY:
+        return DexNewArray.SIZE;
+      case ARRAYLENGTH:
+        return DexArrayLength.SIZE;
+      case ATHROW:
+        return DexThrow.SIZE;
+      case CHECKCAST:
+        return DexCheckCast.SIZE;
+      case INSTANCEOF:
+        return DexInstanceOf.SIZE;
+      case MONITORENTER:
+        return DexMonitorEnter.SIZE;
+      case MONITOREXIT:
+        return DexMonitorExit.SIZE;
+      case MULTIANEWARRAY:
+        return DexFilledNewArray.SIZE;
+
+      case IFNULL:
+      case IFNONNULL:
+        return DexBase1Format.SIZE;
+
+        // Non-CF instructions.
+      case ICONST:
+      case LCONST:
+      case FCONST:
+      case DCONST:
+        return DexConst16.SIZE;
+
+      case INVOKESTATIC_ITF:
+      case INVOKEDIRECT:
+      case INVOKEDIRECT_ITF:
+      case INVOKESUPER:
+      case INVOKESUPER_ITF:
+        return DexBase3Format.SIZE;
+
+      case DEBUGPOS:
+        // Often debug positions will be associated with instructions so assume size 0.
+        return 0;
+
+      case PHI:
+        // Assume a move per phi.
+        return DexMove.SIZE;
+
+      case FALLTHROUGH:
+        // Hopefully fallthrough points will not materialize as instructions.
+        return 0;
+
+      case MOVEEXCEPTION:
+        return DexMoveException.SIZE;
+
+      case DEBUGLOCALWRITE:
+        return DexMove.SIZE;
+
+      case INVOKENEWARRAY:
+        return DexFilledNewArray.SIZE;
+
+      case ITEMBASEDCONSTSTRING:
+        return DexConstString.SIZE;
+
+      case NEWUNBOXEDENUMINSTANCE:
+        return DexConst16.SIZE;
+
+      case INOT:
+        return DexNotInt.SIZE;
+      case LNOT:
+        return DexNotLong.SIZE;
+
+      case DEBUGLOCALREAD:
+        // These reads do not materialize after register allocation.
+        return 0;
+
+      case INITCLASS:
+        return DexSget.SIZE;
+
+      case INVOKEPOLYMORPHIC:
+        return DexBase3Format.SIZE;
+
+      case RECORDFIELDVALUES:
+        // Rewritten to new-array in DEX.
+        return DexNewArray.SIZE;
+
+      case CHECKCAST_SAFE:
+      case CHECKCAST_IGNORE_COMPAT:
+        return DexCheckCast.SIZE;
+
+      case CONSTCLASS_IGNORE_COMPAT:
+        return DexConstClass.SIZE;
+
+      default:
+        throw new Unreachable("Unexpected LIR opcode: " + opcode);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/LirStrategy.java b/src/main/java/com/android/tools/r8/lightir/LirStrategy.java
index 108586f..07ac579 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirStrategy.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirStrategy.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Value;
@@ -29,9 +30,15 @@
  *     the possible encoding for phi and non-phi values.
  */
 public abstract class LirStrategy<V, EV> {
+
+  public static LirStrategy<Value, Integer> getDefaultStrategy() {
+    return new PhiInInstructionsStrategy();
+  }
+
   public abstract LirEncodingStrategy<V, EV> getEncodingStrategy();
 
-  public abstract LirDecodingStrategy<V, EV> getDecodingStrategy(LirCode<EV> code);
+  public abstract LirDecodingStrategy<V, EV> getDecodingStrategy(
+      LirCode<EV> code, NumberGenerator valueNumberGenerator);
 
   /**
    * Encoding of a value with a phi-bit.
@@ -139,8 +146,9 @@
     }
 
     @Override
-    public LirDecodingStrategy<Value, PhiOrValue> getDecodingStrategy(LirCode<PhiOrValue> code) {
-      return new DecodingStrategy(code);
+    public LirDecodingStrategy<Value, PhiOrValue> getDecodingStrategy(
+        LirCode<PhiOrValue> code, NumberGenerator valueNumberGenerator) {
+      return new DecodingStrategy(code, valueNumberGenerator);
     }
 
     private static class StrategyInfo extends LirStrategyInfo<PhiOrValue> {
@@ -234,7 +242,8 @@
       private final Value[] values;
       private final int firstPhiValueIndex;
 
-      DecodingStrategy(LirCode<PhiOrValue> code) {
+      DecodingStrategy(LirCode<PhiOrValue> code, NumberGenerator valueNumberGenerator) {
+        super(valueNumberGenerator);
         values = new Value[code.getArgumentCount() + code.getInstructionCount()];
         int phiValueIndex = -1;
         for (LirInstructionView view : code) {
@@ -270,7 +279,7 @@
         int index = decode(encodedValue, strategyInfo);
         Value value = values[index];
         if (value == null) {
-          value = new Value(index, TypeElement.getBottom(), null);
+          value = new Value(getValueNumber(index), TypeElement.getBottom(), null);
           values[index] = value;
         }
         return value;
@@ -284,7 +293,7 @@
         DebugLocalInfo localInfo = getLocalInfo.apply(encodedValue);
         Value value = values[index];
         if (value == null) {
-          value = new Value(index, type, localInfo);
+          value = new Value(getValueNumber(index), type, localInfo);
           values[index] = value;
         } else {
           value.setType(type);
@@ -306,7 +315,8 @@
         PhiOrValue encodedValue = getEncodedPhiForAbsoluteValueIndex(valueIndex, strategyInfo);
         BasicBlock block = getBlock.apply(encodedValue.getBlockIndex());
         DebugLocalInfo localInfo = getLocalInfo.apply(encodedValue);
-        Phi phi = new Phi(valueIndex, block, type, localInfo, RegisterReadType.NORMAL);
+        Phi phi =
+            new Phi(getValueNumber(valueIndex), block, type, localInfo, RegisterReadType.NORMAL);
         Value value = values[valueIndex];
         if (value != null) {
           // A fake ssa value has already been created, replace the users by the actual phi.
diff --git a/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
new file mode 100644
index 0000000..67f7a4e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/lightir/LirUseRegistryCallback.java
@@ -0,0 +1,153 @@
+// 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.
+
+package com.android.tools.r8.lightir;
+
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
+import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
+import java.util.List;
+
+public class LirUseRegistryCallback<EV> extends LirParsedInstructionCallback<EV> {
+
+  private final UseRegistry registry;
+
+  public LirUseRegistryCallback(LirCode<EV> code, UseRegistry registry) {
+    super(code);
+    this.registry = registry;
+  }
+
+  @Override
+  public int getCurrentValueIndex() {
+    // The registry of instructions does not require knowledge of value indexes.
+    return 0;
+  }
+
+  @Override
+  public void onCheckCast(DexType type, EV value, boolean ignoreCompatRules) {
+    registry.registerCheckCast(type, ignoreCompatRules);
+  }
+
+  @Override
+  public void onSafeCheckCast(DexType type, EV value) {
+    registry.registerSafeCheckCast(type);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public void onConstClass(DexType type, boolean ignoreCompatRules) {
+    registry.registerConstClass(type, null, ignoreCompatRules);
+  }
+
+  @Override
+  public void onConstMethodHandle(DexMethodHandle methodHandle) {
+    registry.registerMethodHandle(methodHandle, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+  }
+
+  @Override
+  public void onConstMethodType(DexProto methodType) {
+    registry.registerProto(methodType);
+  }
+
+  @Override
+  public void onDexItemBasedConstString(
+      DexReference item, NameComputationInfo<?> nameComputationInfo) {
+    if (nameComputationInfo.needsToRegisterReference()) {
+      assert item.isDexType();
+      registry.registerTypeReference(item.asDexType());
+    }
+  }
+
+  @Override
+  public void onInitClass(DexType clazz) {
+    registry.registerInitClass(clazz);
+  }
+
+  @Override
+  public void onInstanceGet(DexField field, EV object) {
+    registry.registerInstanceFieldRead(field);
+  }
+
+  @Override
+  public void onInstancePut(DexField field, EV object, EV value) {
+    registry.registerInstanceFieldWrite(field);
+  }
+
+  @Override
+  public void onStaticGet(DexField field) {
+    registry.registerStaticFieldRead(field);
+  }
+
+  @Override
+  public void onStaticPut(DexField field, EV value) {
+    registry.registerStaticFieldWrite(field);
+  }
+
+  @Override
+  public void onInstanceOf(DexType type, EV value) {
+    registry.registerInstanceOf(type);
+  }
+
+  @Override
+  public void onInvokeCustom(DexCallSite callSite, List<EV> arguments) {
+    registry.registerCallSite(callSite);
+  }
+
+  @Override
+  public void onInvokeDirect(DexMethod method, List<EV> arguments, boolean isInterface) {
+    registry.registerInvokeDirect(method);
+  }
+
+  @Override
+  public void onInvokeInterface(DexMethod method, List<EV> arguments) {
+    registry.registerInvokeInterface(method);
+  }
+
+  @Override
+  public void onInvokeStatic(DexMethod method, List<EV> arguments, boolean isInterface) {
+    registry.registerInvokeStatic(method);
+  }
+
+  @Override
+  public void onInvokeSuper(DexMethod method, List<EV> arguments, boolean isInterface) {
+    registry.registerInvokeSuper(method);
+  }
+
+  @Override
+  public void onInvokeVirtual(DexMethod method, List<EV> arguments) {
+    registry.registerInvokeVirtual(method);
+  }
+
+  @Override
+  public void onInvokeMultiNewArray(DexType type, List<EV> arguments) {
+    registry.registerTypeReference(type);
+  }
+
+  @Override
+  public void onInvokeNewArray(DexType type, List<EV> arguments) {
+    registry.registerTypeReference(type);
+  }
+
+  @Override
+  public void onNewArrayEmpty(DexType type, EV size) {
+    registry.registerTypeReference(type);
+  }
+
+  @Override
+  public void onNewInstance(DexType clazz) {
+    registry.registerNewInstance(clazz);
+  }
+
+  @Override
+  public void onNewUnboxedEnumInstance(DexType type, int ordinal) {
+    registry.registerNewUnboxedEnumInstance(type);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java b/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java
index 0428126..6e9366f 100644
--- a/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java
+++ b/src/main/java/com/android/tools/r8/lightir/PhiInInstructionsStrategy.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Value;
@@ -23,8 +24,9 @@
   }
 
   @Override
-  public LirDecodingStrategy<Value, Integer> getDecodingStrategy(LirCode<Integer> code) {
-    return new DecodingStrategy(code);
+  public LirDecodingStrategy<Value, Integer> getDecodingStrategy(
+      LirCode<Integer> code, NumberGenerator valueNumberGenerator) {
+    return new DecodingStrategy(code, valueNumberGenerator);
   }
 
   private static class EncodingStrategy extends LirEncodingStrategy<Value, Integer> {
@@ -94,7 +96,8 @@
 
     private final Value[] values;
 
-    DecodingStrategy(LirCode<Integer> code) {
+    DecodingStrategy(LirCode<Integer> code, NumberGenerator valueNumberGenerator) {
+      super(valueNumberGenerator);
       values = new Value[code.getArgumentCount() + code.getInstructionCount()];
     }
 
@@ -103,7 +106,7 @@
       int index = encodedValue;
       Value value = values[index];
       if (value == null) {
-        value = new Value(index, TypeElement.getBottom(), null);
+        value = new Value(getValueNumber(index), TypeElement.getBottom(), null);
         values[index] = value;
       }
       return value;
@@ -115,7 +118,7 @@
       DebugLocalInfo localInfo = getLocalInfo.apply(index);
       Value value = values[index];
       if (value == null) {
-        value = new Value(index, type, localInfo);
+        value = new Value(getValueNumber(index), type, localInfo);
         values[index] = value;
       } else {
         value.setType(type);
@@ -138,7 +141,8 @@
         LirStrategyInfo<Integer> strategyInfo) {
       BasicBlock block = getBlock.apply(valueIndex);
       DebugLocalInfo localInfo = getLocalInfo.apply(valueIndex);
-      Phi phi = new Phi(valueIndex, block, type, localInfo, RegisterReadType.NORMAL);
+      Phi phi =
+          new Phi(getValueNumber(valueIndex), block, type, localInfo, RegisterReadType.NORMAL);
       Value value = values[valueIndex];
       if (value != null) {
         // A fake ssa value has already been created, replace the users by the actual phi.
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 6259f3a..9afb2e9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2077,10 +2077,24 @@
 
   public static class TestingOptions {
 
+    public boolean roundtripThroughLir = false;
+    private boolean useLir = false;
+
+    public void enableLir() {
+      useLir = true;
+    }
+
+    public void disableLir() {
+      useLir = false;
+    }
+
+    public boolean canUseLir(AppView<?> appView) {
+      return useLir && appView.enableWholeProgramOptimizations();
+    }
+
     // If false, use the desugared library implementation when desugared library is enabled.
     public boolean alwaysBackportListSetMapMethods = true;
     public boolean neverReuseCfLocalRegisters = false;
-    public boolean roundtripThroughLir = false;
     public boolean checkReceiverAlwaysNullInCallSiteOptimization = true;
     public boolean forceInlineAPIConversions = false;
     public boolean ignoreValueNumbering = false;
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 22f879d..fa1d579 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -132,10 +132,11 @@
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
+        .withOptionConsumer(o -> o.testing.enableLir())
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 1, "lambdadesugaring"))
         .run();
   }
 
@@ -172,9 +173,10 @@
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(AndroidApiLevel.N)
+        .withOptionConsumer(opts -> opts.testing.enableLir())
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 1, "lambdadesugaring"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java b/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java
index f0bc1e5..173efa1 100644
--- a/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java
+++ b/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java
@@ -68,7 +68,7 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    assertEquals(parameters.isCfRuntime() ? 1 : 2, inspector.allClasses().size());
+    assertEquals(1, inspector.allClasses().size());
   }
 
   @Test
@@ -77,6 +77,7 @@
         .addProgramFiles(getProgramFiles(target))
         .setMinApi(parameters)
         .addKeepMainRule(MAIN_CLASS)
+        .addOptionsModification(o -> o.testing.enableLir())
         .run(parameters.getRuntime(), MAIN_CLASS)
         .inspect(this::inspect)
         .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
index 84f5003..ec1f304 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -172,14 +172,14 @@
       assertNotNull(argument);
       Value argumentValue = argument.outValue();
 
-      BasicBlock block1 = irCode.blocks.get(1);
+      BasicBlock block1 = irCode.blocks.stream().filter(b -> b.getNumber() == 1).findFirst().get();
       assertTrue(block1.exit().isIf());
 
-      BasicBlock block3 = irCode.blocks.get(3);
+      BasicBlock block3 = irCode.blocks.stream().filter(b -> b.getNumber() == 3).findFirst().get();
       assertTrue(block1.getSuccessors().contains(block3));
       assertTrue(block3.exit().isGoto());
 
-      BasicBlock block4 = irCode.blocks.get(4);
+      BasicBlock block4 = irCode.blocks.stream().filter(b -> b.getNumber() == 4).findFirst().get();
       assertSame(block3.getUniqueNormalSuccessor(), block4);
 
       Phi firstPhi =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 5ece012..483ed33 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -12,7 +12,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestParameters;
@@ -42,7 +41,6 @@
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.Sets;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.junit.Assume;
@@ -312,6 +310,8 @@
             .allowAccessModification()
             .enableInliningAnnotations()
             .addDontObfuscate()
+            // Using LIR changes the inlining heuristics so enable it consistently.
+            .addOptionsModification(o -> o.testing.enableLir())
             .setMinApi(parameters)
             .run(parameters.getRuntime(), main)
             .assertSuccessWithOutput(javaOutput);
@@ -324,16 +324,14 @@
             .filter(FoundClassSubject::isSynthesizedJavaLambdaClass)
             .map(FoundClassSubject::getFinalName)
             .collect(Collectors.toList());
+    assertEquals(Collections.emptyList(), synthesizedJavaLambdaClasses);
 
-    // TODO(b/120814598): Should only be "java.lang.StringBuilder".
     assertEquals(
-        new HashSet<>(synthesizedJavaLambdaClasses),
+        Collections.singleton("java.lang.StringBuilder"),
         collectTypes(clazz.uniqueMethodWithOriginalName("testStatelessLambda")));
-    assertTrue(
-        inspector.allClasses().stream().anyMatch(ClassSubject::isSynthesizedJavaLambdaClass));
 
     assertEquals(
-        Sets.newHashSet("java.lang.StringBuilder"),
+        Collections.singleton("java.lang.StringBuilder"),
         collectTypes(clazz.uniqueMethodWithOriginalName("testStatefulLambda")));
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
index 8f8541c..37e6c57 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/FieldReadForWriteTest.java
@@ -39,7 +39,7 @@
             HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters)
-        .addOptionsModification(o -> o.testing.roundtripThroughLir = true)
+        .addOptionsModification(o -> o.testing.enableLir())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("42")
         .inspect(inspector -> assertThat(inspector.clazz(anim.class), isAbsent()));
diff --git a/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java b/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java
index abd980d..909c083 100644
--- a/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java
+++ b/src/test/java/com/android/tools/r8/lightir/LirBasicCallbackTest.java
@@ -12,12 +12,12 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRMetadata;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.InternalOptions;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -74,10 +74,13 @@
 
   @Test
   public void test() {
-    DexItemFactory factory = new DexItemFactory();
-    DexMethod method = factory.createMethod(Reference.methodFromDescriptor("LFoo;", "bar", "()V"));
+    InternalOptions options = new InternalOptions();
+    DexMethod method =
+        options
+            .dexItemFactory()
+            .createMethod(Reference.methodFromDescriptor("LFoo;", "bar", "()V"));
     LirCode<?> code =
-        LirCode.builder(method, new ThrowingStrategy(), factory)
+        LirCode.builder(method, new ThrowingStrategy(), options)
             .setMetadata(IRMetadata.unknown())
             .addConstNull()
             .addConstInt(42)
diff --git a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
index c4fa84b..a39c770 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.regress;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
@@ -51,6 +50,7 @@
         .addKeepMainRule(TestClass.class)
         .addInnerClasses(Regress160394262Test.class)
         .setMinApi(parameters)
+        .addOptionsModification(o -> o.testing.enableLir())
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED)
         .inspect(this::checkJoinerIsClassInlined);
@@ -58,14 +58,7 @@
 
   private void checkJoinerIsClassInlined(CodeInspector inspector) {
     assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), isAbsent());
-    // TODO(b/160640028): Joiner should be class inlined.
-    //   When line info tables are kept we appear to successfully inline Joiner. Reason unknown.
-    if (parameters.isCfRuntime()
-        || parameters.getApiLevel().isLessThan(apiLevelWithPcAsLineNumberSupport())) {
-      assertThat(inspector.clazz(Joiner.class), isPresent());
-    } else {
-      assertThat(inspector.clazz(Joiner.class), isAbsent());
-    }
+    assertThat(inspector.clazz(Joiner.class), isAbsent());
   }
 
   static class TestClass {