Support for finalizing synthetics more than once

The synthetic items currently track pending and committed items. At the point of finalization, there can be no pending synthetics and all committed items are finalized. It is an error to add any new synthetics after finalization.

This splits the committed items into two, the committed items that have not been finalized and the finalized committed items. This is used to support finalizing synthetics more than once. Synthetic finalization then finalizes all the non-finalized committed items and merges this into the finalized committed items.

One disadvantage is that synthetics introduced after the first synthetic finalization will not be subject to horizontal class merging if they end up being finalized after class merging.

Change-Id: I1d5093ed85b85d53f6068756cf170ded3f519914
diff --git a/src/main/java/com/android/tools/r8/graph/PrunedItems.java b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
index 3e96a43..00a3744 100644
--- a/src/main/java/com/android/tools/r8/graph/PrunedItems.java
+++ b/src/main/java/com/android/tools/r8/graph/PrunedItems.java
@@ -21,7 +21,6 @@
   private final DexApplication prunedApp;
   private final Set<DexReference> additionalPinnedItems;
   private final Map<DexMethod, ProgramMethod> fullyInlinedMethods;
-  private final Set<DexType> noLongerSyntheticItems;
   private final Set<DexType> removedClasses;
   private final Set<DexField> removedFields;
   private final Set<DexMethod> removedMethods;
@@ -30,14 +29,12 @@
       DexApplication prunedApp,
       Set<DexReference> additionalPinnedItems,
       Map<DexMethod, ProgramMethod> fullyInlinedMethods,
-      Set<DexType> noLongerSyntheticItems,
       Set<DexType> removedClasses,
       Set<DexField> removedFields,
       Set<DexMethod> removedMethods) {
     this.prunedApp = prunedApp;
     this.additionalPinnedItems = additionalPinnedItems;
     this.fullyInlinedMethods = fullyInlinedMethods;
-    this.noLongerSyntheticItems = noLongerSyntheticItems;
     this.removedClasses = removedClasses;
     this.removedFields = removedFields;
     this.removedMethods = removedMethods;
@@ -62,7 +59,6 @@
   public boolean isEmpty() {
     return additionalPinnedItems.isEmpty()
         && fullyInlinedMethods.isEmpty()
-        && noLongerSyntheticItems.isEmpty()
         && removedClasses.isEmpty()
         && removedFields.isEmpty()
         && removedMethods.isEmpty();
@@ -105,10 +101,6 @@
     return fullyInlinedMethods;
   }
 
-  public Set<DexType> getNoLongerSyntheticItems() {
-    return noLongerSyntheticItems;
-  }
-
   public boolean hasRemovedClasses() {
     return !removedClasses.isEmpty();
   }
@@ -143,7 +135,6 @@
 
     private final Set<DexReference> additionalPinnedItems;
     private Map<DexMethod, ProgramMethod> fullyInlinedMethods;
-    private final Set<DexType> noLongerSyntheticItems;
     private Set<DexType> removedClasses;
     private final Set<DexField> removedFields;
     private Set<DexMethod> removedMethods;
@@ -151,7 +142,6 @@
     Builder() {
       additionalPinnedItems = newEmptySet();
       fullyInlinedMethods = newEmptyMap();
-      noLongerSyntheticItems = newEmptySet();
       removedClasses = newEmptySet();
       removedFields = newEmptySet();
       removedMethods = newEmptySet();
@@ -161,7 +151,6 @@
       this();
       assert prunedItems.getFullyInlinedMethods().isEmpty();
       additionalPinnedItems.addAll(prunedItems.getAdditionalPinnedItems());
-      noLongerSyntheticItems.addAll(prunedItems.getNoLongerSyntheticItems());
       prunedApp = prunedItems.getPrunedApp();
       removedClasses.addAll(prunedItems.getRemovedClasses());
       removedFields.addAll(prunedItems.getRemovedFields());
@@ -202,19 +191,12 @@
       fullyInlinedMethods.clear();
     }
 
-    public Builder addNoLongerSyntheticItems(Set<DexType> noLongerSyntheticItems) {
-      this.noLongerSyntheticItems.addAll(noLongerSyntheticItems);
-      return this;
-    }
-
     public Builder addRemovedClass(DexType removedClass) {
-      this.noLongerSyntheticItems.add(removedClass);
       this.removedClasses.add(removedClass);
       return this;
     }
 
     public Builder addRemovedClasses(Set<DexType> removedClasses) {
-      this.noLongerSyntheticItems.addAll(removedClasses);
       this.removedClasses.addAll(removedClasses);
       return this;
     }
@@ -265,7 +247,6 @@
           prunedApp,
           additionalPinnedItems,
           fullyInlinedMethods,
-          noLongerSyntheticItems,
           removedClasses,
           removedFields,
           removedMethods);
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
index 889c466..7df2ab5 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -351,7 +351,9 @@
 
     public Consumer<DexType> addDependencyAllowSyntheticRoot(SyntheticItems syntheticItems) {
       return type -> {
-        assert !roots.contains(type) || syntheticItems.isCommittedSynthetic(type);
+        assert !roots.contains(type)
+            || syntheticItems.isCommittedSynthetic(type)
+            || syntheticItems.isFinalizedSynthetic(type);
         addDependencyIfNotRoot(type);
       };
     }
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
index ea5a020..5ce9d49 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
@@ -29,6 +29,7 @@
   final DexApplication application;
   final SyntheticItems.State state;
   final CommittedSyntheticsCollection committed;
+  final CommittedSyntheticsCollection finalized;
   final ImmutableList<DexType> committedProgramTypes;
   final GlobalSyntheticsStrategy globalSyntheticsStrategy;
 
@@ -36,11 +37,13 @@
       State state,
       DexApplication application,
       CommittedSyntheticsCollection committed,
+      CommittedSyntheticsCollection finalized,
       ImmutableList<DexType> committedProgramTypes,
       GlobalSyntheticsStrategy globalSyntheticsStrategy) {
     this.state = state;
     this.application = application;
     this.committed = committed;
+    this.finalized = finalized;
     this.committedProgramTypes = committedProgramTypes;
     this.globalSyntheticsStrategy = globalSyntheticsStrategy;
     assert committed.verifyTypesAreInApp(application);
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
index 8663f34..fa6cff0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -24,6 +24,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -32,7 +33,7 @@
  * <p>This structure is to make it easier to pass the items from SyntheticItems to CommittedItems
  * and back while also providing a builder for updating the committed synthetics.
  */
-class CommittedSyntheticsCollection {
+public class CommittedSyntheticsCollection {
 
   static class Builder {
     private final CommittedSyntheticsCollection parent;
@@ -179,6 +180,37 @@
     assert verifySyntheticInputsSubsetOfSynthetics();
   }
 
+  public CommittedSyntheticsCollection merge(
+      ImmutableMap<DexType, List<SyntheticMethodReference>> methods,
+      ImmutableMap<DexType, List<SyntheticProgramClassReference>> classes,
+      ImmutableMap<DexType, Set<DexType>> globalContexts,
+      ImmutableSet<DexType> syntheticInputs) {
+    if (isEmpty()) {
+      return new CommittedSyntheticsCollection(methods, classes, globalContexts, syntheticInputs);
+    }
+    return new CommittedSyntheticsCollection(
+        ImmutableMap.<DexType, List<SyntheticMethodReference>>builderWithExpectedSize(
+                this.methods.size() + methods.size())
+            .putAll(this.methods)
+            .putAll(methods)
+            .build(),
+        ImmutableMap.<DexType, List<SyntheticProgramClassReference>>builderWithExpectedSize(
+                this.classes.size() + classes.size())
+            .putAll(this.classes)
+            .putAll(classes)
+            .build(),
+        ImmutableMap.<DexType, Set<DexType>>builderWithExpectedSize(
+                this.globalContexts.size() + globalContexts.size())
+            .putAll(this.globalContexts)
+            .putAll(globalContexts)
+            .build(),
+        ImmutableSet.<DexType>builderWithExpectedSize(
+                this.syntheticInputs.size() + syntheticInputs.size())
+            .addAll(this.syntheticInputs)
+            .addAll(syntheticInputs)
+            .build());
+  }
+
   private boolean verifySyntheticInputsSubsetOfSynthetics() {
     Set<DexType> synthetics =
         ImmutableSet.<DexType>builder().addAll(methods.keySet()).addAll(classes.keySet()).build();
@@ -236,6 +268,32 @@
     return syntheticInputs.contains(type);
   }
 
+  public void forEachClass(BiConsumer<DexType, List<SyntheticProgramClassReference>> consumer) {
+    classes.forEach(consumer);
+  }
+
+  public void forEachClassFlattened(BiConsumer<DexType, SyntheticProgramClassReference> consumer) {
+    classes.forEach(
+        (type, references) -> {
+          for (SyntheticProgramClassReference reference : references) {
+            consumer.accept(type, reference);
+          }
+        });
+  }
+
+  public void forEachMethod(BiConsumer<DexType, List<SyntheticMethodReference>> consumer) {
+    methods.forEach(consumer);
+  }
+
+  public void forEachMethodFlattened(BiConsumer<DexType, SyntheticMethodReference> consumer) {
+    methods.forEach(
+        (type, references) -> {
+          for (SyntheticMethodReference reference : references) {
+            consumer.accept(type, reference);
+          }
+        });
+  }
+
   public Set<DexType> getContextsForGlobal(DexType globalSynthetic) {
     return globalContexts.get(globalSynthetic);
   }
@@ -267,7 +325,7 @@
   }
 
   CommittedSyntheticsCollection pruneItems(PrunedItems prunedItems) {
-    Set<DexType> removed = prunedItems.getNoLongerSyntheticItems();
+    Set<DexType> removed = prunedItems.getRemovedClasses();
     if (removed.isEmpty()) {
       return this;
     }
@@ -296,8 +354,6 @@
     }
     // Global synthetic contexts are only collected for per-file modes which only prune synthetic
     // items, not inputs.
-    assert globalContexts.isEmpty()
-        || prunedItems.getNoLongerSyntheticItems().size() == prunedItems.getRemovedClasses().size();
     return changed ? builder.build() : this;
   }
 
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 d1db8ba..f7cc1e5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -160,10 +160,15 @@
 
   private final SyntheticItems synthetics;
   private final CommittedSyntheticsCollection committed;
+  private final CommittedSyntheticsCollection finalized;
 
-  SyntheticFinalization(SyntheticItems synthetics, CommittedSyntheticsCollection committed) {
+  SyntheticFinalization(
+      SyntheticItems synthetics,
+      CommittedSyntheticsCollection committed,
+      CommittedSyntheticsCollection finalized) {
     this.synthetics = synthetics;
     this.committed = committed;
+    this.finalized = finalized;
   }
 
   public static void finalize(
@@ -286,11 +291,9 @@
         new CommittedItems(
             State.FINALIZED,
             application,
-            new CommittedSyntheticsCollection(
-                finalMethods,
-                finalClasses,
-                committed.getGlobalContexts(),
-                finalInputSynthetics),
+            CommittedSyntheticsCollection.empty(),
+            finalized.merge(
+                finalMethods, finalClasses, committed.getGlobalContexts(), finalInputSynthetics),
             ImmutableList.of(),
             synthetics.getGlobalSyntheticsStrategy()),
         syntheticFinalizationGraphLens,
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 2f4e348..942465b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -77,7 +77,8 @@
     if (definition != null) {
       return definition.getKind().isShareable();
     }
-    Iterable<SyntheticReference<?, ?, ?>> references = committed.getItems(clazz.type);
+    Iterable<SyntheticReference<?, ?, ?>> references =
+        Iterables.concat(committed.getItems(clazz.type), finalized.getItems(clazz.getType()));
     Iterator<SyntheticReference<?, ?, ?>> iterator = references.iterator();
     if (iterator.hasNext()) {
       boolean sharable = iterator.next().getKind().isShareable();
@@ -218,38 +219,38 @@
   private final State state;
   private final SyntheticNaming naming;
   private final CommittedSyntheticsCollection committed;
+  private final CommittedSyntheticsCollection finalized;
   private final PendingSynthetics pending = new PendingSynthetics();
   private final ContextsForGlobalSynthetics globalContexts;
   private final GlobalSyntheticsStrategy globalSyntheticsStrategy;
 
-  @SuppressWarnings("ReferenceEquality")
   public Set<DexType> collectSyntheticsFromContext(DexType context) {
     Set<DexType> result = Sets.newIdentityHashSet();
-    committed
-        .getMethods()
-        .forEach(
-            (synthetic, methodReferences) -> {
-              methodReferences.forEach(
-                  methodReference -> {
-                    if (methodReference.getContext().getSynthesizingContextType() == context) {
-                      result.add(synthetic);
-                    }
-                  });
-            });
-    committed
-        .getClasses()
-        .forEach(
-            (synthetic, classReferences) -> {
-              classReferences.forEach(
-                  classReference -> {
-                    if (classReference.getContext().getSynthesizingContextType() == context) {
-                      result.add(synthetic);
-                    }
-                  });
-            });
+    internalCollectSyntheticsFromContext(context, committed, result);
+    internalCollectSyntheticsFromContext(context, finalized, result);
     return result;
   }
 
+  private static void internalCollectSyntheticsFromContext(
+      DexType context, CommittedSyntheticsCollection committed, Set<DexType> result) {
+    committed.forEachMethodFlattened(
+        (synthetic, methodReference) -> {
+          if (context.isIdenticalTo(methodReference.getContext().getSynthesizingContextType())) {
+            result.add(synthetic);
+          }
+        });
+    committed.forEachClassFlattened(
+        (synthetic, classReference) -> {
+          if (context.isIdenticalTo(classReference.getContext().getSynthesizingContextType())) {
+            result.add(synthetic);
+          }
+        });
+  }
+
+  public CommittedSyntheticsCollection getCommitted() {
+    return committed;
+  }
+
   public SyntheticNaming getNaming() {
     return naming;
   }
@@ -265,6 +266,7 @@
         State.OPEN,
         application,
         CommittedSyntheticsCollection.empty(),
+        CommittedSyntheticsCollection.empty(),
         ImmutableList.of(),
         globalSyntheticsStrategy);
   }
@@ -274,6 +276,7 @@
     this(
         commit.state,
         commit.committed,
+        commit.finalized,
         commit.globalSyntheticsStrategy,
         commit.getApplication().dexItemFactory().getSyntheticNaming());
   }
@@ -281,10 +284,12 @@
   private SyntheticItems(
       State state,
       CommittedSyntheticsCollection committed,
+      CommittedSyntheticsCollection finalized,
       GlobalSyntheticsStrategy globalSyntheticsStrategy,
       SyntheticNaming naming) {
     this.state = state;
     this.committed = committed;
+    this.finalized = finalized;
     this.naming = naming;
     this.globalContexts = globalSyntheticsStrategy.getStrategy();
     this.globalSyntheticsStrategy = globalSyntheticsStrategy;
@@ -293,17 +298,23 @@
   public Map<DexType, Set<DexType>> getFinalGlobalSyntheticContexts(AppView<?> appView) {
     assert isFinalized();
     DexItemFactory factory = appView.dexItemFactory();
-    ImmutableMap<DexType, Set<DexType>> globalContexts = committed.getGlobalContexts();
+    ImmutableMap<DexType, Set<DexType>> committedGlobalContexts = committed.getGlobalContexts();
+    ImmutableMap<DexType, Set<DexType>> finalizedGlobalContexts = finalized.getGlobalContexts();
     NamingLens namingLens = appView.getNamingLens();
-    Map<DexType, Set<DexType>> rewritten = new IdentityHashMap<>(globalContexts.size());
-    globalContexts.forEach(
-        (global, contexts) -> {
-          Set<DexType> old =
-              rewritten.put(
-                  namingLens.lookupType(global, factory),
-                  SetUtils.mapIdentityHashSet(contexts, c -> namingLens.lookupType(c, factory)));
-          assert old == null;
-        });
+    Map<DexType, Set<DexType>> rewritten =
+        new IdentityHashMap<>(committedGlobalContexts.size() + finalizedGlobalContexts.size());
+    Iterables.concat(committedGlobalContexts.entrySet(), finalizedGlobalContexts.entrySet())
+        .forEach(
+            entry -> {
+              var global = entry.getKey();
+              var contexts = entry.getValue();
+              Set<DexType> old =
+                  rewritten.put(
+                      namingLens.lookupType(global, factory),
+                      SetUtils.mapIdentityHashSet(
+                          contexts, c -> namingLens.lookupType(c, factory)));
+              assert old == null;
+            });
     return rewritten;
   }
 
@@ -311,6 +322,7 @@
     // Collecting synthetic items must be the very first task after application build.
     SyntheticItems synthetics = appView.getSyntheticItems();
     assert synthetics.committed.isEmpty();
+    assert synthetics.finalized.isEmpty();
     assert synthetics.pending.isEmpty();
     CommittedSyntheticsCollection.Builder builder = synthetics.committed.builder();
     if (appView.options().intermediate) {
@@ -336,6 +348,7 @@
             synthetics.state,
             appView.appInfo().app(),
             committed,
+            synthetics.finalized,
             ImmutableList.of(),
             synthetics.globalSyntheticsStrategy);
     if (appView.appInfo().hasClassHierarchy()) {
@@ -463,6 +476,10 @@
     return committed.containsType(type);
   }
 
+  public boolean isFinalizedSynthetic(DexType type) {
+    return finalized.containsType(type);
+  }
+
   public boolean isPendingSynthetic(DexType type) {
     return pending.containsType(type);
   }
@@ -472,7 +489,7 @@
   }
 
   public boolean isSynthetic(DexType type) {
-    return committed.containsType(type) || pending.definitions.containsKey(type);
+    return isCommittedSynthetic(type) || isFinalizedSynthetic(type) || isPendingSynthetic(type);
   }
 
   public boolean isSubjectToKeepRules(DexProgramClass clazz) {
@@ -493,7 +510,23 @@
     if (definition != null) {
       return definition.getKind().isGlobal();
     }
-    return isGlobalReferences(committed.getClasses().get(type));
+    List<SyntheticProgramClassReference> committedReferences =
+        committed.getClasses().getOrDefault(type, Collections.emptyList());
+    List<SyntheticProgramClassReference> finalizedReferences =
+        finalized.getClasses().getOrDefault(type, Collections.emptyList());
+    SyntheticProgramClassReference singleReference;
+    if (committedReferences.size() + finalizedReferences.size() == 1) {
+      singleReference =
+          committedReferences.size() == 1 ? committedReferences.get(0) : finalizedReferences.get(0);
+    } else {
+      singleReference = null;
+    }
+    if (singleReference != null && singleReference.getKind().isGlobal()) {
+      return true;
+    }
+    assert verifyNoGlobals(committedReferences);
+    assert verifyNoGlobals(finalizedReferences);
+    return false;
   }
 
   public boolean isGlobalSyntheticClass(DexProgramClass clazz) {
@@ -513,6 +546,10 @@
       // Only a single context should exist for a globally derived synthetic, so return early.
       return isGlobalSyntheticClass(reference.getContext().getSynthesizingContextType());
     }
+    for (SyntheticReference<?, ?, ?> reference : finalized.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());
@@ -520,17 +557,6 @@
     return false;
   }
 
