Reapply "Add support for outlining StringBuilders"
This reverts commit ff0c12bd8a973c2bd12b63f1b5fbac5cb5901c0d.
Change-Id: I1af86778c9f2bffa0ed1e8b55c0290b1dc980696
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 003e17f..2287bbc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -96,10 +96,19 @@
return prev;
}
+ public boolean hasNext() {
+ return next != null;
+ }
+
public Instruction getNext() {
return next;
}
+ @SuppressWarnings("TypeParameterUnusedInFormals")
+ public <T extends Instruction> T nextUntilExclusive(Predicate<Instruction> predicate) {
+ return hasNext() ? getNext().nextUntilInclusive(predicate) : null;
+ }
+
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
public <T extends Instruction> T nextUntilInclusive(Predicate<Instruction> predicate) {
Instruction current = this;
diff --git a/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java b/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java
index a7d82e0..8f61c05 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ThrowBlockOutlineMarker.java
@@ -37,7 +37,7 @@
ListUtils.mapOrElse(
inValues,
(i, argument) -> {
- if (outline.getParentOrSelf().isArgumentConstant(i)) {
+ if (outline.isArgumentConstant(i)) {
argument.removeUser(this);
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
index ae16771..353aba6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutline.java
@@ -295,6 +295,14 @@
return materializedOutlineMethod != null;
}
+ public boolean isStringBuilderToStringOutline() {
+ return !isThrowOutline();
+ }
+
+ public boolean isThrowOutline() {
+ return proto.getReturnType().isVoidType();
+ }
+
public void materialize(AppView<?> appView, MethodProcessingContext methodProcessingContext) {
assert verifyNotMerged();
SyntheticItems syntheticItems = appView.getSyntheticItems();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
index e9de503..04e8567 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlineMarkerRewriter.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
@@ -18,7 +19,6 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
-import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.ThrowBlockOutlineMarker;
import com.android.tools.r8.ir.code.UnusedArgument;
import com.android.tools.r8.ir.code.Value;
@@ -41,10 +41,12 @@
private final AppView<?> appView;
private final DeadCodeRemover deadCodeRemover;
+ private final DexItemFactory factory;
ThrowBlockOutlineMarkerRewriter(AppView<?> appView) {
this.appView = appView;
this.deadCodeRemover = new DeadCodeRemover(appView);
+ this.factory = appView.dexItemFactory();
}
public void processOutlineMethod(
@@ -109,80 +111,92 @@
private void processOutlineMarkers(IRCode code) {
for (BasicBlock block : code.getBlocks()) {
- Throw throwInstruction = block.exit().asThrow();
- if (throwInstruction != null) {
- ThrowBlockOutlineMarker outlineMarker =
- block.entry().nextUntilInclusive(Instruction::isThrowBlockOutlineMarker);
- if (outlineMarker != null) {
- ThrowBlockOutline outline = outlineMarker.getOutline();
- outlineMarker.detachConstantOutlineArguments(outline);
- if (outline.isMaterialized()) {
- // Insert a call to the materialized outline method and load the return value.
- BasicBlockInstructionListIterator instructionIterator =
- block.listIterator(block.exit());
- InvokeStatic invoke =
- InvokeStatic.builder()
- .setArguments(outlineMarker.inValues())
- .setIsInterface(false)
- .setMethod(outline.getMaterializedOutlineMethod())
- .setPosition(throwInstruction)
- .build();
+ ThrowBlockOutlineMarker outlineMarker =
+ block.entry().nextUntilInclusive(Instruction::isThrowBlockOutlineMarker);
+ while (outlineMarker != null) {
+ ThrowBlockOutline outline = outlineMarker.getOutline().getParentOrSelf();
+ Instruction outlineEnd = getOutlineEnd(block, outline, outlineMarker);
+ outlineMarker.detachConstantOutlineArguments(outline);
+ if (outline.isMaterialized()) {
+ // Insert a call to the materialized outline method and load the return value.
+ BasicBlockInstructionListIterator instructionIterator = block.listIterator(outlineEnd);
+ InvokeStatic.Builder invokeBuilder =
+ InvokeStatic.builder()
+ .setArguments(outlineMarker.inValues())
+ .setIsInterface(false)
+ .setMethod(outline.getMaterializedOutlineMethod())
+ .setPosition(outlineEnd);
+ if (outline.isStringBuilderToStringOutline()) {
+ invokeBuilder.setFreshOutValue(
+ code, factory.stringType.toTypeElement(appView), outlineEnd.getLocalInfo());
+ }
+ InvokeStatic invoke = invokeBuilder.build();
+ if (outline.isStringBuilderToStringOutline()) {
+ outlineEnd.replace(invoke);
+ outlineEnd = invoke;
+ } else {
+ assert outline.isThrowOutline();
instructionIterator.add(invoke);
- Value returnValue = addReturnOrThrowValue(code, instructionIterator);
+ Value returnOrThrowValue = addReturnOrThrowValue(code, instructionIterator);
// Replace the throw instruction by a normal return, but throw null in initializers.
if (code.context().getDefinition().isInstanceInitializer()) {
- throwInstruction.replaceValue(0, returnValue);
+ outlineEnd.replaceValue(0, returnOrThrowValue);
} else {
Return returnInstruction =
Return.builder()
.setPositionForNonThrowingInstruction(
- throwInstruction.getPosition(), appView.options())
- .setReturnValue(returnValue)
+ outlineEnd.getPosition(), appView.options())
+ .setReturnValue(returnOrThrowValue)
.build();
block.replaceLastInstruction(returnInstruction);
+ outlineEnd = returnInstruction;
}
+ }
- // Remove all outlined instructions bottom up.
- instructionIterator = block.listIterator(invoke);
- for (Instruction instruction = instructionIterator.previous();
- instruction != outlineMarker;
- instruction = instructionIterator.previous()) {
- Value outValue = instruction.outValue();
- if (outValue == null || !outValue.hasNonDebugUsers()) {
- // Remove all debug users of the out-value.
- if (outValue != null && outValue.hasDebugUsers()) {
- for (Instruction debugUser : outValue.debugUsers()) {
- debugUser.getDebugValues().remove(outValue);
- if (debugUser.isDebugLocalRead() && debugUser.getDebugValues().isEmpty()) {
- debugUser.remove();
- }
+ // Remove all outlined instructions bottom up.
+ instructionIterator = block.listIterator(invoke);
+ for (Instruction outlinedInstruction = instructionIterator.previous();
+ outlinedInstruction != outlineMarker;
+ outlinedInstruction = instructionIterator.previous()) {
+ assert !outlinedInstruction.isThrowBlockOutlineMarker();
+ Value outValue = outlinedInstruction.outValue();
+ if (outValue == null || !outValue.hasNonDebugUsers()) {
+ // Remove all debug users of the out-value.
+ if (outValue != null && outValue.hasDebugUsers()) {
+ for (Instruction debugUser : outValue.debugUsers()) {
+ debugUser.getDebugValues().remove(outValue);
+ if (debugUser.isDebugLocalRead() && debugUser.getDebugValues().isEmpty()) {
+ debugUser.remove();
}
- outValue.clearDebugUsers();
}
- // We are not using `removeOrReplaceByDebugLocalRead` here due to the backwards
- // iteration.
- if (instruction.getDebugValues().isEmpty()) {
- instruction.remove();
- } else {
- DebugLocalRead replacement = new DebugLocalRead();
- instruction.replace(replacement);
- Instruction previous = instructionIterator.previous();
- assert previous == replacement;
- }
+ outValue.clearDebugUsers();
+ }
+ // We are not using `removeOrReplaceByDebugLocalRead` here due to the backwards
+ // iteration.
+ if (outlinedInstruction.getDebugValues().isEmpty()) {
+ outlinedInstruction.remove();
+ } else {
+ DebugLocalRead replacement = new DebugLocalRead();
+ outlinedInstruction.replace(replacement);
+ Instruction previous = instructionIterator.previous();
+ assert previous == replacement;
}
}
}
-
- // Finally delete the outline marker.
- outlineMarker.removeOrReplaceByDebugLocalRead();
-
- // Blocks cannot start with DebugLocalRead.
- while (block.entry().isDebugLocalRead()) {
- block.entry().moveDebugValues(block.entry().getNext());
- block.entry().remove();
- }
}
+
+ // Finally delete the outline marker.
+ outlineMarker.removeOrReplaceByDebugLocalRead();
+
+ // Blocks cannot start with DebugLocalRead.
+ while (block.entry().isDebugLocalRead()) {
+ block.entry().moveDebugValues(block.entry().getNext());
+ block.entry().remove();
+ }
+
+ // Continue searching for outline markers from the end of the current outline.
+ outlineMarker = outlineEnd.nextUntilExclusive(Instruction::isThrowBlockOutlineMarker);
}
assert block.streamInstructions().noneMatch(Instruction::isThrowBlockOutlineMarker);
}
@@ -216,4 +230,15 @@
return null;
}
}
+
+ private Instruction getOutlineEnd(
+ BasicBlock block, ThrowBlockOutline outline, ThrowBlockOutlineMarker outlineMarker) {
+ if (outline.isThrowOutline()) {
+ return block.exit();
+ } else {
+ // The end of a StringBuilder#toString outline is the call to StringBuilder#toString.
+ return outlineMarker.nextUntilExclusive(
+ i -> ThrowBlockOutlinerScanner.isStringBuilderToString(i, factory));
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerOptions.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerOptions.java
index 7f6ea7d..ce48c06 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerOptions.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerOptions.java
@@ -15,6 +15,10 @@
SystemPropertyUtils.parseSystemPropertyOrDefault(
"com.android.tools.r8.throwblockoutliner.enable", false);
+ public boolean enableStringBuilderOutlining =
+ SystemPropertyUtils.parseSystemPropertyOrDefault(
+ "com.android.tools.r8.throwblockoutliner.enablestringbuilder", false);
+
public final int costInBytesForTesting =
SystemPropertyUtils.parseSystemPropertyOrDefault(
"com.android.tools.r8.throwblockoutliner.cost", -1);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java
index 582fc97..2fda1c4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerScanner.java
@@ -42,6 +42,7 @@
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Position.SyntheticPosition;
+import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Throw;
import com.android.tools.r8.ir.code.ThrowBlockOutlineMarker;
import com.android.tools.r8.ir.code.Value;
@@ -92,6 +93,28 @@
if (block.exit().isThrow()) {
new ThrowBlockOutlinerScannerForThrow(code, block).tryBuildOutline();
}
+ if (!appView.options().getThrowBlockOutlinerOptions().enableStringBuilderOutlining) {
+ continue;
+ }
+ Instruction previousOutlineEnd = null;
+ for (Instruction instruction : block.getInstructions()) {
+ // If we encounter an outline marker it is because we were able to outline the tail of the
+ // block above. Since the remainder of the block has been outlined, abort further outlining.
+ if (instruction.isThrowBlockOutlineMarker()) {
+ assert block.exit().isThrow();
+ break;
+ }
+ if (isStringBuilderToString(instruction, factory)) {
+ InvokeVirtual invoke = instruction.asInvokeVirtual();
+ ThrowBlockOutline outline =
+ new ThrowBlockOutlinerScannerForStringBuilderToString(
+ code, block, invoke, previousOutlineEnd)
+ .tryBuildOutline();
+ if (outline != null) {
+ previousOutlineEnd = instruction;
+ }
+ }
+ }
}
if (code.metadata().mayHaveThrowBlockOutlineMarker()) {
if (appView.enableWholeProgramOptimizations()) {
@@ -113,55 +136,68 @@
return outlines.getOutlines();
}
+ static boolean isStringBuilderToString(Instruction instruction, DexItemFactory factory) {
+ InvokeVirtual invoke = instruction.asInvokeVirtual();
+ return invoke != null
+ && invoke.getInvokedMethod().match(factory.objectMembers.toString)
+ && invoke.getReceiver().getType().isClassType(factory.stringBuilderType)
+ && invoke.hasOutValue();
+ }
+
private abstract class ThrowBlockOutlinerScannerForInstruction {
final IRCode code;
final BasicBlock block;
+ final Instruction previousOutlineEnd;
private boolean hasRunPrefixer;
- ThrowBlockOutlinerScannerForInstruction(IRCode code, BasicBlock block) {
+ ThrowBlockOutlinerScannerForInstruction(
+ IRCode code, BasicBlock block, Instruction previousOutlineEnd) {
this.code = code;
this.block = block;
+ this.previousOutlineEnd = previousOutlineEnd;
}
void processInstruction(Instruction instruction, Consumer<OutlineBuilder> continuation) {
- switch (instruction.opcode()) {
- case ASSUME:
- processAssume(instruction.asAssume(), continuation);
- return;
- case CONST_NUMBER:
- case CONST_STRING:
- processConstInstruction(instruction.asConstInstruction(), continuation);
- return;
- case DEBUG_LOCAL_READ:
- case DEBUG_POSITION:
- processNonMaterializingDebugInstruction(instruction, continuation);
- return;
- case INVOKE_DIRECT:
- if (instruction.isInvokeConstructor(factory)) {
- processStringBuilderConstructorCall(instruction.asInvokeDirect(), continuation);
+ if (instruction != previousOutlineEnd) {
+ switch (instruction.opcode()) {
+ case ASSUME:
+ processAssume(instruction.asAssume(), continuation);
return;
- }
- break;
- case INVOKE_STATIC:
- processStringFormatOrValueOf(instruction.asInvokeStatic(), continuation);
- return;
- case INVOKE_VIRTUAL:
- processStringBuilderAppendOrToString(instruction.asInvokeVirtual(), continuation);
- return;
- case MOVE:
- if (instruction.isDebugLocalWrite()) {
- processDebugLocalWrite(instruction.asDebugLocalWrite(), continuation);
+ case CONST_NUMBER:
+ case CONST_STRING:
+ processConstInstruction(instruction.asConstInstruction(), continuation);
return;
- }
- assert false;
- break;
- case NEW_INSTANCE:
- processNewInstanceInstruction(instruction.asNewInstance(), continuation);
- return;
- default:
- break;
+ case DEBUG_LOCAL_READ:
+ case DEBUG_POSITION:
+ processNonMaterializingDebugInstruction(instruction, continuation);
+ return;
+ case INVOKE_DIRECT:
+ if (instruction.isInvokeConstructor(factory)) {
+ processStringBuilderConstructorCall(instruction.asInvokeDirect(), continuation);
+ return;
+ }
+ break;
+ case INVOKE_STATIC:
+ processStringFormatOrValueOf(instruction.asInvokeStatic(), continuation);
+ return;
+ case INVOKE_VIRTUAL:
+ processStringBuilderAppendOrToString(instruction.asInvokeVirtual(), continuation);
+ return;
+ case MOVE:
+ if (instruction.isDebugLocalWrite()) {
+ processDebugLocalWrite(instruction.asDebugLocalWrite(), continuation);
+ return;
+ }
+ assert false;
+ break;
+ case NEW_INSTANCE:
+ processNewInstanceInstruction(instruction.asNewInstance(), continuation);
+ return;
+ default:
+ break;
+ }
}
// Unhandled instruction. Start the outline at the successor instruction.
startOutline(instruction.getNext(), continuation);
@@ -300,7 +336,7 @@
});
}
- private void processStringBuilderAppendOrToString(
+ void processStringBuilderAppendOrToString(
InvokeVirtual invoke, Consumer<OutlineBuilder> continuation) {
DexMethod invokedMethod = invoke.getInvokedMethod();
if (!factory.stringBuilderMethods.isAppendMethod(invokedMethod)
@@ -357,12 +393,75 @@
}
}
+ private class ThrowBlockOutlinerScannerForStringBuilderToString
+ extends ThrowBlockOutlinerScannerForInstruction {
+
+ private final InvokeVirtual stringBuilderToStringInstruction;
+
+ private ThrowBlockOutline outline;
+
+ ThrowBlockOutlinerScannerForStringBuilderToString(
+ IRCode code,
+ BasicBlock block,
+ InvokeVirtual stringBuilderToStringInstruction,
+ Instruction previousOutlineEnd) {
+ super(code, block, previousOutlineEnd);
+ this.stringBuilderToStringInstruction = stringBuilderToStringInstruction;
+ }
+
+ ThrowBlockOutline tryBuildOutline() {
+ // Recursively build up the outline method. On successful outline creation, the resulting
+ // LirCode is passed to the continuation function.
+ processStringBuilderToString(
+ outlineBuilder -> {
+ // On successful outline creation, store the outline for later processing.
+ DexProto proto = outlineBuilder.getProto(appView, factory.stringType);
+ if (proto == null) {
+ return;
+ }
+ LirCode<?> lirCode = outlineBuilder.buildLirCode(appView, code.context());
+ outline = outlines.add(lirCode, proto, code.context());
+ assert proto.isIdenticalTo(outline.getProto());
+ List<Value> arguments = outlineBuilder.buildArguments();
+ outline.addUser(code.reference(), arguments, getAbstractValueFactory());
+
+ // Insert a synthetic marker instruction that references the outline so that we know
+ // where to materialize the outline call.
+ Instruction insertionPoint = outlineBuilder.getFirstOutlinedInstruction();
+ assert insertionPoint.getBlock() == block;
+ ThrowBlockOutlineMarker marker =
+ ThrowBlockOutlineMarker.builder()
+ .setArguments(arguments)
+ .setOutline(outline)
+ .setPosition(Position.none())
+ .build();
+ block.listIterator(insertionPoint).add(marker);
+ });
+ return outline;
+ }
+
+ private void processStringBuilderToString(Consumer<OutlineBuilder> continuation) {
+ processStringBuilderAppendOrToString(
+ stringBuilderToStringInstruction,
+ outlineBuilder -> {
+ Value outlinedStringValue =
+ outlineBuilder.getOutlinedValue(stringBuilderToStringInstruction.outValue());
+ outlineBuilder.add(
+ Return.builder()
+ .setPosition(Position.syntheticNone())
+ .setReturnValue(outlinedStringValue)
+ .build());
+ continuation.accept(outlineBuilder);
+ });
+ }
+ }
+
private class ThrowBlockOutlinerScannerForThrow extends ThrowBlockOutlinerScannerForInstruction {
private final Throw throwInstruction;
ThrowBlockOutlinerScannerForThrow(IRCode code, BasicBlock block) {
- super(code, block);
+ super(code, block, null);
this.throwInstruction = block.exit().asThrow();
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index a3de438..3ad5193 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -322,7 +322,7 @@
assertEquals(Collections.emptyList(), synthesizedJavaLambdaClasses);
assertEquals(
- Collections.singleton("java.lang.StringBuilder"),
+ Collections.emptySet(),
collectTypes(clazz.uniqueMethodWithOriginalName("testStatelessLambda")));
assertEquals(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerConstArgumentTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerConstArgumentTest.java
index 2badee6..87e57c2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerConstArgumentTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/exceptions/ThrowBlockOutlinerConstArgumentTest.java
@@ -22,6 +22,8 @@
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
import org.junit.Test;
public class ThrowBlockOutlinerConstArgumentTest extends ThrowBlockOutlinerTestBase {
@@ -62,10 +64,12 @@
@Override
public void inspectOutlines(Collection<ThrowBlockOutline> outlines, DexItemFactory factory) {
- // Verify that we have two outlines with one and three users, respectively.
- assertEquals(2, outlines.size());
+ // Verify that we have two throw block outlines with one and three users, respectively.
+ List<ThrowBlockOutline> throwOutlines =
+ outlines.stream().filter(ThrowBlockOutline::isThrowOutline).collect(Collectors.toList());
+ assertEquals(2, throwOutlines.size());
IntSet numberOfUsers = new IntArraySet();
- for (ThrowBlockOutline outline : outlines) {
+ for (ThrowBlockOutline outline : throwOutlines) {
numberOfUsers.add(outline.getNumberOfUsers());
}
assertTrue(numberOfUsers.contains(1));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/stringbuilders/StringBuilderOutlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/stringbuilders/StringBuilderOutlinerTest.java
new file mode 100644
index 0000000..b64c5ba
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/stringbuilders/StringBuilderOutlinerTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2025, 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.ir.optimize.outliner.stringbuilders;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.ir.optimize.outliner.exceptions.ThrowBlockOutline;
+import com.android.tools.r8.ir.optimize.outliner.exceptions.ThrowBlockOutlinerTestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Collection;
+import org.junit.Test;
+
+public class StringBuilderOutlinerTest extends ThrowBlockOutlinerTestBase {
+
+ @Test
+ public void testD8() throws Exception {
+ runTest(testForD8(parameters));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeRelease();
+ runTest(testForR8(parameters).addKeepMainRule(Main.class));
+ }
+
+ private void runTest(
+ TestCompilerBuilder<?, ?, ?, ? extends SingleTestRunResult<?>, ?> testBuilder)
+ throws Exception {
+ testBuilder
+ .addInnerClasses(getClass())
+ .apply(this::configure)
+ .compile()
+ .inspect(this::inspectOutput)
+ .run(parameters.getRuntime(), Main.class, "Hel", "lo", ", world", "!")
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ @Override
+ public void inspectOutlines(Collection<ThrowBlockOutline> outlines, DexItemFactory factory) {
+ assertEquals(1, outlines.size());
+ ThrowBlockOutline outline = outlines.iterator().next();
+ assertEquals(2, outline.getNumberOfUsers());
+ }
+
+ private void inspectOutput(CodeInspector inspector) {
+ MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
+ assertTrue(mainMethod.streamInstructions().noneMatch(InstructionSubject::isNewInstance));
+ }
+
+ @Override
+ public boolean shouldOutline(ThrowBlockOutline outline) {
+ return true;
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ String s1 = new StringBuilder().append(args[0]).append(args[1]).toString();
+ String s2 = new StringBuilder().append(args[2]).append(args[3]).toString();
+ System.out.print(s1);
+ System.out.println(s2);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 9646cc8..ef9fece 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -6,12 +6,10 @@
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_5_0;
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_6_0;
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_9_21;
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_2_0_20;
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_2_1_10;
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -60,7 +58,7 @@
}
@Test
- public void testJStyleLambdas() throws Exception {
+ public void testJStyleLambdasNoClassInlining() throws Exception {
// SAM interfaces lambdas are implemented by invoke dynamic in kotlin 1.5 unlike 1.4 where a
// class is generated for each. In CF we leave invokeDynamic but for DEX we desugar the classes
// and merge them.
@@ -87,14 +85,18 @@
"class_inliner_lambda_j_style.MainKt$testStateful3$1");
} else if (testParameters.isDexRuntime()) {
Set<Set<DexType>> mergeGroups = inspector.getMergeGroups();
- assertEquals(1, mergeGroups.size());
- inspector.assertIsCompleteMergeGroup(
- "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda0",
- "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda2",
- "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda4",
- "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda3",
- "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda5",
- "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda6");
+ assertEquals(2, mergeGroups.size());
+ inspector
+ .assertIsCompleteMergeGroup(
+ "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda1",
+ "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda3",
+ "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda4",
+ "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda5",
+ "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda6",
+ "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda7")
+ .assertIsCompleteMergeGroup(
+ "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda2",
+ "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticThrowBlockOutline0");
}
inspector.assertNoOtherClassesMerged();
})
@@ -103,25 +105,16 @@
inspector -> {
if (testParameters.isCfRuntime() && !hasKotlinCGeneratedLambdaClasses) {
assertEquals(5, inspector.allClasses().size());
- } else if (!hasKotlinCGeneratedLambdaClasses) {
- assertThat(
- inspector.clazz(
- "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda1"),
- isPresent());
- assertThat(
- inspector.clazz(
- "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda2"),
- isAbsent());
} else {
- assertThat(
- inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
- isAbsent());
- assertThat(
- inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1"),
- isPresent());
+ assertEquals(7, inspector.allClasses().size());
}
});
+ }
+ @Test
+ public void testJStyleLambdas() throws Exception {
+ boolean hasKotlinCGeneratedLambdaClasses = kotlinParameters.isOlderThan(KOTLINC_1_5_0);
+ String mainClassName = "class_inliner_lambda_j_style.MainKt";
runTest(
"class_inliner_lambda_j_style",
mainClassName,
@@ -136,39 +129,14 @@
inspector -> {
if (testParameters.isCfRuntime() && !hasKotlinCGeneratedLambdaClasses) {
assertEquals(5, inspector.allClasses().size());
- return;
- }
- if (!hasKotlinCGeneratedLambdaClasses) {
- // Kotlin 1.6.20 and later do not create intrinsics.stringPlus for two argument
- // string concatination. That allow R8's stringbuilder optimization to reduce the
- // size of strings and therefore inline the synthetic lambda.
- assertThat(
- inspector.clazz(
- "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda1"),
- isAbsentIf(kotlinParameters.isNewerThan(KOTLINC_1_6_0)));
} else {
- assertThat(
- inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateless$1"),
- isAbsent());
- }
-
- if (hasKotlinCGeneratedLambdaClasses) {
- assertThat(
- testParameters.isCfRuntime()
- ? inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1")
- : inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$2"),
- isPresent());
- } else {
- assertThat(
- inspector.clazz(
- "class_inliner_lambda_j_style.MainKt$$ExternalSyntheticLambda2"),
- isAbsent());
+ assertEquals(7, inspector.allClasses().size());
}
});
}
@Test
- public void testKStyleLambdas() throws Exception {
+ public void testKStyleLambdasNoClassInlining() throws Exception {
String mainClassName = "class_inliner_lambda_k_style.MainKt";
runTest(
"class_inliner_lambda_k_style",
@@ -264,7 +232,11 @@
isPresent());
}
});
+ }
+ @Test
+ public void testKStyleLambdas() throws Exception {
+ String mainClassName = "class_inliner_lambda_k_style.MainKt";
runTest(
"class_inliner_lambda_k_style",
mainClassName,
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java
index c9467fa..b02f228 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingCapturesKotlinStyleTest.java
@@ -13,10 +13,11 @@
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -81,7 +82,20 @@
.addProgramFiles(getProgramFiles())
.addKeepMainRule(getMainClassName())
.addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
+ inspector -> {
+ if (parameters.isDexRuntime()
+ && kotlinParameters.getLambdaGeneration().isInvokeDynamic()) {
+ inspector
+ .assertIsCompleteMergeGroup(
+ SyntheticItemsTestUtils.syntheticThrowBlockOutlineClass(
+ getMainClassReference(), 0),
+ SyntheticItemsTestUtils.syntheticThrowBlockOutlineClass(
+ getMainClassReference(), 1))
+ .assertNoOtherClassesMerged();
+ } else {
+ inspector.assertNoClassesMerged();
+ }
+ })
.allowAccessModification(allowAccessModification)
.setMinApi(parameters)
.compile()
@@ -138,6 +152,10 @@
return getTestName() + ".MainKt";
}
+ private ClassReference getMainClassReference() {
+ return Reference.classFromTypeName(getMainClassName());
+ }
+
private List<Path> getProgramFiles() {
Path kotlinJarFile =
getCompileMemoizer(getKotlinFilesInResource(getTestName()), getTestName())
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
index da524c4..56178bd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingKeepAttributesKotlinStyleTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.kotlin.lambda;
-import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_9_21;
import static com.android.tools.r8.shaking.ProguardKeepAttributes.ENCLOSING_METHOD;
import static com.android.tools.r8.shaking.ProguardKeepAttributes.INNER_CLASSES;
import static com.android.tools.r8.shaking.ProguardKeepAttributes.SIGNATURE;
@@ -189,21 +188,6 @@
ClassReference mainKt = Reference.classFromTypeName(getMainClassName());
List<ClassReference> mergeGroup =
ImmutableList.of(
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 9),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 10),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 11),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 12),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 13),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 14),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 19),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 18),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 20),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 21),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 22),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 23));
- List<ClassReference> otherMergeGroup =
- ImmutableList.of(
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 0),
SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 1),
SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 2),
SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 3),
@@ -212,24 +196,24 @@
SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 6),
SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 7),
SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 8),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 9),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 10),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 11),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 12),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 13),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 14),
SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 15),
SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 16),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 17));
- inspector
- .applyIf(
- kotlinc.getCompilerVersion().isGreaterThanOrEqualTo(KOTLINC_1_9_21)
- && parameters.isDexRuntime()
- && lambdaGeneration.isInvokeDynamic(),
- i ->
- i.assertIsCompleteMergeGroup(
- ImmutableList.<ClassReference>builder()
- .addAll(mergeGroup)
- .addAll(otherMergeGroup)
- .build()),
- i ->
- i.assertIsCompleteMergeGroup(mergeGroup)
- .assertIsCompleteMergeGroup(otherMergeGroup))
- .assertNoOtherClassesMerged();
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 17),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 18),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 19),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 20),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 21),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 22),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 23),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainKt, 24),
+ SyntheticItemsTestUtils.syntheticThrowBlockOutlineClass(mainKt, 0));
+ inspector.assertIsCompleteMergeGroup(mergeGroup).assertNoOtherClassesMerged();
}
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
index ae02a7f..ca2d90c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
@@ -173,13 +173,24 @@
},
i -> {
i.assertIsCompleteMergeGroup(
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 0),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 1),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 3),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 4),
SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 0))
.assertIsCompleteMergeGroup(
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 2),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 3),
- SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 1));
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 7),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 8),
+ SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 2))
+ .assertIsCompleteMergeGroup(
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 5),
+ SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 6),
+ SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 1))
+ .assertIsCompleteMergeGroup(
+ SyntheticItemsTestUtils.syntheticThrowBlockOutlineClass(
+ mainClassReference, 0),
+ SyntheticItemsTestUtils.syntheticThrowBlockOutlineClass(
+ mainClassReference, 1),
+ SyntheticItemsTestUtils.syntheticThrowBlockOutlineClass(
+ mainClassReference, 2));
for (int id = 4; id < 30; id++) {
inspector.assertClassReferencesNotMerged(
SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, id));
@@ -189,48 +200,8 @@
SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 2),
SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 3));
} else {
- inspector
- .assertIsCompleteMergeGroup(
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 1),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 2),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 3),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 4))
- .assertIsCompleteMergeGroup(
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 0),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 9),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 10),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 11),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 12),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 21),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 22),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 23),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 24),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 25),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 26),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 27),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 28))
- .assertIsCompleteMergeGroup(
- SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 0),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 13),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 14))
- .assertIsCompleteMergeGroup(
- SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 1),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 15),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 16))
- .assertIsCompleteMergeGroup(
- SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 2),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 17),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 18))
- .assertIsCompleteMergeGroup(
- SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 3),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 19),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 20))
- .assertIsCompleteMergeGroup(
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 5),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 6))
- .assertIsCompleteMergeGroup(
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 7),
- SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 8));
+ assertEquals(10, inspector.getMergeGroups().size());
+ assertEquals(34, inspector.getSources().size());
}
}
diff --git a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
index 25efdbf..86abea7 100644
--- a/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/testbase/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -65,6 +65,7 @@
options -> {
options.testing.enableTestAssertions = true;
options.getThrowBlockOutlinerOptions().enable = true;
+ options.getThrowBlockOutlinerOptions().enableStringBuilderOutlining = true;
};
public static final Consumer<InternalOptions> DEFAULT_D8_OPTIONS = DEFAULT_OPTIONS;
diff --git a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 9b5bb95..e70142c 100644
--- a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -105,6 +105,10 @@
}
public static ClassReference syntheticThrowBlockOutlineClass(Class<?> clazz, int id) {
+ return syntheticThrowBlockOutlineClass(Reference.classFromClass(clazz), id);
+ }
+
+ public static ClassReference syntheticThrowBlockOutlineClass(ClassReference clazz, int id) {
return syntheticClass(clazz, naming.THROW_BLOCK_OUTLINE, id);
}