Merge commit '7a298ac1ef617d3a2d2d985057b971c7240e4519' into dev-release
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index 9d0265e..6d66d0a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -8,6 +8,28 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+/**
+ * A condition for a keep edge.
+ *
+ * <p>The condition denotes a keep item:
+ *
+ * <ul>
+ *   <li>a class, or pattern on classes;
+ *   <li>a method, or pattern on methods; or
+ *   <li>a field, or pattern on fields.
+ * </ul>
+ *
+ * <p>The structure of a condition item is the same as for a target item but without a notion of
+ * "keep options".
+ */
 @Target(ElementType.ANNOTATION_TYPE)
 @Retention(RetentionPolicy.CLASS)
-public @interface KeepCondition {}
+public @interface KeepCondition {
+  Class<?> classConstant() default Object.class;
+
+  String classTypeName() default "";
+
+  String methodName() default "";
+
+  String fieldName() default "";
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
index 5b6de6e..ca219b2 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
@@ -26,11 +26,20 @@
     public static final String consequences = "consequences";
   }
 
-  public static final class Target {
-    public static final Class<KeepTarget> CLASS = KeepTarget.class;
-    public static final String DESCRIPTOR = getDescriptor(CLASS);
+  // Implicit hidden item which is "super type" of Condition and Target.
+  public static final class Item {
     public static final String classConstant = "classConstant";
     public static final String methodName = "methodName";
     public static final String fieldName = "fieldName";
   }
+
+  public static final class Condition {
+    public static final Class<KeepCondition> CLASS = KeepCondition.class;
+    public static final String DESCRIPTOR = getDescriptor(CLASS);
+  }
+
+  public static final class Target {
+    public static final Class<KeepTarget> CLASS = KeepTarget.class;
+    public static final String DESCRIPTOR = getDescriptor(CLASS);
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java
index 19b1a4a..fb9a772 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java
@@ -8,7 +8,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-@Target(ElementType.TYPE)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
 @Retention(RetentionPolicy.CLASS)
 public @interface KeepEdge {
   KeepCondition[] preconditions() default {};
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index cc6e4ee..dd5be82 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -3,8 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.asm;
 
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Condition;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Item;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
+import com.android.tools.r8.keepanno.ast.KeepCondition;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdgeException;
@@ -22,6 +25,8 @@
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
@@ -52,6 +57,54 @@
       }
       return null;
     }
+
+    @Override
+    public MethodVisitor visitMethod(
+        int access, String name, String descriptor, String signature, String[] exceptions) {
+      return new KeepEdgeMethodVisitor(parent);
+    }
+
+    @Override
+    public FieldVisitor visitField(
+        int access, String name, String descriptor, String signature, Object value) {
+      return new KeepEdgeFieldVisitor(parent);
+    }
+  }
+
+  private static class KeepEdgeMethodVisitor extends MethodVisitor {
+    private final Parent<KeepEdge> parent;
+
+    KeepEdgeMethodVisitor(Parent<KeepEdge> parent) {
+      super(ASM_VERSION);
+      this.parent = parent;
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+      // Skip any visible annotations as @KeepEdge is not runtime visible.
+      if (!visible && descriptor.equals(Edge.DESCRIPTOR)) {
+        return new KeepEdgeVisitor(parent);
+      }
+      return null;
+    }
+  }
+
+  private static class KeepEdgeFieldVisitor extends FieldVisitor {
+    private final Parent<KeepEdge> parent;
+
+    KeepEdgeFieldVisitor(Parent<KeepEdge> parent) {
+      super(ASM_VERSION);
+      this.parent = parent;
+    }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+      // Skip any visible annotations as @KeepEdge is not runtime visible.
+      if (!visible && descriptor.equals(Edge.DESCRIPTOR)) {
+        return new KeepEdgeVisitor(parent);
+      }
+      return null;
+    }
   }
 
   // Interface for providing AST result(s) for a sub-tree back up to its parent.
@@ -113,10 +166,24 @@
 
   private static class KeepPreconditionsVisitor extends AnnotationVisitorBase {
     private final Parent<KeepPreconditions> parent;
+    private final KeepPreconditions.Builder builder = KeepPreconditions.builder();
 
     public KeepPreconditionsVisitor(Parent<KeepPreconditions> parent) {
       this.parent = parent;
     }
+
+    @Override
+    public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+      if (descriptor.equals(Condition.DESCRIPTOR)) {
+        return new KeepConditionVisitor(builder::addCondition);
+      }
+      return super.visitAnnotation(name, descriptor);
+    }
+
+    @Override
+    public void visitEnd() {
+      parent.accept(builder.build());
+    }
   }
 
   private static class KeepConsequencesVisitor extends AnnotationVisitorBase {
@@ -141,27 +208,28 @@
     }
   }
 
