Avoid IR processing of throw null methods

Change-Id: Ia60eb6f42a5ea642f0bb6aa85214606004a65e6f
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index dcc55b1..0263c5f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -979,9 +979,10 @@
                         }
                         if (code.isCfCode()) {
                           assert verifyOriginalMethodInPosition(code.asCfCode(), originalMethod);
-                        } else {
-                          assert code.isDexCode();
+                        } else if (code.isDexCode()) {
                           assert verifyOriginalMethodInDebugInfo(code.asDexCode(), originalMethod);
+                        } else {
+                          assert code.isThrowNullCode();
                         }
                       }
                     }));
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 1a594e9..8671b68 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -133,6 +133,10 @@
     write32BitValue(item.getOffset(mapping), dest);
   }
 
+  public boolean hasOffset() {
+    return offset >= 0;
+  }
+
   public int getOffset() {
     return offset;
   }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index dab9181..10e78f3 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.SourceFileEnvironment;
-import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.dex.FileWriter.ByteBufferResult;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.features.FeatureSplitConfiguration.DataResourceProvidersAndConsumer;
@@ -25,8 +24,6 @@
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexEncodedArray;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -36,6 +33,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -127,7 +125,7 @@
     }
 
     @Override
-    public boolean add(DexCode dexCode) {
+    public boolean add(DexEncodedMethod method, DexWritableCode dexCode) {
       return true;
     }
 
@@ -642,18 +640,9 @@
   }
 
   private void setCallSiteContexts(DexProgramClass clazz) {
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (method.hasCode()) {
-        DexCode code = method.getCode().asDexCode();
-        assert code != null;
-        for (Instruction instruction : code.instructions) {
-          DexCallSite callSite = instruction.getCallSite();
-          if (callSite != null) {
-            callSite.setContext(method.getReference(), instruction.getOffset());
-          }
-        }
-      }
-    }
+    clazz.forEachProgramMethodMatching(
+        DexEncodedMethod::hasCode,
+        method -> method.getDefinition().getCode().asDexWritableCode().setCallSiteContexts(method));
   }
 
   /**
@@ -683,20 +672,23 @@
     // for all code objects and write the processed results into that map.
     // TODO(b/181636450): Reconsider the code mapping setup now that synthetics are never duplicated
     //  in outputs.
-    Map<DexEncodedMethod, DexCode> codeMapping = new IdentityHashMap<>();
+    Map<DexEncodedMethod, DexWritableCode> codeMapping = new IdentityHashMap<>();
     for (DexProgramClass clazz : classes) {
-      clazz.forEachMethod(
+      clazz.forEachProgramMethodMatching(
+          DexEncodedMethod::hasCode,
           method -> {
-            DexCode code =
-                method.rewriteCodeWithJumboStrings(
+            DexWritableCode code = method.getDefinition().getCode().asDexWritableCode();
+            DexWritableCode rewrittenCode =
+                code.rewriteCodeWithJumboStrings(
+                    method,
                     mapping,
                     application.dexItemFactory,
                     options.testing.forceJumboStringProcessing);
-            codeMapping.put(method, code);
+            codeMapping.put(method.getDefinition(), rewrittenCode);
             // The mapping now has ownership of the methods code object. This ensures freeing of
             // code resources once the map entry is cleared and also ensures that we don't end up
             // using the incorrect code pointer again later!
-            method.removeCode();
+            method.getDefinition().unsetCode();
           });
     }
     return MethodToCodeObjectMapping.fromMapBacking(codeMapping);
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 0e1edc9..44affb4 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -33,11 +33,11 @@
     return new DesugaredLibraryCodeToKeep(namingLens, options);
   }
 
-  abstract void recordMethod(DexMethod method);
+  public abstract void recordMethod(DexMethod method);
 
-  abstract void recordField(DexField field);
+  public abstract void recordField(DexField field);
 
-  abstract void recordClass(DexType type);
+  public abstract void recordClass(DexType type);
 
   abstract void recordClassAllAccesses(DexType type);
 
@@ -82,7 +82,7 @@
     }
 
     @Override
-    void recordMethod(DexMethod method) {
+    public void recordMethod(DexMethod method) {
       DexType baseType = method.holder.toBaseType(options.dexItemFactory());
       if (shouldKeep(baseType)) {
         keepClass(baseType);
@@ -101,7 +101,7 @@
     }
 
     @Override
-    void recordField(DexField field) {
+    public void recordField(DexField field) {
       DexType baseType = field.holder.toBaseType(options.dexItemFactory());
       if (shouldKeep(baseType)) {
         keepClass(baseType);
@@ -115,7 +115,7 @@
     }
 
     @Override
-    void recordClass(DexType type) {
+    public void recordClass(DexType type) {
       if (shouldKeep(type)) {
         keepClass(type);
       }
@@ -218,13 +218,13 @@
   public static class NopCodeToKeep extends CodeToKeep {
 
     @Override
-    void recordMethod(DexMethod method) {}
+    public void recordMethod(DexMethod method) {}
 
     @Override
-    void recordField(DexField field) {}
+    public void recordField(DexField field) {}
 
     @Override
-    void recordClass(DexType type) {}
+    public void recordClass(DexType type) {}
 
     @Override
     void recordClassAllAccesses(DexType type) {}
diff --git a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
index b193e73..08f8cad 100644
--- a/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
+++ b/src/main/java/com/android/tools/r8/dex/DexOutputBuffer.java
@@ -4,11 +4,8 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.ByteBufferProvider;
-import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.EncodedValueUtils;
@@ -95,37 +92,17 @@
   }
 
   public void putInstructions(
-      DexCode code,
+      DexWritableCode code,
       ProgramMethod context,
       ObjectToOffsetMapping mapping,
       CodeToKeep desugaredLibraryCodeToKeep) {
-    int size = 0;
-    Instruction[] instructions = code.instructions;
-    for (Instruction instruction : instructions) {
-      size += instruction.getSize();
-    }
+    int size = code.codeSizeInBytes();
     ensureSpaceFor(size * Short.BYTES);
     assert byteBuffer.position() % 2 == 0;
     ShortBuffer shortBuffer = byteBuffer.asShortBuffer();
-    for (int i = 0; i < instructions.length; i++) {
-      Instruction insn = instructions[i];
-      DexMethod method = insn.getMethod();
-      DexField field = insn.getField();
-      if (field != null) {
-        assert method == null;
-        desugaredLibraryCodeToKeep.recordField(field);
-      } else if (method != null) {
-        desugaredLibraryCodeToKeep.recordMethod(method);
-      } else if (insn.isConstClass()) {
-        desugaredLibraryCodeToKeep.recordClass(insn.asConstClass().getType());
-      } else if (insn.isInstanceOf()) {
-        desugaredLibraryCodeToKeep.recordClass(insn.asInstanceOf().getType());
-      } else if (insn.isCheckCast()) {
-        desugaredLibraryCodeToKeep.recordClass(insn.asCheckCast().getType());
-      }
-      insn.write(
-          shortBuffer, context, mapping.getGraphLens(), mapping, mapping.getLensCodeRewriter());
-    }
+    code.writeDex(
+        shortBuffer, context, mapping.getGraphLens(), mapping.getLensCodeRewriter(), mapping);
+    code.writeKeepRulesForDesugaredLibrary(desugaredLibraryCodeToKeep);
     byteBuffer.position(byteBuffer.position() + shortBuffer.position() * Short.BYTES);
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index dc93c37..079d0d6 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.utils.LebUtils.sizeAsUleb128;
 
 import com.android.tools.r8.ByteBufferProvider;
-import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DefaultInterfaceMethodDiagnostic;
 import com.android.tools.r8.errors.InvokeCustomDiagnostic;
@@ -20,7 +19,6 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
 import com.android.tools.r8.graph.DexCode.TryHandler;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
@@ -43,6 +41,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
@@ -341,7 +340,7 @@
     for (DexProgramClass clazz : mapping.getClasses()) {
       clazz.forEachProgramMethod(
           method -> {
-            DexCode code = codeMapping.getCode(method.getDefinition());
+            DexWritableCode code = codeMapping.getCode(method.getDefinition());
             assert code != null || method.getDefinition().shouldNotHaveCode();
             if (code != null) {
               ProgramDexCode programCode = new ProgramDexCode(code, method);
@@ -409,18 +408,15 @@
     return size;
   }
 
-  private int sizeOfCodeItem(DexCode code) {
+  private int sizeOfCodeItem(DexWritableCode code) {
     int result = 16;
-    int insnSize = 0;
-    for (Instruction insn : code.instructions) {
-      insnSize += insn.getSize();
-    }
+    int insnSize = code.codeSizeInBytes();
     result += insnSize * 2;
-    result += code.tries.length * 8;
-    if (code.handlers.length > 0) {
+    result += code.getTries().length * 8;
+    if (code.getHandlers().length > 0) {
       result = alignSize(4, result);
-      result += LebUtils.sizeAsUleb128(code.handlers.length);
-      for (TryHandler handler : code.handlers) {
+      result += LebUtils.sizeAsUleb128(code.getHandlers().length);
+      for (TryHandler handler : code.getHandlers()) {
         boolean hasCatchAll = handler.catchAllAddr != TryHandler.NO_HANDLER;
         result += LebUtils
             .sizeAsSleb128(hasCatchAll ? -handler.pairs.length : handler.pairs.length);
@@ -501,13 +497,13 @@
     writeCodeItem(code.getCode(), code.getMethod());
   }
 
-  private void writeCodeItem(DexCode code, ProgramMethod method) {
-    mixedSectionOffsets.setOffsetFor(code, dest.align(4));
+  private void writeCodeItem(DexWritableCode code, ProgramMethod method) {
+    mixedSectionOffsets.setOffsetFor(method.getDefinition(), code, dest.align(4));
     // Fixed size header information.
-    dest.putShort((short) code.registerSize);
-    dest.putShort((short) code.incomingRegisterSize);
-    dest.putShort((short) code.outgoingRegisterSize);
-    dest.putShort((short) code.tries.length);
+    dest.putShort((short) code.getRegisterSize(method));
+    dest.putShort((short) code.getIncomingRegisterSize(method));
+    dest.putShort((short) code.getOutgoingRegisterSize());
+    dest.putShort((short) code.getTries().length);
     dest.putInt(mixedSectionOffsets.getOffsetFor(code.getDebugInfoForWriting()));
     // Jump over the size.
     int insnSizeOffset = dest.position();
@@ -519,16 +515,16 @@
     dest.rewind(insnSize + 4);
     dest.putInt(insnSize / 2);
     dest.forward(insnSize);
-    if (code.tries.length > 0) {
+    if (code.getTries().length > 0) {
       // The tries need to be 4 byte aligned.
       int beginOfTriesOffset = dest.align(4);
       // First write the handlers, so that we know their mixedSectionOffsets.
-      dest.forward(code.tries.length * 8);
+      dest.forward(code.getTries().length * 8);
       int beginOfHandlersOffset = dest.position();
-      dest.putUleb128(code.handlers.length);
-      short[] offsets = new short[code.handlers.length];
+      dest.putUleb128(code.getHandlers().length);
+      short[] offsets = new short[code.getHandlers().length];
       int i = 0;
-      for (TryHandler handler : code.handlers) {
+      for (TryHandler handler : code.getHandlers()) {
         offsets[i++] = (short) (dest.position() - beginOfHandlersOffset);
         boolean hasCatchAll = handler.catchAllAddr != TryHandler.NO_HANDLER;
         dest.putSleb128(hasCatchAll ? -handler.pairs.length : handler.pairs.length);
@@ -544,7 +540,7 @@
       int endOfCodeOffset = dest.position();
       // Now write the tries.
       dest.moveTo(beginOfTriesOffset);
-      for (Try aTry : code.tries) {
+      for (Try aTry : code.getTries()) {
         dest.putInt(aTry.startAddress);
         dest.putShort((short) aTry.instructionCount);
         dest.putShort(offsets[aTry.handlerIndex]);
@@ -665,13 +661,13 @@
       dest.putUleb128(nextOffset - currentOffset);
       currentOffset = nextOffset;
       dest.putUleb128(method.accessFlags.getAsDexAccessFlags());
-      DexCode code = codeMapping.getCode(method);
+      DexWritableCode code = codeMapping.getCode(method);
       desugaredLibraryCodeToKeep.recordMethod(method.getReference());
       if (code == null) {
         assert method.shouldNotHaveCode();
         dest.putUleb128(0);
       } else {
-        dest.putUleb128(mixedSectionOffsets.getOffsetFor(code));
+        dest.putUleb128(mixedSectionOffsets.getOffsetFor(method, code));
         // Writing the methods starts to take up memory so we are going to flush the
         // code objects since they are no longer necessary after this.
         codeMapping.clearCode(method);
@@ -867,7 +863,7 @@
     dest.putInt((int) adler.getValue());
   }
 
-  private int alignSize(int bytes, int value) {
+  private static int alignSize(int bytes, int value) {
     int mask = bytes - 1;
     return (value + mask) & ~mask;
   }
@@ -1083,7 +1079,7 @@
 
     private final MethodToCodeObjectMapping codeMapping;
 
-    private final Reference2IntMap<DexCode> codes = createReference2IntMap();
+    private final Reference2IntMap<DexEncodedMethod> codes = createReference2IntMap();
     private final Object2IntMap<DexDebugInfo> debugInfos = createObject2IntMap();
     private final Object2IntMap<DexTypeList> typeLists = createObject2IntMap();
     private final Reference2IntMap<DexString> stringData = createReference2IntMap();
@@ -1159,8 +1155,8 @@
     }
 
     @Override
-    public boolean add(DexCode code) {
-      return add(codes, code);
+    public boolean add(DexEncodedMethod method, DexWritableCode code) {
+      return add(codes, method);
     }
 
     @Override
@@ -1201,7 +1197,7 @@
       return add(stringData, string);
     }
 
-    public Collection<DexCode> getCodes() {
+    public Collection<DexEncodedMethod> getCodes() {
       return codes.keySet();
     }
 
@@ -1312,8 +1308,8 @@
       return lookup(annotationSetRefList, annotationSetRefLists);
     }
 
-    public int getOffsetFor(DexCode code) {
-      return lookup(code, codes);
+    public int getOffsetFor(DexEncodedMethod method, DexWritableCode code) {
+      return lookup(method, codes);
     }
 
     private <T> void setOffsetFor(T item, int offset, Object2IntMap<T> map) {
@@ -1330,8 +1326,8 @@
       setOffsetFor(debugInfo, offset, debugInfos);
     }
 
-    void setOffsetFor(DexCode code, int offset) {
-      setOffsetFor(code, offset, codes);
+    void setOffsetFor(DexEncodedMethod method, DexWritableCode code, int offset) {
+      setOffsetFor(method, offset, codes);
     }
 
     void setOffsetFor(DexTypeList typeList, int offset) {
diff --git a/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java b/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
index 5541398..dfdb07a 100644
--- a/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
+++ b/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
@@ -4,24 +4,27 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexWritableCode;
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 public abstract class MethodToCodeObjectMapping {
 
-  public abstract DexCode getCode(DexEncodedMethod method);
+  public abstract DexWritableCode getCode(DexEncodedMethod method);
 
   public abstract void clearCode(DexEncodedMethod method);
 
-  public abstract boolean verifyCodeObjects(Collection<DexCode> codes);
+  public abstract boolean verifyCodeObjects(Collection<DexEncodedMethod> codes);
 
   public static MethodToCodeObjectMapping fromMethodBacking() {
     return MethodBacking.INSTANCE;
   }
 
-  public static MethodToCodeObjectMapping fromMapBacking(Map<DexEncodedMethod, DexCode> map) {
+  public static MethodToCodeObjectMapping fromMapBacking(
+      Map<DexEncodedMethod, DexWritableCode> map) {
     return new MapBacking(map);
   }
 
@@ -30,33 +33,33 @@
     private static final MethodBacking INSTANCE = new MethodBacking();
 
     @Override
-    public DexCode getCode(DexEncodedMethod method) {
+    public DexWritableCode getCode(DexEncodedMethod method) {
       Code code = method.getCode();
-      assert code == null || code.isDexCode();
-      return code == null ? null : code.asDexCode();
+      assert code == null || code.isDexWritableCode();
+      return code == null ? null : code.asDexWritableCode();
     }
 
     @Override
     public void clearCode(DexEncodedMethod method) {
-      method.removeCode();
+      method.unsetCode();
     }
 
     @Override
-    public boolean verifyCodeObjects(Collection<DexCode> codes) {
+    public boolean verifyCodeObjects(Collection<DexEncodedMethod> codes) {
       return true;
     }
   }
 
   private static class MapBacking extends MethodToCodeObjectMapping {
 
-    private final Map<DexEncodedMethod, DexCode> codes;
+    private final Map<DexEncodedMethod, DexWritableCode> codes;
 
-    public MapBacking(Map<DexEncodedMethod, DexCode> codes) {
+    public MapBacking(Map<DexEncodedMethod, DexWritableCode> codes) {
       this.codes = codes;
     }
 
     @Override
-    public DexCode getCode(DexEncodedMethod method) {
+    public DexWritableCode getCode(DexEncodedMethod method) {
       return codes.get(method);
     }
 
@@ -67,7 +70,10 @@
     }
 
     @Override
-    public boolean verifyCodeObjects(Collection<DexCode> codes) {
+    public boolean verifyCodeObjects(Collection<DexEncodedMethod> methods) {
+      // TODO(b/204056443): Convert to a Set<DexWritableCode> when DexCode#hashCode() works.
+      List<DexWritableCode> codes =
+          methods.stream().map(this::getCode).collect(Collectors.toList());
       assert this.codes.values().containsAll(codes);
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
index 2cedf5c..0ca695e 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
@@ -6,13 +6,13 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexEncodedArray;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DexWritableCode;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 
 /**
@@ -66,11 +66,11 @@
   /**
    * Adds the given code item to the collection.
    *
-   * Does not add any dependencies.
+   * <p>Does not add any dependencies.
    *
    * @return true if the item was not added before
    */
