Remove startup base feature split

Change-Id: I829137591c70444695fbfed86f6d02297cf20f8b
diff --git a/src/main/java/com/android/tools/r8/FeatureSplit.java b/src/main/java/com/android/tools/r8/FeatureSplit.java
index 08b1657..03f99fa 100644
--- a/src/main/java/com/android/tools/r8/FeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/FeatureSplit.java
@@ -39,19 +39,6 @@
         }
       };
 
-  public static final FeatureSplit BASE_STARTUP =
-      new FeatureSplit(null, null, null, null) {
-        @Override
-        public boolean isBase() {
-          return true;
-        }
-
-        @Override
-        public boolean isStartupBase() {
-          return true;
-        }
-      };
-
   private ProgramConsumer programConsumer;
   private final List<ProgramResourceProvider> programResourceProviders;
   private final AndroidResourceProvider androidResourceProvider;
@@ -72,10 +59,6 @@
     return false;
   }
 
-  public boolean isStartupBase() {
-    return false;
-  }
-
   void internalSetProgramConsumer(ProgramConsumer consumer) {
     this.programConsumer = consumer;
   }
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 6d0b3e6..0e79887 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -14,10 +14,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 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.graph.PrunedItems;
 import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
@@ -106,18 +104,15 @@
 
   public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
       Set<DexProgramClass> classes, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return getFeatureSplitClasses(
-        classes, appView.options(), appView.getStartupProfile(), appView.getSyntheticItems());
+    return getFeatureSplitClasses(classes, appView.getSyntheticItems());
   }
 
   public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
       Set<DexProgramClass> classes,
-      InternalOptions options,
-      StartupProfile startupProfile,
       SyntheticItems syntheticItems) {
     Map<FeatureSplit, Set<DexProgramClass>> result = new IdentityHashMap<>();
     for (DexProgramClass clazz : classes) {
-      FeatureSplit featureSplit = getFeatureSplit(clazz, options, startupProfile, syntheticItems);
+      FeatureSplit featureSplit = getFeatureSplit(clazz, syntheticItems);
       if (featureSplit != null && !featureSplit.isBase()) {
         result.computeIfAbsent(featureSplit, ignore -> Sets.newIdentityHashSet()).add(clazz);
       }
@@ -127,46 +122,32 @@
 
   public FeatureSplit getFeatureSplit(
       ProgramDefinition definition, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return getFeatureSplit(
-        definition, appView.options(), appView.getStartupProfile(), appView.getSyntheticItems());
+    return getFeatureSplit(definition, appView.getSyntheticItems());
   }
 
   public FeatureSplit getFeatureSplit(
       ProgramDefinition definition,
-      InternalOptions options,
-      StartupProfile startupProfile,
       SyntheticItems syntheticItems) {
-    return getFeatureSplit(definition.getContextType(), options, startupProfile, syntheticItems);
+    return getFeatureSplit(definition.getContextType(), syntheticItems);
   }
 
   public FeatureSplit getFeatureSplit(
       DexType type, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return getFeatureSplit(
-        type, appView.options(), appView.getStartupProfile(), appView.getSyntheticItems());
+    return getFeatureSplit(type, appView.getSyntheticItems());
   }
 
   public FeatureSplit getFeatureSplit(
       DexType type,
-      InternalOptions options,
-      StartupProfile startupProfile,
       SyntheticItems syntheticItems) {
     if (syntheticItems == null) {
       // Called from AndroidApp.dumpProgramResources().
-      assert startupProfile.isEmpty();
       return classToFeatureSplitMap.getOrDefault(type, FeatureSplit.BASE);
     }
     FeatureSplit feature;
     boolean isSynthetic = syntheticItems.isSyntheticClass(type);
     if (isSynthetic) {
       if (syntheticItems.isSyntheticOfKind(type, k -> k.ENUM_UNBOXING_SHARED_UTILITY_CLASS)) {
-        // Use the startup base if there is one, such that we don't merge non-startup classes with
-        // the shared utility class in case it is used during startup. The use of base startup
-        // allows for merging startup classes with the shared utility class, however, which could be
-        // bad for startup if the shared utility class is not used during startup.
-        return startupProfile.isEmpty()
-                || options.getStartupOptions().isStartupBoundaryOptimizationsEnabled()
-            ? FeatureSplit.BASE
-            : FeatureSplit.BASE_STARTUP;
+        return FeatureSplit.BASE;
       }
       feature = syntheticItems.getContextualFeatureSplitOrDefault(type, FeatureSplit.BASE);
       // Verify the synthetic is not in the class to feature split map or the synthetic has the same
@@ -176,10 +157,7 @@
       feature = classToFeatureSplitMap.getOrDefault(type, FeatureSplit.BASE);
     }
     if (feature.isBase()) {
-      return !startupProfile.isStartupClass(type)
-              || options.getStartupOptions().isStartupBoundaryOptimizationsEnabled()
-          ? FeatureSplit.BASE
-          : FeatureSplit.BASE_STARTUP;
+      return FeatureSplit.BASE;
     }
     return feature;
   }
@@ -192,17 +170,9 @@
   }
 
   public boolean isInBase(
-      DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isInBase(
-        clazz, appView.options(), appView.getStartupProfile(), appView.getSyntheticItems());
-  }
-
-  public boolean isInBase(
       DexProgramClass clazz,
-      InternalOptions options,
-      StartupProfile startupProfile,
       SyntheticItems syntheticItems) {
-    return getFeatureSplit(clazz, options, startupProfile, syntheticItems).isBase();
+    return getFeatureSplit(clazz, syntheticItems).isBase();
   }
 
   public boolean isInBaseOrSameFeatureAs(
@@ -212,19 +182,14 @@
     return isInBaseOrSameFeatureAs(
         clazz,
         context,
-        appView.options(),
-        appView.getStartupProfile(),
         appView.getSyntheticItems());
   }
 
   public boolean isInBaseOrSameFeatureAs(
       ProgramDefinition clazz,
       ProgramDefinition context,
-      InternalOptions options,
-      StartupProfile startupProfile,
       SyntheticItems syntheticItems) {
-    return isInBaseOrSameFeatureAs(
-        clazz.getContextType(), context, options, startupProfile, syntheticItems);
+    return isInBaseOrSameFeatureAs(clazz.getContextType(), context, syntheticItems);
   }
 
   public boolean isInBaseOrSameFeatureAs(
@@ -234,60 +199,21 @@
     return isInBaseOrSameFeatureAs(
         clazz,
         context,
-        appView.options(),
-        appView.getStartupProfile(),
         appView.getSyntheticItems());
   }
 
   public boolean isInBaseOrSameFeatureAs(
       DexType clazz,
       ProgramDefinition context,
-      InternalOptions options,
-      StartupProfile startupProfile,
       SyntheticItems syntheticItems) {
-    FeatureSplit split = getFeatureSplit(clazz, options, startupProfile, syntheticItems);
-    return split.isBase()
-        || split == getFeatureSplit(context, options, startupProfile, syntheticItems);
+    FeatureSplit split = getFeatureSplit(clazz, syntheticItems);
+    return split.isBase() || split == getFeatureSplit(context, syntheticItems);
   }
 
   public boolean isInFeature(
       DexProgramClass clazz,
-      InternalOptions options,
-      StartupProfile startupProfile,
       SyntheticItems syntheticItems) {
-    return !isInBase(clazz, options, startupProfile, syntheticItems);
-  }
-
-  public boolean isInSameFeatureOrBothInSameBase(
-      ProgramMethod a, ProgramMethod b, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isInSameFeatureOrBothInSameBase(
-        a, b, appView.options(), appView.getStartupProfile(), appView.getSyntheticItems());
-  }
-
-  public boolean isInSameFeatureOrBothInSameBase(
-      ProgramMethod a,
-      ProgramMethod b,
-      InternalOptions options,
-      StartupProfile startupProfile,
-      SyntheticItems syntheticItems) {
-    return isInSameFeatureOrBothInSameBase(
-        a.getHolder(), b.getHolder(), options, startupProfile, syntheticItems);
-  }
-
-  public boolean isInSameFeatureOrBothInSameBase(
-      DexProgramClass a, DexProgramClass b, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isInSameFeatureOrBothInSameBase(
-        a, b, appView.options(), appView.getStartupProfile(), appView.getSyntheticItems());
-  }
-
-  public boolean isInSameFeatureOrBothInSameBase(
-      DexProgramClass a,
-      DexProgramClass b,
-      InternalOptions options,
-      StartupProfile startupProfile,
-      SyntheticItems syntheticItems) {
-    return getFeatureSplit(a, options, startupProfile, syntheticItems)
-        == getFeatureSplit(b, options, startupProfile, syntheticItems);
+    return !isInBase(clazz, syntheticItems);
   }
 
   public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens, Timing timing) {
@@ -298,7 +224,7 @@
     Map<DexType, FeatureSplit> rewrittenClassToFeatureSplitMap = new IdentityHashMap<>();
     classToFeatureSplitMap.forEach(
         (type, featureSplit) -> {
-          DexType rewrittenType = lens.lookupType(type);
+          DexType rewrittenType = lens.lookupType(type, GraphLens.getIdentityLens());
           if (rewrittenType.isIntType()) {
             // The type was removed by enum unboxing.
             return;
@@ -332,8 +258,6 @@
 
   public static boolean isInFeature(
       DexProgramClass clazz, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return getMap(appView)
-        .isInFeature(
-            clazz, appView.options(), appView.getStartupProfile(), appView.getSyntheticItems());
+    return getMap(appView).isInFeature(clazz, appView.getSyntheticItems());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
index 542c1ab..81d3fe6 100644
--- a/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
+++ b/src/main/java/com/android/tools/r8/features/FeatureSplitBoundaryOptimizationUtils.java
@@ -12,10 +12,7 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.synthesis.SyntheticItems;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OptionalBool;
 
 public class FeatureSplitBoundaryOptimizationUtils {
 
@@ -43,11 +40,8 @@
       DexProgramClass accessedClass,
       ProgramDefinition accessor,
       ClassToFeatureSplitMap classToFeatureSplitMap,
-      InternalOptions options,
-      StartupProfile startupProfile,
       SyntheticItems syntheticItems) {
-    return classToFeatureSplitMap.isInBaseOrSameFeatureAs(
-        accessedClass, accessor, options, startupProfile, syntheticItems);
+    return classToFeatureSplitMap.isInBaseOrSameFeatureAs(accessedClass, accessor, syntheticItems);
   }
 
   public static boolean isSafeForInlining(
@@ -60,42 +54,11 @@
 
     // First guarantee that we don't cross any actual feature split boundaries.
     if (!calleeFeatureSplit.isBase()) {
-      if (calleeFeatureSplit != callerFeatureSplit) {
-        return false;
-      }
-    }
-
-    // Next perform startup checks.
-    if (!callee.getOptimizationInfo().forceInline()) {
-      StartupProfile startupProfile = appView.getStartupProfile();
-      OptionalBool callerIsStartupMethod = isStartupMethod(caller, startupProfile);
-      if (callerIsStartupMethod.isTrue()) {
-        // If the caller is a startup method, then only allow inlining if the callee is also a
-        // startup method.
-        if (isStartupMethod(callee, startupProfile).isFalse()) {
-          return false;
-        }
-      } else if (callerIsStartupMethod.isFalse()) {
-        // If the caller is not a startup method, then only allow inlining if the caller is not a
-        // startup class or the callee is a startup class.
-        if (startupProfile.isStartupClass(caller.getHolderType())
-            && !startupProfile.isStartupClass(callee.getHolderType())) {
-          return false;
-        }
-      }
+      return calleeFeatureSplit == callerFeatureSplit;
     }
     return true;
   }
 
-  private static OptionalBool isStartupMethod(ProgramMethod method, StartupProfile startupProfile) {
-    if (method.getDefinition().isD8R8Synthesized()) {
-      // Due to inadequate rewriting of the startup list during desugaring, we do not give an
-      // accurate result in this case.
-      return OptionalBool.unknown();
-    }
-    return OptionalBool.of(startupProfile.containsMethodRule(method.getReference()));
-  }
-
   public static boolean isSafeForVerticalClassMerging(
       DexProgramClass sourceClass,
       DexProgramClass targetClass,
@@ -107,19 +70,9 @@
     // First guarantee that we don't cross any actual feature split boundaries.
     if (targetFeatureSplit.isBase()) {
       assert sourceFeatureSplit.isBase() : "Unexpected class in base that inherits from feature";
+      return true;
     } else {
-      if (sourceFeatureSplit != targetFeatureSplit) {
-        return false;
-      }
+      return sourceFeatureSplit == targetFeatureSplit;
     }
-
-    // If the source class is a startup class then require that the target class is also a startup
-    // class.
-    StartupProfile startupProfile = appView.getStartupProfile();
-    if (startupProfile.isStartupClass(sourceClass.getType())
-        && !startupProfile.isStartupClass(targetClass.getType())) {
-      return false;
-    }
-    return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index 3819be8..2d5b68a 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -5,9 +5,7 @@
 
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
-import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.synthesis.SyntheticItems;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OptionalBool;
 
 /**
@@ -26,8 +24,6 @@
         clazz,
         context,
         appView.appInfo().getClassToFeatureSplitMap(),
-        appView.options(),
-        appView.getStartupProfile(),
         appView.getSyntheticItems());
   }
 
@@ -35,8 +31,6 @@
       DexClass clazz,
       Definition context,
       ClassToFeatureSplitMap classToFeatureSplitMap,
-      InternalOptions options,
-      StartupProfile startupProfile,
       SyntheticItems syntheticItems) {
     if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
       return OptionalBool.FALSE;
@@ -47,8 +41,6 @@
             clazz.asProgramClass(),
             context.asProgramDefinition(),
             classToFeatureSplitMap,
-            options,
-            startupProfile,
             syntheticItems)) {
       return OptionalBool.UNKNOWN;
     }
@@ -59,13 +51,11 @@
   static OptionalBool isMemberAccessible(
       SuccessfulMemberResolutionResult<?, ?> resolutionResult,
       Definition context,
-      AppView<?> appView,
       AppInfoWithClassHierarchy appInfo) {
     return isMemberAccessible(
         resolutionResult.getResolutionPair(),
         resolutionResult.getInitialResolutionHolder(),
         context,
-        appView,
         appInfo);
   }
 
@@ -74,15 +64,13 @@
       Definition initialResolutionContext,
       Definition context,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isMemberAccessible(
-        member, initialResolutionContext, context, appView, appView.appInfo());
+    return isMemberAccessible(member, initialResolutionContext, context, appView.appInfo());
   }
 
   static OptionalBool isMemberAccessible(
       DexClassAndMember<?, ?> member,
       Definition initialResolutionContext,
       Definition context,
-      AppView<?> appView,
       AppInfoWithClassHierarchy appInfo) {
     AccessFlags<?> memberFlags = member.getDefinition().getAccessFlags();
     OptionalBool classAccessibility =
@@ -90,8 +78,6 @@
             initialResolutionContext.getContextClass(),
             context,
             appInfo.getClassToFeatureSplitMap(),
-            appInfo.options(),
-            appView.getStartupProfile(),
             appInfo.getSyntheticItems());
     if (classAccessibility.isFalse()) {
       return OptionalBool.FALSE;
diff --git a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
index a383253..6bae976 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldResolutionResult.java
@@ -203,7 +203,7 @@
     @Override
     public OptionalBool isAccessibleFrom(
         ProgramDefinition context, AppView<?> appView, AppInfoWithClassHierarchy appInfo) {
-      return AccessControl.isMemberAccessible(this, context, appView, appInfo);
+      return AccessControl.isMemberAccessible(this, context, appInfo);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 710666b..03b3932 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -501,7 +501,7 @@
     @Override
     public OptionalBool isAccessibleFrom(
         ProgramDefinition context, AppView<?> appView, AppInfoWithClassHierarchy appInfo) {
-      return AccessControl.isMemberAccessible(this, context, appView, appInfo);
+      return AccessControl.isMemberAccessible(this, context, appInfo);
     }
 
     @Override
@@ -1484,8 +1484,6 @@
                                       clazz,
                                       context,
                                       appInfo.getClassToFeatureSplitMap(),
-                                      appView.options(),
-                                      appView.getStartupProfile(),
                                       appView.getSyntheticItems())
                                   .isPossiblyFalse())),
           method -> {
@@ -1493,7 +1491,7 @@
             DexClassAndMethod classAndMethod = DexClassAndMethod.create(holder, method);
             seenNoAccess.or(
                 AccessControl.isMemberAccessible(
-                        classAndMethod, initialResolutionHolder, context, appView, appInfo)
+                        classAndMethod, initialResolutionHolder, context, appInfo)
                     .isPossiblyFalse());
           });
       return seenNoAccess.get();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 8d63fb1..da96bcf 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -60,6 +60,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
 import com.android.tools.r8.horizontalclassmerging.policies.SamePackageForNonGlobalMergeSynthetic;
 import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
+import com.android.tools.r8.horizontalclassmerging.policies.SameStartupPartition;
 import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy;
 import com.android.tools.r8.horizontalclassmerging.policies.VerifyMultiClassPolicyAlwaysSatisfied;
 import com.android.tools.r8.horizontalclassmerging.policies.VerifySingleClassPolicyAlwaysSatisfied;
@@ -289,6 +290,7 @@
         new CheckAbstractClasses(appView),
         new NoClassAnnotationCollisions(),
         new SameFeatureSplit(appView),
+        new SameStartupPartition(appView),
         new SameInstanceFields(appView, mode),
         new SameMainDexGroup(appView),
         new SameNestHost(appView),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameStartupPartition.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameStartupPartition.java
new file mode 100644
index 0000000..b04ed19
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameStartupPartition.java
@@ -0,0 +1,45 @@
+// 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
+import com.android.tools.r8.horizontalclassmerging.policies.SameStartupPartition.StartupPartition;
+import com.android.tools.r8.profile.startup.StartupOptions;
+import com.android.tools.r8.profile.startup.profile.StartupProfile;
+
+public class SameStartupPartition extends MultiClassSameReferencePolicy<StartupPartition> {
+
+  public enum StartupPartition {
+    STARTUP,
+    POST_STARTUP
+  }
+
+  private final StartupOptions startupOptions;
+  private final StartupProfile startupProfile;
+
+  public SameStartupPartition(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    this.startupOptions = appView.options().getStartupOptions();
+    this.startupProfile = appView.getStartupProfile();
+  }
+
+  @Override
+  public StartupPartition getMergeKey(DexProgramClass clazz) {
+    return startupProfile.isStartupClass(clazz.getType())
+        ? StartupPartition.STARTUP
+        : StartupPartition.POST_STARTUP;
+  }
+
+  @Override
+  public boolean shouldSkipPolicy() {
+    return startupProfile.isEmpty() || startupOptions.isStartupBoundaryOptimizationsEnabled();
+  }
+
+  @Override
+  public String getName() {
+    return "SameStartupPartition";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 603bcc6..1ed5f9a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.android.tools.r8.profile.startup.optimization.StartupBoundaryOptimizationUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.AssumeInfoCollection;
 import com.android.tools.r8.shaking.MainDexInfo;
@@ -176,6 +177,11 @@
       return false;
     }
 
+    if (!StartupBoundaryOptimizationUtils.isSafeForInlining(method, singleTarget, appView)) {
+      whyAreYouNotInliningReporter.reportInliningAcrossStartupBoundary();
+      return false;
+    }
+
     // Abort inlining attempt if method -> target access is not right.
     if (resolutionResult.isAccessibleFrom(method, appView).isPossiblyFalse()) {
       whyAreYouNotInliningReporter.reportInaccessible();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index d3d3735..1f116b4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -69,6 +69,9 @@
   public void reportInliningAcrossFeatureSplit() {}
 
   @Override
+  public void reportInliningAcrossStartupBoundary() {}
+
+  @Override
   public void reportInstructionBudgetIsExceeded() {}
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 4bf931b..159128c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -76,6 +76,8 @@
 
   public abstract void reportInliningAcrossFeatureSplit();
 
+  public abstract void reportInliningAcrossStartupBoundary();
+
   public abstract void reportInstructionBudgetIsExceeded();
 
   public abstract void reportInvalidDoubleInliningCandidate();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 05e2cb1..23391b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -153,6 +153,11 @@
   }
 
   @Override
+  public void reportInliningAcrossStartupBoundary() {
+    report("cannot inline across startup/non-startup boundary.");
+  }
+
+  @Override
   public void reportInstructionBudgetIsExceeded() {
     report("caller's instruction budget is exceeded.");
   }
diff --git a/src/main/java/com/android/tools/r8/profile/startup/optimization/StartupBoundaryOptimizationUtils.java b/src/main/java/com/android/tools/r8/profile/startup/optimization/StartupBoundaryOptimizationUtils.java
new file mode 100644
index 0000000..194f1dd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/startup/optimization/StartupBoundaryOptimizationUtils.java
@@ -0,0 +1,45 @@
+// 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.startup.optimization;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.profile.startup.profile.StartupProfile;
+
+public class StartupBoundaryOptimizationUtils {
+
+  public static boolean isSafeForInlining(
+      ProgramMethod caller,
+      ProgramMethod callee,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    StartupProfile startupProfile = appView.getStartupProfile();
+    if (startupProfile.isEmpty()
+        || appView.options().getStartupOptions().isStartupBoundaryOptimizationsEnabled()
+        || callee.getOptimizationInfo().forceInline()) {
+      return true;
+    }
+    // It is always OK to inline into a non-startup class.
+    if (!startupProfile.isStartupClass(caller.getHolderType())) {
+      return true;
+    }
+    // Otherwise the caller is a startup method or a post-startup method on a non-startup class. In
+    // either case, only allow inlining if the callee is defined on a startup class.
+    return startupProfile.isStartupClass(callee.getHolderType());
+  }
+
+  public static boolean isSafeForVerticalClassMerging(
+      DexProgramClass sourceClass,
+      DexProgramClass targetClass,
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    StartupProfile startupProfile = appView.getStartupProfile();
+    if (startupProfile.isEmpty()
+        || appView.options().getStartupOptions().isStartupBoundaryOptimizationsEnabled()) {
+      return true;
+    }
+    return !startupProfile.isStartupClass(sourceClass.getType())
+        || startupProfile.isStartupClass(targetClass.getType());
+  }
+}
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 41c19f5..8222389 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -39,7 +39,6 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
@@ -609,25 +608,17 @@
 
   private SynthesizingContext getSynthesizingContext(
       ProgramDefinition context, AppView<?> appView) {
-    InternalOptions options = appView.options();
     if (appView.hasClassHierarchy()) {
       AppInfoWithClassHierarchy appInfo = appView.appInfoWithClassHierarchy();
-      return getSynthesizingContext(
-          context, appInfo.getClassToFeatureSplitMap(), options, appView.getStartupProfile());
+      return getSynthesizingContext(context, appInfo.getClassToFeatureSplitMap());
     }
     return getSynthesizingContext(
-        context,
-        ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
-        options,
-        StartupProfile.empty());
+        context, ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap());
   }
 
   /** Used to find the synthesizing context for a new synthetic that is about to be created. */
   private SynthesizingContext getSynthesizingContext(
-      ProgramDefinition context,
-      ClassToFeatureSplitMap featureSplits,
-      InternalOptions options,
-      StartupProfile startupProfile) {
+      ProgramDefinition context, ClassToFeatureSplitMap featureSplits) {
     DexType contextType = context.getContextType();
     SyntheticDefinition<?, ?, ?> existingDefinition = pending.definitions.get(contextType);
     if (existingDefinition != null) {
@@ -643,8 +634,7 @@
           .getContext();
     }
     // This context is not nested in an existing synthetic context so create a new "leaf" context.
-    FeatureSplit featureSplit =
-        featureSplits.getFeatureSplit(context, options, startupProfile, this);
+    FeatureSplit featureSplit = featureSplits.getFeatureSplit(context, this);
     return SynthesizingContext.fromNonSyntheticInputContext(context, featureSplit);
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 8bbf801..adb0992 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -46,7 +46,6 @@
 import com.android.tools.r8.profile.art.ArtProfileProvider;
 import com.android.tools.r8.profile.art.ArtProfileProviderUtils;
 import com.android.tools.r8.profile.startup.StartupProfileProviderUtils;
-import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.startup.StartupProfileProvider;
 import com.android.tools.r8.synthesis.SyntheticItems;
@@ -717,8 +716,7 @@
                           DexType type = options.dexItemFactory().createType(classDescriptor);
                           SyntheticItems syntheticItems = null;
                           FeatureSplit featureSplit =
-                              classToFeatureSplitMap.getFeatureSplit(
-                                  type, options, StartupProfile.empty(), syntheticItems);
+                              classToFeatureSplitMap.getFeatureSplit(type, syntheticItems);
                           if (featureSplit != null && !featureSplit.isBase()) {
                             return featureSplitArchiveOutputStreams.get(featureSplit);
                           }
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
index 983077e..e2bfb17 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.profile.startup.optimization.StartupBoundaryOptimizationUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.utils.Box;
@@ -105,6 +106,10 @@
         sourceClass, targetClass, appView)) {
       return false;
     }
+    if (!StartupBoundaryOptimizationUtils.isSafeForVerticalClassMerging(
+        sourceClass, targetClass, appView)) {
+      return false;
+    }
     if (appView.appServices().allServiceTypes().contains(sourceClass.getType())
         && appView.getKeepInfo(targetClass).isPinned(options)) {
       return false;
diff --git a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
index 1cb68de..88d5f71 100644
--- a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
+++ b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
@@ -63,12 +63,12 @@
         .inspect(
             inspector -> {
               // Assert that foo is not inlined.
-              ClassSubject A = inspector.clazz(A.class);
-              assertThat(A, isPresent());
-              assertThat(A.uniqueMethodWithOriginalName("foo"), isPresent());
+              ClassSubject B = inspector.clazz(B.class);
+              assertThat(B, isPresent());
+              assertThat(B.uniqueMethodWithOriginalName("foo"), isPresent());
             })
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("A::foo", "A::foo");
+        .assertSuccessWithOutputLines("B::foo", "B::foo");
   }
 
   static class Main {
@@ -81,12 +81,8 @@
 
   public static class A {
 
-    private static void foo() {
-      System.out.println("A::foo");
-    }
-
     private static void bar() {
-      foo();
+      B.foo();
     }
 
     public static void callBarInStartup() {
@@ -97,4 +93,11 @@
       bar();
     }
   }
+
+  public static class B {
+
+    static void foo() {
+      System.out.println("B::foo");
+    }
+  }
 }