-  private static boolean isGlobalReferences(List<SyntheticProgramClassReference> references) {
-    if (references == null) {
-      return false;
-    }
-    if (references.size() == 1 && references.get(0).getKind().isGlobal()) {
-      return true;
-    }
-    assert verifyNoGlobals(references);
-    return false;
-  }
-
   private static boolean verifyNoGlobals(List<SyntheticProgramClassReference> references) {
     for (SyntheticProgramClassReference reference : references) {
       assert !reference.getKind().isGlobal();
@@ -545,12 +571,16 @@
 
   public boolean isSyntheticOfKind(DexType type, SyntheticKindSelector kindSelector) {
     SyntheticKind kind = kindSelector.select(naming);
-    return pending.containsTypeOfKind(type, kind) || committed.containsTypeOfKind(type, kind);
+    return pending.containsTypeOfKind(type, kind)
+        || committed.containsTypeOfKind(type, kind)
+        || finalized.containsTypeOfKind(type, kind);
   }
 
   public Iterable<SyntheticKind> getSyntheticKinds(DexType type) {
     Iterable<SyntheticKind> references =
-        IterableUtils.transform(committed.getItems(type), SyntheticReference::getKind);
+        Iterables.concat(
+            Iterables.transform(committed.getItems(type), SyntheticReference::getKind),
+            Iterables.transform(finalized.getItems(type), SyntheticReference::getKind));
     SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
     if (definition != null) {
       references = Iterables.concat(references, IterableUtils.singleton(definition.getKind()));
@@ -559,7 +589,8 @@
   }
 
   boolean isSyntheticInput(DexProgramClass clazz) {
-    return committed.containsSyntheticInput(clazz.getType());
+    return committed.containsSyntheticInput(clazz.getType())
+        || finalized.containsSyntheticInput(clazz.getType());
   }
 
   public FeatureSplit getContextualFeatureSplitOrDefault(DexType type, FeatureSplit defaultValue) {
@@ -591,6 +622,9 @@
     for (SyntheticReference<?, ?, ?> reference : committed.getItems(type)) {
       consumer.accept(reference.getContext());
     }
+    for (SyntheticReference<?, ?, ?> reference : finalized.getItems(type)) {
+      consumer.accept(reference.getContext());
+    }
     SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
     if (definition != null) {
       consumer.accept(definition.getContext());
@@ -648,6 +682,7 @@
   }
 
   public boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
+    assert finalized.isEmpty();
     for (SyntheticMethodReference reference :
         committed.getMethods().getOrDefault(method.getHolderType(), Collections.emptyList())) {
       if (reference.getKind().equals(naming.STATIC_INTERFACE_CALL)) {
@@ -667,6 +702,7 @@
       DexProgramClass clazz,
       Predicate<DexProgramClass> ifIsLambda,
       Predicate<DexProgramClass> ifNotLambda) {
+    assert finalized.isEmpty();
     Iterable<SyntheticReference<?, ?, ?>> references = committed.getItems(clazz.getType());
     SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(clazz.getType());
     if (definition != null) {
@@ -693,7 +729,8 @@
     if (existingDefinition != null) {
       return existingDefinition.getContext();
     }
-    Iterable<SyntheticReference<?, ?, ?>> existingReferences = committed.getItems(contextType);
+    Iterable<SyntheticReference<?, ?, ?>> existingReferences =
+        Iterables.concat(committed.getItems(contextType), finalized.getItems(contextType));
     if (!Iterables.isEmpty(existingReferences)) {
       // Use a deterministic synthesizing context from the set of contexts.
       return IterableUtils.min(
@@ -1171,7 +1208,6 @@
       AppView<?> appView,
       Consumer<SyntheticMethodBuilder> fn,
       Supplier<String> syntheticIdSupplier) {
-    assert !isFinalized();
     // Obtain the outer synthesizing context in the case the context itself is synthetic.
     // This is to ensure a flat input-type -> synthetic-item mapping.
     SynthesizingContext outerContext = getSynthesizingContext(context, appView);
@@ -1206,7 +1242,14 @@
   }
 
   public CommittedItems commitPrunedItems(PrunedItems prunedItems) {
-    return commit(prunedItems, pending, globalContexts, committed, state, globalSyntheticsStrategy);
+    return commit(
+        prunedItems,
+        pending,
+        globalContexts,
+        committed,
+        finalized,
+        state,
+        globalSyntheticsStrategy);
   }
 
   public CommittedItems commitRewrittenWithLens(
@@ -1219,6 +1262,7 @@
             pending,
             globalContexts,
             committed.rewriteWithLens(lens, timing),
+            finalized.rewriteWithLens(lens, timing),
             state,
             globalSyntheticsStrategy);
     timing.end();
@@ -1230,15 +1274,16 @@
       PendingSynthetics pending,
       ContextsForGlobalSynthetics globalContexts,
       CommittedSyntheticsCollection committed,
+      CommittedSyntheticsCollection finalized,
       State state,
       GlobalSyntheticsStrategy globalSyntheticsStrategy) {
     DexApplication application = prunedItems.getPrunedApp();
-    Set<DexType> removedClasses = prunedItems.getNoLongerSyntheticItems();
-    CommittedSyntheticsCollection.Builder builder = committed.builder();
+    Set<DexType> removedClasses = prunedItems.getRemovedClasses();
+    CommittedSyntheticsCollection.Builder committedBuilder = committed.builder();
     // Compute the synthetic additions and add them to the application.
     ImmutableList<DexType> committedProgramTypes;
     DexApplication amendedApplication;
-    if (pending.definitions.isEmpty()) {
+    if (pending.isEmpty()) {
       committedProgramTypes = ImmutableList.of();
       amendedApplication = application;
     } else {
@@ -1258,30 +1303,32 @@
             assert definition.isClasspathDefinition();
             appBuilder.addClasspathClass(definition.asClasspathDefinition().getHolder());
           }
-          builder.addItem(definition);
+          committedBuilder.addItem(definition);
         }
       }
-      builder.addGlobalContexts(globalContexts);
+      committedBuilder.addGlobalContexts(globalContexts);
       committedProgramTypes = committedProgramTypesBuilder.build();
       amendedApplication = appBuilder.build();
     }
     return new CommittedItems(
         state,
         amendedApplication,
-        builder.build().pruneItems(prunedItems),
+        committedBuilder.build().pruneItems(prunedItems),
+        finalized.pruneItems(prunedItems),
         committedProgramTypes,
         globalSyntheticsStrategy);
   }
 
   public void writeAttributeIfIntermediateSyntheticClass(
       ClassWriter writer, DexProgramClass clazz, AppView<?> appView) {
+    assert committed.isEmpty();
     if (appView.options().testing.disableSyntheticMarkerAttributeWriting) {
       return;
     }
     if (!appView.options().intermediate || !appView.options().isGeneratingClassFiles()) {
       return;
     }
-    Iterator<SyntheticReference<?, ?, ?>> it = committed.getItems(clazz.getType()).iterator();
+    Iterator<SyntheticReference<?, ?, ?>> it = finalized.getItems(clazz.getType()).iterator();
     if (it.hasNext()) {
       SyntheticKind kind = it.next().getKind();
       // When compiling intermediates there should not be any mergings as they may invalidate the
@@ -1296,19 +1343,20 @@
 
   Result computeFinalSynthetics(AppView<?> appView, Timing timing) {
     assert !hasPendingSyntheticClasses();
-    return new SyntheticFinalization(this, committed).computeFinalSynthetics(appView, timing);
+    return new SyntheticFinalization(this, committed, finalized)
+        .computeFinalSynthetics(appView, timing);
   }
 
-  @SuppressWarnings("ReferenceEquality")
   public void reportSyntheticsInformation(SyntheticInfoConsumer consumer) {
     assert isFinalized();
+    assert committed.isEmpty();
     Map<DexType, DexType> seen = new IdentityHashMap<>();
-    committed.forEachItem(
+    finalized.forEachItem(
         ref -> {
           DexType holder = ref.getHolder();
           DexType context = ref.getContext().getSynthesizingContextType();
           DexType old = seen.put(holder, context);
-          assert old == null || old == context;
+          assert old == null || old.isIdenticalTo(context);
           if (old == null) {
             consumer.acceptSyntheticInfo(new SyntheticInfoConsumerDataImpl(holder, context));
           }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
index edfdb92..fb1b109 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -17,7 +17,7 @@
  *
  * <p>This class is internal to the synthetic items collection, thus package-protected.
  */
-class SyntheticMethodDefinition
+public class SyntheticMethodDefinition
     extends SyntheticDefinition<
         SyntheticMethodReference, SyntheticMethodDefinition, DexProgramClass>
     implements SyntheticProgramDefinition {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index e649f48..e6b6760 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -18,7 +18,7 @@
  *
  * <p>This class is internal to the synthetic items collection, thus package-protected.
  */
-class SyntheticMethodReference
+public class SyntheticMethodReference
     extends SyntheticReference<SyntheticMethodReference, SyntheticMethodDefinition, DexProgramClass>
     implements SyntheticProgramReference, Rewritable<SyntheticMethodReference> {
   final DexMethod method;
@@ -39,7 +39,7 @@
   }
 
   @Override
-  SyntheticMethodDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
+  public SyntheticMethodDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
     DexClass clazz = definitions.apply(method.holder);
     if (clazz == null) {
       return null;
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 83beaff..676989f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -86,6 +86,8 @@
   public final SyntheticKind OBJECT_CLONE_OUTLINE = generator.forSingleMethod("ObjectCloneOutline");
   public final SyntheticKind TO_STRING_IF_NOT_NULL =
       generator.forSingleMethodWithGlobalMerging("ToStringIfNotNull");
+  public final SyntheticKind THROW_CCE_IF_NOT_EQUALS =
+      generator.forSingleMethodWithGlobalMerging("ThrowCCEIfNotEquals");
   public final SyntheticKind THROW_CCE_IF_NOT_NULL =
       generator.forSingleMethodWithGlobalMerging("ThrowCCEIfNotNull");
   public final SyntheticKind NON_NULL = generator.forSingleMethodWithGlobalMerging("NonNull");