-  public abstract boolean add(DexCode dexCode);
+  public abstract boolean add(DexEncodedMethod method, DexWritableCode dexCode);
 
   /**
    * Adds the given debug info to the collection.
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 4589a49..455d586 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -62,7 +62,7 @@
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 
-public class CfCode extends Code implements StructuralItem<CfCode> {
+public class CfCode extends Code implements CfWritableCode, StructuralItem<CfCode> {
 
   public enum StackMapStatus {
     NOT_VERIFIED,
@@ -261,11 +261,21 @@
   }
 
   @Override
+  public boolean isCfWritableCode() {
+    return true;
+  }
+
+  @Override
   public CfCode asCfCode() {
     return this;
   }
 
   @Override
+  public CfWritableCode asCfWritableCode() {
+    return this;
+  }
+
+  @Override
   public void acceptHashing(HashingVisitor visitor) {
     // Rather than hash the entire content, hash the sizes and each instruction "type" which
     // should provide a fast yet reasonably distinct key.
@@ -316,7 +326,8 @@
     return true;
   }
 
-  public void write(
+  @Override
+  public void writeCf(
       ProgramMethod method,
       CfVersion classFileVersion,
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/graph/CfWritableCode.java b/src/main/java/com/android/tools/r8/graph/CfWritableCode.java
new file mode 100644
index 0000000..fd23726
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/CfWritableCode.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+
+public interface CfWritableCode {
+
+  void writeCf(
+      ProgramMethod method,
+      CfVersion classFileVersion,
+      AppView<?> appView,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor);
+}
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 0d6c31e..ba7a9c9 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.optimize.OutlinerImpl.OutlineCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -59,10 +58,18 @@
     return false;
   }
 
+  public boolean isCfWritableCode() {
+    return false;
+  }
+
   public boolean isDexCode() {
     return false;
   }
 
+  public boolean isDexWritableCode() {
+    return false;
+  }
+
   public boolean isHorizontalClassMergingCode() {
     return false;
   }
@@ -71,6 +78,17 @@
     return false;
   }
 
+  public boolean isSharedCodeObject() {
+    return false;
+  }
+
+  public boolean isThrowNullCode() {
+    return false;
+  }
+
+  public ThrowNullCode asThrowNullCode() {
+    return null;
+  }
 
   /** Estimate the number of IR instructions emitted by buildIR(). */
   public int estimatedSizeForInlining() {
@@ -88,6 +106,10 @@
     throw new Unreachable(getClass().getCanonicalName() + ".asCfCode()");
   }
 
