Update global synthetics generator to support split CF/DEX desugaring

This change is not binary compatible with the previous generator tool.
It is not yet in use anywhere so that should not be an issue.

Bug: b/332866206
Change-Id: I2cbb596cd2c18b3920d8b8a45a105217f31242ed
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
index 88e9f55..28125f6 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsConsumer.java
@@ -31,7 +31,7 @@
    * <p>The context class is the class for which the global synthetic data is needed. If compiling
    * in DexIndexed mode, the context class will be null.
    *
-   * <p>The accept method will be called at most once for a given context class (any only once at
+   * <p>The accept method will be called at most once for a given context class (and only once at
    * all for a DexIndexed mode compilation). The global data for that class may be the same as for
    * other context classes, but it will be provided for each context.
    *
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index c9590ea..ccfe752 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -34,11 +34,12 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ThrowExceptionCode;
-import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.PrimaryD8L8IRConverter;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
 import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringEventConsumer;
+import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
 import com.android.tools.r8.naming.RecordRewritingNamingLens;
 import com.android.tools.r8.naming.VarHandleDesugaringRewritingNamingLens;
@@ -72,15 +73,14 @@
 @KeepForApi
 public class GlobalSyntheticsGenerator {
 
-  @SuppressWarnings("ReferenceEquality")
   private static boolean ensureAllGlobalSyntheticsModeled(SyntheticNaming naming) {
     for (SyntheticKind kind : naming.kinds()) {
       assert !kind.isGlobal()
           || !kind.isMayOverridesNonProgramType()
-          || kind == naming.RECORD_TAG
-          || kind == naming.API_MODEL_STUB
-          || kind == naming.METHOD_HANDLES_LOOKUP
-          || kind == naming.VAR_HANDLE;
+          || kind.equals(naming.RECORD_TAG)
+          || kind.equals(naming.API_MODEL_STUB)
+          || kind.equals(naming.METHOD_HANDLES_LOOKUP)
+          || kind.equals(naming.VAR_HANDLE);
     }
     return true;
   }
@@ -129,8 +129,13 @@
               timing.end();
 
               assert GlobalSyntheticsGeneratorVerifier.verifyExpectedClassesArePresent(appView);
-
-              ApplicationWriter.create(appView, options.getMarker()).write(executorService, app);
+              if (options.isGeneratingDex()) {
+                ApplicationWriter.create(appView, options.getMarker()).write(executorService, app);
+              } else {
+                assert options.isGeneratingClassFiles();
+                new CfApplicationWriter(appView, options.getMarker())
+                    .write(options.getClassFileConsumer(), app);
+              }
             } catch (ExecutionException e) {
               throw unwrapExecutionException(e);
             } catch (IOException e) {
@@ -202,11 +207,18 @@
     VarHandleDesugaring.ensureMethodHandlesLookupClass(
         appView, varHandleEventConsumer, synthesizingContext);
 
-    IRConverter converter = new IRConverter(appView);
-    converter.processSimpleSynthesizeMethods(methodsToProcess, executorService);
+    // Commit all the synthetics to the program and then convert as per D8.
+    // We must run proper D8 conversion as the global synthetics may give rise to additional
+    // synthetics as part of their implementation.
+    assert appView.getSyntheticItems().hasPendingSyntheticClasses();
+    appView.setAppInfo(
+        new AppInfo(
+            appView.appInfo().getSyntheticItems().commit(appView.app()),
+            appView.appInfo().getMainDexInfo()));
+
+    new PrimaryD8L8IRConverter(appView, Timing.empty()).convert(appView, executorService);
 
     appView
-        .withoutClassHierarchy()
         .setAppInfo(
             new AppInfo(
                 appView.appInfo().getSyntheticItems().commit(appView.app()),
@@ -221,11 +233,12 @@
         VarHandleDesugaringRewritingNamingLens.createVarHandleDesugaringRewritingNamingLens(
             appView));
 
-    // Add global synthetic classes for api stubs.
-    createAllApiStubs(appView, synthesizingContext, executorService);
+    if (appView.options().isGeneratingDex()) {
+      // Add global synthetic classes for api stubs.
+      createAllApiStubs(appView, synthesizingContext, executorService);
+    }
 
     appView
-        .withoutClassHierarchy()
         .setAppInfo(
             new AppInfo(
                 appView.appInfo().getSyntheticItems().commit(appView.app()),
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
index 44f54ba..5cc177b 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
@@ -7,21 +7,26 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.keepanno.annotations.KeepForApi;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
-import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Set;
 
 /**
  * Immutable command structure for an invocation of the {@link GlobalSyntheticsGenerator} compiler.
@@ -29,9 +34,10 @@
 @KeepForApi
 public final class GlobalSyntheticsGeneratorCommand {
 
-  private final ProgramConsumer programConsumer;
+  private final GlobalSyntheticsConsumer globalsConsumer;
   private final Reporter reporter;
   private final int minApiLevel;
+  private final boolean classfileDesugaringOnly;
 
   private final boolean printHelp;
   private final boolean printVersion;
@@ -41,10 +47,15 @@
   private final DexItemFactory factory = new DexItemFactory();
 
   private GlobalSyntheticsGeneratorCommand(
-      AndroidApp inputApp, ProgramConsumer programConsumer, Reporter reporter, int minApiLevel) {
+      AndroidApp inputApp,
+      GlobalSyntheticsConsumer globalsConsumer,
+      Reporter reporter,
+      int minApiLevel,
+      boolean classfileDesugaringOnly) {
     this.inputApp = inputApp;
-    this.programConsumer = programConsumer;
+    this.globalsConsumer = globalsConsumer;
     this.minApiLevel = minApiLevel;
+    this.classfileDesugaringOnly = classfileDesugaringOnly;
     this.reporter = reporter;
     this.printHelp = false;
     this.printVersion = false;
@@ -55,8 +66,9 @@
     this.printVersion = printVersion;
 
     this.inputApp = null;
-    this.programConsumer = null;
+    this.globalsConsumer = null;
     this.minApiLevel = AndroidApiLevel.B.getLevel();
+    this.classfileDesugaringOnly = false;
 
     reporter = new Reporter();
   }
@@ -130,9 +142,11 @@
     assert !internal.debug;
     assert !internal.minimalMainDex;
     internal.setMinApiLevel(AndroidApiLevel.getAndroidApiLevel(minApiLevel));
-    assert !internal.intermediate;
     assert internal.retainCompileTimeAnnotations;
-    internal.programConsumer = programConsumer;
+    internal.intermediate = true;
+    internal.programConsumer =
+        classfileDesugaringOnly ? new ThrowingCfConsumer() : new ThrowingDexConsumer();
+    internal.setGlobalSyntheticsConsumer(globalsConsumer);
 
     // Assert and fixup defaults.
     assert !internal.isShrinking();
@@ -148,6 +162,33 @@
     return internal;
   }
 
+  private static class ThrowingCfConsumer implements ClassFileConsumer {
+
+    @Override
+    public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+      throw new Unreachable("Unexpected attempt to write a non-global artifact");
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      // Nothing to do.
+    }
+  }
+
+  private static class ThrowingDexConsumer implements DexIndexedConsumer {
+
+    @Override
+    public void accept(
+        int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
+      throw new Unreachable("Unexpected attempt to write a non-global artifact");
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      // Nothing to do.
+    }
+  }
+
   /**
    * Builder for constructing a GlobalSyntheticsGeneratorCommand.
    *
@@ -156,9 +197,10 @@
   @KeepForApi
   public static class Builder {
 
-    private ProgramConsumer programConsumer = null;
+    private GlobalSyntheticsConsumer globalsConsumer = null;
     private final Reporter reporter;
     private int minApiLevel = AndroidApiLevel.B.getLevel();
+    private boolean classfileDesugaringOnly = false;
     private boolean printHelp = false;
     private boolean printVersion = false;
     private final AndroidApp.Builder appBuilder = AndroidApp.builder();
@@ -177,6 +219,11 @@
       return this;
     }
 
+    public Builder setClassfileDesugaringOnly(boolean value) {
+      this.classfileDesugaringOnly = value;
+      return this;
+    }
+
     /** Set the value of the print-help flag. */
     public Builder setPrintHelp(boolean printHelp) {
       this.printHelp = printHelp;
@@ -210,17 +257,32 @@
       return this;
     }
 
-    /** Set an output path to consume the resulting program. */
-    public Builder setProgramConsumerOutput(Path path) {
-      return setProgramConsumer(
-          FileUtils.isArchive(path)
-              ? new DexIndexedConsumer.ArchiveConsumer(path, false)
-              : new DexIndexedConsumer.DirectoryConsumer(path, false));
+    /** Set a destination to write the resulting global synthetics output file. */
+    public Builder setGlobalSyntheticsOutput(Path path) {
+      return setGlobalSyntheticsConsumer(
+          new GlobalSyntheticsConsumer() {
+
+            private boolean written = false;
+
+            @Override
+            public synchronized void accept(
+                ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
+              if (written) {
+                throw new Unreachable("Unexpected attempt to repeatedly write global synthetics");
+              }
+              written = true;
+              try {
+                Files.write(path, data.copyByteData(), StandardOpenOption.TRUNCATE_EXISTING);
+              } catch (IOException e) {
+                throw new RuntimeException(e);
+              }
+            }
+          });
     }
 
-    /** Set a consumer for obtaining the resulting program. */
-    public Builder setProgramConsumer(ProgramConsumer programConsumer) {
-      this.programConsumer = programConsumer;
+    /** Set a consumer for obtaining the resulting global synthetics output. */
+    public Builder setGlobalSyntheticsConsumer(GlobalSyntheticsConsumer globalsConsumer) {
+      this.globalsConsumer = globalsConsumer;
       return this;
     }
 
@@ -230,7 +292,7 @@
         return new GlobalSyntheticsGeneratorCommand(printHelp, printVersion);
       }
       return new GlobalSyntheticsGeneratorCommand(
-          appBuilder.build(), programConsumer, reporter, minApiLevel);
+          appBuilder.build(), globalsConsumer, reporter, minApiLevel, classfileDesugaringOnly);
     }
 
     private boolean isPrintHelpOrPrintVersion() {
@@ -241,9 +303,8 @@
       if (isPrintHelpOrPrintVersion()) {
         return;
       }
-      if (!(programConsumer instanceof DexIndexedConsumer)) {
-        reporter.error(
-            "GlobalSyntheticsGenerator does not support compiling to dex per class or class files");
+      if (globalsConsumer == null) {
+        reporter.error("GlobalSyntheticsGenerator does not support compiling without output");
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
index 9022e28..de61833 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
@@ -21,6 +21,7 @@
 
   private static final String LOWER_CASE_NAME = "globalsyntheticsgenerator";
   private static final String MIN_API_FLAG = "--min-api";
+  private static final String CLASSFILE_DESUGARING_MODE = "--classfile";
 
   private static final String USAGE_MESSAGE =
       StringUtils.lines("Usage: " + LOWER_CASE_NAME + " [options] " + "where options are:");
@@ -29,7 +30,14 @@
     return ImmutableList.<ParseFlagInfo>builder()
         .add(ParseFlagInfoImpl.getMinApi())
         .add(ParseFlagInfoImpl.getLib())
-        .add(ParseFlagInfoImpl.flag1("--output", "<dex-file>", "Output result in <dex-file>."))
+        .add(
+            ParseFlagInfoImpl.flag1(
+                "--output", "<globals-file>", "Output result in <globals-file>."))
+        .add(
+            ParseFlagInfoImpl.flag0(
+                "--classfile",
+                "Generate globals for only classfile to classfile desugaring.",
+                "(By default globals for both classfile and dex desugaring are generated)."))
         .add(ParseFlagInfoImpl.getVersion(LOWER_CASE_NAME))
         .add(ParseFlagInfoImpl.getHelp())
         .build();
@@ -98,6 +106,8 @@
         }
       } else if (arg.equals("--lib")) {
         builder.addLibraryFiles(Paths.get(nextArg));
+      } else if (arg.equals(CLASSFILE_DESUGARING_MODE)) {
+        builder.setClassfileDesugaringOnly(true);
       } else if (arg.startsWith("--")) {
         builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
       }
@@ -105,6 +115,6 @@
     if (outputPath == null) {
       outputPath = Paths.get(".");
     }
-    return builder.setProgramConsumerOutput(outputPath);
+    return builder.setGlobalSyntheticsOutput(outputPath);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 6694cb0..4fdbb35 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -89,6 +89,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -216,7 +217,12 @@
       Collection<DexProgramClass> allClasses = classes;
       classes = new ArrayList<>(allClasses.size());
       for (DexProgramClass clazz : allClasses) {
-        if (appView.getSyntheticItems().isGlobalSyntheticClass(clazz)) {
+        if (appView.getSyntheticItems().isGlobalSyntheticClassTransitive(clazz)) {
+          Consumer<DexProgramClass> globalSyntheticCreatedCallback =
+              appView.options().testing.globalSyntheticCreatedCallback;
+          if (globalSyntheticCreatedCallback != null) {
+            globalSyntheticCreatedCallback.accept(clazz);
+          }
           globalSynthetics.add(clazz);
         } else {
           classes.add(clazz);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 9d6a8b6..82f47fe 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -65,7 +65,6 @@
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.membervaluepropagation.D8MemberValuePropagation;
@@ -330,22 +329,6 @@
     return onWaveDoneActions != null;
   }
 
-  public void processSimpleSynthesizeMethods(
-      List<ProgramMethod> methods, ExecutorService executorService) throws ExecutionException {
-    ThreadUtils.processItems(
-        methods,
-        this::processAndFinalizeSimpleSynthesizedMethod,
-        options.getThreadingModule(),
-        executorService);
-  }
-
-  private void processAndFinalizeSimpleSynthesizedMethod(ProgramMethod method) {
-    IRCode code = method.buildIR(appView);
-    assert code != null;
-    new MoveResultRewriter(appView).run(code, Timing.empty());
-    removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
-  }
-
   /**
    * This will replace the Dex code in the method with the Dex code generated from the provided IR.
    *
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index ee0ce51..b5b5299 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -65,6 +65,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
@@ -161,8 +162,13 @@
       Collection<DexProgramClass> allClasses = classes;
       classes = new ArrayList<>(allClasses.size());
       for (DexProgramClass clazz : allClasses) {
-        if (appView.getSyntheticItems().isGlobalSyntheticClass(clazz)) {
+        if (appView.getSyntheticItems().isGlobalSyntheticClassTransitive(clazz)) {
           globalSyntheticClasses.add(clazz);
+          Consumer<DexProgramClass> globalSyntheticCreatedCallback =
+              appView.options().testing.globalSyntheticCreatedCallback;
+          if (globalSyntheticCreatedCallback != null) {
+            globalSyntheticCreatedCallback.accept(clazz);
+          }
         } else {
           classes.add(clazz);
         }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 61fd940..7f56b25 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -427,6 +427,26 @@
     return isGlobalSyntheticClass(clazz.getType());
   }
 
+  public boolean isGlobalSyntheticClassTransitive(DexProgramClass clazz) {
+    // Fast path the common case where the class is not synthetic at all.
+    if (!isSynthetic(clazz)) {
+      return false;
+    }
+    if (isGlobalSyntheticClass(clazz)) {
+      return true;
+    }
+    DexType type = clazz.getType();
+    for (SyntheticReference<?, ?, ?> reference : committed.getItems(type)) {
+      // Only a single context should exist for a globally derived synthetic, so return early.
+      return isGlobalSyntheticClass(reference.getContext().getSynthesizingContextType());
+    }
+    SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
+    if (definition != null) {
+      return isGlobalSyntheticClass(definition.getContext().getSynthesizingContextType());
+    }
+    return false;
+  }
+
   private static boolean isGlobalReferences(List<SyntheticProgramClassReference> references) {
     if (references == null) {
       return false;
@@ -1017,6 +1037,8 @@
     Consumer<DexProgramClass> globalSyntheticCreatedCallback =
         appView.options().testing.globalSyntheticCreatedCallback;
     if (globalSyntheticCreatedCallback != null) {
+      // These are also reported in the writer to ensure transitive classes are reported too.
+      // However, we keep the test reporting here too to fail fast on direct globals.
       globalSyntheticCreatedCallback.accept(globalSynthetic);
     }
     addGlobalContexts(globalSynthetic.getType(), contexts);
@@ -1046,6 +1068,7 @@
     SyntheticKind kind = kindSelector.select(naming);
     DexType type =
         SyntheticNaming.createInternalType(kind, outerContext, syntheticIdSupplier.get(), appView);
+
     SyntheticProgramClassBuilder classBuilder =
         new SyntheticProgramClassBuilder(type, kind, outerContext, appView.dexItemFactory());
     DexProgramClass clazz =
diff --git a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
index 1428459..4fd254a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalGlobalSyntheticsProgramConsumer.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.Version;
+import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
@@ -203,12 +204,28 @@
     public void finished(AppView<?> appView) {
       Map<DexType, Set<DexType>> globalsToContexts =
           appView.getSyntheticItems().getFinalGlobalSyntheticContexts(appView);
+      // The global synthetics generator is generating the world of globals, thus no contexts exist.
+      if (appView.options().tool.equals(Tool.GlobalSyntheticsGenerator)) {
+        assert globalsToContexts.isEmpty();
+        GlobalsFileBuilder builder = new GlobalsFileBuilder(getKind());
+        globalToBytes.forEach(
+            (globalType, globalBytes) -> {
+              builder.addGlobalSynthetic(globalType.toDescriptorString(), globalBytes);
+            });
+        try {
+          clientConsumer.accept(ByteDataView.of(builder.build()), null, appView.reporter());
+        } catch (IOException e) {
+          appView.reporter().error(new ExceptionDiagnostic(e));
+        }
+        clientConsumer.finished(appView.reporter());
+        return;
+      }
+      // Otherwise, there must be at least one context for any global.
       Map<DexType, Set<DexType>> contextToGlobals = new IdentityHashMap<>();
       for (DexType globalType : globalToBytes.keySet()) {
         // It would be good to assert that the global is a synthetic type, but the naming-lens
         // is not applied to SyntheticItems in AppView.
         Set<DexType> contexts = globalsToContexts.get(globalType);
-        // TODO(b/231598779): Contexts should never be null once fixed for records.
         assert contexts != null;
         assert !contexts.isEmpty();
         for (DexType contextType : contexts) {
diff --git a/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java b/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
index 6cab4d7..c3e49a5 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/globalsyntheticsgenerator/GlobalSyntheticsGeneratorTest.java
@@ -3,13 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.compilerapi.globalsyntheticsgenerator;
 
-import com.android.tools.r8.DexIndexedConsumer;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.GlobalSyntheticsConsumer;
 import com.android.tools.r8.GlobalSyntheticsGenerator;
 import com.android.tools.r8.GlobalSyntheticsGeneratorCommand;
-import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.compilerapi.CompilerApiTest;
 import com.android.tools.r8.compilerapi.CompilerApiTestRunner;
+import com.android.tools.r8.references.ClassReference;
 import org.junit.Test;
 
 public class GlobalSyntheticsGeneratorTest extends CompilerApiTestRunner {
@@ -27,8 +33,23 @@
   public void testGlobalSynthetics() throws Exception {
     new ApiTest(ApiTest.PARAMETERS)
         .run(
-            new DexIndexedConsumer.ArchiveConsumer(
-                temp.newFolder().toPath().resolve("output.zip")));
+            new GlobalSyntheticsConsumer() {
+              boolean hasOutput = false;
+
+              @Override
+              public void accept(
+                  ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
+                assertNull(context);
+                assertFalse(hasOutput);
+                assertTrue(data.getLength() > 0);
+                hasOutput = true;
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                assertTrue(hasOutput);
+              }
+            });
   }
 
   public static class ApiTest extends CompilerApiTest {
@@ -37,18 +58,25 @@
       super(parameters);
     }
 
-    public void run(ProgramConsumer programConsumer) throws Exception {
+    public void run(GlobalSyntheticsConsumer consumer) throws Exception {
       GlobalSyntheticsGenerator.run(
           GlobalSyntheticsGeneratorCommand.builder()
               .addLibraryFiles(getAndroidJar())
               .setMinApiLevel(33)
-              .setProgramConsumer(programConsumer)
+              .setGlobalSyntheticsConsumer(consumer)
               .build());
     }
 
     @Test
     public void testGlobalSynthetics() throws Exception {
-      run(DexIndexedConsumer.emptyConsumer());
+      run(
+          new GlobalSyntheticsConsumer() {
+            @Override
+            public void accept(
+                ByteDataView data, ClassReference context, DiagnosticsHandler handler) {
+              // Ignoring output in API test.
+            }
+          });
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
index ff2afbe..9ec7c0f 100644
--- a/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
+++ b/src/test/java/com/android/tools/r8/globalsynthetics/GlobalSyntheticsEnsureClassesOutputTest.java
@@ -5,22 +5,19 @@
 package com.android.tools.r8.globalsynthetics;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.GlobalSyntheticsGenerator;
 import com.android.tools.r8.GlobalSyntheticsGeneratorCommand;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import java.nio.file.Path;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.junit.Test;
@@ -31,91 +28,109 @@
 @RunWith(Parameterized.class)
 public class GlobalSyntheticsEnsureClassesOutputTest extends TestBase {
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withNoneRuntime().build();
+  private final Backend backend;
+
+  @Parameters(name = "{0}, backend:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
   }
 
-  public GlobalSyntheticsEnsureClassesOutputTest(TestParameters parameters) {
+  public GlobalSyntheticsEnsureClassesOutputTest(TestParameters parameters, Backend backend) {
     parameters.assertNoneRuntime();
+    this.backend = backend;
   }
 
   @Test
   public void testNumberOfClassesOnK() throws Exception {
-    Path output = temp.newFolder().toPath().resolve("output.zip");
+    GlobalSyntheticsTestingConsumer globalsConsumer = new GlobalSyntheticsTestingConsumer();
     GlobalSyntheticsGenerator.run(
         GlobalSyntheticsGeneratorCommand.builder()
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
             .setMinApiLevel(AndroidApiLevel.K.getLevel())
-            .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(output))
+            .setGlobalSyntheticsConsumer(globalsConsumer)
+            .setClassfileDesugaringOnly(backend.isCf())
             .build());
-    CodeInspector inspector = new CodeInspector(output);
-    assertEquals(1044, inspector.allClasses().size());
+    assertTrue(globalsConsumer.isSingleGlobal());
+    testForD8(backend)
+        .apply(
+            b ->
+                b.getBuilder().addGlobalSyntheticsResourceProviders(globalsConsumer.getProviders()))
+        .setMinApi(AndroidApiLevel.K)
+        .compile()
+        .inspect(
+            inspector -> assertEquals(backend.isDex() ? 1045 : 4, inspector.allClasses().size()));
   }
 
   @Test
   public void testNumberOfClassesOnLatest() throws Exception {
-    Path output = temp.newFolder().toPath().resolve("output.zip");
+    GlobalSyntheticsTestingConsumer globalsConsumer = new GlobalSyntheticsTestingConsumer();
     GlobalSyntheticsGenerator.run(
         GlobalSyntheticsGeneratorCommand.builder()
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
             .setMinApiLevel(AndroidApiLevel.LATEST.getLevel())
-            .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(output))
+            .setGlobalSyntheticsConsumer(globalsConsumer)
+            .setClassfileDesugaringOnly(backend.isCf())
             .build());
+    assertTrue(globalsConsumer.isSingleGlobal());
     Set<String> expectedInOutput = new HashSet<>();
     // The output contains a RecordTag type that is mapped back to the original java.lang.Record by
     // our codeinspector.
     expectedInOutput.add("Ljava/lang/Record;");
     expectedInOutput.add("Ljava/lang/invoke/VarHandle;");
     expectedInOutput.add("Ljava/lang/invoke/MethodHandles$Lookup;");
-    assertEquals(
-        expectedInOutput,
-        new CodeInspector(output)
-            .allClasses().stream()
-                .map(FoundClassSubject::getFinalDescriptor)
-                .collect(Collectors.toSet()));
+    testForD8(backend)
+        .apply(
+            b ->
+                b.getBuilder().addGlobalSyntheticsResourceProviders(globalsConsumer.getProviders()))
+        .setMinApi(AndroidApiLevel.LATEST)
+        .compile()
+        .inspect(
+            inspector ->
+                assertEquals(
+                    expectedInOutput,
+                    inspector.allClasses().stream()
+                        .map(FoundClassSubject::getFinalDescriptor)
+                        .collect(Collectors.toSet())));
   }
 
   @Test
   public void testClassFileListOutput() throws Exception {
     Set<String> generatedGlobalSynthetics = SetUtils.newConcurrentHashSet();
-    Path output = temp.newFolder().toPath().resolve("output.zip");
+    GlobalSyntheticsTestingConsumer globalsConsumer = new GlobalSyntheticsTestingConsumer();
     runGlobalSyntheticsGenerator(
         GlobalSyntheticsGeneratorCommand.builder()
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
             .setMinApiLevel(AndroidApiLevel.K.getLevel())
-            .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(output))
+            .setGlobalSyntheticsConsumer(globalsConsumer)
+            .setClassfileDesugaringOnly(backend.isCf())
             .build(),
         options ->
             options.testing.globalSyntheticCreatedCallback =
                 programClass -> {
-                  if (programClass
-                      .getClassReference()
-                      .getDescriptor()
-                      .equals(DexItemFactory.varHandleDescriptorString)) {
-                    // We emit a desugared var handle. Rewrite it here to allow checking for final
-                    // type names.
-                    generatedGlobalSynthetics.add(
-                        Reference.classFromDescriptor(
-                                DexItemFactory.desugarVarHandleDescriptorString)
-                            .getTypeName());
-                  } else if (programClass
-                      .getClassReference()
-                      .getDescriptor()
-                      .equals(DexItemFactory.methodHandlesLookupDescriptorString)) {
-                    // We emit a desugared var handle. Rewrite it here to allow checking for final
-                    // type names.
-                    generatedGlobalSynthetics.add(
-                        Reference.classFromDescriptor(
-                                DexItemFactory.desugarMethodHandlesLookupDescriptorString)
-                            .getTypeName());
-                  } else {
-                    generatedGlobalSynthetics.add(programClass.getTypeName());
-                  }
+                  String descriptor = programClass.getClassReference().getDescriptor();
+                  generatedGlobalSynthetics.add(
+                      descriptor
+                          .replace(
+                              "Ljava/lang/invoke/VarHandle",
+                              "Lcom/android/tools/r8/DesugarVarHandle")
+                          .replace(
+                              "Ljava/lang/invoke/MethodHandles$Lookup",
+                              "Lcom/android/tools/r8/DesugarMethodHandlesLookup"));
                 });
-    Set<String> readGlobalSynthetics =
-        new CodeInspector(output)
-            .allClasses().stream().map(FoundClassSubject::getFinalName).collect(Collectors.toSet());
-    assertEquals(generatedGlobalSynthetics, readGlobalSynthetics);
+    assertTrue(globalsConsumer.isSingleGlobal());
+    testForD8()
+        .apply(
+            b ->
+                b.getBuilder().addGlobalSyntheticsResourceProviders(globalsConsumer.getProviders()))
+        .setMinApi(AndroidApiLevel.K)
+        .compile()
+        .inspect(
+            inspector -> {
+              Set<String> readGlobalSynthetics =
+                  inspector.allClasses().stream()
+                      .map(FoundClassSubject::getFinalDescriptor)
+                      .collect(Collectors.toSet());
+              assertEquals(generatedGlobalSynthetics, readGlobalSynthetics);
+            });
   }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
index 3993296..adfdeaf 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -619,7 +619,10 @@
           GlobalSyntheticsGeneratorCommand.builder()
               .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
               .setMinApiLevel(minApiLevel)
-              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .setGlobalSyntheticsConsumer(
+                  (data, context, handler) -> {
+                    // Ignore the data and context, callback is hit below.
+                  })
               .build();
       InternalOptions internalOptions = command.getInternalOptions();
       internalOptions.testing.globalSyntheticCreatedCallback =
diff --git a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1 b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
index 5f24411..32f7368 100644
--- a/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
+++ b/third_party/binary_compatibility_tests/compiler_api_tests.tar.gz.sha1
@@ -1 +1 @@
-37caffb18e8b47c65512ff667a3d6e91c23e6734
\ No newline at end of file
+34436ff2aee451c7d8e5c0f28193134822238d4b
\ No newline at end of file