Generalize ArtProfileAdditions to work on abstract profile

Bug: b/271822426
Change-Id: I4f42c5639f930a1292d8e8a7d7cc1ed819601e60
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
index 67a2116..fe54f24 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfile.java
@@ -110,9 +110,8 @@
       MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder =
           new MissingStartupProfileItemsDiagnostic.Builder(definitions)
               .setOrigin(startupProfileProvider.getOrigin());
-      NonEmptyStartupProfile.Builder startupProfileBuilder =
-          NonEmptyStartupProfile.builder(
-              options, missingItemsDiagnosticBuilder, startupProfileProvider);
+      StartupProfile.Builder startupProfileBuilder =
+          StartupProfile.builder(options, missingItemsDiagnosticBuilder, startupProfileProvider);
       startupProfileProvider.getStartupProfile(startupProfileBuilder);
       startupProfiles.add(startupProfileBuilder.build());
       if (missingItemsDiagnosticBuilder.hasMissingStartupItems()) {
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
index a815ecb..c5ec563 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupProfileProviderUtils.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
-import com.android.tools.r8.experimental.startup.profile.NonEmptyStartupProfile;
 import com.android.tools.r8.experimental.startup.profile.StartupItem;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
@@ -54,9 +53,8 @@
     // Do not report missing items.
     MissingStartupProfileItemsDiagnostic.Builder missingItemsDiagnosticBuilder =
         MissingStartupProfileItemsDiagnostic.Builder.nop();
-    NonEmptyStartupProfile.Builder startupProfileBuilder =
-        NonEmptyStartupProfile.builder(
-            options, missingItemsDiagnosticBuilder, startupProfileProvider);
+    StartupProfile.Builder startupProfileBuilder =
+        StartupProfile.builder(options, missingItemsDiagnosticBuilder, startupProfileProvider);
     // Do not report warnings for lines that cannot be parsed.
     startupProfileBuilder.setIgnoreWarnings();
     // Populate the startup profile builder.
diff --git a/src/main/java/com/android/tools/r8/profile/AbstractProfile.java b/src/main/java/com/android/tools/r8/profile/AbstractProfile.java
new file mode 100644
index 0000000..8265aa2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/AbstractProfile.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2023, 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.profile;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.ThrowingConsumer;
+
+public interface AbstractProfile<
+    ClassRule extends AbstractProfileClassRule, MethodRule extends AbstractProfileMethodRule> {
+
+  boolean containsClassRule(DexType type);
+
+  boolean containsMethodRule(DexMethod method);
+
+  <E1 extends Exception, E2 extends Exception> void forEachRule(
+      ThrowingConsumer<ClassRule, E1> classRuleConsumer,
+      ThrowingConsumer<MethodRule, E2> methodRuleConsumer)
+      throws E1, E2;
+
+  ClassRule getClassRule(DexType type);
+
+  MethodRule getMethodRule(DexMethod method);
+
+  interface Builder<
+      ClassRule extends AbstractProfileClassRule,
+      MethodRule extends AbstractProfileMethodRule,
+      Profile extends AbstractProfile<ClassRule, MethodRule>,
+      ProfileBuilder extends Builder<ClassRule, MethodRule, Profile, ProfileBuilder>> {
+
+    ProfileBuilder addRule(AbstractProfileRule profileRule);
+
+    ProfileBuilder addClassRule(ClassRule classRule);
+
+    ProfileBuilder addMethodRule(MethodRule methodRule);
+
+    Profile build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/AbstractProfileClassRule.java b/src/main/java/com/android/tools/r8/profile/AbstractProfileClassRule.java
new file mode 100644
index 0000000..d948afb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/AbstractProfileClassRule.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2023, 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.profile;
+
+import com.android.tools.r8.graph.DexType;
+
+public interface AbstractProfileClassRule extends AbstractProfileRule {
+
+  DexType getReference();
+
+  interface Builder<ClassRule extends AbstractProfileClassRule> {
+
+    ClassRule build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/AbstractProfileMethodRule.java b/src/main/java/com/android/tools/r8/profile/AbstractProfileMethodRule.java
new file mode 100644
index 0000000..ddb1225
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/AbstractProfileMethodRule.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2023, 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.profile;
+
+import com.android.tools.r8.graph.DexMethod;
+
+public interface AbstractProfileMethodRule extends AbstractProfileRule {
+
+  DexMethod getReference();
+
+  interface Builder<
+      MethodRule extends AbstractProfileMethodRule,
+      MethodRuleBuilder extends Builder<MethodRule, MethodRuleBuilder>> {
+
+    boolean isGreaterThanOrEqualTo(MethodRuleBuilder methodRuleBuilder);
+
+    MethodRuleBuilder join(MethodRule methodRule);
+
+    MethodRuleBuilder join(MethodRuleBuilder methodRuleBuilder);
+
+    MethodRuleBuilder setMethod(DexMethod method);
+
+    MethodRule build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/AbstractProfileRule.java b/src/main/java/com/android/tools/r8/profile/AbstractProfileRule.java
new file mode 100644
index 0000000..c9f0d4c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/AbstractProfileRule.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2023, 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.profile;
+
+import com.android.tools.r8.profile.art.ArtProfileRule;
+
+public interface AbstractProfileRule {
+
+  @SuppressWarnings("unchecked")
+  default ArtProfileRule asArtProfileRule() {
+    return (ArtProfileRule) this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
index 8fb11ef..a1761ec 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfile.java
@@ -19,6 +19,8 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.profile.AbstractProfile;
+import com.android.tools.r8.profile.AbstractProfileRule;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -33,7 +35,7 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 
-public class ArtProfile {
+public class ArtProfile implements AbstractProfile<ArtProfileClassRule, ArtProfileMethodRule> {
 
   private final Map<DexReference, ArtProfileRule> rules;
 
@@ -50,10 +52,12 @@
     return new Builder(artProfileProvider, options);
   }
 
+  @Override
   public boolean containsClassRule(DexType type) {
     return rules.containsKey(type);
   }
 
+  @Override
   public boolean containsMethodRule(DexMethod method) {
     return rules.containsKey(method);
   }
@@ -65,6 +69,7 @@
     }
   }
 
+  @Override
   public <E1 extends Exception, E2 extends Exception> void forEachRule(
       ThrowingConsumer<ArtProfileClassRule, E1> classRuleConsumer,
       ThrowingConsumer<ArtProfileMethodRule, E2> methodRuleConsumer)
@@ -74,10 +79,12 @@
     }
   }
 
+  @Override
   public ArtProfileClassRule getClassRule(DexType type) {
     return (ArtProfileClassRule) rules.get(type);
   }
 
+  @Override
   public ArtProfileMethodRule getMethodRule(DexMethod method) {
     return (ArtProfileMethodRule) rules.get(method);
   }
@@ -259,7 +266,9 @@
                 methodRule.getMethodReference(), methodRule.getMethodRuleInfo()));
   }
 
-  public static class Builder implements ArtProfileBuilder {
+  public static class Builder
+      implements ArtProfileBuilder,
+          AbstractProfile.Builder<ArtProfileClassRule, ArtProfileMethodRule, ArtProfile, Builder> {
 
     private final ArtProfileProvider artProfileProvider;
     private final DexItemFactory dexItemFactory;
@@ -281,19 +290,34 @@
       this.reporter = options.reporter;
     }
 
-    public Builder addRule(ArtProfileRule rule) {
-      assert !rules.containsKey(rule.getReference());
-      rule.accept(
-          classRule -> rules.put(classRule.getType(), classRule),
-          methodRule -> rules.put(methodRule.getMethod(), methodRule));
+    @Override
+    public Builder addRule(AbstractProfileRule rule) {
+      if (rule instanceof ArtProfileClassRule) {
+        return addClassRule((ArtProfileClassRule) rule);
+      } else {
+        assert rule instanceof ArtProfileMethodRule;
+        return addMethodRule((ArtProfileMethodRule) rule);
+      }
+    }
+
+    @Override
+    public Builder addClassRule(ArtProfileClassRule classRule) {
+      assert !rules.containsKey(classRule.getReference());
+      rules.put(classRule.getType(), classRule);
       return this;
     }
 
-    public Builder addRules(Collection<ArtProfileRule> rules) {
-      rules.forEach(this::addRule);
+    @Override
+    public Builder addMethodRule(ArtProfileMethodRule methodRule) {
+      assert !rules.containsKey(methodRule.getReference());
+      rules.put(methodRule.getMethod(), methodRule);
       return this;
     }
 
+    public Builder addRule(ArtProfileRule rule) {
+      return rule.apply(this::addClassRule, this::addMethodRule);
+    }
+
     public Builder addRuleBuilders(Collection<ArtProfileRule.Builder> ruleBuilders) {
       ruleBuilders.forEach(ruleBuilder -> addRule(ruleBuilder.build()));
       return this;
@@ -303,14 +327,14 @@
     public Builder addClassRule(Consumer<ArtProfileClassRuleBuilder> classRuleBuilderConsumer) {
       ArtProfileClassRule.Builder classRuleBuilder = ArtProfileClassRule.builder(dexItemFactory);
       classRuleBuilderConsumer.accept(classRuleBuilder);
-      return addRule(classRuleBuilder.build());
+      return addClassRule(classRuleBuilder.build());
     }
 
     @Override
     public Builder addMethodRule(Consumer<ArtProfileMethodRuleBuilder> methodRuleBuilderConsumer) {
       ArtProfileMethodRule.Builder methodRuleBuilder = ArtProfileMethodRule.builder(dexItemFactory);
       methodRuleBuilderConsumer.accept(methodRuleBuilder);
-      return addRule(methodRuleBuilder.build());
+      return addMethodRule(methodRuleBuilder.build());
     }
 
     @Override
@@ -328,6 +352,7 @@
       return this;
     }
 
+    @Override
     public ArtProfile build() {
       return new ArtProfile(rules);
     }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java
index 283e22b..625d92f 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileClassRule.java
@@ -5,15 +5,16 @@
 package com.android.tools.r8.profile.art;
 
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.profile.AbstractProfileClassRule;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.ThrowingFunction;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 
-public class ArtProfileClassRule extends ArtProfileRule {
+public class ArtProfileClassRule extends ArtProfileRule implements AbstractProfileClassRule {
 
   private final DexType type;
 
@@ -37,6 +38,14 @@
     classRuleConsumer.accept(this);
   }
 
+  @Override
+  public <T, E1 extends Exception, E2 extends Exception> T apply(
+      ThrowingFunction<ArtProfileClassRule, T, E1> classRuleFunction,
+      ThrowingFunction<ArtProfileMethodRule, T, E2> methodRuleFunction)
+      throws E1 {
+    return classRuleFunction.apply(this);
+  }
+
   public ClassReference getClassReference() {
     return Reference.classFromDescriptor(type.toDescriptorString());
   }
@@ -46,7 +55,7 @@
   }
 
   @Override
-  public DexReference getReference() {
+  public DexType getReference() {
     return getType();
   }
 
@@ -55,16 +64,6 @@
   }
 
   @Override
-  public boolean isClassRule() {
-    return true;
-  }
-
-  @Override
-  public ArtProfileClassRule asClassRule() {
-    return this;
-  }
-
-  @Override
   public void writeHumanReadableRuleString(OutputStreamWriter writer) throws IOException {
     writer.write(type.toDescriptorString());
   }
@@ -91,7 +90,8 @@
     return type.toDescriptorString();
   }
 
-  public static class Builder extends ArtProfileRule.Builder implements ArtProfileClassRuleBuilder {
+  public static class Builder extends ArtProfileRule.Builder
+      implements ArtProfileClassRuleBuilder, AbstractProfileClassRule.Builder<ArtProfileClassRule> {
 
     private final DexItemFactory dexItemFactory;
     private DexType type;
@@ -105,11 +105,6 @@
     }
 
     @Override
-    public boolean isClassRuleBuilder() {
-      return true;
-    }
-
-    @Override
     Builder asClassRuleBuilder() {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
index 9eb9a1c..0a256ba 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileCollection.java
@@ -48,10 +48,11 @@
   private static ArtProfile createCompleteArtProfile(AppInfo appInfo) {
     ArtProfile.Builder artProfileBuilder = ArtProfile.builder();
     for (DexProgramClass clazz : appInfo.classesWithDeterministicOrder()) {
-      artProfileBuilder.addRule(ArtProfileClassRule.builder().setType(clazz.getType()).build());
+      artProfileBuilder.addClassRule(
+          ArtProfileClassRule.builder().setType(clazz.getType()).build());
       clazz.forEachMethod(
           method ->
-              artProfileBuilder.addRule(
+              artProfileBuilder.addMethodRule(
                   ArtProfileMethodRule.builder().setMethod(method.getReference()).build()));
     }
     return artProfileBuilder.build();
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java
index fe7bd78..1440b8b 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRule.java
@@ -6,14 +6,16 @@
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.profile.AbstractProfileMethodRule;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.ThrowingFunction;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.util.function.Consumer;
 
-public class ArtProfileMethodRule extends ArtProfileRule {
+public class ArtProfileMethodRule extends ArtProfileRule implements AbstractProfileMethodRule {
 
   private final DexMethod method;
   private final ArtProfileMethodRuleInfoImpl info;
@@ -39,6 +41,14 @@
     methodRuleConsumer.accept(this);
   }
 
+  @Override
+  public <T, E1 extends Exception, E2 extends Exception> T apply(
+      ThrowingFunction<ArtProfileClassRule, T, E1> classRuleFunction,
+      ThrowingFunction<ArtProfileMethodRule, T, E2> methodRuleFunction)
+      throws E2 {
+    return methodRuleFunction.apply(this);
+  }
+
   public DexMethod getMethod() {
     return method;
   }
@@ -57,16 +67,6 @@
   }
 
   @Override
-  public boolean isMethodRule() {
-    return true;
-  }
-
-  @Override
-  public ArtProfileMethodRule asMethodRule() {
-    return this;
-  }
-
-  @Override
   public void writeHumanReadableRuleString(OutputStreamWriter writer) throws IOException {
     info.writeHumanReadableFlags(writer);
     writer.write(method.toSmaliString());
@@ -97,7 +97,8 @@
   }
 
   public static class Builder extends ArtProfileRule.Builder
-      implements ArtProfileMethodRuleBuilder {
+      implements ArtProfileMethodRuleBuilder,
+          AbstractProfileMethodRule.Builder<ArtProfileMethodRule, Builder> {
 
     private final DexItemFactory dexItemFactory;
 
@@ -118,8 +119,8 @@
     }
 
     @Override
-    public boolean isMethodRuleBuilder() {
-      return true;
+    public boolean isGreaterThanOrEqualTo(Builder builder) {
+      return methodRuleInfoBuilder.isGreaterThanOrEqualTo(builder.methodRuleInfoBuilder);
     }
 
     @Override
@@ -128,11 +129,24 @@
     }
 
     @Override
+    public Builder join(Builder builder) {
+      methodRuleInfoBuilder.joinFlags(builder);
+      return this;
+    }
+
+    @Override
+    public Builder join(ArtProfileMethodRule methodRule) {
+      methodRuleInfoBuilder.joinFlags(methodRule.getMethodRuleInfo());
+      return this;
+    }
+
+    @Override
     public Builder setMethodReference(MethodReference methodReference) {
       assert dexItemFactory != null;
       return setMethod(MethodReferenceUtils.toDexMethod(methodReference, dexItemFactory));
     }
 
+    @Override
     public Builder setMethod(DexMethod method) {
       this.method = method;
       return this;
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
index cb482d8..d91553e 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileMethodRuleInfoImpl.java
@@ -105,6 +105,10 @@
       return flags;
     }
 
+    public boolean isGreaterThanOrEqualTo(Builder builder) {
+      return flags == (flags | builder.flags);
+    }
+
     public Builder merge(ArtProfileMethodRuleInfo methodRuleInfo) {
       if (methodRuleInfo.isHot()) {
         setIsHot();
diff --git a/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java b/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java
index 8d6eefd..bb0bcc1 100644
--- a/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java
+++ b/src/main/java/com/android/tools/r8/profile/art/ArtProfileRule.java
@@ -5,17 +5,24 @@
 package com.android.tools.r8.profile.art;
 
 import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.profile.AbstractProfileRule;
 import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.ThrowingFunction;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 
-public abstract class ArtProfileRule implements Comparable<ArtProfileRule> {
+public abstract class ArtProfileRule implements Comparable<ArtProfileRule>, AbstractProfileRule {
 
   public abstract <E1 extends Exception, E2 extends Exception> void accept(
       ThrowingConsumer<ArtProfileClassRule, E1> classRuleConsumer,
       ThrowingConsumer<ArtProfileMethodRule, E2> methodRuleConsumer)
       throws E1, E2;
 
+  public abstract <T, E1 extends Exception, E2 extends Exception> T apply(
+      ThrowingFunction<ArtProfileClassRule, T, E1> classRuleFunction,
+      ThrowingFunction<ArtProfileMethodRule, T, E2> methodRuleFunction)
+      throws E1, E2;
+
   @Override
   public final int compareTo(ArtProfileRule rule) {
     return getReference().compareTo(rule.getReference());
@@ -23,38 +30,14 @@
 
   public abstract DexReference getReference();
 
-  public boolean isClassRule() {
-    return false;
-  }
-
-  public ArtProfileClassRule asClassRule() {
-    return null;
-  }
-
-  public boolean isMethodRule() {
-    return false;
-  }
-
-  public ArtProfileMethodRule asMethodRule() {
-    return null;
-  }
-
   public abstract void writeHumanReadableRuleString(OutputStreamWriter writer) throws IOException;
 
   public abstract static class Builder {
 
-    public boolean isClassRuleBuilder() {
-      return false;
-    }
-
     ArtProfileClassRule.Builder asClassRuleBuilder() {
       return null;
     }
 
-    public boolean isMethodRuleBuilder() {
-      return false;
-    }
-
     ArtProfileMethodRule.Builder asMethodRuleBuilder() {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java
index f4400c7..6207388 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileAdditions.java
@@ -4,306 +4,57 @@
 
 package com.android.tools.r8.profile.art.rewriting;
 
-import static com.android.tools.r8.utils.MapUtils.ignoreKey;
-
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramDefinition;
-import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.profile.AbstractProfileRule;
 import com.android.tools.r8.profile.art.ArtProfile;
 import com.android.tools.r8.profile.art.ArtProfileClassRule;
 import com.android.tools.r8.profile.art.ArtProfileMethodRule;
-import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfoImpl;
 import com.android.tools.r8.profile.art.ArtProfileRule;
-import com.android.tools.r8.utils.WorkList;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Collectors;
+import java.util.Comparator;
 
-/** Mutable extension of an existing ArtProfile. */
-public class ArtProfileAdditions {
+public class ArtProfileAdditions
+    extends ProfileAdditions<
+        ArtProfileAdditions,
+        ArtProfileClassRule,
+        ArtProfileClassRule.Builder,
+        ArtProfileMethodRule,
+        ArtProfileMethodRule.Builder,
+        ArtProfileRule,
+        ArtProfile,
+        ArtProfile.Builder> {
 
-  public interface ArtProfileAdditionsBuilder {
-
-    default ArtProfileAdditionsBuilder addRule(ProgramDefinition definition) {
-      return addRule(definition.getReference());
-    }
-
-    default ArtProfileAdditionsBuilder addRule(DexReference reference) {
-      if (reference.isDexType()) {
-        return addClassRule(reference.asDexType());
-      } else {
-        assert reference.isDexMethod();
-        return addMethodRule(reference.asDexMethod());
-      }
-    }
-
-    ArtProfileAdditionsBuilder addClassRule(DexType type);
-
-    ArtProfileAdditionsBuilder addMethodRule(DexMethod method);
-
-    default ArtProfileAdditionsBuilder removeMovedMethodRule(
-        ProgramMethod oldMethod, ProgramMethod newMethod) {
-      return removeMovedMethodRule(oldMethod.getReference(), newMethod);
-    }
-
-    ArtProfileAdditionsBuilder removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod);
+  ArtProfileAdditions(ArtProfile profile) {
+    super(profile);
   }
 
-  private ArtProfile artProfile;
-
-  private final Map<DexType, ArtProfileClassRule.Builder> classRuleAdditions =
-      new ConcurrentHashMap<>();
-  private final Map<DexMethod, ArtProfileMethodRule.Builder> methodRuleAdditions =
-      new ConcurrentHashMap<>();
-  private final Set<DexMethod> methodRuleRemovals = Sets.newConcurrentHashSet();
-
-  private final NestedMethodRuleAdditionsGraph nestedMethodRuleAdditionsGraph =
-      new NestedMethodRuleAdditionsGraph();
-
-  ArtProfileAdditions(ArtProfile artProfile) {
-    this.artProfile = artProfile;
+  @Override
+  public ArtProfileAdditions create() {
+    return new ArtProfileAdditions(profile);
   }
 
-  void applyIfContextIsInProfile(DexType context, Consumer<ArtProfileAdditions> fn) {
-    if (artProfile.containsClassRule(context) || classRuleAdditions.containsKey(context)) {
-      fn.accept(this);
-    }
+  @Override
+  public ArtProfileClassRule.Builder createClassRuleBuilder(DexType type) {
+    return ArtProfileClassRule.builder().setType(type);
   }
 
-  void applyIfContextIsInProfile(
-      DexMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer) {
-    ArtProfileMethodRule contextMethodRule = artProfile.getMethodRule(context);
-    if (contextMethodRule != null) {
-      builderConsumer.accept(
-          new ArtProfileAdditionsBuilder() {
-
-            @Override
-            public ArtProfileAdditionsBuilder addClassRule(DexType type) {
-              ArtProfileAdditions.this.addClassRule(type);
-              return this;
-            }
-
-            @Override
-            public ArtProfileAdditionsBuilder addMethodRule(DexMethod method) {
-              ArtProfileAdditions.this.addMethodRuleFromContext(
-                  method,
-                  methodRuleInfoBuilder ->
-                      methodRuleInfoBuilder.joinFlags(contextMethodRule.getMethodRuleInfo()));
-              return this;
-            }
-
-            @Override
-            public ArtProfileAdditionsBuilder removeMovedMethodRule(
-                DexMethod oldMethod, ProgramMethod newMethod) {
-              ArtProfileAdditions.this.removeMovedMethodRule(oldMethod, newMethod);
-              return this;
-            }
-          });
-    } else if (methodRuleAdditions.containsKey(context)) {
-      builderConsumer.accept(
-          new ArtProfileAdditionsBuilder() {
-
-            @Override
-            public ArtProfileAdditionsBuilder addClassRule(DexType type) {
-              ArtProfileAdditions.this.addClassRule(type);
-              return this;
-            }
-
-            @Override
-            public ArtProfileAdditionsBuilder addMethodRule(DexMethod method) {
-              ArtProfileMethodRule.Builder contextRuleBuilder = methodRuleAdditions.get(context);
-              ArtProfileAdditions.this.addMethodRuleFromContext(
-                  method,
-                  methodRuleInfoBuilder -> methodRuleInfoBuilder.joinFlags(contextRuleBuilder));
-              nestedMethodRuleAdditionsGraph.recordMethodRuleInfoFlagsLargerThan(method, context);
-              return this;
-            }
-
-            @Override
-            public ArtProfileAdditionsBuilder removeMovedMethodRule(
-                DexMethod oldMethod, ProgramMethod newMethod) {
-              ArtProfileAdditions.this.removeMovedMethodRule(oldMethod, newMethod);
-              return this;
-            }
-          });
-    }
+  @Override
+  public ArtProfileMethodRule.Builder createMethodRuleBuilder(DexMethod method) {
+    return ArtProfileMethodRule.builder().setMethod(method);
   }
 
-  public ArtProfileAdditions addClassRule(DexClass clazz) {
-    addClassRule(clazz.getType());
+  @Override
+  public ArtProfile.Builder createProfileBuilder() {
+    return ArtProfile.builder();
+  }
+
+  @Override
+  public Comparator<AbstractProfileRule> getRuleComparator() {
+    return Comparator.comparing(AbstractProfileRule::asArtProfileRule);
+  }
+
+  @Override
+  public ArtProfileAdditions self() {
     return this;
   }
-
-  public void addClassRule(DexType type) {
-    if (artProfile.containsClassRule(type)) {
-      return;
-    }
-
-    // Create profile rule for class.
-    classRuleAdditions.computeIfAbsent(type, key -> ArtProfileClassRule.builder().setType(key));
-  }
-
-  private void addMethodRuleFromContext(
-      DexMethod method,
-      Consumer<ArtProfileMethodRuleInfoImpl.Builder> methodRuleInfoBuilderConsumer) {
-    addMethodRule(method, methodRuleInfoBuilderConsumer);
-  }
-
-  public ArtProfileAdditions addMethodRule(
-      DexClassAndMethod method,
-      Consumer<ArtProfileMethodRuleInfoImpl.Builder> methodRuleInfoBuilderConsumer) {
-    return addMethodRule(method.getReference(), methodRuleInfoBuilderConsumer);
-  }
-
-  public ArtProfileAdditions addMethodRule(
-      DexMethod method,
-      Consumer<ArtProfileMethodRuleInfoImpl.Builder> methodRuleInfoBuilderConsumer) {
-    // Create profile rule for method.
-    ArtProfileMethodRule.Builder methodRuleBuilder =
-        methodRuleAdditions.computeIfAbsent(
-            method, methodReference -> ArtProfileMethodRule.builder().setMethod(method));
-
-    // Setup the rule.
-    synchronized (methodRuleBuilder) {
-      methodRuleBuilder.acceptMethodRuleInfoBuilder(methodRuleInfoBuilderConsumer);
-    }
-
-    return this;
-  }
-
-  void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod) {
-    assert artProfile.containsMethodRule(oldMethod) || methodRuleAdditions.containsKey(oldMethod);
-    assert methodRuleAdditions.containsKey(newMethod.getReference());
-    methodRuleRemovals.add(oldMethod);
-  }
-
-  ArtProfile createNewArtProfile() {
-    if (!hasAdditions()) {
-      assert !hasRemovals();
-      return artProfile;
-    }
-
-    nestedMethodRuleAdditionsGraph.propagateMethodRuleInfoFlags(methodRuleAdditions);
-
-    // Add existing rules to new profile.
-    ArtProfile.Builder artProfileBuilder = ArtProfile.builder();
-    artProfile.forEachRule(
-        artProfileBuilder::addRule,
-        methodRule -> {
-          if (methodRuleRemovals.contains(methodRule.getMethod())) {
-            return;
-          }
-          ArtProfileMethodRule.Builder methodRuleBuilder =
-              methodRuleAdditions.remove(methodRule.getReference());
-          if (methodRuleBuilder != null) {
-            ArtProfileMethodRule newMethodRule =
-                methodRuleBuilder
-                    .acceptMethodRuleInfoBuilder(
-                        methodRuleInfoBuilder ->
-                            methodRuleInfoBuilder.joinFlags(methodRule.getMethodRuleInfo()))
-                    .build();
-            artProfileBuilder.addRule(newMethodRule);
-          } else {
-            artProfileBuilder.addRule(methodRule);
-          }
-        });
-
-    // Sort and add additions to new profile. Sorting is needed since the additions to this
-    // collection may be concurrent.
-    List<ArtProfileRule> ruleAdditionsSorted =
-        new ArrayList<>(classRuleAdditions.size() + methodRuleAdditions.size());
-    classRuleAdditions
-        .values()
-        .forEach(classRuleBuilder -> ruleAdditionsSorted.add(classRuleBuilder.build()));
-    methodRuleAdditions
-        .values()
-        .forEach(methodRuleBuilder -> ruleAdditionsSorted.add(methodRuleBuilder.build()));
-    ruleAdditionsSorted.sort(ArtProfileRule::compareTo);
-    artProfileBuilder.addRules(ruleAdditionsSorted);
-
-    return artProfileBuilder.build();
-  }
-
-  boolean hasAdditions() {
-    return !classRuleAdditions.isEmpty() || !methodRuleAdditions.isEmpty();
-  }
-
-  private boolean hasRemovals() {
-    return !methodRuleRemovals.isEmpty();
-  }
-
-  ArtProfileAdditions rewriteMethodReferences(Function<DexMethod, DexMethod> methodFn) {
-    ArtProfileAdditions rewrittenAdditions = new ArtProfileAdditions(artProfile);
-    assert methodRuleRemovals.isEmpty();
-    rewrittenAdditions.classRuleAdditions.putAll(classRuleAdditions);
-    methodRuleAdditions.forEach(
-        (method, methodRuleBuilder) -> {
-          DexMethod newMethod = methodFn.apply(method);
-          ArtProfileMethodRule.Builder existingMethodRuleBuilder =
-              rewrittenAdditions.methodRuleAdditions.put(
-                  newMethod, methodRuleBuilder.setMethod(newMethod));
-          assert existingMethodRuleBuilder == null;
-        });
-    return rewrittenAdditions;
-  }
-
-  void setArtProfile(ArtProfile artProfile) {
-    this.artProfile = artProfile;
-  }
-
-  private static class NestedMethodRuleAdditionsGraph {
-
-    private final Map<DexMethod, Set<DexMethod>> successors = new ConcurrentHashMap<>();
-    private final Map<DexMethod, Set<DexMethod>> predecessors = new ConcurrentHashMap<>();
-
-    void recordMethodRuleInfoFlagsLargerThan(DexMethod largerFlags, DexMethod smallerFlags) {
-      predecessors
-          .computeIfAbsent(largerFlags, ignoreKey(Sets::newConcurrentHashSet))
-          .add(smallerFlags);
-      successors
-          .computeIfAbsent(smallerFlags, ignoreKey(Sets::newConcurrentHashSet))
-          .add(largerFlags);
-    }
-
-    void propagateMethodRuleInfoFlags(
-        Map<DexMethod, ArtProfileMethodRule.Builder> methodRuleAdditions) {
-      List<DexMethod> leaves =
-          successors.keySet().stream()
-              .filter(method -> predecessors.getOrDefault(method, Collections.emptySet()).isEmpty())
-              .collect(Collectors.toList());
-      WorkList<DexMethod> worklist = WorkList.newIdentityWorkList(leaves);
-      while (worklist.hasNext()) {
-        DexMethod method = worklist.next();
-        ArtProfileMethodRule.Builder methodRuleBuilder = methodRuleAdditions.get(method);
-        for (DexMethod successor : successors.getOrDefault(method, Collections.emptySet())) {
-          methodRuleAdditions
-              .get(successor)
-              .acceptMethodRuleInfoBuilder(
-                  methodRuleInfoBuilder -> {
-                    int oldFlags = methodRuleInfoBuilder.getFlags();
-                    methodRuleInfoBuilder.joinFlags(methodRuleBuilder);
-                    // If this assertion fails, that means we have synthetics with multiple
-                    // synthesizing contexts, which are not guaranteed to be processed before the
-                    // synthetic itself. In that case this assertion should simply be removed.
-                    assert methodRuleInfoBuilder.getFlags() == oldFlags;
-                  });
-          // Note: no need to addIgnoringSeenSet() since the graph will not have cycles. Indeed, it
-          // should never be the case that a method m2(), which is synthesized from method context
-          // m1(), would itself be a synthesizing context for m1().
-          worklist.addIfNotSeen(successor);
-        }
-      }
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java
index 5198d05..11df596 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ArtProfileCollectionAdditions.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.profile.art.ArtProfileCollection;
-import com.android.tools.r8.profile.art.rewriting.ArtProfileAdditions.ArtProfileAdditionsBuilder;
+import com.android.tools.r8.profile.art.rewriting.ProfileAdditions.ProfileAdditionsBuilder;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -38,7 +38,7 @@
   public abstract void addMethodIfContextIsInProfile(ProgramMethod method, ProgramMethod context);
 
   public abstract void applyIfContextIsInProfile(
-      DexMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer);
+      DexMethod context, Consumer<ProfileAdditionsBuilder> builderConsumer);
 
   public abstract void commit(AppView<?> appView);
 
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java
index 1d1a897..0e6011d 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ConcreteArtProfileCollectionAdditions.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.profile.art.ArtProfileCollection;
 import com.android.tools.r8.profile.art.ArtProfileMethodRuleInfoImpl;
 import com.android.tools.r8.profile.art.NonEmptyArtProfileCollection;
-import com.android.tools.r8.profile.art.rewriting.ArtProfileAdditions.ArtProfileAdditionsBuilder;
+import com.android.tools.r8.profile.art.rewriting.ProfileAdditions.ProfileAdditionsBuilder;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -56,7 +56,11 @@
     } else {
       apply(
           artProfileAdditions ->
-              artProfileAdditions.addMethodRule(method, methodRuleInfoBuilderConsumer));
+              artProfileAdditions.addMethodRule(
+                  method,
+                  methodRuleBuilder ->
+                      methodRuleBuilder.acceptMethodRuleInfoBuilder(
+                          methodRuleInfoBuilderConsumer)));
     }
   }
 
@@ -74,7 +78,7 @@
   void applyIfContextIsInProfile(
       ProgramDefinition context,
       Consumer<ArtProfileAdditions> additionsConsumer,
-      Consumer<ArtProfileAdditionsBuilder> additionsBuilderConsumer) {
+      Consumer<ProfileAdditionsBuilder> additionsBuilderConsumer) {
     if (context.isProgramClass()) {
       applyIfContextIsInProfile(context.asProgramClass(), additionsConsumer);
     } else {
@@ -95,13 +99,13 @@
   }
 
   public void applyIfContextIsInProfile(
-      ProgramMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer) {
+      ProgramMethod context, Consumer<ProfileAdditionsBuilder> builderConsumer) {
     applyIfContextIsInProfile(context.getReference(), builderConsumer);
   }
 
   @Override
   public void applyIfContextIsInProfile(
-      DexMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer) {
+      DexMethod context, Consumer<ProfileAdditionsBuilder> builderConsumer) {
     for (ArtProfileAdditions artProfileAdditions : additionsCollection) {
       artProfileAdditions.applyIfContextIsInProfile(context, builderConsumer);
     }
@@ -125,7 +129,7 @@
     assert hasAdditions();
     List<ArtProfile> newArtProfiles = new ArrayList<>(additionsCollection.size());
     for (ArtProfileAdditions additions : additionsCollection) {
-      newArtProfiles.add(additions.createNewArtProfile());
+      newArtProfiles.add(additions.createNewProfile());
     }
     return new NonEmptyArtProfileCollection(newArtProfiles);
   }
@@ -151,7 +155,7 @@
     assert artProfileCollection.isNonEmpty();
     Iterator<ArtProfile> artProfileIterator = artProfileCollection.asNonEmpty().iterator();
     for (ArtProfileAdditions additions : additionsCollection) {
-      additions.setArtProfile(artProfileIterator.next());
+      additions.setProfile(artProfileIterator.next());
     }
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/NopArtProfileCollectionAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/NopArtProfileCollectionAdditions.java
index 8bfd4a6..9d87d65 100644
--- a/src/main/java/com/android/tools/r8/profile/art/rewriting/NopArtProfileCollectionAdditions.java
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/NopArtProfileCollectionAdditions.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.profile.art.ArtProfileCollection;
-import com.android.tools.r8.profile.art.rewriting.ArtProfileAdditions.ArtProfileAdditionsBuilder;
+import com.android.tools.r8.profile.art.rewriting.ProfileAdditions.ProfileAdditionsBuilder;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -30,7 +30,7 @@
 
   @Override
   public void applyIfContextIsInProfile(
-      DexMethod context, Consumer<ArtProfileAdditionsBuilder> builderConsumer) {
+      DexMethod context, Consumer<ProfileAdditionsBuilder> builderConsumer) {
     // Intentionally empty.
   }
 
diff --git a/src/main/java/com/android/tools/r8/profile/art/rewriting/ProfileAdditions.java b/src/main/java/com/android/tools/r8/profile/art/rewriting/ProfileAdditions.java
new file mode 100644
index 0000000..195963e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/art/rewriting/ProfileAdditions.java
@@ -0,0 +1,309 @@
+// Copyright (c) 2023, 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.profile.art.rewriting;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.profile.AbstractProfile;
+import com.android.tools.r8.profile.AbstractProfileClassRule;
+import com.android.tools.r8.profile.AbstractProfileMethodRule;
+import com.android.tools.r8.profile.AbstractProfileRule;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/** Mutable extension of an existing profile. */
+public abstract class ProfileAdditions<
+    Additions extends
+        ProfileAdditions<
+                Additions,
+                ClassRule,
+                ClassRuleBuilder,
+                MethodRule,
+                MethodRuleBuilder,
+                ProfileRule,
+                Profile,
+                ProfileBuilder>,
+    ClassRule extends AbstractProfileClassRule,
+    ClassRuleBuilder extends AbstractProfileClassRule.Builder<ClassRule>,
+    MethodRule extends AbstractProfileMethodRule,
+    MethodRuleBuilder extends AbstractProfileMethodRule.Builder<MethodRule, MethodRuleBuilder>,
+    ProfileRule extends AbstractProfileRule,
+    Profile extends AbstractProfile<ClassRule, MethodRule>,
+    ProfileBuilder extends
+        AbstractProfile.Builder<ClassRule, MethodRule, Profile, ProfileBuilder>> {
+
+  public interface ProfileAdditionsBuilder {
+
+    default ProfileAdditionsBuilder addRule(ProgramDefinition definition) {
+      return addRule(definition.getReference());
+    }
+
+    default ProfileAdditionsBuilder addRule(DexReference reference) {
+      if (reference.isDexType()) {
+        return addClassRule(reference.asDexType());
+      } else {
+        assert reference.isDexMethod();
+        return addMethodRule(reference.asDexMethod());
+      }
+    }
+
+    ProfileAdditionsBuilder addClassRule(DexType type);
+
+    ProfileAdditionsBuilder addMethodRule(DexMethod method);
+
+    default void removeMovedMethodRule(ProgramMethod oldMethod, ProgramMethod newMethod) {
+      removeMovedMethodRule(oldMethod.getReference(), newMethod);
+    }
+
+    void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod);
+  }
+
+  Profile profile;
+
+  final Map<DexType, ClassRuleBuilder> classRuleAdditions = new ConcurrentHashMap<>();
+  final Map<DexMethod, MethodRuleBuilder> methodRuleAdditions = new ConcurrentHashMap<>();
+  private final Set<DexMethod> methodRuleRemovals = Sets.newConcurrentHashSet();
+
+  private final NestedMethodRuleAdditionsGraph nestedMethodRuleAdditionsGraph =
+      new NestedMethodRuleAdditionsGraph();
+
+  ProfileAdditions(Profile profile) {
+    this.profile = profile;
+  }
+
+  void applyIfContextIsInProfile(DexType context, Consumer<Additions> fn) {
+    if (profile.containsClassRule(context) || classRuleAdditions.containsKey(context)) {
+      fn.accept(self());
+    }
+  }
+
+  void applyIfContextIsInProfile(
+      DexMethod context, Consumer<ProfileAdditionsBuilder> builderConsumer) {
+    MethodRule contextMethodRule = profile.getMethodRule(context);
+    if (contextMethodRule != null) {
+      builderConsumer.accept(
+          new ProfileAdditionsBuilder() {
+
+            @Override
+            public ProfileAdditionsBuilder addClassRule(DexType type) {
+              ProfileAdditions.this.addClassRule(type);
+              return this;
+            }
+
+            @Override
+            public ProfileAdditionsBuilder addMethodRule(DexMethod method) {
+              ProfileAdditions.this.addMethodRule(
+                  method, methodRuleBuilder -> methodRuleBuilder.join(contextMethodRule));
+              return this;
+            }
+
+            @Override
+            public void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod) {
+              ProfileAdditions.this.removeMovedMethodRule(oldMethod, newMethod);
+            }
+          });
+    } else if (methodRuleAdditions.containsKey(context)) {
+      builderConsumer.accept(
+          new ProfileAdditionsBuilder() {
+
+            @Override
+            public ProfileAdditionsBuilder addClassRule(DexType type) {
+              ProfileAdditions.this.addClassRule(type);
+              return this;
+            }
+
+            @Override
+            public ProfileAdditionsBuilder addMethodRule(DexMethod method) {
+              MethodRuleBuilder contextRuleBuilder = methodRuleAdditions.get(context);
+              ProfileAdditions.this.addMethodRule(
+                  method, methodRuleBuilder -> methodRuleBuilder.join(contextRuleBuilder));
+              nestedMethodRuleAdditionsGraph.recordMethodRuleInfoFlagsLargerThan(method, context);
+              return this;
+            }
+
+            @Override
+            public void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod) {
+              ProfileAdditions.this.removeMovedMethodRule(oldMethod, newMethod);
+            }
+          });
+    }
+  }
+
+  public Additions addClassRule(DexClass clazz) {
+    addClassRule(clazz.getType());
+    return self();
+  }
+
+  public void addClassRule(DexType type) {
+    if (profile.containsClassRule(type)) {
+      return;
+    }
+
+    // Create profile rule for class.
+    classRuleAdditions.computeIfAbsent(type, this::createClassRuleBuilder);
+  }
+
+  public Additions addMethodRule(
+      DexClassAndMethod method, Consumer<MethodRuleBuilder> methodRuleBuilderConsumer) {
+    return addMethodRule(method.getReference(), methodRuleBuilderConsumer);
+  }
+
+  public Additions addMethodRule(
+      DexMethod method, Consumer<MethodRuleBuilder> methodRuleBuilderConsumer) {
+    // Create profile rule for method.
+    MethodRuleBuilder methodRuleBuilder =
+        methodRuleAdditions.computeIfAbsent(method, this::createMethodRuleBuilder);
+
+    // Setup the rule.
+    synchronized (methodRuleBuilder) {
+      methodRuleBuilderConsumer.accept(methodRuleBuilder);
+    }
+
+    return self();
+  }
+
+  void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod) {
+    assert profile.containsMethodRule(oldMethod) || methodRuleAdditions.containsKey(oldMethod);
+    assert methodRuleAdditions.containsKey(newMethod.getReference());
+    methodRuleRemovals.add(oldMethod);
+  }
+
+  Profile createNewProfile() {
+    if (!hasAdditions()) {
+      assert !hasRemovals();
+      return profile;
+    }
+
+    nestedMethodRuleAdditionsGraph.propagateMethodRuleInfoFlags(methodRuleAdditions);
+
+    // Add existing rules to new profile.
+    ProfileBuilder profileBuilder = createProfileBuilder();
+    profile.forEachRule(
+        profileBuilder::addClassRule,
+        methodRule -> {
+          if (methodRuleRemovals.contains(methodRule.getReference())) {
+            return;
+          }
+          MethodRuleBuilder methodRuleBuilder =
+              methodRuleAdditions.remove(methodRule.getReference());
+          if (methodRuleBuilder != null) {
+            MethodRule newMethodRule = methodRuleBuilder.join(methodRule).build();
+            profileBuilder.addMethodRule(newMethodRule);
+          } else {
+            profileBuilder.addMethodRule(methodRule);
+          }
+        });
+
+    // Sort and add additions to new profile. Sorting is needed since the additions to this
+    // collection may be concurrent.
+    List<AbstractProfileRule> ruleAdditionsSorted =
+        new ArrayList<>(classRuleAdditions.size() + methodRuleAdditions.size());
+    classRuleAdditions
+        .values()
+        .forEach(classRuleBuilder -> ruleAdditionsSorted.add(classRuleBuilder.build()));
+    methodRuleAdditions
+        .values()
+        .forEach(methodRuleBuilder -> ruleAdditionsSorted.add(methodRuleBuilder.build()));
+    ruleAdditionsSorted.sort(getRuleComparator());
+    ruleAdditionsSorted.forEach(profileBuilder::addRule);
+
+    return profileBuilder.build();
+  }
+
+  boolean hasAdditions() {
+    return !classRuleAdditions.isEmpty() || !methodRuleAdditions.isEmpty();
+  }
+
+  private boolean hasRemovals() {
+    return !methodRuleRemovals.isEmpty();
+  }
+
+  Additions rewriteMethodReferences(Function<DexMethod, DexMethod> methodFn) {
+    Additions rewrittenAdditions = create();
+    assert methodRuleRemovals.isEmpty();
+    rewrittenAdditions.classRuleAdditions.putAll(classRuleAdditions);
+    methodRuleAdditions.forEach(
+        (method, methodRuleBuilder) -> {
+          DexMethod newMethod = methodFn.apply(method);
+          MethodRuleBuilder existingMethodRuleBuilder =
+              rewrittenAdditions.methodRuleAdditions.put(
+                  newMethod, methodRuleBuilder.setMethod(newMethod));
+          assert existingMethodRuleBuilder == null;
+        });
+    return rewrittenAdditions;
+  }
+
+  public abstract Additions create();
+
+  public abstract ClassRuleBuilder createClassRuleBuilder(DexType type);
+
+  public abstract MethodRuleBuilder createMethodRuleBuilder(DexMethod method);
+
+  public abstract ProfileBuilder createProfileBuilder();
+
+  public abstract Comparator<AbstractProfileRule> getRuleComparator();
+
+  public abstract Additions self();
+
+  void setProfile(Profile profile) {
+    this.profile = profile;
+  }
+
+  private class NestedMethodRuleAdditionsGraph {
+
+    private final Map<DexMethod, Set<DexMethod>> successors = new ConcurrentHashMap<>();
+    private final Map<DexMethod, Set<DexMethod>> predecessors = new ConcurrentHashMap<>();
+
+    void recordMethodRuleInfoFlagsLargerThan(DexMethod largerFlags, DexMethod smallerFlags) {
+      predecessors
+          .computeIfAbsent(largerFlags, ignoreKey(Sets::newConcurrentHashSet))
+          .add(smallerFlags);
+      successors
+          .computeIfAbsent(smallerFlags, ignoreKey(Sets::newConcurrentHashSet))
+          .add(largerFlags);
+    }
+
+    void propagateMethodRuleInfoFlags(Map<DexMethod, MethodRuleBuilder> methodRuleAdditions) {
+      List<DexMethod> leaves =
+          successors.keySet().stream()
+              .filter(method -> predecessors.getOrDefault(method, Collections.emptySet()).isEmpty())
+              .collect(Collectors.toList());
+      WorkList<DexMethod> worklist = WorkList.newIdentityWorkList(leaves);
+      while (worklist.hasNext()) {
+        DexMethod method = worklist.next();
+        MethodRuleBuilder methodRuleBuilder = methodRuleAdditions.get(method);
+        for (DexMethod successor : successors.getOrDefault(method, Collections.emptySet())) {
+          MethodRuleBuilder successorMethodRuleBuilder = methodRuleAdditions.get(successor);
+          // If this assertion fails, that means we have synthetics with multiple
+          // synthesizing contexts, which are not guaranteed to be processed before the
+          // synthetic itself. In that case this assertion should simply be removed.
+          assert successorMethodRuleBuilder.isGreaterThanOrEqualTo(methodRuleBuilder);
+          successorMethodRuleBuilder.join(methodRuleBuilder);
+          // Note: no need to addIgnoringSeenSet() since the graph will not have cycles. Indeed, it
+          // should never be the case that a method m2(), which is synthesized from method context
+          // m1(), would itself be a synthesizing context for m1().
+          worklist.addIfNotSeen(successor);
+        }
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index b4c8475..901629a 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.experimental.startup.StartupProfile;
-import com.android.tools.r8.experimental.startup.profile.NonEmptyStartupProfile;
 import com.android.tools.r8.experimental.startup.profile.StartupItem;
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.origin.Origin;
@@ -806,8 +805,8 @@
     MissingStartupProfileItemsDiagnostic.Builder missingStartupProfileItemsDiagnosticBuilder =
         MissingStartupProfileItemsDiagnostic.Builder.nop();
     StartupProfileProvider startupProfileProvider = startupProfileProviders.get(0);
-    NonEmptyStartupProfile.Builder startupProfileBuilder =
-        NonEmptyStartupProfile.builder(
+    StartupProfile.Builder startupProfileBuilder =
+        StartupProfile.builder(
             options, missingStartupProfileItemsDiagnosticBuilder, startupProfileProvider);
     startupProfileProvider.getStartupProfile(startupProfileBuilder);