-  private static class KeepTargetVisitor extends AnnotationVisitorBase {
-    private final Parent<KeepTarget> parent;
+  private abstract static class KeepItemVisitorBase extends AnnotationVisitorBase {
+    private final Parent<KeepItemPattern> parent;
+
     private KeepQualifiedClassNamePattern classNamePattern = null;
     private KeepMethodNamePattern methodName = null;
     private KeepFieldNamePattern fieldName = null;
 
-    public KeepTargetVisitor(Parent<KeepTarget> parent) {
+    public KeepItemVisitorBase(Parent<KeepItemPattern> parent) {
       this.parent = parent;
     }
 
     @Override
     public void visit(String name, Object value) {
-      if (name.equals(Target.classConstant) && value instanceof Type) {
+      if (name.equals(Item.classConstant) && value instanceof Type) {
         classNamePattern = KeepQualifiedClassNamePattern.exact(((Type) value).getClassName());
         return;
       }
-      if (name.equals(Target.methodName) && value instanceof String) {
+      if (name.equals(Item.methodName) && value instanceof String) {
         methodName = KeepMethodNamePattern.exact((String) value);
         return;
       }
-      if (name.equals(Target.fieldName) && value instanceof String) {
+      if (name.equals(Item.fieldName) && value instanceof String) {
         fieldName = KeepFieldNamePattern.exact((String) value);
         return;
       }
@@ -184,8 +252,21 @@
       if (fieldName != null) {
         itemBuilder.setMemberPattern(KeepFieldPattern.builder().setNamePattern(fieldName).build());
       }
-      KeepTarget target = KeepTarget.builder().setItem(itemBuilder.build()).build();
-      parent.accept(target);
+      parent.accept(itemBuilder.build());
+    }
+  }
+
+  private static class KeepTargetVisitor extends KeepItemVisitorBase {
+
+    public KeepTargetVisitor(Parent<KeepTarget> parent) {
+      super(item -> parent.accept(KeepTarget.builder().setItem(item).build()));
+    }
+  }
+
+  private static class KeepConditionVisitor extends KeepItemVisitorBase {
+
+    public KeepConditionVisitor(Parent<KeepCondition> parent) {
+      super(item -> parent.accept(KeepCondition.builder().setItem(item).build()));
     }
   }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
index 0a0ccc8..a8f631a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -3,7 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.asm;
 
+import com.android.tools.r8.keepanno.annotations.KeepConstants;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Condition;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Item;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
@@ -44,7 +47,15 @@
     if (preconditions.isAlways()) {
       return;
     }
-    throw new Unimplemented();
+    String ignoredArrayValueName = null;
+    AnnotationVisitor arrayVisitor = visitor.visitArray(Edge.preconditions);
+    preconditions.forEach(
+        condition -> {
+          AnnotationVisitor conditionVisitor =
+              arrayVisitor.visitAnnotation(ignoredArrayValueName, Condition.DESCRIPTOR);
+          writeItem(conditionVisitor, condition.getItemPattern());
+        });
+    arrayVisitor.visitEnd();
   }
 
   private void writeConsequences(AnnotationVisitor visitor, KeepConsequences consequences) {
@@ -59,26 +70,29 @@
           if (!target.getOptions().isKeepAll()) {
             throw new Unimplemented();
           }
-          KeepItemPattern item = target.getItem();
-          if (item.isAny()) {
-            throw new Unimplemented();
-          }
-          KeepQualifiedClassNamePattern namePattern = item.getClassNamePattern();
-          if (namePattern.isExact()) {
-            Type typeConstant = Type.getType(namePattern.getExactDescriptor());
-            targetVisitor.visit(Target.classConstant, typeConstant);
-          } else {
-            throw new Unimplemented();
-          }
-          if (!item.getExtendsPattern().isAny()) {
-            throw new Unimplemented();
-          }
-          writeMember(item.getMemberPattern(), targetVisitor);
-          targetVisitor.visitEnd();
+          writeItem(targetVisitor, target.getItem());
         });
     arrayVisitor.visitEnd();
   }
 
+  private void writeItem(AnnotationVisitor itemVisitor, KeepItemPattern item) {
+    if (item.isAny()) {
+      throw new Unimplemented();
+    }
+    KeepQualifiedClassNamePattern namePattern = item.getClassNamePattern();
+    if (namePattern.isExact()) {
+      Type typeConstant = Type.getType(namePattern.getExactDescriptor());
+      itemVisitor.visit(KeepConstants.Item.classConstant, typeConstant);
+    } else {
+      throw new Unimplemented();
+    }
+    if (!item.getExtendsPattern().isAny()) {
+      throw new Unimplemented();
+    }
+    writeMember(item.getMemberPattern(), itemVisitor);
+    itemVisitor.visitEnd();
+  }
+
   private void writeMember(KeepMemberPattern memberPattern, AnnotationVisitor targetVisitor) {
     if (memberPattern.isNone()) {
       // Default is "no methods".
@@ -99,7 +113,7 @@
   private void writeField(KeepFieldPattern field, AnnotationVisitor targetVisitor) {
     KeepFieldNameExactPattern exactFieldName = field.getNamePattern().asExact();
     if (exactFieldName != null) {
-      targetVisitor.visit(Target.fieldName, exactFieldName.getName());
+      targetVisitor.visit(Item.fieldName, exactFieldName.getName());
     } else {
       throw new Unimplemented();
     }
@@ -114,7 +128,7 @@
   private void writeMethod(KeepMethodPattern method, AnnotationVisitor targetVisitor) {
     KeepMethodNameExactPattern exactMethodName = method.getNamePattern().asExact();
     if (exactMethodName != null) {
-      targetVisitor.visit(Target.methodName, exactMethodName.getName());
+      targetVisitor.visit(Item.methodName, exactMethodName.getName());
     } else {
       throw new Unimplemented();
     }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
index 43b4176..9909097 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
@@ -36,10 +36,33 @@
   private final KeepItemPattern itemPattern;
 
   private KeepCondition(KeepItemPattern itemPattern) {
+    assert itemPattern != null;
     this.itemPattern = itemPattern;
   }
 
   public KeepItemPattern getItemPattern() {
     return itemPattern;
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    KeepCondition that = (KeepCondition) o;
+    return itemPattern.equals(that.itemPattern);
+  }
+
+  @Override
+  public int hashCode() {
+    return itemPattern.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return itemPattern.toString();
+  }
 }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
index 18d3932..fe49466 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
@@ -9,9 +9,10 @@
 
 import com.android.tools.r8.keepanno.annotations.KeepConstants;
 import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge;
-import com.android.tools.r8.keepanno.annotations.KeepConstants.Target;
+import com.android.tools.r8.keepanno.annotations.KeepConstants.Item;
 import com.android.tools.r8.keepanno.asm.KeepEdgeReader;
 import com.android.tools.r8.keepanno.asm.KeepEdgeWriter;
+import com.android.tools.r8.keepanno.ast.KeepCondition;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepEdge.Builder;
@@ -21,12 +22,15 @@
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
+import com.android.tools.r8.keepanno.ast.KeepPreconditions;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepTarget;
-import com.android.tools.r8.keepanno.utils.Unimplemented;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.Consumer;
 import javax.annotation.processing.AbstractProcessor;
@@ -58,26 +62,33 @@
 
   @Override
   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
-    for (Element rootElement : roundEnv.getRootElements()) {
-      TypeElement typeElement = getEnclosingTypeElement(rootElement);
-      KeepEdge edge = processKeepEdge(typeElement, roundEnv);
-      if (edge != null) {
-        String edgeTargetClass =
-            getClassTypeNameForSynthesizedEdges(typeElement.getQualifiedName().toString());
-        byte[] writtenEdge = writeEdge(edge, edgeTargetClass);
-        Filer filer = processingEnv.getFiler();
-        try {
-          JavaFileObject classFile = filer.createClassFile(edgeTargetClass);
-          classFile.openOutputStream().write(writtenEdge);
-        } catch (IOException e) {
-          error(e.getMessage());
+    Map<String, List<KeepEdge>> collectedEdges = new HashMap<>();
+    for (TypeElement annotation : annotations) {
+      for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
+        KeepEdge edge = processKeepEdge(element, roundEnv);
+        if (edge != null) {
+          TypeElement enclosingType = getEnclosingTypeElement(element);
+          String enclosingTypeName = enclosingType.getQualifiedName().toString();
+          collectedEdges.computeIfAbsent(enclosingTypeName, k -> new ArrayList<>()).add(edge);
         }
       }
     }
+    for (Entry<String, List<KeepEdge>> entry : collectedEdges.entrySet()) {
+      String enclosingTypeName = entry.getKey();
+      String edgeTargetClass = getClassTypeNameForSynthesizedEdges(enclosingTypeName);
+      byte[] writtenEdge = writeEdges(entry.getValue(), edgeTargetClass);
+      Filer filer = processingEnv.getFiler();
+      try {
+        JavaFileObject classFile = filer.createClassFile(edgeTargetClass);
+        classFile.openOutputStream().write(writtenEdge);
+      } catch (IOException e) {
+        error(e.getMessage());
+      }
+    }
     return true;
   }
 
-  private static byte[] writeEdge(KeepEdge edge, String classTypeName) {
+  private static byte[] writeEdges(List<KeepEdge> edges, String classTypeName) {
     String classBinaryName = KeepConstants.getBinaryNameFromClassTypeName(classTypeName);
     ClassWriter classWriter = new ClassWriter(0);
     classWriter.visit(
@@ -88,13 +99,15 @@
         "java/lang/Object",
         null);
     classWriter.visitSource("SynthesizedKeepEdge", null);
-    KeepEdgeWriter.writeEdge(edge, classWriter);
+    for (KeepEdge edge : edges) {
+      KeepEdgeWriter.writeEdge(edge, classWriter);
+    }
     classWriter.visitEnd();
     return classWriter.toByteArray();
   }
 
-  private KeepEdge processKeepEdge(TypeElement keepEdge, RoundEnvironment roundEnv) {
-    AnnotationMirror mirror = getAnnotationMirror(keepEdge, KeepConstants.Edge.CLASS);
+  private KeepEdge processKeepEdge(Element element, RoundEnvironment roundEnv) {
+    AnnotationMirror mirror = getAnnotationMirror(element, KeepConstants.Edge.CLASS);
     if (mirror == null) {
       return null;
     }
@@ -109,7 +122,15 @@
     if (preconditions == null) {
       return;
     }
-    throw new Unimplemented();
+    KeepPreconditions.Builder preconditionsBuilder = KeepPreconditions.builder();
+    new AnnotationListValueVisitor(
+            value -> {
+              KeepCondition.Builder conditionBuilder = KeepCondition.builder();
+              processCondition(conditionBuilder, AnnotationMirrorValueVisitor.getMirror(value));
+              preconditionsBuilder.addCondition(conditionBuilder.build());
+            })
+        .onValue(preconditions);
+    edgeBuilder.setPreconditions(preconditionsBuilder.build());
   }
 
   private void processConsequences(Builder edgeBuilder, AnnotationMirror mirror) {
@@ -141,31 +162,41 @@
     }
   }
 
+  private void processCondition(KeepCondition.Builder builder, AnnotationMirror mirror) {
+    KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder();
+    processItem(itemBuilder, mirror);
+    builder.setItem(itemBuilder.build());
+  }
+
   private void processTarget(KeepTarget.Builder builder, AnnotationMirror mirror) {
     KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder();
-    AnnotationValue classConstantValue = getAnnotationValue(mirror, Target.classConstant);
+    processItem(itemBuilder, mirror);
+    builder.setItem(itemBuilder.build());
+  }
+
+  private void processItem(KeepItemPattern.Builder builder, AnnotationMirror mirror) {
+    AnnotationValue classConstantValue = getAnnotationValue(mirror, Item.classConstant);
     if (classConstantValue != null) {
       DeclaredType type = AnnotationClassValueVisitor.getType(classConstantValue);
       String typeName = getTypeNameForClassConstantElement(type);
-      itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
+      builder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
     }
-    AnnotationValue methodNameValue = getAnnotationValue(mirror, Target.methodName);
-    AnnotationValue fieldNameValue = getAnnotationValue(mirror, Target.fieldName);
+    AnnotationValue methodNameValue = getAnnotationValue(mirror, Item.methodName);
+    AnnotationValue fieldNameValue = getAnnotationValue(mirror, Item.fieldName);
     if (methodNameValue != null && fieldNameValue != null) {
       throw new KeepEdgeException("Cannot define both a method and a field name pattern");
     }
     if (methodNameValue != null) {
       String methodName = AnnotationStringValueVisitor.getString(methodNameValue);
-      itemBuilder.setMemberPattern(
+      builder.setMemberPattern(
           KeepMethodPattern.builder()
               .setNamePattern(KeepMethodNamePattern.exact(methodName))
               .build());
     } else if (fieldNameValue != null) {
       String fieldName = AnnotationStringValueVisitor.getString(fieldNameValue);
-      itemBuilder.setMemberPattern(
+      builder.setMemberPattern(
           KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build());
     }
-    builder.setItem(itemBuilder.build());
   }
 
   private void error(String message) {
@@ -181,9 +212,9 @@
     }
   }
 
-  private static AnnotationMirror getAnnotationMirror(TypeElement typeElement, Class<?> clazz) {
+  private static AnnotationMirror getAnnotationMirror(Element element, Class<?> clazz) {
     String clazzName = clazz.getName();
-    for (AnnotationMirror m : typeElement.getAnnotationMirrors()) {
+    for (AnnotationMirror m : element.getAnnotationMirrors()) {
       if (m.getAnnotationType().toString().equals(clazzName)) {
         return m;
       }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 71898e2..5a791ab 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -51,6 +51,7 @@
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.ir.desugar.records.RecordFieldValuesRewriter;
+import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.NestReducer;
@@ -300,6 +301,9 @@
       if (options.shouldDesugarRecords()) {
         RecordDesugaring.registerSynthesizedCodeReferences(appView.dexItemFactory());
       }
+      if (options.shouldDesugarVarHandle()) {
+        VarHandleDesugaring.registerSynthesizedCodeReferences(appView.dexItemFactory());
+      }
       CfUtilityMethodsForCodeOptimizations.registerSynthesizedCodeReferences(
           appView.dexItemFactory());
 
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 10acb93..23b16cc 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -320,7 +320,9 @@
       return new DexApplicationReadFlags(
           hasReadProgramResourceFromDex,
           hasReadProgramResourceFromCf,
-          application.getRecordWitnesses());
+          application.getRecordWitnesses(),
+          application.getVarHandleWitnesses(),
+          application.getMethodHandlesLookupWitnesses());
     }
 
     private void readDexSources(List<ProgramResource> dexSources, Queue<DexProgramClass> classes)
diff --git a/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java b/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
index 496066b3..cec1de3 100644
--- a/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
+++ b/src/main/java/com/android/tools/r8/graph/ApplicationReaderMap.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
 
 public abstract class ApplicationReaderMap {
 
@@ -17,10 +19,14 @@
   public abstract DexType getInvertedType(DexType type);
 
   public static ApplicationReaderMap getInstance(InternalOptions options) {
+    ApplicationReaderMap result = new EmptyMap();
     if (options.shouldDesugarRecords() && !options.testing.disableRecordApplicationReaderMap) {
-      return new RecordMap(options.dexItemFactory());
+      result = new RecordMap(options.dexItemFactory());
     }
-    return new EmptyMap();
+    if (options.shouldDesugarVarHandle()) {
+      return new VarHandleMap(result);
+    }
+    return result;
   }
 
   public static class EmptyMap extends ApplicationReaderMap {
@@ -66,4 +72,34 @@
       return type == factory.recordType ? factory.recordTagType : type;
     }
   }
+
+  public static class VarHandleMap extends ApplicationReaderMap {
+
+    private final ApplicationReaderMap previous;
+    private final Map<String, String> descriptorMap =
+        ImmutableMap.of(
+            DexItemFactory.varHandleDescriptorString,
+            DexItemFactory.desugarVarHandleDescriptorString,
+            DexItemFactory.methodHandlesLookupDescriptorString,
+            DexItemFactory.desugarMethodHandlesLookupDescriptorString);
+
+    public VarHandleMap(ApplicationReaderMap previous) {
+      this.previous = previous;
+    }
+
+    @Override
+    public String getDescriptor(String descriptor) {
+      return previous.getDescriptor(descriptorMap.getOrDefault(descriptor, descriptor));
+    }
+
+    @Override
+    public DexType getType(DexType type) {
+      return type;
+    }
+
+    @Override
+    public DexType getInvertedType(DexType type) {
+      return type;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java b/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
index 7bce5f6..b64ef99 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
@@ -12,14 +12,20 @@
   private final boolean hasReadProgramClassFromDex;
   private final boolean hasReadProgramClassFromCf;
   private final Set<DexType> recordWitnesses;
+  private final Set<DexType> varHandleWitnesses;
+  private final Set<DexType> methodHandlesLookupWitnesses;
 
   public DexApplicationReadFlags(
       boolean hasReadProgramClassFromDex,
       boolean hasReadProgramClassFromCf,
-      Set<DexType> recordWitnesses) {
+      Set<DexType> recordWitnesses,
+      Set<DexType> varHandleWitnesses,
+      Set<DexType> methodHandlesLookupWitnesses) {
     this.hasReadProgramClassFromDex = hasReadProgramClassFromDex;
     this.hasReadProgramClassFromCf = hasReadProgramClassFromCf;
     this.recordWitnesses = recordWitnesses;
+    this.varHandleWitnesses = varHandleWitnesses;
+    this.methodHandlesLookupWitnesses = methodHandlesLookupWitnesses;
   }
 
   public boolean hasReadProgramClassFromCf() {
@@ -37,4 +43,12 @@
   public Set<DexType> getRecordWitnesses() {
     return recordWitnesses;
   }
+
+  public boolean hasReadVarHandleReferenceFromProgramClass() {
+    return !varHandleWitnesses.isEmpty();
+  }
+
+  public Set<DexType> getVarHandleWitnesses() {
+    return varHandleWitnesses;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index c1e856e..888bbfe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -78,6 +78,10 @@
   public static final String desugarVarHandleDescriptorString =
       "Lcom/android/tools/r8/DesugarVarHandle;";
   public static final String varHandleDescriptorString = "Ljava/lang/invoke/VarHandle;";
+  public static final String desugarMethodHandlesLookupDescriptorString =
+      "Lcom/android/tools/r8/DesugarMethodHandlesLookup;";
+  public static final String methodHandlesLookupDescriptorString =
+      "Ljava/lang/invoke/MethodHandles$Lookup;";
   public static final String dalvikAnnotationOptimizationPrefixString =
       "Ldalvik/annotation/optimization/";
 
@@ -271,6 +275,9 @@
 
   public final DexString varHandleDescriptor = createString(varHandleDescriptorString);
   public final DexString methodHandleDescriptor = createString("Ljava/lang/invoke/MethodHandle;");
+  public final DexString methodHandlesDescriptor = createString("Ljava/lang/invoke/MethodHandles;");
+  public final DexString methodHandlesLookupDescriptor =
+      createString("Ljava/lang/invoke/MethodHandles$Lookup;");
   public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
   public final DexString invocationHandlerDescriptor =
       createString("Ljava/lang/reflect/InvocationHandler;");
@@ -353,6 +360,11 @@
   public final DexString dalvikAnnotationOptimizationPrefix =
       createString(dalvikAnnotationOptimizationPrefixString);
 
+  // Method names used on VarHandle.
+  public final DexString getString = createString("get");
+  public final DexString setString = createString("set");
+  public final DexString compareAndSetString = createString("compareAndSet");
+
   public final DexType booleanType = createStaticallyKnownType(booleanDescriptor);
   public final DexType byteType = createStaticallyKnownType(byteDescriptor);
   public final DexType charType = createStaticallyKnownType(charDescriptor);
@@ -426,6 +438,9 @@
 
   public final DexType varHandleType = createStaticallyKnownType(varHandleDescriptor);
   public final DexType methodHandleType = createStaticallyKnownType(methodHandleDescriptor);
+  public final DexType methodHandlesType = createStaticallyKnownType(methodHandlesDescriptor);
+  public final DexType methodHandlesLookupType =
+      createStaticallyKnownType(methodHandlesLookupDescriptor);
   public final DexType methodTypeType = createStaticallyKnownType(methodTypeDescriptor);
   public final DexType invocationHandlerType =
       createStaticallyKnownType(invocationHandlerDescriptor);
@@ -737,6 +752,8 @@
   public final DexType unsafeType = createStaticallyKnownType("Lsun/misc/Unsafe;");
   public final DexType desugarVarHandleType =
       createStaticallyKnownType(desugarVarHandleDescriptorString);
+  public final DexType desugarMethodHandlesLookupType =
+      createStaticallyKnownType(desugarMethodHandlesLookupDescriptorString);
 
   public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
   public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
@@ -1092,7 +1109,8 @@
     public final DexMethod put =
         createMethod(androidUtilSparseArrayType, createProto(voidType, intType, objectType), "put");
     public final DexMethod set =
-        createMethod(androidUtilSparseArrayType, createProto(voidType, intType, objectType), "set");
+        createMethod(
+            androidUtilSparseArrayType, createProto(voidType, intType, objectType), setString);
   }
 
   public class BooleanMembers extends BoxedPrimitiveMembers {
@@ -2232,7 +2250,7 @@
 
   public class SupplierMembers extends LibraryMembers {
 
-    public final DexMethod get = createMethod(supplierType, createProto(objectType), "get");
+    public final DexMethod get = createMethod(supplierType, createProto(objectType), getString);
 
     private SupplierMembers() {}
   }
@@ -2248,7 +2266,7 @@
             "compareAndExchange",
             "compareAndExchangeAcquire",
             "compareAndExchangeRelease",
-            "get",
+            getString,
             "getAcquire",
             "getAndAdd",
             "getAndAddAcquire",
@@ -2269,11 +2287,11 @@
             "getVolatile");
 
     private final Set<DexString> varHandleSetMethods =
-        createStrings("set", "setOpaque", "setRelease", "setVolatile");
+        createStrings(setString, "setOpaque", "setRelease", "setVolatile");
 
     private final Set<DexString> varHandleCompareAndSetMethods =
         createStrings(
-            "compareAndSet",
+            compareAndSetString,
             "weakCompareAndSet",
             "weakCompareAndSetAcquire",
             "weakCompareAndSetPlain",
@@ -2298,10 +2316,11 @@
       return result;
     }
 
-    private Set<DexString> createStrings(String... strings) {
+    private Set<DexString> createStrings(Object... strings) {
       IdentityHashMap<DexString, DexString> map = new IdentityHashMap<>();
-      for (String string : strings) {
-        DexString dexString = createString(string);
+      for (Object string : strings) {
+        DexString dexString =
+            string instanceof String ? createString((String) string) : (DexString) string;
         map.put(dexString, dexString);
       }
       return map.keySet();
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index d342780..93e56a7 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
+import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
@@ -28,6 +29,8 @@
   private final ConcurrentHashMap<String, DexString> stringCache = new ConcurrentHashMap<>();
   private final ApplicationReaderMap applicationReaderMap;
   private final Set<DexType> recordWitnesses = Sets.newConcurrentHashSet();
+  private final Set<DexType> varHandleWitnesses = Sets.newConcurrentHashSet();
+  private final Set<DexType> methodHandlesLookupWitnesses = Sets.newConcurrentHashSet();
 
   private boolean hasReadRecordReferenceFromProgramClass = false;
 
@@ -177,4 +180,52 @@
       addRecordWitness(dexMethod.getHolderType(), classKind);
     }
   }
+
+  public void addVarHandleWitness(DexType witness, ClassKind<?> classKind) {
+    if (classKind == ClassKind.PROGRAM) {
+      varHandleWitnesses.add(witness);
+    }
+  }
+
+  public Set<DexType> getVarHandleWitnesses() {
+    return varHandleWitnesses;
+  }
+
+  public void checkFieldForVarHandle(DexField dexField, ClassKind<?> classKind) {
+    if (options.shouldDesugarVarHandle()
+        && VarHandleDesugaring.refersToVarHandle(dexField, getFactory())) {
+      addVarHandleWitness(dexField.getHolderType(), classKind);
+    }
+  }
+
+  public void checkMethodForVarHandle(DexMethod dexMethod, ClassKind<?> classKind) {
+    if (options.shouldDesugarVarHandle()
+        && VarHandleDesugaring.refersToVarHandle(dexMethod, getFactory())) {
+      addVarHandleWitness(dexMethod.getHolderType(), classKind);
+    }
+  }
+
+  public void addMethodHandlesLookupWitness(DexType witness, ClassKind<?> classKind) {
+    if (classKind == ClassKind.PROGRAM) {
+      methodHandlesLookupWitnesses.add(witness);
+    }
+  }
+
+  public Set<DexType> getMethodHandlesLookupWitnesses() {
+    return methodHandlesLookupWitnesses;
+  }
+
+  public void checkFieldForMethodHandlesLookup(DexField dexField, ClassKind<?> classKind) {
+    if (options.shouldDesugarVarHandle()
+        && VarHandleDesugaring.refersToMethodHandlesLookup(dexField, getFactory())) {
+      addMethodHandlesLookupWitness(dexField.getHolderType(), classKind);
+    }
+  }
+
+  public void checkMethodForMethodHandlesLookup(DexMethod dexMethod, ClassKind<?> classKind) {
+    if (options.shouldDesugarVarHandle()
+        && VarHandleDesugaring.refersToMethodHandlesLookup(dexMethod, getFactory())) {
+      addMethodHandlesLookupWitness(dexMethod.getHolderType(), classKind);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 6b9015b..fe8d9e4 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -688,6 +688,8 @@
       FieldAccessFlags flags = createFieldAccessFlags(access);
       DexField dexField = parent.application.getField(parent.type, name, desc);
       parent.application.checkFieldForRecord(dexField, parent.classKind);
+      parent.application.checkFieldForMethodHandlesLookup(dexField, parent.classKind);
+      parent.application.checkFieldForVarHandle(dexField, parent.classKind);
       Wrapper<DexField> signature = FieldSignatureEquivalence.get().wrap(dexField);
       if (parent.fieldSignatures.add(signature)) {
         DexAnnotationSet annotationSet =
@@ -906,6 +908,8 @@
     public void visitEnd() {
       InternalOptions options = parent.application.options;
       parent.application.checkMethodForRecord(method, parent.classKind);
+      parent.application.checkMethodForMethodHandlesLookup(method, parent.classKind);
+      parent.application.checkMethodForVarHandle(method, parent.classKind);
       if (!flags.isAbstract() && !flags.isNative() && classRequiresCode()) {
         code = new LazyCfCode(parent.origin, parent.context, parent.application);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java
index 2e95917..5460c34 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringCollection.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterL8Synthesizer;
 import com.android.tools.r8.ir.desugar.itf.ProgramEmulatedInterfaceSynthesizer;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
+import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -38,6 +39,10 @@
     if (recordRewriter != null) {
       synthesizers.add(recordRewriter);
     }
+    VarHandleDesugaring varHandleDesugaring = VarHandleDesugaring.create(appView);
+    if (varHandleDesugaring != null) {
+      synthesizers.add(varHandleDesugaring);
+    }
     if (synthesizers.isEmpty()) {
       return new EmptyCfClassSynthesizerCollection();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
index 6962d3e..dfaa50d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfClassSynthesizerDesugaringEventConsumer.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.L8ProgramEmulatedInterfaceSynthesizerEventConsumer;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringEventConsumer;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
@@ -17,7 +18,8 @@
     implements L8ProgramEmulatedInterfaceSynthesizerEventConsumer,
         DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer,
         DesugaredLibraryRetargeterL8SynthesizerEventConsumer,
-        RecordDesugaringEventConsumer {
+        RecordDesugaringEventConsumer,
+        VarHandleDesugaringEventConsumer {
 
   private Set<DexProgramClass> synthesizedClasses = Sets.newConcurrentHashSet();
 
@@ -46,6 +48,11 @@
     synthesizedClasses.add(clazz);
   }
 
+  @Override
+  public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
+    synthesizedClasses.add(clazz);
+  }
+
   public Set<DexProgramClass> getSynthesizedClasses() {
     return synthesizedClasses;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index e44467e..df1ba99 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaringEventConsumer.RecordInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.twr.TwrCloseResourceDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringEventConsumer;
 import com.android.tools.r8.shaking.Enqueuer.SyntheticAdditions;
 import com.android.tools.r8.shaking.KeepMethodInfo.Joiner;
 import com.google.common.collect.Sets;
@@ -59,7 +60,8 @@
         DesugaredLibraryRetargeterInstructionEventConsumer,
         DesugaredLibraryAPIConverterEventConsumer,
         ClasspathEmulatedInterfaceSynthesizerEventConsumer,
-        ApiInvokeOutlinerDesugaringEventConsumer {
+        ApiInvokeOutlinerDesugaringEventConsumer,
+        VarHandleDesugaringEventConsumer {
 
   public static D8CfInstructionDesugaringEventConsumer createForD8(
       D8MethodProcessor methodProcessor) {
@@ -152,6 +154,11 @@
     }
 
     @Override
+    public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
+      methodProcessor.scheduleDesugaredMethodsForProcessing(clazz.programMethods());
+    }
+
+    @Override
     public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
       synchronized (synthesizedLambdaClasses) {
         synthesizedLambdaClasses.add(lambdaClass);
@@ -353,6 +360,11 @@
     }
 
     @Override
+    public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
+      // Intentionally empty. The class will be hit by tracing if required.
+    }
+
+    @Override
     public void acceptCollectionConversion(ProgramMethod arrayConversion) {
       // Intentionally empty. The method will be hit by tracing if required.
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 54837a8..20e5a46 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.twr.TwrInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.ListUtils;
@@ -143,6 +144,10 @@
     if (recordRewriter != null) {
       desugarings.add(recordRewriter);
     }
+    VarHandleDesugaring varHandleDesugaring = VarHandleDesugaring.create(appView);
+    if (varHandleDesugaring != null) {
+      desugarings.add(varHandleDesugaring);
+    }
     yieldingDesugarings.add(new UnrepresentableInDexInstructionRemover(appView));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
new file mode 100644
index 0000000..9ed7493
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
@@ -0,0 +1,496 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.varhandle;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.contexts.CompilationContext.ClassSynthesisDesugaringContext;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexApplicationReadFlags;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaring;
+import com.android.tools.r8.ir.desugar.CfClassSynthesizerDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public class VarHandleDesugaring implements CfInstructionDesugaring, CfClassSynthesizerDesugaring {
+
+  private final AppView<?> appView;
+  private final DexItemFactory factory;
+
+  public static VarHandleDesugaring create(AppView<?> appView) {
+    return appView.options().shouldDesugarVarHandle() ? new VarHandleDesugaring(appView) : null;
+  }
+
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    VarHandleDesugaringMethods.registerSynthesizedCodeReferences(factory);
+    factory.createSynthesizedType(DexItemFactory.desugarMethodHandlesLookupDescriptorString);
+  }
+
+  public VarHandleDesugaring(AppView<?> appView) {
+    this.appView = appView;
+    this.factory = appView.dexItemFactory();
+  }
+
+  @Override
+  public void scan(
+      ProgramMethod programMethod, CfInstructionDesugaringEventConsumer eventConsumer) {
+    if (programMethod.getHolderType() == factory.desugarVarHandleType) {
+      return;
+    }
+    CfCode cfCode = programMethod.getDefinition().getCode().asCfCode();
+    for (CfInstruction instruction : cfCode.getInstructions()) {
+      scanInstruction(instruction, eventConsumer, programMethod);
+    }
+  }
+
+  private void scanInstruction(
+      CfInstruction instruction,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context) {
+    assert !instruction.isInitClass();
+    if (instruction.isInvoke()) {
+      CfInvoke cfInvoke = instruction.asInvoke();
+      if (refersToVarHandle(cfInvoke.getMethod(), factory)) {
+        ensureVarHandleClass(eventConsumer, context);
+      }
+      if (refersToMethodHandlesLookup(cfInvoke.getMethod(), factory)) {
+        ensureMethodHandlesLookupClass(eventConsumer, context);
+      }
+      return;
+    }
+  }
+
+  private static boolean refersToVarHandle(DexType type, DexItemFactory factory) {
+    if (type == factory.varHandleType) {
+      // All references to java.lang.invoke.VarHandle is rewritten during application reading.
+      assert false;
+      return true;
+    }
+    return type == factory.desugarVarHandleType;
+  }
+
+  private static boolean refersToVarHandle(DexType[] types, DexItemFactory factory) {
+    for (DexType type : types) {
+      if (refersToVarHandle(type, factory)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public static boolean refersToVarHandle(DexMethod method, DexItemFactory factory) {
+    if (refersToVarHandle(method.holder, factory)) {
+      return true;
+    }
+    return refersToVarHandle(method.proto, factory);
+  }
+
+  private static boolean refersToVarHandle(DexProto proto, DexItemFactory factory) {
+    if (refersToVarHandle(proto.returnType, factory)) {
+      return true;
+    }
+    return refersToVarHandle(proto.parameters.values, factory);
+  }
+
+  public static boolean refersToVarHandle(DexField field, DexItemFactory factory) {
+    if (refersToVarHandle(field.holder, factory)) {
+      assert false : "The VarHandle class has no fields.";
+      return true;
+    }
+    return refersToVarHandle(field.type, factory);
+  }
+
+  private static boolean refersToMethodHandlesLookup(DexType type, DexItemFactory factory) {
+    if (type == factory.methodHandlesLookupType) {
+      // All references to java.lang.invoke.MethodHandles$Lookup is rewritten during application
+      // reading.
+      assert false;
+      return true;
+    }
+    return type == factory.desugarMethodHandlesLookupType;
+  }
+
+  private static boolean refersToMethodHandlesLookup(DexType[] types, DexItemFactory factory) {
+    for (DexType type : types) {
+      if (refersToMethodHandlesLookup(type, factory)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public static boolean refersToMethodHandlesLookup(DexMethod method, DexItemFactory factory) {
+    if (refersToMethodHandlesLookup(method.holder, factory)) {
+      return true;
+    }
+    return refersToMethodHandlesLookup(method.proto, factory);
+  }
+
+  private static boolean refersToMethodHandlesLookup(DexProto proto, DexItemFactory factory) {
+    if (refersToMethodHandlesLookup(proto.returnType, factory)) {
+      return true;
+    }
+    return refersToMethodHandlesLookup(proto.parameters.values, factory);
+  }
+
+  public static boolean refersToMethodHandlesLookup(DexField field, DexItemFactory factory) {
+    if (refersToMethodHandlesLookup(field.holder, factory)) {
+      assert false : "The MethodHandles$Lookup class has no fields.";
+      return true;
+    }
+    return refersToMethodHandlesLookup(field.type, factory);
+  }
+
+  private void ensureMethodHandlesLookupClass(
+      VarHandleDesugaringEventConsumer eventConsumer, Collection<ProgramDefinition> contexts) {
+    appView
+        .getSyntheticItems()
+        .ensureGlobalClass(
+            () -> new MissingGlobalSyntheticsConsumerDiagnostic("VarHandle desugaring"),
+            kinds -> kinds.METHOD_HANDLES_LOOKUP,
+            factory.desugarMethodHandlesLookupType,
+            contexts,
+            appView,
+            builder ->
+                VarHandleDesugaringMethods.generateDesugarMethodHandlesLookupClass(
+                    builder, appView.dexItemFactory()),
+            eventConsumer::acceptVarHandleDesugaringClass);
+  }
+
+  private void ensureMethodHandlesLookupClass(
+      VarHandleDesugaringEventConsumer eventConsumer, ProgramDefinition context) {
+    ensureMethodHandlesLookupClass(eventConsumer, ImmutableList.of(context));
+  }
+
+  private void ensureVarHandleClass(
+      VarHandleDesugaringEventConsumer eventConsumer, Collection<ProgramDefinition> contexts) {
+    appView
+        .getSyntheticItems()
+        .ensureGlobalClass(
+            () -> new MissingGlobalSyntheticsConsumerDiagnostic("VarHandle desugaring"),
+            kinds -> kinds.VAR_HANDLE,
+            factory.desugarVarHandleType,
+            contexts,
+            appView,
+            builder ->
+                VarHandleDesugaringMethods.generateDesugarVarHandleClass(
+                    builder, appView.dexItemFactory()),
+            eventConsumer::acceptVarHandleDesugaringClass);
+  }
+
+  private void ensureVarHandleClass(
+      VarHandleDesugaringEventConsumer eventConsumer, ProgramDefinition context) {
+    ensureVarHandleClass(eventConsumer, ImmutableList.of(context));
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    return computeDescription(instruction, context).needsDesugaring();
+  }
+
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringCollection desugaringCollection,
+      DexItemFactory dexItemFactory) {
+    return computeDescription(instruction, context)
+        .desugarInstruction(
+            freshLocalProvider,
+            localStackAllocator,
+            eventConsumer,
+            context,
+            methodProcessingContext,
+            dexItemFactory);
+  }
+
+  private DesugarDescription computeDescription(CfInstruction instruction, ProgramMethod context) {
+    if (!instruction.isInvoke()) {
+      return DesugarDescription.nothing();
+    }
+    CfInvoke invoke = instruction.asInvoke();
+    DexType holder = invoke.getMethod().getHolderType();
+    if (holder != factory.methodHandlesType
+        && holder != factory.methodHandlesLookupType
+        && holder != factory.desugarVarHandleType) {
+      return DesugarDescription.nothing();
+    }
+    DexMethod method = invoke.getMethod();
+    if (method.getHolderType() == factory.methodHandlesType) {
+      if (method.getName().equals(factory.createString("lookup"))
+          && method.getReturnType() == factory.desugarMethodHandlesLookupType
+          && method.getArity() == 0
+          && invoke.isInvokeStatic()) {
+        return computeMethodHandlesLookup(factory);
+      } else {
+        return DesugarDescription.nothing();
+      }
+    }
+
+    if (method.getHolderType() == factory.methodHandlesLookupType) {
+      assert invoke.isInvokeVirtual();
+
+      if (invoke.getMethod().getReturnType().equals(factory.desugarVarHandleType)) {
+        return computeInvokeMethodHandleLookupMethodReturningVarHandle(factory, invoke);
+      } else {
+        assert invoke.getMethod().getReturnType().equals(factory.methodHandleType);
+        return computeInvokeMethodHandleLookupMethodReturningMethodHandle(factory, invoke);
+      }
+    }
+
+    if (method.getHolderType() == factory.desugarVarHandleType) {
+      assert invoke.isInvokeVirtual();
+      DexString name = method.getName();
+      int arity = method.getProto().getArity();
+      // TODO(b/247076137): Support two coordinates (array element VarHandle).
+      if (name.equals(factory.compareAndSetString)) {
+        assert arity == 3;
+        return computeDesugarSignaturePolymorphicMethod(invoke, arity - 2);
+      } else if (name.equals(factory.getString)) {
+        assert arity == 1;
+        return computeDesugarSignaturePolymorphicMethod(invoke, arity);
+      } else if (name.equals(factory.setString)) {
+        assert arity == 2;
+        return computeDesugarSignaturePolymorphicMethod(invoke, arity - 1);
+      } else {
+        // TODO(b/247076137): Insert runtime exception - unsupported VarHandle operation.
+        return DesugarDescription.nothing();
+      }
+    }
+
+    return DesugarDescription.nothing();
+  }
+
+  public DesugarDescription computeMethodHandlesLookup(DexItemFactory factory) {
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (freshLocalProvider,
+                localStackAllocator,
+                eventConsumer,
+                context,
+                methodProcessingContext,
+                dexItemFactory) ->
+                ImmutableList.of(
+                    new CfNew(factory.desugarMethodHandlesLookupType),
+                    new CfStackInstruction(Opcode.Dup),
+                    new CfInvoke(
+                        Opcodes.INVOKESPECIAL,
+                        factory.createMethod(
+                            factory.desugarMethodHandlesLookupType,
+                            factory.createProto(factory.voidType),
+                            factory.constructorMethodName),
+                        false)))
+        .build();
+  }
+
+  public DesugarDescription computeInvokeMethodHandleLookupMethodReturningVarHandle(
+      DexItemFactory factory, CfInvoke invoke) {
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (freshLocalProvider,
+                localStackAllocator,
+                eventConsumer,
+                context,
+                methodProcessingContext,
+                dexItemFactory) ->
+                ImmutableList.of(
+                    new CfInvoke(
+                        Opcodes.INVOKEVIRTUAL,
+                        factory.createMethod(
+                            factory.desugarMethodHandlesLookupType,
+                            factory.createProto(
+                                factory.desugarVarHandleType,
+                                invoke.getMethod().getProto().getParameters()),
+                            invoke.getMethod().getName()),
+                        false)))
+        .build();
+  }
+
+  public DesugarDescription computeInvokeMethodHandleLookupMethodReturningMethodHandle(
+      DexItemFactory factory, CfInvoke invoke) {
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (freshLocalProvider,
+                localStackAllocator,
+                eventConsumer,
+                context,
+                methodProcessingContext,
+                dexItemFactory) ->
+                ImmutableList.of(
+                    new CfInvoke(
+                        Opcodes.INVOKEVIRTUAL,
+                        factory.createMethod(
+                            factory.desugarMethodHandlesLookupType,
+                            invoke.getMethod().getProto(),
+                            invoke.getMethod().getName()),
+                        false)))
+        .build();
+  }
+
+  public DesugarDescription computeDesugarSignaturePolymorphicMethod(
+      CfInvoke invoke, int coordinates) {
+    return DesugarDescription.builder()
+        .setDesugarRewrite(
+            (freshLocalProvider,
+                localStackAllocator,
+                eventConsumer,
+                context,
+                methodProcessingContext,
+                dexItemFactory) ->
+                desugarSignaturePolymorphicMethod(invoke, coordinates, freshLocalProvider))
+        .build();
+  }
+
+  private boolean isPrimitiveThatIsNotBoxed(DexType type) {
+    return type.isIntType() || type.isLongType();
+  }
+
+  private DexType objectOrPrimitiveReturnType(DexType type) {
+    return type.isPrimitiveType() || type.isVoidType() ? type : factory.objectType;
+  }
+
+  private DexType objectOrPrimitiveParameterType(DexType type) {
+    return isPrimitiveThatIsNotBoxed(type) || type.isVoidType() ? type : factory.objectType;
+  }
+
+  private Collection<CfInstruction> desugarSignaturePolymorphicMethod(
+      CfInvoke invoke, int coordinates, FreshLocalProvider freshLocalProvider) {
+    assert invoke.isInvokeVirtual();
+    // TODO(b/247076137): Support two coordinates (array element VarHandle).
+    assert coordinates == 1 && invoke.getMethod().getProto().getArity() >= coordinates;
+    // Only support zero, one and two arguments after coordinates.
+    int nonCoordinateArguments = invoke.getMethod().getProto().getArity() - coordinates;
+    assert nonCoordinateArguments <= 2;
+
+    DexProto proto = invoke.getMethod().getProto();
+    DexType ct1Type = invoke.getMethod().getProto().getParameter(0);
+    if (!ct1Type.isClassType()) {
+      return null;
+    }
+
+    // Convert the arguments by boxing except for primitive int and long.
+    ImmutableList.Builder<CfInstruction> builder = ImmutableList.builder();
+    List<DexType> newParameters = new ArrayList<>(proto.parameters.size());
+    newParameters.add(factory.objectType);
+    if (nonCoordinateArguments > 0) {
+      DexType argumentType = objectOrPrimitiveParameterType(proto.parameters.get(coordinates));
+      boolean hasWideArgument = false;
+      for (int i = coordinates; i < proto.parameters.size(); i++) {
+        hasWideArgument = hasWideArgument || proto.parameters.get(i).isWideType();
+        DexType type = objectOrPrimitiveParameterType(proto.parameters.get(i));
+        if (type != argumentType) {
+          argumentType = factory.objectType;
+        }
+      }
+      assert isPrimitiveThatIsNotBoxed(argumentType) || argumentType == factory.objectType;
+      // Ensure all arguments are boxed.
+      for (int i = coordinates; i < proto.parameters.size(); i++) {
+        if (argumentType.isPrimitiveType()) {
+          newParameters.add(argumentType);
+        } else {
+          boolean lastArgument = i == proto.parameters.size() - 1;
+          // Pass all boxed objects as Object.
+          newParameters.add(factory.objectType);
+          if (!proto.parameters.get(i).isPrimitiveType()) {
+            continue;
+          }
+          int local = -1;
+          // For boxing of the second to last argument (we only have one or two) bring it to TOS.
+          if (!lastArgument) {
+            if (hasWideArgument) {
+              local = freshLocalProvider.getFreshLocal(2);
+              builder.add(new CfStore(ValueType.fromDexType(proto.parameters.get(i + 1)), local));
+            } else {
+              builder.add(new CfStackInstruction(Opcode.Swap));
+            }
+          }
+          builder.add(
+              new CfInvoke(
+                  Opcodes.INVOKESTATIC,
+                  factory.getBoxPrimitiveMethod(proto.parameters.get(i)),
+                  false));
+          // When boxing of the second to last argument (we only have one or two) bring last
+          // argument back to TOS.
+          if (!lastArgument) {
+            if (hasWideArgument) {
+              assert local != -1;
+              builder.add(new CfLoad(ValueType.fromDexType(proto.parameters.get(i + 1)), local));
+            } else {
+              builder.add(new CfStackInstruction(Opcode.Swap));
+            }
+          }
+        }
+      }
+    }
+    assert newParameters.size() == proto.parameters.size();
+    // TODO(b/247076137): Also convert return type if reference type and not Object?.
+    DexProto newProto =
+        factory.createProto(objectOrPrimitiveReturnType(proto.returnType), newParameters);
+    DexMethod newMethod =
+        factory.createMethod(factory.desugarVarHandleType, newProto, invoke.getMethod().getName());
+    builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newMethod, false));
+    if (proto.returnType.isClassType()
+        && proto.returnType != factory.objectType
+        && proto.returnType != factory.voidType) {
+      builder.add(new CfCheckCast(proto.returnType));
+    }
+    return builder.build();
+  }
+
+  @Override
+  public String uniqueIdentifier() {
+    return "$varhandle";
+  }
+
+  @Override
+  // TODO(b/247076137): Is synthesizeClasses needed? Can DesugarVarHandle be created during
+  //  desugaring instead?
+  public void synthesizeClasses(
+      ClassSynthesisDesugaringContext processingContext,
+      CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
+    DexApplicationReadFlags flags = appView.appInfo().app().getFlags();
+    if (flags.hasReadVarHandleReferenceFromProgramClass()) {
+      List<ProgramDefinition> classes = new ArrayList<>();
+      for (DexType varHandleWitness : flags.getVarHandleWitnesses()) {
+        DexClass dexClass = appView.contextIndependentDefinitionFor(varHandleWitness);
+        assert dexClass != null;
+        assert dexClass.isProgramClass();
+        classes.add(dexClass.asProgramClass());
+      }
+      ensureVarHandleClass(eventConsumer, classes);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringEventConsumer.java
new file mode 100644
index 0000000..8251809
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringEventConsumer.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.varhandle;
+
+import com.android.tools.r8.graph.DexProgramClass;
+
+public interface VarHandleDesugaringEventConsumer {
+
+  void acceptVarHandleDesugaringClass(DexProgramClass varHandleClass);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
index 2daf329..78f3611 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
@@ -13,16 +13,25 @@
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.cf.code.CfInstanceFieldRead;
 import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
+import com.android.tools.r8.cf.code.CfInstanceOf;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNumberConversion;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStaticFieldRead;
 import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.frame.FrameType;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -31,14 +40,22 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
 import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
 
 public final class VarHandleDesugaringMethods {
 
   public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
     factory.createSynthesizedType("Lcom/android/tools/r8/DesugarVarHandle;");
+    factory.createSynthesizedType("Ljava/lang/Byte;");
+    factory.createSynthesizedType("Ljava/lang/Integer;");
+    factory.createSynthesizedType("Ljava/lang/Long;");
+    factory.createSynthesizedType("Ljava/lang/RuntimeException;");
+    factory.createSynthesizedType("Ljava/lang/Short;");
     factory.createSynthesizedType("Ljava/lang/reflect/Field;");
     factory.createSynthesizedType("Lsun/misc/Unsafe;");
   }
@@ -81,43 +98,33 @@
                 .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
                 .disableAndroidApiLevelCheck()
                 .build()));
-    DexMethod set =
-        factory.createMethod(
-            builder.getType(),
-            factory.createProto(factory.voidType, factory.objectType, factory.objectType),
-            factory.createString("set"));
     DexMethod get =
         factory.createMethod(
             builder.getType(),
             factory.createProto(factory.objectType, factory.objectType),
             factory.createString("get"));
-    DexMethod compareAndSetInt =
-        factory.createMethod(
-            builder.getType(),
-            factory.createProto(
-                factory.booleanType, factory.objectType, factory.intType, factory.intType),
-            factory.createString("compareAndSet"));
-    DexMethod getInt =
-        factory.createMethod(
-            builder.getType(),
-            factory.createProto(factory.intType, factory.objectType),
-            factory.createString("get"));
     DexMethod compareAndSet =
         factory.createMethod(
             builder.getType(),
             factory.createProto(
                 factory.booleanType, factory.objectType, factory.objectType, factory.objectType),
             factory.createString("compareAndSet"));
+    DexMethod constructor_1 =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(
+                factory.voidType, factory.createType(factory.createString("Ljava/lang/Class;"))),
+            factory.createString("<init>"));
     DexMethod setInt =
         factory.createMethod(
             builder.getType(),
             factory.createProto(factory.voidType, factory.objectType, factory.intType),
             factory.createString("set"));
-    DexMethod getLong =
+    DexMethod toLongIfPossible =
         factory.createMethod(
             builder.getType(),
             factory.createProto(factory.longType, factory.objectType),
-            factory.createString("get"));
+            factory.createString("toLongIfPossible"));
     DexMethod constructor_3 =
         factory.createMethod(
             builder.getType(),
@@ -138,9 +145,49 @@
             factory.createProto(
                 factory.booleanType, factory.objectType, factory.longType, factory.longType),
             factory.createString("compareAndSet"));
+    DexMethod set =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.voidType, factory.objectType, factory.objectType),
+            factory.createString("set"));
+    DexMethod compareAndSetInt =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(
+                factory.booleanType, factory.objectType, factory.intType, factory.intType),
+            factory.createString("compareAndSet"));
+    DexMethod desugarWrongMethodTypeException =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(
+                factory.createType(factory.createString("Ljava/lang/RuntimeException;"))),
+            factory.createString("desugarWrongMethodTypeException"));
+    DexMethod getInt =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.intType, factory.objectType),
+            factory.createString("get"));
+    DexMethod toIntIfPossible =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.intType, factory.objectType),
+            factory.createString("toIntIfPossible"));
+    DexMethod getLong =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.longType, factory.objectType),
+            factory.createString("get"));
     builder.setDirectMethods(
         ImmutableList.of(
             DexEncodedMethod.syntheticBuilder()
+                .setMethod(constructor_1)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true))
+                .setCode(DesugarVarHandle_constructor_1(factory, constructor_1))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
                 .setMethod(constructor_3)
                 .setAccessFlags(
                     MethodAccessFlags.fromSharedAccessFlags(
@@ -151,14 +198,6 @@
     builder.setVirtualMethods(
         ImmutableList.of(
             DexEncodedMethod.syntheticBuilder()
-                .setMethod(set)
-                .setAccessFlags(
-                    MethodAccessFlags.fromSharedAccessFlags(
-                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
-                .setCode(DesugarVarHandle_set(factory, set))
-                .disableAndroidApiLevelCheck()
-                .build(),
-            DexEncodedMethod.syntheticBuilder()
                 .setMethod(get)
                 .setAccessFlags(
                     MethodAccessFlags.fromSharedAccessFlags(
@@ -167,22 +206,6 @@
                 .disableAndroidApiLevelCheck()
                 .build(),
             DexEncodedMethod.syntheticBuilder()
-                .setMethod(compareAndSetInt)
-                .setAccessFlags(
-                    MethodAccessFlags.fromSharedAccessFlags(
-                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
-                .setCode(DesugarVarHandle_compareAndSetInt(factory, compareAndSetInt))
-                .disableAndroidApiLevelCheck()
-                .build(),
-            DexEncodedMethod.syntheticBuilder()
-                .setMethod(getInt)
-                .setAccessFlags(
-                    MethodAccessFlags.fromSharedAccessFlags(
-                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
-                .setCode(DesugarVarHandle_getInt(factory, getInt))
-                .disableAndroidApiLevelCheck()
-                .build(),
-            DexEncodedMethod.syntheticBuilder()
                 .setMethod(compareAndSet)
                 .setAccessFlags(
                     MethodAccessFlags.fromSharedAccessFlags(
@@ -199,11 +222,11 @@
                 .disableAndroidApiLevelCheck()
                 .build(),
             DexEncodedMethod.syntheticBuilder()
-                .setMethod(getLong)
+                .setMethod(toLongIfPossible)
                 .setAccessFlags(
                     MethodAccessFlags.fromSharedAccessFlags(
                         Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
-                .setCode(DesugarVarHandle_getLong(factory, getLong))
+                .setCode(DesugarVarHandle_toLongIfPossible(factory, toLongIfPossible))
                 .disableAndroidApiLevelCheck()
                 .build(),
             DexEncodedMethod.syntheticBuilder()
@@ -221,6 +244,56 @@
                         Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
                 .setCode(DesugarVarHandle_compareAndSetLong(factory, compareAndSetLong))
                 .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(set)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_set(factory, set))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(compareAndSetInt)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_compareAndSetInt(factory, compareAndSetInt))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(desugarWrongMethodTypeException)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(
+                    DesugarVarHandle_desugarWrongMethodTypeException(
+                        factory, desugarWrongMethodTypeException))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(getInt)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_getInt(factory, getInt))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(toIntIfPossible)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_toIntIfPossible(factory, toIntIfPossible))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(getLong)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_getLong(factory, getLong))
+                .disableAndroidApiLevelCheck()
                 .build()));
   }
 
@@ -316,6 +389,126 @@
         ImmutableList.of());
   }
 
+  public static CfCode DesugarVarHandle_constructor_1(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        3,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.objectType,
+                    factory.createProto(factory.voidType),
+                    factory.createString("<init>")),
+                false),
+            label1,
+            new CfConstClass(factory.createType("Lsun/misc/Unsafe;")),
+            new CfConstString(factory.createString("theUnsafe")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.classType,
+                    factory.createProto(
+                        factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+                    factory.createString("getDeclaredField")),
+                false),
+            new CfStore(ValueType.OBJECT, 2),
+            label2,
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfConstNumber(1, ValueType.INT),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/reflect/Field;"),
+                    factory.createProto(factory.voidType, factory.booleanType),
+                    factory.createString("setAccessible")),
+                false),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfConstNull(),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/reflect/Field;"),
+                    factory.createProto(factory.objectType, factory.objectType),
+                    factory.createString("get")),
+                false),
+            new CfCheckCast(factory.createType("Lsun/misc/Unsafe;")),
+            new CfInstanceFieldWrite(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            label4,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInstanceFieldWrite(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("recv"))),
+            label5,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.classType,
+                    factory.createProto(factory.classType),
+                    factory.createString("getComponentType")),
+                false),
+            new CfInstanceFieldWrite(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            label6,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("recv"))),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(factory.intType, factory.classType),
+                    factory.createString("arrayBaseOffset")),
+                false),
+            new CfNumberConversion(NumericType.INT, NumericType.LONG),
+            new CfInstanceFieldWrite(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            label7,
+            new CfReturnVoid(),
+            label8),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode DesugarVarHandle_constructor_3(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -456,12 +649,189 @@
   public static CfCode DesugarVarHandle_compareAndSet(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
     return new CfCode(
         method.holder,
-        1,
+        8,
         4,
         ImmutableList.of(
-            label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 2),
+            label2,
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.intType, factory.objectType),
+                    factory.createString("toIntIfPossible")),
+                false),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.intType, factory.objectType),
+                    factory.createString("toIntIfPossible")),
+                false),
+            label3,
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.booleanType,
+                        factory.objectType,
+                        factory.longType,
+                        factory.intType,
+                        factory.intType),
+                    factory.createString("compareAndSwapInt")),
+                false),
+            new CfReturn(ValueType.INT),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label8),
+            label5,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 2),
+            label6,
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.longType, factory.objectType),
+                    factory.createString("toLongIfPossible")),
+                false),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.longType, factory.objectType),
+                    factory.createString("toLongIfPossible")),
+                false),
+            label7,
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.booleanType,
+                        factory.objectType,
+                        factory.longType,
+                        factory.longType,
+                        factory.longType),
+                    factory.createString("compareAndSwapLong")),
+                false),
+            new CfReturn(ValueType.INT),
+            label8,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.booleanType,
+                        factory.objectType,
+                        factory.longType,
+                        factory.objectType,
+                        factory.objectType),
+                    factory.createString("compareAndSwapObject")),
+                false),
+            new CfReturn(ValueType.INT),
+            label9),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -469,12 +839,153 @@
   public static CfCode DesugarVarHandle_compareAndSetInt(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
     return new CfCode(
         method.holder,
-        1,
+        8,
         4,
         ImmutableList.of(
-            label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.booleanType,
+                        factory.objectType,
+                        factory.longType,
+                        factory.intType,
+                        factory.intType),
+                    factory.createString("compareAndSwapInt")),
+                false),
+            new CfReturn(ValueType.INT),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType(),
+                      FrameType.intType()
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.INT, 2),
+            new CfNumberConversion(NumericType.INT, NumericType.LONG),
+            new CfLoad(ValueType.INT, 3),
+            new CfNumberConversion(NumericType.INT, NumericType.LONG),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.booleanType,
+                        factory.objectType,
+                        factory.longType,
+                        factory.longType,
+                        factory.longType),
+                    factory.createString("compareAndSwapLong")),
+                false),
+            new CfReturn(ValueType.INT),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType(),
+                      FrameType.intType()
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.createProto(factory.createType("Ljava/lang/Integer;"), factory.intType),
+                    factory.createString("valueOf")),
+                false),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.createProto(factory.createType("Ljava/lang/Integer;"), factory.intType),
+                    factory.createString("valueOf")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(
+                        factory.booleanType,
+                        factory.objectType,
+                        factory.objectType,
+                        factory.objectType),
+                    factory.createString("compareAndSet")),
+                false),
+            new CfReturn(ValueType.INT),
+            label5),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -483,12 +994,125 @@
       DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
     return new CfCode(
         method.holder,
-        1,
+        8,
         6,
         ImmutableList.of(
-            label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.LONG, 2),
+            new CfLoad(ValueType.LONG, 4),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.booleanType,
+                        factory.objectType,
+                        factory.longType,
+                        factory.longType,
+                        factory.longType),
+                    factory.createString("compareAndSwapLong")),
+                false),
+            new CfReturn(ValueType.INT),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.longType(),
+                      FrameType.longHighType(),
+                      FrameType.longType(),
+                      FrameType.longHighType()
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.LONG, 2),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.createProto(factory.createType("Ljava/lang/Long;"), factory.longType),
+                    factory.createString("valueOf")),
+                false),
+            new CfLoad(ValueType.LONG, 4),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.createProto(factory.createType("Ljava/lang/Long;"), factory.longType),
+                    factory.createString("valueOf")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(
+                        factory.booleanType,
+                        factory.objectType,
+                        factory.objectType,
+                        factory.objectType),
+                    factory.createString("compareAndSet")),
+                false),
+            new CfReturn(ValueType.INT),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_desugarWrongMethodTypeException(
+      DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        3,
+        1,
+        ImmutableList.of(
+            label0,
+            new CfNew(factory.createType("Ljava/lang/RuntimeException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstString(factory.createString("java.lang.invoke.WrongMethodTypeException")),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/RuntimeException;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("<init>")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label1),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -496,11 +1120,138 @@
   public static CfCode DesugarVarHandle_get(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
     return new CfCode(
         method.holder,
-        1,
+        4,
         2,
-        ImmutableList.of(label0, new CfConstNull(), new CfReturn(ValueType.OBJECT), label1),
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(factory.intType, factory.objectType, factory.longType),
+                    factory.createString("getInt")),
+                false),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.createProto(factory.createType("Ljava/lang/Integer;"), factory.intType),
+                    factory.createString("valueOf")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(factory.longType, factory.objectType, factory.longType),
+                    factory.createString("getLong")),
+                false),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.createProto(factory.createType("Ljava/lang/Long;"), factory.longType),
+                    factory.createString("valueOf")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(factory.objectType, factory.objectType, factory.longType),
+                    factory.createString("getObject")),
+                false),
+            new CfReturn(ValueType.OBJECT),
+            label5),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -510,10 +1261,32 @@
     CfLabel label1 = new CfLabel();
     return new CfCode(
         method.holder,
-        1,
+        4,
         2,
         ImmutableList.of(
-            label0, new CfConstNumber(-1, ValueType.INT), new CfReturn(ValueType.INT), label1),
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(factory.intType, factory.objectType, factory.longType),
+                    factory.createString("getInt")),
+                false),
+            new CfReturn(ValueType.INT),
+            label1),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -534,11 +1307,91 @@
   public static CfCode DesugarVarHandle_set(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
     return new CfCode(
         method.holder,
-        0,
+        5,
         3,
-        ImmutableList.of(label0, new CfReturnVoid(), label1),
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.intType, factory.objectType),
+                    factory.createString("toIntIfPossible")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.voidType, factory.objectType, factory.intType),
+                    factory.createString("set")),
+                false),
+            new CfGoto(label3),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.voidType, factory.objectType, factory.longType, factory.objectType),
+                    factory.createString("putObject")),
+                false),
+            label3,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfReturnVoid(),
+            label4),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -546,11 +1399,139 @@
   public static CfCode DesugarVarHandle_setInt(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
     return new CfCode(
         method.holder,
-        0,
+        6,
         3,
-        ImmutableList.of(label0, new CfReturnVoid(), label1),
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.voidType, factory.objectType, factory.longType, factory.intType),
+                    factory.createString("putInt")),
+                false),
+            new CfGoto(label5),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType()
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4),
+            label3,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.INT, 2),
+            new CfNumberConversion(NumericType.INT, NumericType.LONG),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.voidType, factory.objectType, factory.longType, factory.longType),
+                    factory.createString("putLong")),
+                false),
+            new CfGoto(label5),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType()
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.createProto(factory.createType("Ljava/lang/Integer;"), factory.intType),
+                    factory.createString("valueOf")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.voidType, factory.objectType, factory.objectType),
+                    factory.createString("set")),
+                false),
+            label5,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.intType()
+                    })),
+            new CfReturnVoid(),
+            label6),
         ImmutableList.of(),
         ImmutableList.of());
   }
