Merge "Remove support for package distribution in debug mode"
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index cb1db15..ed992a3 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -55,7 +55,7 @@
*/
public final class D8 {
- private static final String kVersion = "v0.1.0";
+ private static final String VERSION = "v0.1.0";
private static final int STATUS_ERROR = 1;
private D8() {}
@@ -110,7 +110,7 @@
return;
}
if (command.isPrintVersion()) {
- System.out.println("D8 " + kVersion);
+ System.out.println("D8 " + VERSION);
return;
}
run(command);
@@ -161,7 +161,7 @@
return options.getMarker();
}
return new Marker(Tool.D8)
- .put("version", kVersion)
+ .put("version", VERSION)
.put("min-api", options.minApiLevel);
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index bd51b5d..f30ee0a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -71,7 +71,7 @@
public class R8 {
- private static final String kVersion = "v0.1.0";
+ private static final String VERSION = "v0.1.0";
private final Timing timing = new Timing("R8");
private final InternalOptions options;
@@ -86,7 +86,7 @@
return options.getMarker();
}
return new Marker(Tool.R8)
- .put("version", kVersion)
+ .put("version", VERSION)
.put("min-api", options.minApiLevel);
}
@@ -502,7 +502,7 @@
return;
}
if (command.isPrintVersion()) {
- System.out.println("R8 " + kVersion);
+ System.out.println("R8 " + VERSION);
return;
}
run(command);
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index aeaf0f8..ec040a5 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -38,14 +38,10 @@
private Builder() {
super(CompilationMode.RELEASE);
- // TODO(b/62048823): Minifier should not depend on -allowaccessmodification.
- proguardConfigurationConsumer = builder -> builder.setAllowAccessModification(true);
}
private Builder(AndroidApp app) {
super(app, CompilationMode.RELEASE);
- // TODO(b/62048823): Minifier should not depend on -allowaccessmodification.
- proguardConfigurationConsumer = builder -> builder.setAllowAccessModification(true);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index a8f56a7..f1162a9 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -253,10 +253,10 @@
private void sortClassData(Collection<DexProgramClass> classesWithData) {
for (DexProgramClass clazz : classesWithData) {
- sortEncodedFields(clazz.instanceFields);
- sortEncodedFields(clazz.staticFields);
- sortEncodedMethods(clazz.directMethods);
- sortEncodedMethods(clazz.virtualMethods);
+ sortEncodedFields(clazz.instanceFields());
+ sortEncodedFields(clazz.staticFields());
+ sortEncodedMethods(clazz.directMethods());
+ sortEncodedMethods(clazz.virtualMethods());
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index d09d395..3f492b5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -22,10 +22,10 @@
public DexType superType;
public DexTypeList interfaces;
public final DexString sourceFile;
- public DexEncodedField[] staticFields;
- public DexEncodedField[] instanceFields;
- public DexEncodedMethod[] directMethods;
- public DexEncodedMethod[] virtualMethods;
+ protected DexEncodedField[] staticFields;
+ protected DexEncodedField[] instanceFields;
+ protected DexEncodedMethod[] directMethods;
+ protected DexEncodedMethod[] virtualMethods;
public DexAnnotationSet annotations;
public DexClass(
@@ -39,10 +39,10 @@
this.accessFlags = accessFlags;
this.superType = superType;
this.type = type;
- this.staticFields = staticFields;
- this.instanceFields = instanceFields;
- this.directMethods = directMethods;
- this.virtualMethods = virtualMethods;
+ setStaticFields(staticFields);
+ setInstanceFields(instanceFields);
+ setDirectMethods(directMethods);
+ setVirtualMethods(virtualMethods);
this.annotations = annotations;
if (type == superType) {
throw new CompilationError("Class " + type.toString() + " cannot extend itself");
@@ -66,13 +66,22 @@
}
public DexEncodedMethod[] directMethods() {
- return MoreObjects.firstNonNull(directMethods, NO_METHODS);
+ return directMethods;
+ }
+
+ public void setDirectMethods(DexEncodedMethod[] values) {
+ directMethods = MoreObjects.firstNonNull(values, NO_METHODS);
}
public DexEncodedMethod[] virtualMethods() {
- return MoreObjects.firstNonNull(virtualMethods, NO_METHODS);
+ return virtualMethods;
}
+ public void setVirtualMethods(DexEncodedMethod[] values) {
+ virtualMethods = MoreObjects.firstNonNull(values, NO_METHODS);
+ }
+
+
public void forEachMethod(Consumer<DexEncodedMethod> consumer) {
for (DexEncodedMethod method : directMethods()) {
consumer.accept(method);
@@ -103,11 +112,19 @@
}
public DexEncodedField[] staticFields() {
- return MoreObjects.firstNonNull(staticFields, NO_FIELDS);
+ return staticFields;
+ }
+
+ public void setStaticFields(DexEncodedField[] values) {
+ staticFields = MoreObjects.firstNonNull(values, NO_FIELDS);
}
public DexEncodedField[] instanceFields() {
- return MoreObjects.firstNonNull(instanceFields, NO_FIELDS);
+ return instanceFields;
+ }
+
+ public void setInstanceFields(DexEncodedField[] values) {
+ instanceFields = MoreObjects.firstNonNull(values, NO_FIELDS);
}
/**
@@ -187,6 +204,14 @@
return getClassInitializer() != null;
}
+ public boolean hasTrivialClassInitializer() {
+ DexEncodedMethod clinit = getClassInitializer();
+ return clinit != null
+ && clinit.getCode() != null
+ && clinit.getCode().asDexCode().isEmptyVoidMethod();
+ }
+
+
public boolean hasNonTrivialClassInitializer() {
DexEncodedMethod clinit = getClassInitializer();
if (clinit == null || clinit.getCode() == null) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 713bd53..13a4d9d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -85,6 +85,8 @@
public final DexString getClassMethodName = createString("getClass");
public final DexString ordinalMethodName = createString("ordinal");
public final DexString desiredAssertionStatusMethodName = createString("desiredAssertionStatus");
+ public final DexString getNameName = createString("getName");
+ public final DexString getSimpleNameName = createString("getSimpleName");
public final DexString assertionsDisabled = createString("$assertionsDisabled");
public final DexString stringDescriptor = createString("Ljava/lang/String;");
@@ -208,10 +210,15 @@
public class ClassMethods {
public DexMethod desiredAssertionStatus;
+ public DexMethod getName;
+ public DexMethod getSimpleName;
private ClassMethods() {
desiredAssertionStatus = createMethod(classDescriptor,
desiredAssertionStatusMethodName, booleanDescriptor, DexString.EMPTY_ARRAY);
+ getName = createMethod(classDescriptor, getNameName, stringDescriptor, DexString.EMPTY_ARRAY);
+ getSimpleName = createMethod(classDescriptor,
+ getSimpleNameName, stringDescriptor, DexString.EMPTY_ARRAY);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 60a7319..1bdc56c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -152,6 +152,18 @@
directMethods[directMethods.length - 1] = staticMethod;
}
+ public void removeStaticMethod(DexEncodedMethod staticMethod) {
+ assert staticMethod.accessFlags.isStatic();
+ DexEncodedMethod[] newDirectMethods = new DexEncodedMethod[directMethods.length - 1];
+ int toIndex = 0;
+ for (int fromIndex = 0; fromIndex < directMethods.length; fromIndex++) {
+ if (directMethods[fromIndex] != staticMethod) {
+ newDirectMethods[toIndex++] = directMethods[fromIndex];
+ }
+ }
+ directMethods = newDirectMethods;
+ }
+
@Override
public DexProgramClass get() {
return this;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 2ffb5a1..bf93530 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -426,6 +426,10 @@
return isConstant() && getConstInstruction().isConstNumber();
}
+ public boolean isConstString() {
+ return isConstant() && getConstInstruction().isConstString();
+ }
+
public boolean isConstant() {
return definition.isOutConstant() && getLocalInfo() == null;
}
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 3cd40f7..fad378e 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
@@ -38,8 +38,8 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -89,8 +89,7 @@
this.graphLense = graphLense != null ? graphLense : GraphLense.getIdentityLense();
this.options = options;
this.printer = printer;
- Set<DexType> libraryClassesWithOptimizationInfo = markLibraryMethodsReturningReceiver();
- this.codeRewriter = new CodeRewriter(appInfo, libraryClassesWithOptimizationInfo);
+ this.codeRewriter = new CodeRewriter(appInfo, libraryMethodsReturningReceiver());
this.lambdaRewriter = enableDesugaring ? new LambdaRewriter(this) : null;
this.interfaceMethodRewriter =
(enableDesugaring && enableInterfaceMethodDesugaring())
@@ -183,18 +182,12 @@
throw new Unreachable();
}
- private Set<DexType> markLibraryMethodsReturningReceiver() {
+ private Set<DexMethod> libraryMethodsReturningReceiver() {
+ Set<DexMethod> methods = new HashSet<>();
DexItemFactory dexItemFactory = appInfo.dexItemFactory;
- dexItemFactory.stringBuilderMethods.forEachAppendMethod(this::markReturnsReceiver);
- dexItemFactory.stringBufferMethods.forEachAppendMethod(this::markReturnsReceiver);
- return ImmutableSet.of(dexItemFactory.stringBuilderType, dexItemFactory.stringBufferType);
- }
-
- private void markReturnsReceiver(DexMethod method) {
- DexEncodedMethod definition = appInfo.definitionFor(method);
- if (definition != null) {
- definition.markReturnsArgument(0);
- }
+ dexItemFactory.stringBufferMethods.forEachAppendMethod(methods::add);
+ dexItemFactory.stringBuilderMethods.forEachAppendMethod(methods::add);
+ return methods;
}
private void removeLambdaDeserializationMethods() {
@@ -238,7 +231,11 @@
for (DexProgramClass clazz : classes) {
futures.add(executor.submit(() -> clazz.forEachMethod(this::convertMethodToDex)));
}
+
ThreadUtils.awaitFutures(futures);
+
+ // Get rid of <clinit> methods with no code.
+ removeEmptyClassInitializers();
}
private void convertMethodToDex(DexEncodedMethod method) {
@@ -284,6 +281,9 @@
}, executorService);
timing.end();
+ // Get rid of <clinit> methods with no code.
+ removeEmptyClassInitializers();
+
// Build a new application with jumbo string info.
Builder builder = new Builder(application);
builder.setHighestSortingString(highestSortingString);
@@ -325,6 +325,16 @@
return builder.build();
}
+ private void removeEmptyClassInitializers() {
+ application.classes().forEach(this::removeEmptyClassInitializer);
+ }
+
+ private void removeEmptyClassInitializer(DexProgramClass clazz) {
+ if (clazz.hasTrivialClassInitializer()) {
+ clazz.removeStaticMethod(clazz.getClassInitializer());
+ }
+ }
+
private void clearDexMethodCompilationState() {
application.classes().forEach(this::clearDexMethodCompilationState);
}
@@ -512,7 +522,7 @@
}
printMethod(code, "Final IR (non-SSA)");
- // After all the optimizations have take place, we compute whether method should be inlined.
+ // After all the optimizations have take place, we compute whether method should be inlinedex.
Constraint state;
if (!options.inlineAccessors || inliner == null) {
state = Constraint.NEVER;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 236c9e5..b7036f5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -80,15 +80,15 @@
}
// Add the methods.
- DexEncodedMethod[] existing = clazz.virtualMethods;
- clazz.virtualMethods = new DexEncodedMethod[existing.length + methodsToImplement.size()];
- System.arraycopy(existing, 0, clazz.virtualMethods, 0, existing.length);
+ DexEncodedMethod[] existing = clazz.virtualMethods();
+ clazz.setVirtualMethods(new DexEncodedMethod[existing.length + methodsToImplement.size()]);
+ System.arraycopy(existing, 0, clazz.virtualMethods(), 0, existing.length);
for (int i = 0; i < methodsToImplement.size(); i++) {
DexEncodedMethod method = methodsToImplement.get(i);
assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
- clazz.virtualMethods[existing.length + i] = newMethod;
+ clazz.virtualMethods()[existing.length + i] = newMethod;
createdMethods.put(newMethod, method);
}
}
@@ -142,7 +142,7 @@
current = clazz;
while (true) {
// Hide candidates by virtual method of the class.
- hideCandidates(current.virtualMethods, candidates, toBeImplemented);
+ hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
if (candidates.isEmpty()) {
return toBeImplemented;
}
@@ -218,12 +218,12 @@
}
// Hide by virtual methods of this interface.
- for (DexEncodedMethod virtual : clazz.virtualMethods) {
+ for (DexEncodedMethod virtual : clazz.virtualMethods()) {
helper.hideMatches(virtual.method);
}
// Add all default methods of this interface.
- for (DexEncodedMethod encoded : clazz.virtualMethods) {
+ for (DexEncodedMethod encoded : clazz.virtualMethods()) {
if (rewriter.isDefaultMethod(encoded)) {
helper.addDefaultMethod(encoded);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index a84e082..2d06abe 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -44,7 +44,7 @@
// Process virtual interface methods first.
List<DexEncodedMethod> remainingMethods = new ArrayList<>();
- for (DexEncodedMethod virtual : iface.virtualMethods) {
+ for (DexEncodedMethod virtual : iface.virtualMethods()) {
if (rewriter.isDefaultMethod(virtual)) {
// Create a new method in a companion class to represent default method implementation.
DexMethod companionMethod = rewriter.defaultAsMethodOfCompanionClass(virtual.method);
@@ -79,14 +79,14 @@
}
// If at least one bridge methods was removed update the table.
- if (remainingMethods.size() < iface.virtualMethods.length) {
- iface.virtualMethods = remainingMethods.toArray(
- new DexEncodedMethod[remainingMethods.size()]);
+ if (remainingMethods.size() < iface.virtualMethods().length) {
+ iface.setVirtualMethods(remainingMethods.toArray(
+ new DexEncodedMethod[remainingMethods.size()]));
}
remainingMethods.clear();
// Process static methods, move them into companion class as well.
- for (DexEncodedMethod direct : iface.directMethods) {
+ for (DexEncodedMethod direct : iface.directMethods()) {
if (direct.accessFlags.isPrivate()) {
// We only expect to see private methods which are lambda$ methods,
// and they are supposed to be relaxed to package private static methods
@@ -105,9 +105,9 @@
remainingMethods.add(direct);
}
}
- if (remainingMethods.size() < iface.directMethods.length) {
- iface.directMethods = remainingMethods.toArray(
- new DexEncodedMethod[remainingMethods.size()]);
+ if (remainingMethods.size() < iface.directMethods().length) {
+ iface.setDirectMethods(remainingMethods.toArray(
+ new DexEncodedMethod[remainingMethods.size()]));
}
if (companionMethods.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 0a6ea7e..be6252a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -444,7 +444,7 @@
DexMethod implMethod = descriptor.implHandle.asMethod();
DexClass implMethodHolder = definitionFor(implMethod.holder);
- DexEncodedMethod[] directMethods = implMethodHolder.directMethods;
+ DexEncodedMethod[] directMethods = implMethodHolder.directMethods();
for (int i = 0; i < directMethods.length; i++) {
DexEncodedMethod encodedMethod = directMethods[i];
if (implMethod.match(encodedMethod)) {
@@ -491,8 +491,8 @@
DexEncodedMethod accessorEncodedMethod = new DexEncodedMethod(
callTarget, accessorFlags, DexAnnotationSet.empty(), DexAnnotationSetRefList.empty(),
new SynthesizedCode(new AccessorMethodSourceCode(LambdaClass.this)));
- accessorClass.directMethods = appendMethod(
- accessorClass.directMethods, accessorEncodedMethod);
+ accessorClass.setDirectMethods(appendMethod(
+ accessorClass.directMethods(), accessorEncodedMethod));
rewriter.converter.optimizeSynthesizedMethod(accessorEncodedMethod);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index af32cb7..f6f8f2c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -162,7 +162,7 @@
public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
for (DexProgramClass clazz : classes) {
// Search for a lambda deserialization method and remove it if found.
- DexEncodedMethod[] directMethods = clazz.directMethods;
+ DexEncodedMethod[] directMethods = clazz.directMethods();
if (directMethods != null) {
int methodCount = directMethods.length;
for (int i = 0; i < methodCount; i++) {
@@ -177,7 +177,7 @@
DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
System.arraycopy(directMethods, 0, newMethods, 0, i);
System.arraycopy(directMethods, i + 1, newMethods, i, methodCount - i - 1);
- clazz.directMethods = newMethods;
+ clazz.setDirectMethods(newMethods);
// We assume there is only one such method in the class.
break;
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 eab1dfa..78d8369 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
@@ -8,6 +8,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -96,12 +97,12 @@
private final AppInfo appInfo;
private final DexItemFactory dexItemFactory;
- private final Set<DexType> libraryClassesWithOptimizationInfo;
+ private final Set<DexMethod> libraryMethodsReturningReceiver;
- public CodeRewriter(AppInfo appInfo, Set<DexType> libraryClassesWithOptimizationInfo) {
+ public CodeRewriter(AppInfo appInfo, Set<DexMethod> libraryMethodsReturningReceiver) {
this.appInfo = appInfo;
this.dexItemFactory = appInfo.dexItemFactory;
- this.libraryClassesWithOptimizationInfo = libraryClassesWithOptimizationInfo;
+ this.libraryMethodsReturningReceiver = libraryMethodsReturningReceiver;
}
/**
@@ -517,37 +518,39 @@
// Replace result uses for methods where something is known about what is returned.
public void rewriteMoveResult(IRCode code) {
- if (!appInfo.hasSubtyping()) {
- return;
- }
+ AppInfoWithSubtyping appInfoWithSubtyping = appInfo.withSubtyping();
InstructionIterator iterator = code.instructionIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
if (invoke.outValue() != null && invoke.outValue().getLocalInfo() == null) {
- DexEncodedMethod target = invoke.computeSingleTarget(appInfo.withSubtyping());
- // We have a set of library classes with optimization information - consider those
- // as well.
- if ((target == null) &&
- libraryClassesWithOptimizationInfo.contains(invoke.getInvokedMethod().getHolder())) {
- target = appInfo.definitionFor(invoke.getInvokedMethod());
- }
- if (target != null) {
- DexMethod invokedMethod = target.method;
- // Check if the invoked method is known to return one of its arguments.
- DexEncodedMethod definition = appInfo.definitionFor(invokedMethod);
- if (definition != null && definition.getOptimizationInfo().returnsArgument()) {
- int argumentIndex = definition.getOptimizationInfo().getReturnedArgument();
- // Replace the out value of the invoke with the argument and ignore the out value.
- if (argumentIndex != -1 && checkArgumentType(invoke, target.method, argumentIndex)) {
- Value argument = invoke.arguments().get(argumentIndex);
- assert (invoke.outType() == argument.outType()) ||
- (invoke.outType() == MoveType.OBJECT
- && argument.outType() == MoveType.SINGLE
- && argument.getConstInstruction().asConstNumber().isZero());
- invoke.outValue().replaceUsers(argument);
- invoke.setOutValue(null);
+ boolean isLibraryMethodReturningReceiver =
+ libraryMethodsReturningReceiver.contains(invoke.getInvokedMethod());
+ if (isLibraryMethodReturningReceiver) {
+ if (checkArgumentType(invoke, invoke.getInvokedMethod(), 0)) {
+ invoke.outValue().replaceUsers(invoke.arguments().get(0));
+ invoke.setOutValue(null);
+ }
+ } else if (appInfoWithSubtyping != null) {
+ DexEncodedMethod target = invoke.computeSingleTarget(appInfoWithSubtyping);
+ if (target != null) {
+ DexMethod invokedMethod = target.method;
+ // Check if the invoked method is known to return one of its arguments.
+ DexEncodedMethod definition = appInfo.definitionFor(invokedMethod);
+ if (definition != null && definition.getOptimizationInfo().returnsArgument()) {
+ int argumentIndex = definition.getOptimizationInfo().getReturnedArgument();
+ // Replace the out value of the invoke with the argument and ignore the out value.
+ if (argumentIndex != -1 && checkArgumentType(invoke, target.method,
+ argumentIndex)) {
+ Value argument = invoke.arguments().get(argumentIndex);
+ assert (invoke.outType() == argument.outType()) ||
+ (invoke.outType() == MoveType.OBJECT
+ && argument.outType() == MoveType.SINGLE
+ && argument.getConstInstruction().asConstNumber().isZero());
+ invoke.outValue().replaceUsers(argument);
+ invoke.setOutValue(null);
+ }
}
}
}
@@ -619,12 +622,31 @@
}
}
+ // Check if the static put is a constant derived from the class holding the method.
+ // This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
+ private boolean isClassNameConstant(DexEncodedMethod method, StaticPut put) {
+ if (put.getField().type != dexItemFactory.stringType) {
+ return false;
+ }
+ if (put.inValue().definition != null && put.inValue().definition.isInvokeVirtual()) {
+ InvokeVirtual invoke = put.inValue().definition.asInvokeVirtual();
+ if ((invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName
+ || invoke.getInvokedMethod() == dexItemFactory.classMethods.getName)
+ && invoke.inValues().get(0).definition.isConstClass()
+ && invoke.inValues().get(0).definition.asConstClass().getValue()
+ == method.method.getHolder()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public void collectClassInitializerDefaults(DexEncodedMethod method, IRCode code) {
if (!method.isClassInitializer()) {
return;
}
- // Collect all static put which are dominated by the exit block, and not dominated by a
+ // Collect relevant static put which are dominated by the exit block, and not dominated by a
// static get.
// This does not check for instructions that can throw. However, as classes which throw in the
// class initializer are never visible to the program (see Java Virtual Machine Specification
@@ -644,14 +666,21 @@
if (current.isStaticPut()) {
StaticPut put = current.asStaticPut();
DexField field = put.getField();
- if (!(field.type.isPrimitiveType()
- || field.type == dexItemFactory.stringType)
- || field.getHolder() != method.method.getHolder()) {
- continue;
- }
- if (put.inValue().isConstant()) {
- // Collect put as a potential default value.
- puts.put(put.getField(), put);
+ if (field.getHolder() == method.method.getHolder()) {
+ if (put.inValue().isConstant()) {
+ if ((field.type.isClassType() || field.type.isArrayType())
+ && put.inValue().getConstInstruction().isConstNumber() &&
+ put.inValue().getConstInstruction().asConstNumber().isZero()) {
+ // Collect put of zero as a potential default value.
+ puts.put(put.getField(), put);
+ } else if (field.type.isPrimitiveType() || field.type == dexItemFactory.stringType) {
+ // Collect put as a potential default value.
+ puts.put(put.getField(), put);
+ }
+ } else if (isClassNameConstant(method, put)) {
+ // Collect put of class name constant as a potential default value.
+ puts.put(put.getField(), put);
+ }
}
}
if (current.isStaticGet()) {
@@ -670,12 +699,32 @@
DexField field = put.getField();
DexEncodedField encodedField = appInfo.definitionFor(field);
if (field.type == dexItemFactory.stringType) {
- if (put.inValue().getConstInstruction().isConstNumber()) {
- assert put.inValue().getConstInstruction().asConstNumber().isZero();
+ if (put.inValue().isConstant()) {
+ if (put.inValue().getConstInstruction().isConstNumber()) {
+ assert put.inValue().getConstInstruction().asConstNumber().isZero();
+ encodedField.staticValue = DexValueNull.NULL;
+ } else {
+ ConstString cnst = put.inValue().getConstInstruction().asConstString();
+ encodedField.staticValue = new DexValueString(cnst.getValue());
+ }
+ } else {
+ InvokeVirtual invoke = put.inValue().definition.asInvokeVirtual();
+ String name = method.method.getHolder().toSourceString();
+ if (invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName) {
+ String simpleName = name.substring(name.lastIndexOf('.') + 1);
+ encodedField.staticValue =
+ new DexValueString(dexItemFactory.createString(simpleName));
+ } else {
+ assert invoke.getInvokedMethod() == dexItemFactory.classMethods.getName;
+ encodedField.staticValue = new DexValueString(dexItemFactory.createString(name));
+ }
+ }
+ } else if (field.type.isClassType() || field.type.isArrayType()) {
+ if (put.inValue().getConstInstruction().isConstNumber()
+ && put.inValue().getConstInstruction().asConstNumber().isZero()) {
encodedField.staticValue = DexValueNull.NULL;
} else {
- ConstString cnst = put.inValue().getConstInstruction().asConstString();
- encodedField.staticValue = new DexValueString(cnst.getValue());
+ throw new Unreachable("Unexpected default value for field type " + field.type + ".");
}
} else {
ConstNumber cnst = put.inValue().getConstInstruction().asConstNumber();
@@ -696,22 +745,45 @@
} else if (field.type == dexItemFactory.charType) {
encodedField.staticValue = DexValueChar.create((char) cnst.getIntValue());
} else {
- throw new Unreachable("Unexpected field type.");
+ throw new Unreachable("Unexpected field type " + field.type + ".");
}
}
}
// Remove the static put instructions now replaced by static filed initial values.
+ List<Instruction> toRemove = new ArrayList<>();
for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
InstructionListIterator iterator = block.listIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
if (current.isStaticPut() && puts.values().contains(current.asStaticPut())) {
iterator.remove();
+ // Collect, for removal, the instruction that created the value for the static put,
+ // if all users are gone. This is done even if these instructions can throw as for
+ // the current patterns matched these exceptions are not detectable.
+ StaticPut put = current.asStaticPut();
+ if (put.inValue().uniqueUsers().size() == 0) {
+ if (put.inValue().isConstString()) {
+ toRemove.add(put.inValue().definition);
+ } else if (put.inValue().definition.isInvokeVirtual()) {
+ toRemove.add(put.inValue().definition);
+ }
+ }
}
}
}
+ // Remove the instructions collected for removal.
+ if (toRemove.size() > 0) {
+ for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+ InstructionListIterator iterator = block.listIterator();
+ while (iterator.hasNext()) {
+ if (toRemove.contains(iterator.next())) {
+ iterator.remove();
+ }
+ }
+ }
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 31c7f1f..300f18e 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -60,7 +60,7 @@
.collect(Collectors.toSet());
for (DexType type : classes) {
DexClass clazz = appInfo.definitionFor(type);
- clazz.virtualMethods = removeMethods(clazz.virtualMethods, unneededVisibilityBridges);
+ clazz.setVirtualMethods(removeMethods(clazz.virtualMethods(), unneededVisibilityBridges));
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index 2850790..3fd5470 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -35,7 +35,7 @@
DexClass holder = appInfo.definitionFor(type);
scope = new ScopedDexItemSet(scope);
if (holder != null) {
- holder.virtualMethods = processMethods(holder.virtualMethods);
+ holder.setVirtualMethods(processMethods(holder.virtualMethods()));
}
type.forAllExtendsSubtypes(this::processClass);
scope = scope.getParent();
@@ -74,10 +74,6 @@
private final ScopedDexItemSet parent;
private final Set<Wrapper<DexMethod>> items = new HashSet<>();
- private ScopedDexItemSet() {
- this(null);
- }
-
private ScopedDexItemSet(ScopedDexItemSet parent) {
this.parent = parent;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index d3306d9..c04b29a 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -257,20 +257,20 @@
? DexTypeList.empty()
: new DexTypeList(interfaces.toArray(new DexType[interfaces.size()]));
// Step 2: replace fields and methods.
- target.directMethods = mergedDirectMethods
- .toArray(new DexEncodedMethod[mergedDirectMethods.size()]);
- target.virtualMethods = mergedVirtualMethods
- .toArray(new DexEncodedMethod[mergedVirtualMethods.size()]);
- target.staticFields = mergedStaticFields
- .toArray(new DexEncodedField[mergedStaticFields.size()]);
- target.instanceFields = mergedInstanceFields
- .toArray(new DexEncodedField[mergedInstanceFields.size()]);
+ target.setDirectMethods(mergedDirectMethods
+ .toArray(new DexEncodedMethod[mergedDirectMethods.size()]));
+ target.setVirtualMethods(mergedVirtualMethods
+ .toArray(new DexEncodedMethod[mergedVirtualMethods.size()]));
+ target.setStaticFields(mergedStaticFields
+ .toArray(new DexEncodedField[mergedStaticFields.size()]));
+ target.setInstanceFields(mergedInstanceFields
+ .toArray(new DexEncodedField[mergedInstanceFields.size()]));
// Step 3: Unlink old class to ease tree shaking.
source.superType = application.dexItemFactory.objectType;
- source.directMethods = null;
- source.virtualMethods = null;
- source.instanceFields = null;
- source.staticFields = null;
+ source.setDirectMethods(null);
+ source.setVirtualMethods(null);
+ source.setInstanceFields(null);
+ source.setStaticFields(null);
source.interfaces = DexTypeList.empty();
// Step 4: Record merging.
mergedClasses.put(source.type, target.type);
@@ -427,11 +427,11 @@
private GraphLense fixupTypeReferences(GraphLense graphLense) {
// Globally substitute merged class types in protos and holders.
for (DexProgramClass clazz : appInfo.classes()) {
- clazz.directMethods = substituteTypesIn(clazz.directMethods);
- clazz.virtualMethods = substituteTypesIn(clazz.virtualMethods);
- clazz.virtualMethods = removeDupes(clazz.virtualMethods);
- clazz.staticFields = substituteTypesIn(clazz.staticFields);
- clazz.instanceFields = substituteTypesIn(clazz.instanceFields);
+ clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
+ clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
+ clazz.setVirtualMethods(removeDupes(clazz.virtualMethods()));
+ clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
+ clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
}
// Record type renamings so instanceof and checkcast checks are also fixed.
for (DexType type : mergedClasses.keySet()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 58646a8..64294bd 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -84,10 +84,10 @@
// The class is used and must be kept. Remove the unused fields and methods from
// the class.
usagePrinter.visiting(clazz);
- clazz.directMethods = reachableMethods(clazz.directMethods(), clazz);
- clazz.virtualMethods = reachableMethods(clazz.virtualMethods(), clazz);
- clazz.instanceFields = reachableFields(clazz.instanceFields());
- clazz.staticFields = reachableFields(clazz.staticFields());
+ clazz.setDirectMethods(reachableMethods(clazz.directMethods(), clazz));
+ clazz.setVirtualMethods(reachableMethods(clazz.virtualMethods(), clazz));
+ clazz.setInstanceFields(reachableFields(clazz.instanceFields()));
+ clazz.setStaticFields(reachableFields(clazz.staticFields()));
usagePrinter.visited();
}
}
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 063d5fc..beba10e 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -4656,14 +4656,6 @@
// 1) t04
// java.lang.AssertionError
- .put("lang.reflect.Field.getLjava_lang_Object.Field_get_A04", match(R8_AFTER_D8_COMPILER))
- // 1) t02
- // java.lang.AssertionError: expected:<9223372036854775807> but was:<72057594037927935>
-
- .put("lang.reflect.Field.getLongLjava_lang_Object.Field_getLong_A04", match(R8_AFTER_D8_COMPILER))
- // 1)
- // java.lang.AssertionError: expected:<9223372036854775807> but was:<72057594037927935>
-
.build(); // end of failuresToTriage
public static final Multimap<String, TestCondition> flakyWithArt =
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java
deleted file mode 100644
index 085b069..0000000
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.rewrite.staticvalues;
-
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.smali.SmaliTestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.android.tools.r8.utils.InternalOptions;
-import org.junit.Test;
-
-public class StaticValues extends SmaliTestBase {
-
- @Test
- public void testAllTypes() {
- SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
- builder.addStaticField("booleanField", "Z");
- builder.addStaticField("byteField", "B");
- builder.addStaticField("shortField", "S");
- builder.addStaticField("intField", "I");
- builder.addStaticField("longField", "J");
- builder.addStaticField("floatField", "F");
- builder.addStaticField("doubleField", "D");
- builder.addStaticField("charField", "C");
- builder.addStaticField("stringField", "Ljava/lang/String;");
-
- builder.addStaticInitializer(
- 2,
- "const v0, 1",
- "sput-byte v0, LTest;->booleanField:Z",
- "sput-byte v0, LTest;->byteField:B",
- "const v0, 2",
- "sput-short v0, LTest;->shortField:S",
- "const v0, 3",
- "sput v0, LTest;->intField:I",
- "const-wide v0, 4",
- "sput-wide v0, LTest;->longField:J",
- "const v0, 0x40a00000", // 5.0.
- "sput v0, LTest;->floatField:F",
- "const-wide v0, 0x4018000000000000L", // 6.0.
- "sput-wide v0, LTest;->doubleField:D",
- "const v0, 0x37", // ASCII 7.
- "sput-char v0, LTest;->charField:C",
- "const-string v0, \"8\"",
- "sput-object v0, LTest;->stringField:Ljava/lang/String;",
- "return-void"
- );
- builder.addMainMethod(
- 3,
- "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
- "sget-boolean v1, LTest;->booleanField:Z",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Z)V",
- "sget-byte v1, LTest;->byteField:B",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
- "sget-short v1, LTest;->shortField:S",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
- "sget v1, LTest;->intField:I",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
- "sget-wide v1, LTest;->longField:J",
- "invoke-virtual { v0, v1, v2 }, Ljava/io/PrintStream;->println(J)V",
- "sget v1, LTest;->floatField:F",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(F)V",
- "sget-wide v1, LTest;->doubleField:D",
- "invoke-virtual { v0, v1, v2 }, Ljava/io/PrintStream;->println(D)V",
- "sget-char v1, LTest;->charField:C",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(C)V",
- "sget-object v1, LTest;->stringField:Ljava/lang/String;",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
- "return-void"
- );
-
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
-
- DexInspector inspector = new DexInspector(processedApplication);
- MethodSubject clinit = inspector.clazz("Test").clinit();
- // The const-string and return-void instructions are left.
- assertEquals(2, clinit.getMethod().getCode().asDexCode().instructions.length);
-
- String result = runArt(processedApplication, options);
-
- assertEquals("true\n1\n2\n3\n4\n5.0\n6.0\n7\n8\n", result);
- }
-
- @Test
- public void getBeforePut() {
- SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
- builder.addStaticField("field1", "I", "1");
- builder.addStaticField("field2", "I", "2");
-
- builder.addStaticInitializer(
- 1,
- "sget v0, LTest;->field1:I",
- "sput v0, LTest;->field2:I",
- "const v0, 0",
- "sput v0, LTest;->field1:I",
- "return-void"
- );
- builder.addMainMethod(
- 2,
- "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
- "sget v1, LTest;->field1:I",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
- "sget v1, LTest;->field2:I",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
- "return-void"
- );
-
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
-
- DexInspector inspector = new DexInspector(processedApplication);
- MethodSubject clinit = inspector.clazz("Test").clinit();
- // Nothing changed in the class initializer.
- assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
-
- String result = runArt(processedApplication, options);
-
- assertEquals("0\n1\n", result);
- }
-
- @Test
- public void nullString() {
- SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
- builder.addStaticField("stringField", "Ljava/lang/String;", "Hello");
-
- builder.addStaticInitializer(
- 1,
- "const v0, 0",
- "sput-object v0, LTest;->stringField:Ljava/lang/String;",
- "return-void"
- );
- builder.addMainMethod(
- 2,
- "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
- "sget-object v1, LTest;->stringField:Ljava/lang/String;",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
- "return-void"
- );
-
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
-
- DexInspector inspector = new DexInspector(processedApplication);
- MethodSubject clinit = inspector.clazz("Test").clinit();
- // The return-void instruction is left.
- assertEquals(1, clinit.getMethod().getCode().asDexCode().instructions.length);
-
- String result = runArt(processedApplication, options);
-
- assertEquals("null\n", result);
- }
-
- @Test
- public void fieldOnOtherClass() {
- SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
-
- builder.addStaticInitializer(
- 1,
- "const v0, 2",
- "sput v0, LOther;->field:I",
- "return-void"
- );
- builder.addMainMethod(
- 2,
- "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
- "sget v1, LOther;->field:I",
- "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
- "return-void"
- );
-
- builder.addClass("Other");
- builder.addStaticField("field", "I", "1");
-
- InternalOptions options = new InternalOptions();
- DexApplication originalApplication = buildApplication(builder, options);
- DexApplication processedApplication = processApplication(originalApplication, options);
-
- DexInspector inspector = new DexInspector(processedApplication);
- MethodSubject clinit = inspector.clazz("Test").clinit();
- // Nothing changed in the class initializer.
- assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
-
- String result = runArt(processedApplication, options);
-
- assertEquals("2\n", result);
- }
-
-}
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
new file mode 100644
index 0000000..195c3aa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -0,0 +1,359 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.rewrite.staticvalues;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.smali.SmaliTestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import org.junit.Test;
+
+public class StaticValuesTest extends SmaliTestBase {
+
+ @Test
+ public void testAllTypes() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("booleanField", "Z");
+ builder.addStaticField("byteField", "B");
+ builder.addStaticField("shortField", "S");
+ builder.addStaticField("intField", "I");
+ builder.addStaticField("longField", "J");
+ builder.addStaticField("floatField", "F");
+ builder.addStaticField("doubleField", "D");
+ builder.addStaticField("charField", "C");
+ builder.addStaticField("stringField", "Ljava/lang/String;");
+
+ builder.addStaticInitializer(
+ 2,
+ "const v0, 1",
+ "sput-byte v0, LTest;->booleanField:Z",
+ "sput-byte v0, LTest;->byteField:B",
+ "const v0, 2",
+ "sput-short v0, LTest;->shortField:S",
+ "const v0, 3",
+ "sput v0, LTest;->intField:I",
+ "const-wide v0, 4",
+ "sput-wide v0, LTest;->longField:J",
+ "const v0, 0x40a00000", // 5.0.
+ "sput v0, LTest;->floatField:F",
+ "const-wide v0, 0x4018000000000000L", // 6.0.
+ "sput-wide v0, LTest;->doubleField:D",
+ "const v0, 0x37", // ASCII 7.
+ "sput-char v0, LTest;->charField:C",
+ "const-string v0, \"8\"",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-boolean v1, LTest;->booleanField:Z",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Z)V",
+ "sget-byte v1, LTest;->byteField:B",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget-short v1, LTest;->shortField:S",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget v1, LTest;->intField:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget-wide v1, LTest;->longField:J",
+ "invoke-virtual { v0, v1, v2 }, Ljava/io/PrintStream;->println(J)V",
+ "sget v1, LTest;->floatField:F",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(F)V",
+ "sget-wide v1, LTest;->doubleField:D",
+ "invoke-virtual { v0, v1, v2 }, Ljava/io/PrintStream;->println(D)V",
+ "sget-char v1, LTest;->charField:C",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(C)V",
+ "sget-object v1, LTest;->stringField:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertFalse(inspector.clazz("Test").clinit().isPresent());
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("true\n1\n2\n3\n4\n5.0\n6.0\n7\n8\n", result);
+ }
+
+ @Test
+ public void getBeforePut() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("field1", "I", "1");
+ builder.addStaticField("field2", "I", "2");
+
+ builder.addStaticInitializer(
+ 1,
+ "sget v0, LTest;->field1:I",
+ "sput v0, LTest;->field2:I",
+ "const v0, 0",
+ "sput v0, LTest;->field1:I",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v1, LTest;->field1:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "sget v1, LTest;->field2:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ MethodSubject clinit = inspector.clazz("Test").clinit();
+ // Nothing changed in the class initializer.
+ assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("0\n1\n", result);
+ }
+
+ @Test
+ public void testNull() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("stringField", "Ljava/lang/String;", "Hello");
+ builder.addStaticField("arrayField", "[I");
+ builder.addStaticField("arrayField2", "[[[[I");
+
+ builder.addStaticInitializer(
+ 2,
+ "const v0, 0",
+ "sput-object v0, LTest;->stringField:Ljava/lang/String;",
+ "sput-object v0, LTest;->arrayField:[I",
+ "sput-object v0, LTest;->arrayField2:[[[[I",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-object v1, LTest;->stringField:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, LTest;->arrayField:[I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V",
+ "sget-object v1, LTest;->arrayField2:[[[[I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertFalse(inspector.clazz("Test").clinit().isPresent());
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("null\nnull\nnull\n", result);
+ }
+
+ @Test
+ public void testString() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticField("stringField1", "Ljava/lang/String;", "Hello");
+ builder.addStaticField("stringField2", "Ljava/lang/String;", "Hello");
+ builder.addStaticField("stringField3", "Ljava/lang/String;", "Hello");
+
+ builder.addStaticInitializer(
+ 2,
+ "const-string v0, \"Value1\"",
+ "sput-object v0, LTest;->stringField1:Ljava/lang/String;",
+ "const-string v0, \"Value2\"",
+ "sput-object v0, LTest;->stringField2:Ljava/lang/String;",
+ "sput-object v0, LTest;->stringField3:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-object v1, LTest;->stringField1:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, LTest;->stringField2:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, LTest;->stringField3:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertFalse(inspector.clazz("Test").clinit().isPresent());
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("Value1\nValue2\nValue2\n", result);
+ }
+
+ @Test
+ public void testInitializationToOwnClassName() {
+ String className = "org.example.Test";
+ SmaliBuilder builder = new SmaliBuilder(className);
+
+ builder.addStaticField("name1", "Ljava/lang/String;");
+ builder.addStaticField("name2", "Ljava/lang/String;");
+ builder.addStaticField("name3", "Ljava/lang/String;");
+ builder.addStaticField("simpleName1", "Ljava/lang/String;");
+ builder.addStaticField("simpleName2", "Ljava/lang/String;");
+ builder.addStaticField("simpleName3", "Ljava/lang/String;");
+
+ String descriptor = builder.getCurrentClassDescriptor();
+
+ builder.addStaticInitializer(
+ 3,
+ "const-class v0, " + descriptor,
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getSimpleName()Ljava/lang/String;",
+ "move-result-object v0",
+ "sput-object v0, " + descriptor + "->simpleName1:Ljava/lang/String;",
+ "const-class v0, " + descriptor,
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getName()Ljava/lang/String;",
+ "move-result-object v0",
+ "sput-object v0, " + descriptor + "->name1:Ljava/lang/String;",
+ "const-class v0, " + descriptor,
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getSimpleName()Ljava/lang/String;",
+ "move-result-object v1",
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getName()Ljava/lang/String;",
+ "move-result-object v2",
+ "sput-object v1, " + descriptor + "->simpleName2:Ljava/lang/String;",
+ "sput-object v1, " + descriptor + "->simpleName3:Ljava/lang/String;",
+ "sput-object v2, " + descriptor + "->name2:Ljava/lang/String;",
+ "sput-object v2, " + descriptor + "->name3:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-object v1, " + descriptor + "->simpleName1:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->name1:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->simpleName2:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->name2:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->simpleName3:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->name3:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertTrue(inspector.clazz(className).isPresent());
+ assertFalse(inspector.clazz(className).clinit().isPresent());
+
+ String result = runArt(processedApplication, options, className);
+
+ assertEquals(
+ "Test\n" + className + "\nTest\n" + className + "\nTest\n" + className + "\n", result);
+ }
+
+ @Test
+ public void testInitializationToOtherClassName() {
+ String className = "org.example.Test";
+ SmaliBuilder builder = new SmaliBuilder(className);
+
+ builder.addStaticField("simpleName", "Ljava/lang/String;");
+ builder.addStaticField("name", "Ljava/lang/String;");
+
+ String descriptor = builder.getCurrentClassDescriptor();
+
+ builder.addStaticInitializer(
+ 3,
+ "const-class v0, Lorg/example/Test2;",
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getSimpleName()Ljava/lang/String;",
+ "move-result-object v0",
+ "sput-object v0, " + descriptor + "->simpleName:Ljava/lang/String;",
+ "const-class v0, Lorg/example/Test2;",
+ "invoke-virtual { v0 }, Ljava/lang/Class;->getName()Ljava/lang/String;",
+ "move-result-object v0",
+ "sput-object v0, " + descriptor + "->name:Ljava/lang/String;",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 3,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget-object v1, " + descriptor + "->simpleName:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "sget-object v1, " + descriptor + "->name:Ljava/lang/String;",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/String;)V",
+ "return-void"
+ );
+
+ builder.addClass("org.example.Test2");
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ assertTrue(inspector.clazz(className).isPresent());
+ assertTrue(inspector.clazz(className).clinit().isPresent());
+
+ String result = runArt(processedApplication, options, className);
+
+ assertEquals("Test2\norg.example.Test2\n", result);
+ }
+
+ @Test
+ public void fieldOnOtherClass() {
+ SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
+
+ builder.addStaticInitializer(
+ 1,
+ "const v0, 2",
+ "sput v0, LOther;->field:I",
+ "return-void"
+ );
+ builder.addMainMethod(
+ 2,
+ "sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v1, LOther;->field:I",
+ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(I)V",
+ "return-void"
+ );
+
+ builder.addClass("Other");
+ builder.addStaticField("field", "I", "1");
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = processApplication(originalApplication, options);
+
+ DexInspector inspector = new DexInspector(processedApplication);
+ MethodSubject clinit = inspector.clazz("Test").clinit();
+ // Nothing changed in the class initializer.
+ assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
+
+ String result = runArt(processedApplication, options);
+
+ assertEquals("2\n", result);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 4cb6736..03df65c 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -126,9 +126,7 @@
private static void inspectShaking1(PrintUsageInspector inspector) {
assertTrue(inspector.clazz("shaking1.Unused").isPresent());
- assertTrue(inspector.clazz("shaking1.Used").isPresent());
- ClassSubject used = inspector.clazz("shaking1.Used").get();
- assertTrue(used.method("void", "<clinit>", ImmutableList.of()));
+ assertFalse(inspector.clazz("shaking1.Used").isPresent());
}
private static void inspectShaking2(PrintUsageInspector inspector) {
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 4c1fc69..51b6ab5 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -584,9 +584,12 @@
private static void checkSameStructure(FoundMethodSubject refMethod, ClassSubject clazz) {
MethodSignature signature = refMethod.getOriginalSignature();
- Assert.assertTrue("Missing Method: " + clazz.getDexClass().toSourceString() + "."
- + signature.toString(),
- clazz.method(signature).isPresent());
+ // Don't check for existence of class initializers, as the code optimization can remove them.
+ if (!refMethod.isClassInitializer()) {
+ Assert.assertTrue("Missing Method: " + clazz.getDexClass().toSourceString() + "."
+ + signature.toString(),
+ clazz.method(signature).isPresent());
+ }
}
private static void checkSameStructure(FoundFieldSubject refField, ClassSubject clazz) {
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
index 79ef0b9..1d256e0 100644
--- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
+++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -53,6 +53,9 @@
);
InternalOptions options = new InternalOptions();
+ // (b/64777953) Disable outliner optimization for this test because it increases time
+ // from 1 minute to 7 minutes.
+ options.outline.enabled = false;
DexApplication originalApplication = buildApplication(smaliBuilder, options);
DexApplication processedApplication = processApplication(originalApplication, options);
String result = runArt(processedApplication, options);
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 4656cd6..c726aad 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -828,13 +828,13 @@
DexInspector inspector = new DexInspector(processedApplication);
ClassSubject clazz = inspector.clazz(options.outline.className);
assertTrue(clazz.isPresent());
- assertEquals(3, clazz.getDexClass().directMethods.length);
+ assertEquals(3, clazz.getDexClass().directMethods().length);
// Collect the return types of the putlines for the body of method1 and method2.
List<DexType> r = new ArrayList<>();
- for (int i = 0; i < clazz.getDexClass().directMethods.length; i++) {
- if (clazz.getDexClass().directMethods[i].getCode().asDexCode().instructions[0]
+ for (int i = 0; i < clazz.getDexClass().directMethods().length; i++) {
+ if (clazz.getDexClass().directMethods()[i].getCode().asDexCode().instructions[0]
instanceof InvokeVirtual) {
- r.add(clazz.getDexClass().directMethods[i].method.proto.returnType);
+ r.add(clazz.getDexClass().directMethods()[i].method.proto.returnType);
}
}
assert r.size() == 2;
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index ab9bf96..4253395 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -518,12 +518,16 @@
}
public String runArt(DexApplication application, InternalOptions options) {
+ return runArt(application, options, DEFAULT_MAIN_CLASS_NAME);
+ }
+
+ public String runArt(DexApplication application, InternalOptions options, String mainClass) {
try {
AndroidApp app = writeDex(application, options);
Path out = temp.getRoot().toPath().resolve("run-art-input.zip");
// TODO(sgjesse): Pass in a unique temp directory for each run.
app.writeToZip(out, OutputMode.Indexed);
- return ToolHelper.runArtNoVerificationErrors(out.toString(), DEFAULT_MAIN_CLASS_NAME);
+ return ToolHelper.runArtNoVerificationErrors(out.toString(), mainClass);
} catch (IOException e) {
throw new RuntimeException(e);
}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 16c791d..0ad240c 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -521,6 +521,8 @@
public abstract boolean isBridge();
+ public abstract boolean isClassInitializer();
+
public abstract DexEncodedMethod getMethod();
public Iterator<InstructionSubject> iterateInstructions() {
@@ -578,6 +580,11 @@
}
@Override
+ public boolean isClassInitializer() {
+ return false;
+ }
+
+ @Override
public DexEncodedMethod getMethod() {
return null;
}
@@ -644,6 +651,11 @@
}
@Override
+ public boolean isClassInitializer() {
+ return dexMethod.isClassInitializer();
+ }
+
+ @Override
public DexEncodedMethod getMethod() {
return dexMethod;
}
diff --git a/tools/test_framework.py b/tools/test_framework.py
index 139b9c9..9e5f63f 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -69,6 +69,7 @@
tool_args = ['--dex', '--output=' + temp_dir, '--multi-dex',
'--min-sdk-version=' + MIN_SDK_VERSION]
+ xmx = None
if args.tool.startswith('goyt'):
tool_file = GOYT_EXE
tool_args = ['--num-threads=8'] + tool_args
@@ -76,12 +77,13 @@
tool_args.append('--no-locals')
elif args.tool == 'dx':
tool_file = DX_JAR
+ xmx = '-Xmx1600m'
else:
tool_file = D8_JAR
tool_args = ['--output', temp_dir, '--min-api', MIN_SDK_VERSION]
if args.tool == 'd8-release':
tool_args.append('--release')
-
+ xmx = '-Xmx600m'
cmd = []
@@ -91,7 +93,8 @@
cmd.extend(['tools/track_memory.sh', track_memory_file])
if tool_file.endswith('.jar'):
- cmd.extend(['java', '-Xmx600m', '-jar'])
+ assert xmx is not None
+ cmd.extend(['java', xmx, '-jar'])
cmd.extend([tool_file] + tool_args + [FRAMEWORK_JAR])