Merge "Eliminate moves of aliased values."
diff --git a/build.gradle b/build.gradle
index e2c21b6..722106c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,11 +6,15 @@
apply plugin: 'java'
apply plugin: 'idea'
-apply plugin: 'jacoco'
apply plugin: 'com.google.protobuf'
apply from: 'copyAdditionalJctfCommonFiles.gradle'
+
+if (project.hasProperty('with_code_coverage')) {
+ apply plugin: 'jacoco'
+}
+
repositories {
maven { url 'https://maven.google.com' }
mavenCentral()
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 4c8cdd5..cb1db15 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.0.1";
+ private static final String kVersion = "v0.1.0";
private static final int STATUS_ERROR = 1;
private D8() {}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index acbbe0a..bd51b5d 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.0.1";
+ private static final String kVersion = "v0.1.0";
private final Timing timing = new Timing("R8");
private final InternalOptions options;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index c94fc3c..1ba7755 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -15,7 +15,7 @@
public final DexField field;
public final DexAccessFlags accessFlags;
public DexAnnotationSet annotations;
- public final DexValue staticValue;
+ public DexValue staticValue;
public DexEncodedField(DexField field, DexAccessFlags accessFlags, DexAnnotationSet annotations,
DexValue staticValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index d53c577..1880668 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -48,6 +48,10 @@
return outValue;
}
+ public boolean getBooleanValue() {
+ return !isZero();
+ }
+
public int getIntValue() {
assert type == ConstType.INT || type == ConstType.INT_OR_FLOAT;
return (int) value;
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 9f28eea..c8a17dc2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -110,6 +110,39 @@
};
}
+ /**
+ * Returns an iterator over all dominator blocks of <code>dominated</code>.
+ *
+ * Iteration order is always the immediate dominator of the previously returned block. The
+ * iteration starts by returning <code>dominated</code>.
+ */
+ public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated) {
+ return () -> new Iterator<BasicBlock>() {
+ private BasicBlock current = dominated;
+
+ @Override
+ public boolean hasNext() {
+ return current != null;
+ }
+
+ @Override
+ public BasicBlock next() {
+ if (!hasNext()) {
+ return null;
+ } else {
+ BasicBlock result = current;
+ if (current.getNumber() == 0) {
+ current = null;
+ } else {
+ current = immediateDominator(current);
+ assert current != result;
+ }
+ return result;
+ }
+ }
+ };
+ }
+
public BasicBlock[] getSortedBlocks() {
return sorted;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 48c8eb0..dad58d8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -328,16 +327,57 @@
this.graph = graph;
}
- private void processInvoke(DexEncodedMethod source, Invoke.Type type, DexMethod method) {
+ private void addClassInitializerTarget(DexClass clazz) {
+ assert clazz != null;
+ if (clazz.hasClassInitializer() && !clazz.isLibraryClass()) {
+ DexEncodedMethod possibleTarget = clazz.getClassInitializer();
+ addTarget(possibleTarget);
+ }
+ }
+
+ private void addClassInitializerTarget(DexType type) {
+ if (type.isArrayType()) {
+ type = type.toBaseType(appInfo.dexItemFactory);
+ }
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz != null) {
+ addClassInitializerTarget(clazz);
+ }
+ }
+
+ private void addTarget(DexEncodedMethod target) {
+ Node callee = graph.ensureMethodNode(target);
+ graph.addCall(caller, callee);
+ }
+
+ private void addPossibleTarget(DexEncodedMethod possibleTarget) {
+ DexClass possibleTargetClass =
+ appInfo.definitionFor(possibleTarget.method.getHolder());
+ if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
+ addTarget(possibleTarget);
+ }
+ }
+
+ private void addPossibleTargets(
+ DexEncodedMethod definition, Set<DexEncodedMethod> possibleTargets) {
+ for (DexEncodedMethod possibleTarget : possibleTargets) {
+ if (possibleTarget != definition) {
+ addPossibleTarget(possibleTarget);
+ }
+ }
+ }
+
+ private void processInvoke(Type type, DexMethod method) {
+ DexEncodedMethod source = caller.method;
method = graphLense.lookupMethod(method, source);
DexEncodedMethod definition = appInfo.lookup(type, method);
if (definition != null) {
assert !source.accessFlags.isBridge() || definition != caller.method;
- DexType definitionHolder = definition.method.getHolder();
- assert definitionHolder.isClassType();
- if (!appInfo.definitionFor(definitionHolder).isLibraryClass()) {
- Node callee = graph.ensureMethodNode(definition);
- graph.addCall(caller, callee);
+ DexClass definitionHolder = appInfo.definitionFor(definition.method.getHolder());
+ assert definitionHolder != null;
+ if (!definitionHolder.isLibraryClass()) {
+ addClassInitializerTarget(definitionHolder);
+ addTarget(definition);
// For virtual and interface calls add all potential targets that could be called.
if (type == Type.VIRTUAL || type == Type.INTERFACE) {
Set<DexEncodedMethod> possibleTargets;
@@ -346,73 +386,74 @@
} else {
possibleTargets = appInfo.lookupVirtualTargets(definition.method);
}
- for (DexEncodedMethod possibleTarget : possibleTargets) {
- if (possibleTarget != definition) {
- DexClass possibleTargetClass =
- appInfo.definitionFor(possibleTarget.method.getHolder());
- if (possibleTargetClass != null && !possibleTargetClass.isLibraryClass()) {
- callee = graph.ensureMethodNode(possibleTarget);
- graph.addCall(caller, callee);
- }
- }
- }
+ addPossibleTargets(definition, possibleTargets);
}
}
}
}
+ private void processFieldAccess(DexField field) {
+ // Any field access implicitly calls the class initializer.
+ addClassInitializerTarget(field.getHolder());
+ }
+
@Override
public boolean registerInvokeVirtual(DexMethod method) {
- processInvoke(caller.method, Type.VIRTUAL, method);
+ processInvoke(Type.VIRTUAL, method);
return false;
}
@Override
public boolean registerInvokeDirect(DexMethod method) {
- processInvoke(caller.method, Type.DIRECT, method);
+ processInvoke(Type.DIRECT, method);
return false;
}
@Override
public boolean registerInvokeStatic(DexMethod method) {
- processInvoke(caller.method, Type.STATIC, method);
+ processInvoke(Type.STATIC, method);
return false;
}
@Override
public boolean registerInvokeInterface(DexMethod method) {
- processInvoke(caller.method, Type.INTERFACE, method);
+ processInvoke(Type.INTERFACE, method);
return false;
}
@Override
public boolean registerInvokeSuper(DexMethod method) {
- processInvoke(caller.method, Type.SUPER, method);
+ processInvoke(Type.SUPER, method);
return false;
}
@Override
public boolean registerInstanceFieldWrite(DexField field) {
+ processFieldAccess(field);
return false;
}
@Override
public boolean registerInstanceFieldRead(DexField field) {
+ processFieldAccess(field);
return false;
}
@Override
public boolean registerNewInstance(DexType type) {
+ addClassInitializerTarget(type);
return false;
}
@Override
public boolean registerStaticFieldRead(DexField field) {
+ processFieldAccess(field);
return false;
}
@Override
public boolean registerStaticFieldWrite(DexField field) {
+ processFieldAccess(field);
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 23b6b70..3cd40f7 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
@@ -463,6 +463,7 @@
codeRewriter.foldConstants(code);
codeRewriter.rewriteSwitch(code);
codeRewriter.simplifyIf(code);
+ codeRewriter.collectClassInitializerDefaults(method, code);
if (Log.ENABLED) {
Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
method.toSourceString(), code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
index 27cf89a..9f833f0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -5,19 +5,25 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.google.common.base.Equivalence;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
class BasicBlockInstructionsEquivalence extends Equivalence<BasicBlock> {
-
+ private static final int UNKNOW_HASH = -1;
private static final int MAX_HASH_INSTRUCTIONS = 5;
private final RegisterAllocator allocator;
+ private final int[] hashes;
- BasicBlockInstructionsEquivalence(RegisterAllocator allocator) {
+ BasicBlockInstructionsEquivalence(IRCode code, RegisterAllocator allocator) {
this.allocator = allocator;
+ hashes = new int[code.getHighestBlockNumber() + 1];
+ Arrays.fill(hashes, UNKNOW_HASH);
}
private boolean hasIdenticalInstructions(BasicBlock first, BasicBlock second) {
@@ -60,8 +66,23 @@
return hasIdenticalInstructions(a, b);
}
+ void clearComputedHash(BasicBlock basicBlock) {
+ hashes[basicBlock.getNumber()] = UNKNOW_HASH;
+ }
+
@Override
protected int doHash(BasicBlock basicBlock) {
+ int hash = hashes[basicBlock.getNumber()];
+ if (hash != UNKNOW_HASH) {
+ assert hash == computeHash(basicBlock);
+ return hash;
+ }
+ hash = computeHash(basicBlock);
+ hashes[basicBlock.getNumber()] = hash;
+ return hash;
+ }
+
+ private int computeHash(BasicBlock basicBlock) {
List<Instruction> instructions = basicBlock.getInstructions();
int hash = instructions.size();
for (int i = 0; i < instructions.size() && i < MAX_HASH_INSTRUCTIONS; i++) {
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 48a64b8..eab1dfa 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
@@ -6,13 +6,26 @@
import com.android.tools.r8.dex.Constants;
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.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue.DexValueBoolean;
+import com.android.tools.r8.graph.DexValue.DexValueByte;
+import com.android.tools.r8.graph.DexValue.DexValueChar;
+import com.android.tools.r8.graph.DexValue.DexValueDouble;
+import com.android.tools.r8.graph.DexValue.DexValueFloat;
+import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueLong;
+import com.android.tools.r8.graph.DexValue.DexValueNull;
+import com.android.tools.r8.graph.DexValue.DexValueShort;
+import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.Binop;
@@ -55,6 +68,7 @@
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -605,6 +619,102 @@
}
}
+ 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
+ // 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
+ // section 5.5, https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5), this
+ // does not matter (except maybe for removal of const-string instructions, but that is
+ // acceptable).
+ DominatorTree dominatorTree = new DominatorTree(code);
+ BasicBlock exit = code.getNormalExitBlock();
+ if (exit == null) {
+ return;
+ }
+ Map<DexField, StaticPut> puts = Maps.newIdentityHashMap();
+ for (BasicBlock block : dominatorTree.dominatorBlocks(exit)) {
+ InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
+ while (iterator.hasPrevious()) {
+ Instruction current = iterator.previous();
+ 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 (current.isStaticGet()) {
+ // If a static field is read, any collected potential default value cannot be a
+ // default value.
+ if (puts.containsKey(current.asStaticGet().getField())) {
+ puts.remove(current.asStaticGet().getField());
+ }
+ }
+ }
+ }
+
+ if (!puts.isEmpty()) {
+ // Set initial values for static fields from the definitive static put instructions collected.
+ for (StaticPut put : puts.values()) {
+ 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();
+ encodedField.staticValue = DexValueNull.NULL;
+ } else {
+ ConstString cnst = put.inValue().getConstInstruction().asConstString();
+ encodedField.staticValue = new DexValueString(cnst.getValue());
+ }
+ } else {
+ ConstNumber cnst = put.inValue().getConstInstruction().asConstNumber();
+ if (field.type == dexItemFactory.booleanType) {
+ encodedField.staticValue = DexValueBoolean.create(cnst.getBooleanValue());
+ } else if (field.type == dexItemFactory.byteType) {
+ encodedField.staticValue = DexValueByte.create((byte) cnst.getIntValue());
+ } else if (field.type == dexItemFactory.shortType) {
+ encodedField.staticValue = DexValueShort.create((short) cnst.getIntValue());
+ } else if (field.type == dexItemFactory.intType) {
+ encodedField.staticValue = DexValueInt.create(cnst.getIntValue());
+ } else if (field.type == dexItemFactory.longType) {
+ encodedField.staticValue = DexValueLong.create(cnst.getLongValue());
+ } else if (field.type == dexItemFactory.floatType) {
+ encodedField.staticValue = DexValueFloat.create(cnst.getFloatValue());
+ } else if (field.type == dexItemFactory.doubleType) {
+ encodedField.staticValue = DexValueDouble.create(cnst.getDoubleValue());
+ } else if (field.type == dexItemFactory.charType) {
+ encodedField.staticValue = DexValueChar.create((char) cnst.getIntValue());
+ } else {
+ throw new Unreachable("Unexpected field type.");
+ }
+ }
+ }
+
+ // Remove the static put instructions now replaced by static filed initial values.
+ 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();
+ }
+ }
+ }
+
+ }
+ }
+
/**
* Due to inlining, we might see chains of casts on subtypes. It suffices to cast to the lowest
* subtype, as that will fail if a cast on a supertype would have failed.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index 4ad05cc..a7d6e8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -173,7 +173,7 @@
*/
private static void removeIdenticalPredecessorBlocks(IRCode code, RegisterAllocator allocator) {
BasicBlockInstructionsEquivalence equivalence =
- new BasicBlockInstructionsEquivalence(allocator);
+ new BasicBlockInstructionsEquivalence(code, allocator);
// Locate one block at a time that has identical predecessors. Rewrite those predecessors and
// then start over. Restarting when one blocks predecessors have been rewritten simplifies
// the rewriting and reduces the size of the data structures.
@@ -194,6 +194,7 @@
BasicBlock otherPred = block.getPredecessors().get(otherPredIndex);
pred.clearCatchHandlers();
pred.getInstructions().clear();
+ equivalence.clearComputedHash(pred);
for (BasicBlock succ : pred.getSuccessors()) {
succ.removePredecessor(pred);
}
diff --git a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
index 8903525..d6848a4 100644
--- a/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/EncodedValueUtils.java
@@ -13,10 +13,11 @@
long result = 0;
int shift = 0;
for (int i = 1; i < numberOfBytes; i++) {
- result |= ((long) ((file.get() & 0xFF))) << shift;
+ result |= ((long) (file.get() & 0xFF)) << shift;
shift += 8;
}
- return result | (file.get() << shift);
+ // Let the last byte sign-extend into any remaining bytes.
+ return result | (((long) file.get()) << shift);
}
// Inspired by com.android.dex.EncodedValueCodec
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index ea81b10..8712b7a 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -109,7 +109,7 @@
return join(collection, separator, BraceType.NONE);
}
- public static <T> String join(String separator, T... strings) {
+ public static String join(String separator, String... strings) {
return join(Arrays.asList(strings), separator, BraceType.NONE);
}
@@ -124,15 +124,15 @@
return builder.toString();
}
- public static <T> String lines(T... lines) {
+ public static String lines(String... lines) {
StringBuilder builder = new StringBuilder();
- for (T line : lines) {
+ for (String line : lines) {
builder.append(line).append(LINE_SEPARATOR);
}
return builder.toString();
}
- public static <T> String joinLines(T... lines) {
+ public static String joinLines(String... lines) {
return join(LINE_SEPARATOR, lines);
}
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
new file mode 100644
index 0000000..085b069
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValues.java
@@ -0,0 +1,198 @@
+// 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/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 03df65c..4cb6736 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -126,7 +126,9 @@
private static void inspectShaking1(PrintUsageInspector inspector) {
assertTrue(inspector.clazz("shaking1.Unused").isPresent());
- assertFalse(inspector.clazz("shaking1.Used").isPresent());
+ assertTrue(inspector.clazz("shaking1.Used").isPresent());
+ ClassSubject used = inspector.clazz("shaking1.Used").get();
+ assertTrue(used.method("void", "<clinit>", ImmutableList.of()));
}
private static void inspectShaking2(PrintUsageInspector inspector) {
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 f070130..ab9bf96 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -71,6 +71,10 @@
this.returnType = returnType;
this.parameterTypes = parameterTypes;
}
+
+ public static MethodSignature staticInitializer(String clazz) {
+ return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of());
+ }
}
public static class SmaliBuilder {
@@ -210,6 +214,29 @@
);
}
+ public void addStaticField(String name, String type, String defaultValue) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(".field static ");
+ builder.append(name);
+ builder.append(":");
+ builder.append(type);
+ if (defaultValue != null) {
+ builder.append(" = ");
+ if (type.equals("Ljava/lang/String;")) {
+ builder.append('"');
+ builder.append(defaultValue);
+ builder.append('"');
+ } else {
+ builder.append(defaultValue);
+ }
+ }
+ getSource(currentClassName).add(builder.toString());
+ }
+
+ public void addStaticField(String name, String type) {
+ addStaticField(name, type, null);
+ }
+
public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
int locals, String... instructions) {
StringBuilder builder = new StringBuilder();
@@ -222,8 +249,30 @@
public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters,
int locals, String code) {
+ return addStaticMethod("", returnType, name, parameters, locals, code);
+ }
+
+ public MethodSignature addStaticInitializer(int locals, String... instructions) {
+ StringBuilder builder = new StringBuilder();
+ for (String instruction : instructions) {
+ builder.append(instruction);
+ builder.append("\n");
+ }
+ return addStaticInitializer(locals, builder.toString());
+ }
+
+ public MethodSignature addStaticInitializer(int locals, String code) {
+ return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code);
+ }
+
+ private MethodSignature addStaticMethod(String flags, String returnType, String name,
+ List<String> parameters, int locals, String code) {
StringBuilder builder = new StringBuilder();
builder.append(".method public static ");
+ if (flags != null && flags.length() > 0) {
+ builder.append(flags);
+ builder.append(" ");
+ }
builder.append(name);
builder.append("(");
for (String parameter : parameters) {
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 61c3c14..16c791d 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -267,6 +267,10 @@
public abstract MethodSubject method(String returnType, String name, List<String> parameters);
+ public MethodSubject clinit() {
+ return method("void", "<clinit>", ImmutableList.of());
+ }
+
public MethodSubject method(MethodSignature signature) {
return method(signature.type, signature.name, ImmutableList.copyOf(signature.parameters));
}
diff --git a/tools/test.py b/tools/test.py
index 8e016c7..ccc36d8 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -58,6 +58,9 @@
result.add_option('--disable_assertions',
help="Disable assertions when running tests.",
default=False, action='store_true')
+ result.add_option('--with_code_coverage',
+ help="Enable code coverage with Jacoco.",
+ default=False, action='store_true')
return result.parse_args()
@@ -72,10 +75,12 @@
def Main():
(options, args) = ParseOptions()
- gradle_args = ['cleanTest', 'test']
if len(args) > 1:
print("test.py takes at most one argument, the pattern for tests to run")
return -1
+
+ gradle_args = []
+ # Set all necessary Gradle properties and options first.
if options.verbose:
gradle_args.append('-Pprint_test_stdout')
if options.no_internal:
@@ -96,9 +101,8 @@
gradle_args.append('-Pjctf_compile_only')
if options.disable_assertions:
gradle_args.append('-Pdisable_assertions')
- if len(args) > 0:
- gradle_args.append('--tests')
- gradle_args.append(args[0])
+ if options.with_code_coverage:
+ gradle_args.append('-Pwith_code_coverage')
if os.name == 'nt':
# temporary hack
gradle_args.append('-Pno_internal')
@@ -108,6 +112,19 @@
gradle_args.append('jctfCommonJar')
gradle_args.append('-x')
gradle_args.append('jctfTestsClasses')
+
+ # Add Gradle tasks
+ gradle_args.append('cleanTest')
+ gradle_args.append('test')
+ if len(args) > 0:
+ # Test filtering. Must always follow the 'test' task.
+ gradle_args.append('--tests')
+ gradle_args.append(args[0])
+ if options.with_code_coverage:
+ # Create Jacoco report after tests.
+ gradle_args.append('jacocoTestReport')
+
+ # 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:
return_code = gradle.RunGradle(gradle_args + ['-Pdex_vm=%s' % art_vm],
diff --git a/tools/test_framework.py b/tools/test_framework.py
index b6f5a19..32f0bc7 100755
--- a/tools/test_framework.py
+++ b/tools/test_framework.py
@@ -43,7 +43,7 @@
' third_party/framework/framework*.jar.'
' Report Golem-compatible CodeSize and RunTimeRaw values.')
parser.add_argument('--tool',
- choices = ['dx', 'd8', 'd8-release', 'goyt'],
+ choices = ['dx', 'd8', 'd8-release', 'goyt', 'goyt-release'],
required = True,
help = 'Compiler tool to use.')
parser.add_argument('--name',
@@ -65,13 +65,15 @@
with utils.TempDir() as temp_dir:
- if args.tool in ['dx', 'goyt']:
+ if args.tool in ['dx', 'goyt', 'goyt-release']:
tool_args = ['--dex', '--output=' + temp_dir, '--multi-dex',
'--min-sdk-version=' + MIN_SDK_VERSION]
- if args.tool == 'goyt':
+ if args.tool.startswith('goyt'):
tool_file = GOYT_EXE
tool_args = ['--num-threads=8'] + tool_args
+ if args.tool == 'goyt-release':
+ tool_args.append('--no-locals')
elif args.tool == 'dx':
tool_file = DX_JAR
else: