Merge "Publicize private constructors."
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 805bdf7..fd547cf 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -235,7 +235,7 @@
System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
}
// TODO(b/65390962): Remove this warning once the CF backend is complete.
- if (options.isGeneratingClassFiles()) {
+ if (options.isGeneratingClassFiles() && !options.testing.suppressExperimentalCfBackendWarning) {
options.reporter.warning(new StringDiagnostic(
"R8 support for generating Java classfiles is incomplete and experimental. "
+ "Even if R8 appears to succeed, the generated output is likely incorrect."));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 584b06d..f0ae9e0 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -618,6 +618,13 @@
internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
internal.dataResourceConsumer = internal.programConsumer.getDataResourceConsumer();
+ // Default is to remove Java assertion code as Dalvik and Art does not reliable support
+ // Java assertions. When generation class file output always keep the Java assertions code.
+ assert internal.disableAssertions;
+ if (internal.isGeneratingClassFiles()) {
+ internal.disableAssertions = false;
+ }
+
// EXPERIMENTAL flags.
assert !internal.forceProguardCompatibility;
internal.forceProguardCompatibility = forceProguardCompatibility;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 55c4134..0ef8a34 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -489,17 +489,22 @@
}
public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
+ DexClass clazz = definitionFor(type);
+ assert clazz != null;
+ return canTriggerStaticInitializer(clazz, ignoreTypeItself);
+ }
+
+ public boolean canTriggerStaticInitializer(DexClass clazz, boolean ignoreTypeItself) {
Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
// Process superclass chain.
- DexType clazz = type;
- while (clazz != null && clazz != dexItemFactory.objectType) {
- DexClass definition = definitionFor(clazz);
- if (canTriggerStaticInitializer(definition) && (!ignoreTypeItself || clazz != type)) {
+ DexClass current = clazz;
+ while (current != null && current.type != dexItemFactory.objectType) {
+ if (canTriggerStaticInitializer(current) && (!ignoreTypeItself || current != clazz)) {
return true;
}
- knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
- clazz = definition.superType;
+ knownInterfaces.addAll(Arrays.asList(current.interfaces.values));
+ current = current.superType != null ? definitionFor(current.superType) : null;
}
// Process interfaces.
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index b8e0bd8..776cc42 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import java.util.Collections;
import java.util.List;
import org.objectweb.asm.Label;
@@ -230,7 +231,13 @@
throw new Unimplemented(
"Converting CfCode to IR not supported for DEX output of synchronized methods.");
}
- CfSourceCode source = new CfSourceCode(this, encodedMethod, callerPosition, origin);
+ CfSourceCode source =
+ new CfSourceCode(
+ this,
+ encodedMethod,
+ callerPosition,
+ origin,
+ options.lineNumberOptimization == LineNumberOptimization.ON);
IRBuilder builder =
(generator == null)
? new IRBuilder(encodedMethod, appInfo, source, options)
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 64c85f3..fc1ef7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.ThrowingConsumer;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Iterators;
import java.util.Arrays;
@@ -138,16 +137,6 @@
}
}
- public <E extends Throwable> void forEachMethodThrowing(
- ThrowingConsumer<DexEncodedMethod, E> consumer) throws E {
- for (DexEncodedMethod method : directMethods()) {
- consumer.accept(method);
- }
- for (DexEncodedMethod method : virtualMethods()) {
- consumer.accept(method);
- }
- }
-
public DexEncodedMethod[] allMethodsSorted() {
int vLen = virtualMethods.length;
int dLen = directMethods.length;
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 3586d6d..9a44dde 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -634,6 +634,7 @@
// class inliner, null value indicates that the method is not eligible.
private ClassInlinerEligibility classInlinerEligibility = null;
private TrivialInitializer trivialInitializerInfo = null;
+ private boolean initializerEnablingJavaAssertions = false;
private ParameterUsagesInfo parametersUsages = null;
private BitSet kotlinNotNullParamHints = null;
@@ -705,6 +706,14 @@
return this.trivialInitializerInfo;
}
+ private void setInitializerEnablingJavaAssertions() {
+ this.initializerEnablingJavaAssertions = true;
+ }
+
+ public boolean isInitializerEnablingJavaAssertions() {
+ return initializerEnablingJavaAssertions;
+ }
+
public long getReturnedConstant() {
assert returnsConstant();
return returnedConstant;
@@ -754,6 +763,10 @@
forceInline = true;
}
+ private void unsetForceInline() {
+ forceInline = false;
+ }
+
private void markPublicized() {
publicized = true;
}
@@ -831,10 +844,18 @@
ensureMutableOI().setTrivialInitializer(info);
}
+ synchronized public void setInitializerEnablingJavaAssertions() {
+ ensureMutableOI().setInitializerEnablingJavaAssertions();
+ }
+
synchronized public void markForceInline() {
ensureMutableOI().markForceInline();
}
+ public synchronized void unsetForceInline() {
+ ensureMutableOI().unsetForceInline();
+ }
+
synchronized public void markPublicized() {
ensureMutableOI().markPublicized();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 8ab2c1a..5bed061 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
import com.android.tools.r8.graph.DexItemFactory;
@@ -468,7 +467,7 @@
return instruction;
}
}
- throw new Unreachable();
+ return null;
}
public void clearUserInfo() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
new file mode 100644
index 0000000..66456cd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2018, 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.code;
+
+import com.android.tools.r8.graph.DexMethod;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Maintains a set of canonical positions. Also supports appending a new caller at the end of the
+ * caller chain of a Position.
+ */
+public class CanonicalPositions {
+ private final Position callerPosition;
+ private final boolean preserveCaller;
+ private final Map<Position, Position> canonicalPositions;
+ private final Position preamblePosition;
+
+ /** For callerPosition and preserveCaller see canonicalizeCallerPosition. */
+ public CanonicalPositions(
+ Position callerPosition,
+ boolean preserveCaller,
+ int expectedPositionsCount,
+ DexMethod method) {
+ canonicalPositions =
+ new HashMap<>(1 + (callerPosition == null ? 0 : 1) + expectedPositionsCount);
+ this.preserveCaller = preserveCaller;
+ this.callerPosition = callerPosition;
+ if (callerPosition != null) {
+ canonicalPositions.put(callerPosition, callerPosition);
+ }
+ preamblePosition =
+ callerPosition == null
+ ? Position.synthetic(0, method, null)
+ : new Position(0, null, method, callerPosition);
+ canonicalPositions.put(preamblePosition, preamblePosition);
+ }
+
+ public Position getPreamblePosition() {
+ return preamblePosition;
+ }
+
+ /**
+ * Update the internal set if this is the first occurence of the position's value and return
+ * canonical instance of position.
+ */
+ public Position getCanonical(Position position) {
+ Position canonical = canonicalPositions.putIfAbsent(position, position);
+ return canonical != null ? canonical : position;
+ }
+
+ /**
+ * Append callerPosition (supplied in constructor) to the end of caller's caller chain and return
+ * the canonical instance. Always returns null if preserveCaller (also supplied in constructor) is
+ * false.
+ */
+ public Position canonicalizeCallerPosition(Position caller) {
+ if (!preserveCaller) {
+ return null;
+ }
+
+ if (caller == null) {
+ return callerPosition;
+ }
+ if (caller.callerPosition == null && callerPosition == null) {
+ return getCanonical(caller);
+ }
+ Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
+ return getCanonical(
+ caller.isNone()
+ ? Position.noneWithMethod(caller.method, callerOfCaller)
+ : new Position(caller.line, caller.file, caller.method, callerOfCaller));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 6b3c89c..3b1b2d1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -34,6 +34,14 @@
return inValues.get(0);
}
+ public boolean isEnter() {
+ return type == Type.ENTER;
+ }
+
+ public boolean isExit() {
+ return type == Type.EXIT;
+ }
+
@Override
public void buildDex(DexBuilder builder) {
// If the monitor object is an argument, we use the argument register for all the monitor
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
index c414342..69618b3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -48,6 +48,10 @@
if (other == INT_OR_FLOAT_OR_NULL) {
return other.meet(this);
}
+ if (isPreciseType() && other.isPreciseType()) {
+ // Precise types must be identical, hitting the first check above.
+ throw new CompilationError("Cannot compute meet of types: " + this + " and " + other);
+ }
switch (this) {
case OBJECT:
{
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 650757e..382dd64 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
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.conversion;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,16 +16,23 @@
import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.logging.Log;
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.ThrowingBiConsumer;
+import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -56,7 +64,7 @@
this.shuffle = options.testing.irOrdering;
}
- private static class Node {
+ public static class Node {
public final DexEncodedMethod method;
private int invokeCount = 0;
@@ -68,7 +76,7 @@
// Incoming calls to this method.
private final Set<Node> callers = new LinkedHashSet<>();
- private Node(DexEncodedMethod method) {
+ public Node(DexEncodedMethod method) {
this.method = method;
}
@@ -76,19 +84,20 @@
return method.accessFlags.isBridge();
}
- private void addCallee(Node method) {
+ public void addCallee(Node method) {
callees.add(method);
+ method.callers.add(this);
}
- private void addCaller(Node method) {
- callers.add(method);
+ public boolean hasCallee(Node method) {
+ return callees.contains(method);
}
boolean isSelfRecursive() {
return isSelfRecursive;
}
- boolean isLeaf() {
+ public boolean isLeaf() {
return callees.isEmpty();
}
@@ -96,7 +105,7 @@
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("MethodNode for: ");
- builder.append(method.qualifiedName());
+ builder.append(method.toSourceString());
builder.append(" (");
builder.append(callees.size());
builder.append(" callees, ");
@@ -114,7 +123,7 @@
builder.append("Callees:\n");
for (Node call : callees) {
builder.append(" ");
- builder.append(call.method.qualifiedName());
+ builder.append(call.method.toSourceString());
builder.append("\n");
}
}
@@ -122,7 +131,7 @@
builder.append("Callers:\n");
for (Node caller : callers) {
builder.append(" ");
- builder.append(caller.method.qualifiedName());
+ builder.append(caller.method.toSourceString());
builder.append("\n");
}
}
@@ -136,8 +145,12 @@
private final Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet();
private final Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
- public static CallGraph build(DexApplication application, AppInfoWithLiveness appInfo,
- GraphLense graphLense, InternalOptions options) {
+ public static CallGraph build(
+ DexApplication application,
+ AppInfoWithLiveness appInfo,
+ GraphLense graphLense,
+ InternalOptions options,
+ Timing timing) {
CallGraph graph = new CallGraph(options);
DexClass[] classes = application.classes().toArray(new DexClass[application.classes().size()]);
Arrays.sort(classes, (DexClass a, DexClass b) -> a.type.slowCompareTo(b.type));
@@ -149,8 +162,13 @@
}
}
assert allMethodsExists(application, graph);
- graph.breakCycles();
- assert graph.breakCycles() == 0; // This time the cycles should be gone.
+
+ timing.begin("Cycle elimination");
+ CycleEliminator cycleEliminator = new CycleEliminator(graph.nodes.values(), options);
+ cycleEliminator.breakCycles();
+ timing.end();
+ assert cycleEliminator.breakCycles() == 0; // This time the cycles should be gone.
+
graph.fillCallSiteSets(appInfo);
return graph;
}
@@ -196,9 +214,10 @@
/**
* Extract the next set of leaves (nodes with an call (outgoing) degree of 0) if any.
- * <p>
- * 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>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 CycleEliminator#breakCycles}).
+ *
* <p>
*/
private Set<DexEncodedMethod> extractLeaves() {
@@ -215,48 +234,197 @@
.collect(Collectors.toCollection(LinkedHashSet::new)));
}
- private int traverse(Node node, Set<Node> stack, Set<Node> marked) {
- int numberOfCycles = 0;
- if (!marked.contains(node)) {
- assert !stack.contains(node);
- stack.add(node);
- ArrayList<Node> toBeRemoved = null;
- // Sort the callees before calling traverse recursively.
- // This will ensure cycles are broken the same way across
- // multiple invocations of the R8 compiler.
+ public static class CycleEliminator {
+
+ public static final String CYCLIC_FORCE_INLINING_MESSAGE =
+ "Unable to satisfy force inlining constraints due to cyclic force inlining";
+
+ private static class CallEdge {
+
+ private final Node caller;
+ private final Node callee;
+
+ public CallEdge(Node caller, Node callee) {
+ this.caller = caller;
+ this.callee = callee;
+ }
+ }
+
+ private final Collection<Node> nodes;
+ private final InternalOptions options;
+
+ // DFS stack.
+ private Deque<Node> stack = new ArrayDeque<>();
+
+ // Set of nodes on the DFS stack.
+ private Set<Node> stackSet = Sets.newIdentityHashSet();
+
+ // Set of nodes that have been visited entirely.
+ private Set<Node> marked = Sets.newIdentityHashSet();
+
+ private int numberOfCycles = 0;
+
+ public CycleEliminator(Collection<Node> nodes, InternalOptions options) {
+ this.options = options;
+
+ // Call to reorderNodes must happen after assigning options.
+ this.nodes =
+ options.testing.nondeterministicCycleElimination
+ ? reorderNodes(new ArrayList<>(nodes))
+ : nodes;
+ }
+
+ public int breakCycles() {
+ // Break cycles in this call graph by removing edges causing cycles.
+ for (Node node : nodes) {
+ traverse(node);
+ }
+ int result = numberOfCycles;
+ reset();
+ return result;
+ }
+
+ private void reset() {
+ assert stack.isEmpty();
+ assert stackSet.isEmpty();
+ marked.clear();
+ numberOfCycles = 0;
+ }
+
+ private void traverse(Node node) {
+ if (marked.contains(node)) {
+ // Already visited all nodes that can be reached from this node.
+ return;
+ }
+
+ push(node);
+
+ // Sort the callees before calling traverse recursively. This will ensure cycles are broken
+ // the same way across multiple invocations of the R8 compiler.
Node[] callees = node.callees.toArray(new Node[node.callees.size()]);
Arrays.sort(callees, (Node a, Node b) -> a.method.method.slowCompareTo(b.method.method));
+ if (options.testing.nondeterministicCycleElimination) {
+ reorderNodes(Arrays.asList(callees));
+ }
+
for (Node callee : callees) {
- if (stack.contains(callee)) {
- if (toBeRemoved == null) {
- toBeRemoved = new ArrayList<>();
+ if (stackSet.contains(callee)) {
+ // Found a cycle that needs to be eliminated.
+ numberOfCycles++;
+
+ if (edgeRemovalIsSafe(node, callee)) {
+ // Break the cycle by removing the edge node->callee.
+ callee.callers.remove(node);
+ node.callees.remove(callee);
+
+ if (Log.ENABLED) {
+ Log.info(
+ CallGraph.class,
+ "Removed call edge from method '%s' to '%s'",
+ node.method.toSourceString(),
+ callee.method.toSourceString());
+ }
+ } else {
+ // The cycle has a method that is marked as force inline.
+ LinkedList<Node> cycle = extractCycle(callee);
+
+ if (Log.ENABLED) {
+ Log.info(
+ CallGraph.class, "Extracted cycle to find an edge that can safely be removed");
+ }
+
+ // Break the cycle by finding an edge that can be removed without breaking force
+ // inlining. If that is not possible, this call fails with a compilation error.
+ CallEdge edge = findCallEdgeForRemoval(cycle);
+
+ // The edge will be null if this cycle has already been eliminated as a result of
+ // another cycle elimination.
+ if (edge != null) {
+ assert edgeRemovalIsSafe(edge.caller, edge.callee);
+
+ // Break the cycle by removing the edge caller->callee.
+ edge.caller.callees.remove(edge.callee);
+ edge.callee.callers.remove(edge.caller);
+
+ if (Log.ENABLED) {
+ Log.info(
+ CallGraph.class,
+ "Removed call edge from force inlined method '%s' to '%s' to ensure that "
+ + "force inlining will succeed",
+ node.method.toSourceString(),
+ callee.method.toSourceString());
+ }
+ }
+
+ // Recover the stack.
+ recoverStack(cycle);
}
- // We have a cycle; break it by removing node->callee.
- toBeRemoved.add(callee);
- callee.callers.remove(node);
} else {
- numberOfCycles += traverse(callee, stack, marked);
+ traverse(callee);
}
}
- if (toBeRemoved != null) {
- numberOfCycles += toBeRemoved.size();
- node.callees.removeAll(toBeRemoved);
- }
- stack.remove(node);
+ pop(node);
marked.add(node);
}
- return numberOfCycles;
- }
- private int breakCycles() {
- // Break cycles in this call graph by removing edges causing cycles.
- int numberOfCycles = 0;
- Set<Node> stack = Sets.newIdentityHashSet();
- Set<Node> marked = Sets.newIdentityHashSet();
- for (Node node : nodes.values()) {
- numberOfCycles += traverse(node, stack, marked);
+ private void push(Node node) {
+ stack.push(node);
+ boolean changed = stackSet.add(node);
+ assert changed;
}
- return numberOfCycles;
+
+ private void pop(Node node) {
+ Node popped = stack.pop();
+ assert popped == node;
+ boolean changed = stackSet.remove(node);
+ assert changed;
+ }
+
+ private LinkedList<Node> extractCycle(Node entry) {
+ LinkedList<Node> cycle = new LinkedList<>();
+ do {
+ assert !stack.isEmpty();
+ cycle.add(stack.pop());
+ } while (cycle.getLast() != entry);
+ return cycle;
+ }
+
+ private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) {
+ Node callee = extractedCycle.getLast();
+ for (Node caller : extractedCycle) {
+ if (!caller.callees.contains(callee)) {
+ // No need to break any edges since this cycle has already been broken previously.
+ assert !callee.callers.contains(caller);
+ return null;
+ }
+ if (edgeRemovalIsSafe(caller, callee)) {
+ return new CallEdge(caller, callee);
+ }
+ callee = caller;
+ }
+ throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE);
+ }
+
+ private static boolean edgeRemovalIsSafe(Node caller, Node callee) {
+ // All call edges where the callee is a method that should be force inlined must be kept,
+ // to guarantee that the IR converter will process the callee before the caller.
+ return !callee.method.getOptimizationInfo().forceInline();
+ }
+
+ private void recoverStack(LinkedList<Node> extractedCycle) {
+ Iterator<Node> descendingIt = extractedCycle.descendingIterator();
+ while (descendingIt.hasNext()) {
+ stack.push(descendingIt.next());
+ }
+ }
+
+ private Collection<Node> reorderNodes(List<Node> nodes) {
+ assert options.testing.nondeterministicCycleElimination;
+ if (!InternalOptions.DETERMINISTIC_DEBUGGING) {
+ Collections.shuffle(nodes);
+ }
+ return nodes;
+ }
}
synchronized private Node ensureMethodNode(DexEncodedMethod method) {
@@ -268,7 +436,6 @@
assert callee != null;
if (caller != callee) {
caller.addCallee(callee);
- callee.addCaller(caller);
} else {
caller.isSelfRecursive = true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 4cbebcf..92645c4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.CanonicalPositions;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
@@ -184,8 +185,6 @@
private final Position callerPosition;
private final Origin origin;
- // Synthetic position with line = 0.
- private final Position preamblePosition;
private final Reference2IntMap<CfLabel> labelOffsets = new Reference2IntOpenHashMap<>();
private TryHandlerList cachedTryHandlerList;
private LocalVariableList cachedLocalVariableList;
@@ -196,21 +195,31 @@
private Int2ReferenceMap<Int2ObjectMap<DebugLocalInfo>> definitelyLiveIncomingLocals =
new Int2ReferenceOpenHashMap<>();
private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
+ private final CanonicalPositions canonicalPositions;
public CfSourceCode(
- CfCode code, DexEncodedMethod method, Position callerPosition, Origin origin) {
+ CfCode code,
+ DexEncodedMethod method,
+ Position callerPosition,
+ Origin origin,
+ boolean preserveCaller) {
this.code = code;
this.method = method;
this.callerPosition = callerPosition;
this.origin = origin;
- preamblePosition = Position.synthetic(0, method.method, null);
+ int cfPositionCount = 0;
for (int i = 0; i < code.getInstructions().size(); i++) {
CfInstruction instruction = code.getInstructions().get(i);
if (instruction instanceof CfLabel) {
labelOffsets.put((CfLabel) instruction, instructionOffset(i));
}
+ if (instruction instanceof CfPosition) {
+ ++cfPositionCount;
+ }
}
this.state = new CfState(origin);
+ canonicalPositions =
+ new CanonicalPositions(callerPosition, preserveCaller, cfPositionCount, this.method.method);
}
@Override
@@ -383,7 +392,21 @@
}
state.clear();
} else {
- instruction.buildIR(builder, state, this);
+ if (instruction instanceof CfPosition) {
+ CfPosition cfPosition = (CfPosition) instruction;
+ Position position = cfPosition.getPosition();
+ Position newPosition =
+ canonicalPositions.getCanonical(
+ new Position(
+ position.line,
+ position.file,
+ position.method,
+ canonicalPositions.canonicalizeCallerPosition(position.callerPosition)));
+ CfPosition newCfPosition = new CfPosition(cfPosition.getLabel(), newPosition);
+ newCfPosition.buildIR(builder, state, this);
+ } else {
+ instruction.buildIR(builder, state, this);
+ }
ensureDebugValueLiveness(builder);
if (builder.getCFG().containsKey(currentInstructionIndex + 1)) {
recordStateForTarget(currentInstructionIndex + 1, state.getSnapshot());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index b59794b..9a9b836 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.CanonicalPositions;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
@@ -68,25 +69,20 @@
private Instruction currentDexInstruction = null;
private Position currentPosition = null;
- private Map<Position, Position> canonicalPositions = null;
+ private final CanonicalPositions canonicalPositions;
private final List<ValueType> argumentTypes;
private List<DexDebugEntry> debugEntries = null;
// In case of inlining the position of the invoke in the caller.
- private final Position callerPosition;
private final DexMethod method;
- private final boolean preserveCaller;
- private final Position preamblePosition;
public DexSourceCode(
DexCode code, DexEncodedMethod method, Position callerPosition, boolean preserveCaller) {
this.code = code;
this.proto = method.method.proto;
this.accessFlags = method.accessFlags;
- this.callerPosition = callerPosition;
this.method = method.method;
- this.preserveCaller = preserveCaller;
argumentTypes = computeArgumentTypes();
DexDebugInfo info = code.getDebugInfo();
@@ -94,18 +90,11 @@
debugEntries = info.computeEntries(method.method);
}
canonicalPositions =
- new HashMap<>(
- 1
- + (callerPosition == null ? 0 : 1)
- + (debugEntries == null ? 0 : debugEntries.size()));
- if (callerPosition != null) {
- canonicalPositions.put(callerPosition, callerPosition);
- }
- preamblePosition =
- callerPosition == null
- ? Position.synthetic(0, this.method, null)
- : new Position(0, null, this.method, callerPosition);
- canonicalPositions.put(preamblePosition, preamblePosition);
+ new CanonicalPositions(
+ callerPosition,
+ preserveCaller,
+ debugEntries == null ? 0 : debugEntries.size(),
+ this.method);
}
@Override
@@ -148,7 +137,7 @@
@Override
public void buildPrelude(IRBuilder builder) {
- currentPosition = preamblePosition;
+ currentPosition = canonicalPositions.getPreamblePosition();
if (code.incomingRegisterSize == 0) {
return;
}
@@ -197,7 +186,9 @@
@Override
public Position getDebugPositionAtOffset(int offset) {
DexDebugEntry entry = getDebugEntryAtOffset(offset);
- return entry == null ? preamblePosition : getCanonicalPositionAppendCaller(entry);
+ return entry == null
+ ? canonicalPositions.getPreamblePosition()
+ : getCanonicalPositionAppendCaller(entry);
}
@Override
@@ -250,7 +241,7 @@
int offset = instructionOffset(instructionIndex);
DexDebugEntry entry = getDebugEntryAtOffset(offset);
if (entry == null) {
- currentPosition = preamblePosition;
+ currentPosition = canonicalPositions.getPreamblePosition();
} else {
currentPosition = getCanonicalPositionAppendCaller(entry);
if (entry.lineEntry && entry.address == offset) {
@@ -259,39 +250,17 @@
}
}
- private Position getCanonicalPosition(Position position) {
- Position canonical = canonicalPositions.putIfAbsent(position, position);
- return canonical != null ? canonical : position;
- }
-
- private Position canonicalizeCallerPosition(Position caller) {
- // We are not supposed to get here from getCanonicalPositionAppendCaller if !preserveCaller.
- assert preserveCaller;
-
- if (caller == null) {
- return callerPosition;
- }
- if (caller.callerPosition == null && callerPosition == null) {
- return getCanonicalPosition(caller);
- }
- Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
- return getCanonicalPosition(
- caller.isNone()
- ? Position.noneWithMethod(caller.method, callerOfCaller)
- : new Position(caller.line, caller.file, caller.method, callerOfCaller));
- }
-
private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
// If this instruction has already been inlined then this.method must be the outermost caller.
assert entry.callerPosition == null
|| entry.callerPosition.getOutermostCaller().method == method;
- return getCanonicalPosition(
+ return canonicalPositions.getCanonical(
new Position(
entry.line,
entry.sourceFile,
entry.method,
- preserveCaller ? canonicalizeCallerPosition(entry.callerPosition) : null));
+ canonicalPositions.canonicalizeCallerPosition(entry.callerPosition)));
}
@Override
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 63843ee..33531da 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
@@ -46,6 +46,7 @@
import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
+import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -104,7 +105,10 @@
private final Devirtualizer devirtualizer;
private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
+ private final boolean enableWholeProgramOptimizations;
+
private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
+ private final OptimizationFeedback simpleOptimizationFeedback = new OptimizationFeedbackSimple();
private DexString highestSortingString;
private IRConverter(
@@ -134,6 +138,7 @@
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
: null;
+ this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
if (enableWholeProgramOptimizations) {
assert appInfo.hasLiveness();
this.nonNullTracker = new NonNullTracker();
@@ -172,7 +177,8 @@
}
this.classInliner =
(options.enableClassInlining && options.enableInlining && inliner != null)
- ? new ClassInliner(appInfo.dexItemFactory, options.classInliningInstructionLimit)
+ ? new ClassInliner(
+ appInfo.dexItemFactory, lambdaRewriter, options.classInliningInstructionLimit)
: null;
}
@@ -352,24 +358,34 @@
ExecutorService executor) throws ExecutionException {
List<Future<?>> futures = new ArrayList<>();
for (DexProgramClass clazz : classes) {
- futures.add(
- executor.submit(
- () -> {
- clazz.forEachMethodThrowing(this::convertMethodToDex);
- return null; // we want a Callable not a Runnable to be able to throw
- }));
+ futures.add(executor.submit(() -> convertMethodsToDex(clazz)));
}
ThreadUtils.awaitFutures(futures);
}
- void convertMethodToDex(DexEncodedMethod method) {
+ private void convertMethodsToDex(DexProgramClass clazz) {
+ // When converting all methods on a class always convert <clinit> first.
+ for (DexEncodedMethod method : clazz.directMethods()) {
+ if (method.isClassInitializer()) {
+ convertMethodToDex(method);
+ break;
+ }
+ }
+ clazz.forEachMethod(method -> {
+ if (!method.isClassInitializer()) {
+ convertMethodToDex(method);
+ }
+ });
+ }
+
+ private void convertMethodToDex(DexEncodedMethod method) {
assert options.isGeneratingDex();
if (method.getCode() != null) {
boolean matchesMethodFilter = options.methodMatchesFilter(method);
if (matchesMethodFilter) {
if (!(options.passthroughDexCode && method.getCode().isDexCode())) {
// We do not process in call graph order, so anything could be a leaf.
- rewriteCode(method, ignoreOptimizationFeedback, x -> true, CallSiteInformation.empty(),
+ rewriteCode(method, simpleOptimizationFeedback, x -> true, CallSiteInformation.empty(),
Outliner::noProcessing);
}
updateHighestSortingStrings(method);
@@ -401,8 +417,8 @@
OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
{
timing.begin("Build call graph");
- CallGraph callGraph = CallGraph
- .build(application, appInfo.withLiveness(), graphLense, options);
+ CallGraph callGraph =
+ CallGraph.build(application, appInfo.withLiveness(), graphLense, options, timing);
timing.end();
timing.begin("IR conversion phase 1");
BiConsumer<IRCode, DexEncodedMethod> outlineHandler =
@@ -561,7 +577,7 @@
try {
codeRewriter.enterCachedClass(clazz);
// Process the generated class, but don't apply any outlining.
- clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
+ clazz.forEachMethod(this::optimizeSynthesizedMethod);
} finally {
codeRewriter.leaveCachedClass(clazz);
}
@@ -668,7 +684,7 @@
codeRewriter.removeSwitchMaps(code);
}
if (options.disableAssertions) {
- codeRewriter.disableAssertions(code);
+ codeRewriter.disableAssertions(appInfo, method, code, feedback);
}
if (options.enableNonNullTracking && nonNullTracker != null) {
nonNullTracker.addNonNull(code);
@@ -695,6 +711,7 @@
codeRewriter.rewriteSwitch(code);
codeRewriter.processMethodsNeverReturningNormally(code);
codeRewriter.simplifyIf(code, typeEnvironment);
+ new RedundantFieldLoadElimination(appInfo, code, enableWholeProgramOptimizations).run();
if (options.testing.invertConditionals) {
invertConditionalsForTesting(code);
@@ -728,11 +745,6 @@
assert code.isConsistentSSA();
}
- if (interfaceMethodRewriter != null) {
- interfaceMethodRewriter.rewriteMethodReferences(method, code);
- assert code.isConsistentSSA();
- }
-
if (classInliner != null) {
// Class inliner should work before lambda merger, so if it inlines the
// lambda, it is not get collected by merger.
@@ -750,6 +762,11 @@
assert code.isConsistentSSA();
}
+ if (interfaceMethodRewriter != null) {
+ interfaceMethodRewriter.rewriteMethodReferences(method, code);
+ assert code.isConsistentSSA();
+ }
+
if (lambdaMerger != null) {
lambdaMerger.processMethodCode(method, code);
assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 9a88674..1877053 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -15,6 +15,8 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
+import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -36,11 +38,11 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
-import java.util.stream.Collectors;
public class LensCodeRewriter {
@@ -80,16 +82,7 @@
callSite.methodProto, graphLense::lookupType, protoFixupCache);
DexMethodHandle newBootstrapMethod =
rewriteDexMethodHandle(callSite.bootstrapMethod, method);
- List<DexValue> newArgs = callSite.bootstrapArgs.stream().map(
- (arg) -> {
- if (arg instanceof DexValueMethodHandle) {
- return new DexValueMethodHandle(
- rewriteDexMethodHandle(((DexValueMethodHandle) arg).value, method));
- }
- return arg;
- })
- .collect(Collectors.toList());
-
+ List<DexValue> newArgs = rewriteBootstrapArgs(callSite.bootstrapArgs, method);
if (!newMethodProto.equals(callSite.methodProto)
|| newBootstrapMethod != callSite.bootstrapMethod
|| !newArgs.equals(callSite.bootstrapArgs)) {
@@ -225,6 +218,47 @@
assert code.isConsistentSSA();
}
+ private List<DexValue> rewriteBootstrapArgs(
+ List<DexValue> bootstrapArgs, DexEncodedMethod method) {
+ List<DexValue> newBoostrapArgs = null;
+ boolean changed = false;
+ for (int i = 0; i < bootstrapArgs.size(); i++) {
+ DexValue argument = bootstrapArgs.get(i);
+ DexValue newArgument = null;
+ if (argument instanceof DexValueMethodHandle) {
+ DexMethodHandle oldHandle = ((DexValueMethodHandle) argument).value;
+ DexMethodHandle newHandle = rewriteDexMethodHandle(oldHandle, method);
+ if (newHandle != oldHandle) {
+ newArgument = new DexValueMethodHandle(newHandle);
+ }
+ } else if (argument instanceof DexValueMethodType) {
+ DexProto oldProto = ((DexValueMethodType) argument).value;
+ DexProto newProto =
+ appInfo.dexItemFactory.applyClassMappingToProto(
+ oldProto, graphLense::lookupType, protoFixupCache);
+ if (newProto != oldProto) {
+ newArgument = new DexValueMethodType(newProto);
+ }
+ } else if (argument instanceof DexValueType) {
+ DexType oldType = ((DexValueType) argument).value;
+ DexType newType = graphLense.lookupType(oldType);
+ if (newType != oldType) {
+ newArgument = new DexValueType(newType);
+ }
+ }
+ if (newArgument != null) {
+ if (newBoostrapArgs == null) {
+ newBoostrapArgs = new ArrayList<>(bootstrapArgs.subList(0, i));
+ }
+ newBoostrapArgs.add(newArgument);
+ changed = true;
+ } else if (newBoostrapArgs != null) {
+ newBoostrapArgs.add(argument);
+ }
+ }
+ return changed ? newBoostrapArgs : bootstrapArgs;
+ }
+
private DexMethodHandle rewriteDexMethodHandle(
DexMethodHandle methodHandle, DexEncodedMethod context) {
if (methodHandle.isMethodHandle()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index c26aecf..49bfac8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -21,6 +21,7 @@
void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info);
+ void setInitializerEnablingJavaAssertions(DexEncodedMethod method);
void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo);
void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index 2b1a6db..1d7a2d6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -60,6 +60,11 @@
}
@Override
+ public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+ method.setInitializerEnablingJavaAssertions();
+ }
+
+ @Override
public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
method.setParameterUsages(parameterUsagesInfo);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index a547b36..2fb2969 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -44,6 +44,10 @@
}
@Override
+ public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+ }
+
+ @Override
public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
new file mode 100644
index 0000000..b31f28a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
@@ -0,0 +1,77 @@
+// 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;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import java.util.BitSet;
+
+public class OptimizationFeedbackSimple implements OptimizationFeedback {
+
+ @Override
+ public void methodReturnsArgument(DexEncodedMethod method, int argument) {
+ // Ignored.
+ }
+
+ @Override
+ public void methodReturnsConstant(DexEncodedMethod method, long value) {
+ // Ignored.
+ }
+
+ @Override
+ public void methodNeverReturnsNull(DexEncodedMethod method) {
+ // Ignored.
+ }
+
+ @Override
+ public void methodNeverReturnsNormally(DexEncodedMethod method) {
+ // Ignored.
+ }
+
+ @Override
+ public void markProcessed(DexEncodedMethod method, Constraint state) {
+ // Just as processed, don't provide any inlining constraints.
+ method.markProcessed(Constraint.NEVER);
+ }
+
+ @Override
+ public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+ // Ignored.
+ }
+
+ @Override
+ public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+ // Ignored.
+ }
+
+ @Override
+ public void setClassInlinerEligibility(
+ DexEncodedMethod method, ClassInlinerEligibility eligibility) {
+ // Ignored.
+ }
+
+ @Override
+ public void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info) {
+ // Ignored.
+ }
+
+ @Override
+ public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+ method.setInitializerEnablingJavaAssertions();
+ }
+
+ @Override
+ public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
+ // Ignored.
+ }
+
+ @Override
+ public void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints) {
+ // Ignored.
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 81407bd..86f3f17 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,11 +29,13 @@
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.google.common.base.Suppliers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
/**
* Represents lambda class generated for a lambda descriptor in context
@@ -64,6 +66,8 @@
final Target target;
final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<DexProgramClass>(1);
+ private final Supplier<DexProgramClass> lazyDexClass =
+ Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
LambdaClass(LambdaRewriter rewriter, DexType accessedFrom,
DexType lambdaClassType, LambdaDescriptor descriptor) {
@@ -119,7 +123,11 @@
return rewriter.factory.createType(lambdaClassDescriptor.toString());
}
- final DexProgramClass synthesizeLambdaClass() {
+ final DexProgramClass getLambdaClass() {
+ return lazyDexClass.get();
+ }
+
+ private DexProgramClass synthesizeLambdaClass() {
return new DexProgramClass(
type,
null,
@@ -171,7 +179,7 @@
Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
+ new SynthesizedCode(() -> new LambdaMainMethodSourceCode(this, mainMethod)));
// Synthesize bridge methods.
for (DexProto bridgeProto : descriptor.bridges) {
@@ -188,7 +196,7 @@
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
new SynthesizedCode(
- new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
+ () -> new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
}
return methods;
}
@@ -208,7 +216,7 @@
true),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(new LambdaConstructorSourceCode(this)));
+ new SynthesizedCode(() -> new LambdaConstructorSourceCode(this)));
// Class constructor for stateless lambda classes.
if (stateless) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index e7686db..51b8b8a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -141,7 +141,6 @@
if (method.name == deserializeLambdaMethodName &&
method.proto == deserializeLambdaMethodProto) {
assert encoded.accessFlags.isStatic();
- assert encoded.accessFlags.isPrivate();
assert encoded.accessFlags.isSynthetic();
DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
@@ -170,10 +169,19 @@
}
}
+ /**
+ * Returns a synthetic class for desugared lambda or `null` if the `type`
+ * does not represent one. Method can be called concurrently.
+ */
+ public DexProgramClass getLambdaClass(DexType type) {
+ LambdaClass lambdaClass = getKnown(knownLambdaClasses, type);
+ return lambdaClass == null ? null : lambdaClass.getLambdaClass();
+ }
+
/** Generates lambda classes and adds them to the builder. */
public void synthesizeLambdaClasses(Builder<?> builder) {
for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
- DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
+ DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
converter.optimizeSynthesizedClass(synthesizedClass);
builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 94e8147..d87a508 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1265,7 +1265,29 @@
* }
* </pre>
*/
- public void disableAssertions(IRCode code) {
+ public void disableAssertions(
+ AppInfo appInfo, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ if (method.isClassInitializer()) {
+ if (!hasJavacClinitAssertionCode(code)) {
+ return;
+ }
+ // Mark the clinit as having code to turn on assertions.
+ feedback.setInitializerEnablingJavaAssertions(method);
+ } else {
+ // If the clinit of this class did not have have code to turn on assertions don't try to
+ // remove assertion code from the method.
+ DexClass clazz = appInfo.definitionFor(method.method.holder);
+ if (clazz == null) {
+ return;
+ }
+ DexEncodedMethod clinit = clazz.getClassInitializer();
+ if (clinit == null
+ || !clinit.isProcessed()
+ || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+ return;
+ }
+ }
+
InstructionIterator iterator = code.instructionIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
@@ -1288,6 +1310,78 @@
}
}
+ private boolean isClassDesiredAssertionStatusInvoke(Instruction instruction) {
+ return instruction.isInvokeMethod()
+ && instruction.asInvokeMethod().getInvokedMethod()
+ == dexItemFactory.classMethods.desiredAssertionStatus;
+ }
+
+ private boolean isAssertionsDisabledFieldPut(Instruction instruction) {
+ return instruction.isStaticPut()
+ && instruction.asStaticPut().getField().name == dexItemFactory.assertionsDisabled;
+ }
+
+ private boolean isNotDebugInstruction(Instruction instruction) {
+ return !instruction.isDebugInstruction();
+ }
+
+ private Value blockWithSingleConstNumberAndGoto(BasicBlock block) {
+ InstructionIterator iterator = block.iterator();
+ Instruction constNumber = iterator.nextUntil(this::isNotDebugInstruction);
+ if (constNumber == null || !constNumber.isConstNumber()) {
+ return null;
+ }
+ Instruction exit = iterator.nextUntil(this::isNotDebugInstruction);
+ return exit != null && exit.isGoto() ? constNumber.outValue() : null;
+ }
+
+ private Value blockWithAssertionsDisabledFieldPut(BasicBlock block) {
+ InstructionIterator iterator = block.iterator();
+ Instruction fieldPut = iterator.nextUntil(this::isNotDebugInstruction);
+ return fieldPut != null
+ && isAssertionsDisabledFieldPut(fieldPut) ? fieldPut.inValues().get(0) : null;
+ }
+
+ private boolean hasJavacClinitAssertionCode(IRCode code) {
+ InstructionIterator iterator = code.instructionIterator();
+ Instruction current = iterator.nextUntil(this::isClassDesiredAssertionStatusInvoke);
+ if (current == null) {
+ return false;
+ }
+
+ Value DesiredAssertionStatus = current.outValue();
+ assert iterator.hasNext();
+ current = iterator.next();
+ if (!current.isIf()
+ || !current.asIf().isZeroTest()
+ || current.asIf().inValues().get(0) != DesiredAssertionStatus) {
+ return false;
+ }
+
+ If theIf = current.asIf();
+ BasicBlock trueTarget = theIf.getTrueTarget();
+ BasicBlock falseTarget = theIf.fallthroughBlock();
+ if (trueTarget == falseTarget) {
+ return false;
+ }
+
+ Value trueValue = blockWithSingleConstNumberAndGoto(trueTarget);
+ Value falseValue = blockWithSingleConstNumberAndGoto(falseTarget);
+ if (trueValue == null
+ || falseValue == null
+ || (trueTarget.exit().asGoto().getTarget() != falseTarget.exit().asGoto().getTarget())) {
+ return false;
+ }
+
+ BasicBlock target = trueTarget.exit().asGoto().getTarget();
+ Value storeValue = blockWithAssertionsDisabledFieldPut(target);
+ return storeValue != null
+ && storeValue.isPhi()
+ && storeValue.asPhi().getOperands().size() == 2
+ && storeValue.asPhi().getOperands().contains(trueValue)
+ && storeValue.asPhi().getOperands().contains(falseValue);
+ }
+
// Check if the static put is a constant derived from the class holding the method.
// This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
private boolean isClassNameConstant(DexEncodedMethod method, StaticPut put) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index d3d1e2f..85f7a39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -60,7 +60,8 @@
@Override
public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
- assert target.isProcessed();
+ // Do nothing. If the method is not yet processed, we still should
+ // be able to build IR for inlining, though.
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
new file mode 100644
index 0000000..905d4a8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -0,0 +1,223 @@
+// Copyright (c) 2018, 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;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Eliminate redundant field loads.
+ *
+ * <p>Simple algorithm that goes through all blocks in one pass in dominator order and propagates
+ * active field sets across control-flow edges where the target has only one predecessor.
+ */
+// TODO(ager): Evaluate speed/size for computing active field sets in a fixed-point computation.
+public class RedundantFieldLoadElimination {
+
+ private final AppInfo appInfo;
+ private final DexEncodedMethod method;
+ private final IRCode code;
+ private final boolean enableWholeProgramOptimizations;
+ private final DominatorTree dominatorTree;
+
+ // Maps keeping track of fields that have an already loaded value at basic block entry.
+ private final HashMap<BasicBlock, HashMap<FieldAndObject, Instruction>>
+ activeInstanceFieldsAtEntry = new HashMap<>();
+ private final HashMap<BasicBlock, HashMap<DexField, Instruction>> activeStaticFieldsAtEntry =
+ new HashMap<>();
+
+ // Maps keeping track of fields with already loaded values for the current block during
+ // elimination.
+ private HashMap<FieldAndObject, Instruction> activeInstanceFields;
+ private HashMap<DexField, Instruction> activeStaticFields;
+
+ public RedundantFieldLoadElimination(
+ AppInfo appInfo, IRCode code, boolean enableWholeProgramOptimizations) {
+ this.appInfo = appInfo;
+ this.method = code.method;
+ this.code = code;
+ this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
+ dominatorTree = new DominatorTree(code);
+ }
+
+ private static class FieldAndObject {
+ private final DexField field;
+ private final Value object;
+
+ private FieldAndObject(DexField field, Value receiver) {
+ this.field = field;
+ this.object = receiver;
+ }
+
+ @Override
+ public int hashCode() {
+ return field.hashCode() * 7 + object.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof FieldAndObject)) {
+ return false;
+ }
+ FieldAndObject o = (FieldAndObject) other;
+ return o.object == object && o.field == field;
+ }
+ }
+
+ private boolean couldBeVolatile(DexField field) {
+ if (!enableWholeProgramOptimizations && field.getHolder() != method.method.getHolder()) {
+ return true;
+ }
+ DexEncodedField definition = appInfo.definitionFor(field);
+ return definition == null || definition.accessFlags.isVolatile();
+ }
+
+ public void run() {
+ for (BasicBlock block : dominatorTree.getSortedBlocks()) {
+ activeInstanceFields =
+ activeInstanceFieldsAtEntry.containsKey(block)
+ ? activeInstanceFieldsAtEntry.get(block)
+ : new HashMap<>();
+ activeStaticFields =
+ activeStaticFieldsAtEntry.containsKey(block)
+ ? activeStaticFieldsAtEntry.get(block)
+ : new HashMap<>();
+ InstructionListIterator it = block.listIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instruction.isFieldInstruction()) {
+ DexField field = instruction.asFieldInstruction().getField();
+ if (instruction.isInstancePut() || instruction.isStaticPut()) {
+ killActiveFields(instruction.asFieldInstruction());
+ } else if (couldBeVolatile(field)) {
+ assert instruction.isInstanceGet() || instruction.isStaticGet();
+ killAllActiveFields();
+ } else {
+ assert instruction.isInstanceGet() || instruction.isStaticGet();
+ assert !couldBeVolatile(field);
+ if (instruction.isInstanceGet() && !instruction.outValue().hasLocalInfo()) {
+ Value object = instruction.asInstanceGet().object();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ if (activeInstanceFields.containsKey(fieldAndObject)) {
+ Instruction active = activeInstanceFields.get(fieldAndObject);
+ eliminateRedundantRead(it, instruction, active);
+ } else {
+ activeInstanceFields.put(fieldAndObject, instruction);
+ }
+ } else if (instruction.isStaticGet() && !instruction.outValue().hasLocalInfo()) {
+ if (activeStaticFields.containsKey(field)) {
+ Instruction active = activeStaticFields.get(field);
+ eliminateRedundantRead(it, instruction, active);
+ } else {
+ // A field get on a different class can cause <clinit> to run and change static
+ // field values.
+ killActiveFields(instruction.asFieldInstruction());
+ activeStaticFields.put(field, instruction);
+ }
+ }
+ }
+ }
+ if ((instruction.isMonitor() && instruction.asMonitor().isEnter())
+ || instruction.isInvokeMethod()) {
+ killAllActiveFields();
+ }
+ }
+ propagateActiveFieldsFrom(block);
+ }
+ assert code.isConsistentSSA();
+ }
+
+ private void propagateActiveFieldsFrom(BasicBlock block) {
+ for (BasicBlock successor : block.getSuccessors()) {
+ // Allow propagation across exceptional edges, just be careful not to propagate if the
+ // throwing instruction is a field instruction.
+ if (successor.getPredecessors().size() == 1) {
+ if (block.hasCatchSuccessor(successor)) {
+ Instruction exceptionalExit = block.exceptionalExit();
+ if (exceptionalExit != null && exceptionalExit.isFieldInstruction()) {
+ killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
+ }
+ }
+ assert !activeInstanceFieldsAtEntry.containsKey(successor);
+ activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFields));
+ assert !activeStaticFieldsAtEntry.containsKey(successor);
+ activeStaticFieldsAtEntry.put(successor, new HashMap<>(activeStaticFields));
+ }
+ }
+ }
+
+ private void killAllActiveFields() {
+ activeInstanceFields.clear();
+ activeStaticFields.clear();
+ }
+
+ private void killActiveFields(FieldInstruction instruction) {
+ DexField field = instruction.getField();
+ if (instruction.isInstancePut()) {
+ // Remove all the field/object pairs that refer to this field to make sure
+ // that we are conservative.
+ List<FieldAndObject> keysToRemove = new ArrayList<>();
+ for (FieldAndObject key : activeInstanceFields.keySet()) {
+ if (key.field == field) {
+ keysToRemove.add(key);
+ }
+ }
+ keysToRemove.forEach((k) -> activeInstanceFields.remove(k));
+ } else if (instruction.isInstanceGet()) {
+ Value object = instruction.asInstanceGet().object();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ activeInstanceFields.remove(fieldAndObject);
+ } else if (instruction.isStaticPut()) {
+ if (field.clazz != code.method.method.holder) {
+ // Accessing a static field on a different object could cause <clinit> to run which
+ // could modify any static field on any other object.
+ activeStaticFields.clear();
+ } else {
+ activeStaticFields.remove(field);
+ }
+ } else if (instruction.isStaticGet()) {
+ if (field.clazz != code.method.method.holder) {
+ // Accessing a static field on a different object could cause <clinit> to run which
+ // could modify any static field on any other object.
+ activeStaticFields.clear();
+ }
+ }
+ }
+
+ // If a field get instruction throws an exception it did not have an effect on the
+ // value of the field. Therefore, when propagating across exceptional edges for a
+ // field get instruction we have to exclude that field from the set of known
+ // field values.
+ private void killActiveFieldsForExceptionalExit(FieldInstruction instruction) {
+ DexField field = instruction.getField();
+ if (instruction.isInstanceGet()) {
+ Value object = instruction.asInstanceGet().object();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ activeInstanceFields.remove(fieldAndObject);
+ } else if (instruction.isStaticGet()) {
+ activeStaticFields.remove(field);
+ }
+ }
+
+ private void eliminateRedundantRead(
+ InstructionListIterator it, Instruction redundant, Instruction active) {
+ redundant.outValue().replaceUsers(active.outValue());
+ it.removeOrReplaceByDebugLocalRead();
+ active.outValue().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 5bf2c74..3a2371d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -8,10 +8,10 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -27,15 +27,18 @@
public final class ClassInliner {
private final DexItemFactory factory;
+ private final LambdaRewriter lambdaRewriter;
private final int totalMethodInstructionLimit;
- private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexClass, Boolean> knownClasses = new ConcurrentHashMap<>();
public interface InlinerAction {
void inline(Map<InvokeMethod, InliningInfo> methods);
}
- public ClassInliner(DexItemFactory factory, int totalMethodInstructionLimit) {
+ public ClassInliner(DexItemFactory factory,
+ LambdaRewriter lambdaRewriter, int totalMethodInstructionLimit) {
this.factory = factory;
+ this.lambdaRewriter = lambdaRewriter;
this.totalMethodInstructionLimit = totalMethodInstructionLimit;
}
@@ -142,8 +145,8 @@
while (rootsIterator.hasNext()) {
Instruction root = rootsIterator.next();
InlineCandidateProcessor processor =
- new InlineCandidateProcessor(factory, appInfo,
- type -> isClassEligible(appInfo, type),
+ new InlineCandidateProcessor(factory, appInfo, lambdaRewriter,
+ clazz -> isClassEligible(appInfo, clazz),
isProcessedConcurrently, method, root);
// Assess eligibility of instance and class.
@@ -180,7 +183,7 @@
} while (repeat);
}
- private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
+ private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
Boolean eligible = knownClasses.get(clazz);
if (eligible == null) {
Boolean computed = computeClassEligible(appInfo, clazz);
@@ -195,15 +198,14 @@
// - is not an abstract class or interface
// - does not declare finalizer
// - does not trigger any static initializers except for its own
- private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
- DexClass definition = appInfo.definitionFor(clazz);
- if (definition == null || definition.isLibraryClass() ||
- definition.accessFlags.isAbstract() || definition.accessFlags.isInterface()) {
+ private boolean computeClassEligible(AppInfo appInfo, DexClass clazz) {
+ if (clazz == null || clazz.isLibraryClass() ||
+ clazz.accessFlags.isAbstract() || clazz.accessFlags.isInterface()) {
return false;
}
// Class must not define finalizer.
- for (DexEncodedMethod method : definition.virtualMethods()) {
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
if (method.method.name == factory.finalizeMethodName &&
method.method.proto == factory.objectMethods.finalize.proto) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 57ce42f..5fedf37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -53,7 +54,8 @@
private final DexItemFactory factory;
private final AppInfoWithLiveness appInfo;
- private final Predicate<DexType> isClassEligible;
+ private final LambdaRewriter lambdaRewriter;
+ private final Predicate<DexClass> isClassEligible;
private final Predicate<DexEncodedMethod> isProcessedConcurrently;
private final DexEncodedMethod method;
private final Instruction root;
@@ -61,6 +63,7 @@
private Value eligibleInstance;
private DexType eligibleClass;
private DexClass eligibleClassDefinition;
+ private boolean isDesugaredLambda;
private final Map<InvokeMethod, InliningInfo> methodCallsOnInstance
= new IdentityHashMap<>();
@@ -73,10 +76,11 @@
InlineCandidateProcessor(
DexItemFactory factory, AppInfoWithLiveness appInfo,
- Predicate<DexType> isClassEligible,
+ LambdaRewriter lambdaRewriter, Predicate<DexClass> isClassEligible,
Predicate<DexEncodedMethod> isProcessedConcurrently,
DexEncodedMethod method, Instruction root) {
this.factory = factory;
+ this.lambdaRewriter = lambdaRewriter;
this.isClassEligible = isClassEligible;
this.method = method;
this.root = root;
@@ -99,6 +103,11 @@
eligibleClass = isNewInstance() ?
root.asNewInstance().clazz : root.asStaticGet().getField().type;
eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+ if (eligibleClassDefinition == null && lambdaRewriter != null) {
+ // Check if the class is synthesized for a desugared lambda
+ eligibleClassDefinition = lambdaRewriter.getLambdaClass(eligibleClass);
+ isDesugaredLambda = eligibleClassDefinition != null;
+ }
return eligibleClassDefinition != null;
}
@@ -114,7 +123,7 @@
// * class has class initializer marked as TrivialClassInitializer, and
// class initializer initializes the field we are reading here.
boolean isClassAndUsageEligible() {
- if (!isClassEligible.test(eligibleClass)) {
+ if (!isClassEligible.test(eligibleClassDefinition)) {
return false;
}
@@ -129,6 +138,11 @@
assert root.isStaticGet();
+ // We know that desugared lambda classes satisfy eligibility requirements.
+ if (isDesugaredLambda) {
+ return true;
+ }
+
// Checking if we can safely inline class implemented following singleton-like
// pattern, by which we assume a static final field holding on to the reference
// initialized in class constructor.
@@ -444,7 +458,7 @@
: "Inlined constructor? [invoke: " + initInvoke +
", expected class: " + eligibleClass + "]";
- DexEncodedMethod definition = appInfo.definitionFor(init);
+ DexEncodedMethod definition = findSingleTarget(init, true);
if (definition == null || isProcessedConcurrently.test(definition)) {
return null;
}
@@ -455,6 +469,12 @@
return null;
}
+ if (isDesugaredLambda) {
+ // Lambda desugaring synthesizes eligible constructors.
+ markSizeForInlining(definition);
+ return new InliningInfo(definition, eligibleClass);
+ }
+
// If the superclass of the initializer is NOT java.lang.Object, the super class
// initializer being called must be classified as TrivialInstanceInitializer.
//
@@ -499,7 +519,7 @@
private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
- DexEncodedMethod singleTarget = findSingleTarget(callee);
+ DexEncodedMethod singleTarget = findSingleTarget(callee, false);
if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) {
return null;
}
@@ -507,6 +527,16 @@
return null; // Don't inline itself.
}
+ if (isDesugaredLambda) {
+ // If this is the call to method of the desugared lambda, we consider only calls
+ // to main lambda method eligible (for both direct and indirect calls).
+ if (singleTarget.accessFlags.isBridge()) {
+ return null;
+ }
+ markSizeForInlining(singleTarget);
+ return new InliningInfo(singleTarget, eligibleClass);
+ }
+
OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
@@ -652,6 +682,9 @@
private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) {
DexType inlineeHolder = inlinee.method.holder;
+ if (isDesugaredLambda && inlineeHolder == eligibleClass) {
+ return true;
+ }
if (appInfo.isPinned(inlineeHolder)) {
return false;
}
@@ -674,14 +707,16 @@
return root.isNewInstance();
}
- private DexEncodedMethod findSingleTarget(DexMethod callee) {
+ private DexEncodedMethod findSingleTarget(DexMethod callee, boolean isDirect) {
// We don't use computeSingleTarget(...) on invoke since it sometimes fails to
// find the single target, while this code may be more successful since we exactly
// know what is the actual type of the receiver.
// Note that we also intentionally limit ourselves to methods directly defined in
// the instance's class. This may be improved later.
- return eligibleClassDefinition.lookupVirtualMethod(callee);
+ return isDirect
+ ? eligibleClassDefinition.lookupDirectMethod(callee)
+ : eligibleClassDefinition.lookupVirtualMethod(callee);
}
private void removeInstruction(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index a5807c3..7835da2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -10,25 +10,33 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.SourceCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import java.util.function.Consumer;
+import java.util.function.Supplier;
public final class SynthesizedCode extends Code {
- private final SourceCode sourceCode;
+ private final Supplier<SourceCode> sourceCodeProvider;
private final Consumer<UseRegistry> registryCallback;
- public SynthesizedCode(SourceCode sourceCode) {
- this.sourceCode = sourceCode;
+ public SynthesizedCode(SourceCode sourceCodeProvider) {
+ this(() -> sourceCodeProvider);
+ }
+
+ public SynthesizedCode(Supplier<SourceCode> sourceCodeProvider) {
+ this.sourceCodeProvider = sourceCodeProvider;
this.registryCallback = SynthesizedCode::registerReachableDefinitionsDefault;
}
- public SynthesizedCode(SourceCode sourceCode, Consumer<UseRegistry> callback) {
- this.sourceCode = sourceCode;
+ public SynthesizedCode(
+ SourceCode sourceCode, Consumer<UseRegistry> callback) {
+ this.sourceCodeProvider = () -> sourceCode;
this.registryCallback = callback;
}
@@ -40,7 +48,15 @@
@Override
public final IRCode buildIR(
DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
- return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
+ return new IRBuilder(encodedMethod, appInfo, sourceCodeProvider.get(), options).build();
+ }
+
+ @Override
+ public IRCode buildInliningIR(
+ DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options,
+ ValueNumberGenerator valueNumberGenerator, Position callerPosition, Origin origin) {
+ return new IRBuilder(encodedMethod, appInfo,
+ sourceCodeProvider.get(), options, valueNumberGenerator).build();
}
@Override
@@ -59,17 +75,16 @@
@Override
protected final int computeHashCode() {
- return sourceCode.hashCode();
+ throw new Unreachable();
}
@Override
protected final boolean computeEquals(Object other) {
- return other instanceof SynthesizedCode &&
- this.sourceCode.equals(((SynthesizedCode) other).sourceCode);
+ throw new Unreachable();
}
@Override
public final String toString(DexEncodedMethod method, ClassNameMapper naming) {
- return "SynthesizedCode: " + sourceCode.toString();
+ return "SynthesizedCode";
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 05c7b92..fb6ef33 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -10,11 +10,20 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.Set;
/** Class provides basic information about symbols related to Kotlin support. */
public final class Kotlin {
+ // Simply "Lkotlin/", but to avoid being renamed by Shadow.relocate
+ private static final String KOTLIN =
+ String.join("", ImmutableList.of("L", "k", "o", "t", "l", "i", "n", "/"));
+
+ static String addKotlinPrefix(String str) {
+ return KOTLIN + str;
+ }
+
public final DexItemFactory factory;
public final Functional functional;
@@ -44,14 +53,15 @@
//
// This implementation just ignores lambdas with arity > 22.
for (int i = 0; i <= 22; i++) {
- functions.add(factory.createType("Lkotlin/jvm/functions/Function" + i + ";"));
+ functions.add(factory.createType(addKotlinPrefix("jvm/functions/Function") + i + ";"));
}
}
public final DexString kotlinStyleLambdaInstanceName = factory.createString("INSTANCE");
- public final DexType functionBase = factory.createType("Lkotlin/jvm/internal/FunctionBase;");
- public final DexType lambdaType = factory.createType("Lkotlin/jvm/internal/Lambda;");
+ public final DexType functionBase =
+ factory.createType(addKotlinPrefix("jvm/internal/FunctionBase;"));
+ public final DexType lambdaType = factory.createType(addKotlinPrefix("jvm/internal/Lambda;"));
public final DexMethod lambdaInitializerMethod = factory.createMethod(
lambdaType,
@@ -64,7 +74,7 @@
}
public final class Metadata {
- public final DexType kotlinMetadataType = factory.createType("Lkotlin/Metadata;");
+ public final DexType kotlinMetadataType = factory.createType(addKotlinPrefix("Metadata;"));
public final DexString kind = factory.createString("k");
public final DexString metadataVersion = factory.createString("mv");
public final DexString bytecodeVersion = factory.createString("bv");
@@ -77,7 +87,7 @@
// kotlin.jvm.internal.Intrinsics class
public final class Intrinsics {
- public final DexType type = factory.createType("Lkotlin/jvm/internal/Intrinsics;");
+ public final DexType type = factory.createType(addKotlinPrefix("jvm/internal/Intrinsics;"));
public final DexMethod throwParameterIsNullException = factory.createMethod(type,
factory.createProto(factory.voidType, factory.stringType), "throwParameterIsNullException");
public final DexMethod checkParameterIsNotNull = factory.createMethod(type,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 32aebff..389a8f8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -36,7 +36,7 @@
new StringDiagnostic("Class " + clazz.type.toSourceString()
+ " has malformed kotlin.Metadata: " + e.getMessage()));
} catch (Throwable e) {
- reporter.warning(
+ reporter.warning(
new StringDiagnostic("Unexpected error while reading " + clazz.type.toSourceString()
+ "'s kotlin.Metadata: " + e.getMessage()));
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 38473f3..4d072c4 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -895,50 +895,61 @@
}
private void redirectSuperCallsInTarget(DexMethod oldTarget, DexMethod newTarget) {
- // If we merge class B into class C, and class C contains an invocation super.m(), then it
- // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
- // C.m$B denotes the direct method that has been created in C for B.m). In particular, there
- // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is
- // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()".
- //
- // We handle this by adding a mapping for [target] and all of its supertypes.
- DexClass holder = target;
- while (holder != null && holder.isProgramClass()) {
- DexMethod signatureInHolder =
- application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name);
- // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
- boolean resolutionSucceeds =
- holder.lookupVirtualMethod(signatureInHolder) != null
- || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
- if (resolutionSucceeds) {
- deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInHolder, newTarget, target.type);
- } else {
- break;
- }
+ if (source.accessFlags.isInterface()) {
+ // If we merge a default interface method from interface I to its subtype C, then we need
+ // to rewrite invocations on the form "invoke-super I.m()" to "invoke-direct C.m$I()".
+ //
+ // Unlike when we merge a class into its subclass (the else-branch below), we should *not*
+ // rewrite any invocations on the form "invoke-super J.m()" to "invoke-direct C.m$I()",
+ // if I has a supertype J. This is due to the fact that invoke-super instructions that
+ // resolve to a method on an interface never hit an implementation below that interface.
+ deferredRenamings.mapVirtualMethodToDirectInType(oldTarget, newTarget, target.type);
+ } else {
+ // If we merge class B into class C, and class C contains an invocation super.m(), then it
+ // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
+ // C.m$B denotes the direct method that has been created in C for B.m). In particular, there
+ // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is
+ // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()".
+ //
+ // We handle this by adding a mapping for [target] and all of its supertypes.
+ DexClass holder = target;
+ while (holder != null && holder.isProgramClass()) {
+ DexMethod signatureInHolder =
+ application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name);
+ // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
+ boolean resolutionSucceeds =
+ holder.lookupVirtualMethod(signatureInHolder) != null
+ || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
+ if (resolutionSucceeds) {
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ signatureInHolder, newTarget, target.type);
+ } else {
+ break;
+ }
- // Consider that A gets merged into B and B's subclass C gets merged into D. Instructions
- // on the form "invoke-super {B,C,D}.m()" in D are changed into "invoke-direct D.m$C()" by
- // the code above. However, instructions on the form "invoke-super A.m()" should also be
- // changed into "invoke-direct D.m$C()". This is achieved by also considering the classes
- // that have been merged into [holder].
- Set<DexType> mergedTypes = mergedClassesInverse.get(holder.type);
- if (mergedTypes != null) {
- for (DexType type : mergedTypes) {
- DexMethod signatureInType =
- application.dexItemFactory.createMethod(type, oldTarget.proto, oldTarget.name);
- // Resolution would have succeeded if the method used to be in [type], or if one of
- // its super classes declared the method.
- boolean resolutionSucceededBeforeMerge =
- renamedMembersLense.hasMappingForSignatureInContext(holder.type, signatureInType)
- || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
- if (resolutionSucceededBeforeMerge) {
- deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInType, newTarget, target.type);
+ // Consider that A gets merged into B and B's subclass C gets merged into D. Instructions
+ // on the form "invoke-super {B,C,D}.m()" in D are changed into "invoke-direct D.m$C()" by
+ // the code above. However, instructions on the form "invoke-super A.m()" should also be
+ // changed into "invoke-direct D.m$C()". This is achieved by also considering the classes
+ // that have been merged into [holder].
+ Set<DexType> mergedTypes = mergedClassesInverse.get(holder.type);
+ if (mergedTypes != null) {
+ for (DexType type : mergedTypes) {
+ DexMethod signatureInType =
+ application.dexItemFactory.createMethod(type, oldTarget.proto, oldTarget.name);
+ // Resolution would have succeeded if the method used to be in [type], or if one of
+ // its super classes declared the method.
+ boolean resolutionSucceededBeforeMerge =
+ renamedMembersLense.hasMappingForSignatureInContext(holder.type, signatureInType)
+ || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
+ if (resolutionSucceededBeforeMerge) {
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ signatureInType, newTarget, target.type);
+ }
}
}
+ holder = holder.superType != null ? appInfo.definitionFor(holder.superType) : null;
}
- holder = holder.superType != null ? appInfo.definitionFor(holder.superType) : null;
}
}
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 eb8ee0e..93a80b6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -447,7 +447,9 @@
public boolean placeExceptionalBlocksLast = false;
public boolean dontCreateMarkerInD8 = false;
public boolean forceJumboStringProcessing = false;
+ public boolean nondeterministicCycleElimination = false;
public Set<Inliner.Reason> validInliningReasons = null;
+ public boolean suppressExperimentalCfBackendWarning = false;
}
public boolean canUseInvokePolymorphicOnVarHandle() {
diff --git a/src/test/examples/classmerging/CallGraphCycleTest.java b/src/test/examples/classmerging/CallGraphCycleTest.java
new file mode 100644
index 0000000..40347d6
--- /dev/null
+++ b/src/test/examples/classmerging/CallGraphCycleTest.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2018, 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 classmerging;
+
+public class CallGraphCycleTest {
+
+ public static void main(String[] args) {
+ new B(true);
+ }
+
+ public static class A {
+
+ public A(boolean instantiateB) {
+ if (instantiateB) {
+ new B(false);
+ }
+ System.out.println("A(" + instantiateB + ")");
+ }
+ }
+
+ public static class B extends A {
+
+ public B(boolean instantiateBinA) {
+ super(instantiateBinA);
+ System.out.println("B(" + instantiateBinA + ")");
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 4ba014e..37a3010 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
-keep public class classmerging.Test {
public static void main(...);
}
+-keep public class classmerging.CallGraphCycleTest {
+ public static void main(...);
+}
-keep public class classmerging.ClassWithNativeMethodTest {
public static void main(...);
}
diff --git a/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java b/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
new file mode 100644
index 0000000..781d2b4
--- /dev/null
+++ b/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2018 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 uninitializedfinal;
+
+// Test that leaks an instance before its final field has been initialized to a thread that
+// reads that field. This tests that redundant field load elimination does not eliminate
+// field reads (even of final fields) that cross a monitor operation.
+public class UninitializedFinalFieldLeak {
+
+ public static class PollingThread extends Thread {
+ public int result = 0;
+ UninitializedFinalFieldLeak f;
+
+ PollingThread(UninitializedFinalFieldLeak f) {
+ this.f = f;
+ }
+
+ // Read the field a number of times. Then lock on the object to await field initialization.
+ public void run() {
+ result += f.i;
+ result += f.i;
+ result += f.i;
+ f.threadReadsDone = true;
+ synchronized (f) {
+ result += f.i;
+ }
+ // The right result is 42. Reading the uninitialized 0 three times and then
+ // reading the initialized value. It is safe to remove the two redundant loads
+ // before the monitor operation.
+ System.out.println(result);
+ }
+ }
+
+ public final int i;
+ public volatile boolean threadReadsDone = false;
+
+ public UninitializedFinalFieldLeak() throws InterruptedException {
+ // Leak the object to a thread and start the thread with the lock on the object taken.
+ // Then allow the other thread to run and read the uninitialized field.
+ // Finally, initialize the field and release the lock.
+ PollingThread t = new PollingThread(this);
+ synchronized (this) {
+ t.start();
+ while (!threadReadsDone) {
+ Thread.yield();
+ }
+ i = 42;
+ }
+ t.join();
+ }
+
+ public static void main(String[] args) throws InterruptedException {
+ new UninitializedFinalFieldLeak();
+ }
+}
diff --git a/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java b/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java
new file mode 100644
index 0000000..7a0e325
--- /dev/null
+++ b/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2018, 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 classmerging;
+
+public class NestedDefaultInterfaceMethodsTest {
+
+ public static void main(String[] args) {
+ new C().m();
+ }
+
+ public interface A {
+
+ default void m() {
+ System.out.println("In A.m()");
+ }
+ }
+
+ public interface B extends A {
+
+ @Override
+ default void m() {
+ System.out.println("In B.m()");
+ A.super.m();
+ }
+ }
+
+ public static class C implements B {
+
+ @Override
+ public void m() {
+ System.out.println("In C.m()");
+ B.super.m();
+ }
+ }
+}
diff --git a/src/test/examplesAndroidO/classmerging/keep-rules.txt b/src/test/examplesAndroidO/classmerging/keep-rules.txt
index fc91808..4df182e 100644
--- a/src/test/examplesAndroidO/classmerging/keep-rules.txt
+++ b/src/test/examplesAndroidO/classmerging/keep-rules.txt
@@ -10,6 +10,9 @@
-keep public class classmerging.MergeDefaultMethodIntoClassTest {
public static void main(...);
}
+-keep public class classmerging.NestedDefaultInterfaceMethodsTest {
+ public static void main(...);
+}
# TODO(herhut): Consider supporting merging of inner-class attributes.
# -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index f20335b..be9261f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -4,11 +4,15 @@
package com.android.tools.r8;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
import com.android.tools.r8.utils.OffOrAuto;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -94,6 +98,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -101,6 +106,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
.run();
}
@@ -112,6 +118,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -119,6 +126,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
.run();
}
@@ -131,6 +139,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -139,6 +148,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
.run();
}
@@ -151,6 +161,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -159,9 +170,21 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
.run();
}
+ private void checkLambdaCount(DexInspector inspector, int expectedCount, String prefix) {
+ int count = 0;
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ if (clazz.isSynthesizedJavaLambdaClass() &&
+ clazz.getOriginalName().startsWith(prefix)) {
+ count++;
+ }
+ }
+ assertEquals(expectedCount, count);
+ }
+
class R8TestRunner extends TestRunner<R8TestRunner> {
R8TestRunner(String testName, String packageName, String mainClass) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 36f7594..221d427 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -45,9 +45,11 @@
"instancevariable.InstanceVariable",
"instanceofstring.InstanceofString",
"invoke.Invoke",
+ "invokeempty.InvokeEmpty",
"jumbostring.JumboString",
"loadconst.LoadConst",
"loop.UdpServer",
+ "nestedtrycatches.NestedTryCatches",
"newarray.NewArray",
"regalloc.RegAlloc",
"returns.Returns",
@@ -58,9 +60,7 @@
"throwing.Throwing",
"trivial.Trivial",
"trycatch.TryCatch",
- "nestedtrycatches.NestedTryCatches",
"trycatchmany.TryCatchMany",
- "invokeempty.InvokeEmpty",
"regress.Regress",
"regress2.Regress2",
"regress_37726195.Regress",
@@ -82,6 +82,7 @@
"enclosingmethod_proguarded.Main",
"interfaceinlining.Main",
"switchmaps.Switches",
+ "uninitializedfinal.UninitializedFinalFieldLeak",
};
List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index 0f3cadc..11ad12b 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -11,12 +11,14 @@
import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -89,6 +91,11 @@
// This list is currently empty!
);
+ private Set<String> failingOnX8 = ImmutableSet.of(
+ // Contains use of register as both an int and a float.
+ "regression/33336471"
+ );
+
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -161,6 +168,10 @@
.addLibraryFiles(ToolHelper.getDefaultAndroidJar())
.setOutput(Paths.get(outputPath), OutputMode.DexIndexed);
ToolHelper.getAppBuilder(builder).addProgramFiles(originalDexFile);
+
+ if (failingOnX8.contains(directoryName)) {
+ thrown.expect(CompilationFailedException.class);
+ }
R8.run(builder.build());
if (!ToolHelper.artSupported()) {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 7b4f199..855265d 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -54,6 +54,11 @@
public class TestBase {
+ protected enum Backend {
+ CF,
+ DEX
+ };
+
// Actually running Proguard should only be during development.
private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
// Actually running r8.jar in a forked process.
@@ -166,6 +171,17 @@
}
/**
+ * Copy test classes to the specified directory.
+ */
+ protected void copyTestClasses(Path dest, Class... classes) throws IOException {
+ for (Class clazz : classes) {
+ Path path = dest.resolve(clazz.getCanonicalName().replace('.', '/') + ".class");
+ Files.createDirectories(path.getParent());
+ Files.copy(ToolHelper.getClassFileForTestClass(clazz), path);
+ }
+ }
+
+ /**
* Create a temporary JAR file containing the specified test classes.
*/
protected Path jarTestClasses(Class... classes) throws IOException {
@@ -518,7 +534,11 @@
}
protected ProcessResult runOnJavaRaw(String main, byte[]... classes) throws IOException {
- Path file = writeToZip(Arrays.asList(classes));
+ return runOnJavaRaw(main, Arrays.asList(classes));
+ }
+
+ protected ProcessResult runOnJavaRaw(String main, List<byte[]> classes) throws IOException {
+ Path file = writeToZip(classes);
return ToolHelper.runJavaNoVerify(file, main);
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 200a1e7..2767df7 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -474,8 +474,9 @@
public static byte[] getClassAsBytes(Class clazz) throws IOException {
String s = clazz.getSimpleName() + ".class";
Class outer = clazz.getEnclosingClass();
- if (outer != null) {
+ while (outer != null) {
s = outer.getSimpleName() + '$' + s;
+ outer = outer.getEnclosingClass();
}
return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
}
@@ -745,10 +746,6 @@
return parts;
}
- public static Path getPackageDirectoryForTestClass(Class clazz) {
- return getPackageDirectoryForTestPackage(clazz.getPackage());
- }
-
public static List<Path> getClassFilesForTestPackage(Package pkg) throws IOException {
Path dir = ToolHelper.getPackageDirectoryForTestPackage(pkg);
return Files.walk(dir)
@@ -762,6 +759,11 @@
Paths.get("", parts.toArray(new String[parts.size() - 1])));
}
+ public static Path getFileNameForTestClass(Class clazz) {
+ List<String> parts = getNamePartsForTestClass(clazz);
+ return Paths.get("", parts.toArray(new String[parts.size() - 1]));
+ }
+
public static String getJarEntryForTestClass(Class clazz) {
List<String> parts = getNamePartsForTestClass(clazz);
return String.join("/", parts);
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index f28c86f..7635f0c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -70,6 +70,7 @@
options.enableClassMerging = true;
options.enableClassInlining = false;
options.enableMinification = false;
+ options.testing.nondeterministicCycleElimination = true;
}
private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
@@ -115,6 +116,29 @@
}
}
+ // This test has a cycle in the call graph consisting of the methods A.<init> and B.<init>.
+ // When nondeterministicCycleElimination is enabled, we shuffle the nodes in the call graph
+ // before the cycle elimination. Therefore, it is nondeterministic if the cycle detector will
+ // detect the cycle upon the call edge A.<init> to B.<init>, or B.<init> to A.<init>. In order
+ // increase the likelihood that this test encounters both orderings, the test is repeated 5 times.
+ // Assuming that the chance of observing one of the two orderings is 50%, this test therefore has
+ // approximately 3% chance of succeeding although there is an issue.
+ @Test
+ public void testCallGraphCycle() throws Exception {
+ String main = "classmerging.CallGraphCycleTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("CallGraphCycleTest.class"),
+ CF_DIR.resolve("CallGraphCycleTest$A.class"),
+ CF_DIR.resolve("CallGraphCycleTest$B.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of("classmerging.CallGraphCycleTest", "classmerging.CallGraphCycleTest$B");
+ for (int i = 0; i < 5; i++) {
+ runTest(main, programFiles, preservedClassNames::contains);
+ }
+ }
+
@Test
public void testConflictInGeneratedName() throws Exception {
String main = "classmerging.ConflictInGeneratedNameTest";
@@ -251,6 +275,50 @@
}
@Test
+ public void testNestedDefaultInterfaceMethodsTest() throws Exception {
+ String main = "classmerging.NestedDefaultInterfaceMethodsTest";
+ Path[] programFiles =
+ new Path[] {
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest.class"),
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$A.class"),
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$B.class"),
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$C.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.NestedDefaultInterfaceMethodsTest",
+ "classmerging.NestedDefaultInterfaceMethodsTest$B",
+ "classmerging.NestedDefaultInterfaceMethodsTest$C");
+ runTest(
+ main, programFiles, preservedClassNames::contains, getProguardConfig(JAVA8_EXAMPLE_KEEP));
+ }
+
+ @Test
+ public void testNestedDefaultInterfaceMethodsWithCDumpTest() throws Exception {
+ String main = "classmerging.NestedDefaultInterfaceMethodsTest";
+ Path[] programFiles =
+ new Path[] {
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest.class"),
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$A.class"),
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$B.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.NestedDefaultInterfaceMethodsTest",
+ "classmerging.NestedDefaultInterfaceMethodsTest$B",
+ "classmerging.NestedDefaultInterfaceMethodsTest$C");
+ runTestOnInput(
+ main,
+ AndroidApp.builder()
+ .addProgramFiles(programFiles)
+ .addClassProgramData(
+ NestedDefaultInterfaceMethodsTestDump.CDump.dump(), Origin.unknown())
+ .build(),
+ preservedClassNames::contains,
+ getProguardConfig(JAVA8_EXAMPLE_KEEP));
+ }
+
+ @Test
public void testPinnedParameterTypes() throws Exception {
String main = "classmerging.PinnedParameterTypesTest";
Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java b/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java
new file mode 100644
index 0000000..e55802d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2018, 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.classmerging;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class NestedDefaultInterfaceMethodsTestDump {
+
+ // Generated by running ./tools/asmifier.py build/test/examplesAndroidO/classes/classmerging/-
+ // NestedDefaultInterfaceMethodsTest\$C.class, and changing "invoke-special B.m()" to "invoke-
+ // special A.m()".
+ public static class CDump implements Opcodes {
+
+ public static byte[] dump() {
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "classmerging/NestedDefaultInterfaceMethodsTest$C",
+ null,
+ "java/lang/Object",
+ new String[] {"classmerging/NestedDefaultInterfaceMethodsTest$B"});
+
+ cw.visitInnerClass(
+ "classmerging/NestedDefaultInterfaceMethodsTest$C",
+ "classmerging/NestedDefaultInterfaceMethodsTest",
+ "C",
+ ACC_PUBLIC + ACC_STATIC);
+
+ cw.visitInnerClass(
+ "classmerging/NestedDefaultInterfaceMethodsTest$B",
+ "classmerging/NestedDefaultInterfaceMethodsTest",
+ "B",
+ ACC_PUBLIC + ACC_STATIC + ACC_ABSTRACT + ACC_INTERFACE);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "m", "()V", null, null);
+ mv.visitCode();
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn("In C.m()");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitVarInsn(ALOAD, 0);
+ // The signature "classmerging/NestedDefaultInterfaceMethodsTest$B" has been changed to
+ // "classmerging/NestedDefaultInterfaceMethodsTest$A".
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "classmerging/NestedDefaultInterfaceMethodsTest$A", "m", "()V", true);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java
new file mode 100644
index 0000000..68277dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2018, 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.debug;
+
+public class ArrayDimensionGreaterThanSevenTest {
+
+ public static float foo(int x) {
+ try {
+ float[] fs1 = new float[] {42f};
+ float[][] fs2 = new float[][] {fs1};
+ float[][][] fs3 = new float[][][] {fs2};
+ float[][][][] fs4 = new float[][][][] {fs3};
+ float[][][][][] fs5 = new float[][][][][] {fs4};
+ float[][][][][][] fs6 = new float[][][][][][] {fs5};
+ float[][][][][][][] fs7 = new float[][][][][][][] {fs6};
+ float[][][][][][][][] fs8 = new float[][][][][][][][] {fs7};
+ while (x-- > 0) {
+ try {
+ fs8 = x == 0 ? fs8 : null;
+ fs7 = x == 1 ? fs8[1] : fs8[0];
+ fs6 = x == 2 ? fs7[1] : fs7[0];
+ fs5 = x == 3 ? fs6[1] : fs6[0];
+ fs4 = x == 4 ? fs5[1] : fs5[0];
+ fs3 = x == 5 ? fs4[1] : fs4[0];
+ fs2 = x == 6 ? fs3[1] : fs3[0];
+ fs1 = x == 7 ? fs2[1] : fs2[0];
+ } catch (NullPointerException e) {
+ System.out.println("null pointer");
+ }
+ }
+ } catch (RuntimeException e) {
+ return -1f;
+ }
+ return 42;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo(args.length + 1));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
new file mode 100644
index 0000000..5cd652d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
@@ -0,0 +1,590 @@
+package com.android.tools.r8.debug;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class ArrayDimensionGreaterThanSevenTestDump implements Opcodes {
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "foo", "(I)F", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ Label l1 = new Label();
+ Label l2 = new Label();
+ mv.visitTryCatchBlock(l0, l1, l2, "java/lang/NullPointerException");
+ Label l3 = new Label();
+ Label l4 = new Label();
+ Label l5 = new Label();
+ mv.visitTryCatchBlock(l3, l4, l5, "java/lang/RuntimeException");
+ mv.visitLabel(l3);
+ mv.visitInsn(ICONST_1);
+ mv.visitIntInsn(NEWARRAY, T_FLOAT);
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitLdcInsn(new Float("42.0"));
+ mv.visitInsn(FASTORE);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 3);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 4);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 5);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 5);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 6);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 6);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 7);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 7);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 8);
+ Label l6 = new Label();
+ mv.visitLabel(l6);
+ // mv.visitFrame(Opcodes.F_FULL, 9, new Object[] {Opcodes.INTEGER, "[F", "[[F", "[[[F",
+ // "[[[[F", "[[[[[F", "[[[[[[F", "[[[[[[[F", "[[[[[[[[F"}, 0, new Object[] {});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitIincInsn(0, -1);
+ mv.visitJumpInsn(IFLE, l4);
+ mv.visitLabel(l0);
+ mv.visitVarInsn(ILOAD, 0);
+ Label l7 = new Label();
+ mv.visitJumpInsn(IFNE, l7);
+ mv.visitVarInsn(ALOAD, 8);
+ Label l8 = new Label();
+ mv.visitJumpInsn(GOTO, l8);
+ mv.visitLabel(l7);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitInsn(ACONST_NULL);
+ mv.visitTypeInsn(CHECKCAST, "[[[[[[[[F");
+ mv.visitLabel(l8);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[[[[F"});
+ mv.visitVarInsn(ASTORE, 8);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_1);
+ Label l9 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l9);
+ mv.visitVarInsn(ALOAD, 8);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l10 = new Label();
+ mv.visitJumpInsn(GOTO, l10);
+ mv.visitLabel(l9);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 8);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l10);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[[[F"});
+ mv.visitVarInsn(ASTORE, 7);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_2);
+ Label l11 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l11);
+ mv.visitVarInsn(ALOAD, 7);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l12 = new Label();
+ mv.visitJumpInsn(GOTO, l12);
+ mv.visitLabel(l11);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 7);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l12);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[[F"});
+ mv.visitVarInsn(ASTORE, 6);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_3);
+ Label l13 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l13);
+ mv.visitVarInsn(ALOAD, 6);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l14 = new Label();
+ mv.visitJumpInsn(GOTO, l14);
+ mv.visitLabel(l13);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 6);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l14);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[F"});
+ mv.visitVarInsn(ASTORE, 5);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_4);
+ Label l15 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l15);
+ mv.visitVarInsn(ALOAD, 5);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l16 = new Label();
+ mv.visitJumpInsn(GOTO, l16);
+ mv.visitLabel(l15);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 5);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l16);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[F"});
+ mv.visitVarInsn(ASTORE, 4);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_5);
+ Label l17 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l17);
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l18 = new Label();
+ mv.visitJumpInsn(GOTO, l18);
+ mv.visitLabel(l17);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l18);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[F"});
+ mv.visitVarInsn(ASTORE, 3);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitIntInsn(BIPUSH, 6);
+ Label l19 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l19);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l20 = new Label();
+ mv.visitJumpInsn(GOTO, l20);
+ mv.visitLabel(l19);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l20);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[F"});
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitIntInsn(BIPUSH, 7);
+ Label l21 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l21);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l22 = new Label();
+ mv.visitJumpInsn(GOTO, l22);
+ mv.visitLabel(l21);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l22);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[F"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(l1);
+ mv.visitJumpInsn(GOTO, l6);
+ mv.visitLabel(l2);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]
+ // {"java/lang/NullPointerException"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"java/lang/NullPointerException"});
+ mv.visitVarInsn(ASTORE, 9);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn("null pointer");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitJumpInsn(GOTO, l6);
+ mv.visitLabel(l4);
+ // mv.visitFrame(Opcodes.F_FULL, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+ mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+ Label l23 = new Label();
+ mv.visitJumpInsn(GOTO, l23);
+ mv.visitLabel(l5);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/RuntimeException"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 1,
+ new Object[] {Opcodes.INTEGER},
+ 1,
+ new Object[] {"java/lang/RuntimeException"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLdcInsn(new Float("-1.0"));
+ mv.visitInsn(FRETURN);
+ mv.visitLabel(l23);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+ mv.visitLdcInsn(new Float("42.0"));
+ mv.visitInsn(FRETURN);
+ mv.visitMaxs(4, 10);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARRAYLENGTH);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(IADD);
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest",
+ "foo",
+ "(I)F",
+ false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(F)V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
new file mode 100644
index 0000000..7866e1b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2018, 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.debug;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ArrayDimensionGreaterThanSevenTestRunner extends DebugTestBase {
+
+ private static final Class CLASS = ArrayDimensionGreaterThanSevenTest.class;
+ private static final String NAME = CLASS.getCanonicalName();
+
+ private DebugTestConfig getR8CfConfig(String s, Consumer<InternalOptions> optionsConsumer)
+ throws IOException, com.android.tools.r8.CompilationFailedException {
+ Path cfOut = temp.getRoot().toPath().resolve(s);
+ ToolHelper.runR8(
+ R8Command.builder()
+ .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+ .setMode(CompilationMode.DEBUG)
+ .setOutput(cfOut, OutputMode.ClassFile)
+ .build(),
+ optionsConsumer);
+ return new CfDebugTestConfig(cfOut);
+ }
+
+ private Stream<DebuggeeState> createStream(DebugTestConfig config) throws Exception {
+ return streamDebugTest(config, NAME, ANDROID_FILTER);
+ }
+
+ @Test
+ @Ignore("b/111296969")
+ // Once R8 does not use expanded frames this can be enabled again.
+ public void test() throws Exception {
+ DebugTestConfig cfConfig = new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+ DebugTestConfig d8Config = new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+ DebugTestConfig r8JarConfig =
+ getR8CfConfig("r8jar.jar", options -> options.enableCfFrontend = false);
+ DebugTestConfig r8CfConfig =
+ getR8CfConfig("r8cf.jar", options -> options.enableCfFrontend = true);
+ new DebugStreamComparator()
+ .add("CF", createStream(cfConfig))
+ .add("R8/CF", createStream(r8CfConfig))
+ .add("R8/Jar", createStream(r8JarConfig))
+ .add("D8", createStream(d8Config))
+ .compare();
+ }
+
+ @Test
+ // Verify that ASM fails when using expanded frames directly.
+ // See b/111296969
+ public void runTestOnAsmDump() throws Exception {
+ Path out = temp.getRoot().toPath().resolve("out.jar");
+ ArchiveConsumer consumer = new ArchiveConsumer(out);
+ consumer.accept(
+ ArrayDimensionGreaterThanSevenTestDump.dump(),
+ DescriptorUtils.javaTypeToDescriptor(NAME),
+ null);
+ consumer.finished(null);
+ ProcessResult result = ToolHelper.runJava(out, NAME);
+ assertEquals("Expected ASM to fail when using visitFrame(F_NEW, ...)", 1, result.exitCode);
+ assertThat(result.stderr, containsString("java.lang.NoClassDefFoundError: F"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index f2e96b3..f010357 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -3,11 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debug;
+
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.google.common.collect.ImmutableList;
@@ -25,45 +27,64 @@
@RunWith(Parameterized.class)
public class DebugInfoWhenInliningTest extends DebugTestBase {
+ public enum Config {
+ CF,
+ DEX_NO_FORCE_JUMBO,
+ DEX_FORCE_JUMBO
+ };
+
private static final String SOURCE_FILE = "Inlining1.java";
private DebugTestConfig makeConfig(
LineNumberOptimization lineNumberOptimization,
- boolean writeProguardMap)
+ boolean writeProguardMap,
+ RuntimeKind runtimeKind)
throws Exception {
- AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+ DebugTestConfig config = null;
Path outdir = temp.newFolder().toPath();
- Path outjar = outdir.resolve("dex_r8_compiled.jar");
- Path proguardMapPath = writeProguardMap ? outdir.resolve("proguard.map") : null;
+ Path outjar = outdir.resolve("r8_compiled.jar");
R8Command.Builder builder =
- R8Command.builder()
- .addProgramFiles(DEBUGGEE_JAR)
- .setMinApiLevel(minSdk.getLevel())
- .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
- .setMode(CompilationMode.RELEASE)
- .setOutput(outjar, OutputMode.DexIndexed);
- if (proguardMapPath != null) {
- builder.setProguardMapOutputPath(proguardMapPath);
+ R8Command.builder().addProgramFiles(DEBUGGEE_JAR).setMode(CompilationMode.RELEASE);
+
+ if (runtimeKind == RuntimeKind.DEX) {
+ AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+ builder
+ .setMinApiLevel(minSdk.getLevel())
+ .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
+ .setOutput(outjar, OutputMode.DexIndexed);
+ config = new DexDebugTestConfig(outjar);
+ } else {
+ assert (runtimeKind == RuntimeKind.CF);
+ builder.setOutput(outjar, OutputMode.ClassFile);
+ config = new CfDebugTestConfig(outjar);
}
+
+ if (writeProguardMap) {
+ Path proguardMapPath = outdir.resolve("proguard.map");
+ builder.setProguardMapOutputPath(proguardMapPath);
+ config.setProguardMap(proguardMapPath);
+ }
+
ToolHelper.runR8(
builder.build(), options -> {
options.lineNumberOptimization = lineNumberOptimization;
options.testing.forceJumboStringProcessing = forceJumboStringProcessing;
});
- DebugTestConfig config = new DexDebugTestConfig(outjar);
- config.setProguardMap(proguardMapPath);
+
return config;
}
private boolean forceJumboStringProcessing;
+ private RuntimeKind runtimeKind;
- @Parameters(name="forceJumbo: {0}")
- public static Collection<Boolean> data() {
- return Arrays.asList(true, false);
+ @Parameters(name = "config: {0}")
+ public static Collection<Config> data() {
+ return Arrays.asList(Config.values());
}
- public DebugInfoWhenInliningTest(boolean forceJumboStringProcessing) {
- this.forceJumboStringProcessing = forceJumboStringProcessing;
+ public DebugInfoWhenInliningTest(Config config) {
+ this.forceJumboStringProcessing = config == Config.DEX_FORCE_JUMBO;
+ this.runtimeKind = config == Config.CF ? RuntimeKind.CF : RuntimeKind.DEX;
}
@Test
@@ -75,7 +96,7 @@
// (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
// are emitted duplicated in the dex code, the debugger stops only when there's a change.
int[] lineNumbers = {7, 32, 11, 7};
- testEachLine(makeConfig(LineNumberOptimization.OFF, false), lineNumbers);
+ testEachLine(makeConfig(LineNumberOptimization.OFF, false, runtimeKind), lineNumbers);
}
@Test
@@ -87,13 +108,13 @@
// (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
// are emitted duplicated in the dex code, the debugger stops only when there's a change.
int[] lineNumbers = {7, 32, 11, 7};
- testEachLine(makeConfig(LineNumberOptimization.OFF, true), lineNumbers);
+ testEachLine(makeConfig(LineNumberOptimization.OFF, true, runtimeKind), lineNumbers);
}
@Test
public void testEachLineOptimized() throws Throwable {
int[] lineNumbers = {1, 2, 3, 4, 5, 6, 7, 8};
- testEachLine(makeConfig(LineNumberOptimization.ON, false), lineNumbers);
+ testEachLine(makeConfig(LineNumberOptimization.ON, false, runtimeKind), lineNumbers);
}
@Test
@@ -130,7 +151,7 @@
new SignatureAndLine("void Inlining2.differentFileMultilevelInliningLevel1()", 36),
new SignatureAndLine("void main(java.lang.String[])", 26)));
testEachLine(
- makeConfig(LineNumberOptimization.ON, true), lineNumbers, inlineFramesList);
+ makeConfig(LineNumberOptimization.ON, true, runtimeKind), lineNumbers, inlineFramesList);
}
private void testEachLine(DebugTestConfig config, int[] lineNumbers) throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
index eb85a7c..3a83e30 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
@@ -214,7 +214,8 @@
try {
if (done) {
assertTrue(
- "Not all streams completed at the same time",
+ "Not all streams completed at the same time. "
+ + "Set 'DebugTestBase.DEBUG_TEST = true' to aid in diagnosing the issue.",
states.stream().allMatch(Objects::isNull));
return;
} else {
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index bda9ac5..6877617 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -10,11 +10,22 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.invokesuper2.C0;
+import com.android.tools.r8.graph.invokesuper2.C1;
+import com.android.tools.r8.graph.invokesuper2.C2;
+import com.android.tools.r8.graph.invokesuper2.I0;
+import com.android.tools.r8.graph.invokesuper2.I1;
+import com.android.tools.r8.graph.invokesuper2.I2;
+import com.android.tools.r8.graph.invokesuper2.I3;
+import com.android.tools.r8.graph.invokesuper2.I4;
+import com.android.tools.r8.graph.invokesuper2.Main;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
import java.util.Collections;
import org.junit.Test;
@@ -200,6 +211,63 @@
AndroidApp processedApp = processApplication(application);
assertEquals("42", runArt(processedApp));
}
+
+ @Test
+ public void testLookupSuperTarget() throws Exception {
+ String pkg = Main.class.getPackage().getName().replace('.', '/');
+
+ AndroidApp.Builder builder = AndroidApp.builder();
+ for (Class clazz : new Class[]{
+ I0.class, I1.class, I2.class, I3.class, I4.class,
+ C0.class, C1.class, C2.class,
+ Main.class}) {
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+ // At least java.lang.Object is needed as interface method lookup have special handling
+ // of methods on java.lang.Object.
+ builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ }
+ AndroidApp application = builder.build();
+ AppInfo appInfo = getAppInfo(application);
+ DexItemFactory factory = appInfo.dexItemFactory;
+
+ DexType i0 = factory.createType("L" + pkg + "/I0;");
+ DexType i1 = factory.createType("L" + pkg + "/I1;");
+ DexType i2 = factory.createType("L" + pkg + "/I2;");
+ DexType i3 = factory.createType("L" + pkg + "/I3;");
+ DexType i4 = factory.createType("L" + pkg + "/I4;");
+ DexType c0 = factory.createType("L" + pkg + "/C0;");
+ DexType c1 = factory.createType("L" + pkg + "/C1;");
+ DexType c2 = factory.createType("L" + pkg + "/C2;");
+
+ DexProto mProto = factory.createProto(factory.intType);
+ DexString m = factory.createString("m");
+ DexMethod mOnC0 = factory.createMethod(c0, mProto, m);
+ DexMethod mOnC1 = factory.createMethod(c1, mProto, m);
+ DexMethod mOnI0 = factory.createMethod(i0, mProto, m);
+ DexMethod mOnI1 = factory.createMethod(i1, mProto, m);
+ DexMethod mOnI2 = factory.createMethod(i2, mProto, m);
+ DexMethod mOnI3 = factory.createMethod(i3, mProto, m);
+ DexMethod mOnI4 = factory.createMethod(i4, mProto, m);
+
+ assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC0, c1).method);
+ assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI1, c1).method);
+ assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI2, c1).method);
+
+ assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC1, c2).method);
+ assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI3, c2).method);
+ assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI4, c2).method);
+
+ // Copy classes to run on the Java VM.
+ Path out = temp.newFolder().toPath();
+ copyTestClasses(out, I0.class, I1.class, I2.class, I3.class, I4.class);
+ copyTestClasses(out, C0.class, C1.class, C2.class, Main.class);
+ ProcessResult result = ToolHelper.runJava(out, Main.class.getCanonicalName());
+ assertEquals(0, result.exitCode);
+
+ // Process the application and expect the same result on Art.
+ AndroidApp processedApp = processApplication(application);
+ assertEquals(result.stdout, runArt(processedApp, Main.class.getCanonicalName()));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java
new file mode 100644
index 0000000..8e92fb4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, 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.graph.invokesuper2;
+
+public class C0 implements I0 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java
new file mode 100644
index 0000000..ff98da9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, 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.graph.invokesuper2;
+
+public class C1 extends C0 implements I1, I2 {
+ public int m() {
+ // super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/C0.m()I
+ System.out.println(super.m());
+ // I1.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I1.m:()I
+ System.out.println(I1.super.m());
+ // I2.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I2.m:()I
+ System.out.println(I2.super.m());
+ return 3;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java
new file mode 100644
index 0000000..abdf7ff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, 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.graph.invokesuper2;
+
+public class C2 extends C0 implements I3, I4 {
+ public int m() {
+ // super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/C0.m()I
+ System.out.println(super.m());
+ // I1.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I3.m:()I
+ System.out.println(I3.super.m());
+ // I2.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I4.m:()I
+ System.out.println(I4.super.m());
+ return 3;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java
new file mode 100644
index 0000000..02b59a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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.graph.invokesuper2;
+
+public interface I0 {
+ default int m() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java
new file mode 100644
index 0000000..0593d90
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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.graph.invokesuper2;
+
+public interface I1 extends I0 {
+ default int m() {
+ return 1;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java
new file mode 100644
index 0000000..22956c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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.graph.invokesuper2;
+
+public interface I2 extends I0 {
+ default int m() {
+ return 2;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java
new file mode 100644
index 0000000..e64154e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, 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.graph.invokesuper2;
+
+public interface I3 extends I1 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java
new file mode 100644
index 0000000..ddc5ad0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, 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.graph.invokesuper2;
+
+public interface I4 extends I2 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java
new file mode 100644
index 0000000..6a2a3b9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, 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.graph.invokesuper2;
+
+public class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new C1().m());
+ System.out.println(new C2().m());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java b/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
new file mode 100644
index 0000000..56699e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
@@ -0,0 +1,214 @@
+// Copyright (c) 2018, 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.callgraph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.conversion.CallGraph.CycleEliminator;
+import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class CycleEliminationTest extends TestBase {
+
+ private static class Configuration {
+
+ final Collection<Node> nodes;
+ final Set<Node> forceInline;
+ final BooleanSupplier test;
+
+ Configuration(Collection<Node> nodes, Set<Node> forceInline, BooleanSupplier test) {
+ this.nodes = nodes;
+ this.forceInline = forceInline;
+ this.test = test;
+ }
+ }
+
+ private DexItemFactory dexItemFactory = new DexItemFactory();
+
+ @Rule public final ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void testSimpleCycle() {
+ Node method = createNode("n1");
+ Node forceInlinedMethod = createForceInlinedNode("n2");
+
+ Iterable<Collection<Node>> orderings =
+ ImmutableList.of(
+ ImmutableList.of(method, forceInlinedMethod),
+ ImmutableList.of(forceInlinedMethod, method));
+
+ for (Collection<Node> nodes : orderings) {
+ // Create a cycle between the two nodes.
+ method.addCallee(forceInlinedMethod);
+ forceInlinedMethod.addCallee(method);
+
+ // Check that the cycle eliminator finds the cycle.
+ CycleEliminator cycleEliminator = new CycleEliminator(nodes, new InternalOptions());
+ assertEquals(1, cycleEliminator.breakCycles());
+
+ // The edge from method to forceInlinedMethod should be removed to ensure that force inlining
+ // will work.
+ assertTrue(forceInlinedMethod.isLeaf());
+
+ // Check that the cycle eliminator agrees that there are no more cycles left.
+ assertEquals(0, cycleEliminator.breakCycles());
+ }
+ }
+
+ @Test
+ public void testSimpleCycleWithCyclicForceInlining() {
+ Node method = createForceInlinedNode("n1");
+ Node forceInlinedMethod = createForceInlinedNode("n2");
+
+ // Create a cycle between the two nodes.
+ method.addCallee(forceInlinedMethod);
+ forceInlinedMethod.addCallee(method);
+
+ CycleEliminator cycleEliminator =
+ new CycleEliminator(ImmutableList.of(method, forceInlinedMethod), new InternalOptions());
+
+ exception.expect(CompilationError.class);
+ exception.expectMessage(CycleEliminator.CYCLIC_FORCE_INLINING_MESSAGE);
+
+ // Should throw because force inlining will fail.
+ cycleEliminator.breakCycles();
+ }
+
+ @Test
+ public void testGraphWithNestedCycles() {
+ Node n1 = createNode("n1");
+ Node n2 = createNode("n2");
+ Node n3 = createNode("n3");
+
+ BooleanSupplier canInlineN1 =
+ () -> {
+ // The node n1 should be force inlined into n2 and n3, so these edges must be kept.
+ assertTrue(n2.hasCallee(n1));
+ assertTrue(n3.hasCallee(n1));
+ // Furthermore, the edge from n1 to n2 must be removed.
+ assertFalse(n1.hasCallee(n2));
+ return true;
+ };
+
+ BooleanSupplier canInlineN3 =
+ () -> {
+ // The node n3 should be force inlined into n2, so this edge must be kept.
+ assertTrue(n2.hasCallee(n3));
+ // Furthermore, one of the edges n1 -> n2 and n3 -> n1 must be removed.
+ assertFalse(n1.hasCallee(n2) && n3.hasCallee(n1));
+ return true;
+ };
+
+ BooleanSupplier canInlineN1N3 =
+ () -> {
+ // The edge n1 -> n2 must be removed.
+ assertFalse(n1.hasCallee(n2));
+ // Check that both can be force inlined.
+ return canInlineN1.getAsBoolean() && canInlineN3.getAsBoolean();
+ };
+
+ List<Collection<Node>> orderings =
+ ImmutableList.of(
+ ImmutableList.of(n1, n2, n3),
+ ImmutableList.of(n1, n3, n2),
+ ImmutableList.of(n2, n1, n3),
+ ImmutableList.of(n2, n3, n1),
+ ImmutableList.of(n3, n1, n2),
+ ImmutableList.of(n3, n2, n1));
+
+ List<Configuration> configurations = new ArrayList<>();
+ // All orderings, where no methods are marked as force inline.
+ orderings
+ .stream()
+ .map(ordering -> new Configuration(ordering, ImmutableSet.of(), null))
+ .forEach(configurations::add);
+ // All orderings, where n1 is marked as force inline
+ // (the configuration where n2 is marked as force inline is symmetric).
+ orderings
+ .stream()
+ .map(ordering -> new Configuration(ordering, ImmutableSet.of(n1), canInlineN1))
+ .forEach(configurations::add);
+ // All orderings, where n3 is marked as force inline.
+ orderings
+ .stream()
+ .map(ordering -> new Configuration(ordering, ImmutableSet.of(n3), canInlineN3))
+ .forEach(configurations::add);
+ // All orderings, where n1 and n3 are marked as force inline.
+ orderings
+ .stream()
+ .map(ordering -> new Configuration(ordering, ImmutableSet.of(n1, n3), canInlineN1N3))
+ .forEach(configurations::add);
+
+ for (Configuration configuration : configurations) {
+ // Create a cycle between the three nodes.
+ n1.addCallee(n2);
+ n2.addCallee(n3);
+ n3.addCallee(n1);
+
+ // Create a cycle in the graph between node n1 and n2.
+ n2.addCallee(n1);
+
+ for (Node node : configuration.nodes) {
+ if (configuration.forceInline.contains(node)) {
+ node.method.markForceInline();
+ } else {
+ node.method.unsetForceInline();
+ }
+ }
+
+ // Check that the cycle eliminator finds the cycles.
+ CycleEliminator cycleEliminator =
+ new CycleEliminator(configuration.nodes, new InternalOptions());
+ int numberOfCycles = cycleEliminator.breakCycles();
+ if (numberOfCycles == 1) {
+ // If only one cycle was removed, then it must be the edge from n1 -> n2 that was removed.
+ assertTrue(n1.isLeaf());
+ } else {
+ // Check that the cycle eliminator found both cycles.
+ assertEquals(2, numberOfCycles);
+ }
+
+ // Check that the cycle eliminator agrees that there are no more cycles left.
+ assertEquals(0, cycleEliminator.breakCycles());
+
+ // Check that force inlining is guaranteed to succeed.
+ if (configuration.test != null) {
+ assertTrue(configuration.test.getAsBoolean());
+ }
+ }
+ }
+
+ private Node createNode(String methodName) {
+ DexMethod signature =
+ dexItemFactory.createMethod(
+ dexItemFactory.objectType,
+ dexItemFactory.createProto(dexItemFactory.voidType),
+ methodName);
+ return new Node(new DexEncodedMethod(signature, null, null, null, null));
+ }
+
+ private Node createForceInlinedNode(String methodName) {
+ Node node = createNode(methodName);
+ node.method.markForceInline();
+ return node;
+ }
+}
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 3239191..e7a8285 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
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.optimize.classinliner.code.C;
import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.lambdas.LambdasTestClass;
import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -301,6 +302,37 @@
assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
}
+ @Test
+ public void testDesugaredLambdas() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(LambdasTestClass.class),
+ ToolHelper.getClassAsBytes(LambdasTestClass.Iface.class),
+ ToolHelper.getClassAsBytes(LambdasTestClass.IfaceUtil.class),
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), LambdasTestClass.class);
+
+ String javaOutput = runOnJava(LambdasTestClass.class);
+ String artOutput = runOnArt(app, LambdasTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(LambdasTestClass.class);
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder"),
+ collectTypes(clazz, "testStatelessLambda", "void"));
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder"),
+ collectTypes(clazz, "testStatefulLambda", "void", "java.lang.String", "java.lang.String"));
+
+ assertEquals(0,
+ inspector.allClasses().stream()
+ .filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
+ }
+
private Set<String> collectTypes(
ClassSubject clazz, String methodName, String retValue, String... params) {
return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
new file mode 100644
index 0000000..130f895
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2018, 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.classinliner.lambdas;
+
+public class LambdasTestClass {
+ private static int ID = 0;
+
+ private static int nextInt() {
+ return ID++;
+ }
+
+ private static String next() {
+ return Integer.toString(nextInt());
+ }
+
+ public interface Iface {
+ String foo();
+ }
+
+ public static class IfaceUtil {
+ public static void act(Iface iface) {
+ System.out.println("" + next() + "> " + iface.foo());
+ }
+ }
+
+ public static void main(String[] args) {
+ LambdasTestClass test = new LambdasTestClass();
+ test.testStatelessLambda();
+ test.testStatefulLambda(next(), next());
+ }
+
+ public static String exact() {
+ return next();
+ }
+
+ public static String almost(String... s) {
+ return next();
+ }
+
+ private synchronized void testStatelessLambda() {
+ IfaceUtil.act(() -> next());
+ IfaceUtil.act(LambdasTestClass::next);
+ IfaceUtil.act(LambdasTestClass::exact);
+ IfaceUtil.act(LambdasTestClass::almost);
+ }
+
+ private synchronized void testStatefulLambda(String a, String b) {
+ IfaceUtil.act(() -> a);
+ IfaceUtil.act(() -> a + b);
+ IfaceUtil.act((a + b)::toLowerCase);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 82381dd..9c15ce7 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -353,12 +353,16 @@
return out.toByteArray();
}
- public List<byte[]> buildClasses() throws Exception {
- List<byte[]> result = new ArrayList<>();
+ public ImmutableList.Builder<byte[]> buildClasses(ImmutableList.Builder<byte[]> builder)
+ throws Exception {
for (ClassBuilder clazz : classes) {
- result.add(compile(clazz));
+ builder.add(compile(clazz));
}
- return result;
+ return builder;
+ }
+
+ public List<byte[]> buildClasses() throws Exception {
+ return buildClasses(ImmutableList.builder()).build();
}
public AndroidApp build() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index b9ff7ea..f3f1211 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@@ -21,6 +20,7 @@
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_8;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DiagnosticsChecker;
import com.android.tools.r8.R8Command;
@@ -33,11 +33,17 @@
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
+@RunWith(Parameterized.class)
public class MinifierClassSignatureTest extends TestBase {
/*
@@ -62,6 +68,16 @@
String outerSignature = "<T:Ljava/lang/Object;>Ljava/lang/Object;";
String extendsInnerSignature = "LOuter<TT;>.Inner;";
String extendsInnerInnerSignature = "LOuter<TT;>.Inner.InnerInner;";
+ Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MinifierClassSignatureTest(Backend backend) {
+ this.backend = backend;
+ }
private byte[] dumpSimple(String classSignature) throws Exception {
@@ -290,26 +306,36 @@
Consumer<DexInspector> inspect)
throws Exception {
DiagnosticsChecker checker = new DiagnosticsChecker();
- DexInspector inspector = new DexInspector(
- ToolHelper.runR8(R8Command.builder(checker)
- .addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
- .addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
- .addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
- .addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
- .addClassProgramData(
- dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
- .addClassProgramData(
- dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
- .addClassProgramData(
- dumpExtendsInnerInner(
- signatures.get("Outer$Inner$ExtendsInnerInner")), Origin.unknown())
- .addProguardConfiguration(ImmutableList.of(
- "-keepattributes InnerClasses,EnclosingMethod,Signature",
- "-keep,allowobfuscation class **"
- ), Origin.unknown())
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .setProguardMapConsumer(StringConsumer.emptyConsumer())
- .build()));
+ assert (backend == Backend.CF || backend == Backend.DEX);
+ DexInspector inspector =
+ new DexInspector(
+ ToolHelper.runR8(
+ R8Command.builder(checker)
+ .addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
+ .addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
+ .addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
+ .addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
+ .addClassProgramData(
+ dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
+ .addClassProgramData(
+ dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
+ .addClassProgramData(
+ dumpExtendsInnerInner(signatures.get("Outer$Inner$ExtendsInnerInner")),
+ Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod,Signature",
+ "-keep,allowobfuscation class **"),
+ Origin.unknown())
+ .setProgramConsumer(
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer())
+ .setProguardMapConsumer(StringConsumer.emptyConsumer())
+ .build(),
+ options -> {
+ options.testing.suppressExperimentalCfBackendWarning = true;
+ }));
// All classes are kept, and renamed.
assertThat(inspector.clazz("Simple"), isRenamed());
assertThat(inspector.clazz("Base"), isRenamed());
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index e48ce56..a5351e2 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -21,6 +21,7 @@
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_8;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DiagnosticsChecker;
import com.android.tools.r8.R8Command;
@@ -34,13 +35,18 @@
import com.android.tools.r8.utils.DexInspector.FieldSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Map;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
-
+@RunWith(Parameterized.class)
public class MinifierFieldSignatureTest extends TestBase {
/*
@@ -59,6 +65,16 @@
private String anArrayOfXSignature = "[TX;";
private String aFieldsOfXSignature = "LFields<TX;>;";
private String aFieldsOfXInnerSignature = "LFields<TX;>.Inner;";
+ private Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MinifierFieldSignatureTest(Backend backend) {
+ this.backend = backend;
+ }
public byte[] dumpFields(Map<String, String> signatures) throws Exception {
@@ -164,17 +180,27 @@
Consumer<DexInspector> inspect)
throws Exception {
DiagnosticsChecker checker = new DiagnosticsChecker();
- DexInspector inspector = new DexInspector(
- ToolHelper.runR8(R8Command.builder(checker)
- .addClassProgramData(dumpFields(signatures), Origin.unknown())
- .addClassProgramData(dumpInner(), Origin.unknown())
- .addProguardConfiguration(ImmutableList.of(
- "-keepattributes InnerClasses,EnclosingMethod,Signature",
- "-keep,allowobfuscation class ** { *; }"
- ), Origin.unknown())
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .setProguardMapConsumer(StringConsumer.emptyConsumer())
- .build()));
+ assert (backend == Backend.CF || backend == Backend.DEX);
+ DexInspector inspector =
+ new DexInspector(
+ ToolHelper.runR8(
+ R8Command.builder(checker)
+ .addClassProgramData(dumpFields(signatures), Origin.unknown())
+ .addClassProgramData(dumpInner(), Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod,Signature",
+ "-keep,allowobfuscation class ** { *; }"),
+ Origin.unknown())
+ .setProgramConsumer(
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer())
+ .setProguardMapConsumer(StringConsumer.emptyConsumer())
+ .build(),
+ options -> {
+ options.testing.suppressExperimentalCfBackendWarning = true;
+ }));
// All classes are kept, and renamed.
ClassSubject clazz = inspector.clazz("Fields");
assertThat(clazz, isRenamed());
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index 41750ae..baeedbb 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -24,6 +24,7 @@
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_8;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DiagnosticsChecker;
import com.android.tools.r8.R8Command;
@@ -37,12 +38,18 @@
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Map;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
+@RunWith(Parameterized.class)
public class MinifierMethodSignatureTest extends TestBase {
/*
@@ -61,6 +68,16 @@
private String parameterizedReturnSignature = "()LMethods<TX;>.Inner;";
private String parameterizedArgumentsSignature = "(TX;LMethods<TX;>.Inner;)V";
private String parametrizedThrowsSignature = "()V^TX;";
+ Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MinifierMethodSignatureTest(Backend backend) {
+ this.backend = backend;
+ }
private byte[] dumpMethods(Map<String, String> signatures) throws Exception {
@@ -185,17 +202,27 @@
Consumer<DexInspector> inspect)
throws Exception {
DiagnosticsChecker checker = new DiagnosticsChecker();
- DexInspector inspector = new DexInspector(
- ToolHelper.runR8(R8Command.builder(checker)
- .addClassProgramData(dumpMethods(signatures), Origin.unknown())
- .addClassProgramData(dumpInner(), Origin.unknown())
- .addProguardConfiguration(ImmutableList.of(
- "-keepattributes InnerClasses,EnclosingMethod,Signature",
- "-keep,allowobfuscation class ** { *; }"
- ), Origin.unknown())
- .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
- .setProguardMapConsumer(StringConsumer.emptyConsumer())
- .build()));
+ assert (backend == Backend.CF || backend == Backend.DEX);
+ DexInspector inspector =
+ new DexInspector(
+ ToolHelper.runR8(
+ R8Command.builder(checker)
+ .addClassProgramData(dumpMethods(signatures), Origin.unknown())
+ .addClassProgramData(dumpInner(), Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod,Signature",
+ "-keep,allowobfuscation class ** { *; }"),
+ Origin.unknown())
+ .setProgramConsumer(
+ backend == Backend.DEX
+ ? DexIndexedConsumer.emptyConsumer()
+ : ClassFileConsumer.emptyConsumer())
+ .setProguardMapConsumer(StringConsumer.emptyConsumer())
+ .build(),
+ options -> {
+ options.testing.suppressExperimentalCfBackendWarning = true;
+ }));
// All classes are kept, and renamed.
ClassSubject clazz = inspector.clazz("Methods");
assertThat(clazz, isRenamed());
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index c929eaf..878cb7c 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.DexInstructionSubject;
import com.android.tools.r8.utils.DexInspector.InstructionSubject;
import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
@@ -82,7 +83,8 @@
assertTrue(insn.isInvoke());
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("throwNpe"));
- assertTrue(nextInstruction(instructions).isConst4());
+ insn = nextInstruction(instructions);
+ assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
assertTrue(nextInstruction(instructions).isThrow());
assertFalse(instructions.hasNext());
@@ -105,7 +107,8 @@
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("throwNpe"));
}
- assertTrue(nextInstruction(instructions).isConst4());
+ insn = nextInstruction(instructions);
+ assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
assertTrue(nextInstruction(instructions).isThrow());
assertFalse(instructions.hasNext());
@@ -119,7 +122,8 @@
assertTrue(insn.isInvoke());
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("innerNotReachable"));
- assertTrue(nextInstruction(instructions).isConst4());
+ insn = nextInstruction(instructions);
+ assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
assertTrue(nextInstruction(instructions).isThrow());
assertFalse(instructions.hasNext());
}
diff --git a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
new file mode 100644
index 0000000..84fc5c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
@@ -0,0 +1,361 @@
+// Copyright (c) 2018, 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.regress.b111250398;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Iget;
+import com.android.tools.r8.code.IgetObject;
+import com.android.tools.r8.code.Sget;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import org.junit.Test;
+
+// Copy of javax.inject.Provider.
+interface Provider<T> {
+ T get();
+}
+
+// Copy of dagger.internal.SingleClass.
+final class SingleCheck<T> implements Provider<T> {
+ private static final Object UNINITIALIZED = new Object();
+
+ private volatile Provider<T> provider;
+ private volatile Object instance = UNINITIALIZED;
+
+ private SingleCheck(Provider<T> provider) {
+ assert provider != null;
+ this.provider = provider;
+ }
+
+ @SuppressWarnings("unchecked") // cast only happens when result comes from the delegate provider
+ @Override
+ public T get() {
+ Object local = instance;
+ if (local == UNINITIALIZED) {
+ // provider is volatile and might become null after the check, so retrieve the provider first
+ Provider<T> providerReference = provider;
+ if (providerReference == null) {
+ // The provider was null, so the instance must already be set
+ local = instance;
+ } else {
+ local = providerReference.get();
+ instance = local;
+
+ // Null out the reference to the provider. We are never going to need it again, so we can
+ // make it eligible for GC.
+ provider = null;
+ }
+ }
+ return (T) local;
+ }
+
+ // This method is not relevant for the test.
+ /*
+ public static <P extends Provider<T>, T> Provider<T> provider(P provider) {
+ // If a scoped @Binds delegates to a scoped binding, don't cache the value again.
+ if (provider instanceof SingleCheck || provider instanceof DoubleCheck) {
+ return provider;
+ }
+ return new SingleCheck<T>(checkNotNull(provider));
+ }
+ */
+}
+
+// Several field gets on non-volatile and volatile fields on the same class.
+class A {
+ int t;
+ int f;
+ static int sf;
+ volatile int v;
+ static volatile int sv;
+
+ public void mf() {
+ t = f;
+ t = f;
+ t = f;
+ t = f;
+ t = f;
+ }
+
+ public void mfWithMonitor() {
+ t = f;
+ synchronized (this) {
+ t = f;
+ t = f;
+ }
+ t = f;
+ t = f;
+ }
+
+ public void msf() {
+ t = sf;
+ t = sf;
+ t = sf;
+ t = sf;
+ t = sf;
+ }
+
+ public void mv() {
+ t = v;
+ t = v;
+ t = v;
+ t = v;
+ t = v;
+ }
+
+ public void msv() {
+ t = sv;
+ t = sv;
+ t = sv;
+ t = sv;
+ t = sv;
+ }
+}
+
+// Several field gets on non-volatile and volatile fields on different class.
+class B {
+ int t;
+
+ public void mf(A a) {
+ t = a.f;
+ t = a.f;
+ t = a.f;
+ t = a.f;
+ t = a.f;
+ }
+
+ public void msf() {
+ t = A.sf;
+ t = A.sf;
+ t = A.sf;
+ t = A.sf;
+ t = A.sf;
+ }
+
+ public void mv(A a) {
+ t = a.v;
+ t = a.v;
+ t = a.v;
+ t = a.v;
+ t = a.v;
+ }
+
+ public void msv() {
+ t = A.sv;
+ t = A.sv;
+ t = A.sv;
+ t = A.sv;
+ t = A.sv;
+ }
+}
+
+// Modified sample from http://tutorials.jenkov.com/java-concurrency/volatile.html.
+class C {
+ private int years;
+ private int months;
+ private volatile int days;
+
+ public int totalDays() {
+ int total = this.days;
+ total += months * 30;
+ total += years * 365;
+ return total;
+ }
+
+ public int totalDaysTimes2() {
+ int total = this.days;
+ total += months * 30;
+ total += years * 365;
+ total += this.days;
+ total += months * 30;
+ total += years * 365;
+ return total;
+ }
+
+ public int totalDaysTimes3() {
+ int total = this.days;
+ total += months * 30;
+ total += years * 365;
+ total += this.days;
+ total += months * 30;
+ total += years * 365;
+ total += this.days;
+ total += months * 30;
+ total += years * 365;
+ return total;
+ }
+
+ public void update(int years, int months, int days){
+ this.years = years;
+ this.months = months;
+ this.days = days;
+ }
+}
+
+public class B111250398 extends TestBase {
+
+ private void releaseMode(InternalOptions options) {
+ options.debug = false;
+ }
+
+ private long countIget(DexCode code, DexField field) {
+ return Arrays.stream(code.instructions)
+ .filter(instruction -> instruction instanceof Iget)
+ .map(instruction -> (Iget) instruction)
+ .filter(get -> get.getField() == field)
+ .count();
+ }
+
+ private long countSget(DexCode code, DexField field) {
+ return Arrays.stream(code.instructions)
+ .filter(instruction -> instruction instanceof Sget)
+ .map(instruction -> (Sget) instruction)
+ .filter(get -> get.getField() == field)
+ .count();
+ }
+
+ private long countIgetObject(MethodSubject method, FieldSubject field) {
+ return Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
+ .filter(instruction -> instruction instanceof IgetObject)
+ .map(instruction -> (IgetObject) instruction)
+ .filter(get -> get.getField() == field.getField().field)
+ .count();
+ }
+
+ private void check(DexInspector inspector, int mfOnBGets, int msfOnBGets) {
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+ MethodSubject mfOnA = classA.method("void", "mf", ImmutableList.of());
+ assertThat(mfOnA, isPresent());
+ MethodSubject mfWithMonitorOnA = classA.method("void", "mfWithMonitor", ImmutableList.of());
+ assertThat(mfWithMonitorOnA, isPresent());
+ MethodSubject msfOnA = classA.method("void", "msf", ImmutableList.of());
+ assertThat(msfOnA, isPresent());
+ MethodSubject mvOnA = classA.method("void", "mv", ImmutableList.of());
+ assertThat(mvOnA, isPresent());
+ MethodSubject msvOnA = classA.method("void", "msv", ImmutableList.of());
+ assertThat(msvOnA, isPresent());
+ FieldSubject fOnA = classA.field("int", "f");
+ assertThat(fOnA, isPresent());
+ FieldSubject sfOnA = classA.field("int", "sf");
+ assertThat(sfOnA, isPresent());
+ FieldSubject vOnA = classA.field("int", "v");
+ assertThat(vOnA, isPresent());
+ FieldSubject svOnA = classA.field("int", "sv");
+ assertThat(svOnA, isPresent());
+ ClassSubject classB = inspector.clazz(B.class);
+ assertThat(classB, isPresent());
+ MethodSubject mfOnB = classB.method("void", "mf", ImmutableList.of(classA.getOriginalName()));
+ assertThat(mfOnB, isPresent());
+ MethodSubject msfOnB = classB.method("void", "msf", ImmutableList.of());
+ assertThat(msfOnB, isPresent());
+ MethodSubject mvOnB = classB.method("void", "mv", ImmutableList.of(classA.getOriginalName()));
+ assertThat(mvOnB, isPresent());
+ MethodSubject msvOnB = classB.method("void", "msv", ImmutableList.of());
+ assertThat(msvOnB, isPresent());
+ // Field load of volatile fields are never eliminated.
+ assertEquals(5, countIget(mvOnA.getMethod().getCode().asDexCode(), vOnA.getField().field));
+ assertEquals(5, countSget(msvOnA.getMethod().getCode().asDexCode(), svOnA.getField().field));
+ assertEquals(5, countIget(mvOnB.getMethod().getCode().asDexCode(), vOnA.getField().field));
+ assertEquals(5, countSget(msvOnB.getMethod().getCode().asDexCode(), svOnA.getField().field));
+ // For fields on the same class both separate compilation (D8) and whole program
+ // compilation (R8) will eliminate field loads on non-volatile fields.
+ assertEquals(1, countIget(mfOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
+ assertEquals(1, countSget(msfOnA.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+ // TODO(111380066). This could be 2 in stead of 4, but right now the optimization tracks the
+ // combined set of fields for all successors, and for synchronized code all blocks have
+ // exceptional edges for ensuring monitor exit causing the active load to be invalidated for
+ // both normal and exceptional successors.
+ assertEquals(4,
+ countIget(mfWithMonitorOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
+
+ // For fields on other class both separate compilation (D8) and whole program
+ // compilation (R8) will differ in the eliminated field loads of non-volatile fields.
+ assertEquals(mfOnBGets,
+ countIget(mfOnB.getMethod().getCode().asDexCode(), fOnA.getField().field));
+ assertEquals(msfOnBGets,
+ countSget(msfOnB.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+ }
+
+ @Test
+ public void testSeparateCompilation() throws Exception {
+ DexInspector inspector =
+ new DexInspector(compileWithD8(readClasses(A.class, B.class), this::releaseMode));
+ check(inspector, 5, 5);
+ }
+
+ @Test
+ public void testWholeProgram() throws Exception {
+ DexInspector inspector =
+ new DexInspector(compileWithR8(readClasses(A.class, B.class), this::releaseMode));
+ // The reason for getting two Igets in B.mf is that the first Iget inserts a NonNull
+ // instruction which creates a new value for the remaining Igets.
+ check(inspector, 2, 1);
+ }
+
+ private void checkMixed(AndroidApp app) throws Exception{
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject classC = inspector.clazz(C.class);
+ assertThat(classC, isPresent());
+ MethodSubject totalDays = classC.method("int", "totalDays", ImmutableList.of());
+ assertThat(totalDays, isPresent());
+ MethodSubject totalDaysTimes2 = classC.method("int", "totalDaysTimes2", ImmutableList.of());
+ assertThat(totalDaysTimes2, isPresent());
+ MethodSubject totalDaysTimes3 = classC.method("int", "totalDaysTimes3", ImmutableList.of());
+ assertThat(totalDaysTimes3, isPresent());
+ FieldSubject years = classC.field("int", "years");
+ assertThat(years, isPresent());
+ FieldSubject months = classC.field("int", "months");
+ assertThat(months, isPresent());
+ FieldSubject days = classC.field("int", "days");
+ assertThat(days, isPresent());
+
+
+ for (FieldSubject field : new FieldSubject[]{years, months, days}) {
+ assertEquals(1,
+ countIget(totalDays.getMethod().getCode().asDexCode(), field.getField().field));
+ assertEquals(2,
+ countIget(totalDaysTimes2.getMethod().getCode().asDexCode(), field.getField().field));
+ assertEquals(3,
+ countIget(totalDaysTimes3.getMethod().getCode().asDexCode(), field.getField().field));
+ }
+ }
+
+ @Test
+ public void testMixedVolatileNonVolatile() throws Exception {
+ AndroidApp app = readClasses(C.class);
+ checkMixed(compileWithD8(app, this::releaseMode));
+ checkMixed(compileWithR8(app, this::releaseMode));
+ }
+
+ private void checkDaggerSingleProviderGet(AndroidApp app) throws Exception {
+ DexInspector inspector = new DexInspector(app);
+ MethodSubject get =
+ inspector.clazz(SingleCheck.class).method("java.lang.Object", "get", ImmutableList.of());
+ assertThat(get, isPresent());
+ FieldSubject instance =
+ inspector.clazz(SingleCheck.class).field("java.lang.Object", "instance");
+ assertEquals(2, countIgetObject(get, instance));
+ }
+
+ @Test
+ public void testDaggerSingleProvider() throws Exception {
+ AndroidApp app = readClasses(Provider.class, SingleCheck.class);
+ checkDaggerSingleProviderGet(compileWithD8(app, this::releaseMode));
+ checkDaggerSingleProviderGet(compileWithR8(app, this::releaseMode));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
new file mode 100644
index 0000000..772ef09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, 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.rewrite.assertions;
+
+public class ChromuimAssertionHookMock {
+ public static void assertFailureHandler(AssertionError assertion) {
+ System.out.println("Got AssertionError " + assertion);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
index cc56790..49fe85e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
@@ -16,7 +16,9 @@
}
int getX() {
+ System.out.println("1");
assert condition();
+ System.out.println("2");
return x;
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 3c724c0..6574e7b 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -4,17 +4,139 @@
package com.android.tools.r8.rewrite.assertions;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import java.util.function.Function;
import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// This ASM class visitor has been adapted from
+// https://chromium.googlesource.com/chromium/src/+/164e81fcd0828b40f5496e9025349ea728cde7f5/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java
+// See b/110887293.
+
+/**
+ * An ClassVisitor for replacing Java ASSERT statements with a function by modifying Java bytecode.
+ *
+ * We do this in two steps, first step is to enable assert.
+ * Following bytecode is generated for each class with ASSERT statements:
+ * 0: ldc #8 // class CLASSNAME
+ * 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z
+ * 5: ifne 12
+ * 8: iconst_1
+ * 9: goto 13
+ * 12: iconst_0
+ * 13: putstatic #2 // Field $assertionsDisabled:Z
+ * Replaces line #13 to the following:
+ * 13: pop
+ * Consequently, $assertionsDisabled is assigned the default value FALSE.
+ * This is done in the first if statement in overridden visitFieldInsn. We do this per per-assert.
+ *
+ * Second step is to replace assert statement with a function:
+ * The followed instructions are generated by a java assert statement:
+ * getstatic #3 // Field $assertionsDisabled:Z
+ * ifne 118 // Jump to instruction as if assertion if not enabled
+ * ...
+ * ifne 19
+ * new #4 // class java/lang/AssertionError
+ * dup
+ * ldc #5 // String (don't have this line if no assert message given)
+ * invokespecial #6 // Method java/lang/AssertionError.
+ * athrow
+ * Replace athrow with:
+ * invokestatic #7 // Method org/chromium/base/JavaExceptionReporter.assertFailureHandler
+ * goto 118
+ * JavaExceptionReporter.assertFailureHandler is a function that handles the AssertionError,
+ * 118 is the instruction to execute as if assertion if not enabled.
+ */
+class AssertionEnablerClassAdapter extends ClassVisitor {
+ AssertionEnablerClassAdapter(ClassVisitor visitor) {
+ super(Opcodes.ASM6, visitor);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name, String desc,
+ String signature, String[] exceptions) {
+ return new RewriteAssertMethodVisitor(
+ Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions));
+ }
+
+ static class RewriteAssertMethodVisitor extends MethodVisitor {
+ static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled";
+ static final String INSERT_INSTRUCTION_NAME = "assertFailureHandler";
+ static final String INSERT_INSTRUCTION_DESC =
+ Type.getMethodDescriptor(Type.VOID_TYPE, Type.getObjectType("java/lang/AssertionError"));
+ static final boolean INSERT_INSTRUCTION_ITF = false;
+
+ boolean mStartLoadingAssert;
+ Label mGotoLabel;
+
+ public RewriteAssertMethodVisitor(int api, MethodVisitor mv) {
+ super(api, mv);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
+ super.visitInsn(Opcodes.POP); // enable assert
+ } else if (opcode == Opcodes.GETSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
+ mStartLoadingAssert = true;
+ super.visitFieldInsn(opcode, owner, name, desc);
+ } else {
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ if (mStartLoadingAssert && opcode == Opcodes.IFNE && mGotoLabel == null) {
+ mGotoLabel = label;
+ }
+ super.visitJumpInsn(opcode, label);
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if (!mStartLoadingAssert || opcode != Opcodes.ATHROW) {
+ super.visitInsn(opcode);
+ } else {
+ super.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ ChromuimAssertionHookMock.class.getCanonicalName().replace('.', '/'),
+ INSERT_INSTRUCTION_NAME,
+ INSERT_INSTRUCTION_DESC,
+ INSERT_INSTRUCTION_ITF);
+ super.visitJumpInsn(Opcodes.GOTO, mGotoLabel);
+ mStartLoadingAssert = false;
+ mGotoLabel = null;
+ }
+ }
+ }
+}
public class RemoveAssertionsTest extends TestBase {
@@ -38,4 +160,75 @@
clazz.method(new MethodSignature(Constants.CLASS_INITIALIZER_NAME, "void", new String[]{}));
assertTrue(!clinit.isPresent());
}
+
+ private Path buildTestToCf(Consumer<InternalOptions> consumer) throws Exception {
+ Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(readClasses(ClassWithAssertions.class))
+ .setMode(CompilationMode.DEBUG)
+ .setOutput(outputJar, OutputMode.ClassFile)
+ .build();
+ ToolHelper.runR8(command, consumer);
+ return outputJar;
+ }
+
+ @Test
+ public void testCfOutput() throws Exception {
+ String main = ClassWithAssertions.class.getCanonicalName();
+ ProcessResult result;
+ // Assertion is hit.
+ result = ToolHelper.runJava(buildTestToCf(options -> {}), "-ea", main, "0");
+ assertEquals(1, result.exitCode);
+ assertEquals("1\n".replace("\n", System.lineSeparator()), result.stdout);
+ // Assertion is not hit.
+ result = ToolHelper.runJava(buildTestToCf(options -> {}), "-ea", main, "1");
+ assertEquals(0, result.exitCode);
+ assertEquals("1\n2\n".replace("\n", System.lineSeparator()), result.stdout);
+ // Assertion is hit, but removed.
+ result = ToolHelper.runJava(
+ buildTestToCf(
+ options -> options.disableAssertions = true), "-ea", main, "0");
+ assertEquals(0, result.exitCode);
+ assertEquals("1\n2\n".replace("\n", System.lineSeparator()), result.stdout);
+ }
+
+ private byte[] identity(byte[] classBytes) {
+ return classBytes;
+ }
+
+ private byte[] chromiumAssertionEnabler(byte[] classBytes) {
+ ClassWriter writer = new ClassWriter(0);
+ new ClassReader(classBytes).accept(new AssertionEnablerClassAdapter(writer), 0);
+ return writer.toByteArray();
+ }
+
+ private AndroidApp runRegress110887293(Function<byte[], byte[]> rewriter) throws Exception {
+ return ToolHelper.runR8(
+ R8Command.builder()
+ .addClassProgramData(
+ rewriter.apply(ToolHelper.getClassAsBytes(ClassWithAssertions.class)),
+ Origin.unknown())
+ .addClassProgramData(
+ ToolHelper.getClassAsBytes(ChromuimAssertionHookMock.class), Origin.unknown())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setMode(CompilationMode.DEBUG)
+ .build());
+ }
+
+ @Test
+ public void regress110887293() throws Exception {
+ AndroidApp app;
+ // Assertions removed for default assertion code.
+ app = runRegress110887293(this::identity);
+ assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "0"));
+ assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "1"));
+ // Assertions not removed when default assertion code is not present.
+ app = runRegress110887293(this::chromiumAssertionEnabler);
+ assertEquals(
+ "1\nGot AssertionError java.lang.AssertionError\n2\n".replace("\n", System.lineSeparator()),
+ runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "0"));
+ assertEquals(
+ "1\n2\n".replace("\n", System.lineSeparator()),
+ runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "1"));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
index 5270cd4..86c4242 100644
--- a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
@@ -37,28 +36,23 @@
builder.addStaticField("stringField", "Ljava/lang/String;");
builder.addStaticField("testField", "LTest;");
- boolean isDalvik = ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST);
- String additionalConstZero = isDalvik ? "const v0, 0" : "";
- String additionalConstZeroWide = isDalvik ? "const-wide v0, 0" : "";
-
builder.addStaticMethod("void", "test", ImmutableList.of(),
2,
- "const v0, 0",
+ "const v0, 0", // single non-float typed zero (ie, int type)
"sput-boolean v0, LTest;->booleanField:Z",
"sput-byte v0, LTest;->byteField:B",
+ "sput-char v0, LTest;->charField:C",
"sput-short v0, LTest;->shortField:S",
"sput v0, LTest;->intField:I",
- // Dalvik 4.x. does not require a new const 0 here.
+ "const v0, 0", // float typed zero
"sput v0, LTest;->floatField:F",
- additionalConstZero, // Required for Dalvik 4.x.
- "sput-char v0, LTest;->charField:C",
- "const v0, 0",
+ "const v0, 0", // reference typed null
"sput-object v0, LTest;->objectField:Ljava/lang/Object;",
"sput-object v0, LTest;->stringField:Ljava/lang/String;",
"sput-object v0, LTest;->testField:LTest;",
- "const-wide v0, 0",
+ "const-wide v0, 0", // wide typed long
"sput-wide v0, LTest;->longField:J",
- additionalConstZeroWide, // Required for Dalvik 4.x.
+ "const-wide v0, 0", // wide typed double
"sput-wide v0, LTest;->doubleField:D",
"return-void");
@@ -103,28 +97,23 @@
builder.addInstanceField("stringField", "Ljava/lang/String;");
builder.addInstanceField("testField", "LTest;");
- boolean isDalvik = ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST);
- String additionalConstZero = isDalvik ? "const v0, 0" : "";
- String additionalConstZeroWide = isDalvik ? "const-wide v0, 0" : "";
-
builder.addInstanceMethod("void", "test", ImmutableList.of(),
2,
"const v0, 0",
"iput-boolean v0, p0, LTest;->booleanField:Z",
"iput-byte v0, p0, LTest;->byteField:B",
+ "iput-char v0, p0, LTest;->charField:C",
"iput-short v0, p0, LTest;->shortField:S",
"iput v0, p0, LTest;->intField:I",
- // Dalvik 4.x. does not require a new const 0 here.
+ "const v0, 0",
"iput v0, p0, LTest;->floatField:F",
- additionalConstZero, // Required for Dalvik 4.x.
- "iput-char v0, p0, LTest;->charField:C",
"const v0, 0",
"iput-object v0, p0, LTest;->objectField:Ljava/lang/Object;",
"iput-object v0, p0, LTest;->stringField:Ljava/lang/String;",
"iput-object v0, p0, LTest;->testField:LTest;",
"const-wide v0, 0",
"iput-wide v0, p0, LTest;->longField:J",
- additionalConstZeroWide, // Required for Dalvik 4.x.
+ "const-wide v0, 0",
"iput-wide v0, p0, LTest;->doubleField:D",
"return-void");
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index eba3be8..059a24a 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -54,6 +54,7 @@
import com.android.tools.r8.code.SputWide;
import com.android.tools.r8.code.Throw;
import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexAnnotationSet;
@@ -81,6 +82,7 @@
import com.android.tools.r8.naming.signature.GenericSignatureAction;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.DexInspector.FieldAccessInstructionSubject;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@@ -106,8 +108,6 @@
private final ClassNameMapper mapping;
private final BiMap<String, String> originalToObfuscatedMapping;
- private final InstructionSubjectFactory factory = new InstructionSubjectFactory();
-
public static MethodSignature MAIN =
new MethodSignature("main", "void", new String[]{"java.lang.String[]"});
@@ -304,18 +304,18 @@
return obfuscatedType;
}
- public abstract class Subject {
+ public abstract static class Subject {
public abstract boolean isPresent();
public abstract boolean isRenamed();
}
- public abstract class AnnotationSubject extends Subject {
+ public abstract static class AnnotationSubject extends Subject {
public abstract DexEncodedAnnotation getAnnotation();
}
- public class FoundAnnotationSubject extends AnnotationSubject {
+ public static class FoundAnnotationSubject extends AnnotationSubject {
private final DexAnnotation annotation;
@@ -339,7 +339,7 @@
}
}
- public class AbsentAnnotationSubject extends AnnotationSubject {
+ public static class AbsentAnnotationSubject extends AnnotationSubject {
@Override
public boolean isPresent() {
@@ -357,8 +357,7 @@
}
}
-
- public abstract class ClassSubject extends Subject {
+ public abstract static class ClassSubject extends Subject {
public abstract void forAllMethods(Consumer<FoundMethodSubject> inspection);
@@ -423,6 +422,8 @@
public abstract boolean isAnonymousClass();
+ public abstract boolean isSynthesizedJavaLambdaClass();
+
public abstract String getOriginalSignatureAttribute();
public abstract String getFinalSignatureAttribute();
@@ -514,6 +515,11 @@
}
@Override
+ public boolean isSynthesizedJavaLambdaClass() {
+ return false;
+ }
+
+ @Override
public String getOriginalSignatureAttribute() {
return null;
}
@@ -709,6 +715,11 @@
}
@Override
+ public boolean isSynthesizedJavaLambdaClass() {
+ return dexClass.type.getName().contains("$Lambda$");
+ }
+
+ @Override
public String getOriginalSignatureAttribute() {
return DexInspector.this.getOriginalSignatureAttribute(
dexClass.annotations, GenericSignatureParser::parseClassSignature);
@@ -725,7 +736,7 @@
}
}
- public abstract class MemberSubject extends Subject {
+ public abstract static class MemberSubject extends Subject {
public abstract boolean isPublic();
@@ -748,7 +759,7 @@
}
}
- public abstract class MethodSubject extends MemberSubject {
+ public abstract static class MethodSubject extends MemberSubject {
public abstract boolean isAbstract();
@@ -958,7 +969,7 @@
@Override
public Iterator<InstructionSubject> iterateInstructions() {
- return new InstructionIterator(this);
+ return createInstructionIterator(this);
}
@Override
@@ -973,7 +984,7 @@
}
}
- public abstract class FieldSubject extends MemberSubject {
+ public abstract static class FieldSubject extends MemberSubject {
public abstract boolean hasExplicitStaticValue();
public abstract DexEncodedField getField();
@@ -987,7 +998,7 @@
public abstract String getFinalSignatureAttribute();
}
- public class AbsentFieldSubject extends FieldSubject {
+ public static class AbsentFieldSubject extends FieldSubject {
@Override
public boolean isPublic() {
@@ -1180,91 +1191,139 @@
}
}
- private class InstructionSubjectFactory {
+ public interface InstructionSubject {
+ boolean isFieldAccess();
- InstructionSubject create(Instruction instruction) {
- if (isInvoke(instruction)) {
- return new InvokeInstructionSubject(this, instruction);
- } else if (isFieldAccess(instruction)) {
- return new FieldAccessInstructionSubject(this, instruction);
- } else {
- return new InstructionSubject(this, instruction);
- }
+ boolean isInvokeVirtual();
+
+ boolean isInvokeInterface();
+
+ boolean isInvokeDirect();
+
+ boolean isInvokeSuper();
+
+ boolean isInvokeStatic();
+
+ boolean isNop();
+
+ boolean isConstString();
+
+ boolean isConstString(String value);
+
+ boolean isGoto();
+
+ boolean isIfNez();
+
+ boolean isIfEqz();
+
+ boolean isReturnVoid();
+
+ boolean isThrow();
+
+ default boolean isInvoke() {
+ return isInvokeVirtual()
+ || isInvokeInterface()
+ || isInvokeDirect()
+ || isInvokeSuper()
+ || isInvokeStatic();
+ }
+ }
+
+ public InstructionSubject createInstructionSubject(Instruction instruction) {
+ DexInstructionSubject dexInst = new DexInstructionSubject(instruction);
+ if (dexInst.isInvoke()) {
+ return new InvokeDexInstructionSubject(instruction);
+ } else if (dexInst.isFieldAccess()) {
+ return new FieldAccessDexInstructionSubject(instruction);
+ } else {
+ return dexInst;
+ }
+ }
+
+ public class DexInstructionSubject implements InstructionSubject {
+ protected Instruction instruction;
+
+ public DexInstructionSubject(Instruction instruction) {
+ this.instruction = instruction;
}
- boolean isInvoke(Instruction instruction) {
- return isInvokeVirtual(instruction)
- || isInvokeInterface(instruction)
- || isInvokeDirect(instruction)
- || isInvokeSuper(instruction)
- || isInvokeStatic(instruction);
+ @Override
+ public boolean isFieldAccess() {
+ return isInstanceGet() || isInstancePut() || isStaticGet() || isStaticSet();
}
- boolean isInvokeVirtual(Instruction instruction) {
+ @Override
+ public boolean isInvokeVirtual() {
return instruction instanceof InvokeVirtual || instruction instanceof InvokeVirtualRange;
}
- boolean isInvokeInterface(Instruction instruction) {
+ @Override
+ public boolean isInvokeInterface() {
return instruction instanceof InvokeInterface || instruction instanceof InvokeInterfaceRange;
}
- boolean isInvokeDirect(Instruction instruction) {
+ @Override
+ public boolean isInvokeDirect() {
return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
}
- boolean isInvokeSuper(Instruction instruction) {
+ @Override
+ public boolean isInvokeSuper() {
return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
}
- boolean isInvokeStatic(Instruction instruction) {
+ @Override
+ public boolean isInvokeStatic() {
return instruction instanceof InvokeStatic || instruction instanceof InvokeStaticRange;
}
- boolean isNop(Instruction instruction) {
+ @Override
+ public boolean isNop() {
return instruction instanceof Nop;
}
- boolean isGoto(Instruction instruction) {
- return instruction instanceof Goto;
- }
-
- boolean isReturnVoid(Instruction instruction) {
- return instruction instanceof ReturnVoid;
- }
-
- boolean isConst4(Instruction instruction) {
- return instruction instanceof Const4;
- }
-
- boolean isThrow(Instruction instruction) {
- return instruction instanceof Throw;
- }
-
- boolean isConstString(Instruction instruction) {
+ @Override
+ public boolean isConstString() {
return instruction instanceof ConstString;
}
- boolean isConstString(Instruction instruction, String value) {
+ @Override
+ public boolean isConstString(String value) {
return instruction instanceof ConstString
&& ((ConstString) instruction).BBBB.toSourceString().equals(value);
}
- boolean isIfNez(Instruction instruction) {
+ @Override
+ public boolean isGoto() {
+
+ return instruction instanceof Goto;
+ }
+
+ @Override
+ public boolean isIfNez() {
return instruction instanceof IfNez;
}
- boolean isIfEqz(Instruction instruction) {
+ @Override
+ public boolean isIfEqz() {
return instruction instanceof IfEqz;
}
- boolean isFieldAccess(Instruction instruction) {
- return isInstanceGet(instruction)
- || isInstancePut(instruction)
- || isStaticGet(instruction)
- || isStaticSet(instruction);
+ @Override
+ public boolean isReturnVoid() {
+ return instruction instanceof ReturnVoid;
}
- boolean isInstanceGet(Instruction instruction) {
+ @Override
+ public boolean isThrow() {
+ return instruction instanceof Throw;
+ }
+
+ public boolean isConst4() {
+ return instruction instanceof Const4;
+ }
+
+ public boolean isInstanceGet() {
return instruction instanceof Iget
|| instruction instanceof IgetBoolean
|| instruction instanceof IgetByte
@@ -1274,7 +1333,7 @@
|| instruction instanceof IgetObject;
}
- boolean isInstancePut(Instruction instruction) {
+ public boolean isInstancePut() {
return instruction instanceof Iput
|| instruction instanceof IputBoolean
|| instruction instanceof IputByte
@@ -1284,7 +1343,7 @@
|| instruction instanceof IputObject;
}
- boolean isStaticGet(Instruction instruction) {
+ public boolean isStaticGet() {
return instruction instanceof Sget
|| instruction instanceof SgetBoolean
|| instruction instanceof SgetByte
@@ -1294,7 +1353,7 @@
|| instruction instanceof SgetObject;
}
- boolean isStaticSet(Instruction instruction) {
+ public boolean isStaticSet() {
return instruction instanceof Sput
|| instruction instanceof SputBoolean
|| instruction instanceof SputByte
@@ -1305,89 +1364,21 @@
}
}
- public class InstructionSubject {
+ public interface InvokeInstructionSubject extends InstructionSubject {
+ TypeSubject holder();
- protected final InstructionSubjectFactory factory;
- protected final Instruction instruction;
-
- protected InstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
- this.factory = factory;
- this.instruction = instruction;
- }
-
- public boolean isInvoke() {
- return factory.isInvoke(instruction);
- }
-
- public boolean isFieldAccess() {
- return factory.isFieldAccess(instruction);
- }
-
- public boolean isInvokeVirtual() {
- return factory.isInvokeVirtual(instruction);
- }
-
- public boolean isInvokeInterface() {
- return factory.isInvokeInterface(instruction);
- }
-
- public boolean isInvokeDirect() {
- return factory.isInvokeDirect(instruction);
- }
-
- public boolean isInvokeSuper() {
- return factory.isInvokeSuper(instruction);
- }
-
- public boolean isInvokeStatic() {
- return factory.isInvokeStatic(instruction);
- }
-
- boolean isFieldAccess(Instruction instruction) {
- return factory.isFieldAccess(instruction);
- }
-
- public boolean isNop() {
- return factory.isNop(instruction);
- }
-
- public boolean isConstString() {
- return factory.isConstString(instruction);
- }
-
- public boolean isConstString(String value) {
- return factory.isConstString(instruction, value);
- }
-
- public boolean isGoto() {
- return factory.isGoto(instruction);
- }
-
- public boolean isIfNez() {
- return factory.isIfNez(instruction);
- }
-
- public boolean isIfEqz() {
- return factory.isIfEqz(instruction);
- }
-
- public boolean isReturnVoid() {
- return factory.isReturnVoid(instruction);
- }
-
- public boolean isConst4() {
- return factory.isConst4(instruction);
- }
-
- public boolean isThrow() {
- return factory.isThrow(instruction);
- }
+ DexMethod invokedMethod();
}
- public class InvokeInstructionSubject extends InstructionSubject {
+ public interface FieldAccessInstructionSubject extends InstructionSubject {
+ TypeSubject holder();
+ }
- InvokeInstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
- super(factory, instruction);
+ public class InvokeDexInstructionSubject extends DexInstructionSubject
+ implements InvokeInstructionSubject {
+
+ public InvokeDexInstructionSubject(Instruction instruction) {
+ super(instruction);
assert isInvoke();
}
@@ -1396,156 +1387,41 @@
}
public DexMethod invokedMethod() {
- if (instruction instanceof InvokeVirtual) {
- return ((InvokeVirtual) instruction).getMethod();
- }
- if (instruction instanceof InvokeVirtualRange) {
- return ((InvokeVirtualRange) instruction).getMethod();
- }
- if (instruction instanceof InvokeInterface) {
- return ((InvokeInterface) instruction).getMethod();
- }
- if (instruction instanceof InvokeInterfaceRange) {
- return ((InvokeInterfaceRange) instruction).getMethod();
- }
- if (instruction instanceof InvokeDirect) {
- return ((InvokeDirect) instruction).getMethod();
- }
- if (instruction instanceof InvokeDirectRange) {
- return ((InvokeDirectRange) instruction).getMethod();
- }
- if (instruction instanceof InvokeSuper) {
- return ((InvokeSuper) instruction).getMethod();
- }
- if (instruction instanceof InvokeSuperRange) {
- return ((InvokeSuperRange) instruction).getMethod();
- }
- if (instruction instanceof InvokeDirect) {
- return ((InvokeDirect) instruction).getMethod();
- }
- if (instruction instanceof InvokeDirectRange) {
- return ((InvokeDirectRange) instruction).getMethod();
- }
- if (instruction instanceof InvokeStatic) {
- return ((InvokeStatic) instruction).getMethod();
- }
- if (instruction instanceof InvokeStaticRange) {
- return ((InvokeStaticRange) instruction).getMethod();
- }
- assert false;
- return null;
+ return instruction.getMethod();
}
}
- public class FieldAccessInstructionSubject extends InstructionSubject {
+ public class FieldAccessDexInstructionSubject extends DexInstructionSubject
+ implements FieldAccessInstructionSubject {
- FieldAccessInstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
- super(factory, instruction);
+ public FieldAccessDexInstructionSubject(Instruction instruction) {
+ super(instruction);
assert isFieldAccess();
}
public TypeSubject holder() {
- return new TypeSubject(accessedField().getHolder());
- }
-
- public DexField accessedField() {
- if (instruction instanceof Iget) {
- return ((Iget) instruction).getField();
- }
- if (instruction instanceof IgetBoolean) {
- return ((IgetBoolean) instruction).getField();
- }
- if (instruction instanceof IgetByte) {
- return ((IgetByte) instruction).getField();
- }
- if (instruction instanceof IgetShort) {
- return ((IgetShort) instruction).getField();
- }
- if (instruction instanceof IgetChar) {
- return ((IgetChar) instruction).getField();
- }
- if (instruction instanceof IgetWide) {
- return ((IgetWide) instruction).getField();
- }
- if (instruction instanceof IgetObject) {
- return ((IgetObject) instruction).getField();
- }
- if (instruction instanceof Iput) {
- return ((Iput) instruction).getField();
- }
- if (instruction instanceof IputBoolean) {
- return ((IputBoolean) instruction).getField();
- }
- if (instruction instanceof IputByte) {
- return ((IputByte) instruction).getField();
- }
- if (instruction instanceof IputShort) {
- return ((IputShort) instruction).getField();
- }
- if (instruction instanceof IputChar) {
- return ((IputChar) instruction).getField();
- }
- if (instruction instanceof IputWide) {
- return ((IputWide) instruction).getField();
- }
- if (instruction instanceof IputObject) {
- return ((IputObject) instruction).getField();
- }
- if (instruction instanceof Sget) {
- return ((Sget) instruction).getField();
- }
- if (instruction instanceof SgetBoolean) {
- return ((SgetBoolean) instruction).getField();
- }
- if (instruction instanceof SgetByte) {
- return ((SgetByte) instruction).getField();
- }
- if (instruction instanceof SgetShort) {
- return ((SgetShort) instruction).getField();
- }
- if (instruction instanceof SgetChar) {
- return ((SgetChar) instruction).getField();
- }
- if (instruction instanceof SgetWide) {
- return ((SgetWide) instruction).getField();
- }
- if (instruction instanceof SgetObject) {
- return ((SgetObject) instruction).getField();
- }
- if (instruction instanceof Sput) {
- return ((Sput) instruction).getField();
- }
- if (instruction instanceof SputBoolean) {
- return ((SputBoolean) instruction).getField();
- }
- if (instruction instanceof SputByte) {
- return ((SputByte) instruction).getField();
- }
- if (instruction instanceof SputShort) {
- return ((SputShort) instruction).getField();
- }
- if (instruction instanceof SputChar) {
- return ((SputChar) instruction).getField();
- }
- if (instruction instanceof SputWide) {
- return ((SputWide) instruction).getField();
- }
- if (instruction instanceof SputObject) {
- return ((SputObject) instruction).getField();
- }
- assert false;
- return null;
+ return new TypeSubject(instruction.getField().getHolder());
}
}
- private class InstructionIterator implements Iterator<InstructionSubject> {
+ private interface InstructionIterator extends Iterator<InstructionSubject> {}
+
+ private InstructionIterator createInstructionIterator(MethodSubject method) {
+ Code code = method.getMethod().getCode();
+ assert code != null && code.isDexCode();
+ return new DexInstructionIterator(method);
+ }
+
+ private class DexInstructionIterator implements InstructionIterator {
private final DexCode code;
private int index;
- InstructionIterator(MethodSubject method) {
+ DexInstructionIterator(MethodSubject method) {
assert method.isPresent();
- this.code = method.getMethod().getCode().asDexCode();
+ Code code = method.getMethod().getCode();
+ assert code != null && code.isDexCode();
+ this.code = code.asDexCode();
this.index = 0;
}
@@ -1559,7 +1435,7 @@
if (index == code.instructions.length) {
throw new NoSuchElementException();
}
- return factory.create(code.instructions[index++]);
+ return createInstructionSubject(code.instructions[index++]);
}
}
@@ -1570,7 +1446,7 @@
private InstructionSubject pendingNext = null;
FilteredInstructionIterator(MethodSubject method, Predicate<InstructionSubject> predicate) {
- this.iterator = new InstructionIterator(method);
+ this.iterator = createInstructionIterator(method);
this.predicate = predicate;
hasNext();
}
diff --git a/tools/proguard.py b/tools/proguard.py
index efcf560..640094f 100755
--- a/tools/proguard.py
+++ b/tools/proguard.py
@@ -15,14 +15,14 @@
PROGUARD_JAR = os.path.join(utils.REPO_ROOT, 'third_party', 'proguard',
'proguard_internal_159423826', 'ProGuard_deploy.jar')
-def run(args, track_memory_file = None):
+def run(args, track_memory_file = None, stdout=None, stderr=None):
cmd = []
if track_memory_file:
cmd.extend(['tools/track_memory.sh', track_memory_file])
cmd.extend(['java', '-jar', PROGUARD_JAR])
cmd.extend(args)
utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ subprocess.call(cmd, stdout=stdout, stderr=stderr)
def Main():
run(sys.argv[1:])
diff --git a/tools/run_proguard_dx_on_app.py b/tools/run_proguard_dx_on_app.py
index a953229..4624f26 100755
--- a/tools/run_proguard_dx_on_app.py
+++ b/tools/run_proguard_dx_on_app.py
@@ -115,7 +115,10 @@
track_memory_file = None
if options.print_memoryuse:
track_memory_file = join(temp, utils.MEMORY_USE_TMP_FILE)
- proguard.run(args, track_memory_file = track_memory_file)
+ proguard.run(
+ args,
+ track_memory_file = track_memory_file,
+ stdout=open(os.devnull, 'w'))
if options.print_memoryuse:
proguard_memoryuse = utils.grep_memoryuse(track_memory_file)