Make Machine specification backward-compatible.

Bug: b/330833507
Change-Id: Iffe839f6d6989f56074d01189ce2fd4a863bd0d6
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs.json b/src/library_desugar/jdk11/desugar_jdk_libs.json
index 0020bab..f2dfda6 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs.json
@@ -63,14 +63,6 @@
       "rewrite_prefix": {
         "java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
       },
-      "emulate_interface": {
-        "java.util.Collection": {
-          "rewrittenType": "j$.util.Collection",
-          "emulatedMethods": [
-            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
-          ]
-        }
-      },
       "retarget_method": {
         "java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
         "java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()": "java.util.concurrent.DesugarTimeUnit",
@@ -99,6 +91,14 @@
       "rewrite_prefix": {
         "java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
       },
+      "emulate_interface": {
+        "java.util.Collection": {
+          "rewrittenType": "j$.util.Collection",
+          "emulatedMethods": [
+            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+          ]
+        }
+      },
       "retarget_method": {
         "java.util.stream.Collector java.util.stream.Collectors#filtering(java.util.function.Predicate, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
         "java.util.stream.Collector java.util.stream.Collectors#flatMapping(java.util.function.Function, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
@@ -252,6 +252,7 @@
         "java.util.Collection": {
           "rewrittenType": "j$.util.Collection",
           "emulatedMethods": [
+            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)",
             "java.util.stream.Stream java.util.Collection#stream()",
             "java.util.stream.Stream java.util.Collection#parallelStream()",
             "java.util.Spliterator java.util.Collection#spliterator()",
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index aa8b1c0..1aff889 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -77,14 +77,6 @@
         "java.io.DesugarInputStream": "j$.io.DesugarInputStream",
         "java.util.concurrent.DesugarTimeUnit": "j$.util.concurrent.DesugarTimeUnit"
       },
-      "emulate_interface": {
-        "java.util.Collection": {
-          "rewrittenType": "j$.util.Collection",
-          "emulatedMethods": [
-            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
-          ]
-        }
-      },
       "retarget_method": {
         "java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)": "java.util.concurrent.DesugarTimeUnit",
         "java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()": "java.util.concurrent.DesugarTimeUnit",
@@ -119,6 +111,14 @@
       "rewrite_prefix": {
         "java.util.stream.DesugarCollectors": "j$.util.stream.DesugarCollectors"
       },
+      "emulate_interface": {
+        "java.util.Collection": {
+          "rewrittenType": "j$.util.Collection",
+          "emulatedMethods": [
+            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
+          ]
+        }
+      },
       "retarget_method": {
         "java.util.stream.Collector java.util.stream.Collectors#filtering(java.util.function.Predicate, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
         "java.util.stream.Collector java.util.stream.Collectors#flatMapping(java.util.function.Function, java.util.stream.Collector)": "java.util.stream.DesugarCollectors",
@@ -402,6 +402,7 @@
         "java.util.Collection": {
           "rewrittenType": "j$.util.Collection",
           "emulatedMethods": [
+            "java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)",
             "java.util.stream.Stream java.util.Collection#stream()",
             "java.util.stream.Stream java.util.Collection#parallelStream()",
             "java.util.Spliterator java.util.Collection#spliterator()",
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
index e13fb7c..9f9738e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/ApiLevelRange.java
@@ -24,6 +24,8 @@
 
   public ApiLevelRange(
       AndroidApiLevel apiLevelBelowOrEqual, AndroidApiLevel apiLevelGreaterOrEqual) {
+    assert apiLevelGreaterOrEqual == null
+        || apiLevelBelowOrEqual.isGreaterThanOrEqualTo(apiLevelGreaterOrEqual);
     this.apiLevelBelowOrEqual = apiLevelBelowOrEqual;
     this.apiLevelGreaterOrEqual = apiLevelGreaterOrEqual;
   }
@@ -40,6 +42,15 @@
     return apiLevelGreaterOrEqual != null;
   }
 
+  public boolean overlap(ApiLevelRange other) {
+    AndroidApiLevel start =
+        apiLevelGreaterOrEqual == null ? AndroidApiLevel.B : apiLevelGreaterOrEqual;
+    AndroidApiLevel otherStart =
+        other.apiLevelGreaterOrEqual == null ? AndroidApiLevel.B : other.apiLevelGreaterOrEqual;
+    return start.isLessThanOrEqualTo(other.apiLevelBelowOrEqual)
+        && otherStart.isLessThanOrEqualTo(apiLevelBelowOrEqual);
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -74,4 +85,13 @@
     }
     return apiLevelGreaterOrEqual.compareTo(other.apiLevelGreaterOrEqual);
   }
+
+  @Override
+  public String toString() {
+    return "[ "
+        + (apiLevelGreaterOrEqual == null ? "B" : apiLevelGreaterOrEqual)
+        + " ; "
+        + apiLevelBelowOrEqual
+        + " ]";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
index e3a9dff..80ca5b3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedInterfaceDescriptor.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.Objects;
 
@@ -35,18 +34,6 @@
     return emulatedMethods;
   }
 
-  public EmulatedInterfaceDescriptor merge(EmulatedInterfaceDescriptor other) {
-    if (!rewrittenType.isIdenticalTo(other.getRewrittenType())) {
-      throw new UnsupportedOperationException(
-          "Emulated interface descriptor can only be merged on the same rewritten type.");
-    }
-    ImmutableMap.Builder<DexMethod, EmulatedDispatchMethodDescriptor> builder =
-        ImmutableMap.builder();
-    builder.putAll(getEmulatedMethods());
-    builder.putAll(other.getEmulatedMethods());
-    return new EmulatedInterfaceDescriptor(rewrittenType, builder.build());
-  }
-
   @Override
   public Object[] toJsonStruct(
       MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter exporter) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index f28dae7..d27ba9b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -362,8 +362,8 @@
         emulatedVirtualRetargetThroughEmulatedInterface = ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod[]> apiGenericTypesConversion =
         ImmutableMap.builder();
-    private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
-        new IdentityHashMap<>();
+    private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
+        ImmutableMap.builder();
     private final LinkedHashMap<DexType, WrapperDescriptor> wrappers = new LinkedHashMap<>();
     private final ImmutableMap.Builder<DexType, DexType> legacyBackport = ImmutableMap.builder();
     private final ImmutableSet.Builder<DexType> dontRetarget = ImmutableSet.builder();
@@ -409,12 +409,8 @@
       nonEmulatedVirtualRetarget.put(src, dest);
     }
 
-    public void putEmulatedInterface(DexType src, EmulatedInterfaceDescriptor newDescriptor) {
-      assert newDescriptor != null;
-      EmulatedInterfaceDescriptor oldDescriptor = emulatedInterfaces.get(src);
-      EmulatedInterfaceDescriptor mergedDescriptor =
-          oldDescriptor == null ? newDescriptor : newDescriptor.merge(oldDescriptor);
-      emulatedInterfaces.put(src, mergedDescriptor);
+    public void putEmulatedInterface(DexType src, EmulatedInterfaceDescriptor descriptor) {
+      emulatedInterfaces.put(src, descriptor);
     }
 
     public void putEmulatedVirtualRetarget(DexMethod src, EmulatedDispatchMethodDescriptor dest) {
@@ -491,7 +487,7 @@
           emulatedVirtualRetarget.build(),
           emulatedVirtualRetargetThroughEmulatedInterface.build(),
           apiGenericTypesConversion.build(),
-          ImmutableMap.copyOf(emulatedInterfaces),
+          emulatedInterfaces.build(),
           wrappers,
           legacyBackport.build(),
           dontRetarget.build(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
index 46ec6ba..f15da9b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
@@ -56,7 +56,7 @@
 
 public class MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter {
 
-  static final int MACHINE_VERSION_NUMBER = 201;
+  static final int MACHINE_VERSION_NUMBER = 200;
 
   private final DexItemFactory factory;
   private final Map<String, String> packageMap = new TreeMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index c5c7348..843a915 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.desugar.desugaredlibrary.specificationconversion;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexApplication;
@@ -30,8 +31,10 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 public class HumanToMachineSpecificationConverter {
@@ -63,6 +66,8 @@
     Map<ApiLevelRange, MachineRewritingFlags> libraryFlags =
         convertRewritingFlagMap(humanSpec.getLibraryFlags(), synthesizedPrefix, true, identifier);
 
+    checkDisjointEmulatedInterfaceFlags(commonFlags, programFlags, libraryFlags);
+
     MultiAPILevelMachineDesugaredLibrarySpecification machineSpec =
         new MultiAPILevelMachineDesugaredLibrarySpecification(
             humanSpec.getOrigin(), machineTopLevelFlags, commonFlags, libraryFlags, programFlags);
@@ -70,6 +75,56 @@
     return machineSpec;
   }
 
+  // For backward compatibility, there can be only one emulated interface flag for a given type at
+  // a given API level.
+  private void checkDisjointEmulatedInterfaceFlags(
+      Map<ApiLevelRange, MachineRewritingFlags> commonFlags,
+      Map<ApiLevelRange, MachineRewritingFlags> programFlags,
+      Map<ApiLevelRange, MachineRewritingFlags> libraryFlags) {
+    Set<DexType> commonTypes = emulatedInterfaceTypes(commonFlags);
+    Set<DexType> programTypes = emulatedInterfaceTypes(programFlags);
+    Set<DexType> libraryTypes = emulatedInterfaceTypes(libraryFlags);
+    if (!Sets.intersection(commonTypes, programTypes).isEmpty()
+        || !Sets.intersection(commonTypes, libraryTypes).isEmpty()
+        || !Sets.intersection(libraryTypes, programTypes).isEmpty()) {
+      throw new CompilationError("Cannot have emulated interface split across flag types");
+    }
+    checkEmulatedInterfaceMap(commonFlags);
+    checkEmulatedInterfaceMap(programFlags);
+    checkEmulatedInterfaceMap(libraryFlags);
+  }
+
+  private void checkEmulatedInterfaceMap(Map<ApiLevelRange, MachineRewritingFlags> flagMap) {
+    Map<DexType, List<ApiLevelRange>> rangesForType = new IdentityHashMap<>();
+    flagMap.forEach(
+        (range, flags) ->
+            flags
+                .getEmulatedInterfaces()
+                .forEach(
+                    (ei, descr) -> {
+                      rangesForType.putIfAbsent(ei, new ArrayList<>());
+                      rangesForType.get(ei).add(range);
+                    }));
+    rangesForType.keySet().removeIf(t -> rangesForType.get(t).size() == 1);
+    rangesForType.forEach(
+        (type, ranges) -> {
+          for (ApiLevelRange range1 : ranges) {
+            for (ApiLevelRange range2 : ranges) {
+              if (!Objects.equals(range1, range2) && range1.overlap(range2)) {
+                throw new CompilationError(
+                    "Unsupported Machine specification for " + type + " " + range1 + " " + range2);
+              }
+            }
+          }
+        });
+  }
+
+  private Set<DexType> emulatedInterfaceTypes(Map<ApiLevelRange, MachineRewritingFlags> flagMap) {
+    Set<DexType> types = Sets.newIdentityHashSet();
+    flagMap.forEach((range, flags) -> types.addAll(flags.getEmulatedInterfaces().keySet()));
+    return types;
+  }
+
   private Map<ApiLevelRange, MachineRewritingFlags> convertRewritingFlagMap(
       Map<ApiLevelRange, HumanRewritingFlags> libFlags,
       String synthesizedPrefix,
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java
new file mode 100644
index 0000000..0747d96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ApiLevelRangeTest.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2024, 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.desugar.backports;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ApiLevelRangeTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ApiLevelRangeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRange() {
+    ApiLevelRange rs = new ApiLevelRange(AndroidApiLevel.S, AndroidApiLevel.R);
+    ApiLevelRange op = new ApiLevelRange(AndroidApiLevel.P, AndroidApiLevel.O);
+    ApiLevelRange ir = new ApiLevelRange(AndroidApiLevel.R, AndroidApiLevel.I);
+    ApiLevelRange r = new ApiLevelRange(AndroidApiLevel.R, null);
+    ApiLevelRange i = new ApiLevelRange(AndroidApiLevel.I, null);
+
+    // Overlap with null.
+    assertTrue(i.overlap(r));
+    assertTrue(r.overlap(i));
+
+    // No overlap with null.
+    assertFalse(i.overlap(rs));
+    assertFalse(rs.overlap(i));
+
+    // No overlap.
+    assertFalse(rs.overlap(op));
+    assertFalse(op.overlap(rs));
+
+    // Partial overlap.
+    assertTrue(ir.overlap(rs));
+    assertTrue(rs.overlap(ir));
+
+    // Full overlap.
+    assertTrue(ir.overlap(op));
+    assertTrue(op.overlap(ir));
+  }
+}