Generate DesugarVarHandle class

This extends the method generator to generate a class as well.

Bug: b/247076137
Change-Id: I3c5e99021eade28e53770820e15214814cd03c33
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index 293e189..f00f611 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -100,7 +100,11 @@
 
   public CfCodePrinter() {}
 
-  public List<String> getImports() {
+  public Set<String> getImports() {
+    return imports;
+  }
+
+  public List<String> getImportsSorted() {
     ArrayList<String> sorted = new ArrayList<>(imports);
     sorted.sort(String::compareTo);
     return sorted;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 47fabfa..9807531 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -353,7 +353,7 @@
     return new Builder(false);
   }
 
-  private static Builder builder(DexEncodedField from) {
+  public static Builder builder(DexEncodedField from) {
     return new Builder(from.isD8R8Synthesized(), from);
   }
 
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 2e2755d..809f8e6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -75,6 +75,9 @@
   public static final String dalvikAnnotationSignatureString = "Ldalvik/annotation/Signature;";
   public static final String recordTagDescriptorString = "Lcom/android/tools/r8/RecordTag;";
   public static final String recordDescriptorString = "Ljava/lang/Record;";
+  public static final String desugarVarHandleDescriptorString =
+      "Lcom/android/tools/r8/DesugarVarHandle;";
+  public static final String varHandleDescriptorString = "Ljava/lang/invoke/VarHandle;";
   public static final String dalvikAnnotationOptimizationPrefixString =
       "Ldalvik/annotation/optimization/";
 
@@ -266,7 +269,7 @@
   public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
   public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
 
-  public final DexString varHandleDescriptor = createString("Ljava/lang/invoke/VarHandle;");
+  public final DexString varHandleDescriptor = createString(varHandleDescriptorString);
   public final DexString methodHandleDescriptor = createString("Ljava/lang/invoke/MethodHandle;");
   public final DexString methodTypeDescriptor = createString("Ljava/lang/invoke/MethodType;");
   public final DexString invocationHandlerDescriptor =
@@ -738,6 +741,8 @@
   public final DexType stringConcatFactoryType =
       createStaticallyKnownType("Ljava/lang/invoke/StringConcatFactory;");
   public final DexType unsafeType = createStaticallyKnownType("Lsun/misc/Unsafe;");
+  public final DexType desugarVarHandleType =
+      createStaticallyKnownType(desugarVarHandleDescriptorString);
 
   public final ObjectMethodsMembers objectMethodsMembers = new ObjectMethodsMembers();
   public final ServiceLoaderMethods serviceLoaderMethods = new ServiceLoaderMethods();
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
new file mode 100644
index 0000000..d463636
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
@@ -0,0 +1,475 @@
+// 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateVarHandleMethods.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.ir.desugar.varhandle;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstClass;
+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.CfInstanceFieldRead;
+import com.android.tools.r8.cf.code.CfInstanceFieldWrite;
+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.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+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.ValueType;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.google.common.collect.ImmutableList;
+
+public final class VarHandleDesugaringMethods {
+
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("Lcom/android/tools/r8/DesugarVarHandle;");
+    factory.createSynthesizedType("Ljava/lang/reflect/Field;");
+    factory.createSynthesizedType("Lsun/misc/Unsafe;");
+  }
+
+  public static void generateDesugarVarHandleClass(
+      SyntheticProgramClassBuilder builder, DexItemFactory factory) {
+    builder.setInstanceFields(
+        ImmutableList.of(
+            DexEncodedField.syntheticBuilder()
+                .setField(
+                    factory.createField(
+                        builder.getType(),
+                        factory.createType(factory.createString("Lsun/misc/Unsafe;")),
+                        factory.createString("U")))
+                .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedField.syntheticBuilder()
+                .setField(
+                    factory.createField(
+                        builder.getType(),
+                        factory.createType(factory.createString("Ljava/lang/Class;")),
+                        factory.createString("recv")))
+                .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedField.syntheticBuilder()
+                .setField(
+                    factory.createField(
+                        builder.getType(),
+                        factory.createType(factory.createString("Ljava/lang/Class;")),
+                        factory.createString("type")))
+                .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedField.syntheticBuilder()
+                .setField(
+                    factory.createField(
+                        builder.getType(), factory.longType, factory.createString("offset")))
+                .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 setInt =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.voidType, factory.objectType, factory.intType),
+            factory.createString("set"));
+    DexMethod getLong =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.longType, factory.objectType),
+            factory.createString("get"));
+    DexMethod constructor_3 =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(
+                factory.voidType,
+                factory.createType(factory.createString("Ljava/lang/Class;")),
+                factory.createType(factory.createString("Ljava/lang/String;")),
+                factory.createType(factory.createString("Ljava/lang/Class;"))),
+            factory.createString("<init>"));
+    DexMethod setLong =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(factory.voidType, factory.objectType, factory.longType),
+            factory.createString("set"));
+    DexMethod compareAndSetLong =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(
+                factory.booleanType, factory.objectType, factory.longType, factory.longType),
+            factory.createString("compareAndSet"));
+    builder.setDirectMethods(
+        ImmutableList.of(
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(constructor_3)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true))
+                .setCode(DesugarVarHandle_constructor_3(factory, constructor_3))
+                .disableAndroidApiLevelCheck()
+                .build()));
+    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(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_get(factory, get))
+                .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(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_compareAndSet(factory, compareAndSet))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(setInt)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_setInt(factory, setInt))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(getLong)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_getLong(factory, getLong))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(setLong)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_setLong(factory, setLong))
+                .disableAndroidApiLevelCheck()
+                .build(),
+            DexEncodedMethod.syntheticBuilder()
+                .setMethod(compareAndSetLong)
+                .setAccessFlags(
+                    MethodAccessFlags.fromSharedAccessFlags(
+                        Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false))
+                .setCode(DesugarVarHandle_compareAndSetLong(factory, compareAndSetLong))
+                .disableAndroidApiLevelCheck()
+                .build()));
+  }
+
+  public static CfCode DesugarVarHandle_constructor_3(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,
+        4,
+        6,
+        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, 4),
+            label2,
+            new CfLoad(ValueType.OBJECT, 4),
+            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, 4),
+            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, 1),
+            new CfLoad(ValueType.OBJECT, 2),
+            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, 5),
+            label6,
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 5),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/reflect/Field;"),
+                    factory.createProto(factory.classType),
+                    factory.createString("getType")),
+                false),
+            new CfInstanceFieldWrite(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.classType,
+                    factory.createString("type"))),
+            label7,
+            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, 1),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.classType,
+                    factory.createProto(
+                        factory.createType("Ljava/lang/reflect/Field;"), factory.stringType),
+                    factory.createString("getDeclaredField")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.createType("Lsun/misc/Unsafe;"),
+                    factory.createProto(
+                        factory.longType, factory.createType("Ljava/lang/reflect/Field;")),
+                    factory.createString("objectFieldOffset")),
+                false),
+            new CfInstanceFieldWrite(
+                factory.createField(
+                    factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"),
+                    factory.longType,
+                    factory.createString("offset"))),
+            label8,
+            new CfReturnVoid(),
+            label9),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_compareAndSet(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        4,
+        ImmutableList.of(
+            label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_compareAndSetInt(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        4,
+        ImmutableList.of(
+            label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_compareAndSetLong(
+      DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        6,
+        ImmutableList.of(
+            label0, new CfConstNumber(0, ValueType.INT), new CfReturn(ValueType.INT), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_get(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        2,
+        ImmutableList.of(label0, new CfConstNull(), new CfReturn(ValueType.OBJECT), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_getInt(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        1,
+        2,
+        ImmutableList.of(
+            label0, new CfConstNumber(-1, ValueType.INT), new CfReturn(ValueType.INT), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_getLong(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        2,
+        ImmutableList.of(
+            label0, new CfConstNumber(-1, ValueType.LONG), new CfReturn(ValueType.LONG), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_set(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        0,
+        3,
+        ImmutableList.of(label0, new CfReturnVoid(), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_setInt(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        0,
+        3,
+        ImmutableList.of(label0, new CfReturnVoid(), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode DesugarVarHandle_setLong(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        0,
+        4,
+        ImmutableList.of(label0, new CfReturnVoid(), label1),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
index c706f50..0630571 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/CfClassGenerator.java
@@ -363,7 +363,7 @@
       codePrinter.visitMethod(generatedMethodName, method.getCode().asCfCode());
       index++;
     }
-    codePrinter.getImports().forEach(imports::addImport);
+    codePrinter.getImportsSorted().forEach(imports::addImport);
     return createCfCodeMethodNames;
   }
 
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 28ab9a7..0cd6b23 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -8,30 +8,65 @@
 import com.android.tools.r8.cf.CfCodePrinter;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.graph.JarClassFileReader;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Reporter;
+import com.beust.jcommander.internal.Sets;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+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.TreeSet;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 public abstract class MethodGenerationBase extends CodeGenerationBase {
 
   protected abstract List<Class<?>> getMethodTemplateClasses();
 
+  protected List<Class<?>> getClassesToGenerate() {
+    return ImmutableList.of();
+  }
+
+  protected DexEncodedField getField(DexEncodedField field) {
+    return field;
+  }
+
+  protected boolean includeMethod(DexEncodedMethod method) {
+    return !method.isInstanceInitializer();
+  }
+
   protected CfCode getCode(String holderName, String methodName, CfCode code) {
     return code;
   }
 
+  protected DexEncodedMethod mapMethod(DexEncodedMethod method) {
+    return method;
+  }
+
   // Running this method will regenerate / overwrite the content of the generated class.
   protected void generateMethodsAndWriteThemToFile() throws IOException {
     FileUtils.writeToFile(getGeneratedFile(), null, generateMethods().getBytes());
@@ -43,31 +78,47 @@
 
     File tempFile = File.createTempFile("output-", ".java");
 
-    readMethodTemplatesInto(codePrinter);
-    generateRawOutput(codePrinter, tempFile.toPath());
+    Map<DexEncodedMethod, String> generatedMethods = new HashMap<>();
+    List<DexEncodedField> fields = new ArrayList<>();
+    readMethodTemplatesInto(codePrinter, generatedMethods::put, fields::add);
+    generateRawOutput(generatedMethods, fields, codePrinter, tempFile.toPath());
     String result = formatRawOutput(tempFile.toPath());
 
     tempFile.deleteOnExit();
     return result;
   }
 
-  private void readMethodTemplatesInto(CfCodePrinter codePrinter) throws IOException {
+  private void readMethodTemplatesInto(
+      CfCodePrinter codePrinter,
+      BiConsumer<DexEncodedMethod, String> generatedMethods,
+      Consumer<DexEncodedField> generatedFields)
+      throws IOException {
     InternalOptions options = new InternalOptions(factory, new Reporter());
     options.testing.readInputStackMaps = true;
     JarClassFileReader<DexProgramClass> reader =
         new JarClassFileReader<>(
             new JarApplicationReader(options),
             clazz -> {
+              for (DexEncodedField field : clazz.fields()) {
+                DexEncodedField fieldToAddToClass = getField(field);
+                if (fieldToAddToClass != null) {
+                  generatedFields.accept(fieldToAddToClass);
+                }
+              }
               for (DexEncodedMethod method : clazz.allMethodsSorted()) {
-                if (method.isInitializer()) {
+                if (!includeMethod(method)) {
                   continue;
                 }
                 String holderName = method.getHolderType().getName();
                 String methodName = method.getReference().name.toString();
+                if (methodName.equals("<init>")) {
+                  methodName = "constructor_" + method.getProto().getArity();
+                }
                 String generatedMethodName = holderName + "_" + methodName;
                 CfCode code = getCode(holderName, methodName, method.getCode().asCfCode());
                 if (code != null) {
                   codePrinter.visitMethod(generatedMethodName, code);
+                  generatedMethods.accept(method, generatedMethodName);
                 }
               }
             },
@@ -77,11 +128,222 @@
     }
   }
 
-  private void generateRawOutput(CfCodePrinter codePrinter, Path tempFile) throws IOException {
+  private String createType(DexType type) {
+    if (type.isVoidType()) {
+      return "factory.voidType";
+    }
+    if (type.isBooleanType()) {
+      return "factory.booleanType";
+    }
+    if (type.isIntType()) {
+      return "factory.intType";
+    }
+    if (type.isLongType()) {
+      return "factory.longType";
+    }
+    if (type.descriptor.toString().equals("Ljava/lang/Object;")) {
+      return "factory.objectType";
+    }
+    return "factory.createType(factory.createString(\"" + type.getDescriptor() + "\"))";
+  }
+
+  private String createField(DexEncodedField field) {
+    return "factory.createField(\n"
+        + "builder.getType(),\n"
+        + createType(field.getType())
+        + ",\n"
+        + "factory.createString(\""
+        + field.getName()
+        + "\"))\n";
+  }
+
+  private String buildSyntheticMethod(
+      DexEncodedMethod method, String codeGenerator, Set<String> requiredImports) {
+    String name =
+        method.isInstanceInitializer()
+            ? "constructor_" + method.getProto().getArity()
+            : method.getName().toString();
+    requiredImports.add("com.android.tools.r8.graph.DexEncodedMethod");
+    requiredImports.add("com.android.tools.r8.graph.MethodAccessFlags");
+    requiredImports.add("com.android.tools.r8.dex.Constants");
+    return "DexEncodedMethod.syntheticBuilder()\n"
+        + "        .setMethod("
+        + name
+        + ")\n"
+        + "        .setAccessFlags(\n"
+        + "            MethodAccessFlags.fromSharedAccessFlags(\n"
+        + "                Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, "
+        + (method.isInstanceInitializer())
+        + "))\n"
+        + "        .setCode("
+        + codeGenerator
+        + "(factory, "
+        + name
+        + "))\n"
+        + "        .disableAndroidApiLevelCheck()\n"
+        + "        .build()";
+  }
+
+  private String generateCreateProto(DexProto proto) {
+    StringBuilder builder = new StringBuilder("factory.createProto(");
+    builder.append(createType(proto.returnType));
+    proto.getParameters().forEach(type -> builder.append(", ").append(createType(type)));
+    builder.append(")");
+    return builder.toString();
+  }
+
+  private String generateCreateMethod(DexEncodedMethod method) {
+    return "factory.createMethod(\n"
+        + "builder.getType(), "
+        + generateCreateProto(method.getProto())
+        + ", factory.createString(\""
+        + method.getName()
+        + "\"));";
+  }
+
+  private void generateDexMethodLocals(
+      Map<DexEncodedMethod, String> generatedMethods, PrintStream printer, Class<?> clazz) {
+    // Generate local variable for a DexMethod:
+    //  DexMethod name = factory.createMethod(
+    //      builder.getType(), factory.createProto(...), factory.createString(...));
+
+    generatedMethods.forEach(
+        (method, codeGenerator) -> {
+          if (method.getHolderType().toSourceString().equals(clazz.getCanonicalName())) {
+            String name =
+                method.isInstanceInitializer()
+                    ? "constructor_" + method.getProto().getArity()
+                    : method.getName().toString();
+            printer.println("DexMethod " + name + " = " + generateCreateMethod(mapMethod(method)));
+          }
+        });
+  }
+
+  private void generateSyntheticMethodsList(
+      Map<DexEncodedMethod, String> generatedMethods,
+      Set<String> requiredImports,
+      PrintStream printer,
+      Class<?> clazz,
+      Predicate<DexEncodedMethod> filter) {
+    printer.println("ImmutableList.of(");
+    BooleanBox first = new BooleanBox(true);
+    generatedMethods.forEach(
+        (method, codeGenerator) -> {
+          if (method.getHolderType().toSourceString().equals(clazz.getCanonicalName())
+              && filter.test(method)) {
+            if (!first.get()) {
+              printer.println(",\n");
+            }
+            first.set(false);
+            printer.println(buildSyntheticMethod(method, codeGenerator, requiredImports));
+          }
+        });
+    printer.println(")");
+  }
+
+  private String buildSyntheticField(DexEncodedField field, Set<String> requiredImports) {
+    requiredImports.add("com.android.tools.r8.graph.DexEncodedField");
+    requiredImports.add("com.android.tools.r8.graph.FieldAccessFlags");
+    return "            DexEncodedField.syntheticBuilder()\n"
+        + "                .setField("
+        + createField(field)
+        + ")\n"
+        + "                .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic())\n"
+        + "                .disableAndroidApiLevelCheck()\n"
+        + "                .build()\n";
+  }
+
+  private void generateFieldList(
+      List<DexEncodedField> fields,
+      Set<String> requiredImports,
+      PrintStream printer,
+      Class<?> clazz) {
+    printer.println("ImmutableList.of(");
+    BooleanBox first = new BooleanBox(true);
+    for (DexEncodedField field : fields) {
+      if (field.getHolderType().toSourceString().equals(clazz.getCanonicalName())) {
+        if (!first.get()) {
+          printer.println(",\n");
+        }
+        first.set(false);
+        printer.println(buildSyntheticField(field, requiredImports));
+      }
+    }
+    printer.println(")");
+  }
+
+  private <K, V> Map<K, V> filterMapOnKey(Map<K, V> map, Predicate<K> predicate) {
+    return map.entrySet().stream()
+        .filter(entry -> predicate.test(entry.getKey()))
+        .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
+  }
+
+  private void generateGenerateClasses(
+      Map<DexEncodedMethod, String> allMethods,
+      List<DexEncodedField> allFields,
+      Set<String> requiredImports,
+      PrintStream printer) {
+    for (Class<?> clazz : getClassesToGenerate()) {
+      String simpleName =
+          clazz.getSimpleName(); // "DesugarVarHandle"; //name.substring(name.lastIndexOf('.'));
+      List<DexEncodedField> classFields =
+          ListUtils.filter(
+              allFields,
+              field -> field.getHolderType().toSourceString().equals(clazz.getCanonicalName()));
+      Map<DexEncodedMethod, String> classMethods =
+          filterMapOnKey(
+              allMethods,
+              method -> method.getHolderType().toSourceString().equals(clazz.getCanonicalName()));
+      requiredImports.add("com.android.tools.r8.synthesis.SyntheticProgramClassBuilder");
+      printer.println(
+          "public static void generate"
+              + simpleName
+              + "Class(SyntheticProgramClassBuilder builder, DexItemFactory factory) {");
+      printer.println("builder.setInstanceFields(");
+      generateFieldList(classFields, requiredImports, printer, clazz);
+      printer.println(");");
+      generateDexMethodLocals(classMethods, printer, clazz);
+      if (clazz.getSuperclass() != Object.class) {
+        printer.println(
+            "    builder.setSuperType("
+                + createType(factory.createType(Reference.classFromClass(clazz.getSuperclass())))
+                + ");");
+      }
+      printer.println("builder.setDirectMethods(");
+      generateSyntheticMethodsList(
+          classMethods, requiredImports, printer, clazz, DexEncodedMethod::isInstanceInitializer);
+      printer.println(");");
+      printer.println("builder.setVirtualMethods(");
+      generateSyntheticMethodsList(
+          allMethods, requiredImports, printer, clazz, method -> !method.isInstanceInitializer());
+      printer.println(");");
+      printer.println("}");
+    }
+  }
+
+  private void generateRawOutput(
+      Map<DexEncodedMethod, String> generatedMethods,
+      List<DexEncodedField> fields,
+      CfCodePrinter codePrinter,
+      Path tempFile)
+      throws IOException {
     try (PrintStream printer = new PrintStream(Files.newOutputStream(tempFile))) {
       printer.print(getHeaderString());
-      printer.println("import com.android.tools.r8.graph.DexItemFactory;");
-      codePrinter.getImports().forEach(i -> printer.println("import " + i + ";"));
+
+      Set<String> imports = Sets.newHashSet();
+      imports.add("com.android.tools.r8.graph.DexItemFactory");
+      // TODO(b/260985726): Consider only calling generateGenerateClasses once.
+      // Generate classes into an unused PrintStream to collect imports.
+      generateGenerateClasses(
+          generatedMethods,
+          fields,
+          imports,
+          new PrintStream(new ByteArrayOutputStream(), true, StandardCharsets.UTF_8.name()));
+      imports.addAll(codePrinter.getImports());
+      ArrayList<String> sortedImports = new ArrayList<>(imports);
+      sortedImports.sort(String::compareTo);
+      sortedImports.forEach(i -> printer.println("import " + i + ";"));
+
       printer.println("public final class " + getGeneratedClassName() + " {\n");
       printer.println(
           "public static void registerSynthesizedCodeReferences(DexItemFactory factory) {");
@@ -89,8 +351,12 @@
         printer.println("factory.createSynthesizedType(\"" + type + "\");");
       }
       printer.println("}");
+      // TODO(b/260985726): Consider only calling generateGenerateClasses once.
+      // Generate classes ignoring the collected imports (they are already added to the file).
+      generateGenerateClasses(generatedMethods, fields, Sets.newHashSet(), printer);
       codePrinter.getMethods().forEach(printer::println);
       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
new file mode 100644
index 0000000..2a19a45
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
@@ -0,0 +1,78 @@
+// 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 java.lang.reflect.Field;
+
+// Template class for desugaring VarHandle into com.android.tools.r8.DesugarVarHandle.
+public final class DesugarVarHandle {
+
+  // This only have methods found in libcore/libart/src/main/java/sun/misc/Unsafe.java for Lollipop.
+  private static class UnsafeStub {
+
+    public long objectFieldOffset(Field f) {
+      throw new RuntimeException("Stub called.");
+    }
+  }
+
+  private final UnsafeStub U;
+  private final Class<?> recv;
+  private final Class<?> type;
+  private final long offset;
+
+  DesugarVarHandle(Class<?> recv, String name, Class<?> type) throws Exception {
+    Field theUnsafe = UnsafeStub.class.getDeclaredField("theUnsafe");
+    theUnsafe.setAccessible(true);
+    U = (UnsafeStub) theUnsafe.get(null);
+    this.recv = recv;
+    Field field = recv.getDeclaredField(name);
+    this.type = field.getType();
+    this.offset = U.objectFieldOffset(recv.getDeclaredField(name));
+  }
+
+  // get variants.
+  Object get(Object ct1) {
+    // TODO(b/247076137): Implement.
+    return null;
+  }
+
+  int getInt(Object ct1) {
+    // TODO(b/247076137): Implement.
+    return -1;
+  }
+
+  long getLong(Object ct1) {
+    // TODO(b/247076137): Implement.
+    return -1L;
+  }
+
+  // set variants.
+  void set(Object ct1, Object newValue) {
+    // TODO(b/247076137): Implement.
+  }
+
+  void setInt(Object ct1, int newValue) {
+    // TODO(b/247076137): Implement.
+  }
+
+  void setLong(Object ct1, long newValue) {
+    // TODO(b/247076137): Implement.
+  }
+
+  boolean compareAndSet(Object ct1, Object expectedValue, Object newValue) {
+    // TODO(b/247076137): Implement.
+    return false;
+  }
+
+  boolean compareAndSetInt(Object ct1, int expectedValue, int newValue) {
+    // TODO(b/247076137): Implement.
+    return false;
+  }
+
+  boolean compareAndSetLong(Object ct1, long expectedValue, long newValue) {
+    // TODO(b/247076137): Implement.
+    return false;
+  }
+}
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
new file mode 100644
index 0000000..e691744
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/GenerateVarHandleMethods.java
@@ -0,0 +1,208 @@
+// 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 static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexType;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenerateVarHandleMethods extends MethodGenerationBase {
+
+  private final DexType GENERATED_TYPE =
+      factory.createType("Lcom/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods;");
+  private final List<Class<?>> METHOD_TEMPLATE_CLASSES = ImmutableList.of(DesugarVarHandle.class);
+
+  protected final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+  }
+
+  public GenerateVarHandleMethods(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Override
+  protected DexType getGeneratedType() {
+    return GENERATED_TYPE;
+  }
+
+  @Override
+  protected List<Class<?>> getMethodTemplateClasses() {
+    return METHOD_TEMPLATE_CLASSES;
+  }
+
+  @Override
+  protected List<Class<?>> getClassesToGenerate() {
+    return ImmutableList.of(DesugarVarHandle.class);
+  }
+
+  @Override
+  protected boolean includeMethod(DexEncodedMethod method) {
+    // Include all methods, including constructors.
+    return true;
+  }
+
+  @Override
+  protected int getYear() {
+    return 2022;
+  }
+
+  private static CfInstruction rewriteToUnsafe(DexItemFactory factory, CfInstruction instruction) {
+    DexType unsafe = factory.unsafeType;
+    DexType unsafeStub =
+        factory.createType(
+            "L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + "$UnsafeStub;");
+    // Rewrite references to UnsafeStub to sun.misc.Unsafe
+    if (instruction.isTypeInstruction()
+        && instruction.asTypeInstruction().getType() == unsafeStub) {
+      return instruction.asTypeInstruction().withType(unsafe);
+    }
+    if (instruction.isFieldInstruction()) {
+      CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
+      if (fieldInstruction.getField().getType() == unsafeStub) {
+        return fieldInstruction.createWithField(
+            factory.createField(
+                fieldInstruction.getField().getHolderType(),
+                unsafe,
+                fieldInstruction.getField().name));
+      }
+    }
+    if (instruction.isInvoke()) {
+      CfInvoke invoke = instruction.asInvoke();
+      DexMethod method = invoke.getMethod();
+      String name = method.getName().toString();
+      if (invoke.getMethod().getHolderType() == unsafeStub) {
+        return new CfInvoke(
+            invoke.getOpcode(),
+            factory.createMethod(unsafe, invoke.getMethod().getProto(), factory.createString(name)),
+            invoke.isInterface());
+      }
+    }
+    if (instruction.isFrame()) {
+      return instruction.asFrame().mapReferenceTypes(type -> (type == unsafeStub) ? unsafe : type);
+    }
+    return instruction;
+  }
+
+  private static CfInstruction rewriteDesugarVarHandle(
+      DexItemFactory factory, CfInstruction instruction) {
+    // Rewrite references to com.android.tools.r8.ir.desugar.varhandle.DesugarVarHandle to
+    // com.android.tools.r8.DesugarVarHandle.
+    DexType desugarVarHandle = factory.desugarVarHandleType;
+    DexType desugarVarHandleStub =
+        factory.createType("L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + ";");
+    if (instruction.isFieldInstruction()) {
+      CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
+      if (fieldInstruction.getField().getHolderType() == desugarVarHandleStub) {
+        return fieldInstruction.createWithField(
+            factory.createField(
+                desugarVarHandle,
+                fieldInstruction.getField().getType(),
+                fieldInstruction.getField().name));
+      }
+    }
+    return instruction;
+  }
+
+  @Override
+  protected DexEncodedField getField(DexEncodedField field) {
+    if (field.getType().getTypeName().endsWith("$UnsafeStub")) {
+      return DexEncodedField.builder(field)
+          .setField(
+              factory.createField(
+                  field.getHolderType(), factory.createType("Lsun/misc/Unsafe;"), field.getName()))
+          .build();
+    }
+    return field;
+  }
+
+  @Override
+  protected CfCode getCode(String holderName, String methodName, CfCode code) {
+    if (methodName.endsWith("Stub")) {
+      // Don't include stubs targeted only for rewriting in the generated code.
+      return null;
+    }
+    if (!holderName.equals("DesugarVarHandle")) {
+      throw new RuntimeException("Unexpected");
+    }
+    code.setInstructions(
+        code.getInstructions().stream()
+            .map(instruction -> rewriteToUnsafe(factory, instruction))
+            .map(instruction -> rewriteDesugarVarHandle(factory, instruction))
+            .collect(Collectors.toList()));
+    return code;
+  }
+
+  private DexEncodedMethod methodWithName(DexEncodedMethod method, String name) {
+    DexType holder = method.getHolderType();
+    DexType desugarVarHandle = factory.desugarVarHandleType;
+    DexType desugarVarHandleStub =
+        factory.createType("L" + DesugarVarHandle.class.getTypeName().replace('.', '/') + ";");
+    // Map methods to be on the final DesugarVarHandle class.
+    if (holder == desugarVarHandleStub) {
+      holder = desugarVarHandle;
+    }
+    DexProto proto = method.getProto();
+    if (proto.getReturnType() == desugarVarHandleStub) {
+      proto = factory.createProto(desugarVarHandle, proto.parameters);
+    }
+    return DexEncodedMethod.syntheticBuilder(method)
+        .setMethod(factory.createMethod(holder, proto, factory.createString(name)))
+        .build();
+  }
+
+  @Override
+  protected DexEncodedMethod mapMethod(DexEncodedMethod method) {
+    // Map VarHandle access mode methods to not have the Int/Long postfix.
+    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");
+        return methodWithName(method, prefix);
+      }
+    }
+    return methodWithName(method, method.getName().toString());
+  }
+
+  @Test
+  public void testVarHandleDesugaringGenerated() throws Exception {
+    ArrayList<Class<?>> sorted = new ArrayList<>(getMethodTemplateClasses());
+    sorted.sort(Comparator.comparing(Class::getTypeName));
+    assertEquals("Classes should be listed in sorted order", sorted, getMethodTemplateClasses());
+    assertEquals(
+        FileUtils.readTextFile(getGeneratedFile(), StandardCharsets.UTF_8), generateMethods());
+  }
+
+  public static void main(String[] args) throws Exception {
+    new GenerateVarHandleMethods(null).generateMethodsAndWriteThemToFile();
+  }
+}