[LIR] Add support for int-switch.

This also moves legacy tests invoke, invokeempty and instanceofstring.

Bug: b/225838009
Bug: b/167145686
Change-Id: I61314aebf2fb89a785164928a227cac4d4efc4cf
diff --git a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
index 15712e1..de5ce5a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IntSwitch.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.utils.IntObjConsumer;
 import com.android.tools.r8.utils.InternalOutputMode;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -35,6 +36,11 @@
   }
 
   @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addIntSwitch(value(), getKeys(), getKeyToTargetMap(), fallthroughBlock());
+  }
+
+  @Override
   public int opcode() {
     return Opcodes.INT_SWITCH;
   }
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index 0583876..bda771a 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -38,6 +38,7 @@
 import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.IntSwitch;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeInterface;
 import com.android.tools.r8.ir.code.InvokeNewArray;
@@ -68,9 +69,12 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
 import com.android.tools.r8.lightir.LirCode.PositionEntry;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -477,6 +481,39 @@
     }
 
     @Override
+    public void onIntSwitch(EV value, IntSwitchPayload payload) {
+      // keys is the 'value' -> 'target index' mapping.
+      int[] keys = payload.keys;
+      // successorIndices is the 'target index' to 'IR successor index'.
+      int[] successorIndices = new int[keys.length];
+      List<BasicBlock> successorBlocks = new ArrayList<>();
+      {
+        // The mapping from instruction to successor is a temp mapping to track if any targets
+        // point to the same block.
+        Int2IntMap instructionToSuccessor = new Int2IntOpenHashMap();
+        for (int i = 0; i < successorIndices.length; i++) {
+          int instructionIndex = payload.targets[i];
+          if (instructionToSuccessor.containsKey(instructionIndex)) {
+            successorIndices[i] = instructionToSuccessor.get(instructionIndex);
+          } else {
+            int successorIndex = successorBlocks.size();
+            successorIndices[i] = successorIndex;
+            instructionToSuccessor.put(instructionIndex, successorIndex);
+            successorBlocks.add(getBasicBlock(instructionIndex));
+          }
+        }
+      }
+      int fallthrough = successorBlocks.size();
+      addInstruction(new IntSwitch(getValue(value), keys, successorIndices, fallthrough));
+      // The call to addInstruction will ensure the current block so don't amend to it before here.
+      // If the block has successors then the index mappings are not valid / need to be offset.
+      assert currentBlock.getSuccessors().isEmpty();
+      successorBlocks.forEach(currentBlock::link);
+      currentBlock.link(getBasicBlock(nextInstructionIndex));
+      closeCurrentBlock();
+    }
+
+    @Override
     public void onInvokeDirect(DexMethod target, List<EV> arguments, boolean isInterface) {
       Value dest = getInvokeInstructionOutputValue(target);
       List<Value> ssaArgumentValues = getValues(arguments);
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
index 54bdd09..7a88ae2 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -37,6 +37,7 @@
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
@@ -83,12 +84,25 @@
   private int[] valueIndexBuffer = new int[MAX_VALUE_COUNT];
 
   /**
+   * Internal "DexItem" for the instruction payloads such that they can be put in the pool.
+   *
+   * <p>The instruction encoding assumes the instruction operand payload size is u1, so this allows
+   * the data payload to be stored in the constant pool instead.
+   */
+  public abstract static class InstructionPayload extends DexItem {
+    @Override
+    protected final void collectMixedSectionItems(MixedSectionCollection collection) {
+      throw new Unreachable();
+    }
+  }
+
+  /**
    * Internal "DexItem" for the fill-array payloads such that they can be put in the pool.
    *
    * <p>The instruction encoding assumes the instruction operand payload size is u1, so the data
    * payload is stored in the constant pool instead.
    */
-  public static class FillArrayPayload extends DexItem {
+  public static class FillArrayPayload extends InstructionPayload {
     public final int element_width;
     public final long size;
     public final short[] data;
@@ -98,10 +112,16 @@
       this.size = size;
       this.data = data;
     }
+  }
 
-    @Override
-    protected void collectMixedSectionItems(MixedSectionCollection collection) {
-      throw new Unreachable();
+  public static class IntSwitchPayload extends InstructionPayload {
+    public final int[] keys;
+    public final int[] targets;
+
+    public IntSwitchPayload(int[] keys, int[] targets) {
+      assert keys.length == targets.length;
+      this.keys = keys;
+      this.targets = targets;
     }
   }
 
@@ -489,6 +509,22 @@
     return this;
   }
 
+  public LirBuilder<V, EV> addIntSwitch(
+      V value,
+      int[] keys,
+      Int2ReferenceSortedMap<BasicBlock> keyToTargetMap,
+      BasicBlock fallthroughBlock) {
+    int[] targets = new int[keys.length];
+    for (int i = 0; i < keys.length; i++) {
+      targets[i] = getBlockIndex(keyToTargetMap.get(keys[i]));
+    }
+    IntSwitchPayload payload = new IntSwitchPayload(keys, targets);
+    return addInstructionTemplate(
+        LirOpcodes.TABLESWITCH,
+        Collections.singletonList(payload),
+        Collections.singletonList(value));
+  }
+
   public LirBuilder<V, EV> addIf(
       IfType ifKind, ValueType valueType, V value, BasicBlock trueTarget) {
     int opcode;
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
index fa36ce6..d418d96 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.lightir.LirBuilder.FillArrayPayload;
+import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -288,6 +289,10 @@
     onInstruction();
   }
 
+  public void onIntSwitch(EV value, IntSwitchPayload payload) {
+    onInstruction();
+  }
+
   public void onFallthrough() {
     onInstruction();
   }
@@ -831,6 +836,14 @@
           onGoto(blockIndex);
           return;
         }