+  public CfWritableCode asCfWritableCode() {
+    throw new Unreachable(getClass().getCanonicalName() + ".asCfWritableCode()");
+  }
+
   public LazyCfCode asLazyCfCode() {
     throw new Unreachable(getClass().getCanonicalName() + ".asLazyCfCode()");
   }
@@ -96,8 +118,8 @@
     throw new Unreachable(getClass().getCanonicalName() + ".asDexCode()");
   }
 
-  public OutlineCode asOutlineCode() {
-    throw new Unreachable(getClass().getCanonicalName() + ".asOutlineCode()");
+  public DexWritableCode asDexWritableCode() {
+    throw new Unreachable(getClass().getCanonicalName() + ".asDexWritableCode()");
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index af3bcde..c23bf18 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -7,7 +7,10 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.code.SwitchPayload;
+import com.android.tools.r8.dex.CodeToKeep;
+import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.JumboStringRewriter;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
@@ -32,6 +35,7 @@
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import com.google.common.base.Strings;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import java.nio.ShortBuffer;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -41,7 +45,7 @@
 import java.util.Set;
 
 // DexCode corresponds to code item in dalvik/dex-format.html
-public class DexCode extends Code implements StructuralItem<DexCode> {
+public class DexCode extends Code implements DexWritableCode, StructuralItem<DexCode> {
 
   public static final String FAKE_THIS_PREFIX = "_";
   public static final String FAKE_THIS_SUFFIX = "this";
@@ -130,6 +134,36 @@
     return DexCode::specify;
   }
 
+  @Override
+  public DexWritableCode rewriteCodeWithJumboStrings(
+      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+    DexString firstJumboString = null;
+    if (force) {
+      firstJumboString = mapping.getFirstString();
+    } else {
+      assert highestSortingString != null
+          || Arrays.stream(instructions).noneMatch(Instruction::isConstString);
+      assert Arrays.stream(instructions).noneMatch(Instruction::isDexItemBasedConstString);
+      if (highestSortingString != null
+          && mapping.getOffsetFor(highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
+        firstJumboString = mapping.getFirstJumboString();
+      }
+    }
+    return firstJumboString != null
+        ? new JumboStringRewriter(method.getDefinition(), firstJumboString, factory).rewrite()
+        : this;
+  }
+
+  @Override
+  public void setCallSiteContexts(ProgramMethod method) {
+    for (Instruction instruction : instructions) {
+      DexCallSite callSite = instruction.getCallSite();
+      if (callSite != null) {
+        callSite.setContext(method.getReference(), instruction.getOffset());
+      }
+    }
+  }
+
   public DexCode withoutThisParameter() {
     // Note that we assume the original code has a register associated with 'this'
     // argument of the (former) instance method. We also assume (but do not check)
@@ -153,6 +187,16 @@
   }
 
   @Override
+  public boolean isDexWritableCode() {
+    return true;
+  }
+
+  @Override
+  public DexWritableCode asDexWritableCode() {
+    return this;
+  }
+
+  @Override
   public int estimatedSizeForInlining() {
     return codeSizeInBytes();
   }
@@ -510,6 +554,7 @@
       }
   }
 
+  @Override
   public DexDebugInfoForWriting getDebugInfoForWriting() {
     if (debugInfo == null) {
       return null;
@@ -521,6 +566,36 @@
     return debugInfoForWriting;
   }
 
+  @Override
+  public TryHandler[] getHandlers() {
+    return handlers;
+  }
+
+  @Override
+  public DexString getHighestSortingString() {
+    return highestSortingString;
+  }
+
+  @Override
+  public Try[] getTries() {
+    return tries;
+  }
+
+  @Override
+  public int getRegisterSize(ProgramMethod method) {
+    return registerSize;
+  }
+
+  @Override
+  public int getIncomingRegisterSize(ProgramMethod method) {
+    return incomingRegisterSize;
+  }
+
+  @Override
+  public int getOutgoingRegisterSize() {
+    return outgoingRegisterSize;
+  }
+
   private void updateHighestSortingString(DexString candidate) {
     assert candidate != null;
     if (highestSortingString == null || highestSortingString.compareTo(candidate) < 0) {
@@ -533,17 +608,59 @@
   }
 
   @Override
-  void collectMixedSectionItems(MixedSectionCollection mixedItems) {
-    if (mixedItems.add(this)) {
-      if (debugInfo != null) {
-        getDebugInfoForWriting().collectMixedSectionItems(mixedItems);
+  public void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+    if (debugInfo != null) {
+      getDebugInfoForWriting().collectMixedSectionItems(mixedItems);
+    }
+  }
+
+  @Override
+  public int codeSizeInBytes() {
+    Instruction last = instructions[instructions.length - 1];
+    assert last.hasOffset();
+    int result = last.getOffset() + last.getSize();
+    assert result == computeCodeSizeInBytes();
+    return result;
+  }
+
+  private int computeCodeSizeInBytes() {
+    int size = 0;
+    for (Instruction insn : instructions) {
+      size += insn.getSize();
+    }
+    return size;
+  }
+
+  @Override
+  public void writeKeepRulesForDesugaredLibrary(CodeToKeep desugaredLibraryCodeToKeep) {
+    for (Instruction instruction : instructions) {
+      DexMethod method = instruction.getMethod();
+      DexField field = instruction.getField();
+      if (field != null) {
+        assert method == null;
+        desugaredLibraryCodeToKeep.recordField(field);
+      } else if (method != null) {
+        desugaredLibraryCodeToKeep.recordMethod(method);
+      } else if (instruction.isConstClass()) {
+        desugaredLibraryCodeToKeep.recordClass(instruction.asConstClass().getType());
+      } else if (instruction.isInstanceOf()) {
+        desugaredLibraryCodeToKeep.recordClass(instruction.asInstanceOf().getType());
+      } else if (instruction.isCheckCast()) {
+        desugaredLibraryCodeToKeep.recordClass(instruction.asCheckCast().getType());
       }
     }
   }
 
-  public int codeSizeInBytes() {
-    Instruction last = instructions[instructions.length - 1];
-    return last.getOffset() + last.getSize();
+  @Override
+  public void writeDex(
+      ShortBuffer shortBuffer,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils lensCodeRewriter,
+      ObjectToOffsetMapping mapping) {
+    for (Instruction instruction : instructions) {
+      instruction.write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+    }
   }
 
   public static class Try extends DexItem implements StructuralItem<Try> {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 14a7ff5..afdef0a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -40,8 +40,6 @@
 import com.android.tools.r8.code.Return;
 import com.android.tools.r8.code.Throw;
 import com.android.tools.r8.code.XorIntLit8;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.dex.JumboStringRewriter;
 import com.android.tools.r8.dex.MethodToCodeObjectMapping;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.InternalCompilerError;
@@ -776,8 +774,8 @@
 
   public void collectMixedSectionItemsWithCodeMapping(
       MixedSectionCollection mixedItems, MethodToCodeObjectMapping mapping) {
-    DexCode code = mapping.getCode(this);
-    if (code != null) {
+    DexWritableCode code = mapping.getCode(this);
+    if (code != null && mixedItems.add(this, code)) {
       code.collectMixedSectionItems(mixedItems);
     }
     annotations().collectMixedSectionItems(mixedItems);
@@ -797,11 +795,6 @@
     return code;
   }
 
-  public void removeCode() {
-    checkIfObsolete();
-    code = null;
-  }
-
   public CfVersion getClassFileVersion() {
     checkIfObsolete();
     assert classFileVersion != null;
@@ -1298,34 +1291,6 @@
     return method;
   }
 
-  /** Rewrites the code in this method to have JumboString bytecode if required by mapping. */
-  public DexCode rewriteCodeWithJumboStrings(
-      ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
-    checkIfObsolete();
-    assert code == null || code.isDexCode();
-    if (code == null) {
-      return null;
-    }
-    DexCode code = this.code.asDexCode();
-    DexString firstJumboString = null;
-    if (force) {
-      firstJumboString = mapping.getFirstString();
-    } else {
-      assert code.highestSortingString != null
-          || Arrays.stream(code.instructions).noneMatch(Instruction::isConstString);
-      assert Arrays.stream(code.instructions).noneMatch(Instruction::isDexItemBasedConstString);
-      if (code.highestSortingString != null
-          && mapping.getOffsetFor(code.highestSortingString) > Constants.MAX_NON_JUMBO_INDEX) {
-        firstJumboString = mapping.getFirstJumboString();
-      }
-    }
-    if (firstJumboString != null) {
-      JumboStringRewriter rewriter = new JumboStringRewriter(this, firstJumboString, factory);
-      return rewriter.rewrite();
-    }
-    return code;
-  }
-
   public String codeToString() {
     checkIfObsolete();
     return code == null ? "<no code>" : code.toString(this, null);
diff --git a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
new file mode 100644
index 0000000..9aea5e4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.dex.CodeToKeep;
+import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import java.nio.ShortBuffer;
+
+public interface DexWritableCode {
+
+  int codeSizeInBytes();
+
+  void collectMixedSectionItems(MixedSectionCollection mixedItems);
+
+  void writeKeepRulesForDesugaredLibrary(CodeToKeep codeToKeep);
+
+  DexDebugInfoForWriting getDebugInfoForWriting();
+
+  DexString getHighestSortingString();
+
+  TryHandler[] getHandlers();
+
+  Try[] getTries();
+
+  int getRegisterSize(ProgramMethod method);
+
+  int getIncomingRegisterSize(ProgramMethod method);
+
+  int getOutgoingRegisterSize();
+
+  /** Rewrites the code to have JumboString bytecode if required by mapping. */
+  DexWritableCode rewriteCodeWithJumboStrings(
+      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force);
+
+  void setCallSiteContexts(ProgramMethod method);
+
+  void writeDex(
+      ShortBuffer shortBuffer,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils lensCodeRewriter,
+      ObjectToOffsetMapping mapping);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index f222dbf..9589d1e 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -168,6 +168,8 @@
         // If code is (lazy) CF code, then use the CF code object rather than the lazy wrapper.
         if (code.isCfCode()) {
           code = code.asCfCode();
+        } else if (code.isSharedCodeObject()) {
+          continue;
         }
         DexEncodedMethod otherMethod = codeOwners.put(code, method);
         assert otherMethod == null;
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index ba5faec..2403804 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -121,6 +121,11 @@
   }
 
   @Override
+  public boolean isCfWritableCode() {
+    return true;
+  }
+
+  @Override
   public LazyCfCode asLazyCfCode() {
     return this;
   }
@@ -134,6 +139,11 @@
     return code;
   }
 
+  @Override
+  public CfWritableCode asCfWritableCode() {
+    return asCfCode();
+  }
+
   private void internalParseCode() {
     ReparseContext context = this.context;
     JarApplicationReader application = this.application;
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java b/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java
index 93e42d1..4e15eac 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramDexCode.java
@@ -6,15 +6,15 @@
 
 public class ProgramDexCode {
 
-  private final DexCode code;
+  private final DexWritableCode code;
   private final ProgramMethod method;
 
-  public ProgramDexCode(DexCode code, ProgramMethod method) {
+  public ProgramDexCode(DexWritableCode code, ProgramMethod method) {
     this.code = code;
     this.method = method;
   }
 
-  public DexCode getCode() {
+  public DexWritableCode getCode() {
     return code;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 146e33f..90278d0 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -61,11 +61,14 @@
       IndexedItemCollection indexedItems, GraphLens graphLens, LensCodeRewriterUtils rewriter) {
     DexEncodedMethod definition = getDefinition();
     assert !definition.isObsolete();
-    assert !definition.hasCode() || definition.getCode().isDexCode();
     getReference().collectIndexedItems(indexedItems);
-    Code code = definition.getCode();
-    if (code != null && code.isDexCode()) {
-      code.asDexCode().collectIndexedItems(indexedItems, this, graphLens, rewriter);
+    if (definition.hasCode()) {
+      Code code = definition.getCode();
+      if (code.isDexCode()) {
+        code.asDexCode().collectIndexedItems(indexedItems, this, graphLens, rewriter);
+      } else {
+        assert code.isThrowNullCode();
+      }
     }
     definition.annotations().collectIndexedItems(indexedItems);
     definition.parameterAnnotationsList.collectIndexedItems(indexedItems);
@@ -106,9 +109,8 @@
   public void convertToThrowNullMethod(AppView<?> appView) {
     MethodAccessFlags accessFlags = getAccessFlags();
     accessFlags.demoteFromAbstract();
-    Code emptyThrowingCode = getDefinition().buildEmptyThrowingCode(appView.options());
     getDefinition().setApiLevelForCode(AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView));
-    getDefinition().setCode(emptyThrowingCode, appView);
+    getDefinition().setCode(ThrowNullCode.get(), appView);
     getSimpleFeedback().markProcessed(getDefinition(), ConstraintWithTarget.ALWAYS);
     getSimpleFeedback().unsetOptimizationInfoForThrowNullMethod(this);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
new file mode 100644
index 0000000..c9d875a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -0,0 +1,377 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.Throw;
+import com.android.tools.r8.dex.CodeToKeep;
+import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.conversion.SourceCode;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.ConsumerUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.ShortBuffer;
+import java.util.List;
+import java.util.function.Consumer;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class ThrowNullCode extends Code implements CfWritableCode, DexWritableCode {
+
+  private static final ThrowNullCode INSTANCE = new ThrowNullCode();
+
+  private ThrowNullCode() {}
+
+  public static ThrowNullCode get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+    ThrowNullSourceCode source = new ThrowNullSourceCode(method);
+    return IRBuilder.create(method, appView, source, origin).build(method);
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      ProgramMethod context,
+      ProgramMethod method,
+      AppView<?> appView,
+      GraphLens codeLens,
+      NumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      Origin origin,
+      RewrittenPrototypeDescription protoChanges) {
+    ThrowNullSourceCode source = new ThrowNullSourceCode(method, callerPosition);
+    return IRBuilder.createForInlining(
+            method, appView, codeLens, source, origin, valueNumberGenerator, protoChanges)
+        .build(context);
+  }
+
+  @Override
+  public int codeSizeInBytes() {
+    return Const4.SIZE + Throw.SIZE;
+  }
+
+  @Override
+  public void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+    // Intentionally empty.
+  }
+
+  @Override
+  protected int computeHashCode() {
+    return System.identityHashCode(this);
+  }
+
+  @Override
+  protected boolean computeEquals(Object other) {
+    return this == other;
+  }
+
+  @Override
+  public int estimatedDexCodeSizeUpperBoundInBytes() {
+    return codeSizeInBytes();
+  }
+
+  @Override
+  public DexDebugInfoForWriting getDebugInfoForWriting() {
+    return null;
+  }
+
+  @Override
+  public TryHandler[] getHandlers() {
+    return new TryHandler[0];
+  }
+
+  @Override
+  public DexString getHighestSortingString() {
+    return null;
+  }
+
+  @Override
+  public int getIncomingRegisterSize(ProgramMethod method) {
+    return getMaxLocals(method);
+  }
+
+  private int getMaxLocals(ProgramMethod method) {
+    int maxLocals = method.getAccessFlags().isStatic() ? 0 : 1;
+    for (DexType parameter : method.getParameters()) {
+      maxLocals += parameter.getRequiredRegisters();
+    }
+    return maxLocals;
+  }
+
+  @Override
+  public int getOutgoingRegisterSize() {
+    return 0;
+  }
+
+  @Override
+  public int getRegisterSize(ProgramMethod method) {
+    return Math.max(getIncomingRegisterSize(method), 1);
+  }
+
+  @Override
+  public Try[] getTries() {
+    return new Try[0];
+  }
+
+  @Override
+  public boolean isCfWritableCode() {
+    return true;
+  }
+
+  @Override
+  public CfWritableCode asCfWritableCode() {
+    return this;
+  }
+
+  @Override
+  public boolean isDexWritableCode() {
+    return true;
+  }
+
+  @Override
+  public DexWritableCode asDexWritableCode() {
+    return this;
+  }
+
+  @Override
+  public boolean isEmptyVoidMethod() {
+    return false;
+  }
+
+  @Override
+  public boolean isSharedCodeObject() {
+    return true;
+  }
+
+  @Override
+  public boolean isThrowNullCode() {
+    return true;
+  }
+
+  @Override
+  public ThrowNullCode asThrowNullCode() {
+    return this;
+  }
+
+  @Override
+  public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public DexWritableCode rewriteCodeWithJumboStrings(
+      ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
+    // Intentionally empty. This piece of code does not have any const-string instructions.
+    return this;
+  }
+
+  @Override
+  public void setCallSiteContexts(ProgramMethod method) {
+    // Intentionally empty. This piece of code does not have any call sites.
+  }
+
+  @Override
+  public void writeCf(
+      ProgramMethod method,
+      CfVersion classFileVersion,
+      AppView<?> appView,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    int maxStack = 1;
+    visitor.visitInsn(Opcodes.ACONST_NULL);
+    visitor.visitInsn(Opcodes.ATHROW);
+    visitor.visitEnd();
+    visitor.visitMaxs(maxStack, getMaxLocals(method));
+  }
+
+  @Override
+  public void writeDex(
+      ShortBuffer shortBuffer,
+      ProgramMethod context,
+      GraphLens graphLens,
+      LensCodeRewriterUtils lensCodeRewriter,
+      ObjectToOffsetMapping mapping) {
+    int register = 0;
+    new Const4(register, 0).write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+    new Throw(register).write(shortBuffer, context, graphLens, mapping, lensCodeRewriter);
+  }
+
+  @Override
+  public void writeKeepRulesForDesugaredLibrary(CodeToKeep codeToKeep) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public String toString() {
+    return "ThrowNullCode";
+  }
+
+  @Override
+  public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+    return "ThrowNullCode";
+  }
+
+  static class ThrowNullSourceCode implements SourceCode {
+
+    private static final List<Consumer<IRBuilder>> instructionBuilders =
+        ImmutableList.of(builder -> builder.addNullConst(0), builder -> builder.addThrow(0));
+
+    private final ProgramMethod method;
+    private final Position position;
+
+    ThrowNullSourceCode(ProgramMethod method) {
+      this(method, null);
+    }
+
+    ThrowNullSourceCode(ProgramMethod method, Position callerPosition) {
+      this.method = method;
+      this.position =
+          SyntheticPosition.builder()
+              .setLine(0)
+              .setMethod(method.getReference())
+              .setCallerPosition(callerPosition)
+              .build();
+    }
+
+    @Override
+    public int instructionCount() {
+      return instructionBuilders.size();
+    }
+
+    @Override
+    public int instructionIndex(int instructionOffset) {
+      return instructionOffset;
+    }
+
+    @Override
+    public int instructionOffset(int instructionIndex) {
+      return instructionIndex;
+    }
+
+    @Override
+    public void buildPrelude(IRBuilder builder) {
+      int firstArgumentRegister = 0;
+      builder.buildArgumentsWithRewrittenPrototypeChanges(
+          firstArgumentRegister, method.getDefinition(), ConsumerUtils.emptyBiConsumer());
+    }
+
+    @Override
+    public void buildInstruction(
+        IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
+      instructionBuilders.get(instructionIndex).accept(builder);
+    }
+
+    @Override
+    public void buildPostlude(IRBuilder builder) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void clear() {
+      // Intentionally empty.
+    }
+
+    @Override
+    public Position getCanonicalDebugPositionAtOffset(int offset) {
+      return null;
+    }
+
+    @Override
+    public CatchHandlers<Integer> getCurrentCatchHandlers(IRBuilder builder) {
+      return null;
+    }
+
+    @Override
+    public Position getCurrentPosition() {
+      return position;
+    }
+
+    @Override
+    public DebugLocalInfo getIncomingLocal(int register) {
+      return null;
+    }
+
+    @Override
+    public DebugLocalInfo getIncomingLocalAtBlock(int register, int blockOffset) {
+      return null;
+    }
+
+    @Override
+    public DebugLocalInfo getOutgoingLocal(int register) {
+      return null;
+    }
+
+    @Override
+    public void setUp() {
+      // Intentionally empty.
+    }
+
+    @Override
+    public int traceInstruction(int instructionIndex, IRBuilder builder) {
+      // This instruction does not close the block.
+      return -1;
+    }
+
+    @Override
+    public boolean verifyCurrentInstructionCanThrow() {
+      return true;
+    }
+
+    @Override
+    public boolean verifyRegister(int register) {
+      return true;
+    }
+
+    @Override
+    public void buildBlockTransfer(
+        IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public int getMoveExceptionRegister(int instructionIndex) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public void resolveAndBuildNewArrayFilledData(
+        int arrayRef, int payloadOffset, IRBuilder builder) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public void resolveAndBuildSwitch(
+        int value, int fallthroughOffset, int payloadOffset, IRBuilder builder) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public boolean verifyLocalInScope(DebugLocalInfo local) {
+      throw new Unreachable();
+    }
+  }
+}
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 3aefd80..eb30bd3 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
@@ -561,7 +561,10 @@
     }
   }
 
-  private boolean needsIRConversion() {
+  private boolean needsIRConversion(ProgramMethod method) {
+    if (method.getDefinition().getCode().isThrowNullCode()) {
+      return false;
+    }
     if (appView.enableWholeProgramOptimizations()) {
       return true;
     }
@@ -1062,7 +1065,7 @@
       options.testing.hookInIrConversion.run();
     }
 
-    if (!needsIRConversion() || options.skipIR) {
+    if (!needsIRConversion(method) || options.skipIR) {
       feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return Timing.empty();
     }
@@ -1683,7 +1686,9 @@
   }
 
   private synchronized void updateHighestSortingStrings(DexEncodedMethod method) {
-    DexString highestSortingReferencedString = method.getCode().asDexCode().highestSortingString;
+    Code code = method.getCode();
+    assert code.isDexWritableCode();
+    DexString highestSortingReferencedString = code.asDexWritableCode().getHighestSortingString();
     if (highestSortingReferencedString != null) {
       if (highestSortingString == null
           || highestSortingReferencedString.compareTo(highestSortingString) > 0) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 7513257..c89f90c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -267,7 +267,7 @@
                       assert InvalidCode.isInvalidCode(defaultMethod.getCode());
                       assert !InvalidCode.isInvalidCode(companionMethod.getCode());
                       defaultMethod.accessFlags.setAbstract();
-                      defaultMethod.removeCode();
+                      defaultMethod.unsetCode();
                       graphLensBuilder.recordCodeMovedToCompanionClass(
                           defaultMethod.getReference(), companionMethod.getReference());
                     });
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index 68111d4..921bc7f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -1773,11 +1773,6 @@
     }
 
     @Override
-    public OutlineCode asOutlineCode() {
-      return this;
-    }
-
-    @Override
     public boolean isEmptyVoidMethod() {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 7b8da09..b10c36a 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.errors.ConstantPoolOverflowDiagnostic;
 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.Code;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -591,8 +591,10 @@
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    CfCode code = method.getDefinition().getCode().asCfCode();
-    code.write(method, classFileVersion, appView, namingLens, rewriter, visitor);
+    Code code = method.getDefinition().getCode();
+    assert code.isCfWritableCode();
+    code.asCfWritableCode()
+        .writeCf(method, classFileVersion, appView, namingLens, rewriter, visitor);
   }
 
   public static String printCf(byte[] result) {
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index a22a766..f00e19c 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -156,8 +156,7 @@
           instructions[i] = constString;
         }
       }
-    } else {
-      assert code.isCfCode();
+    } else if (code.isCfCode()) {
       List<CfInstruction> instructions = code.asCfCode().getInstructions();
       List<CfInstruction> newInstructions =
           ListUtils.mapOrElse(
@@ -176,6 +175,8 @@
               },
               instructions);
       code.asCfCode().setInstructions(newInstructions);
+    } else {
+      assert code.isThrowNullCode();
     }
   }
 }