@@ -558,11 +1539,257 @@
   public static CfCode DesugarVarHandle_setLong(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
     return new CfCode(
         method.holder,
-        0,
+        6,
         4,
-        ImmutableList.of(label0, new CfReturnVoid(), label1),
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            new CfStaticFieldRead(
+                factory.createField(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.classType,
+                    factory.createString("TYPE"))),
+            new CfIfCmp(If.Type.NE, ValueType.OBJECT, label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createString("U"))),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInstanceFieldRead(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            new CfLoad(ValueType.LONG, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.voidType, factory.objectType, factory.longType, factory.longType),
+                    factory.createString("putLong")),
+                false),
+            new CfGoto(label3),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.longType(),
+                      FrameType.longHighType()
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.createType("Ljava/lang/RuntimeException;")),
+                    factory.createString("desugarWrongMethodTypeException")),
+                false),
+            new CfThrow(),
+            label3,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType),
+                      FrameType.longType(),
+                      FrameType.longHighType()
+                    })),
+            new CfReturnVoid(),
+            label4),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_toIntIfPossible(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    CfLabel label8 = new CfLabel();
+    CfLabel label9 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInstanceOf(factory.createType("Ljava/lang/Integer;")),
+            new CfIf(If.Type.EQ, ValueType.INT, label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfCheckCast(factory.createType("Ljava/lang/Integer;")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Integer;"),
+                    factory.createProto(factory.intType),
+                    factory.createString("intValue")),
+                false),
+            new CfReturn(ValueType.INT),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInstanceOf(factory.createType("Ljava/lang/Byte;")),
+            new CfIf(If.Type.EQ, ValueType.INT, label4),
+            label3,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfCheckCast(factory.createType("Ljava/lang/Byte;")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Byte;"),
+                    factory.createProto(factory.byteType),
+                    factory.createString("byteValue")),
+                false),
+            new CfReturn(ValueType.INT),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInstanceOf(factory.boxedCharType),
+            new CfIf(If.Type.EQ, ValueType.INT, label6),
+            label5,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfCheckCast(factory.boxedCharType),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.boxedCharType,
+                    factory.createProto(factory.charType),
+                    factory.createString("charValue")),
+                false),
+            new CfReturn(ValueType.INT),
+            label6,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInstanceOf(factory.createType("Ljava/lang/Short;")),
+            new CfIf(If.Type.EQ, ValueType.INT, label8),
+            label7,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfCheckCast(factory.createType("Ljava/lang/Short;")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Short;"),
+                    factory.createProto(factory.shortType),
+                    factory.createString("shortValue")),
+                false),
+            new CfReturn(ValueType.INT),
+            label8,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.createType("Ljava/lang/RuntimeException;")),
+                    factory.createString("desugarWrongMethodTypeException")),
+                false),
+            new CfThrow(),
+            label9),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_toLongIfPossible(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInstanceOf(factory.createType("Ljava/lang/Long;")),
+            new CfIf(If.Type.EQ, ValueType.INT, label2),
+            label1,
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfCheckCast(factory.createType("Ljava/lang/Long;")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Long;"),
+                    factory.createProto(factory.longType),
+                    factory.createString("longValue")),
+                false),
+            new CfReturn(ValueType.LONG),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1},
+                    new FrameType[] {
+                      FrameType.initializedNonNullReference(
+                          factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")),
+                      FrameType.initializedNonNullReference(factory.objectType)
+                    })),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.createProto(factory.intType, factory.objectType),
+                    factory.createString("toIntIfPossible")),
+                false),
+            new CfNumberConversion(NumericType.INT, NumericType.LONG),
+            new CfReturn(ValueType.LONG),
+            label3),
         ImmutableList.of(),
         ImmutableList.of());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 5002bac..a2cd8e8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -2461,7 +2461,9 @@
       Set<BasicBlock> visitedBlocks = Sets.newIdentityHashSet();
       for (Instruction instruction : instructionsToRemove) {
         BasicBlock ownerBlock = instruction.getBlock();
-        if (visitedBlocks.add(ownerBlock)) {
+        // If owner block is null, then the instruction has been removed already. We can't rely on
+        // just having the block pointer nulled, so the visited blocks guards reprocessing.
+        if (ownerBlock != null && visitedBlocks.add(ownerBlock)) {
           InstructionListIterator removeIt = ownerBlock.listIterator(code);
           while (removeIt.hasNext()) {
             if (instructionsToRemove.contains(removeIt.next())) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 54ef01b..5e95467 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -354,7 +354,7 @@
           assert !prefix.contains(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
           DexType context =
               dexItemFactory.createType(DescriptorUtils.getDescriptorFromClassBinaryName(prefix));
-          assert isNotSyntheticType(context);
+          assert isNotSyntheticType(context) || context == dexItemFactory.desugarVarHandleType;
         });
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 2aa20c2..59aff72 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -27,6 +27,8 @@
   // Global synthetics.
   public final SyntheticKind RECORD_TAG = generator.forGlobalClass();
   public final SyntheticKind API_MODEL_STUB = generator.forGlobalClass();
+  public final SyntheticKind METHOD_HANDLES_LOOKUP = generator.forGlobalClass();
+  public final SyntheticKind VAR_HANDLE = generator.forGlobalClass();
 
   // Classpath only synthetics in the global type namespace.
   public final SyntheticKind GENERIC_API_CONVERSION_STUB = generator.forGlobalClasspathClass();
@@ -438,7 +440,8 @@
   }
 
   public static boolean verifyNotInternalSynthetic(String typeBinaryNameOrDescriptor) {
-    assert !typeBinaryNameOrDescriptor.contains(INTERNAL_SYNTHETIC_CLASS_SEPARATOR);
+    assert !typeBinaryNameOrDescriptor.contains(INTERNAL_SYNTHETIC_CLASS_SEPARATOR)
+        : typeBinaryNameOrDescriptor;
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index acb142b..d98f497 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -95,6 +95,10 @@
     return internalToDescriptor(typeName, false, false);
   }
 
+  public static String javaClassToDescriptor(Class<?> clazz) {
+    return javaTypeToDescriptor(clazz.getTypeName());
+  }
+
   /**
    * Convert a Java type name to a descriptor string ignoring primitive types.
    *
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 4f8669b..38e6b3f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -601,6 +601,10 @@
     return desugarState.isOn() && !canUseRecords();
   }
 
+  public boolean shouldDesugarVarHandle() {
+    return desugarState.isOn() && !canUseVarHandle() && enableVarHandleDesugaring;
+  }
+
   public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter();
   public Set<String> extensiveInterfaceMethodMinifierLoggingFilter =
       getExtensiveInterfaceMethodMinifierLoggingFilter();
@@ -622,6 +626,8 @@
   public boolean enableLoadStoreOptimization = true;
   // Flag to turn on/off desugaring in D8/R8.
   public DesugarState desugarState = DesugarState.ON;
+  // Flag to turn on/off partial VarHandle desugaring.
+  public boolean enableVarHandleDesugaring = false;
   // Flag to turn on/off backport methods.
   public boolean enableBackportMethods = true;
   // Flag to turn on/off reduction of nest to improve class merging optimizations.
@@ -2351,6 +2357,14 @@
     return hasFeaturePresentFrom(privateInterfaceMethodsApiLevel());
   }
 
+  public static AndroidApiLevel varHandleApiLevel() {
+    return AndroidApiLevel.T;
+  }
+
+  public boolean canUseVarHandle() {
+    return hasFeaturePresentFrom(varHandleApiLevel());
+  }
+
   public boolean canUseNestBasedAccess() {
     return hasFeaturePresentFrom(null) || emitNestAnnotationsInDex;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 69cef74..a5b9936 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -12,6 +12,8 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -375,6 +377,13 @@
     return string.length();
   }
 
+  public static String replaceAll(String subject, Map<String, String> map) {
+    for (Entry<String, String> entry : map.entrySet()) {
+      subject = replaceAll(subject, entry.getKey(), entry.getValue());
+    }
+    return subject;
+  }
+
   public static String replaceAll(String subject, String target, String replacement) {
     return subject.replaceAll(Pattern.quote(target), Matcher.quoteReplacement(replacement));
   }
diff --git a/src/test/examplesJava9/varhandle/VarHandleTests.java b/src/test/examplesJava9/varhandle/VarHandleTests.java
index 624843e..c1af388 100644
--- a/src/test/examplesJava9/varhandle/VarHandleTests.java
+++ b/src/test/examplesJava9/varhandle/VarHandleTests.java
@@ -3,16 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package varhandle;
 
-import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
 import java.lang.invoke.VarHandle;
 
 public class VarHandleTests {
 
   private static boolean staticField = true;
 
-  public static void test1() throws NoSuchFieldException, IllegalAccessException {
+  public static void test() throws NoSuchFieldException, IllegalAccessException {
     VarHandle vb = MethodHandles.lookup()
         .findStaticVarHandle(VarHandleTests.class, "staticField", boolean.class);
     System.out.println((boolean) vb.get());
@@ -21,6 +19,6 @@
   }
 
   public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
-    VarHandleTests.test1();
+    VarHandleTests.test();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
index 4d15a0a..9ae8ecd 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
@@ -84,4 +84,9 @@
   protected String getExpectedOutput() {
     return EXPECTED_OUTPUT;
   }
+
+  @Override
+  protected boolean getTestWithDesugaring() {
+    return true;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
index 8a5bb2c..6a6c9d5 100644
--- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
+++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
@@ -32,7 +32,9 @@
   public static TestParametersCollection data() {
     return getTestParameters()
         .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
-        .withDexRuntimes()
+        // Running on 4.0.4 and 4.4.4 needs to be checked. Output seems correct, but at the
+        // same time there are VFY errors on stderr.
+        .withDexRuntimesStartingFromExcluding(Version.V4_4_4)
         .withAllApiLevels()
         .build();
   }
@@ -47,6 +49,11 @@
 
   protected abstract String getExpectedOutput();
 
+  // TODO(b/247076137): Remove this when all tests can run with desugaring.
+  protected boolean getTestWithDesugaring() {
+    return false;
+  }
+
   @Test
   public void testReference() throws Throwable {
     assumeTrue(parameters.isCfRuntime());
@@ -59,34 +66,45 @@
   @Test
   public void testD8() throws Throwable {
     assumeTrue(parameters.isDexRuntime());
-    testForD8(parameters.getBackend())
-        // Use android.jar from Android T to get the VarHandle type. This is not strictly needed
-        // to D8 as it does not fail on missing types.
-        // TODO(b/247076137): With desugaring removing VarHandle the type should not be needed in
-        //  the library and any android.jar should work.
-        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
-        .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
-        .setMinApi(parameters.getApiLevel())
-        .run(parameters.getRuntime(), getMainClass())
-        // TODO(b/247076137): Test should pass on all platforms with desugaring implemented.
-        .applyIf(
-            // VarHandle is available from Android 9, even though it was not a public API until 13.
-            parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V7_0_0),
-            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
-            parameters.getApiLevel().isLessThan(AndroidApiLevel.P)
-                || parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V8_1_0),
-            r -> r.assertFailure(),
-            r -> r.assertSuccessWithOutput(getExpectedOutput()));
+    if (getTestWithDesugaring()) {
+      testForD8(parameters.getBackend())
+          .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
+          .setMinApi(parameters.getApiLevel())
+          .addOptionsModification(options -> options.enableVarHandleDesugaring = true)
+          .run(parameters.getRuntime(), getMainClass())
+          .applyIf(
+              parameters.isDexRuntime()
+                  && parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V4_4_4),
+              // TODO(sgjesse): Running on 4.0.4 and 4.4.4 needs to be checked. Output seems
+              // correct,
+              //  but at the same time there are VFY errors on stderr.
+              r -> r.assertFailureWithErrorThatThrows(NoSuchFieldException.class),
+              r -> r.assertSuccessWithOutput(getExpectedOutput()));
+    } else {
+      testForD8(parameters.getBackend())
+          .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
+          .setMinApi(parameters.getApiLevel())
+          .run(parameters.getRuntime(), getMainClass())
+          .applyIf(
+              // VarHandle is available from Android 9, even though it was not a public API until
+              // 13.
+              parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V7_0_0),
+              r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+              parameters.getApiLevel().isLessThan(AndroidApiLevel.P)
+                  || parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V8_1_0),
+              r -> r.assertFailure(),
+              r -> r.assertSuccessWithOutput(getExpectedOutput()));
+    }
   }
 
+  // TODO(b/247076137: Also turn on VarHandle desugaring for R8 tests.
   @Test
   public void testR8() throws Throwable {
     testForR8(parameters.getBackend())
         // Use android.jar from Android T to get the VarHandle type.
-        // TODO(b/247076137): With desugaring removing VarHandle the type should not be needed in
-        //  the library and any android.jar should work.
         .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
         .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry()))
+        .addLibraryFiles()
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(getMainClass())
         .addKeepRules(getKeepRules())
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 0cd6b23..f87ec27 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -358,5 +358,4 @@
       printer.println("}");
     }
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
index 7e88943..57c13dd 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
@@ -15,6 +15,55 @@
     public long objectFieldOffset(Field f) {
       throw new RuntimeException("Stub called.");
     }
+
+    public boolean compareAndSwapInt(Object obj, long offset, int expectedValue, int newValue) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public boolean compareAndSwapLong(Object obj, long offset, long expectedValue, long newValue) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public boolean compareAndSwapObject(
+        Object receiver, long offset, Object expect, Object update) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public int getInt(Object obj, long offset) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public void putInt(Object obj, long offset, int newValue) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public long getLong(Object obj, long offset) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public void putLong(Object obj, long offset, long newValue) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public Object getObject(Object receiver, long offset) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public void putObject(Object obj, long offset, Object newValue) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public int getIntVolatile(Object obj, long offset) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public int arrayBaseOffset(Class<?> clazz) {
+      throw new RuntimeException("Stub called.");
+    }
+
+    public int arrayIndexScale(Class<?> clazz) {
+      throw new RuntimeException("Stub called.");
+    }
   }
 
   private final UnsafeStub U;
@@ -33,15 +82,56 @@
     this.offset = U.objectFieldOffset(recv.getDeclaredField(name));
   }
 
+  DesugarVarHandle(Class<?> arrayType) throws Exception {
+    Field theUnsafe = UnsafeStub.class.getDeclaredField("theUnsafe");
+    theUnsafe.setAccessible(true);
+    U = (UnsafeStub) theUnsafe.get(null);
+    this.recv = arrayType;
+    this.type = arrayType.getComponentType();
+    this.offset = U.arrayBaseOffset(recv);
+  }
+
+  // Helpers.
+  RuntimeException desugarWrongMethodTypeException() {
+    return new RuntimeException("java.lang.invoke.WrongMethodTypeException");
+  }
+
+  int toIntIfPossible(Object value) {
+    if (value instanceof Integer) {
+      return (Integer) value;
+    }
+    if (value instanceof Byte) {
+      return (Byte) value;
+    }
+    if (value instanceof Character) {
+      return (Character) value;
+    }
+    if (value instanceof Short) {
+      return (Short) value;
+    }
+    throw desugarWrongMethodTypeException();
+  }
+
+  long toLongIfPossible(Object value) {
+    if (value instanceof Long) {
+      return (Long) value;
+    }
+    return toIntIfPossible(value);
+  }
+
   // get variants.
   Object get(Object ct1) {
-    // TODO(b/247076137): Implement.
-    return null;
+    if (type == int.class) {
+      return U.getInt(ct1, offset);
+    }
+    if (type == long.class) {
+      return U.getLong(ct1, offset);
+    }
+    return U.getObject(ct1, offset);
   }
 
   int getInt(Object ct1) {
-    // TODO(b/247076137): Implement.
-    return -1;
+    return U.getInt(ct1, offset);
   }
 
   long getLong(Object ct1) {
@@ -51,29 +141,57 @@
 
   // set variants.
   void set(Object ct1, Object newValue) {
-    // TODO(b/247076137): Implement.
+    if (type == int.class) {
+      setInt(ct1, toIntIfPossible(newValue));
+    } else {
+      U.putObject(ct1, offset, newValue);
+    }
   }
 
   void setInt(Object ct1, int newValue) {
-    // TODO(b/247076137): Implement.
+    if (type == int.class) {
+      U.putInt(ct1, offset, newValue);
+    } else if (type == long.class) {
+      U.putLong(ct1, offset, newValue);
+    } else {
+      set(ct1, newValue);
+    }
   }
 
   void setLong(Object ct1, long newValue) {
-    // TODO(b/247076137): Implement.
+    if (type == long.class) {
+      U.putLong(ct1, offset, newValue);
+    } else {
+      throw desugarWrongMethodTypeException();
+    }
   }
 
-  boolean compareAndSet(Object ct1, Object expectedValue, Object newValue) {
-    // TODO(b/247076137): Implement.
-    return false;
+  boolean compareAndSet(Object ct1, Object expetedValue, Object newValue) {
+    if (type == int.class) {
+      return U.compareAndSwapInt(
+          ct1, offset, toIntIfPossible(expetedValue), toIntIfPossible((newValue)));
+    }
+    if (type == long.class) {
+      return U.compareAndSwapLong(
+          ct1, offset, toLongIfPossible(expetedValue), toLongIfPossible((newValue)));
+    }
+    return U.compareAndSwapObject(ct1, offset, expetedValue, newValue);
   }
 
-  boolean compareAndSetInt(Object ct1, int expectedValue, int newValue) {
-    // TODO(b/247076137): Implement.
-    return false;
+  boolean compareAndSetInt(Object ct1, int expetedValue, int newValue) {
+    if (type == int.class) {
+      return U.compareAndSwapInt(ct1, offset, expetedValue, newValue);
+    } else if (type == long.class) {
+      return U.compareAndSwapLong(ct1, offset, expetedValue, newValue);
+    } else {
+      return compareAndSet(ct1, expetedValue, newValue);
+    }
   }
 
-  boolean compareAndSetLong(Object ct1, long expectedValue, long newValue) {
-    // TODO(b/247076137): Implement.
-    return false;
+  boolean compareAndSetLong(Object ct1, long expetedValue, long newValue) {
+    if (type == long.class) {
+      return U.compareAndSwapLong(ct1, offset, expetedValue, newValue);
+    }
+    return compareAndSet(ct1, expetedValue, newValue);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
index 6ce480b..b72d827 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
@@ -29,6 +29,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -96,9 +97,11 @@
   // TODO(b/261024278): Share this code.
   private class InstructionTypeMapper {
     private final Map<DexType, DexType> typeMap;
+    private final Function<String, String> methodNameMap;
 
-    InstructionTypeMapper(Map<DexType, DexType> typeMap) {
+    InstructionTypeMapper(Map<DexType, DexType> typeMap, Function<String, String> methodNameMap) {
       this.typeMap = typeMap;
+      this.methodNameMap = methodNameMap;
     }
 
     private CfInstruction rewriteInstruction(CfInstruction instruction) {
@@ -124,12 +127,14 @@
       String name = method.getName().toString();
       DexType holderType = invoke.getMethod().getHolderType();
       DexType rewrittenType = typeMap.getOrDefault(holderType, holderType);
+      String rewrittenName =
+          rewrittenType == factory.desugarVarHandleType ? methodNameMap.apply(name) : name;
       if (rewrittenType != holderType) {
         // TODO(b/261024278): If sharing this code also rewrite signature.
         return new CfInvoke(
             invoke.getOpcode(),
             factory.createMethod(
-                rewrittenType, invoke.getMethod().getProto(), factory.createString(name)),
+                rewrittenType, invoke.getMethod().getProto(), factory.createString(rewrittenName)),
             invoke.isInterface());
       }
       return instruction;
@@ -178,7 +183,8 @@
                 factory.desugarVarHandleType,
                 factory.createType(
                     "L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + "$UnsafeStub;"),
-                factory.unsafeType));
+                factory.unsafeType),
+            GenerateVarHandleMethods::mapMethodName);
     code.setInstructions(
         code.getInstructions().stream()
             .map(instructionTypeMapper::rewriteInstruction)
@@ -207,15 +213,18 @@
   @Override
   protected DexEncodedMethod mapMethod(DexEncodedMethod method) {
     // Map VarHandle access mode methods to not have the Int/Long postfix.
+    return methodWithName(method, mapMethodName(method.getName().toString()));
+  }
+
+  private static String mapMethodName(String name) {
     for (String prefix : ImmutableList.of("get", "set", "compareAndSet")) {
-      if (method.getName().startsWith(prefix)) {
-        assert method.getName().toString().substring(prefix.length()).equals("Int")
-            || method.getName().toString().substring(prefix.length()).equals("Long")
-            || method.getName().toString().equals(prefix);
-        return methodWithName(method, prefix);
+      if (name.startsWith(prefix)
+          && (name.substring(prefix.length()).equals("Int")
+              || name.substring(prefix.length()).equals("Long"))) {
+        return prefix;
       }
     }
-    return methodWithName(method, method.getName().toString());
+    return name;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
index fcdab96..01ec5ac 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
 import com.android.tools.r8.keepanno.processor.KeepEdgeProcessor;
 import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
+import com.android.tools.r8.keepanno.testsource.KeepDependentFieldSource;
 import com.android.tools.r8.keepanno.testsource.KeepFieldSource;
 import com.android.tools.r8.keepanno.testsource.KeepSourceEdges;
 import com.android.tools.r8.references.ClassReference;
@@ -42,7 +43,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.objectweb.asm.AnnotationVisitor;
 
 @RunWith(Parameterized.class)
 public class KeepEdgeAnnotationsTest extends TestBase {
@@ -66,7 +66,10 @@
       Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
 
   private static List<Class<?>> getTestClasses() {
-    return ImmutableList.of(KeepClassAndDefaultConstructorSource.class, KeepFieldSource.class);
+    return ImmutableList.of(
+        KeepClassAndDefaultConstructorSource.class,
+        KeepFieldSource.class,
+        KeepDependentFieldSource.class);
   }
 
   private final TestParameters parameters;
@@ -145,14 +148,9 @@
     // Strip out all the annotations to ensure they are actually added again.
     byte[] stripped =
         transformer(source)
-            .addClassTransformer(
-                new ClassTransformer() {
-                  @Override
-                  public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
-                    // Ignore all input annotations.
-                    return null;
-                  }
-                })
+            .removeClassAnnotations()
+            .removeMethodAnnotations()
+            .removeFieldAnnotations()
             .transform();
     // Manually add in the expected edges again.
     byte[] readded =
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java
new file mode 100644
index 0000000..4a0df32
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepDependentFieldSource.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.testsource;
+
+import com.android.tools.r8.keepanno.annotations.KeepCondition;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import java.lang.reflect.Field;
+
+public class KeepDependentFieldSource {
+
+  public static class A {
+
+    public int f;
+
+    public A(int x) {
+      f = x;
+    }
+  }
+
+  // The keep edge is context independent, but natural to place close to the reflection usage.
+  @KeepEdge(
+      preconditions = {
+        // The edge is only needed if the main method that uses reflection is actually present.
+        @KeepCondition(classConstant = KeepDependentFieldSource.class, methodName = "main")
+      },
+      consequences = {
+        // Keep the reflectively accessed field.
+        @KeepTarget(classConstant = KeepDependentFieldSource.A.class, fieldName = "f")
+      })
+  public static void main(String[] args) throws Exception {
+    int x = 42 + args.length;
+    Object o = System.nanoTime() > 0 ? new A(x) : null;
+    Field f = o.getClass().getDeclaredField("f");
+    int y = f.getInt(o);
+    if (x == y) {
+      System.out.println("The values match!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
index 7af9aee..d4c7121 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
@@ -3,17 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.testsource;
 
+import com.android.tools.r8.keepanno.ast.KeepCondition;
 import com.android.tools.r8.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.keepanno.ast.KeepConsequences.Builder;
 import com.android.tools.r8.keepanno.ast.KeepEdge;
 import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
 import com.android.tools.r8.keepanno.ast.KeepItemPattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
+import com.android.tools.r8.keepanno.ast.KeepPreconditions;
 import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
 import com.android.tools.r8.keepanno.ast.KeepTarget;
 import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -23,22 +29,50 @@
  */
 public class KeepSourceEdges {
 
-  public static Set<KeepEdge> getExpectedEdges(Class<?> clazz) {
-    if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
-      return getKeepClassAndDefaultConstructorSourceEdges();
+  private static class SourceData {
+    final Class<?> clazz;
+    final String expected;
+    final Set<KeepEdge> edges;
+
+    public SourceData(Class<?> clazz, String expected, Set<KeepEdge> edges) {
+      this.clazz = clazz;
+      this.expected = expected;
+      this.edges = edges;
     }
-    if (clazz.equals(KeepFieldSource.class)) {
-      return getKeepFieldSourceEdges();
+  }
+
+  private static final List<SourceData> SOURCES = new ArrayList<>();
+
+  static {
+    SOURCES.add(
+        new SourceData(
+            KeepClassAndDefaultConstructorSource.class,
+            getKeepClassAndDefaultConstructorSourceExpected(),
+            getKeepClassAndDefaultConstructorSourceEdges()));
+    SOURCES.add(
+        new SourceData(
+            KeepFieldSource.class, getKeepFieldSourceExpected(), getKeepFieldSourceEdges()));
+    SOURCES.add(
+        new SourceData(
+            KeepDependentFieldSource.class,
+            getKeepDependentFieldSourceExpected(),
+            getKeepDependentFieldSourceEdges()));
+  }
+
+  public static Set<KeepEdge> getExpectedEdges(Class<?> clazz) {
+    for (SourceData source : SOURCES) {
+      if (source.clazz == clazz) {
+        return source.edges;
+      }
     }
     throw new RuntimeException();
   }
 
   public static String getExpected(Class<?> clazz) {
-    if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
-      return getKeepClassAndDefaultConstructorSourceExpected();
-    }
-    if (clazz.equals(KeepFieldSource.class)) {
-      return getKeepFieldSourceExpected();
+    for (SourceData source : SOURCES) {
+      if (source.clazz == clazz) {
+        return source.expected;
+      }
     }
     throw new RuntimeException();
   }
@@ -49,21 +83,8 @@
 
   public static Set<KeepEdge> getKeepClassAndDefaultConstructorSourceEdges() {
     Class<?> clazz = KeepClassAndDefaultConstructorSource.A.class;
-    // Build the class target.
-    KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
-    KeepItemPattern classItem = KeepItemPattern.builder().setClassPattern(name).build();
-    KeepTarget classTarget = KeepTarget.builder().setItem(classItem).build();
-    // Build the constructor target.
-    KeepMethodPattern constructorMethod =
-        KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact("<init>")).build();
-    KeepItemPattern constructorItem =
-        KeepItemPattern.builder().setClassPattern(name).setMemberPattern(constructorMethod).build();
-    KeepTarget constructorTarget = KeepTarget.builder().setItem(constructorItem).build();
-    // The consequet set is the class an its constructor.
-    KeepConsequences consequences =
-        KeepConsequences.builder().addTarget(classTarget).addTarget(constructorTarget).build();
-    KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
-    return Collections.singleton(edge);
+    return Collections.singleton(
+        mkEdge(mkConsequences(mkTarget(mkClass(clazz)), mkTarget(mkMethod(clazz, "<init>")))));
   }
 
   public static String getKeepFieldSourceExpected() {
@@ -71,15 +92,71 @@
   }
 
   public static Set<KeepEdge> getKeepFieldSourceEdges() {
-    Class<?> clazz = KeepFieldSource.A.class;
+    return Collections.singleton(
+        mkEdge(mkConsequences(mkTarget(mkField(KeepFieldSource.A.class, "f")))));
+  }
+
+  public static String getKeepDependentFieldSourceExpected() {
+    return getKeepFieldSourceExpected();
+  }
+
+  public static Set<KeepEdge> getKeepDependentFieldSourceEdges() {
+    return Collections.singleton(
+        mkDepEdge(
+            mkPreconditions(mkCondition(mkMethod(KeepDependentFieldSource.class, "main"))),
+            mkConsequences(mkTarget(mkField(KeepDependentFieldSource.A.class, "f")))));
+  }
+
+  // Ast helpers.
+
+  static KeepItemPattern mkClass(Class<?> clazz) {
+    KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+    return KeepItemPattern.builder().setClassPattern(name).build();
+  }
+
+  static KeepItemPattern mkMethod(Class<?> clazz, String methodName) {
+    KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+    KeepMethodPattern methodPattern =
+        KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact(methodName)).build();
+    KeepItemPattern methodItem =
+        KeepItemPattern.builder().setClassPattern(name).setMemberPattern(methodPattern).build();
+    return methodItem;
+  }
+
+  static KeepItemPattern mkField(Class<?> clazz, String fieldName) {
     KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
     KeepFieldPattern fieldPattern =
-        KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact("f")).build();
+        KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build();
     KeepItemPattern fieldItem =
         KeepItemPattern.builder().setClassPattern(name).setMemberPattern(fieldPattern).build();
-    KeepTarget fieldTarget = KeepTarget.builder().setItem(fieldItem).build();
-    KeepConsequences consequences = KeepConsequences.builder().addTarget(fieldTarget).build();
-    KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
-    return Collections.singleton(edge);
+    return fieldItem;
+  }
+
+  static KeepTarget mkTarget(KeepItemPattern item) {
+    return KeepTarget.builder().setItem(item).build();
+  }
+
+  static KeepCondition mkCondition(KeepItemPattern item) {
+    return KeepCondition.builder().setItem(item).build();
+  }
+
+  static KeepConsequences mkConsequences(KeepTarget... targets) {
+    Builder builder = KeepConsequences.builder();
+    Arrays.asList(targets).forEach(builder::addTarget);
+    return builder.build();
+  }
+
+  static KeepPreconditions mkPreconditions(KeepCondition... conditions) {
+    KeepPreconditions.Builder builder = KeepPreconditions.builder();
+    Arrays.asList(conditions).forEach(builder::addCondition);
+    return builder.build();
+  }
+
+  static KeepEdge mkEdge(KeepConsequences consequences) {
+    return KeepEdge.builder().setConsequences(consequences).build();
+  }
+
+  static KeepEdge mkDepEdge(KeepPreconditions preconditions, KeepConsequences consequences) {
+    return KeepEdge.builder().setPreconditions(preconditions).setConsequences(consequences).build();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 8108730..afc975a 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -1002,15 +1003,17 @@
 
   public ClassFileTransformer replaceClassDescriptorInMethodInstructions(
       String oldDescriptor, String newDescriptor) {
+    return replaceClassDescriptorInMethodInstructions(
+        ImmutableMap.of(oldDescriptor, newDescriptor));
+  }
+
+  public ClassFileTransformer replaceClassDescriptorInMethodInstructions(Map<String, String> map) {
     return addMethodTransformer(
         new MethodTransformer() {
           @Override
           public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
             super.visitFieldInsn(
-                opcode,
-                rewriteASMInternalTypeName(owner),
-                name,
-                replaceAll(descriptor, oldDescriptor, newDescriptor));
+                opcode, rewriteASMInternalTypeName(owner), name, replaceAll(descriptor, map));
           }
 
           @Override
@@ -1037,8 +1040,7 @@
           public void visitLdcInsn(Object value) {
             if (value instanceof Type) {
               Type type = (Type) value;
-              super.visitLdcInsn(
-                  Type.getType(replaceAll(type.getDescriptor(), oldDescriptor, newDescriptor)));
+              super.visitLdcInsn(Type.getType(replaceAll(type.getDescriptor(), map)));
             } else {
               super.visitLdcInsn(value);
             }
@@ -1051,7 +1053,7 @@
                 opcode,
                 rewriteASMInternalTypeName(owner),
                 name,
-                replaceAll(descriptor, oldDescriptor, newDescriptor),
+                replaceAll(descriptor, map),
                 isInterface);
           }
 
@@ -1069,8 +1071,7 @@
               Object arg = bootstrapMethodArguments[i];
               if (arg instanceof Handle) {
                 Handle oldHandle = (Handle) arg;
-                String repl =
-                    replaceAll("L" + oldHandle.getOwner() + ";", oldDescriptor, newDescriptor);
+                String repl = replaceAll("L" + oldHandle.getOwner() + ";", map);
                 String newOwner = repl.substring(1, repl.length() - 1);
                 Handle newHandle =
                     new Handle(
@@ -1093,9 +1094,7 @@
           }
 
           private String rewriteASMInternalTypeName(String type) {
-            return Type.getType(
-                    replaceAll(
-                        Type.getObjectType(type).getDescriptor(), oldDescriptor, newDescriptor))
+            return Type.getType(replaceAll(Type.getObjectType(type).getDescriptor(), map))
                 .getInternalName();
           }
         });
@@ -1463,4 +1462,42 @@
           }
         });
   }
+
+  public ClassFileTransformer removeClassAnnotations() {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+            // Ignore all input annotations.
+            return null;
+          }
+        });
+  }
+
+  public ClassFileTransformer removeMethodAnnotations() {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          @Override
+          public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+            return null;
+          }
+        });
+  }
+
+  public ClassFileTransformer removeFieldAnnotations() {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            FieldVisitor fv = visitField(access, name, descriptor, signature, value);
+            return new FieldVisitor(ASM_VERSION, fv) {
+              @Override
+              public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+                return null;
+              }
+            };
+          }
+        });
+  }
 }