+      case LirOpcodes.TABLESWITCH:
+        {
+          IntSwitchPayload payload =
+              (IntSwitchPayload) getConstantItem(view.getNextConstantOperand());
+          EV value = getNextValueOperand(view);
+          onIntSwitch(value, payload);
+          return;
+        }
       case LirOpcodes.INVOKEDIRECT:
       case LirOpcodes.INVOKEDIRECT_ITF:
         {
diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
index c3c06b1..e19661e 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.IfType;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
 import java.util.List;
@@ -202,6 +203,12 @@
   }
 
   @Override
+  public void onIntSwitch(EV value, IntSwitchPayload payload) {
+    appendValueArguments(value);
+    // TODO(b/225838009): Consider printing the switch payload info.
+  }
+
+  @Override
   public void onFallthrough() {
     // Nothing to append.
   }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 418dad3..cac9b82 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -25,46 +25,43 @@
   @Parameters(name = "{0}_{1}_{2}_{3}_{5}_{6}")
   public static Collection<String[]> data() {
     String[] tests = {
-        "arithmetic.Arithmetic",
-        "inlining.Inlining",
-        "instanceofstring.InstanceofString",
-        "invoke.Invoke",
-        "invokeempty.InvokeEmpty",
-        "jumbostring.JumboString",
-        "loadconst.LoadConst",
-        "loop.UdpServer",
-        "nestedtrycatches.NestedTryCatches",
-        "regalloc.RegAlloc",
-        "returns.Returns",
-        "staticfield.StaticField",
-        "stringbuilding.StringBuilding",
-        "switches.Switches",
-        "sync.Sync",
-        "throwing.Throwing",
-        "trivial.Trivial",
-        "trycatch.TryCatch",
-        "trycatchmany.TryCatchMany",
-        "regress.Regress",
-        "regress2.Regress2",
-        "regress_37726195.Regress",
-        "regress_37658666.Regress",
-        "regress_37875803.Regress",
-        "regress_37955340.Regress",
-        "regress_62300145.Regress",
-        "regress_64881691.Regress",
-        "regress_65104300.Regress",
-        "regress_70703087.Test",
-        "regress_70736958.Test",
-        "regress_70737019.Test",
-        "regress_72361252.Test",
-        "regress_110373181.Regress",
-        "memberrebinding2.Memberrebinding",
-        "memberrebinding3.Memberrebinding",
-        "minification.Minification",
-        "enclosingmethod.Main",
-        "enclosingmethod_proguarded.Main",
-        "switchmaps.Switches",
-        "uninitializedfinal.UninitializedFinalFieldLeak",
+      "arithmetic.Arithmetic",
+      "inlining.Inlining",
+      "jumbostring.JumboString",
+      "loadconst.LoadConst",
+      "loop.UdpServer",
+      "nestedtrycatches.NestedTryCatches",
+      "regalloc.RegAlloc",
+      "returns.Returns",
+      "staticfield.StaticField",
+      "stringbuilding.StringBuilding",
+      "switches.Switches",
+      "sync.Sync",
+      "throwing.Throwing",
+      "trivial.Trivial",
+      "trycatch.TryCatch",
+      "trycatchmany.TryCatchMany",
+      "regress.Regress",
+      "regress2.Regress2",
+      "regress_37726195.Regress",
+      "regress_37658666.Regress",
+      "regress_37875803.Regress",
+      "regress_37955340.Regress",
+      "regress_62300145.Regress",
+      "regress_64881691.Regress",
+      "regress_65104300.Regress",
+      "regress_70703087.Test",
+      "regress_70736958.Test",
+      "regress_70737019.Test",
+      "regress_72361252.Test",
+      "regress_110373181.Regress",
+      "memberrebinding2.Memberrebinding",
+      "memberrebinding3.Memberrebinding",
+      "minification.Minification",
+      "enclosingmethod.Main",
+      "enclosingmethod_proguarded.Main",
+      "switchmaps.Switches",
+      "uninitializedfinal.UninitializedFinalFieldLeak",
     };
 
     List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index fe9bed4..25caafc 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -60,16 +60,6 @@
   }
 
   @Test
