Merge "Remove trivial phis for all string new-instance values."
diff --git a/build.gradle b/build.gradle
index f379bbf..8ea2b3f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1239,6 +1239,18 @@
}
test {
+ if (project.hasProperty('generate_golden_files_to')) {
+ systemProperty 'generate_golden_files_to', project.property('generate_golden_files_to')
+ assert project.hasProperty('HEAD_sha1')
+ systemProperty 'test_git_HEAD_sha1', project.property('HEAD_sha1')
+ }
+
+ if (project.hasProperty('use_golden_files_in')) {
+ systemProperty 'use_golden_files_in', project.property('use_golden_files_in')
+ assert project.hasProperty('HEAD_sha1')
+ systemProperty 'test_git_HEAD_sha1', project.property('HEAD_sha1')
+ }
+
dependsOn getJarsFromSupportLibs
testLogging.exceptionFormat = 'full'
if (project.hasProperty('print_test_stdout')) {
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 4f64416..9b1599f 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -161,6 +161,7 @@
InternalOptions getInternalOptions() {
InternalOptions internal = new InternalOptions();
internal.useSmaliSyntax = useSmali;
+ internal.enableCfFrontend = true;
return internal;
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index f907ffb..66664e6 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -59,6 +59,8 @@
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.DescriptorUtils;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.IntList;
@@ -80,15 +82,18 @@
private final Map<CfLabel, String> labels;
private final StringBuilder builder = new StringBuilder();
+ private final ClassNameMapper mapper;
/** Entry for printing single instructions without global knowledge (eg, label numbers). */
public CfPrinter() {
indent = "";
labels = null;
+ mapper = null;
}
/** Entry for printing a complete code object. */
- public CfPrinter(CfCode code) {
+ public CfPrinter(CfCode code, ClassNameMapper mapper) {
+ this.mapper = mapper;
indent = " ";
labels = new HashMap<>();
int nextLabelNumber = 0;
@@ -284,12 +289,13 @@
}
public void print(CfFrame frame) {
- StringBuilder builder = new StringBuilder("frame: [");
+ indent();
+ builder.append("; frame: [");
{
String separator = "";
for (Entry<FrameType> entry : frame.getLocals().int2ReferenceEntrySet()) {
builder.append(separator).append(entry.getIntKey()).append(':');
- print(entry.getValue(), builder);
+ print(entry.getValue());
separator = ", ";
}
}
@@ -298,17 +304,18 @@
String separator = "";
for (FrameType element : frame.getStack()) {
builder.append(separator);
- print(element, builder);
+ print(element);
separator = ", ";
}
}
builder.append(']');
- comment(builder.toString());
}
- private void print(FrameType type, StringBuilder builder) {
+ private void print(FrameType type) {
if (type.isUninitializedNew()) {
builder.append("uninitialized ").append(getLabel(type.getUninitializedLabel()));
+ } else if (type.isInitialized()) {
+ appendType(type.getInitializedType());
} else {
builder.append(type.toString());
}
@@ -566,19 +573,50 @@
}
private void appendDescriptor(DexType type) {
+ if (mapper != null) {
+ builder.append(DescriptorUtils.javaTypeToDescriptor(mapper.originalNameOf(type)));
+ return;
+ }
builder.append(type.toDescriptorString());
}
+ private void appendType(DexType type) {
+ if (type.isArrayType() || type.isClassType()) {
+ appendClass(type);
+ } else {
+ builder.append(type);
+ }
+ }
+
private void appendClass(DexType type) {
- builder.append(type.getInternalName());
+ assert type.isArrayType() || type.isClassType();
+ if (mapper == null) {
+ builder.append(type.getInternalName());
+ } else if (type == DexItemFactory.nullValueType) {
+ builder.append("NULL");
+ } else {
+ builder.append(
+ DescriptorUtils.descriptorToInternalName(
+ DescriptorUtils.javaTypeToDescriptor(mapper.originalNameOf(type))));
+ }
}
private void appendField(DexField field) {
+ if (mapper != null) {
+ builder.append(mapper.originalSignatureOf(field).toString());
+ return;
+ }
appendClass(field.getHolder());
builder.append('/').append(field.name);
}
private void appendMethod(DexMethod method) {
+ if (mapper != null) {
+ MethodSignature signature = (MethodSignature) mapper.originalSignatureOf(method);
+ builder.append(mapper.originalNameOf(method.holder)).append('.');
+ builder.append(signature.name).append(signature.toDescriptor());
+ return;
+ }
builder.append(method.qualifiedName());
builder.append(method.proto.toDescriptorString());
}
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 63a2958..89abadd 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -79,7 +79,7 @@
void writeMethod(DexEncodedMethod method, PrintStream ps) {
ClassNameMapper naming = application.getProguardMap();
String methodName = naming != null
- ? naming.originalSignatureOf(method.method).toString()
+ ? naming.originalSignatureOf(method.method).name
: method.method.name.toString();
ps.println("#");
ps.println("# Method: '" + methodName + "':");
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 1fd4a73..ffa34d1 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -251,11 +251,11 @@
@Override
public String toString() {
- return new CfPrinter(this).toString();
+ return new CfPrinter(this, null).toString();
}
@Override
public String toString(DexEncodedMethod method, ClassNameMapper naming) {
- return null;
+ return new CfPrinter(this, naming).toString();
}
}
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 9f97f88..831edbe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -43,12 +43,9 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
import java.util.function.Consumer;
public class DexEncodedMethod extends KeyedDexItem<DexMethod> implements ResolutionResult {
@@ -582,6 +579,14 @@
return m1.method.slowCompareTo(m2.method);
}
+ public static class ClassInlinerEligibility {
+ public final boolean returnsReceiver;
+
+ public ClassInlinerEligibility(boolean returnsReceiver) {
+ this.returnsReceiver = returnsReceiver;
+ }
+ }
+
public static class OptimizationInfo {
private int returnedArgument = -1;
@@ -593,8 +598,9 @@
private boolean useIdentifierNameString = false;
private boolean checksNullReceiverBeforeAnySideEffect = false;
private boolean triggersClassInitBeforeAnySideEffect = false;
- private Set<DexField> receiverOnlyUsedForReadingFields = null;
- private Map<DexField, Integer> onlyInitializesFieldsWithNoOtherSideEffects = null;
+ // Stores information about instance methods and constructors for
+ // class inliner, null value indicates that the method is not eligible.
+ private ClassInlinerEligibility classInlinerEligibility = null;
private OptimizationInfo() {
// Intentionally left empty.
@@ -631,13 +637,12 @@
return returnsConstant;
}
- public boolean isReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
- return receiverOnlyUsedForReadingFields != null &&
- fields.containsAll(receiverOnlyUsedForReadingFields);
+ private void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
+ this.classInlinerEligibility = eligibility;
}
- public Map<DexField, Integer> onlyInitializesFieldsWithNoOtherSideEffects() {
- return onlyInitializesFieldsWithNoOtherSideEffects;
+ public ClassInlinerEligibility getClassInlinerEligibility() {
+ return this.classInlinerEligibility;
}
public long getReturnedConstant() {
@@ -681,19 +686,6 @@
returnedConstant = value;
}
- private void markReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
- receiverOnlyUsedForReadingFields = fields;
- }
-
- private void markOnlyInitializesFieldsWithNoOtherSideEffects(Map<DexField, Integer> mapping) {
- if (mapping == null) {
- onlyInitializesFieldsWithNoOtherSideEffects = null;
- } else {
- onlyInitializesFieldsWithNoOtherSideEffects = mapping.isEmpty()
- ? Collections.emptyMap() : ImmutableMap.copyOf(mapping);
- }
- }
-
private void markForceInline() {
forceInline = true;
}
@@ -751,13 +743,8 @@
ensureMutableOI().markReturnsConstant(value);
}
- synchronized public void markReceiverOnlyUsedForReadingFields(Set<DexField> fields) {
- ensureMutableOI().markReceiverOnlyUsedForReadingFields(fields);
- }
-
- synchronized public void markOnlyInitializesFieldsWithNoOtherSideEffects(
- Map<DexField, Integer> mapping) {
- ensureMutableOI().markOnlyInitializesFieldsWithNoOtherSideEffects(mapping);
+ synchronized public void setClassInlinerEligibility(ClassInlinerEligibility eligibility) {
+ ensureMutableOI().setClassInlinerEligibility(eligibility);
}
synchronized public void markForceInline() {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 8ecc1ab..454ed29 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -203,7 +203,7 @@
@Override
public String toString(DexEncodedMethod method, ClassNameMapper naming) {
- return null;
+ return asCfCode().toString(method, naming);
}
private static class ClassCodeVisitor extends ClassVisitor {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 186839b..51850a0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -76,6 +76,10 @@
}
public void addOperands(List<Value> operands) {
+ addOperands(operands, true);
+ }
+
+ public void addOperands(List<Value> operands, boolean removeTrivialPhi) {
// Phi operands are only filled in once to complete the phi. Some phis are incomplete for a
// period of time to break cycles. When the cycle has been resolved they are completed
// exactly once by adding the operands.
@@ -91,7 +95,9 @@
if (!canBeNull) {
markNeverNull();
}
- removeTrivialPhi();
+ if (removeTrivialPhi) {
+ removeTrivialPhi();
+ }
}
public void addDebugValue(Value value) {
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 306d7a1..81dc436 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
@@ -169,7 +169,8 @@
}
this.classInliner =
(options.enableClassInlining && options.enableInlining && inliner != null)
- ? new ClassInliner(appInfo.dexItemFactory) : null;
+ ? new ClassInliner(appInfo.dexItemFactory, options.classInliningInstructionLimit)
+ : null;
}
/**
@@ -756,10 +757,7 @@
}
// Analysis must be done after method is rewritten by logArgumentTypes()
- codeRewriter.identifyReceiverOnlyUsedForReadingFields(method, code, feedback);
- if (method.isInstanceInitializer()) {
- codeRewriter.identifyOnlyInitializesFieldsWithNoOtherSideEffects(method, code, feedback);
- }
+ codeRewriter.identifyClassInlinerEligibility(method, code, feedback);
printMethod(code, "Optimized IR (SSA)");
finalizeIR(method, code, feedback);
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 26ca6b2..16a77c8 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
@@ -5,10 +5,8 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import java.util.Map;
-import java.util.Set;
public interface OptimizationFeedback {
void methodReturnsArgument(DexEncodedMethod method, int argument);
@@ -18,7 +16,5 @@
void markProcessed(DexEncodedMethod method, Constraint state);
void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
- void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields);
- void markOnlyInitializesFieldsWithNoOtherSideEffects(
- DexEncodedMethod method, Map<DexField, Integer> mapping);
+ void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
}
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 645df64..7303b6b 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
@@ -5,10 +5,8 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import java.util.Map;
-import java.util.Set;
public class OptimizationFeedbackDirect implements OptimizationFeedback {
@@ -48,13 +46,8 @@
}
@Override
- public void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields) {
- method.markReceiverOnlyUsedForReadingFields(fields);
- }
-
- @Override
- public void markOnlyInitializesFieldsWithNoOtherSideEffects(DexEncodedMethod method,
- Map<DexField, Integer> mapping) {
- method.markOnlyInitializesFieldsWithNoOtherSideEffects(mapping);
+ public void setClassInlinerEligibility(
+ DexEncodedMethod method, ClassInlinerEligibility eligibility) {
+ method.setClassInlinerEligibility(eligibility);
}
}
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 97fe052..6915a2f 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
@@ -5,10 +5,8 @@
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
-import java.util.Map;
-import java.util.Set;
public class OptimizationFeedbackIgnore implements OptimizationFeedback {
@@ -34,11 +32,7 @@
public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
@Override
- public void markReceiverOnlyUsedForReadingFields(DexEncodedMethod method, Set<DexField> fields) {
- }
-
- @Override
- public void markOnlyInitializesFieldsWithNoOtherSideEffects(DexEncodedMethod method,
- Map<DexField, Integer> mapping) {
+ public void setClassInlinerEligibility(
+ DexEncodedMethod method, ClassInlinerEligibility eligibility) {
}
}
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 6ab4845..bd3442c 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
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -47,8 +48,6 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.If.Type;
-import com.android.tools.r8.ir.code.InstanceGet;
-import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -740,120 +739,72 @@
}
}
- public void identifyReceiverOnlyUsedForReadingFields(
+ public void identifyClassInlinerEligibility(
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- if (!method.isNonAbstractVirtualMethod()) {
+ // Method eligibility is calculated in similar way for regular method
+ // and for the constructor. To be eligible method should only be using its
+ // receiver in the following ways:
+ //
+ // (1) as a receiver of reads/writes of instance fields of the holder class
+ // (2) as a return value
+ // (3) as a receiver of a call to the superclass initializer
+ //
+ boolean instanceInitializer = method.isInstanceInitializer();
+ if (method.accessFlags.isNative() ||
+ (!method.isNonAbstractVirtualMethod() && !instanceInitializer)) {
return;
}
- feedback.markReceiverOnlyUsedForReadingFields(method, null);
+ feedback.setClassInlinerEligibility(method, null); // To allow returns below.
- Value instance = code.getThis();
- if (instance.numberOfPhiUsers() > 0) {
- return;
- }
-
- Set<DexField> fields = Sets.newIdentityHashSet();
- for (Instruction insn : instance.uniqueUsers()) {
- if (!insn.isInstanceGet()) {
- return;
- }
- InstanceGet get = insn.asInstanceGet();
- if (get.dest() == instance || get.object() != instance) {
- return;
- }
- fields.add(get.getField());
- }
- feedback.markReceiverOnlyUsedForReadingFields(method, fields);
- }
-
- public void identifyOnlyInitializesFieldsWithNoOtherSideEffects(
- DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- assert method.isInstanceInitializer();
-
- feedback.markOnlyInitializesFieldsWithNoOtherSideEffects(method, null);
-
- if (code.hasCatchHandlers()) {
- return;
- }
-
- List<Value> args = code.collectArguments(true /* not interested in receiver */);
- Map<DexField, Integer> mapping = new IdentityHashMap<>();
Value receiver = code.getThis();
-
- InstructionIterator it = code.instructionIterator();
- while (it.hasNext()) {
- Instruction instruction = it.next();
-
- // Mark an argument.
- if (instruction.isArgument()) {
- continue;
- }
-
- // Allow super call to java.lang.Object.<init>() on 'this'.
- if (instruction.isInvokeDirect()) {
- InvokeDirect invokedDirect = instruction.asInvokeDirect();
- if (invokedDirect.getInvokedMethod() != dexItemFactory.objectMethods.constructor ||
- invokedDirect.getReceiver() != receiver) {
- return;
- }
- continue;
- }
-
- // Allow final return.
- if (instruction.isReturn()) {
- continue;
- }
-
- // Allow assignment to this class' fields. If the assigned value is an argument
- // reference update the mep. Otherwise just allow assigning any value, since all
- // invalid values should be filtered out at the definitions.
- if (instruction.isInstancePut()) {
- InstancePut instancePut = instruction.asInstancePut();
- DexField field = instancePut.getField();
- if (instancePut.object() != receiver) {
- return;
- }
-
- Value value = instancePut.value();
- if (value.isArgument() && !value.isThis()) {
- assert (args.contains(value));
- mapping.put(field, args.indexOf(value));
- } else {
- mapping.remove(field);
- }
- continue;
- }
-
- // Allow non-throwing constants.
- if (instruction.isConstInstruction()) {
- if (instruction.instructionTypeCanThrow()) {
- return;
- }
- continue;
- }
-
- // Allow goto instructions jumping to the next block.
- if (instruction.isGoto()) {
- if (instruction.getBlock().getNumber() + 1 !=
- instruction.asGoto().getTarget().getNumber()) {
- return;
- }
- continue;
- }
-
- // Allow binary and unary instructions if they don't throw.
- if (instruction.isBinop() || instruction.isUnop()) {
- if (instruction.instructionTypeCanThrow()) {
- return;
- }
- continue;
- }
-
+ if (receiver.numberOfPhiUsers() > 0) {
return;
}
- feedback.markOnlyInitializesFieldsWithNoOtherSideEffects(method, mapping);
+ boolean receiverUsedAsReturnValue = false;
+ boolean seenSuperInitCall = false;
+ for (Instruction insn : receiver.uniqueUsers()) {
+ if (insn.isReturn()) {
+ receiverUsedAsReturnValue = true;
+ continue;
+ }
+
+ if (insn.isInstanceGet() ||
+ (insn.isInstancePut() && insn.asInstancePut().object() == receiver)) {
+ DexField field = insn.asFieldInstruction().getField();
+ if (field.clazz == method.method.holder) {
+ // Since class inliner currently only supports classes directly extending
+ // java.lang.Object, we don't need to worry about fields defined in superclasses.
+ continue;
+ }
+ return;
+ }
+
+ // If this is an instance initializer allow one call
+ // to java.lang.Object.<init>() on 'this'.
+ if (instanceInitializer && insn.isInvokeDirect()) {
+ InvokeDirect invokedDirect = insn.asInvokeDirect();
+ if (invokedDirect.getInvokedMethod() == dexItemFactory.objectMethods.constructor &&
+ invokedDirect.getReceiver() == receiver &&
+ !seenSuperInitCall) {
+ seenSuperInitCall = true;
+ continue;
+ }
+ return;
+ }
+
+ // Other receiver usages make the method not eligible.
+ return;
+ }
+
+ if (instanceInitializer && !seenSuperInitCall) {
+ // Call to super constructor not found?
+ return;
+ }
+
+ feedback.setClassInlinerEligibility(
+ method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 7a6a846..92bda1d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -86,6 +86,7 @@
});
}
+ code.removeAllTrivialPhis();
assert code.isConsistentSSA();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 845e17b..01dcf0d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -315,6 +315,12 @@
}
@Override
+ public boolean isValidTarget(InvokeMethod invoke, DexEncodedMethod target, IRCode inlinee) {
+ return !target.isInstanceInitializer()
+ || inliner.legalConstructorInline(method, invoke, inlinee);
+ }
+
+ @Override
public boolean exceededAllowance() {
return instructionAllowance < 0;
}
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 86d40d9..b56dc21 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
@@ -61,6 +61,11 @@
}
@Override
+ public boolean isValidTarget(InvokeMethod invoke, DexEncodedMethod target, IRCode inlinee) {
+ return true;
+ }
+
+ @Override
public ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor) {
return blockIterator;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index b75597d..25cd969 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -285,7 +285,7 @@
return numOfInstructions;
}
- private boolean legalConstructorInline(DexEncodedMethod method,
+ boolean legalConstructorInline(DexEncodedMethod method,
InvokeMethod invoke, IRCode code) {
// In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and
@@ -447,8 +447,7 @@
// Make sure constructor inlining is legal.
assert !target.isClassInitializer();
- if (target.isInstanceInitializer()
- && !legalConstructorInline(method, invoke, inlinee)) {
+ if (!strategy.isValidTarget(invoke, target, inlinee)) {
continue;
}
DexType downcast = createDowncastIfNeeded(strategy, invoke, target);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index 7b88b38..2443d11 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -18,6 +18,8 @@
void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee);
+ boolean isValidTarget(InvokeMethod invoke, DexEncodedMethod target, IRCode inlinee);
+
ListIterator<BasicBlock> updateTypeInformationIfNeeded(IRCode inlinee,
ListIterator<BasicBlock> blockIterator, BasicBlock block, BasicBlock invokeSuccessor);
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 9ff44eb..7bdff33 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
@@ -9,68 +9,75 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.google.common.collect.Streams;
-import java.util.Collections;
+import java.util.ArrayList;
import java.util.IdentityHashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public final class ClassInliner {
private final DexItemFactory factory;
+ private final int totalMethodInstructionLimit;
private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
- private static final Map<DexField, Integer> NO_MAPPING = new IdentityHashMap<>();
-
public interface InlinerAction {
void inline(Map<InvokeMethodWithReceiver, InliningInfo> methods);
}
- public ClassInliner(DexItemFactory factory) {
+ public ClassInliner(DexItemFactory factory, int totalMethodInstructionLimit) {
this.factory = factory;
+ this.totalMethodInstructionLimit = totalMethodInstructionLimit;
}
// Process method code and inline eligible class instantiations, in short:
//
// - collect all 'new-instance' instructions in the original code. Note that class
- // inlining, if happens, mutates code and can add 'new-instance' instructions.
- // Processing them as well is possible, but does not seem to have much value.
+ // inlining, if happens, mutates code and may add new 'new-instance' instructions.
+ // Processing them as well is possible, but does not seem to bring much value.
//
// - for each 'new-instance' we check if it is eligible for inlining, i.e:
// -> the class of the new instance is 'eligible' (see computeClassEligible(...))
- // -> the instance is initialized with 'eligible' constructor (see
- // onlyInitializesFieldsWithNoOtherSideEffects flag in method's optimization
- // info); eligible constructor also defines a set of instance fields directly
- // initialized with parameter values, called field initialization mapping below
+ // -> the instance is initialized with 'eligible' constructor (see comments in
+ // CodeRewriter::identifyClassInlinerEligibility(...))
// -> has only 'eligible' uses, i.e:
- // * it is a receiver of a field read if the field is present in the
- // field initialization mapping
- // * it is a receiver of virtual or interface call with single target being
- // a method only reading fields in the current field initialization mapping
+ // * as a receiver of a field read/write for a field defined in same class
+ // as method.
+ // * as a receiver of virtual or interface call with single target being
+ // an eligible method according to identifyClassInlinerEligibility(...);
+ // NOTE: if method receiver is used as a return value, the method call
+ // should ignore return value
//
// - inline eligible 'new-instance' instructions, i.e:
- // -> force inline methods called on the instance (which may introduce additional
- // instance field reads, but only for fields present in the current field
- // initialization mapping)
- // -> replace instance field reads with appropriate values passed to the constructor
- // according to field initialization mapping
- // -> remove constructor call
+ // -> force inline methods called on the instance (including the initializer);
+ // (this may introduce additional instance field reads/writes on the receiver)
+ // -> replace instance field reads with appropriate values calculated based on
+ // fields writes
+ // -> remove the call to superclass initializer
+ // -> remove all field writes
// -> remove 'new-instance' instructions
//
// For example:
@@ -117,7 +124,6 @@
.map(Instruction::asNewInstance)
.collect(Collectors.toList());
- nextNewInstance:
for (NewInstance newInstance : newInstances) {
Value eligibleInstance = newInstance.outValue();
if (eligibleInstance == null) {
@@ -129,177 +135,281 @@
continue;
}
- // No Phi users.
- if (eligibleInstance.numberOfPhiUsers() > 0) {
+ Map<InvokeMethodWithReceiver, InliningInfo> methodCalls = checkInstanceUsersEligibility(
+ appInfo, method, isProcessedConcurrently, newInstance, eligibleInstance, eligibleClass);
+ if (methodCalls == null) {
continue;
}
- Set<Instruction> uniqueUsers = eligibleInstance.uniqueUsers();
+ if (getTotalEstimatedMethodSize(methodCalls) >= totalMethodInstructionLimit) {
+ continue;
+ }
- // Find an initializer invocation.
- InvokeDirect eligibleInitCall = null;
- Map<DexField, Integer> mappings = null;
- for (Instruction user : uniqueUsers) {
- if (!user.isInvokeDirect()) {
+ // Inline the class instance.
+ forceInlineAllMethodInvocations(inliner, methodCalls);
+ removeSuperClassInitializerAndFieldReads(code, newInstance, eligibleInstance);
+ removeFieldWrites(eligibleInstance, eligibleClass);
+ removeInstruction(newInstance);
+
+ // Restore normality.
+ code.removeAllTrivialPhis();
+ assert code.isConsistentSSA();
+ }
+ }
+
+ private Map<InvokeMethodWithReceiver, InliningInfo> checkInstanceUsersEligibility(
+ AppInfoWithSubtyping appInfo, DexEncodedMethod method,
+ Predicate<DexEncodedMethod> isProcessedConcurrently,
+ NewInstance newInstanceInsn, Value receiver, DexType clazz) {
+
+ // No Phi users.
+ if (receiver.numberOfPhiUsers() > 0) {
+ return null; // Not eligible.
+ }
+
+ Map<InvokeMethodWithReceiver, InliningInfo> methodCalls = new IdentityHashMap<>();
+
+ for (Instruction user : receiver.uniqueUsers()) {
+ // Field read/write.
+ if (user.isInstanceGet() ||
+ (user.isInstancePut() && user.asInstancePut().value() != receiver)) {
+ if (user.asFieldInstruction().getField().clazz == newInstanceInsn.clazz) {
+ // Eligible field read or write. Note: as long as we consider only classes eligible
+ // if they directly extend java.lang.Object we don't need to check if the field
+ // really exists in the class.
continue;
}
+ return null; // Not eligible.
+ }
- InvokeDirect candidate = user.asInvokeDirect();
- DexMethod candidateInit = candidate.getInvokedMethod();
- if (factory.isConstructor(candidateInit) &&
- candidate.inValues().lastIndexOf(eligibleInstance) == 0) {
+ // Eligible constructor call.
+ if (user.isInvokeDirect()) {
+ InliningInfo inliningInfo = isEligibleConstructorCall(appInfo, method,
+ user.asInvokeDirect(), receiver, clazz, isProcessedConcurrently);
+ if (inliningInfo != null) {
+ methodCalls.put(user.asInvokeDirect(), inliningInfo);
+ continue;
+ }
+ return null; // Not eligible.
+ }
- if (candidateInit.holder != eligibleClass) {
- // Inlined constructor call? We won't get field initialization mapping in this
- // case, but since we only support eligible classes extending java.lang.Object,
- // it's safe to assume an empty mapping.
- if (candidateInit.holder == factory.objectType) {
- mappings = Collections.emptyMap();
- }
+ // Eligible virtual method call.
+ if (user.isInvokeVirtual() || user.isInvokeInterface()) {
+ InliningInfo inliningInfo = isEligibleMethodCall(
+ appInfo, method, user.asInvokeMethodWithReceiver(),
+ receiver, clazz, isProcessedConcurrently);
+ if (inliningInfo != null) {
+ methodCalls.put(user.asInvokeMethodWithReceiver(), inliningInfo);
+ continue;
+ }
+ return null; // Not eligible.
+ }
- } else {
- // Is it a call to an *eligible* constructor?
- mappings = getConstructorFieldMappings(appInfo, candidateInit, isProcessedConcurrently);
- }
+ return null; // Not eligible.
+ }
+ return methodCalls;
+ }
- eligibleInitCall = candidate;
+ // Remove call to superclass initializer, replace field reads with appropriate
+ // values, insert phis when needed.
+ private void removeSuperClassInitializerAndFieldReads(
+ IRCode code, NewInstance newInstance, Value eligibleInstance) {
+ Map<DexField, FieldValueHelper> fieldHelpers = new IdentityHashMap<>();
+ for (Instruction user : eligibleInstance.uniqueUsers()) {
+ // Remove the call to superclass constructor.
+ if (user.isInvokeDirect() &&
+ user.asInvokeDirect().getInvokedMethod() == factory.objectMethods.constructor) {
+ removeInstruction(user);
+ continue;
+ }
+
+ if (user.isInstanceGet()) {
+ // Replace a field read with appropriate value.
+ replaceFieldRead(code, newInstance, user.asInstanceGet(), fieldHelpers);
+ continue;
+ }
+
+ if (user.isInstancePut()) {
+ // Skip in this iteration since these instructions are needed to
+ // properly calculate what value should field reads be replaced with.
+ continue;
+ }
+
+ throw new Unreachable("Unexpected usage left after method inlining: " + user);
+ }
+ }
+
+ private void removeFieldWrites(Value receiver, DexType clazz) {
+ for (Instruction user : receiver.uniqueUsers()) {
+ if (!user.isInstancePut()) {
+ throw new Unreachable("Unexpected usage left after field reads removed: " + user);
+ }
+ if (user.asInstancePut().getField().clazz != clazz) {
+ throw new Unreachable("Unexpected field write left after field reads removed: " + user);
+ }
+ removeInstruction(user);
+ }
+ }
+
+ private int getTotalEstimatedMethodSize(Map<InvokeMethodWithReceiver, InliningInfo> methodCalls) {
+ int totalSize = 0;
+ for (InliningInfo info : methodCalls.values()) {
+ totalSize += info.target.getCode().estimatedSizeForInlining();
+ }
+ return totalSize;
+ }
+
+ private void replaceFieldRead(IRCode code, NewInstance newInstance,
+ InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) {
+
+ Value value = fieldRead.outValue();
+ if (value != null) {
+ FieldValueHelper helper = fieldHelpers.computeIfAbsent(
+ fieldRead.getField(), field -> new FieldValueHelper(field, code, newInstance));
+ Value newValue = helper.getValueForFieldRead(fieldRead.getBlock(), fieldRead);
+ value.replaceUsers(newValue);
+ for (FieldValueHelper fieldValueHelper : fieldHelpers.values()) {
+ fieldValueHelper.replaceValue(value, newValue);
+ }
+ assert value.numberOfAllUsers() == 0;
+ }
+ removeInstruction(fieldRead);
+ }
+
+ // Describes and caches what values are supposed to be used instead of field reads.
+ private static final class FieldValueHelper {
+ final DexField field;
+ final IRCode code;
+ final NewInstance newInstance;
+
+ private Value defaultValue = null;
+ private final Map<BasicBlock, Value> ins = new IdentityHashMap<>();
+ private final Map<BasicBlock, Value> outs = new IdentityHashMap<>();
+
+ private FieldValueHelper(DexField field, IRCode code, NewInstance newInstance) {
+ this.field = field;
+ this.code = code;
+ this.newInstance = newInstance;
+ }
+
+ void replaceValue(Value oldValue, Value newValue) {
+ for (Entry<BasicBlock, Value> entry : ins.entrySet()) {
+ if (entry.getValue() == oldValue) {
+ entry.setValue(newValue);
+ }
+ }
+ for (Entry<BasicBlock, Value> entry : outs.entrySet()) {
+ if (entry.getValue() == oldValue) {
+ entry.setValue(newValue);
+ }
+ }
+ }
+
+ Value getValueForFieldRead(BasicBlock block, Instruction valueUser) {
+ assert valueUser != null;
+ Value value = getValueDefinedInTheBlock(block, valueUser);
+ return value != null ? value : getOrCreateInValue(block);
+ }
+
+ private Value getOrCreateOutValue(BasicBlock block) {
+ Value value = outs.get(block);
+ if (value != null) {
+ return value;
+ }
+
+ value = getValueDefinedInTheBlock(block, null);
+ if (value == null) {
+ // No value defined in the block.
+ value = getOrCreateInValue(block);
+ }
+
+ assert value != null;
+ outs.put(block, value);
+ return value;
+ }
+
+ private Value getOrCreateInValue(BasicBlock block) {
+ Value value = ins.get(block);
+ if (value != null) {
+ return value;
+ }
+
+ List<BasicBlock> predecessors = block.getPredecessors();
+ if (predecessors.size() == 1) {
+ value = getOrCreateOutValue(predecessors.get(0));
+ ins.put(block, value);
+ } else {
+ // Create phi, add it to the block, cache in ins map for future use.
+ Phi phi = new Phi(code.valueNumberGenerator.next(),
+ block, ValueType.fromDexType(field.type), null);
+ ins.put(block, phi);
+
+ List<Value> operands = new ArrayList<>();
+ for (BasicBlock predecessor : block.getPredecessors()) {
+ operands.add(getOrCreateOutValue(predecessor));
+ }
+ // Add phi, but don't remove trivial phis; since we cache the phi
+ // we just created for future use we should delay removing trivial
+ // phis until we are done with replacing fields reads.
+ phi.addOperands(operands, false);
+ value = phi;
+ }
+
+ assert value != null;
+ return value;
+ }
+
+ private Value getValueDefinedInTheBlock(BasicBlock block, Instruction stopAt) {
+ InstructionListIterator iterator = stopAt == null ?
+ block.listIterator(block.getInstructions().size()) : block.listIterator(stopAt);
+
+ Instruction valueProducingInsn = null;
+ while (iterator.hasPrevious()) {
+ Instruction instruction = iterator.previous();
+ assert instruction != null;
+
+ if (instruction == newInstance ||
+ (instruction.isInstancePut() &&
+ instruction.asInstancePut().getField() == field &&
+ instruction.asInstancePut().object() == newInstance.outValue())) {
+ valueProducingInsn = instruction;
break;
}
}
- if (mappings == null) {
- continue;
+ if (valueProducingInsn == null) {
+ return null;
+ }
+ if (valueProducingInsn.isInstancePut()) {
+ return valueProducingInsn.asInstancePut().value();
}
- // Check all regular users.
- Map<InvokeMethodWithReceiver, InliningInfo> methodCalls = new IdentityHashMap<>();
-
- for (Instruction user : uniqueUsers) {
- if (user == eligibleInitCall) {
- continue /* next user */;
- }
-
- if (user.isInstanceGet()) {
- InstanceGet instanceGet = user.asInstanceGet();
- if (mappings.containsKey(instanceGet.getField())) {
- continue /* next user */;
- }
-
- // Not replaceable field read.
- continue nextNewInstance;
- }
-
- if (user.isInvokeVirtual() || user.isInvokeInterface()) {
- InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
- if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) {
- continue nextNewInstance; // Instance must only be passes as a receiver.
- }
-
- DexEncodedMethod singleTarget =
- findSingleTarget(appInfo, invoke, eligibleClass);
- if (singleTarget == null) {
- continue nextNewInstance;
- }
- if (isProcessedConcurrently.test(singleTarget)) {
- continue nextNewInstance;
- }
- if (method == singleTarget) {
- continue nextNewInstance; // Don't inline itself.
- }
-
- if (!singleTarget.getOptimizationInfo()
- .isReceiverOnlyUsedForReadingFields(mappings.keySet())) {
- continue nextNewInstance; // Target must be trivial.
- }
-
- if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
- // We won't be able to inline it here.
-
- // Note that there may be some false negatives here since the method may
- // reference private fields of its class which are supposed to be replaced
- // with arguments after inlining. We should try and improve it later.
-
- // Using -allowaccessmodification mitigates this.
- continue nextNewInstance;
- }
-
- methodCalls.put(invoke, new InliningInfo(singleTarget, eligibleClass));
- continue /* next user */;
- }
-
- continue nextNewInstance; // Unsupported user.
+ assert newInstance == valueProducingInsn;
+ if (defaultValue == null) {
+ // If we met newInstance it means that default value is supposed to be used.
+ defaultValue = code.createValue(ValueType.fromDexType(field.type));
+ ConstNumber defaultValueInsn = new ConstNumber(defaultValue, 0);
+ defaultValueInsn.setPosition(newInstance.getPosition());
+ LinkedList<Instruction> instructions = block.getInstructions();
+ instructions.add(instructions.indexOf(newInstance) + 1, defaultValueInsn);
+ defaultValueInsn.setBlock(block);
}
-
- // Force-inline of method invocation if any.
- inlineAllCalls(inliner, methodCalls);
- assert assertOnlyConstructorAndFieldReads(eligibleInstance, eligibleInitCall, mappings);
-
- // Replace all field reads with arguments passed to the constructor.
- patchFieldReads(eligibleInstance, eligibleInitCall, mappings);
- assert assertOnlyConstructor(eligibleInstance, eligibleInitCall);
-
- // Remove constructor call and new-instance instructions.
- removeInstruction(eligibleInitCall);
- removeInstruction(newInstance);
- code.removeAllTrivialPhis();
+ return defaultValue;
}
}
- private void inlineAllCalls(
+ private void forceInlineAllMethodInvocations(
InlinerAction inliner, Map<InvokeMethodWithReceiver, InliningInfo> methodCalls) {
if (!methodCalls.isEmpty()) {
inliner.inline(methodCalls);
}
}
- private void patchFieldReads(
- Value instance, InvokeDirect invokeMethod, Map<DexField, Integer> mappings) {
- for (Instruction user : instance.uniqueUsers()) {
- if (!user.isInstanceGet()) {
- continue;
- }
- InstanceGet fieldRead = user.asInstanceGet();
-
- // Replace the field read with
- assert mappings.containsKey(fieldRead.getField());
- Value arg = invokeMethod.inValues().get(1 + mappings.get(fieldRead.getField()));
- assert arg != null;
- Value value = fieldRead.outValue();
- if (value != null) {
- value.replaceUsers(arg);
- assert value.numberOfAllUsers() == 0;
- }
-
- // Remove instruction.
- removeInstruction(fieldRead);
- }
- }
-
private void removeInstruction(Instruction instruction) {
instruction.inValues().forEach(v -> v.removeUser(instruction));
instruction.getBlock().removeInstruction(instruction);
}
- private boolean assertOnlyConstructorAndFieldReads(
- Value instance, InvokeDirect invokeMethod, Map<DexField, Integer> mappings) {
- for (Instruction user : instance.uniqueUsers()) {
- if (user != invokeMethod &&
- !(user.isInstanceGet() && mappings.containsKey(user.asFieldInstruction().getField()))) {
- throw new Unreachable("Not all calls are inlined!");
- }
- }
- return true;
- }
-
- private boolean assertOnlyConstructor(Value instance, InvokeDirect invokeMethod) {
- for (Instruction user : instance.uniqueUsers()) {
- if (user != invokeMethod) {
- throw new Unreachable("Not all field reads are substituted!");
- }
- }
- return true;
- }
-
private DexEncodedMethod findSingleTarget(
AppInfo appInfo, InvokeMethodWithReceiver invoke, DexType instanceType) {
@@ -322,24 +432,94 @@
return null;
}
- private Map<DexField, Integer> getConstructorFieldMappings(
- AppInfo appInfo, DexMethod init, Predicate<DexEncodedMethod> isProcessedConcurrently) {
- assert isClassEligible(appInfo, init.holder);
+ private InliningInfo isEligibleConstructorCall(
+ AppInfoWithSubtyping appInfo,
+ DexEncodedMethod method,
+ InvokeDirect initInvoke,
+ Value receiver,
+ DexType inlinedClass,
+ Predicate<DexEncodedMethod> isProcessedConcurrently) {
+
+ // Must be a constructor of the exact same class.
+ DexMethod init = initInvoke.getInvokedMethod();
+ if (!factory.isConstructor(init)) {
+ return null;
+ }
+ // Must be a constructor called on the receiver.
+ if (initInvoke.inValues().lastIndexOf(receiver) != 0) {
+ return null;
+ }
+
+ assert init.holder == inlinedClass
+ : "Inlined constructor? [invoke: " + initInvoke +
+ ", expected class: " + inlinedClass + "]";
DexEncodedMethod definition = appInfo.definitionFor(init);
- if (definition == null) {
- return NO_MAPPING;
+ if (definition == null || isProcessedConcurrently.test(definition)) {
+ return null;
}
- if (isProcessedConcurrently.test(definition)) {
- return NO_MAPPING;
+ if (!definition.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
+ // We won't be able to inline it here.
+
+ // Note that there may be some false negatives here since the method may
+ // reference private fields of its class which are supposed to be replaced
+ // with arguments after inlining. We should try and improve it later.
+
+ // Using -allowaccessmodification mitigates this.
+ return null;
}
- if (definition.accessFlags.isAbstract() || definition.accessFlags.isNative()) {
- return NO_MAPPING;
+ return definition.getOptimizationInfo().getClassInlinerEligibility() != null
+ ? new InliningInfo(definition, inlinedClass) : null;
+ }
+
+ private InliningInfo isEligibleMethodCall(
+ AppInfoWithSubtyping appInfo,
+ DexEncodedMethod method,
+ InvokeMethodWithReceiver invoke,
+ Value receiver,
+ DexType inlinedClass,
+ Predicate<DexEncodedMethod> isProcessedConcurrently) {
+
+ if (invoke.inValues().lastIndexOf(receiver) > 0) {
+ return null; // Instance passed as an argument.
}
- return definition.getOptimizationInfo().onlyInitializesFieldsWithNoOtherSideEffects();
+ DexEncodedMethod singleTarget =
+ findSingleTarget(appInfo, invoke, inlinedClass);
+ if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) {
+ return null;
+ }
+ if (method == singleTarget) {
+ return null; // Don't inline itself.
+ }
+
+ ClassInlinerEligibility eligibility =
+ singleTarget.getOptimizationInfo().getClassInlinerEligibility();
+ if (eligibility == null) {
+ return null;
+ }
+
+ // If the method returns receiver and the return value is actually
+ // used in the code the method is not eligible.
+ if (eligibility.returnsReceiver &&
+ invoke.outValue() != null && invoke.outValue().numberOfAllUsers() > 0) {
+ return null;
+ }
+
+ if (!singleTarget.isInliningCandidate(method, Reason.SIMPLE, appInfo)) {
+ // We won't be able to inline it here.
+
+ // Note that there may be some false negatives here since the method may
+ // reference private fields of its class which are supposed to be replaced
+ // with arguments after inlining. We should try and improve it later.
+
+ // Using -allowaccessmodification mitigates this.
+ return null;
+ }
+
+ return new InliningInfo(singleTarget, inlinedClass);
}
private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
@@ -354,7 +534,7 @@
}
// Class is eligible for this optimization. Eligibility implementation:
- // - not an abstract or interface
+ // - is not an abstract class or interface
// - directly extends java.lang.Object
// - does not declare finalizer
// - does not trigger any static initializers
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 436c94f..5f0ec45 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -101,6 +101,7 @@
public boolean enableInlining =
!Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
public boolean enableClassInlining = true;
+ public int classInliningInstructionLimit = 50;
public int inliningInstructionLimit = 5;
public boolean enableSwitchMapRemoval = true;
public final OutlineOptions outline = new OutlineOptions();
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index ede7e53..e01373c 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -15,6 +15,7 @@
import org.hamcrest.core.IsInstanceOf;
import org.hamcrest.core.StringContains;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Test;
import org.junit.internal.matchers.ThrowableMessageMatcher;
@@ -156,16 +157,20 @@
// TODO check compilation warnings are correctly reported
// Missing interface B is causing the wrong code to be executed.
- if (ToolHelper.artSupported()) {
- thrown.expect(AssertionError.class);
- execute(
- "testMissingInterfaceDesugared2AndroidK",
- "desugaringwithmissingclasstest2.Main",
- new Path[] {
- lib1.getInputJar(), lib2.getInputJar(), lib3.getInputJar(), test.getInputJar()
- },
- new Path[] {lib1Dex, lib2Dex, lib3Dex, testDex});
+ if (!ToolHelper.artSupported() && !ToolHelper.compareAgaintsGoldenFiles()) {
+ return;
}
+ if (ToolHelper.artSupported() && !ToolHelper.compareAgaintsGoldenFiles()) {
+ thrown.expect(AssertionError.class);
+ }
+ execute(
+ "testMissingInterfaceDesugared2AndroidK",
+ "desugaringwithmissingclasstest2.Main",
+ new Path[] {
+ lib1.getInputJar(), lib2.getInputJar(), lib3.getInputJar(), test.getInputJar()
+ },
+ new Path[] {lib1Dex, lib2Dex, lib3Dex, testDex});
+
}
@Test
@@ -256,17 +261,20 @@
Path testDex = test.build();
// TODO check compilation warnings are correctly reported
+ Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
+
// Missing interface B is causing the wrong method to be executed.
- if (ToolHelper.artSupported()) {
+ if (ToolHelper.artSupported() && !ToolHelper.compareAgaintsGoldenFiles()) {
thrown.expect(AssertionError.class);
- execute(
- "testCallToMissingSuperInterfaceDesugaredAndroidK",
- "desugaringwithmissingclasstest3.Main",
- new Path[] {
- lib1.getInputJar(), lib2.getInputJar(), lib3.getInputJar(), test.getInputJar()
- },
- new Path[] {lib1Dex, lib2Dex, lib3Dex, testDex});
}
+ execute(
+ "testCallToMissingSuperInterfaceDesugaredAndroidK",
+ "desugaringwithmissingclasstest3.Main",
+ new Path[] {
+ lib1.getInputJar(), lib2.getInputJar(), lib3.getInputJar(), test.getInputJar()
+ },
+ new Path[] {lib1Dex, lib2Dex, lib3Dex, testDex});
+
}
@Test
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index ca6feed..2032866 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Kind;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -22,6 +23,7 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
@@ -1010,8 +1012,7 @@
// Test depends on exception produced for missing method or similar cases, but
// after class inlining removes class instantiations and references the exception
// is not produced.
- "042-new-instance",
- "075-verification-error"
+ "435-new-instance"
);
private static List<String> hasMissingClasses = ImmutableList.of(
@@ -1363,6 +1364,9 @@
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
public R8RunArtTestsTest(String name, DexTool toolchain) {
this.name = name;
this.toolchain = toolchain;
@@ -1554,6 +1558,8 @@
if (disableClassInlining) {
options.enableClassInlining = false;
}
+ // Make sure we don't depend on this settings.
+ options.classInliningInstructionLimit = 10000;
options.lineNumberOptimization = LineNumberOptimization.OFF;
// Some tests actually rely on missing classes for what they test.
options.ignoreMissingClasses = hasMissingClasses;
@@ -1768,7 +1774,7 @@
specification.disableInlining, specification.disableClassInlining,
specification.hasMissingClasses);
- if (!ToolHelper.artSupported()) {
+ if (!ToolHelper.artSupported() && !ToolHelper.dealsWithGoldenFiles()) {
return;
}
@@ -1789,9 +1795,11 @@
boolean compileOnly = System.getProperty("jctf_compile_only", "0").equals("1");
if (compileOnly || specification.skipArt) {
- // verify dex code instead of running it
- Path oatFile = temp.getRoot().toPath().resolve("all.oat");
- ToolHelper.runDex2Oat(processedFile.toPath(), oatFile);
+ if (ToolHelper.isDex2OatSupported()) {
+ // verify dex code instead of running it
+ Path oatFile = temp.getRoot().toPath().resolve("all.oat");
+ ToolHelper.runDex2Oat(processedFile.toPath(), oatFile);
+ }
return;
}
@@ -1963,7 +1971,7 @@
specification.hasMissingClasses);
}
- if (!specification.skipArt && ToolHelper.artSupported()) {
+ if (!specification.skipArt && (ToolHelper.artSupported() || ToolHelper.dealsWithGoldenFiles())) {
File originalFile;
File processedFile;
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 62dd4af..fe5ae72 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
@@ -21,6 +22,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -69,6 +71,9 @@
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
private final Input input;
private final CompilerUnderTest compiler;
private final CompilationMode mode;
@@ -183,9 +188,8 @@
@Test
public void outputIsIdentical() throws IOException, InterruptedException, ExecutionException {
- if (!ToolHelper.artSupported()) {
- return;
- }
+ Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
+
DexVm vm = ToolHelper.getDexVm();
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index fc373d2..0f3cadc 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.nio.file.Path;
@@ -91,6 +92,9 @@
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
@Parameters(name = "{0}")
public static Collection<String[]> data() {
return Arrays.asList(new String[][]{
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
index bb8c686..83df845 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.nio.file.Path;
@@ -73,7 +74,7 @@
build(inputFile, out);
- if (!ToolHelper.artSupported()) {
+ if (!ToolHelper.artSupported() && !ToolHelper.dealsWithGoldenFiles()) {
return;
}
@@ -118,6 +119,8 @@
@Rule public ExpectedException thrown = ExpectedException.none();
+ @Rule public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
@Test
public void staticInterfaceMethods() throws Throwable {
test("staticinterfacemethods", "interfacemethods", "StaticInterfaceMethods")
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 9fdbf0a..2c6a72a 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
@@ -47,6 +48,7 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestWatcher;
public abstract class RunExamplesAndroidOTest
<B extends BaseCommand.Builder<? extends BaseCommand, B>> {
@@ -148,7 +150,7 @@
build(inputFile, out);
- if (!ToolHelper.artSupported()) {
+ if (!ToolHelper.artSupported() && !ToolHelper.dealsWithGoldenFiles()) {
return;
}
@@ -270,6 +272,9 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
boolean failsOn(Map<ToolHelper.DexVm.Version, List<String>> failsOn, String name) {
Version vmVersion = ToolHelper.getDexVm().getVersion();
return failsOn.containsKey(vmVersion)
@@ -553,14 +558,14 @@
throws IOException {
boolean expectedToFail = expectedToFail(testName);
- if (expectedToFail) {
+ if (expectedToFail && !ToolHelper.compareAgaintsGoldenFiles()) {
thrown.expect(Throwable.class);
}
String output = ToolHelper.runArtNoVerificationErrors(
Arrays.stream(dexes).map(path -> path.toString()).collect(Collectors.toList()),
qualifiedMainClass,
null);
- if (!expectedToFail && !skipRunningOnJvm(testName)) {
+ if (!expectedToFail && !skipRunningOnJvm(testName) && !ToolHelper.compareAgaintsGoldenFiles()) {
ToolHelper.ProcessResult javaResult =
ToolHelper.runJava(ImmutableList.copyOf(jars), qualifiedMainClass);
assertEquals("JVM run failed", javaResult.exitCode, 0);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index bc898bd..182fe80 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.utils.DexInspector.InstructionSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
@@ -143,7 +144,7 @@
build(inputFile, out);
- if (!ToolHelper.artSupported()) {
+ if (!ToolHelper.artSupported() && !ToolHelper.dealsWithGoldenFiles()) {
return;
}
@@ -193,6 +194,9 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) {
DexVm.Version vmVersion = ToolHelper.getDexVm().getVersion();
return failsOn.containsKey(vmVersion)
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 281b0da..284ed68 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.utils.DexInspector.InstructionSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
@@ -143,7 +144,7 @@
build(inputFile, out);
- if (!ToolHelper.artSupported()) {
+ if (!ToolHelper.artSupported() && !ToolHelper.dealsWithGoldenFiles()) {
return;
}
@@ -224,6 +225,9 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) {
DexVm.Version vmVersion = ToolHelper.getDexVm().getVersion();
return failsOn.containsKey(vmVersion)
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 331af0a..add86c2 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.PreloadedClassFileProvider;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -59,6 +60,9 @@
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
/**
* Check if tests should also run Proguard when applicable.
*/
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 68d87c1..000076e 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -23,18 +23,23 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
+import com.google.gson.Gson;
import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -702,6 +707,10 @@
return true;
}
+ public static boolean isDex2OatSupported() {
+ return !isWindows();
+ }
+
public static Path getClassPathForTests() {
return Paths.get(BUILD_DIR, "classes", "test");
}
@@ -793,12 +802,22 @@
}
public static R8Command.Builder prepareR8CommandBuilder(AndroidApp app) {
- return R8Command.builder(app).setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ return prepareR8CommandBuilder(app, DexIndexedConsumer.emptyConsumer());
+ }
+
+ public static R8Command.Builder prepareR8CommandBuilder(
+ AndroidApp app, ProgramConsumer programConsumer) {
+ return R8Command.builder(app).setProgramConsumer(programConsumer);
}
public static AndroidApp runR8(AndroidApp app) throws IOException {
+ return runR8WithProgramConsumer(app, DexIndexedConsumer.emptyConsumer());
+ }
+
+ public static AndroidApp runR8WithProgramConsumer(AndroidApp app, ProgramConsumer programConsumer)
+ throws IOException {
try {
- return runR8(prepareR8CommandBuilder(app).build());
+ return runR8(prepareR8CommandBuilder(app, programConsumer).build());
} catch (CompilationFailedException e) {
throw new RuntimeException(e);
}
@@ -1011,17 +1030,197 @@
return runArtRaw(files, mainClass, extras, null);
}
+ // Index used to name directory aimed at storing dex files and process result
+ // for one invokation of runArtRaw() in order to avoid conflicts in case of
+ // multiple calls within the same test.
+ private static int testOutputPathIndex = 0;
+
public static ProcessResult runArtRaw(List<String> files, String mainClass,
Consumer<ArtCommandBuilder> extras, DexVm version)
throws IOException {
- ArtCommandBuilder builder =
- version != null ? new ArtCommandBuilder(version) : new ArtCommandBuilder();
- files.forEach(builder::appendClasspath);
- builder.setMainClass(mainClass);
- if (extras != null) {
- extras.accept(builder);
+
+ ArtCommandBuilder builder =
+ version != null ? new ArtCommandBuilder(version) : new ArtCommandBuilder();
+ files.forEach(builder::appendClasspath);
+ builder.setMainClass(mainClass);
+ if (extras != null) {
+ extras.accept(builder);
+ }
+
+ ProcessResult processResult = null;
+
+ // Whenever we start a new test method we reset the index count.
+ String reset_output_index = System.getProperty("reset_output_index");
+ if (reset_output_index != null) {
+ System.clearProperty("reset_output_index");
+ testOutputPathIndex = 0;
+ } else {
+ assert testOutputPathIndex >= 0;
+ testOutputPathIndex++;
+ }
+
+ String goldenFilesDirInProp = System.getProperty("use_golden_files_in");
+ if (goldenFilesDirInProp != null) {
+ File goldenFileDir = new File(goldenFilesDirInProp);
+ assert goldenFileDir.isDirectory();
+ processResult = compareAgainstGoldenFiles(
+ files.stream().map(f -> new File(f)).collect(Collectors.toList()), goldenFileDir);
+ if (processResult.exitCode == 0) {
+ processResult = readProcessResult(goldenFileDir);
+ }
+ } else {
+ processResult = runArtProcessRaw(builder);
+ }
+
+ String goldenFilesDirToProp = System.getProperty("generate_golden_files_to");
+ if (goldenFilesDirToProp != null) {
+ File goldenFileDir = new File(goldenFilesDirToProp);
+ assert goldenFileDir.isDirectory();
+ storeAsGoldenFiles(files.stream().map(f -> new File(f)).collect(Collectors.toList()),
+ goldenFileDir);
+ storeProcessResult(processResult, goldenFileDir);
+ }
+
+ return processResult;
+ }
+
+ private static Path findNonConflictingDestinationFilePath(Path testOutputPath) {
+ int index = 0;
+ Path destFilePath;
+ do {
+ destFilePath = Paths.get(testOutputPath.toString(),
+ "classes-" + String.format("%03d", index) + FileUtils.DEX_EXTENSION);
+ index++;
+ } while (destFilePath.toFile().exists());
+
+ return destFilePath;
+ }
+
+ private static Path getTestOutputPath(File destDir) throws IOException {
+ assert destDir.exists();
+ assert destDir.isDirectory();
+
+ String testClassName = System.getProperty("test_class_name");
+ String testName = System.getProperty("test_name");
+ String headSha1 = System.getProperty("test_git_HEAD_sha1");
+
+ assert testClassName != null;
+ assert testName != null;
+ assert headSha1 != null;
+
+ return Files.createDirectories(
+ Paths.get(destDir.getAbsolutePath(), headSha1, testClassName, testName + "-" + String
+ .format("%03d", testOutputPathIndex)));
+ }
+
+ private static List<File> unzipDexFilesArchive(File zipFile) throws IOException {
+ File tmpDir = Files.createTempDirectory("r8-test-").toFile();
+ tmpDir.deleteOnExit();
+ return ZipUtils.unzip(zipFile.getAbsolutePath(), tmpDir);
+ }
+
+ private static void storeAsGoldenFiles(List<File> files, File destDir) throws IOException {
+ Path testOutputPath = getTestOutputPath(destDir);
+
+ for (File f : files) {
+ Path filePath = f.toPath();
+ // TODO(jmhenaff): Check it's been produced by D8/R8?
+ List<File> testFiles = Collections.singletonList(f);
+ if (FileUtils.isArchive(filePath)) {
+ testFiles = unzipDexFilesArchive(f);
+ }
+ for (File testFile : testFiles) {
+ Path testFilePath = testFile.toPath();
+ if (FileUtils.isDexFile(testFilePath)) {
+ Path destFile = findNonConflictingDestinationFilePath(testOutputPath);
+ FileUtils.writeToFile(destFile, null, Files.readAllBytes(testFilePath));
+ }
+ }
}
- return runArtProcessRaw(builder);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void storeProcessResult(ProcessResult processResult, File dest)
+ throws IOException {
+ Gson gson = new Gson();
+ Path testOutputPath = getTestOutputPath(dest);
+ try (FileWriter fw = new FileWriter(new File(testOutputPath.toFile(), "processResult.json"))) {
+ gson.toJson(processResult, ProcessResult.class, fw);
+ }
+ }
+
+ private static ProcessResult readProcessResult(File dest) throws IOException {
+ File processResultFile = new File(getTestOutputPath(dest).toFile(), "processResult.json");
+ Gson gson = new Gson();
+ try (FileReader fr = new FileReader(processResultFile)) {
+ return gson.fromJson(fr, ProcessResult.class);
+ }
+ }
+
+ private static ProcessResult compareAgainstGoldenFiles(List<File> files, File destDir)
+ throws IOException {
+ Path testOutputPath = getTestOutputPath(destDir);
+
+ int index = 0;
+ String stdErr = "";
+ boolean passed = true;
+ for (File f : files) {
+ Path filePath = f.toPath();
+
+ List<File> testFiles = Collections.singletonList(f);
+ if (FileUtils.isArchive(filePath)) {
+ testFiles = unzipDexFilesArchive(f);
+ }
+
+ for (File testFile : testFiles) {
+ Path testFilePath = testFile.toPath();
+ // TODO(jmhenaff): Check it's been produced by D8/R8?
+ if (FileUtils.isDexFile(testFilePath)) {
+ File goldenFile = Paths.get(testOutputPath.toString(),
+ "classes-" + String.format("%03d", index) + FileUtils.DEX_EXTENSION).toFile();
+ if (!goldenFile.exists()) {
+ String fileDesc = "'" + testFile.getAbsolutePath() + "'";
+ if (FileUtils.isZipFile(filePath)) {
+ fileDesc += " (extracted from '" + f.getAbsolutePath() + "')";
+ }
+ stdErr += "Cannot find golden file '" + goldenFile.getAbsolutePath()
+ + "' to compare against test file " + fileDesc + "\n";
+ passed = false;
+ } else if (!com.google.common.io.Files.equal(testFile, goldenFile)) {
+ String fileDesc = "'" + testFile.getAbsolutePath() + "'";
+ if (FileUtils.isZipFile(filePath)) {
+ fileDesc += " (extracted from '" + f.getAbsolutePath() + "')";
+ }
+ stdErr +=
+ "File " + fileDesc + " differs from golden file '" + goldenFile.getAbsolutePath()
+ + "'\n";
+ passed = false;
+ }
+ index++;
+ }
+ }
+ }
+ // Ensure we processed as many files as there are golden files
+ File goldenFile = Paths.get(testOutputPath.toString(),
+ "classes-" + String.format("%03d", index) + FileUtils.DEX_EXTENSION).toFile();
+ if (goldenFile.exists()) {
+ stdErr += "Less dex files have been produced: there is at least one more golden file ('"
+ + goldenFile.getAbsolutePath() + "'\n";
+ passed = false;
+ }
+ return new ProcessResult(passed ? 0 : -1, "", stdErr);
+ }
+
+ public static boolean dealsWithGoldenFiles() {
+ return compareAgaintsGoldenFiles() || generateGoldenFiles();
+ }
+
+ public static boolean compareAgaintsGoldenFiles() {
+ return System.getProperty("use_golden_files_in") != null;
+ }
+
+ public static boolean generateGoldenFiles() {
+ return System.getProperty("generate_golden_files_to") != null;
}
public static ProcessResult runArtNoVerificationErrorsRaw(String file, String mainClass)
@@ -1076,7 +1275,7 @@
}
private static ProcessResult runArtProcessRaw(ArtCommandBuilder builder) throws IOException {
- Assume.assumeTrue(ToolHelper.artSupported());
+ Assume.assumeTrue(artSupported() || dealsWithGoldenFiles());
ProcessResult result;
if (builder.isForDevice()) {
try {
@@ -1151,7 +1350,7 @@
}
public static void runDex2Oat(Path file, Path outFile, DexVm vm) throws IOException {
- Assume.assumeTrue(ToolHelper.artSupported());
+ Assume.assumeTrue(ToolHelper.isDex2OatSupported());
// TODO(jmhenaff): find a way to run this on windows (push dex and run on device/emulator?)
Assume.assumeTrue(!ToolHelper.isWindows());
assert Files.exists(file);
diff --git a/src/test/java/com/android/tools/r8/VmTestRunner.java b/src/test/java/com/android/tools/r8/VmTestRunner.java
index 978cbe7..a3e76a4 100644
--- a/src/test/java/com/android/tools/r8/VmTestRunner.java
+++ b/src/test/java/com/android/tools/r8/VmTestRunner.java
@@ -68,7 +68,7 @@
@Override
protected boolean isIgnored(FrameworkMethod child) {
// Do not run VM tests if running VMs is not even supported.
- if (!ToolHelper.artSupported()) {
+ if (!ToolHelper.artSupported() && !ToolHelper.dealsWithGoldenFiles()) {
return true;
}
if (super.isIgnored(child)) {
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
index a24e95a..7d88649 100644
--- a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import java.nio.file.Path;
import org.junit.Rule;
import org.junit.Test;
@@ -25,6 +26,9 @@
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
@Test
public void test() throws Exception {
ProcessResult runInput =
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
new file mode 100644
index 0000000..fd696bc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -0,0 +1,88 @@
+// 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.cf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import java.nio.file.Paths;
+import java.util.Set;
+import org.junit.Test;
+
+public class IdenticalCatchHandlerTest extends TestBase {
+
+ private static class TestClass {
+ public void foo(Object a, Object b, Object c) {
+ if (a == null) {
+ try {
+ System.out.println(b.toString());
+ } catch (RuntimeException e) {
+ }
+ } else {
+ try {
+ System.out.println(c.toString());
+ } catch (RuntimeException e) {
+ }
+ }
+ System.out.println("Hello");
+ }
+ }
+
+ @Test
+ public void test() throws Exception {
+ byte[] inputBytes = ToolHelper.getClassAsBytes(TestClass.class);
+ AndroidApp inputApp =
+ AndroidApp.builder()
+ .addClassProgramData(inputBytes, Origin.unknown())
+ .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+ .build();
+ assertEquals(2, countCatchHandlers(inputApp));
+ AndroidApp outputDexApp = ToolHelper.runR8(inputApp);
+ assertEquals(1, countCatchHandlers(outputDexApp));
+ AndroidApp outputCfApp =
+ ToolHelper.runR8WithProgramConsumer(inputApp, ClassFileConsumer.emptyConsumer());
+ // TODO(b/80514966): Change to assertEquals when fixed.
+ assertNotEquals(1, countCatchHandlers(outputCfApp));
+ }
+
+ private int countCatchHandlers(AndroidApp inputApp) throws Exception {
+ DexInspector inspector = new DexInspector(inputApp, o -> o.enableCfFrontend = true);
+ DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
+ Code code = dexClass.virtualMethods()[0].getCode();
+ if (code.isCfCode()) {
+ CfCode cfCode = code.asCfCode();
+ Set<CfLabel> targets = Sets.newIdentityHashSet();
+ for (CfTryCatch tryCatch : cfCode.getTryCatchRanges()) {
+ targets.addAll(tryCatch.targets);
+ }
+ return targets.size();
+ } else if (code.isDexCode()) {
+ DexCode dexCode = code.asDexCode();
+ IntSet targets = new IntOpenHashSet();
+ for (Try aTry : dexCode.tries) {
+ targets.add(aTry.handlerOffset);
+ }
+ return targets.size();
+ } else {
+ throw new Unimplemented(code.getClass().getName());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index e43ffd7..6069b4c 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -31,6 +31,7 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
+
@RunWith(Parameterized.class)
public class MethodHandleTestRunner extends TestBase {
static final Class<?> CLASS = MethodHandleTest.class;
@@ -143,8 +144,8 @@
if (runInput.exitCode != runDex.exitCode) {
System.out.println(runDex.stderr);
}
- assertEquals(runInput.stdout, runDex.stdout);
assertEquals(runInput.exitCode, runDex.exitCode);
+ assertEquals(runInput.stdout, runDex.stdout);
}
private void build(ProgramConsumer programConsumer) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 25356ae..d41ee84 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.naming.MemberNaming.Signature;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongList;
@@ -113,6 +114,9 @@
@Rule
public TestName testName = new TestName();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
protected static final boolean supportsDefaultMethod(DebugTestConfig config) {
return config.isCfRuntime()
|| ToolHelper.getMinApiLevelForDexVm().getLevel() >= AndroidApiLevel.N.getLevel();
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java
index d0ad1df..6f85b18 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoTestBase.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -31,6 +32,9 @@
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
static AndroidApp compileWithD8(Class... classes) throws IOException, CompilationFailedException {
D8Command.Builder builder = D8Command.builder();
for (Class clazz : classes) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java
index 6760bdd..cb6df88 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningRegressionTests.java
@@ -35,7 +35,7 @@
}
private void buildAndTest(String folder, String mainClass) throws Exception {
- Assume.assumeTrue(ToolHelper.artSupported());
+ Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
Path proguardRules = Paths.get(ToolHelper.EXAMPLES_DIR, folder, "keep-rules.txt");
Path jarFile =
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 e9319d2..d36e6ad 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
@@ -16,6 +16,11 @@
import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.ir.optimize.classinliner.builders.BuildersTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.builders.ControlFlow;
+import com.android.tools.r8.ir.optimize.classinliner.builders.Pair;
+import com.android.tools.r8.ir.optimize.classinliner.builders.PairBuilder;
+import com.android.tools.r8.ir.optimize.classinliner.builders.Tuple;
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;
@@ -75,8 +80,7 @@
collectNewInstanceTypes(clazz, "testConstructorMapping1"));
assertEquals(
- Collections.singleton(
- "com.android.tools.r8.ir.optimize.classinliner.trivial.ReferencedFields"),
+ Collections.emptySet(),
collectNewInstanceTypes(clazz, "testConstructorMapping2"));
assertEquals(
@@ -120,6 +124,60 @@
assertFalse(inspector.clazz(CycleReferenceBA.class).isPresent());
}
+ @Test
+ public void testBuilders() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(BuildersTestClass.class),
+ ToolHelper.getClassAsBytes(BuildersTestClass.Pos.class),
+ ToolHelper.getClassAsBytes(Tuple.class),
+ ToolHelper.getClassAsBytes(Pair.class),
+ ToolHelper.getClassAsBytes(PairBuilder.class),
+ ToolHelper.getClassAsBytes(ControlFlow.class),
+ };
+ String main = BuildersTestClass.class.getCanonicalName();
+ ProcessResult javaOutput = runOnJava(main, classes);
+ assertEquals(0, javaOutput.exitCode);
+
+ AndroidApp app = runR8(buildAndroidApp(classes), BuildersTestClass.class);
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(BuildersTestClass.class);
+
+ assertEquals(
+ Sets.newHashSet(
+ "com.android.tools.r8.ir.optimize.classinliner.builders.Pair",
+ "java.lang.StringBuilder"),
+ collectNewInstanceTypes(clazz, "testSimpleBuilder"));
+
+ // Note that Pair created instances were also inlined in the following method since
+ // we use 'System.out.println(pX.toString())', if we used 'System.out.println(pX)'
+ // as in the above method, the instance of pair would be passed to println() which
+ // would make it not eligible for inlining.
+ assertEquals(
+ Collections.singleton("java.lang.StringBuilder"),
+ collectNewInstanceTypes(clazz, "testSimpleBuilderWithMultipleBuilds"));
+
+ assertFalse(inspector.clazz(PairBuilder.class).isPresent());
+
+ assertEquals(
+ Collections.singleton("java.lang.StringBuilder"),
+ collectNewInstanceTypes(clazz, "testBuilderConstructors"));
+
+ assertFalse(inspector.clazz(Tuple.class).isPresent());
+
+ assertEquals(
+ Collections.singleton("java.lang.StringBuilder"),
+ collectNewInstanceTypes(clazz, "testWithControlFlow"));
+
+ assertFalse(inspector.clazz(ControlFlow.class).isPresent());
+
+ assertEquals(
+ Collections.emptySet(),
+ collectNewInstanceTypes(clazz, "testWithMoreControlFlow"));
+
+ assertFalse(inspector.clazz(BuildersTestClass.Pos.class).isPresent());
+ }
+
private Set<String> collectNewInstanceTypes(
ClassSubject clazz, String methodName, String... params) {
assertNotNull(clazz);
@@ -135,7 +193,11 @@
+ "-dontobfuscate\n"
+ "-allowaccessmodification";
- AndroidApp compiled = compileWithR8(app, config, o -> o.enableClassInlining = true);
+ AndroidApp compiled = compileWithR8(app, config,
+ o -> {
+ o.enableClassInlining = true;
+ o.classInliningInstructionLimit = 10000;
+ });
// Materialize file for execution.
Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
new file mode 100644
index 0000000..1516e14
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/BuildersTestClass.java
@@ -0,0 +1,95 @@
+// 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.builders;
+
+public class BuildersTestClass {
+ private static int ID = 0;
+
+ private static int nextInt() {
+ return ID++;
+ }
+
+ private static String next() {
+ return Integer.toString(nextInt());
+ }
+
+ public static void main(String[] args) {
+ BuildersTestClass test = new BuildersTestClass();
+ test.testSimpleBuilder();
+ test.testSimpleBuilderWithMultipleBuilds();
+ test.testBuilderConstructors();
+ test.testWithControlFlow();
+ test.testWithMoreControlFlow();
+ }
+
+ private synchronized void testSimpleBuilder() {
+ System.out.println(
+ new PairBuilder<String, String>().setFirst("f-" + next()).build().toString());
+ testSimpleBuilder2();
+ testSimpleBuilder3();
+ }
+
+ private synchronized void testSimpleBuilder2() {
+ System.out.println(
+ new PairBuilder<String, String>().setSecond("s-" + next()).build().toString());
+ }
+
+ private synchronized void testSimpleBuilder3() {
+ System.out.println(new PairBuilder<String, String>()
+ .setFirst("f-" + next()).setSecond("s-" + next()).build().toString());
+ }
+
+ private synchronized void testSimpleBuilderWithMultipleBuilds() {
+ PairBuilder<String, String> builder = new PairBuilder<>();
+ Pair p1 = builder.build();
+ System.out.println(p1.toString());
+ builder.setFirst("f-" + next());
+ Pair p2 = builder.build();
+ System.out.println(p2.toString());
+ builder.setSecond("s-" + next());
+ Pair p3 = builder.build();
+ System.out.println(p3.toString());
+ }
+
+ private synchronized void testBuilderConstructors() {
+ System.out.println(new Tuple().toString());
+ System.out.println(new Tuple(true, (byte) 77, (short) 9977, '#', 42,
+ 987654321123456789L, -12.34f, 43210.98765, "s-" + next() + "-s").toString());
+ }
+
+ private synchronized void testWithControlFlow() {
+ ControlFlow flow = new ControlFlow(-1, 2, 7);
+ for (int k = 0; k < 25; k++) {
+ if (k % 3 == 0) {
+ flow.foo(k);
+ } else if (k % 3 == 1) {
+ flow.bar(nextInt(), nextInt(), nextInt(), nextInt());
+ }
+ }
+ System.out.println("flow = " + flow.toString());
+ }
+
+ private synchronized void testWithMoreControlFlow() {
+ String str = "1234567890";
+ Pos pos = new Pos();
+ while (pos.y < str.length()) {
+ pos.x = pos.y;
+ pos.y = pos.x;
+
+ if (str.charAt(pos.x) != '*') {
+ if ('0' <= str.charAt(pos.y) && str.charAt(pos.y) <= '9') {
+ while (pos.y < str.length() && '0' <= str.charAt(pos.y) && str.charAt(pos.y) <= '9') {
+ pos.y++;
+ }
+ }
+ }
+ }
+ }
+
+ public static class Pos {
+ public int x = 0;
+ public int y = 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/ControlFlow.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/ControlFlow.java
new file mode 100644
index 0000000..7b95dc1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/ControlFlow.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.builders;
+
+public class ControlFlow {
+ int a;
+ int b;
+ int c = 1234;
+ int d;
+ String s = ">";
+
+ ControlFlow(int b, int c, int d) {
+ this.s += this.a++ + ">";
+ this.s += this.b + ">";
+ this.b = b;
+ this.s += this.b + ">";
+ this.s += this.c + ">";
+ this.c += c;
+ this.s += this.c + ">";
+ this.s += (this.d = d) + ">";
+ }
+
+ public void foo(int count) {
+ for (int i = 0; i < count; i++) {
+ switch (i % 4) {
+ case 0:
+ this.s += ++this.a + ">";
+ break;
+ case 1:
+ this.c += this.b;
+ this.s += this.c + ">";
+ break;
+ case 2:
+ this.d += this.d++ + this.c++ + this.b++ + this.a++;
+ this.s += this.d + ">";
+ break;
+ }
+ }
+ }
+
+ public void bar(int a, int b, int c, int d) {
+ this.a += a;
+ this.b += b;
+ this.c += c;
+ this.d += d;
+ }
+
+ @Override
+ public String toString() {
+ return s;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Pair.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Pair.java
new file mode 100644
index 0000000..af45cab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Pair.java
@@ -0,0 +1,22 @@
+// 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.builders;
+
+public class Pair<F, S> {
+ public final F first;
+ public final S second;
+
+ public Pair(F first, S second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ @Override
+ public String toString() {
+ return "Pair(" +
+ (first == null ? "<null>" : first) + ", " +
+ (second == null ? "<null>" : second) + ")";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/PairBuilder.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/PairBuilder.java
new file mode 100644
index 0000000..0c80c53
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/PairBuilder.java
@@ -0,0 +1,29 @@
+// 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.builders;
+
+public class PairBuilder<F, S> {
+ public F first;
+ public S second = null;
+
+ public PairBuilder<F, S> setFirst(F first) {
+ System.out.println("[before] first = " + this.first);
+ this.first = first;
+ System.out.println("[after] first = " + this.first);
+ return this;
+ }
+
+ public PairBuilder<F, S> setSecond(S second) {
+ System.out.println("[before] second = " + this.second);
+ this.second = second;
+ System.out.println("[after] second = " + this.second);
+ return this;
+ }
+
+ public Pair<F, S> build() {
+ return new Pair<>(first, second);
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Tuple.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Tuple.java
new file mode 100644
index 0000000..3a77cc2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/builders/Tuple.java
@@ -0,0 +1,39 @@
+// 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.builders;
+
+public class Tuple {
+ public boolean z;
+ public byte b;
+ public short s;
+ public char c;
+ public int i;
+ public long l;
+ public float f;
+ public double d;
+ public Object o;
+
+ Tuple() {
+ }
+
+ Tuple(boolean z, byte b, short s, char c, int i, long l, float f, double d, Object o) {
+ this.z = z;
+ this.b = b;
+ this.s = s;
+ this.c = c;
+ this.i = i;
+ this.l = l;
+ this.f = f;
+ this.d = d;
+ this.o = o;
+ }
+
+ @Override
+ public String toString() {
+ return "Tuple1(" + z + ", " + b + ", " + s + ", " +
+ ((int) c) + ", " + i + ", " + l + ", " + f + ", " +
+ d + ", " + (o == null ? "<null>" : o) + ")";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
index 57d694d..86c15a2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -87,4 +87,4 @@
assertEquals(0, artOutput.exitCode);
assertEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
index bdedf43..3028525 100644
--- a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
+++ b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.File;
@@ -30,6 +31,7 @@
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -267,6 +269,9 @@
@ClassRule
public static TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
@BeforeClass
public static void compileLibraries() throws Exception {
// Selects appropriate jar according to min api level for the selected runtime.
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index c175e04..f99934e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -223,7 +223,7 @@
protected void runTest(String folder, String mainClass, String extraProguardRules,
Consumer<InternalOptions> optionsConsumer, AndroidAppInspector inspector) throws Exception {
- Assume.assumeTrue(ToolHelper.artSupported());
+ Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
String proguardRules = buildProguardRules(mainClass);
if (extraProguardRules != null) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index c8c8aec..73d1e50 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -10,7 +10,9 @@
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 java.util.Collections;
+import java.util.function.Consumer;
import org.junit.Test;
public class R8KotlinDataClassTest extends AbstractR8KotlinTestBase {
@@ -33,13 +35,15 @@
private static final MethodSignature COPY_DEFAULT_METHOD =
TEST_DATA_CLASS.getCopyDefaultSignature();
+ private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false;
+
@Test
public void test_dataclass_gettersOnly() throws Exception {
final String mainClassName = "dataclass.MainGettersOnlyKt";
final MethodSignature testMethodSignature =
new MethodSignature("testDataClassGetters", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
- runTest("dataclass", mainClassName, extraRules, (app) -> {
+ runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
@@ -74,7 +78,7 @@
final MethodSignature testMethodSignature =
new MethodSignature("testAllDataClassComponentFunctions", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
- runTest("dataclass", mainClassName, extraRules, (app) -> {
+ runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
@@ -109,7 +113,7 @@
final MethodSignature testMethodSignature =
new MethodSignature("testSomeDataClassComponentFunctions", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
- runTest("dataclass", mainClassName, extraRules, (app) -> {
+ runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
@@ -144,7 +148,7 @@
final MethodSignature testMethodSignature =
new MethodSignature("testDataClassCopy", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
- runTest("dataclass", mainClassName, extraRules, (app) -> {
+ runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
@@ -159,7 +163,7 @@
final MethodSignature testMethodSignature =
new MethodSignature("testDataClassCopyWithDefault", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
- runTest("dataclass", mainClassName, extraRules, (app) -> {
+ runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index e2dfb6d..46a439d 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.utils.DexInspector.InstructionSubject;
import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -26,6 +27,7 @@
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -60,6 +62,9 @@
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
public MemberRebindingTest(TestConfiguration configuration) {
this.kind = configuration.kind;
originalDex = configuration.getDexPath();
@@ -319,9 +324,8 @@
@Test
public void memberRebindingTest() throws IOException, InterruptedException, ExecutionException {
- if (!ToolHelper.artSupported()) {
- return;
- }
+ Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
+
String out = temp.getRoot().getCanonicalPath();
Path processed = Paths.get(out, "classes.dex");
diff --git a/src/test/java/com/android/tools/r8/resource/DataResourceTest.java b/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
index 2df0612..66161e1 100644
--- a/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
+++ b/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -23,6 +24,9 @@
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
@Test
public void dataResourceTest() throws IOException, CompilationFailedException {
String packageName = "dataresource";
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index f341969..c9488b4 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.utils.DexInspector.FoundFieldSubject;
import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -76,6 +77,9 @@
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Rule
+ public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
+
protected TreeShakingTest(
String name, String mainClass, Frontend frontend, Backend backend, MinifyMode minify) {
this.name = name;
@@ -204,7 +208,7 @@
}
return;
}
- if (!ToolHelper.artSupported()) {
+ if (!ToolHelper.artSupported() && !ToolHelper.compareAgaintsGoldenFiles()) {
return;
}
Consumer<ArtCommandBuilder> extraArtArgs = builder -> {
diff --git a/src/test/java/com/android/tools/r8/utils/TestDescriptionWatcher.java b/src/test/java/com/android/tools/r8/utils/TestDescriptionWatcher.java
new file mode 100644
index 0000000..5295c0e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/TestDescriptionWatcher.java
@@ -0,0 +1,29 @@
+// 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.utils;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+/**
+ * This {@link TestWatcher} passes test description information
+ * (namely test class and method names) to the test framework
+ * via system properties.
+ */
+public class TestDescriptionWatcher extends TestWatcher {
+
+ @Override
+ protected void starting(Description description) {
+ System.setProperty("test_class_name", description.getClassName());
+ System.setProperty("test_name", description.getMethodName());
+ System.setProperty("reset_output_index", "true");
+ }
+
+ @Override
+ protected void finished(Description description) {
+ System.clearProperty("test_class_name");
+ System.clearProperty("test_name");
+ }
+
+}
diff --git a/tools/download_from_x20.py b/tools/download_from_x20.py
index 01ba4e1..5f64684 100755
--- a/tools/download_from_x20.py
+++ b/tools/download_from_x20.py
@@ -19,17 +19,10 @@
def parse_options():
return optparse.OptionParser().parse_args()
-def extract_dir(filename):
- return filename[0:len(filename) - len('.tar.gz')]
-
-def unpack_archive(filename):
- dest_dir = extract_dir(filename)
- if os.path.exists(dest_dir):
- print 'Deleting existing dir %s' % dest_dir
- shutil.rmtree(dest_dir)
- dirname = os.path.dirname(os.path.abspath(filename))
- with tarfile.open(filename, 'r:gz') as tar:
- tar.extractall(path=dirname)
+def download(src, dest):
+ print 'Downloading %s to %s' % (src, dest)
+ shutil.copyfile(src, dest)
+ utils.unpack_archive(dest)
def Main():
(options, args) = parse_options()
@@ -41,20 +34,18 @@
sha1 = input_sha.readline()
if os.path.exists(dest) and utils.get_sha1(dest) == sha1:
print 'sha1 matches, not downloading'
- dest_dir = extract_dir(dest)
+ dest_dir = utils.extract_dir(dest)
if os.path.exists(dest_dir):
print 'destination directory exists, no extraction'
else:
- unpack_archive(dest)
+ utils.unpack_archive(dest)
return
src = os.path.join(GMSCORE_DEPS, sha1)
if not os.path.exists(src):
print 'File (%s) does not exist on x20' % src
print 'Maybe pass -Pno_internal to your gradle invocation'
return 42
- print 'Downloading %s to %s' % (src, dest)
- shutil.copyfile(src, dest)
- unpack_archive(dest)
+ download(src, dest)
if __name__ == '__main__':
sys.exit(Main())
diff --git a/tools/test.py b/tools/test.py
index ed99c1d..b92c8dc 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -15,6 +15,7 @@
import utils
import uuid
import notify
+import upload_to_x20
ALL_ART_VMS = ["default", "7.0.0", "6.0.1", "5.1.1", "4.4.4", "4.0.4"]
@@ -78,6 +79,13 @@
' Note that the directory will not be cleared before the test.')
result.add_option('--java_home',
help='Use a custom java version to run tests.')
+ result.add_option('--generate_golden_files_to',
+ help='Store dex files produced by tests in the specified directory.'
+ ' It is aimed to be read on platforms with no host runtime available'
+ ' for comparison.')
+ result.add_option('--use_golden_files_in',
+ help='Download golden files hierarchy for this commit in the specified'
+ ' location and use them instead of executing on host runtime.')
return result.parse_args()
@@ -93,7 +101,7 @@
def Main():
(options, args) = ParseOptions()
- gradle_args = []
+ gradle_args = ['--stacktrace']
# Set all necessary Gradle properties and options first.
if options.verbose:
gradle_args.append('-Pprint_test_stdout')
@@ -134,7 +142,16 @@
os.makedirs(options.test_dir)
if options.java_home:
gradle_args.append('-Dorg.gradle.java.home=' + options.java_home)
-
+ if options.generate_golden_files_to:
+ gradle_args.append('-Pgenerate_golden_files_to=' + options.generate_golden_files_to)
+ if not os.path.exists(options.generate_golden_files_to):
+ os.makedirs(options.generate_golden_files_to)
+ gradle_args.append('-PHEAD_sha1=' + utils.get_HEAD_sha1())
+ if options.use_golden_files_in:
+ gradle_args.append('-Puse_golden_files_in=' + options.use_golden_files_in)
+ if not os.path.exists(options.use_golden_files_in):
+ os.makedirs(options.use_golden_files_in)
+ gradle_args.append('-PHEAD_sha1=' + utils.get_HEAD_sha1())
# Add Gradle tasks
gradle_args.append('cleanTest')
gradle_args.append('test')
@@ -146,12 +163,29 @@
# Create Jacoco report after tests.
gradle_args.append('jacocoTestReport')
+ if options.use_golden_files_in:
+ sha1 = '%s' % utils.get_HEAD_sha1()
+ with utils.ChangedWorkingDirectory(options.use_golden_files_in):
+ utils.download_file_from_cloud_storage(
+ 'gs://r8-test-results/golden-files/%s.tar.gz' % sha1,
+ '%s.tar.gz' % sha1)
+ utils.unpack_archive('%s.tar.gz' % sha1)
+
+
# Now run tests on selected runtime(s).
vms_to_test = [options.dex_vm] if options.dex_vm != "all" else ALL_ART_VMS
for art_vm in vms_to_test:
vm_kind_to_test = "_" + options.dex_vm_kind if art_vm != "default" else ""
return_code = gradle.RunGradle(gradle_args + ['-Pdex_vm=%s' % (art_vm + vm_kind_to_test)],
throw_on_failure=False)
+
+ if options.generate_golden_files_to:
+ sha1 = '%s' % utils.get_HEAD_sha1()
+ with utils.ChangedWorkingDirectory(options.generate_golden_files_to):
+ archive = utils.create_archive(sha1)
+ utils.upload_file_to_cloud_storage(archive,
+ 'gs://r8-test-results/golden-files/' + archive)
+
if return_code != 0:
if options.archive_failures and os.name != 'nt':
archive_failures()
diff --git a/tools/upload_to_x20.py b/tools/upload_to_x20.py
index 1efacf5..2d08362 100755
--- a/tools/upload_to_x20.py
+++ b/tools/upload_to_x20.py
@@ -20,11 +20,10 @@
def parse_options():
return optparse.OptionParser().parse_args()
-def create_archive(name):
- tarname = '%s.tar.gz' % name
- with tarfile.open(tarname, 'w:gz') as tar:
- tar.add(name)
- return tarname
+def uploadFile(filename, dest):
+ print 'Uploading to %s' % dest
+ shutil.copyfile(filename, dest)
+ subprocess.check_call(['chmod', '664', dest])
def Main():
(options, args) = parse_options()
@@ -34,12 +33,10 @@
if not name in os.listdir('.'):
print 'You must be standing directly below the directory you are uploading'
return 1
- filename = create_archive(name)
+ filename = utils.create_archive(name)
sha1 = utils.get_sha1(filename)
dest = os.path.join(GMSCORE_DEPS, sha1)
- print 'Uploading to %s' % dest
- shutil.copyfile(filename, dest)
- subprocess.check_call(['chmod', '664', dest])
+ uploadFile(filename, dest)
sha1_file = '%s.sha1' % filename
with open(sha1_file, 'w') as output:
output.write(sha1)
diff --git a/tools/utils.py b/tools/utils.py
index 3aab572..859da97 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -10,6 +10,7 @@
import shutil
import subprocess
import sys
+import tarfile
import tempfile
TOOLS_DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..')))
@@ -71,6 +72,12 @@
sha1.update(chunk)
return sha1.hexdigest()
+def get_HEAD_sha1():
+ cmd = ['git', 'rev-parse', 'HEAD']
+ PrintCmd(cmd)
+ with ChangedWorkingDirectory(REPO_ROOT):
+ return subprocess.check_output(cmd).strip()
+
def makedirs_if_needed(path):
try:
os.makedirs(path)
@@ -92,6 +99,29 @@
PrintCmd(cmd)
subprocess.check_call(cmd)
+def download_file_from_cloud_storage(source, destination):
+ cmd = ['gsutil.py', 'cp', source, destination]
+ PrintCmd(cmd)
+ subprocess.check_call(cmd)
+
+def create_archive(name):
+ tarname = '%s.tar.gz' % name
+ with tarfile.open(tarname, 'w:gz') as tar:
+ tar.add(name)
+ return tarname
+
+def extract_dir(filename):
+ return filename[0:len(filename) - len('.tar.gz')]
+
+def unpack_archive(filename):
+ dest_dir = extract_dir(filename)
+ if os.path.exists(dest_dir):
+ print 'Deleting existing dir %s' % dest_dir
+ shutil.rmtree(dest_dir)
+ dirname = os.path.dirname(os.path.abspath(filename))
+ with tarfile.open(filename, 'r:gz') as tar:
+ tar.extractall(path=dirname)
+
# Note that gcs is eventually consistent with regards to list operations.
# This is not a problem in our case, but don't ever use this method
# for synchronization.