Process inlining of methods in waves instead of ruling out breakers.
We used to never inline across an edge in the call-graph that was
removed during cycle removal to avoid non-determinism. With this
change, we only disallow inlining of methods that are being processed
concurrently. This is equivalent, as a method that is being inlined
concurrently must have had an edge in the call-graph that was removed if
it is called from another method. However, we do not rule out inlining
of such breakers across unrelated edges.
Bug:
Change-Id: I463fe850e677686624104060f48c2b2eab401c98
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 8d28546..8c071c8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
@@ -107,13 +108,13 @@
return accessFlags.isConstructor() && accessFlags.isStatic();
}
- public boolean isInliningCandidate(DexEncodedMethod container, boolean alwaysInline,
+ public boolean isInliningCandidate(DexEncodedMethod container, Reason inliningReason,
AppInfoWithSubtyping appInfo) {
if (isClassInitializer()) {
// This will probably never happen but never inline a class initializer.
return false;
}
- if (alwaysInline) {
+ if (inliningReason == Reason.FORCE) {
return true;
}
switch (compilationState) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 585260b..b32f34d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -18,12 +18,11 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.ThrowingConsumer;
+import com.android.tools.r8.utils.ThrowingBiConsumer;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -32,8 +31,8 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
@@ -51,7 +50,7 @@
* <p>
* Recursive calls are not present.
*/
-public class CallGraph {
+public class CallGraph extends CallSiteInformation {
private CallGraph(InternalOptions options) {
this.shuffle = options.testing.irOrdering;
@@ -132,15 +131,7 @@
}
private final Map<DexEncodedMethod, Node> nodes = new LinkedHashMap<>();
- private final Map<DexEncodedMethod, Set<DexEncodedMethod>> breakers = new HashMap<>();
- private final Function<List<DexEncodedMethod>, List<DexEncodedMethod>> shuffle;
-
- // Returns whether the method->callee edge has been removed from the call graph
- // to break a cycle in the call graph.
- public boolean isBreaker(DexEncodedMethod method, DexEncodedMethod callee) {
- Set<DexEncodedMethod> value = breakers.get(method);
- return (value != null) && value.contains(callee);
- }
+ private final Function<Set<DexEncodedMethod>, Set<DexEncodedMethod>> shuffle;
private Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet();
private Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
@@ -170,10 +161,12 @@
* For pinned methods (methods kept through Proguard keep rules) this will always answer
* <code>false</code>.
*/
+ @Override
public boolean hasSingleCallSite(DexEncodedMethod method) {
return singleCallSite.contains(method);
}
+ @Override
public boolean hasDoubleCallSite(DexEncodedMethod method) {
return doubleCallSite.contains(method);
}
@@ -211,12 +204,10 @@
* All nodes in the graph are extracted if called repeatedly until null is returned.
* Please note that there are no cycles in this graph (see {@link #breakCycles}).
* <p>
- *
- * @return List of {@link DexEncodedMethod}.
*/
- private List<DexEncodedMethod> extractLeaves() {
+ private Set<DexEncodedMethod> extractLeaves() {
if (isEmpty()) {
- return Collections.emptyList();
+ return Collections.emptySet();
}
// First identify all leaves before removing them from the graph.
List<Node> leaves = nodes.values().stream().filter(Node::isLeaf).collect(Collectors.toList());
@@ -224,7 +215,8 @@
leaf.callers.forEach(caller -> caller.callees.remove(leaf));
nodes.remove(leaf.method);
});
- return shuffle.apply(leaves.stream().map(leaf -> leaf.method).collect(Collectors.toList()));
+ return shuffle.apply(leaves.stream().map(leaf -> leaf.method)
+ .collect(Collectors.toCollection(LinkedHashSet::new)));
}
private int traverse(Node node, Set<Node> stack, Set<Node> marked) {
@@ -246,8 +238,6 @@
// We have a cycle; break it by removing node->callee.
toBeRemoved.add(callee);
callee.callers.remove(node);
- breakers.computeIfAbsent(node.method,
- ignore -> Sets.newIdentityHashSet()).add(callee.method);
} else {
numberOfCycles += traverse(callee, stack, marked);
}
@@ -264,7 +254,6 @@
private int breakCycles() {
// Break cycles in this call graph by removing edges causing cycles.
- // The remove edges are stored in @breakers.
int numberOfCycles = 0;
Set<Node> stack = Sets.newIdentityHashSet();
Set<Node> marked = Sets.newIdentityHashSet();
@@ -294,16 +283,23 @@
return nodes.size() == 0;
}
+ /**
+ * Applies the given method to all leaf nodes of the graph.
+ * <p>
+ * As second parameter, a predicate that can be used to decide whether another method is
+ * processed at the same time is passed. This can be used to avoid races in concurrent processing.
+ */
public <E extends Exception> void forEachMethod(
- ThrowingConsumer<DexEncodedMethod, E> consumer, ExecutorService executorService)
+ ThrowingBiConsumer<DexEncodedMethod, Predicate<DexEncodedMethod>, E> consumer,
+ ExecutorService executorService)
throws ExecutionException {
while (!isEmpty()) {
- List<DexEncodedMethod> methods = extractLeaves();
+ Set<DexEncodedMethod> methods = extractLeaves();
assert methods.size() > 0;
List<Future<?>> futures = new ArrayList<>();
for (DexEncodedMethod method : methods) {
futures.add(executorService.submit(() -> {
- consumer.accept(method);
+ consumer.accept(method, methods::contains);
return null; // we want a Callable not a Runnable to be able to throw
}));
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
new file mode 100644
index 0000000..651fb6f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2017, 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.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+
+public abstract class CallSiteInformation {
+
+ /**
+ * Check if the <code>method</code> is guaranteed to only have a single call site.
+ * <p>
+ * For pinned methods (methods kept through Proguard keep rules) this will always answer
+ * <code>false</code>.
+ */
+ public abstract boolean hasSingleCallSite(DexEncodedMethod method);
+
+ public abstract boolean hasDoubleCallSite(DexEncodedMethod method);
+
+ public static CallSiteInformation empty() {
+ return EmptyCallSiteInformation.EMPTY_INFO;
+ }
+
+ private static class EmptyCallSiteInformation extends CallSiteInformation {
+
+ private static EmptyCallSiteInformation EMPTY_INFO = new EmptyCallSiteInformation();
+
+ @Override
+ public boolean hasSingleCallSite(DexEncodedMethod method) {
+ return false;
+ }
+
+ @Override
+ public boolean hasDoubleCallSite(DexEncodedMethod method) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 82cc92f..b1e68f7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -49,6 +49,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
+import java.util.function.Predicate;
public class IRConverter {
@@ -68,7 +69,6 @@
private final LensCodeRewriter lensCodeRewriter;
private final Inliner inliner;
private final ProtoLitePruner protoLiteRewriter;
- private CallGraph callGraph;
private OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
private DexString highestSortingString;
@@ -251,7 +251,9 @@
boolean matchesMethodFilter = options.methodMatchesFilter(method);
if (matchesMethodFilter) {
if (method.getCode().isJarCode()) {
- rewriteCode(method, ignoreOptimizationFeedback, Outliner::noProcessing);
+ // We do not process in call graph order, so anything could be a leaf.
+ rewriteCode(method, ignoreOptimizationFeedback, x -> true, CallSiteInformation.empty(),
+ Outliner::noProcessing);
}
updateHighestSortingStrings(method);
}
@@ -271,10 +273,6 @@
throws ExecutionException, ApiLevelException {
removeLambdaDeserializationMethods();
- timing.begin("Build call graph");
- callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense, options);
- timing.end();
-
// The process is in two phases.
// 1) Subject all DexEncodedMethods to optimization (except outlining).
// - a side effect is candidates for outlining are identified.
@@ -282,13 +280,19 @@
// Ideally, we should outline eagerly when threshold for a template has been reached.
// Process the application identifying outlining candidates.
- timing.begin("IR conversion phase 1");
OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
- callGraph.forEachMethod(method -> {
- processMethod(method, directFeedback,
- outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
- }, executorService);
- timing.end();
+ {
+ timing.begin("Build call graph");
+ CallGraph callGraph = CallGraph
+ .build(application, appInfo.withSubtyping(), graphLense, options);
+ timing.end();
+ timing.begin("IR conversion phase 1");
+ callGraph.forEachMethod((method, isProcessedConcurrently) -> {
+ processMethod(method, directFeedback, isProcessedConcurrently, callGraph,
+ outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
+ }, executorService);
+ timing.end();
+ }
// Get rid of <clinit> methods with no code.
removeEmptyClassInitializers();
@@ -313,16 +317,17 @@
if (outlineClass != null) {
// We need a new call graph to ensure deterministic order and also processing inside out
// to get maximal inlining. Use a identity lense, as the code has been rewritten.
- callGraph = CallGraph
+ CallGraph callGraph = CallGraph
.build(application, appInfo.withSubtyping(), GraphLense.getIdentityLense(), options);
Set<DexEncodedMethod> outlineMethods = outliner.getMethodsSelectedForOutlining();
- callGraph.forEachMethod(method -> {
+ callGraph.forEachMethod((method, isProcessedConcurrently) -> {
if (!outlineMethods.contains(method)) {
return;
}
// This is the second time we compile this method first mark it not processed.
assert !method.getCode().isOutlineCode();
- processMethod(method, ignoreOptimizationFeedback, outliner::applyOutliningCandidate);
+ processMethod(method, ignoreOptimizationFeedback, isProcessedConcurrently, callGraph,
+ outliner::applyOutliningCandidate);
assert method.isProcessed();
}, executorService);
builder.addSynthesizedClass(outlineClass, true);
@@ -405,7 +410,8 @@
public void optimizeSynthesizedMethod(DexEncodedMethod method) throws ApiLevelException {
// Process the generated method, but don't apply any outlining.
- processMethod(method, ignoreOptimizationFeedback, Outliner::noProcessing);
+ processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
+ Outliner::noProcessing);
}
private String logCode(InternalOptions options, DexEncodedMethod method) {
@@ -414,12 +420,14 @@
public void processMethod(DexEncodedMethod method,
OptimizationFeedback feedback,
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ CallSiteInformation callSiteInformation,
BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
throws ApiLevelException {
Code code = method.getCode();
boolean matchesMethodFilter = options.methodMatchesFilter(method);
if (code != null && matchesMethodFilter) {
- rewriteCode(method, feedback, outlineHandler);
+ rewriteCode(method, feedback, isProcessedConcurrently, callSiteInformation, outlineHandler);
} else {
// Mark abstract methods as processed as well.
method.markProcessed(Constraint.NEVER);
@@ -428,6 +436,8 @@
private void rewriteCode(DexEncodedMethod method,
OptimizationFeedback feedback,
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ CallSiteInformation callSiteInformation,
BiConsumer<IRCode, DexEncodedMethod> outlineHandler)
throws ApiLevelException {
if (options.verbose) {
@@ -473,7 +483,7 @@
if (options.inlineAccessors && inliner != null) {
// TODO(zerny): Should we support inlining in debug mode? b/62937285
assert !options.debug;
- inliner.performInlining(method, code, callGraph);
+ inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation);
}
codeRewriter.removeCastChains(code);
codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index b90d2ad..a11d924 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -23,7 +23,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.conversion.CallGraph;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.LensCodeRewriter;
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
@@ -36,6 +36,7 @@
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
public class Inliner {
@@ -136,7 +137,8 @@
.sorted(DexEncodedMethod::slowCompare)
.collect(Collectors.toList());
for (DexEncodedMethod method : methods) {
- converter.processMethod(method, feedback, Outliner::noProcessing);
+ converter.processMethod(method, feedback, x -> false, CallSiteInformation.empty(),
+ Outliner::noProcessing);
assert method.isProcessed();
}
}
@@ -222,7 +224,7 @@
this.reason = reason;
}
- boolean forceInline() {
+ boolean ignoreInstructionBudget() {
return reason != Reason.SIMPLE;
}
@@ -315,7 +317,9 @@
return true;
}
- public void performInlining(DexEncodedMethod method, IRCode code, CallGraph callGraph)
+ public void performInlining(DexEncodedMethod method, IRCode code,
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ CallSiteInformation callSiteInformation)
throws ApiLevelException {
int instruction_allowance = 1500;
instruction_allowance -= numberOfInstructions(code);
@@ -323,7 +327,8 @@
return;
}
computeReceiverMustBeNonNull(code);
- InliningOracle oracle = new InliningOracle(this, method, callGraph);
+ InliningOracle oracle = new InliningOracle(this, method, callSiteInformation,
+ isProcessedConcurrently);
List<BasicBlock> blocksToRemove = new ArrayList<>();
ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -339,15 +344,7 @@
InvokeMethod invoke = current.asInvokeMethod();
InlineAction result = invoke.computeInlining(oracle);
if (result != null) {
- DexEncodedMethod target = appInfo.lookup(invoke.getType(), invoke.getInvokedMethod());
- if (target == null) {
- // The declared target cannot be found so skip inlining.
- continue;
- }
- if (!(target.isProcessed() || result.reason == Reason.FORCE)) {
- // Do not inline code that was not processed unless we have to force inline.
- continue;
- }
+ DexEncodedMethod target = result.target;
IRCode inlinee = result
.buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
if (inlinee != null) {
@@ -355,10 +352,6 @@
if (block.hasCatchHandlers() && inlinee.getNormalExitBlock() == null) {
continue;
}
- if (callGraph.isBreaker(method, target)) {
- // Make sure we don't inline a call graph breaker.
- continue;
- }
// If this code did not go through the full pipeline, apply inlining to make sure
// that force inline targets get processed.
if (!target.isProcessed()) {
@@ -366,7 +359,8 @@
if (Log.ENABLED) {
Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
}
- performInlining(target, inlinee, callGraph);
+ performInlining(target, inlinee, isProcessedConcurrently,
+ callSiteInformation);
}
// Make sure constructor inlining is legal.
assert !target.isClassInitializer();
@@ -378,7 +372,7 @@
if (invoke.isInvokeMethodWithReceiver()) {
// If the invoke has a receiver but the declared method holder is different
// from the computed target holder, inlining requires a downcast of the receiver.
- if (result.target.method.getHolder() != target.method.getHolder()) {
+ if (target.method.getHolder() != invoke.getInvokedMethod().getHolder()) {
downcast = result.target.method.getHolder();
}
}
@@ -386,7 +380,7 @@
// Back up before the invoke instruction.
iterator.previous();
instruction_allowance -= numberOfInstructions(inlinee);
- if (instruction_allowance >= 0 || result.forceInline()) {
+ if (instruction_allowance >= 0 || result.ignoreInstructionBudget()) {
iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
}
// If we inlined the invoke from a bridge method, it is no longer a bridge method.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 91f0a61..8b7a05e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -11,10 +11,11 @@
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokePolymorphic;
import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.conversion.CallGraph;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.logging.Log;
+import java.util.function.Predicate;
/**
* The InliningOracle contains information needed for when inlining
@@ -26,16 +27,19 @@
private final Inliner inliner;
private final DexEncodedMethod method;
- private final CallGraph callGraph;
+ private final CallSiteInformation callSiteInformation;
+ private final Predicate<DexEncodedMethod> isProcessedConcurrently;
private final InliningInfo info;
InliningOracle(
Inliner inliner,
DexEncodedMethod method,
- CallGraph callGraph) {
+ CallSiteInformation callSiteInformation,
+ Predicate<DexEncodedMethod> isProcessedConcurrently) {
this.inliner = inliner;
this.method = method;
- this.callGraph = callGraph;
+ this.callSiteInformation = callSiteInformation;
+ this.isProcessedConcurrently = isProcessedConcurrently;
info = Log.ENABLED ? new InliningInfo(method) : null;
}
@@ -66,7 +70,7 @@
&& inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
return Reason.ALWAYS;
}
- if (callGraph.hasSingleCallSite(target)) {
+ if (callSiteInformation.hasSingleCallSite(target)) {
return Reason.SINGLE_CALLER;
}
if (isDoubleInliningTarget(target)) {
@@ -91,18 +95,13 @@
private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
// 10 is found from measuring.
- return callGraph.hasDoubleCallSite(candidate)
+ return callSiteInformation.hasDoubleCallSite(candidate)
&& candidate.getCode().isDexCode()
&& (candidate.getCode().asDexCode().instructions.length <= 10);
}
private boolean passesInliningConstraints(InvokeMethod invoke, DexEncodedMethod candidate,
Reason reason) {
- if (callGraph.isBreaker(method, candidate)) {
- // Cycle breaker so abort to preserve compilation order.
- return false;
- }
-
if (method == candidate) {
// Cannot handle recursive inlining at this point.
// Force inlined method should never be recursive.
@@ -113,6 +112,13 @@
return false;
}
+ if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
+ if (info != null) {
+ info.exclude(invoke, "is processed in parallel");
+ }
+ return false;
+ }
+
// Abort inlining attempt if method -> target access is not right.
if (!inliner.hasInliningAccess(method, candidate)) {
if (info != null) {
@@ -176,7 +182,7 @@
}
Reason reason = computeInliningReason(candidate);
- if (!candidate.isInliningCandidate(method, reason == Reason.FORCE, inliner.appInfo)) {
+ if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
// Abort inlining attempt if the single target is not an inlining candidate.
if (info != null) {
info.exclude(invoke, "target is not identified for inlining");
@@ -202,7 +208,7 @@
Reason reason = computeInliningReason(candidate);
// Determine if this should be inlined no matter how big it is.
- if (!candidate.isInliningCandidate(method, reason == Reason.FORCE, inliner.appInfo)) {
+ if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
// Abort inlining attempt if the single target is not an inlining candidate.
if (info != null) {
info.exclude(invoke, "target is not identified for inlining");
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 9f3488c..9a87320 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -14,6 +14,7 @@
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.List;
+import java.util.Set;
import java.util.function.Function;
public class InternalOptions {
@@ -197,7 +198,7 @@
public static class TestingOptions {
- public Function<List<DexEncodedMethod>, List<DexEncodedMethod>> irOrdering =
+ public Function<Set<DexEncodedMethod>, Set<DexEncodedMethod>> irOrdering =
Function.identity();
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index bc3d758..75a0275 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -11,19 +11,23 @@
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.OutputMode;
+import com.beust.jcommander.internal.Lists;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase {
- public List<DexEncodedMethod> shuffle(List<DexEncodedMethod> methods) {
- Collections.shuffle(methods);
- return methods;
+ public Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
+ List<DexEncodedMethod> toShuffle = Lists.newArrayList(methods);
+ Collections.shuffle(toShuffle);
+ return new LinkedHashSet<>(toShuffle);
}
private AndroidApp doRun()