-  public void testInstanceofString() throws Exception {
-    testDebugging("instanceofstring", "InstanceofString");
-  }
-
-  @Test
-  public void testInvoke() throws Exception {
-    testDebugging("invoke", "Invoke");
-  }
-
-  @Test
   public void testJumboString() throws Exception {
     testDebugging("jumbostring", "JumboString");
   }
diff --git a/src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceOfStringTestRunner.java b/src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceOfStringTestRunner.java
new file mode 100644
index 0000000..14b2620
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceOfStringTestRunner.java
@@ -0,0 +1,50 @@
+// 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.examples.instanceofstring;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.examples.ExamplesTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InstanceOfStringTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public InstanceOfStringTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return InstanceofString.class;
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines("is-string:true");
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    runTestDesugaring();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTestR8();
+  }
+
+  @Test
+  public void testDebug() throws Exception {
+    runTestDebugComparator();
+  }
+}
diff --git a/src/test/examples/instanceofstring/InstanceofString.java b/src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceofString.java
similarity index 86%
rename from src/test/examples/instanceofstring/InstanceofString.java
rename to src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceofString.java
index c7ff011..8073d0a 100644
--- a/src/test/examples/instanceofstring/InstanceofString.java
+++ b/src/test/java/com/android/tools/r8/examples/instanceofstring/InstanceofString.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2016, 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 instanceofstring;
+package com.android.tools.r8.examples.instanceofstring;
 
 class InstanceofString {
   public static void main(String[] args) {
diff --git a/src/test/examples/invoke/Invoke.java b/src/test/java/com/android/tools/r8/examples/invoke/Invoke.java
similarity index 99%
rename from src/test/examples/invoke/Invoke.java
rename to src/test/java/com/android/tools/r8/examples/invoke/Invoke.java
index d936140..6625471 100644
--- a/src/test/examples/invoke/Invoke.java
+++ b/src/test/java/com/android/tools/r8/examples/invoke/Invoke.java
@@ -5,7 +5,7 @@
 // This code is not run directly. It needs to be compiled to dex code.
 // 'invoke.dex' is what is run.
 
-package invoke;
+package com.android.tools.r8.examples.invoke;
 
 public class Invoke extends SuperClass implements InvokeInterface {
 
diff --git a/src/test/examples/invoke/InvokeInterface.java b/src/test/java/com/android/tools/r8/examples/invoke/InvokeInterface.java
similarity index 91%
rename from src/test/examples/invoke/InvokeInterface.java
rename to src/test/java/com/android/tools/r8/examples/invoke/InvokeInterface.java
index 7f5a08e..bba0c12 100644
--- a/src/test/examples/invoke/InvokeInterface.java
+++ b/src/test/java/com/android/tools/r8/examples/invoke/InvokeInterface.java
@@ -2,7 +2,7 @@
 // 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 invoke;
+package com.android.tools.r8.examples.invoke;
 
 public interface InvokeInterface {
   public void interface0();
diff --git a/src/test/java/com/android/tools/r8/examples/invoke/InvokeTestRunner.java b/src/test/java/com/android/tools/r8/examples/invoke/InvokeTestRunner.java
new file mode 100644
index 0000000..5e22ac6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/invoke/InvokeTestRunner.java
@@ -0,0 +1,117 @@
+// 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.examples.invoke;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.examples.ExamplesTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public InvokeTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return Invoke.class;
+  }
+
+  @Override
+  public List<Class<?>> getTestClasses() {
+    return ImmutableList.of(Invoke.class, InvokeInterface.class, SuperClass.class);
+  }
+
+  @Override
+  public String getExpected() {
+    return StringUtils.lines(
+        "static0",
+        "static1 1",
+        "static2 1 2",
+        "static3 1 2 3",
+        "static4 1 2 3 4",
+        "static5 1 2 3 4 5",
+        "staticRange 1 2 3 4 5 6",
+        "staticDouble0 0.1",
+        "staticDouble2 0.1 0.2",
+        "staticDoubleRange 0.1 0.2 0.3",
+        "direct0",
+        "direct1 1",
+        "direct2 1 2",
+        "direct3 1 2 3",
+        "direct4 1 2 3 4",
+        "directRange 1 2 3 4 5 6",
+        "interface0",
+        "interface1 1",
+        "interface2 1 2",
+        "interface3 1 2 3",
+        "interface4 1 2 3 4",
+        "interfaceRange 1 2 3 4 5 6",
+        "virtual0",
+        "virtual1 1",
+        "virtual2 1 2",
+        "virtual3 1 2 3",
+        "virtual4 1 2 3 4",
+        "virtualRange 1 2 3 4 5 6",
+        "super0",
+        "super1 1",
+        "super2 1 2",
+        "super3 1 2 3",
+        "super4 1 2 3 4",
+        "superRange 1 2 3 4 5 6",
+        "rangeInvoke0 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "rangeInvoke0 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "rangeInvoke1 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "rangeInvoke1 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "rangeInvoke2 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "rangeInvoke2 i 0 j 2 d 42.42 e 43.43 l 64424509440",
+        "oneArgumentMethod 0",
+        "oneArgumentMethod 3",
+        "oneArgumentMethod 16",
+        "twoArgumentMethod 16 9",
+        "twoArgumentMethod 16 10",
+        "twoArgumentMethod 16 11",
+        "oneDoubleArgumentMethod 1.1",
+        "oneDoubleArgumentMethod 4.4",
+        "oneDoubleArgumentMethod 16.6",
+        "twoDoubleArgumentMethod 16.6 9.9",
+        "twoDoubleArgumentMethod 16.6 10.1",
+        "twoDoubleArgumentMethod 16.6 11.2",
+        "rangeInvokesRepeatedArgument0 0 1 0 1 0 1 0 1",
+        "rangeInvokesRepeatedArgument0 0 1 1 1 1 1 1 1",
+        "int: 42",
+        "double: 42.42",
+        "int: 32",
+        "double: 32.32",
+        "519",
+        "15");
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    runTestDesugaring();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTestR8();
+  }
+
+  @Test
+  public void testDebug() throws Exception {
+    runTestDebugComparator();
+  }
+}
diff --git a/src/test/examples/invoke/SuperClass.java b/src/test/java/com/android/tools/r8/examples/invoke/SuperClass.java
similarity index 95%
rename from src/test/examples/invoke/SuperClass.java
rename to src/test/java/com/android/tools/r8/examples/invoke/SuperClass.java
index c4f7ae5..9b1deb2 100644
--- a/src/test/examples/invoke/SuperClass.java
+++ b/src/test/java/com/android/tools/r8/examples/invoke/SuperClass.java
@@ -2,7 +2,7 @@
 // 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 invoke;
+package com.android.tools.r8.examples.invoke;
 
 public class SuperClass {
   public void super0() {
diff --git a/src/test/examples/invokeempty/ClassA.java b/src/test/java/com/android/tools/r8/examples/invokeempty/ClassA.java
similarity index 86%
rename from src/test/examples/invokeempty/ClassA.java
rename to src/test/java/com/android/tools/r8/examples/invokeempty/ClassA.java
index 50ac63c..248643f 100644
--- a/src/test/examples/invokeempty/ClassA.java
+++ b/src/test/java/com/android/tools/r8/examples/invokeempty/ClassA.java
@@ -2,7 +2,7 @@
 // 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 invokeempty;
+package com.android.tools.r8.examples.invokeempty;
 
 public class ClassA {
 
diff --git a/src/test/examples/invokeempty/ClassB.java b/src/test/java/com/android/tools/r8/examples/invokeempty/ClassB.java
similarity index 86%
rename from src/test/examples/invokeempty/ClassB.java
rename to src/test/java/com/android/tools/r8/examples/invokeempty/ClassB.java
index 305baa6..dd96e04 100644
--- a/src/test/examples/invokeempty/ClassB.java
+++ b/src/test/java/com/android/tools/r8/examples/invokeempty/ClassB.java
@@ -2,7 +2,7 @@
 // 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 invokeempty;
+package com.android.tools.r8.examples.invokeempty;
 
 public class ClassB {
 
diff --git a/src/test/examples/invokeempty/InvokeEmpty.java b/src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmpty.java
similarity index 88%
rename from src/test/examples/invokeempty/InvokeEmpty.java
rename to src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmpty.java
index 6160eb2..e1969bf 100644
--- a/src/test/examples/invokeempty/InvokeEmpty.java
+++ b/src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmpty.java
@@ -2,7 +2,7 @@
 // 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 invokeempty;
+package com.android.tools.r8.examples.invokeempty;
 
 public class InvokeEmpty {
 
diff --git a/src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmptyTestRunner.java b/src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmptyTestRunner.java
new file mode 100644
index 0000000..967fba6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/examples/invokeempty/InvokeEmptyTestRunner.java
@@ -0,0 +1,56 @@
+// 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.examples.invokeempty;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.examples.ExamplesTestBase;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeEmptyTestRunner extends ExamplesTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build();
+  }
+
+  public InvokeEmptyTestRunner(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  public Class<?> getMainClass() {
+    return InvokeEmpty.class;
+  }
+
+  @Override
+  public List<Class<?>> getTestClasses() {
+    return ImmutableList.of(getMainClass(), ClassA.class, ClassB.class);
+  }
+
+  @Override
+  public String getExpected() {
+    return "AB";
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    runTestDesugaring();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    runTestR8();
+  }
+
+  @Test
+  public void testDebug() throws Exception {
+    runTestDebugComparator();
+  }
+}